use manta_backend_dispatcher::error::Error;
use manta_backend_dispatcher::interfaces::bss::BootParametersTrait;
use manta_backend_dispatcher::interfaces::ims::ImsTrait;
use manta_backend_dispatcher::types::bss::BootParameters;
use manta_backend_dispatcher::types::ims::Image;
use std::collections::HashMap;
use crate::common;
use crate::common::app_context::InfraContext;
pub struct GetKernelParametersParams {
pub hsm_group: Option<String>,
pub nodes: Option<String>,
pub settings_hsm_group_name: Option<String>,
}
pub async fn get_kernel_parameters(
infra: &InfraContext<'_>,
token: &str,
params: &GetKernelParametersParams,
) -> Result<Vec<BootParameters>, Error> {
let xname_vec = common::node_ops::resolve_target_nodes(
infra.backend,
token,
params.nodes.as_deref(),
params.hsm_group.as_deref(),
params.settings_hsm_group_name.as_deref(),
)
.await?;
let boot_parameter_vec = infra.backend
.get_bootparameters(token, &xname_vec)
.await?;
Ok(boot_parameter_vec)
}
pub enum KernelParamOperation<'a> {
Add { params: &'a str, overwrite: bool },
Apply { params: &'a str },
Delete { params: &'a str },
}
impl<'a> KernelParamOperation<'a> {
pub fn verb(&self) -> &'static str {
match self {
Self::Add { .. } => "Add",
Self::Apply { .. } => "Apply",
Self::Delete { .. } => "Delete",
}
}
pub fn params(&self) -> &str {
match self {
Self::Add { params, .. }
| Self::Apply { params }
| Self::Delete { params } => params,
}
}
fn mutate(
&self,
boot_parameter: &mut BootParameters,
) -> bool {
match self {
Self::Add { params, overwrite } => {
boot_parameter.add_kernel_params(params, *overwrite)
}
Self::Apply { params } => {
boot_parameter.apply_kernel_params(params)
}
Self::Delete { params } => {
boot_parameter.delete_kernel_params(params)
}
}
}
pub fn confirm_message(&self) -> &'static str {
match self {
Self::Add { .. } => {
"This operation will add the kernel parameters for the nodes below. \
Please confirm to proceed"
}
Self::Apply { .. } => {
"This operation will replace the kernel parameters for the nodes below. \
Please confirm to proceed"
}
Self::Delete { .. } => {
"This operation will delete the kernel parameters for the nodes below. \
Please confirm to proceed"
}
}
}
fn handles_sbps_images(&self) -> bool {
match self {
Self::Add { .. } | Self::Apply { .. } => true,
Self::Delete { .. } => false,
}
}
}
#[derive(serde::Serialize)]
pub struct KernelParamsChangeset {
pub boot_params: Vec<BootParameters>,
pub xnames_to_reboot: Vec<String>,
pub has_changes: bool,
pub sbps_candidates: Vec<(String, Image)>,
}
pub async fn prepare_kernel_params_changes(
infra: &InfraContext<'_>,
token: &str,
xname_vec: &[String],
operation: &KernelParamOperation<'_>,
) -> Result<KernelParamsChangeset, Error> {
let mut boot_params: Vec<BootParameters> = infra
.backend
.get_bootparameters(token, xname_vec)
.await?;
let mut has_changes = false;
let mut xnames_to_reboot: Vec<String> = Vec::new();
let mut seen_images: HashMap<String, bool> = HashMap::new();
let mut sbps_candidates: Vec<(String, Image)> = Vec::new();
for bp in &mut boot_params {
let changed = operation.mutate(bp);
if changed {
has_changes = true;
xnames_to_reboot.extend(bp.hosts.iter().cloned());
}
if operation.handles_sbps_images() {
if let Some(image_id) = bp.try_get_boot_image_id() {
if !seen_images.contains_key(&image_id) {
seen_images.insert(image_id.clone(), true);
let image: Image = infra
.backend
.get_images(token, Some(&image_id))
.await?
.first()
.ok_or_else(|| Error::NotFound("No image found for the given image id".to_string()))?
.clone();
if bp.is_root_kernel_param_iscsi_ready() {
sbps_candidates.push((image_id, image));
}
}
}
}
}
Ok(KernelParamsChangeset {
boot_params,
xnames_to_reboot,
has_changes,
sbps_candidates,
})
}
pub async fn apply_kernel_params_changes(
infra: &InfraContext<'_>,
token: &str,
changeset: &KernelParamsChangeset,
images_to_project: &HashMap<String, Image>,
) -> Result<(), Error> {
for bp in &changeset.boot_params {
infra
.backend
.update_bootparameters(token, bp)
.await?;
}
for (_, image) in images_to_project {
infra
.backend
.update_image(
token,
image
.id
.clone()
.ok_or_else(|| Error::MissingField("Image has no id".to_string()))?
.as_str(),
&image.clone().into(),
)
.await?;
}
Ok(())
}
pub fn build_images_to_project(
changeset: &KernelParamsChangeset,
project_sbps: bool,
) -> HashMap<String, Image> {
if !project_sbps {
return HashMap::new();
}
changeset
.sbps_candidates
.iter()
.map(|(id, img)| {
let mut img = img.clone();
img.set_boot_image_iscsi_ready();
(id.clone(), img)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn add(params: &str) -> KernelParamOperation<'_> {
KernelParamOperation::Add { params, overwrite: false }
}
fn add_overwrite(params: &str) -> KernelParamOperation<'_> {
KernelParamOperation::Add { params, overwrite: true }
}
fn apply(params: &str) -> KernelParamOperation<'_> {
KernelParamOperation::Apply { params }
}
fn delete(params: &str) -> KernelParamOperation<'_> {
KernelParamOperation::Delete { params }
}
#[test]
fn verb_returns_correct_string() {
assert_eq!(add("quiet").verb(), "Add");
assert_eq!(apply("quiet").verb(), "Apply");
assert_eq!(delete("quiet").verb(), "Delete");
}
#[test]
fn params_returns_the_string() {
assert_eq!(add("quiet crashkernel=auto").params(), "quiet crashkernel=auto");
assert_eq!(apply("console=ttyS0").params(), "console=ttyS0");
assert_eq!(delete("splash").params(), "splash");
}
#[test]
fn overwrite_flag_preserved() {
match add_overwrite("x") {
KernelParamOperation::Add { overwrite, .. } => assert!(overwrite),
_ => panic!("wrong variant"),
}
match add("x") {
KernelParamOperation::Add { overwrite, .. } => assert!(!overwrite),
_ => panic!("wrong variant"),
}
}
#[test]
fn handles_sbps_images_only_for_add_and_apply() {
assert!(add("quiet").handles_sbps_images());
assert!(apply("quiet").handles_sbps_images());
assert!(!delete("quiet").handles_sbps_images());
}
#[test]
fn confirm_messages_are_nonempty() {
assert!(!add("x").confirm_message().is_empty());
assert!(!apply("x").confirm_message().is_empty());
assert!(!delete("x").confirm_message().is_empty());
}
#[test]
fn confirm_messages_are_distinct() {
assert_ne!(add("x").confirm_message(), apply("x").confirm_message());
assert_ne!(add("x").confirm_message(), delete("x").confirm_message());
assert_ne!(apply("x").confirm_message(), delete("x").confirm_message());
}
}