scl_lib/api_objects/
controller.rs

1// SPDX-License-Identifier: EUPL-1.2
2use crate::api_objects::{Error, MetaData, SclName, SclObject, TransitionError};
3use serde::{Deserialize, Serialize};
4
5#[cfg(feature = "openapi")]
6use aide::OperationIo;
7#[cfg(feature = "openapi")]
8use schemars::JsonSchema;
9
10#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
11#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
12#[cfg_attr(feature = "openapi", aide(output))]
13#[serde(rename_all = "camelCase")]
14pub struct Controller {
15    #[serde(flatten)]
16    pub metadata: MetaData,
17    pub kind: ControllerKind,
18    /// Latest status of the controller. Does not need to be specified by the user when
19    /// creating (POST), as it is initialized with the default values.
20    #[serde(default)]
21    pub status: ControllerStatus,
22}
23
24impl Controller {
25    pub fn new(name: SclName, kind: ControllerKind) -> Self {
26        Controller {
27            metadata: MetaData::new(name),
28            kind,
29            status: ControllerStatus::default(),
30        }
31    }
32
33    pub fn update_last_interaction(&mut self) {
34        self.status.last_interaction = std::time::SystemTime::now()
35            .duration_since(std::time::UNIX_EPOCH)
36            .unwrap()
37            .as_secs()
38            .to_string();
39    }
40}
41
42impl SclObject for Controller {
43    /// Prefix for all [Controller]s (`"/controllers"`).
44    const PREFIX: &'static str = "/controllers";
45
46    fn name(&self) -> &SclName {
47        &self.metadata.name
48    }
49
50    /// Overrides the default implementation of the [SclObject] trait to ignore the `sc` argument
51    /// when constructing API endpoints.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use scl_lib::api_objects::{Controller, SclObject};
57    ///
58    /// assert_eq!(Controller::api_endpoint(None, "ctrl-01"), "/controllers/ctrl-01");
59    /// assert_eq!(Controller::api_endpoint(None, "ctrl-01"), Controller::api_endpoint("ctrl-01", "ctrl-01"));
60    /// assert_eq!(Controller::api_endpoint("ctrl-01", None), Controller::api_endpoint(None, None));
61    /// assert_eq!(Controller::api_endpoint(None, None), Controller::PREFIX);
62    /// ```
63    fn api_endpoint<'a>(
64        _sc: impl Into<Option<&'a str>>,
65        name: impl Into<Option<&'a str>>,
66    ) -> String {
67        if let Some(name) = name.into() {
68            format!("{}/{}", Self::PREFIX, name)
69        } else {
70            Self::PREFIX.into()
71        }
72    }
73
74    /// Overrides the default implementation of the [SclObject] trait to use either the `sc` or the
75    /// `name` argument for DB key construction.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use scl_lib::api_objects::{Controller, SclObject};
81    ///
82    /// assert_eq!(Controller::db_key(None, "ctrl-01"), "/controllers/ctrl-01");
83    /// assert_eq!(Controller::db_key(None, "ctrl-01"), Controller::db_key("ctrl-01", "ctrl-01"));
84    /// assert_eq!(Controller::db_key("ctrl-01", None), Controller::db_key(None, None));
85    /// assert_eq!(Controller::db_key(None, None), Controller::PREFIX);
86    /// ```
87    fn db_key<'a>(_sc: impl Into<Option<&'a str>>, name: impl Into<Option<&'a str>>) -> String {
88        if let Some(name) = name.into() {
89            format!("{}/{}", Self::PREFIX, name)
90        } else {
91            Self::PREFIX.into()
92        }
93    }
94
95    fn metadata(&self) -> &MetaData {
96        &self.metadata
97    }
98
99    fn metadata_mut(&mut self) -> &mut MetaData {
100        &mut self.metadata
101    }
102
103    fn referenced_db_keys(&self) -> Vec<String> {
104        vec![]
105    }
106
107    fn validate_fields_before_create(&self) -> Result<(), Error> {
108        self.metadata.validate_fields_before_create()
109    }
110
111    fn validate_fields_before_update(
112        current_db_state: &Self,
113        proposed_new_state: &Self,
114    ) -> Result<(), Error> {
115        MetaData::validate_fields_before_regular_update(
116            &current_db_state.metadata,
117            &proposed_new_state.metadata,
118        )?;
119
120        if current_db_state.kind != proposed_new_state.kind {
121            Err(TransitionError::Other("Field kind may not be changed!".to_string()).into())
122        } else {
123            Ok(())
124        }
125    }
126}
127
128#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
129#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
130#[cfg_attr(feature = "openapi", aide(output))]
131#[serde(rename_all = "camelCase")]
132pub enum ControllerKind {
133    SchedulerController,
134    StorageController,
135    NetworkController,
136    VmController,
137}
138
139#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
140#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
141#[cfg_attr(feature = "openapi", aide(output))]
142#[serde(rename_all = "camelCase")]
143pub struct ControllerStatus {
144    pub last_interaction: String, // TODO u64? Also Adjust transition validation if this makes sense.
145    alive: bool,
146}
147
148#[cfg(test)]
149mod test {
150    use super::Controller;
151    use crate::api_objects::test::{
152        detect_invalid_metadata_create_mutations, detect_invalid_metadata_update_mutations,
153        parse_json_dir,
154    };
155    use crate::api_objects::{ControllerKind, ControllerStatus, MetaData, SclName, SclObject};
156    use std::path::Path;
157
158    #[test]
159    fn parse_sample_json() {
160        parse_json_dir::<Controller>(Path::new("../test/sample_json/controllers"));
161    }
162
163    fn example_controller() -> Controller {
164        Controller {
165            metadata: MetaData::new(SclName::try_from("example").unwrap()),
166            kind: ControllerKind::SchedulerController,
167            status: ControllerStatus {
168                last_interaction: "Never".to_string(),
169                alive: false,
170            },
171        }
172    }
173
174    #[test]
175    fn validate_fields_before_update() {
176        let current = example_controller();
177        let mut proposed = current.clone();
178        assert!(Controller::validate_fields_before_update(&current, &proposed).is_ok());
179        proposed.kind = ControllerKind::NetworkController;
180        assert!(Controller::validate_fields_before_update(&current, &proposed).is_err());
181    }
182
183    #[test]
184    fn detect_invalid_metadata_mutations() {
185        let t = example_controller();
186        detect_invalid_metadata_create_mutations(&t);
187        detect_invalid_metadata_update_mutations(&t);
188    }
189}