1use crate::api_objects::{Error, MetaData, Result, SclName, SclObject, TransitionError, Url};
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 Node {
15 #[serde(flatten)]
16 pub metadata: MetaData,
17 pub resources: Resources,
19 pub node_api: Endpoint,
21 pub nic_api: Endpoint,
23 #[serde(default)]
26 pub status: NodeStatus,
27}
28
29impl SclObject for Node {
30 const PREFIX: &'static str = "/nodes";
32
33 fn name(&self) -> &SclName {
34 &self.metadata.name
35 }
36
37 fn api_endpoint<'a>(
51 _sc: impl Into<Option<&'a str>>,
52 name: impl Into<Option<&'a str>>,
53 ) -> String {
54 if let Some(name) = name.into() {
55 format!("{}/{}", Self::PREFIX, name)
56 } else {
57 Self::PREFIX.into()
58 }
59 }
60
61 fn db_key<'a>(_sc: impl Into<Option<&'a str>>, name: impl Into<Option<&'a str>>) -> String {
75 if let Some(name) = name.into() {
76 format!("{}/{}", Self::PREFIX, name)
77 } else {
78 Self::PREFIX.into()
79 }
80 }
81
82 fn metadata(&self) -> &MetaData {
83 &self.metadata
84 }
85
86 fn metadata_mut(&mut self) -> &mut MetaData {
87 &mut self.metadata
88 }
89
90 fn referenced_db_keys(&self) -> Vec<String> {
91 vec![]
92 }
93
94 fn validate_fields_before_create(&self) -> Result<()> {
95 self.metadata.validate_fields_before_create()?;
96
97 if !self.resources.all_resources_are_greater_than_zero() {
98 return Err(Error::IllegalInitialValue(
99 "All resources must be greater than 0!".to_string(),
100 ));
101 }
102
103 Ok(())
104 }
105
106 fn validate_fields_before_update(
107 current_db_state: &Self,
108 proposed_new_state: &Self,
109 ) -> Result<()> {
110 MetaData::validate_fields_before_regular_update(
111 ¤t_db_state.metadata,
112 &proposed_new_state.metadata,
113 )?;
114
115 if current_db_state.nic_api != proposed_new_state.nic_api
116 || current_db_state.node_api != proposed_new_state.node_api
117 || current_db_state.resources != proposed_new_state.resources
118 {
119 Err(TransitionError::Other("Only status field may be updated!".to_string()).into())
120 } else {
121 Ok(())
122 }
123 }
124}
125
126#[derive(Clone, Default, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
127#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
128#[cfg_attr(feature = "openapi", aide(output))]
129#[serde(rename_all = "camelCase")]
130pub struct Resources {
131 #[cfg_attr(feature = "openapi", validate(range(min = 1)))]
133 pub vcpu: u16,
134 #[cfg_attr(feature = "openapi", validate(range(min = 1)))]
136 #[serde(rename = "ramMiB")]
137 pub ram_mib: u32,
138}
139
140impl Resources {
141 pub fn all_resources_are_greater_than_zero(&self) -> bool {
142 self.vcpu > 0 && self.ram_mib > 0
143 }
144}
145
146#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
147#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
148#[cfg_attr(feature = "openapi", aide(output))]
149#[serde(rename_all = "camelCase")]
150pub struct Endpoint {
151 pub url: Url,
153
154 pub version: u8, }
157
158#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
159#[cfg_attr(feature = "openapi", derive(OperationIo, JsonSchema))]
160#[cfg_attr(feature = "openapi", aide(output))]
161#[serde(rename_all = "camelCase")]
162pub struct NodeStatus {
163 pub last_heartbeat: String, pub healthy: bool,
165}
166
167#[cfg(test)]
168mod test {
169 use super::Node;
170 use crate::api_objects::test::{
171 detect_invalid_metadata_create_mutations, detect_invalid_metadata_update_mutations,
172 detect_invalid_update_mutations, parse_json_dir,
173 };
174 use crate::api_objects::{Endpoint, MetaData, Resources, SclName, SclObject, Url};
175 use std::path::Path;
176
177 #[test]
178 fn parse_sample_json() {
179 parse_json_dir::<Node>(Path::new("../test/sample_json/nodes"));
180 }
181
182 fn example_node() -> Node {
183 let endpoint = Endpoint {
184 url: Url::try_from("ftp://example.com").unwrap(),
185 version: 1,
186 };
187
188 Node {
189 metadata: MetaData::new(SclName::try_from("example").unwrap()),
190 resources: Resources {
191 vcpu: 1,
192 ram_mib: 1,
193 },
194 node_api: endpoint.clone(),
195 nic_api: endpoint,
196 status: Default::default(),
197 }
198 }
199
200 #[test]
201 fn validate_fields_before_create() {
202 let mut node = example_node();
203 assert!(node.validate_fields_before_create().is_ok());
204 node.resources.vcpu = 0;
205 assert!(node.validate_fields_before_create().is_err());
206 node.resources.vcpu = 1;
207 node.resources.ram_mib = 0;
208 assert!(node.validate_fields_before_create().is_err());
209 }
210
211 #[test]
212 fn validate_fields_before_update() {
213 let current = example_node();
214 assert!(Node::validate_fields_before_update(¤t, ¤t).is_ok());
215
216 #[allow(clippy::type_complexity)]
217 let invalid_mutations: Vec<Box<dyn Fn(&mut Node)>> = vec![
218 Box::new(|t| t.resources.ram_mib = 999),
219 Box::new(|t| t.resources.vcpu = 999),
220 Box::new(|t| t.nic_api.url = Url::try_from("ftp://other.com").unwrap()),
221 Box::new(|t| t.nic_api.version = 123),
222 Box::new(|t| t.node_api.url = Url::try_from("ftp://other.com").unwrap()),
223 Box::new(|t| t.node_api.version = 123),
224 ];
225
226 detect_invalid_update_mutations(¤t, ¤t, invalid_mutations);
227 }
228
229 #[test]
230 fn detect_invalid_metadata_mutations() {
231 let t = example_node();
232 detect_invalid_metadata_create_mutations(&t);
233 detect_invalid_metadata_update_mutations(&t);
234 }
235
236 #[test]
237 fn test_all_resources_are_greater_than_zero() {
238 let mut r = Resources {
239 vcpu: 0,
240 ram_mib: 0,
241 };
242 assert!(!r.all_resources_are_greater_than_zero());
243 r.vcpu = 1;
244 assert!(!r.all_resources_are_greater_than_zero());
245 r.ram_mib = 1;
246 assert!(r.all_resources_are_greater_than_zero());
247 r.vcpu = 0;
248 assert!(!r.all_resources_are_greater_than_zero());
249 }
250}