Skip to main content

csm_rs/bss/
types.rs

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