use anyhow::{Context, Error, bail};
use manta_backend_dispatcher::{
interfaces::{
bss::BootParametersTrait,
cfs::CfsTrait,
ims::ImsTrait,
},
types::{
bss::BootParameters,
ims::{Image, PatchImage},
},
};
use std::collections::HashMap;
use crate::common;
use crate::common::app_context::InfraContext;
use crate::common::authorization::validate_target_hsm_members;
use crate::common::ims_ops::get_image_vec_related_cfs_configuration_name;
use crate::manta_backend_dispatcher::StaticBackendDispatcher;
pub struct GetBootParametersParams {
pub hsm_group: Option<String>,
pub nodes: Option<String>,
pub settings_hsm_group_name: Option<String>,
}
pub async fn get_boot_parameters(
infra: &InfraContext<'_>,
token: &str,
params: &GetBootParametersParams,
) -> 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?;
log::info!("Get boot parameters");
infra.backend
.get_bootparameters(token, &xname_vec)
.await
.map_err(|e| anyhow::anyhow!(e))
}
pub async fn delete_boot_parameters(
infra: &InfraContext<'_>,
token: &str,
hosts: Vec<String>,
) -> Result<(), Error> {
let boot_parameters = BootParameters {
hosts,
macs: None,
nids: None,
params: String::new(),
kernel: String::new(),
initrd: String::new(),
cloud_init: None,
};
infra
.backend
.delete_bootparameters(token, &boot_parameters)
.await
.context("Failed to delete boot parameters")?;
Ok(())
}
pub async fn add_boot_parameters(
infra: &InfraContext<'_>,
token: &str,
boot_parameters: &BootParameters,
) -> Result<(), Error> {
infra
.backend
.add_bootparameters(token, boot_parameters)
.await?;
Ok(())
}
pub struct UpdateBootParametersParams {
pub hosts: Vec<String>,
pub nids: Option<Vec<u32>>,
pub macs: Option<Vec<String>>,
pub params: String,
pub kernel: String,
pub initrd: String,
}
pub async fn update_boot_parameters(
infra: &InfraContext<'_>,
token: &str,
params: UpdateBootParametersParams,
) -> Result<(), Error> {
validate_target_hsm_members(infra.backend, token, ¶ms.hosts).await?;
let boot_parameters = BootParameters {
hosts: params.hosts,
macs: params.macs,
nids: params.nids,
params: params.params,
kernel: params.kernel,
initrd: params.initrd,
cloud_init: None,
};
log::debug!("new boot params: {:#?}", boot_parameters);
infra
.backend
.update_bootparameters(token, &boot_parameters)
.await?;
Ok(())
}
pub struct BootConfigChangeset {
pub xname_vec: Vec<String>,
pub boot_param_vec: Vec<BootParameters>,
pub image_vec: HashMap<String, Image>,
pub need_restart: bool,
}
pub async fn prepare_boot_config(
infra: &InfraContext<'_>,
token: &str,
hosts_expression: &str,
new_boot_image_id_opt: Option<&str>,
new_boot_image_configuration_opt: Option<&str>,
new_kernel_parameters_opt: Option<&str>,
) -> Result<BootConfigChangeset, Error> {
let backend = infra.backend;
let mut need_restart = false;
let xname_vec = common::node_ops::resolve_hosts_expression(
backend,
token,
hosts_expression,
false,
)
.await?;
let mut current_node_boot_param_vec: Vec<BootParameters> = backend
.get_bootparameters(token, &xname_vec)
.await
.context("Failed to get boot parameters")?;
let new_boot_image_opt = get_new_boot_image(
backend,
token,
infra.shasta_base_url,
infra.shasta_root_cert,
new_boot_image_configuration_opt,
new_boot_image_id_opt,
)
.await?;
if let Some(new_kernel_parameters) = new_kernel_parameters_opt {
need_restart |= apply_kernel_params(
&mut current_node_boot_param_vec,
new_kernel_parameters,
)?;
}
let mut image_vec = collect_boot_images(
backend,
token,
&mut current_node_boot_param_vec,
new_boot_image_opt,
&mut need_restart,
)
.await?;
if current_node_boot_param_vec
.first()
.context("No boot parameters found")?
.is_root_kernel_param_iscsi_ready()
{
for image in image_vec.values_mut() {
image.set_boot_image_iscsi_ready();
}
}
Ok(BootConfigChangeset {
xname_vec,
boot_param_vec: current_node_boot_param_vec,
image_vec,
need_restart,
})
}
pub async fn persist_boot_config(
infra: &InfraContext<'_>,
token: &str,
changeset: &BootConfigChangeset,
new_runtime_configuration_opt: Option<&str>,
) -> Result<(), Error> {
log::info!("Persist changes");
for boot_parameter in &changeset.boot_param_vec {
log::debug!("Updating boot parameter:\n{:#?}", boot_parameter);
let component_patch_rep = infra
.backend
.update_bootparameters(token, boot_parameter)
.await;
log::debug!(
"Component boot parameters resp:\n{:#?}",
component_patch_rep
);
}
if let Some(new_runtime_configuration_name) = new_runtime_configuration_opt {
println!(
"Updating runtime configuration to '{}'",
new_runtime_configuration_name
);
infra
.backend
.update_runtime_configuration(
token,
infra.shasta_base_url,
infra.shasta_root_cert,
&changeset.xname_vec,
new_runtime_configuration_name,
true,
)
.await
.context("Error updating runtime configuration")?;
for (_, image) in &changeset.image_vec {
let image_id = image.id.clone().context("Image id is missing")?;
let patch_image: PatchImage = image.clone().into();
infra
.backend
.update_image(token, &image_id, &patch_image)
.await?;
}
} else {
log::info!("Runtime configuration does not change.");
}
Ok(())
}
async fn get_new_boot_image(
backend: &StaticBackendDispatcher,
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
new_boot_image_configuration_opt: Option<&str>,
new_boot_image_id_opt: Option<&str>,
) -> Result<Option<Image>, Error> {
let new_boot_image = if let Some(new_boot_image_configuration) =
new_boot_image_configuration_opt
{
log::info!(
"Boot configuration '{}' provided",
new_boot_image_configuration
);
let mut image_vec = get_image_vec_related_cfs_configuration_name(
backend,
shasta_token,
shasta_base_url,
shasta_root_cert,
new_boot_image_configuration.to_string(),
)
.await?;
if image_vec.is_empty() {
bail!(
"Could not find boot image related to \
configuration '{}'",
new_boot_image_configuration
);
}
backend.filter_images(&mut image_vec)?;
let most_recent_image = image_vec
.iter()
.last()
.context("No image found for configuration")?;
log::debug!(
"Boot image id related to configuration \
'{}' found:\n{:#?}",
new_boot_image_configuration,
most_recent_image
);
Some(most_recent_image.clone())
} else if let Some(boot_image_id) = new_boot_image_id_opt {
log::info!("Boot image id '{}' provided", boot_image_id);
let image_in_csm_vec = backend
.get_images(shasta_token, new_boot_image_id_opt)
.await?;
if image_in_csm_vec.is_empty() {
bail!("boot image id '{}' not found", boot_image_id);
}
image_in_csm_vec.first().cloned()
} else {
None
};
Ok(new_boot_image)
}
fn apply_kernel_params(
boot_param_vec: &mut [BootParameters],
new_kernel_parameters: &str,
) -> Result<bool, Error> {
let mut any_changed = false;
for boot_parameter in boot_param_vec.iter_mut() {
log::info!(
"Updating '{:?}' kernel parameters to '{}'",
boot_parameter.hosts,
new_kernel_parameters
);
let changed = boot_parameter.apply_kernel_params(new_kernel_parameters);
any_changed = changed || any_changed;
log::info!("need restart? {}", any_changed);
let image_id = boot_parameter.try_get_boot_image_id().ok_or_else(|| {
Error::msg(format!(
"Could not get boot image id from boot \
parameters for hosts: {:?}",
boot_parameter.hosts
))
})?;
let _ = boot_parameter
.update_boot_image(&image_id, &boot_parameter.get_boot_image_etag())?;
}
Ok(any_changed)
}
async fn collect_boot_images(
backend: &StaticBackendDispatcher,
shasta_token: &str,
boot_param_vec: &mut [BootParameters],
new_boot_image_opt: Option<Image>,
need_restart: &mut bool,
) -> Result<HashMap<String, Image>, Error> {
let mut image_vec = HashMap::<String, Image>::new();
if let Some(new_boot_image) = new_boot_image_opt {
let new_boot_image_id = new_boot_image
.id
.as_ref()
.context("New boot image id is missing")?
.clone();
let new_boot_image_etag = new_boot_image
.link
.as_ref()
.and_then(|link| link.etag.as_ref())
.context("New boot image etag is missing")?;
image_vec.insert(new_boot_image_id.clone(), new_boot_image.clone());
let any_differ = boot_param_vec.iter().any(|bp| {
bp.try_get_boot_image_id().as_deref() != Some(new_boot_image_id.as_str())
});
if any_differ {
for boot_parameter in boot_param_vec.iter_mut() {
log::info!(
"Updating '{:?}' boot image to '{}'",
boot_parameter.hosts,
new_boot_image_id
);
boot_parameter
.update_boot_image(&new_boot_image_id, new_boot_image_etag)
.context("Failed to update boot image")?;
}
*need_restart = true;
}
} else {
for boot_parameter in boot_param_vec.iter() {
let boot_image_id =
boot_parameter.try_get_boot_image_id().ok_or_else(|| {
Error::msg(format!(
"Could not get boot image id from boot \
parameters for hosts: {:?}",
boot_parameter.hosts
))
})?;
let boot_image = backend
.get_images(shasta_token, Some(boot_image_id.as_str()))
.await?
.first()
.context("No image found for boot image id")?
.clone();
image_vec.insert(boot_image_id, boot_image);
}
}
Ok(image_vec)
}