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