diff --git a/nexus/reconfigurator/execution/src/datasets.rs b/nexus/reconfigurator/execution/src/datasets.rs index 30c656eca5..92506718bc 100644 --- a/nexus/reconfigurator/execution/src/datasets.rs +++ b/nexus/reconfigurator/execution/src/datasets.rs @@ -49,7 +49,7 @@ pub(crate) async fn deploy_datasets( &log, ); - let config: DatasetsConfig = config.clone().into(); + let config: DatasetsConfig = config.clone().into_in_service_datasets(); let result = client.datasets_put(&config).await.with_context( || format!("Failed to put {config:#?} to sled {sled_id}"), @@ -100,3 +100,103 @@ pub(crate) async fn deploy_datasets( Err(errors) } } + +#[cfg(test)] +mod tests { + use super::*; + use nexus_sled_agent_shared::inventory::SledRole; + use nexus_test_utils_macros::nexus_test; + use nexus_types::deployment::id_map::IdMap; + use nexus_types::deployment::BlueprintDatasetConfig; + use nexus_types::deployment::BlueprintDatasetDisposition; + use nexus_types::deployment::BlueprintDatasetsConfig; + use omicron_common::api::external::Generation; + use omicron_common::api::internal::shared::DatasetKind; + use omicron_common::disk::CompressionAlgorithm; + use omicron_common::zpool_name::ZpoolName; + use omicron_uuid_kinds::DatasetUuid; + use omicron_uuid_kinds::ZpoolUuid; + use std::net::SocketAddr; + + type ControlPlaneTestContext = + nexus_test_utils::ControlPlaneTestContext; + + #[nexus_test] + async fn test_deploy_datasets(cptestctx: &ControlPlaneTestContext) { + // Set up. + let nexus = &cptestctx.server.server_context().nexus; + let datastore = nexus.datastore(); + let opctx = OpContext::for_tests( + cptestctx.logctx.log.clone(), + datastore.clone(), + ); + + let sim_sled_agent_addr = match cptestctx.sled_agents[0].local_addr() { + SocketAddr::V6(addr) => addr, + _ => panic!("Unexpected address type for sled agent (wanted IPv6)"), + }; + let sim_sled_agent = &cptestctx.sled_agents[0].sled_agent(); + + // This is a fully fabricated dataset list for a simulated sled agent. + // + // We're testing the validity of the deployment calls here, not of any + // blueprint. + + let sleds_by_id = BTreeMap::from([( + sim_sled_agent.id, + Sled::new( + sim_sled_agent.id, + sim_sled_agent_addr, + SledRole::Scrimlet, + ), + )]); + + // Create two datasets which look like they came from the blueprint: One + // which is in-service, and one which is expunged. + // + // During deployment, the in-service dataset should be deployed, but the + // expunged dataset should be ignored. + let dataset_id = DatasetUuid::new_v4(); + let expunged_dataset_id = DatasetUuid::new_v4(); + let mut datasets = IdMap::new(); + datasets.insert(BlueprintDatasetConfig { + disposition: BlueprintDatasetDisposition::InService, + id: dataset_id, + pool: ZpoolName::new_external(ZpoolUuid::new_v4()), + kind: DatasetKind::Crucible, + address: None, + quota: None, + reservation: None, + compression: CompressionAlgorithm::Off, + }); + datasets.insert(BlueprintDatasetConfig { + disposition: BlueprintDatasetDisposition::Expunged, + id: expunged_dataset_id, + pool: ZpoolName::new_external(ZpoolUuid::new_v4()), + kind: DatasetKind::Crucible, + address: None, + quota: None, + reservation: None, + compression: CompressionAlgorithm::Off, + }); + + let datasets_config = + BlueprintDatasetsConfig { generation: Generation::new(), datasets }; + let sled_configs = + BTreeMap::from([(sim_sled_agent.id, datasets_config.clone())]); + + // Give the simulated sled agent a configuration to deploy + deploy_datasets(&opctx, &sleds_by_id, &sled_configs) + .await + .expect("Deploying datasets should have succeeded"); + + // Observe the latest configuration stored on the simulated sled agent, + // and verify that this output matches the "deploy_datasets" input. + let observed_config = sim_sled_agent.datasets_config_list().unwrap(); + assert_eq!(observed_config, datasets_config.into_in_service_datasets()); + + // We expect to see the single in-service dataset we supplied as input. + assert_eq!(observed_config.datasets.len(), 1,); + assert!(observed_config.datasets.contains_key(&dataset_id)); + } +} diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index a11435c8be..39e417e1ed 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -949,14 +949,29 @@ pub struct BlueprintDatasetsConfig { pub datasets: IdMap, } -impl From for DatasetsConfig { - fn from(config: BlueprintDatasetsConfig) -> Self { - Self { - generation: config.generation, - datasets: config +impl BlueprintDatasetsConfig { + /// Converts [Self] into [DatasetsConfig]. + /// + /// [DatasetsConfig] is a format of the dataset configuration that can be + /// passed to Sled Agents for deployment. + /// + /// This function is effectively a [std::convert::From] implementation, but + /// is named slightly more explicitly, as it filters the blueprint + /// configuration to only consider in-service datasets. + pub fn into_in_service_datasets(self) -> DatasetsConfig { + DatasetsConfig { + generation: self.generation, + datasets: self .datasets .into_iter() - .map(|d| (d.id, d.into())) + .filter_map(|d| { + if d.disposition.matches(BlueprintDatasetFilter::InService) + { + Some((d.id, d.into())) + } else { + None + } + }) .collect(), } }