ochami_rs/bss/types.rs
1use manta_backend_dispatcher::types::bss::BootParameters as FrontEndBootParameters;
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::error::Error;
9
10#[derive(Debug, Serialize, Deserialize, Default, Clone)]
11pub struct BootParameters {
12 #[serde(default)]
13 pub hosts: Vec<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub macs: Option<Vec<String>>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub nids: Option<Vec<u32>>,
18 #[serde(default)]
19 pub params: String, // FIXME: change type to HashMap<String, String> by using function
20 // bss::utils::convert_kernel_params_to_map AND create new method
21 // bss::BootParameters::num_kernel_params which returns the list of kernel parameters
22 #[serde(default)]
23 pub kernel: String,
24 #[serde(default)]
25 pub initrd: String,
26 #[serde(rename = "cloud-init")]
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub cloud_init: Option<Value>,
29}
30
31impl From<FrontEndBootParameters> for BootParameters {
32 fn from(value: FrontEndBootParameters) -> Self {
33 BootParameters {
34 hosts: value.hosts,
35 macs: value.macs,
36 nids: value.nids,
37 params: value.params,
38 kernel: value.kernel,
39 initrd: value.initrd,
40 cloud_init: value.cloud_init,
41 }
42 }
43}
44
45impl Into<FrontEndBootParameters> for BootParameters {
46 fn into(self) -> FrontEndBootParameters {
47 FrontEndBootParameters {
48 hosts: self.hosts,
49 macs: self.macs,
50 nids: self.nids,
51 params: self.params,
52 kernel: self.kernel,
53 initrd: self.initrd,
54 cloud_init: self.cloud_init,
55 }
56 }
57}
58
59impl BootParameters {
60 pub fn new(
61 hosts: Vec<&str>,
62 macs: Option<Vec<&str>>,
63 nids: Option<Vec<&str>>,
64 params: &str,
65 kernel: &str,
66 initrd: &str,
67 cloud_init_opt: Option<&str>,
68 ) -> Self {
69 BootParameters {
70 hosts: hosts.iter().map(|value| value.to_string()).collect(),
71 macs: macs
72 .map(|mac_vec| mac_vec.iter().map(|value| value.to_string()).collect()),
73 nids: nids.map(|nid_vec| {
74 nid_vec
75 .iter()
76 .map(|value| value.parse::<u32>().unwrap())
77 .collect()
78 }),
79 params: params.to_string(),
80 kernel: kernel.to_string(),
81 initrd: initrd.to_string(),
82 cloud_init: cloud_init_opt
83 .map(|cloud_init| serde_json::to_value(cloud_init).unwrap()),
84 }
85 }
86
87 /// Returns the image id. This function may fail since it assumes kernel path has the following
88 /// format `s3://xxxxx/<image id>/kernel`
89 pub fn get_boot_image(&self) -> String {
90 let mut path_elem_vec = self.kernel.split("/").skip(3);
91
92 let mut image_id: String =
93 path_elem_vec.next().unwrap_or_default().to_string();
94
95 for path_elem in path_elem_vec {
96 if !path_elem.eq("kernel") {
97 image_id = format!("{}/{}", image_id, path_elem);
98 } else {
99 break;
100 }
101 }
102
103 image_id
104 }
105
106 /// Update boot image in kernel boot parameters and also in kernel and initrd fields if
107 /// exists. Otherwise nothing is changed. This method updates both kernel params related to
108 /// NCN and also CN
109 /// Returns a boolean that indicates if kernel parameters have change:
110 /// - kernel parameter value changed
111 /// - number of kernel parameters have changed
112 pub fn update_boot_image(
113 &mut self,
114 new_image_id: &str,
115 ) -> Result<bool, Error> {
116 let mut changed = false;
117 // replace image id in 'root' kernel param
118
119 // convert kernel params to a hashmap
120 let mut params: HashMap<&str, &str> = self
121 .params
122 .split_whitespace()
123 .map(|kernel_param| {
124 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
125 })
126 .collect();
127
128 // NOTE: CN nodes have UIID image id in 'root' kernel parameter
129 // Get `root` kernel parameter and split it by '/'
130 let root_kernel_param_rslt = params.get("root");
131
132 let mut root_kernel_param: Vec<&str> = match root_kernel_param_rslt {
133 Some(root_kernel_param) => {
134 root_kernel_param.split("/").collect::<Vec<&str>>()
135 }
136 None => {
137 return Err(Error::Message(
138 "ERROR - The 'root' kernel param is missing from user input"
139 .to_string(),
140 ));
141 }
142 };
143
144 // Replace image id in root kernel param with new image id
145 for current_image_id in &mut root_kernel_param {
146 // Look for any substring between '/' that matches an UUID formant and take it as
147 // the image id
148 if uuid::Uuid::try_parse(current_image_id).is_ok() {
149 if *current_image_id != new_image_id {
150 changed = true;
151 }
152 // Replace image id in `root` kernel parameter with new value
153 *current_image_id = new_image_id;
154 }
155 }
156
157 // Create new `root` kernel param string
158 let new_root_kernel_param = root_kernel_param.join("/");
159
160 // Create new kernel parameters
161 params
162 .entry("root")
163 .and_modify(|root_param| *root_param = &new_root_kernel_param);
164
165 self.update_kernel_param("root", &new_root_kernel_param);
166
167 // replace image id in 'nmd_data' kernel param
168
169 // convert kernel params to a hashmap
170 let mut params: HashMap<&str, &str> = self
171 .params
172 .split_whitespace()
173 .map(|kernel_param| {
174 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
175 })
176 .collect();
177
178 // NOTE: NCN nodes have UUID image id in 'metal.server' kernel parameter
179 let mut metal_server_kernel_param: Vec<&str>;
180 if let Some(metal_server_data) = params.get("metal.server") {
181 metal_server_kernel_param = metal_server_data.split("/").collect();
182
183 for substring in &mut metal_server_kernel_param {
184 if uuid::Uuid::try_parse(substring).is_ok() {
185 *substring = new_image_id;
186 changed = true;
187 }
188 }
189
190 let new_metal_server_kernel_param = metal_server_kernel_param.join("/");
191
192 params
193 .entry("metal.server")
194 .and_modify(|metal_server_param| {
195 *metal_server_param = &new_metal_server_kernel_param
196 });
197
198 self.update_kernel_param("metal.server", &new_metal_server_kernel_param);
199
200 // convert kernel params to a hashmap
201 params = self
202 .params
203 .split_whitespace()
204 .map(|kernel_param| {
205 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
206 })
207 .collect();
208 } else {
209 };
210
211 // NOTE: NCN nodes have UUID image id 'nmd_data' kernel parameter
212 let mut nmd_kernel_param: Vec<&str>;
213 if let Some(nmd_data) = params.get("nmd_data") {
214 nmd_kernel_param = nmd_data.split("/").collect();
215
216 for substring in &mut nmd_kernel_param {
217 if uuid::Uuid::try_parse(substring).is_ok() {
218 *substring = new_image_id;
219 changed = true;
220 }
221 }
222
223 let new_nmd_kernel_param = nmd_kernel_param.join("/");
224
225 params
226 .entry("nmd_data")
227 .and_modify(|nmd_param| *nmd_param = &new_nmd_kernel_param);
228
229 self.update_kernel_param("nmd_data", &new_nmd_kernel_param);
230 } else {
231 };
232
233 /* self.params = self
234 .params
235 .split_whitespace()
236 .map(|kernel_param| {
237 if kernel_param.contains("metal.server=s3://boot-images/") {
238 // NCN node
239 let aux = kernel_param
240 .trim_start_matches("metal.server=s3://boot-images/")
241 .split_once('/')
242 .unwrap()
243 .1;
244
245 format!("metal.server=s3://boot-images/{}/{}", new_image_id, aux)
246 } else if kernel_param.contains("root=craycps-s3:s3://boot-images/") {
247 // CN node
248 format!("root=craycps-s3:s3://boot-images/{}/rootfs:etag:dvs:api-gw-service-nmn.local:300:hsn0,nmn0:0", new_image_id)
249 /* let aux = kernel_param
250 .trim_start_matches("root=craycps-s3:s3://boot-images/")
251 .split_once('/')
252 .unwrap_or_default() // NCN has root=live:LABEL=SQFSRAID
253 .1;
254
255 format!("root=craycps-s3:s3://boot-images/{}/{}", new_image_id, aux) */
256 } else if kernel_param.contains("nmd_data=") {
257 // CN node
258 format!("nmd_data=url=s3://boot-images/{}/rootfs,etag=etag", new_image_id)
259 /* let aux = kernel_param
260 .trim_start_matches("nmd_data=url=s3://boot-images/")
261 .split_once('/')
262 .unwrap()
263 .1;
264
265 format!("nmd_data=url=s3://boot-images/{}/{}", new_image_id, aux) */
266 } else {
267 kernel_param.to_string()
268 }
269 })
270 .collect::<Vec<String>>()
271 .join(" "); */
272
273 self.kernel = format!("s3://boot-images/{}/kernel", new_image_id);
274
275 self.initrd = format!("s3://boot-images/{}/initrd", new_image_id);
276
277 Ok(changed)
278 }
279
280 pub fn get_kernel_param_value(&self, key: &str) -> Option<String> {
281 let params: HashMap<&str, &str> = self
282 .params
283 .split_whitespace()
284 .map(|kernel_param| {
285 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
286 })
287 .collect();
288
289 params.get(key).map(|value| value.to_string())
290 }
291
292 pub fn get_num_kernel_params(&self) -> usize {
293 let params: HashMap<&str, &str> = self
294 .params
295 .split_whitespace()
296 .map(|kernel_param| {
297 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
298 })
299 .collect();
300
301 params.len()
302 }
303
304 /// Apply a str of kernel parameters:
305 /// - if kernel parameter already exists, then it will be updated
306 /// - if kernel parameters does not exists, then it will be deleted
307 /// - if kernel parameter does not exists, then it will be added
308 /// Returns true if kernel params have change
309 pub fn apply_kernel_params(&mut self, new_params: &str) -> bool {
310 let mut change = false;
311
312 let new_params: Vec<(&str, &str)> = new_params
313 .split_whitespace()
314 .map(|kernel_param| {
315 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
316 })
317 .collect();
318
319 let mut params: HashMap<&str, &str> = self
320 .params
321 .split_whitespace()
322 .map(|kernel_param| {
323 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
324 })
325 .collect();
326
327 for (new_key, new_value) in &new_params {
328 for (key, value) in params.iter_mut() {
329 if *key == *new_key {
330 log::debug!("key '{}' found", key);
331 if value != new_value {
332 log::info!("changing key {} from {} to {}", key, value, new_value);
333
334 *value = new_value;
335 change = true
336 } else {
337 log::debug!("key '{}' value does not change ({})", key, value);
338 }
339 }
340 }
341 }
342
343 if change == false {
344 log::debug!("No value change in kernel params. Checking is either new params have been added or removed");
345 if new_params.len() != params.len() {
346 log::info!("num kernel parameters have changed");
347 change = true;
348 }
349 }
350
351 self.params = new_params
352 .iter()
353 .map(|(key, value)| {
354 if !value.is_empty() {
355 format!("{key}={value}")
356 } else {
357 key.to_string()
358 }
359 })
360 .collect::<Vec<String>>()
361 .join(" ");
362
363 change
364 }
365
366 /// Add a kernel parameter:
367 /// - if kernel parameter does not exists, then it will be added,
368 /// otherwise nothing will change
369 /// Returns true if kernel params have change
370 pub fn add_kernel_param(&mut self, key: &str, new_value: &str) -> bool {
371 let mut changed = false;
372 let mut params: HashMap<&str, &str> = self
373 .params
374 .split_whitespace()
375 .map(|kernel_param| {
376 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
377 })
378 .collect();
379
380 // NOTE: do not use --> `params.entry(key).or_insert(new_value);` otherwise, I don't know
381 // how do we know if the key already exists or not
382 if params.contains_key(key) {
383 log::info!("key '{}' already exists, the new kernel parameter won't be added since it already exists", key);
384 return changed;
385 } else {
386 log::info!(
387 "key '{}' not found, adding new kernel param with value '{}'",
388 key,
389 new_value
390 );
391 params.insert(key, new_value);
392 changed = true
393 }
394
395 self.params = params
396 .iter()
397 .map(|(key, value)| {
398 if !value.is_empty() {
399 format!("{key}={value}")
400 } else {
401 key.to_string()
402 }
403 })
404 .collect::<Vec<String>>()
405 .join(" ");
406
407 changed
408 }
409
410 /* /// Apply kernel parameter. If kernel parameter already exists, then it will be updated,
411 /// otherwise it will be added
412 /// Input value expected the list of kernel parameters separated by space. eg: `console=ttyS0,115200 bad_page=panic crashkernel=512M hugepagelist=2m-2g intel_pstate=disable`
413 pub fn upsert_kernel_params(&mut self, new_params: &str) {
414 let new_params: Vec<(&str, &str)> = new_params
415 .split_whitespace()
416 .map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
417 .collect();
418
419 let mut params: HashMap<&str, &str> = self
420 .params
421 .split_whitespace()
422 .map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
423 .collect();
424
425 for (key, new_value) in new_params {
426 params
427 .entry(key)
428 .and_modify(|value| *value = new_value)
429 .or_insert(new_value);
430 }
431
432 self.params = params
433 .iter()
434 .map(|(key, value)| {
435 if !value.is_empty() {
436 format!("{key}={value}")
437 } else {
438 key.to_string()
439 }
440 })
441 .collect::<Vec<String>>()
442 .join(" ");
443 }
444
445 /// Apply kernel parameter. If kernel parameter already exists, then it will be updated,
446 /// otherwise it will be added
447 pub fn upsert_kernel_param(&mut self, key: &str, new_value: &str) {
448 let mut params: HashMap<&str, &str> = self
449 .params
450 .split_whitespace()
451 .map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
452 .collect();
453
454 params
455 .entry(key)
456 .and_modify(|value| *value = new_value)
457 .or_insert(new_value);
458
459 self.params = params
460 .iter()
461 .map(|(key, value)| {
462 if !value.is_empty() {
463 format!("{key}={value}")
464 } else {
465 key.to_string()
466 }
467 })
468 .collect::<Vec<String>>()
469 .join(" ");
470 } */
471
472 /* /// Update kernel parameter. If kernel parameter exists, then it will be updated with new
473 /// value. otherwise nothing will change
474 /// Input value expected the list of kernel parameters separated by space. eg: `console=ttyS0,115200 bad_page=panic crashkernel=512M hugepagelist=2m-2g intel_pstate=disable`
475 pub fn update_kernel_params(&mut self, new_params: &str) {
476 let new_params: Vec<(&str, &str)> = new_params
477 .split_whitespace()
478 .map(|kernel_param| kernel_param.split_once('=').unwrap())
479 .collect();
480
481 let mut params: HashMap<&str, &str> = self
482 .params
483 .split_whitespace()
484 .map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
485 .collect();
486
487 for (key, new_value) in new_params {
488 params
489 .entry(key)
490 .and_modify(|value| *value = new_value)
491 .or_insert(new_value);
492 }
493
494 self.params = params
495 .iter()
496 .map(|(key, value)| {
497 if !value.is_empty() {
498 format!("{key}={value}")
499 } else {
500 key.to_string()
501 }
502 })
503 .collect::<Vec<String>>()
504 .join(" ");
505 } */
506
507 /// Update kernel parameter. If kernel parameter exists, then it will be updated with new
508 /// Note: This function won't make any change to params without values (eg: 'quiet') since
509 /// they don't have values
510 /// value. otherwise nothing will change
511 pub fn update_kernel_param(&mut self, key: &str, new_value: &str) -> bool {
512 let mut changed = false;
513 // convert kernel params to a hashmap
514 let mut params: HashMap<&str, &str> = self
515 .params
516 .split_whitespace()
517 .map(|kernel_param| {
518 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
519 })
520 .collect();
521
522 // Update kernel param with new value
523 // params.entry(key).and_modify(|value| *value = new_value);
524 for (current_key, current_value) in params.iter_mut() {
525 if *current_key == key {
526 *current_value = new_value;
527 changed = true;
528 }
529 }
530
531 // Create new kernel params as a string
532 self.params = params
533 .iter()
534 .map(|(key, value)| {
535 if !value.is_empty() {
536 format!("{key}={value}")
537 } else {
538 key.to_string()
539 }
540 })
541 .collect::<Vec<String>>()
542 .join(" ");
543
544 changed
545 }
546
547 /* /// Delete kernel parameter. If kernel parameter exists, then it will be removed, otherwise
548 /// nothing will be changed
549 /// Input expected the list of kernel param keys separated by space. eg: `console bad_page crashkernel hugepagelist intel_pstate`
550 pub fn delete_kernel_params(&mut self, keys: &str) {
551 let keys: Vec<&str> = keys.split_whitespace().collect();
552
553 let mut params: HashMap<&str, &str> = self
554 .params
555 .split_whitespace()
556 .map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
557 .collect();
558
559 for key in keys {
560 params.remove(key);
561 }
562
563 self.params = params
564 .iter()
565 .map(|(key, value)| {
566 if !value.is_empty() {
567 format!("{key}={value}")
568 } else {
569 key.to_string()
570 }
571 })
572 .collect::<Vec<String>>()
573 .join(" ");
574 } */
575
576 /// Delete kernel parameter. If kernel parameter exists, then it will be removed, otherwise
577 /// nothing will be changed
578 pub fn delete_kernel_param(&mut self, key: &str) -> bool {
579 let mut params: HashMap<&str, &str> = self
580 .params
581 .split_whitespace()
582 .map(|kernel_param| {
583 kernel_param.split_once('=').unwrap_or((kernel_param, ""))
584 })
585 .collect();
586
587 let changed = params.remove(key).is_some();
588
589 self.params = params
590 .iter()
591 .map(|(key, value)| {
592 if !value.is_empty() {
593 format!("{key}={value}")
594 } else {
595 key.to_string()
596 }
597 })
598 .collect::<Vec<String>>()
599 .join(" ");
600
601 changed
602 }
603}