Skip to main content

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}