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