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::server::common::app_context::InfraContext;
use crate::service::authorization::validate_user_group_members_access;
use crate::service::node_ops;
pub use manta_shared::types::api::kernel_parameters::GetKernelParametersParams;
pub async fn get_kernel_parameters(
infra: &InfraContext<'_>,
token: &str,
params: &GetKernelParametersParams,
) -> Result<Vec<BootParameters>, Error> {
let xname_vec = node_ops::resolve_target_nodes(
infra,
token,
params.nodes.as_deref(),
params.group_name.as_deref(),
params.settings_group_name.as_deref(),
)
.await?;
validate_user_group_members_access(infra, token, &xname_vec).await?;
let boot_parameter_vec =
infra.backend.get_bootparameters(token, &xname_vec).await?;
Ok(boot_parameter_vec)
}
pub(crate) enum KernelParamOperation<'a> {
Add {
params: &'a str,
overwrite: bool,
},
Apply {
params: &'a str,
},
Delete {
params: &'a str,
},
}
impl<'a> KernelParamOperation<'a> {
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),
}
}
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(crate) 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 handles_sbps = operation.handles_sbps_images();
let mut sbps_image_ids: Vec<String> = Vec::new();
let mut seen_image_ids: std::collections::HashSet<String> =
std::collections::HashSet::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 handles_sbps
&& bp.is_root_kernel_param_iscsi_ready()
&& let Some(image_id) = bp.try_get_boot_image_id()
&& seen_image_ids.insert(image_id.clone())
{
sbps_image_ids.push(image_id);
}
}
let sbps_candidates: Vec<(String, Image)> = if sbps_image_ids.is_empty() {
Vec::new()
} else {
futures::future::try_join_all(sbps_image_ids.into_iter().map(|id| async {
let image = infra
.backend
.get_images(token, Some(id.as_str()))
.await?
.first()
.ok_or_else(|| {
Error::NotFound(format!("No image found for image id '{id}'"))
})?
.clone();
Ok::<_, Error>((id, image))
}))
.await?
};
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> {
validate_user_group_members_access(infra, token, &changeset.xnames_to_reboot)
.await?;
for bp in &changeset.boot_params {
infra.backend.update_bootparameters(token, bp).await?;
}
for image in images_to_project.values() {
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 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());
}
}