From 84e9c27e5f1af5d7de17384baea3b3639096a68e Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Sat, 20 Apr 2024 12:47:39 -0400 Subject: [PATCH] Remove ability to generate a Blueprint from an inventory collection (#5583) We want to add information in blueprints, particularly `BlueprintZoneConfig` and `BlueprintZoneType`, that is not present in the sled-agent types `OmicronZoneConfig` and `OmicronZoneType`. Today on main conversion between those types is bidirectional. As we add to blueprints, we will continue to be able to convert a `BlueprintZoneConfig` into an `OmicronZoneConfig`, but the opposite direction will become more and more difficult as callers need to provide the additional information required for blueprints. We have enough users of the inventory -> blueprint direction that it's quite painful to try to add to blueprints, so this PR attempts to knock out one of the more common uses: converting an inventory collection into a blueprint via `BlueprintBuilder::build_initial_from_collection()`. This method is removed, and all the remaining changes are fallout from that. Most uses of this have been replaced by either the blueprint produced by `ExampleSystem` or the new `BlueprintBuilder::build_empty_with_sleds()` helper for constructing an empty blueprint. One test needed the full blueprint-from-inventory, so that one actually gained a new use of converting OmicronZoneConfig -> BlueprintZoneConfig. (Not ideal, but fine for now!) --- dev-tools/omdb/src/bin/omdb/nexus.rs | 31 -- dev-tools/reconfigurator-cli/src/main.rs | 37 -- .../db-queries/src/db/datastore/deployment.rs | 136 ++---- nexus/db-queries/src/db/datastore/rack.rs | 451 +++++++++--------- nexus/reconfigurator/execution/src/dns.rs | 135 +++--- .../planning/src/blueprint_builder/builder.rs | 266 +++-------- .../planning/src/blueprint_builder/zones.rs | 11 +- nexus/reconfigurator/planning/src/example.rs | 44 +- nexus/reconfigurator/planning/src/planner.rs | 90 +--- .../output/blueprint_builder_initial_diff.txt | 2 +- .../output/planner_nonprovisionable_1_2.txt | 2 +- .../output/planner_nonprovisionable_bp2.txt | 2 +- nexus/src/app/deployment.rs | 32 -- nexus/src/internal_api/http_entrypoints.rs | 29 -- nexus/types/src/deployment.rs | 37 +- openapi/nexus-internal.json | 49 -- 16 files changed, 448 insertions(+), 906 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index a7fcc6badc..67b91e0280 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -97,8 +97,6 @@ enum BlueprintsCommands { Delete(BlueprintIdArgs), /// Interact with the current target blueprint Target(BlueprintsTargetArgs), - /// Generate an initial blueprint from a specific inventory collection - GenerateFromCollection(CollectionIdArgs), /// Generate a new blueprint Regenerate, /// Import a blueprint @@ -361,15 +359,6 @@ impl NexusArgs { let token = omdb.check_allow_destructive()?; cmd_nexus_blueprints_regenerate(&client, token).await } - NexusCommands::Blueprints(BlueprintsArgs { - command: BlueprintsCommands::GenerateFromCollection(args), - }) => { - let token = omdb.check_allow_destructive()?; - cmd_nexus_blueprints_generate_from_collection( - &client, args, token, - ) - .await - } NexusCommands::Blueprints(BlueprintsArgs { command: BlueprintsCommands::Import(args), }) => { @@ -1134,26 +1123,6 @@ async fn cmd_nexus_blueprints_target_set_enabled( Ok(()) } -async fn cmd_nexus_blueprints_generate_from_collection( - client: &nexus_client::Client, - args: &CollectionIdArgs, - _destruction_token: DestructiveOperationToken, -) -> Result<(), anyhow::Error> { - let blueprint = client - .blueprint_generate_from_collection( - &nexus_client::types::CollectionId { - collection_id: args.collection_id, - }, - ) - .await - .context("creating blueprint from collection id")?; - eprintln!( - "created blueprint {} from collection id {}", - blueprint.id, args.collection_id - ); - Ok(()) -} - async fn cmd_nexus_blueprints_regenerate( client: &nexus_client::Client, _destruction_token: DestructiveOperationToken, diff --git a/dev-tools/reconfigurator-cli/src/main.rs b/dev-tools/reconfigurator-cli/src/main.rs index 58d310f56e..ae4a6bd648 100644 --- a/dev-tools/reconfigurator-cli/src/main.rs +++ b/dev-tools/reconfigurator-cli/src/main.rs @@ -314,9 +314,6 @@ fn process_entry(sim: &mut ReconfiguratorSim, entry: String) -> LoopResult { Commands::InventoryList => cmd_inventory_list(sim), Commands::InventoryGenerate => cmd_inventory_generate(sim), Commands::BlueprintList => cmd_blueprint_list(sim), - Commands::BlueprintFromInventory(args) => { - cmd_blueprint_from_inventory(sim, args) - } Commands::BlueprintEdit(args) => cmd_blueprint_edit(sim, args), Commands::BlueprintPlan(args) => cmd_blueprint_plan(sim, args), Commands::BlueprintShow(args) => cmd_blueprint_show(sim, args), @@ -374,8 +371,6 @@ enum Commands { /// list all blueprints BlueprintList, - /// generate a blueprint that represents the contents of an inventory - BlueprintFromInventory(InventoryArgs), /// run planner to generate a new blueprint BlueprintPlan(BlueprintPlanArgs), /// edit contents of a blueprint directly @@ -718,38 +713,6 @@ fn cmd_blueprint_list( Ok(Some(table)) } -fn cmd_blueprint_from_inventory( - sim: &mut ReconfiguratorSim, - args: InventoryArgs, -) -> anyhow::Result> { - let collection_id = args.collection_id; - let collection = sim - .collections - .get(&collection_id) - .ok_or_else(|| anyhow!("no such collection: {}", collection_id))?; - let dns_version = Generation::new(); - let planning_input = sim - .system - .to_planning_input_builder() - .context("generating planning_input builder")? - .build(); - let creator = "reconfigurator-sim"; - let blueprint = BlueprintBuilder::build_initial_from_collection( - collection, - dns_version, - dns_version, - planning_input.all_sled_ids(SledFilter::All), - creator, - ) - .context("building collection")?; - let rv = format!( - "generated blueprint {} from inventory collection {}", - blueprint.id, collection_id - ); - sim.blueprint_insert_new(blueprint); - Ok(Some(rv)) -} - fn cmd_blueprint_plan( sim: &mut ReconfiguratorSim, args: BlueprintPlanArgs, diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 4d5b753c7f..5a17b39fdd 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -1263,12 +1263,12 @@ mod tests { use nexus_inventory::now_db_precision; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::blueprint_builder::Ensure; + use nexus_reconfigurator_planning::example::example; use nexus_test_utils::db::test_setup_database; use nexus_types::deployment::BlueprintZoneDisposition; use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::PlanningInputBuilder; - use nexus_types::deployment::Policy; use nexus_types::deployment::SledDetails; use nexus_types::deployment::SledDisk; use nexus_types::deployment::SledFilter; @@ -1279,7 +1279,6 @@ mod tests { use nexus_types::external_api::views::SledState; use nexus_types::inventory::Collection; use omicron_common::address::Ipv6Subnet; - use omicron_common::api::external::Generation; use omicron_common::disk::DiskIdentity; use omicron_test_utils::dev; use omicron_uuid_kinds::PhysicalDiskUuid; @@ -1288,6 +1287,7 @@ mod tests { use pretty_assertions::assert_eq; use rand::thread_rng; use rand::Rng; + use slog::Logger; use std::mem; use std::net::Ipv6Addr; @@ -1359,65 +1359,32 @@ mod tests { } } - // Create a `Policy` that contains all the sleds found in `collection` - fn policy_from_collection(collection: &Collection) -> Policy { - Policy { - service_ip_pool_ranges: Vec::new(), - target_nexus_zone_count: collection - .all_omicron_zones() - .filter(|z| z.zone_type.is_nexus()) - .count(), - } - } + fn representative( + log: &Logger, + test_name: &str, + ) -> (Collection, PlanningInput, Blueprint) { + // We'll start with an example system. + let (mut base_collection, planning_input, mut blueprint) = + example(log, test_name, 3); - fn representative() -> (Collection, PlanningInput, Blueprint) { - // We'll start with a representative collection... + // Take a more thorough collection representative (includes SPs, + // etc.)... let mut collection = nexus_inventory::examples::representative().builder.build(); - // ...and then mutate it such that the omicron zones it reports match - // the sled agent IDs it reports. Steal the sled agent info and drop the - // fake sled-agent IDs: - let mut empty_map = BTreeMap::new(); - mem::swap(&mut empty_map, &mut collection.sled_agents); - let mut sled_agents = empty_map.into_values().collect::>(); - - // Now reinsert them with IDs pulled from the omicron zones. This - // assumes we have more fake sled agents than omicron zones, which is - // currently true for the representative collection. - for &sled_id in collection.omicron_zones.keys() { - let some_sled_agent = sled_agents.pop().expect( - "fewer representative sled agents than \ - representative omicron zones sleds", - ); - collection.sled_agents.insert(sled_id, some_sled_agent); - } + // ... and replace its sled agent and Omicron zones with those from our + // example system. + mem::swap( + &mut collection.sled_agents, + &mut base_collection.sled_agents, + ); + mem::swap( + &mut collection.omicron_zones, + &mut base_collection.omicron_zones, + ); - let policy = policy_from_collection(&collection); - let planning_input = { - let mut builder = PlanningInputBuilder::new( - policy, - Generation::new(), - Generation::new(), - ); - for (sled_id, agent) in &collection.sled_agents { - builder - .add_sled( - *sled_id, - fake_sled_details(Some(*agent.sled_agent_address.ip())), - ) - .expect("failed to add sled to representative"); - } - builder.build() - }; - let blueprint = BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - Generation::new(), - planning_input.all_sled_ids(SledFilter::All), - "test", - ) - .unwrap(); + // Treat this blueprint as the initial blueprint for the system. + blueprint.parent_blueprint_id = None; (collection, planning_input, blueprint) } @@ -1442,17 +1409,11 @@ mod tests { let mut db = test_setup_database(&logctx.log).await; let (opctx, datastore) = datastore_test(&logctx, &db).await; - // Create an empty collection and a blueprint from it - let collection = - nexus_inventory::CollectionBuilder::new("test").build(); - let blueprint1 = BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - Generation::new(), + // Create an empty blueprint from it + let blueprint1 = BlueprintBuilder::build_empty_with_sleds( std::iter::empty(), "test", - ) - .unwrap(); + ); let authz_blueprint = authz_blueprint_from_id(blueprint1.id); // Trying to read it from the database should fail with the relevant @@ -1471,7 +1432,7 @@ mod tests { let blueprint_read = datastore .blueprint_read(&opctx, &authz_blueprint) .await - .expect("failed to read collection back"); + .expect("failed to read blueprint back"); assert_eq!(blueprint1, blueprint_read); assert_eq!( blueprint_list_all_ids(&opctx, &datastore).await, @@ -1501,13 +1462,15 @@ mod tests { #[tokio::test] async fn test_representative_blueprint() { + const TEST_NAME: &str = "test_representative_blueprint"; // Setup - let logctx = dev::test_setup_log("test_representative_blueprint"); + let logctx = dev::test_setup_log(TEST_NAME); let mut db = test_setup_database(&logctx.log).await; let (opctx, datastore) = datastore_test(&logctx, &db).await; // Create a cohesive representative collection/policy/blueprint - let (collection, planning_input, blueprint1) = representative(); + let (collection, planning_input, blueprint1) = + representative(&logctx.log, TEST_NAME); let authz_blueprint1 = authz_blueprint_from_id(blueprint1.id); // Write it to the database and read it back. @@ -1632,10 +1595,23 @@ mod tests { let blueprint2 = builder.build(); let authz_blueprint2 = authz_blueprint_from_id(blueprint2.id); + let diff = blueprint2.diff_since_blueprint(&blueprint1).unwrap(); + println!("b1 -> b2: {}", diff.display()); + println!("b1 disks: {:?}", blueprint1.blueprint_disks); + println!("b2 disks: {:?}", blueprint2.blueprint_disks); // Check that we added the new sled, as well as its disks and zones. assert_eq!( - blueprint1.blueprint_disks.len() + new_sled_zpools.len(), - blueprint2.blueprint_disks.len(), + blueprint1 + .blueprint_disks + .values() + .map(|c| c.disks.len()) + .sum::() + + new_sled_zpools.len(), + blueprint2 + .blueprint_disks + .values() + .map(|c| c.disks.len()) + .sum::() ); assert_eq!( blueprint1.blueprint_zones.len() + 1, @@ -1757,16 +1733,10 @@ mod tests { // Create three blueprints: // * `blueprint1` has no parent // * `blueprint2` and `blueprint3` both have `blueprint1` as parent - let collection = - nexus_inventory::CollectionBuilder::new("test").build(); - let blueprint1 = BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - Generation::new(), + let blueprint1 = BlueprintBuilder::build_empty_with_sleds( std::iter::empty(), "test1", - ) - .unwrap(); + ); let blueprint2 = BlueprintBuilder::new_based_on( &logctx.log, &blueprint1, @@ -1911,16 +1881,10 @@ mod tests { let (opctx, datastore) = datastore_test(&logctx, &db).await; // Create an initial blueprint and a child. - let collection = - nexus_inventory::CollectionBuilder::new("test").build(); - let blueprint1 = BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - Generation::new(), + let blueprint1 = BlueprintBuilder::build_empty_with_sleds( std::iter::empty(), "test1", - ) - .unwrap(); + ); let blueprint2 = BlueprintBuilder::new_based_on( &logctx.log, &blueprint1, diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index 45793d26f7..0f4b1b245e 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -959,20 +959,19 @@ mod test { use async_bb8_diesel::AsyncSimpleConnection; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_db_model::{DnsGroup, Generation, InitialDnsGroup, SledUpdate}; - use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; + use nexus_inventory::now_db_precision; use nexus_reconfigurator_planning::system::{ SledBuilder, SystemDescription, }; use nexus_test_utils::db::test_setup_database; - use nexus_types::deployment::OmicronZoneConfig; - use nexus_types::deployment::OmicronZonesConfig; - use nexus_types::deployment::SledFilter; + use nexus_types::deployment::BlueprintZoneConfig; + use nexus_types::deployment::BlueprintZoneDisposition; + use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::external_api::shared::SiloIdentityMode; use nexus_types::identity::Asset; use nexus_types::internal_api::params::DnsRecord; use nexus_types::inventory::NetworkInterface; use nexus_types::inventory::NetworkInterfaceKind; - use nexus_types::inventory::OmicronZoneType; use omicron_common::address::{ DNS_OPTE_IPV4_SUBNET, NEXUS_OPTE_IPV4_SUBNET, NTP_OPTE_IPV4_SUBNET, }; @@ -982,8 +981,9 @@ mod test { }; use omicron_common::api::internal::shared::SourceNatConfig; use omicron_test_utils::dev; + use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::TypedUuid; - use omicron_uuid_kinds::{GenericUuid, SledUuid, ZpoolUuid}; + use omicron_uuid_kinds::{GenericUuid, ZpoolUuid}; use sled_agent_client::types::OmicronZoneDataset; use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; @@ -1270,61 +1270,52 @@ mod test { SledBuilder::new().id(TypedUuid::from_untyped_uuid(sled3.id())), ) .expect("failed to add sled3"); - let planning_input = system - .to_planning_input_builder() - .expect("failed to make planning input") - .build(); - let mut inventory_builder = system - .to_collection_builder() - .expect("failed to make collection builder"); let external_dns_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)); let external_dns_pip = DNS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); - let external_dns_id = Uuid::new_v4(); + let external_dns_id = OmicronZoneUuid::new_v4(); let nexus_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 6)); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); - let nexus_id = Uuid::new_v4(); + let nexus_id = OmicronZoneUuid::new_v4(); let ntp1_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 5)); let ntp1_pip = NTP_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); - let ntp1_id = Uuid::new_v4(); + let ntp1_id = OmicronZoneUuid::new_v4(); let ntp2_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 5)); let ntp2_pip = NTP_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 2) .unwrap(); - let ntp2_id = Uuid::new_v4(); - let ntp3_id = Uuid::new_v4(); + let ntp2_id = OmicronZoneUuid::new_v4(); + let ntp3_id = OmicronZoneUuid::new_v4(); let mut macs = MacAddr::iter_system(); - // Add services for our sleds to the inventory (which will cause them to - // be present in the blueprint we'll generate from it). - inventory_builder - .found_sled_omicron_zones( - "sled1", - SledUuid::from_untyped_uuid(sled1.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![ - OmicronZoneConfig { - id: external_dns_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::ExternalDns { + let mut blueprint_zones = BTreeMap::new(); + blueprint_zones.insert( + sled1.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![ + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: external_dns_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::ExternalDns( + blueprint_zone_type::ExternalDns { dataset: random_dataset(), - http_address: "[::1]:80".to_string(), + http_address: "[::1]:80".parse().unwrap(), dns_address: SocketAddr::new( external_dns_ip, 53, - ) - .to_string(), + ), nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: external_dns_id, + id: external_dns_id.into_untyped_uuid(), }, name: "external-dns".parse().unwrap(), ip: external_dns_pip.into(), @@ -1338,19 +1329,22 @@ mod test { slot: 0, }, }, - }, - OmicronZoneConfig { - id: ntp1_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::BoundaryNtp { - address: "[::1]:80".to_string(), + ), + }, + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: ntp1_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::BoundaryNtp( + blueprint_zone_type::BoundaryNtp { + address: "[::1]:80".parse().unwrap(), ntp_servers: vec![], dns_servers: vec![], domain: None, nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: ntp1_id, + id: ntp1_id.into_untyped_uuid(), }, name: "ntp1".parse().unwrap(), ip: ntp1_pip.into(), @@ -1368,30 +1362,30 @@ mod test { ) .unwrap(), }, - }, - ], - }, - ) - .expect("recording Omicron zones"); - inventory_builder - .found_sled_omicron_zones( - "sled2", - SledUuid::from_untyped_uuid(sled2.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![ - OmicronZoneConfig { - id: nexus_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: "[::1]:80".to_string(), + ), + }, + ], + }, + ); + blueprint_zones.insert( + sled2.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![ + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: nexus_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::Nexus( + blueprint_zone_type::Nexus { + internal_address: "[::1]:80".parse().unwrap(), external_ip: nexus_ip, external_tls: false, external_dns_servers: vec![], nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: nexus_id, + id: nexus_id.into_untyped_uuid(), }, name: "nexus".parse().unwrap(), ip: nexus_pip.into(), @@ -1405,19 +1399,22 @@ mod test { slot: 0, }, }, - }, - OmicronZoneConfig { - id: ntp2_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::BoundaryNtp { - address: "[::1]:80".to_string(), + ), + }, + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: ntp2_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::BoundaryNtp( + blueprint_zone_type::BoundaryNtp { + address: "[::1]:80".parse().unwrap(), ntp_servers: vec![], dns_servers: vec![], domain: None, nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: ntp2_id, + id: ntp2_id.into_untyped_uuid(), }, name: "ntp2".parse().unwrap(), ip: ntp2_pip.into(), @@ -1435,39 +1432,44 @@ mod test { ) .unwrap(), }, - }, - ], - }, - ) - .expect("recording Omicron zones"); - inventory_builder - .found_sled_omicron_zones( - "sled3", - SledUuid::from_untyped_uuid(sled3.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![OmicronZoneConfig { - id: ntp3_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::InternalNtp { - address: "[::1]:80".to_string(), + ), + }, + ], + }, + ); + blueprint_zones.insert( + sled3.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: ntp3_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::InternalNtp( + blueprint_zone_type::InternalNtp { + address: "[::1]:80".parse().unwrap(), ntp_servers: vec![], dns_servers: vec![], domain: None, }, - }], - }, - ) - .expect("recording Omicron zones"); - let blueprint = BlueprintBuilder::build_initial_from_collection_seeded( - &inventory_builder.build(), - *Generation::new(), - *Generation::new(), - planning_input.all_sled_ids(SledFilter::All), - "test suite", - (test_name, "initial blueprint"), - ) - .expect("failed to build blueprint"); + ), + }], + }, + ); + for zone_config in blueprint_zones.values_mut() { + zone_config.sort(); + } + let blueprint = Blueprint { + id: Uuid::new_v4(), + blueprint_zones, + blueprint_disks: BTreeMap::new(), + parent_blueprint_id: None, + internal_dns_version: *Generation::new(), + external_dns_version: *Generation::new(), + time_created: now_db_precision(), + creator: "test suite".to_string(), + comment: "test blueprint".to_string(), + }; let rack = datastore .rack_set_initialized( @@ -1497,23 +1499,23 @@ mod test { assert_eq!(observed_external_ips.len(), 4); let dns_external_ip = observed_external_ips .iter() - .find(|e| e.parent_id == Some(external_dns_id)) + .find(|e| e.parent_id == Some(external_dns_id.into_untyped_uuid())) .unwrap(); let nexus_external_ip = observed_external_ips .iter() - .find(|e| e.parent_id == Some(nexus_id)) + .find(|e| e.parent_id == Some(nexus_id.into_untyped_uuid())) .unwrap(); let ntp1_external_ip = observed_external_ips .iter() - .find(|e| e.parent_id == Some(ntp1_id)) + .find(|e| e.parent_id == Some(ntp1_id.into_untyped_uuid())) .unwrap(); let ntp2_external_ip = observed_external_ips .iter() - .find(|e| e.parent_id == Some(ntp2_id)) + .find(|e| e.parent_id == Some(ntp2_id.into_untyped_uuid())) .unwrap(); assert!(!observed_external_ips .iter() - .any(|e| e.parent_id == Some(ntp3_id))); + .any(|e| e.parent_id == Some(ntp3_id.into_untyped_uuid()))); assert!(dns_external_ip.is_service); assert_eq!(dns_external_ip.kind, IpKind::Floating); @@ -1600,16 +1602,9 @@ mod test { SledBuilder::new().id(TypedUuid::from_untyped_uuid(sled.id())), ) .expect("failed to add sled"); - let planning_input = system - .to_planning_input_builder() - .expect("failed to make planning input") - .build(); - let mut inventory_builder = system - .to_collection_builder() - .expect("failed to make collection builder"); - - let nexus_id1 = Uuid::new_v4(); - let nexus_id2 = Uuid::new_v4(); + + let nexus_id1 = OmicronZoneUuid::new_v4(); + let nexus_id2 = OmicronZoneUuid::new_v4(); let nexus_pip1 = NEXUS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); @@ -1618,25 +1613,26 @@ mod test { .unwrap(); let mut macs = MacAddr::iter_system(); - inventory_builder - .found_sled_omicron_zones( - "sled", - SledUuid::from_untyped_uuid(sled.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![ - OmicronZoneConfig { - id: nexus_id1, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: "[::1]:80".to_string(), + let mut blueprint_zones = BTreeMap::new(); + blueprint_zones.insert( + sled.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![ + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: nexus_id1, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::Nexus( + blueprint_zone_type::Nexus { + internal_address: "[::1]:80".parse().unwrap(), external_ip: nexus_ip_start.into(), external_tls: false, external_dns_servers: vec![], nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: nexus_id1, + id: nexus_id1.into_untyped_uuid(), }, name: "nexus1".parse().unwrap(), ip: nexus_pip1.into(), @@ -1650,19 +1646,22 @@ mod test { slot: 0, }, }, - }, - OmicronZoneConfig { - id: nexus_id2, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: "[::1]:80".to_string(), + ), + }, + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: nexus_id2, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::Nexus( + blueprint_zone_type::Nexus { + internal_address: "[::1]:80".parse().unwrap(), external_ip: nexus_ip_end.into(), external_tls: false, external_dns_servers: vec![], nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: nexus_id2, + id: nexus_id2.into_untyped_uuid(), }, name: "nexus2".parse().unwrap(), ip: nexus_pip2.into(), @@ -1676,11 +1675,11 @@ mod test { slot: 0, }, }, - }, - ], - }, - ) - .expect("recording Omicron zones"); + ), + }, + ], + }, + ); let datasets = vec![]; @@ -1706,15 +1705,20 @@ mod test { HashMap::from([("api.sys".to_string(), external_records.clone())]), ); - let blueprint = BlueprintBuilder::build_initial_from_collection_seeded( - &inventory_builder.build(), - *Generation::new(), - *Generation::new(), - planning_input.all_sled_ids(SledFilter::All), - "test suite", - (test_name, "initial blueprint"), - ) - .expect("failed to build blueprint"); + for zone_config in blueprint_zones.values_mut() { + zone_config.sort(); + } + let blueprint = Blueprint { + id: Uuid::new_v4(), + blueprint_zones, + blueprint_disks: BTreeMap::new(), + parent_blueprint_id: None, + internal_dns_version: *Generation::new(), + external_dns_version: *Generation::new(), + time_created: now_db_precision(), + creator: "test suite".to_string(), + comment: "test blueprint".to_string(), + }; let rack = datastore .rack_set_initialized( @@ -1866,38 +1870,32 @@ mod test { SledBuilder::new().id(TypedUuid::from_untyped_uuid(sled.id())), ) .expect("failed to add sled"); - let planning_input = system - .to_planning_input_builder() - .expect("failed to make planning input") - .build(); - let mut inventory_builder = system - .to_collection_builder() - .expect("failed to make collection builder"); let nexus_ip = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); - let nexus_id = Uuid::new_v4(); + let nexus_id = OmicronZoneUuid::new_v4(); let mut macs = MacAddr::iter_system(); - inventory_builder - .found_sled_omicron_zones( - "sled", - SledUuid::from_untyped_uuid(sled.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![OmicronZoneConfig { - id: nexus_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: "[::1]:80".to_string(), + let mut blueprint_zones = BTreeMap::new(); + blueprint_zones.insert( + sled.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: nexus_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::Nexus( + blueprint_zone_type::Nexus { + internal_address: "[::1]:80".parse().unwrap(), external_ip: nexus_ip, external_tls: false, external_dns_servers: vec![], nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: nexus_id, + id: nexus_id.into_untyped_uuid(), }, name: "nexus".parse().unwrap(), ip: nexus_pip.into(), @@ -1911,20 +1909,24 @@ mod test { slot: 0, }, }, - }], - }, - ) - .expect("recording Omicron zones"); - - let blueprint = BlueprintBuilder::build_initial_from_collection_seeded( - &inventory_builder.build(), - *Generation::new(), - *Generation::new(), - planning_input.all_sled_ids(SledFilter::All), - "test suite", - (test_name, "initial blueprint"), - ) - .expect("failed to build blueprint"); + ), + }], + }, + ); + for zone_config in blueprint_zones.values_mut() { + zone_config.sort(); + } + let blueprint = Blueprint { + id: Uuid::new_v4(), + blueprint_zones, + blueprint_disks: BTreeMap::new(), + parent_blueprint_id: None, + internal_dns_version: *Generation::new(), + external_dns_version: *Generation::new(), + time_created: now_db_precision(), + creator: "test suite".to_string(), + comment: "test blueprint".to_string(), + }; let result = datastore .rack_set_initialized( @@ -1964,44 +1966,37 @@ mod test { SledBuilder::new().id(TypedUuid::from_untyped_uuid(sled.id())), ) .expect("failed to add sled"); - let planning_input = system - .to_planning_input_builder() - .expect("failed to make planning input") - .build(); - let mut inventory_builder = system - .to_collection_builder() - .expect("failed to make collection builder"); // Request two services which happen to be using the same IP address. - let external_dns_id = Uuid::new_v4(); + let external_dns_id = OmicronZoneUuid::new_v4(); let external_dns_pip = DNS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); - let nexus_id = Uuid::new_v4(); + let nexus_id = OmicronZoneUuid::new_v4(); let nexus_pip = NEXUS_OPTE_IPV4_SUBNET .nth(NUM_INITIAL_RESERVED_IP_ADDRESSES as u32 + 1) .unwrap(); let mut macs = MacAddr::iter_system(); - inventory_builder - .found_sled_omicron_zones( - "sled", - SledUuid::from_untyped_uuid(sled.id()), - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![ - OmicronZoneConfig { - id: external_dns_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::ExternalDns { + let mut blueprint_zones = BTreeMap::new(); + blueprint_zones.insert( + sled.id(), + BlueprintZonesConfig { + generation: Generation::new().next(), + zones: vec![ + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: external_dns_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::ExternalDns( + blueprint_zone_type::ExternalDns { dataset: random_dataset(), - http_address: "[::1]:80".to_string(), - dns_address: SocketAddr::new(ip, 53) - .to_string(), + http_address: "[::1]:80".parse().unwrap(), + dns_address: SocketAddr::new(ip, 53), nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: external_dns_id, + id: external_dns_id.into_untyped_uuid(), }, name: "external-dns".parse().unwrap(), ip: external_dns_pip.into(), @@ -2015,19 +2010,22 @@ mod test { slot: 0, }, }, - }, - OmicronZoneConfig { - id: nexus_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: "[::1]:80".to_string(), + ), + }, + BlueprintZoneConfig { + disposition: BlueprintZoneDisposition::InService, + id: nexus_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: BlueprintZoneType::Nexus( + blueprint_zone_type::Nexus { + internal_address: "[::1]:80".parse().unwrap(), external_ip: ip, external_tls: false, external_dns_servers: vec![], nic: NetworkInterface { id: Uuid::new_v4(), kind: NetworkInterfaceKind::Service { - id: nexus_id, + id: nexus_id.into_untyped_uuid(), }, name: "nexus".parse().unwrap(), ip: nexus_pip.into(), @@ -2041,21 +2039,26 @@ mod test { slot: 0, }, }, - }, - ], - }, - ) - .expect("recording Omicron zones"); + ), + }, + ], + }, + ); - let blueprint = BlueprintBuilder::build_initial_from_collection_seeded( - &inventory_builder.build(), - *Generation::new(), - *Generation::new(), - planning_input.all_sled_ids(SledFilter::All), - "test suite", - (test_name, "initial blueprint"), - ) - .expect("failed to build blueprint"); + for zone_config in blueprint_zones.values_mut() { + zone_config.sort(); + } + let blueprint = Blueprint { + id: Uuid::new_v4(), + blueprint_zones, + blueprint_disks: BTreeMap::new(), + parent_blueprint_id: None, + internal_dns_version: *Generation::new(), + external_dns_version: *Generation::new(), + time_created: now_db_precision(), + creator: "test suite".to_string(), + comment: "test blueprint".to_string(), + }; let result = datastore .rack_set_initialized( diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 79eb86fe09..c93ac94408 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -465,7 +465,7 @@ mod test { use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; - use nexus_inventory::CollectionBuilder; + use nexus_inventory::now_db_precision; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::blueprint_builder::EnsureMultiple; use nexus_reconfigurator_planning::example::example; @@ -476,13 +476,9 @@ mod test { use nexus_types::deployment::BlueprintTarget; use nexus_types::deployment::BlueprintZoneConfig; use nexus_types::deployment::BlueprintZoneDisposition; - use nexus_types::deployment::SledDisk; - use nexus_types::deployment::SledFilter; - use nexus_types::deployment::SledResources; + use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::external_api::params; use nexus_types::external_api::shared; - use nexus_types::external_api::views::PhysicalDiskPolicy; - use nexus_types::external_api::views::PhysicalDiskState; use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsConfigParams; use nexus_types::internal_api::params::DnsConfigZone; @@ -497,11 +493,9 @@ mod test { use omicron_common::address::SLED_PREFIX; use omicron_common::api::external::Generation; use omicron_common::api::external::IdentityMetadataCreateParams; - use omicron_common::disk::DiskIdentity; use omicron_test_utils::dev::test_setup_log; + use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::OmicronZoneUuid; - use omicron_uuid_kinds::PhysicalDiskUuid; - use omicron_uuid_kinds::ZpoolUuid; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::collections::HashMap; @@ -510,23 +504,11 @@ mod test { use std::net::Ipv6Addr; use std::net::SocketAddrV6; use std::sync::Arc; + use uuid::Uuid; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; - fn blueprint_empty() -> Blueprint { - let builder = CollectionBuilder::new("test-suite"); - let collection = builder.build(); - BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - Generation::new(), - std::iter::empty(), - "test-suite", - ) - .expect("failed to generate empty blueprint") - } - fn dns_config_empty() -> DnsConfigParams { DnsConfigParams { generation: 1, @@ -541,7 +523,10 @@ mod test { /// test blueprint_internal_dns_config(): trivial case of an empty blueprint #[test] fn test_blueprint_internal_dns_empty() { - let blueprint = blueprint_empty(); + let blueprint = BlueprintBuilder::build_empty_with_sleds( + std::iter::empty(), + "test-suite", + ); let blueprint_dns = blueprint_internal_dns_config( &blueprint, &BTreeMap::new(), @@ -566,45 +551,46 @@ mod test { let rack_subnet = ipnet::Ipv6Net::new(rack_subnet_base, RACK_PREFIX).unwrap(); let possible_sled_subnets = rack_subnet.subnets(SLED_PREFIX).unwrap(); - // Ignore sleds with no associated zones in the inventory. - // This is included in the "representative" collection, but it's - // not allowed by BlueprintBuilder::build_initial_from_collection(). - let policy_sleds = collection - .omicron_zones - .keys() - .zip(possible_sled_subnets) - .map(|(sled_id, subnet)| { - let sled_resources = SledResources { - zpools: BTreeMap::from([( - ZpoolUuid::new_v4(), - SledDisk { - disk_identity: DiskIdentity { - vendor: String::from("v"), - serial: format!("s-{sled_id}"), - model: String::from("m"), - }, - disk_id: PhysicalDiskUuid::new_v4(), - policy: PhysicalDiskPolicy::InService, - state: PhysicalDiskState::Active, - }, - )]), - subnet: Ipv6Subnet::new(subnet.network()), - }; - (*sled_id, sled_resources) - }) - .collect::>(); + + // Convert the inventory `OmicronZonesConfig`s into + // `BlueprintZonesConfig`. This is going to get more painful over time + // as we add to blueprints, but for now we can make this work. + let mut blueprint_zones = BTreeMap::new(); + for (sled_id, zones_config) in collection.omicron_zones { + blueprint_zones.insert( + sled_id.into_untyped_uuid(), + BlueprintZonesConfig { + generation: zones_config.zones.generation, + zones: zones_config + .zones + .zones + .into_iter() + .map(|config| { + BlueprintZoneConfig::from_omicron_zone_config( + config, + BlueprintZoneDisposition::InService, + ) + .expect("failed to convert zone config") + }) + .collect(), + }, + ); + } let dns_empty = dns_config_empty(); let initial_dns_generation = Generation::from(u32::try_from(dns_empty.generation).unwrap()); - let mut blueprint = BlueprintBuilder::build_initial_from_collection( - &collection, - initial_dns_generation, - Generation::new(), - policy_sleds.keys().copied(), - "test-suite", - ) - .expect("failed to build initial blueprint"); + let mut blueprint = Blueprint { + id: Uuid::new_v4(), + blueprint_zones, + blueprint_disks: BTreeMap::new(), + parent_blueprint_id: None, + internal_dns_version: initial_dns_generation, + external_dns_version: Generation::new(), + time_created: now_db_precision(), + creator: "test-suite".to_string(), + comment: "test blueprint".to_string(), + }; // To make things slightly more interesting, let's add a zone that's // not currently in service. @@ -630,18 +616,23 @@ mod test { // To generate the blueprint's DNS config, we need to make up a // different set of information about the Quiesced fake system. - let sleds_by_id = policy_sleds - .iter() + let sleds_by_id = blueprint + .blueprint_zones + .keys() + .zip(possible_sled_subnets) .enumerate() - .map(|(i, (sled_id, sled_resources))| { + .map(|(i, (sled_id, subnet))| { + let sled_id = SledUuid::from_untyped_uuid(*sled_id); let sled_info = Sled { - id: *sled_id, - sled_agent_address: get_sled_address(sled_resources.subnet), + id: sled_id, + sled_agent_address: get_sled_address(Ipv6Subnet::new( + subnet.network(), + )), // The first two of these (arbitrarily) will be marked // Scrimlets. is_scrimlet: i < 2, }; - (*sled_id, sled_info) + (sled_id, sled_info) }) .collect(); @@ -693,7 +684,8 @@ mod test { .iter() .filter_map(|(sled_id, sled)| { if sled.is_scrimlet { - let sled_subnet = policy_sleds.get(sled_id).unwrap().subnet; + let sled_subnet = + sleds_by_id.get(sled_id).unwrap().subnet(); let switch_zone_ip = get_switch_zone_address(sled_subnet); Some((switch_zone_ip, *sled_id)) } else { @@ -829,16 +821,9 @@ mod test { async fn test_blueprint_external_dns_basic() { static TEST_NAME: &str = "test_blueprint_external_dns_basic"; let logctx = test_setup_log(TEST_NAME); - let (collection, input) = example(&logctx.log, TEST_NAME, 5); - let initial_external_dns_generation = Generation::new(); - let mut blueprint = BlueprintBuilder::build_initial_from_collection( - &collection, - Generation::new(), - initial_external_dns_generation, - input.all_sled_ids(SledFilter::All), - "test suite", - ) - .expect("failed to generate initial blueprint"); + let (_, _, mut blueprint) = example(&logctx.log, TEST_NAME, 5); + blueprint.internal_dns_version = Generation::new(); + blueprint.external_dns_version = Generation::new(); let my_silo = Silo::new(params::SiloCreate { identity: IdentityMetadataCreateParams { diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index e1621a11c8..a58b96162b 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -24,13 +24,11 @@ use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::deployment::DiskFilter; -use nexus_types::deployment::InvalidOmicronZoneType; use nexus_types::deployment::OmicronZoneDataset; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledFilter; use nexus_types::deployment::SledResources; use nexus_types::deployment::ZpoolName; -use nexus_types::inventory::Collection; use omicron_common::address::get_internal_dns_server_addresses; use omicron_common::address::get_sled_address; use omicron_common::address::get_switch_zone_address; @@ -92,8 +90,6 @@ pub enum Error { ExhaustedNexusIps, #[error("programming error in planner")] Planner(#[from] anyhow::Error), - #[error("invalid OmicronZoneType in collection")] - InvalidOmicronZoneType(#[from] InvalidOmicronZoneType), } /// Describes whether an idempotent "ensure" operation resulted in action taken @@ -129,11 +125,11 @@ fn zpool_id_to_external_name(zpool_id: ZpoolUuid) -> anyhow::Result { /// /// There are two basic ways to assemble a new blueprint: /// -/// 1. Build one directly from a collection. Such blueprints have no parent -/// blueprint. They are not customizable. Use -/// [`BlueprintBuilder::build_initial_from_collection`] for this. This would -/// generally only be used once in the lifetime of a rack, to assemble the -/// first blueprint. +/// 1. Build one directly. This would generally only be used once in the +/// lifetime of a rack, to assemble the first blueprint during rack setup. +/// It is also common in tests. To start with a blueprint that contains an +/// empty zone config for some number of sleds, use +/// [`BlueprintBuilder::build_empty_with_sleds`]. /// /// 2. Build one _from_ another blueprint, called the "parent", making changes /// as desired. Use [`BlueprintBuilder::new_based_on`] for this. Once the @@ -174,102 +170,57 @@ pub struct BlueprintBuilder<'a> { } impl<'a> BlueprintBuilder<'a> { - /// Directly construct a `Blueprint` from the contents of a particular - /// collection (representing no changes from the collection state) - pub fn build_initial_from_collection( - collection: &Collection, - internal_dns_version: Generation, - external_dns_version: Generation, - all_sleds: impl Iterator, + /// Directly construct a `Blueprint` that contains an empty zone config for + /// the given sleds. + pub fn build_empty_with_sleds( + sled_ids: impl Iterator, creator: &str, - ) -> Result { - Self::build_initial_impl( - collection, - internal_dns_version, - external_dns_version, - all_sleds, + ) -> Blueprint { + Self::build_empty_with_sleds_impl( + sled_ids, creator, BlueprintBuilderRng::new(), ) } - /// A version of [`Self::build_initial_from_collection`] that allows the + /// A version of [`Self::build_empty_with_sleds`] that allows the /// blueprint ID to be generated from a random seed. - pub fn build_initial_from_collection_seeded( - collection: &Collection, - internal_dns_version: Generation, - external_dns_version: Generation, - all_sleds: impl Iterator, + pub fn build_empty_with_sleds_seeded( + sled_ids: impl Iterator, creator: &str, seed: H, - ) -> Result { + ) -> Blueprint { let mut rng = BlueprintBuilderRng::new(); rng.set_seed(seed); - Self::build_initial_impl( - collection, - internal_dns_version, - external_dns_version, - all_sleds, - creator, - rng, - ) + Self::build_empty_with_sleds_impl(sled_ids, creator, rng) } - fn build_initial_impl( - collection: &Collection, - internal_dns_version: Generation, - external_dns_version: Generation, - all_sleds: impl Iterator, + fn build_empty_with_sleds_impl( + sled_ids: impl Iterator, creator: &str, mut rng: BlueprintBuilderRng, - ) -> Result { - let blueprint_zones = all_sleds + ) -> Blueprint { + let blueprint_zones = sled_ids .map(|sled_id| { - let zones = collection - .omicron_zones - .get(&sled_id) - .map(|z| &z.zones) - .ok_or_else(|| { - // We should not find a sled that's supposed to be - // in-service but is not part of the inventory. It's - // not that that can't ever happen. This could happen - // when a sled is first being added to the system. Of - // course it could also happen if this sled agent failed - // our inventory request. But this is the initial - // blueprint (so this shouldn't be the "add sled" case) - // and we want to get it right (so we don't want to - // leave out sleds whose sled agent happened to be down - // when we tried to do this). The operator (or, more - // likely, a support person) will have to sort out - // what's going on if this happens. - Error::Planner(anyhow!( - "building initial blueprint: sled {:?} is \ - supposed to be in service but has no zones \ - in inventory", - sled_id - )) - })?; - let config = - BlueprintZonesConfig::initial_from_collection(&zones)?; - - Ok(( - // TODO-cleanup use `TypedUuid` everywhere - sled_id.into_untyped_uuid(), - config, - )) + let config = BlueprintZonesConfig { + generation: Generation::new(), + zones: Vec::new(), + }; + (sled_id.into_untyped_uuid(), config) }) - .collect::>()?; - Ok(Blueprint { + .collect::>(); + let num_sleds = blueprint_zones.len(); + Blueprint { id: rng.blueprint_rng.next(), blueprint_zones, blueprint_disks: BTreeMap::new(), parent_blueprint_id: None, - internal_dns_version, - external_dns_version, + internal_dns_version: Generation::new(), + external_dns_version: Generation::new(), time_created: now_db_precision(), creator: creator.to_owned(), - comment: format!("from collection {}", collection.id), - }) + comment: format!("starting blueprint with {num_sleds} empty sleds"), + } } /// Construct a new `BlueprintBuilder` based on a previous blueprint, @@ -1206,7 +1157,6 @@ pub mod test { use nexus_types::deployment::BlueprintZoneFilter; use omicron_common::address::IpRange; use omicron_test_utils::dev::test_setup_log; - use sled_agent_client::types::OmicronZoneType; use std::collections::BTreeSet; use test_strategy::proptest; @@ -1239,18 +1189,8 @@ pub mod test { // describes no changes. static TEST_NAME: &str = "blueprint_builder_test_initial"; let logctx = test_setup_log(TEST_NAME); - let (collection, input) = + let (collection, input, blueprint_initial) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - let blueprint_initial = - BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "the_test", - TEST_NAME, - ) - .expect("failed to create initial blueprint"); verify_blueprint(&blueprint_initial); let diff = @@ -1425,21 +1365,14 @@ pub mod test { fn test_add_physical_disks() { static TEST_NAME: &str = "blueprint_builder_test_add_physical_disks"; let logctx = test_setup_log(TEST_NAME); - let (collection, input) = - example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); + let (_, input, _) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - // We don't care about the DNS versions here. - let internal_dns_version = Generation::new(); - let external_dns_version = Generation::new(); - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - internal_dns_version, - external_dns_version, + // Start with an empty blueprint (sleds with no zones). + let parent = BlueprintBuilder::build_empty_with_sleds_seeded( input.all_sled_ids(SledFilter::All), "test", TEST_NAME, - ) - .expect("failed to create initial blueprint"); + ); { // We start empty, and can add a disk @@ -1477,33 +1410,19 @@ pub mod test { static TEST_NAME: &str = "blueprint_builder_test_add_nexus_with_no_existing_nexus_zones"; let logctx = test_setup_log(TEST_NAME); - let (mut collection, input) = - example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - - // We don't care about the DNS versions here. - let internal_dns_version = Generation::new(); - let external_dns_version = Generation::new(); - - // Adding a new Nexus zone currently requires copying settings from an - // existing Nexus zone. If we remove all Nexus zones from the - // collection, create a blueprint, then try to add a Nexus zone, it - // should fail. - for zones in collection.omicron_zones.values_mut() { - zones.zones.zones.retain(|z| { - !matches!(z.zone_type, OmicronZoneType::Nexus { .. }) - }); - } - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - internal_dns_version, - external_dns_version, + // Discard the example blueprint and start with an empty one. + let (collection, input, _) = + example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); + let parent = BlueprintBuilder::build_empty_with_sleds_seeded( input.all_sled_ids(SledFilter::All), "test", TEST_NAME, - ) - .expect("failed to create initial blueprint"); + ); + // Adding a new Nexus zone currently requires copying settings from an + // existing Nexus zone. `parent` has no zones, so we should fail if we + // try to add a Nexus zone. let mut builder = BlueprintBuilder::new_based_on( &logctx.log, &parent, @@ -1536,13 +1455,9 @@ pub mod test { fn test_add_nexus_error_cases() { static TEST_NAME: &str = "blueprint_builder_test_add_nexus_error_cases"; let logctx = test_setup_log(TEST_NAME); - let (mut collection, input) = + let (mut collection, input, mut parent) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - // We don't care about the DNS versions here. - let internal_dns_version = Generation::new(); - let external_dns_version = Generation::new(); - // Remove the Nexus zone from one of the sleds so that // `sled_ensure_zone_nexus` can attempt to add a Nexus zone to // `sled_id`. @@ -1550,27 +1465,22 @@ pub mod test { let mut selected_sled_id = None; for (sled_id, zones) in &mut collection.omicron_zones { let nzones_before_retain = zones.zones.zones.len(); - zones.zones.zones.retain(|z| { - !matches!(z.zone_type, OmicronZoneType::Nexus { .. }) - }); + zones.zones.zones.retain(|z| !z.zone_type.is_nexus()); if zones.zones.zones.len() < nzones_before_retain { selected_sled_id = Some(*sled_id); + // Also remove this zone from the blueprint. + parent + .blueprint_zones + .get_mut(sled_id.as_untyped_uuid()) + .expect("missing sled") + .zones + .retain(|z| !z.zone_type.is_nexus()); break; } } selected_sled_id.expect("found no sleds with Nexus zone") }; - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - internal_dns_version, - external_dns_version, - input.all_sled_ids(SledFilter::All), - "test", - TEST_NAME, - ) - .expect("failed to create initial blueprint"); - { // Attempting to add Nexus to the sled we removed it from (with no // other changes to the environment) should succeed. @@ -1657,7 +1567,7 @@ pub mod test { "blueprint_builder_test_invalid_parent_blueprint_\ two_zones_with_same_external_ip"; let logctx = test_setup_log(TEST_NAME); - let (mut collection, input) = + let (_, input, mut parent) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); // We should fail if the parent blueprint claims to contain two @@ -1667,10 +1577,12 @@ pub mod test { let mut found_second_nexus_zone = false; let mut nexus_external_ip = None; - 'outer: for zones in collection.omicron_zones.values_mut() { - for z in zones.zones.zones.iter_mut() { - if let OmicronZoneType::Nexus { external_ip, .. } = - &mut z.zone_type + 'outer: for zones in parent.blueprint_zones.values_mut() { + for z in zones.zones.iter_mut() { + if let BlueprintZoneType::Nexus(blueprint_zone_type::Nexus { + external_ip, + .. + }) = &mut z.zone_type { if let Some(ip) = nexus_external_ip { *external_ip = ip; @@ -1685,16 +1597,6 @@ pub mod test { } assert!(found_second_nexus_zone, "only one Nexus zone present?"); - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "test", - TEST_NAME, - ) - .unwrap(); - match BlueprintBuilder::new_based_on( &logctx.log, &parent, @@ -1717,7 +1619,7 @@ pub mod test { "blueprint_builder_test_invalid_parent_blueprint_\ two_nexus_zones_with_same_nic_ip"; let logctx = test_setup_log(TEST_NAME); - let (mut collection, input) = + let (_, input, mut parent) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); // We should fail if the parent blueprint claims to contain two @@ -1727,9 +1629,13 @@ pub mod test { let mut found_second_nexus_zone = false; let mut nexus_nic_ip = None; - 'outer: for zones in collection.omicron_zones.values_mut() { - for z in zones.zones.zones.iter_mut() { - if let OmicronZoneType::Nexus { nic, .. } = &mut z.zone_type { + 'outer: for zones in parent.blueprint_zones.values_mut() { + for z in zones.zones.iter_mut() { + if let BlueprintZoneType::Nexus(blueprint_zone_type::Nexus { + nic, + .. + }) = &mut z.zone_type + { if let Some(ip) = nexus_nic_ip { nic.ip = ip; found_second_nexus_zone = true; @@ -1743,16 +1649,6 @@ pub mod test { } assert!(found_second_nexus_zone, "only one Nexus zone present?"); - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "test", - TEST_NAME, - ) - .unwrap(); - match BlueprintBuilder::new_based_on( &logctx.log, &parent, @@ -1775,7 +1671,7 @@ pub mod test { "blueprint_builder_test_invalid_parent_blueprint_\ two_zones_with_same_vnic_mac"; let logctx = test_setup_log(TEST_NAME); - let (mut collection, input) = + let (_, input, mut parent) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); // We should fail if the parent blueprint claims to contain two @@ -1785,9 +1681,13 @@ pub mod test { let mut found_second_nexus_zone = false; let mut nexus_nic_mac = None; - 'outer: for zones in collection.omicron_zones.values_mut() { - for z in zones.zones.zones.iter_mut() { - if let OmicronZoneType::Nexus { nic, .. } = &mut z.zone_type { + 'outer: for zones in parent.blueprint_zones.values_mut() { + for z in zones.zones.iter_mut() { + if let BlueprintZoneType::Nexus(blueprint_zone_type::Nexus { + nic, + .. + }) = &mut z.zone_type + { if let Some(mac) = nexus_nic_mac { nic.mac = mac; found_second_nexus_zone = true; @@ -1801,16 +1701,6 @@ pub mod test { } assert!(found_second_nexus_zone, "only one Nexus zone present?"); - let parent = BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "test", - TEST_NAME, - ) - .unwrap(); - match BlueprintBuilder::new_based_on( &logctx.log, &parent, diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/zones.rs b/nexus/reconfigurator/planning/src/blueprint_builder/zones.rs index 5f8c0625a7..c0e0918503 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/zones.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/zones.rs @@ -217,16 +217,7 @@ mod tests { let logctx = test_setup_log(TEST_NAME); let mut example = ExampleSystem::new(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - let blueprint_initial = - BlueprintBuilder::build_initial_from_collection_seeded( - &example.collection, - Generation::new(), - Generation::new(), - example.input.all_sled_ids(SledFilter::All), - "the_test", - TEST_NAME, - ) - .expect("creating initial blueprint"); + let blueprint_initial = example.blueprint; // Add a completely bare sled to the input. let (new_sled_id, input2) = { diff --git a/nexus/reconfigurator/planning/src/example.rs b/nexus/reconfigurator/planning/src/example.rs index 2c96e1e5a8..760e880b8d 100644 --- a/nexus/reconfigurator/planning/src/example.rs +++ b/nexus/reconfigurator/planning/src/example.rs @@ -15,11 +15,9 @@ use nexus_types::deployment::OmicronZoneNic; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledFilter; use nexus_types::inventory::Collection; -use omicron_common::api::external::Generation; use omicron_uuid_kinds::ExternalIpUuid; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::SledKind; -use sled_agent_client::types::OmicronZonesConfig; use typed_rng::TypedUuidRng; pub struct ExampleSystem { @@ -53,37 +51,14 @@ impl ExampleSystem { let mut input_builder = system .to_planning_input_builder() .expect("failed to make planning input builder"); - let mut inventory_builder = - system.to_collection_builder().expect("failed to build collection"); let base_input = input_builder.clone().build(); - // For each sled, have it report 0 zones in the initial inventory. - // This will enable us to build a blueprint from the initial - // inventory, which we can then use to build new blueprints. - for &sled_id in &sled_ids { - inventory_builder - .found_sled_omicron_zones( - "fake sled agent", - sled_id, - OmicronZonesConfig { - generation: Generation::new(), - zones: vec![], - }, - ) - .expect("recording Omicron zones"); - } - - let empty_zone_inventory = inventory_builder.build(); - let initial_blueprint = - BlueprintBuilder::build_initial_from_collection_seeded( - &empty_zone_inventory, - Generation::new(), - Generation::new(), - base_input.all_sled_ids(SledFilter::All), - "test suite", - (test_name, "ExampleSystem initial"), - ) - .unwrap(); + // Start with an empty blueprint containing only our sleds, no zones. + let initial_blueprint = BlueprintBuilder::build_empty_with_sleds_seeded( + base_input.all_sled_ids(SledFilter::All), + "test suite", + (test_name, "ExampleSystem initial"), + ); // Now make a blueprint and collection with some zones on each sled. let mut builder = BlueprintBuilder::new_based_on( @@ -176,7 +151,8 @@ impl ExampleSystem { } } -/// Returns a collection and planning input describing a pretty simple system. +/// Returns a collection, planning input, and blueprint describing a pretty +/// simple system. /// /// The test name is used as the RNG seed. /// @@ -187,7 +163,7 @@ pub fn example( log: &slog::Logger, test_name: &str, nsleds: usize, -) -> (Collection, PlanningInput) { +) -> (Collection, PlanningInput, Blueprint) { let example = ExampleSystem::new(log, test_name, nsleds); - (example.collection, example.input) + (example.collection, example.input, example.blueprint) } diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 46716754a1..a252f9b821 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -396,7 +396,6 @@ mod test { use super::Planner; use crate::blueprint_builder::test::verify_blueprint; use crate::blueprint_builder::test::DEFAULT_N_SLEDS; - use crate::blueprint_builder::BlueprintBuilder; use crate::example::example; use crate::example::ExampleSystem; use crate::system::SledBuilder; @@ -410,7 +409,6 @@ mod test { use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::DiffSledModified; - use nexus_types::deployment::SledFilter; use nexus_types::external_api::views::SledPolicy; use nexus_types::external_api::views::SledProvisionPolicy; use nexus_types::external_api::views::SledState; @@ -426,34 +424,18 @@ mod test { static TEST_NAME: &str = "planner_basic_add_sled"; let logctx = test_setup_log(TEST_NAME); - // For our purposes, we don't care about the DNS generations. - let internal_dns_version = Generation::new(); - let external_dns_version = Generation::new(); - - // Use our example inventory collection. + // Use our example system. let mut example = ExampleSystem::new(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - - // Build the initial blueprint. We don't bother verifying it here - // because there's a separate test for that. - let blueprint1 = - BlueprintBuilder::build_initial_from_collection_seeded( - &example.collection, - internal_dns_version, - external_dns_version, - example.input.all_sled_ids(SledFilter::All), - "the_test", - (TEST_NAME, "bp1"), - ) - .expect("failed to create initial blueprint"); - verify_blueprint(&blueprint1); + let blueprint1 = &example.blueprint; + verify_blueprint(blueprint1); // Now run the planner. It should do nothing because our initial // system didn't have any issues that the planner currently knows how to // fix. let blueprint2 = Planner::new_based_on( logctx.log.clone(), - &blueprint1, + blueprint1, &example.input, "no-op?", &example.collection, @@ -463,7 +445,7 @@ mod test { .plan() .expect("failed to plan"); - let diff = blueprint2.diff_since_blueprint(&blueprint1).unwrap(); + let diff = blueprint2.diff_since_blueprint(blueprint1).unwrap(); println!("1 -> 2 (expected no changes):\n{}", diff.display()); assert_eq!(diff.sleds_added().len(), 0); assert_eq!(diff.sleds_removed().len(), 0); @@ -626,14 +608,10 @@ mod test { static TEST_NAME: &str = "planner_add_multiple_nexus_to_one_sled"; let logctx = test_setup_log(TEST_NAME); - // For our purposes, we don't care about the DNS generations. - let internal_dns_version = Generation::new(); - let external_dns_version = Generation::new(); - - // Use our example inventory collection as a starting point, but strip - // it down to just one sled. - let (sled_id, collection, input) = { - let (mut collection, input) = + // Use our example system as a starting point, but strip it down to just + // one sled. + let (sled_id, blueprint1, collection, input) = { + let (mut collection, input, mut blueprint) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); // Pick one sled ID to keep and remove the rest. @@ -646,22 +624,13 @@ mod test { assert_eq!(collection.sled_agents.len(), 1); assert_eq!(collection.omicron_zones.len(), 1); + blueprint + .blueprint_zones + .retain(|k, _v| keep_sled_id.as_untyped_uuid() == k); - (keep_sled_id, collection, builder.build()) + (keep_sled_id, blueprint, collection, builder.build()) }; - // Build the initial blueprint. - let blueprint1 = - BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - internal_dns_version, - external_dns_version, - input.all_sled_ids(SledFilter::All), - "the_test", - (TEST_NAME, "bp1"), - ) - .expect("failed to create initial blueprint"); - // This blueprint should only have 1 Nexus instance on the one sled we // kept. assert_eq!(blueprint1.blueprint_zones.len(), 1); @@ -724,22 +693,10 @@ mod test { "planner_spread_additional_nexus_zones_across_sleds"; let logctx = test_setup_log(TEST_NAME); - // Use our example inventory collection as a starting point. - let (collection, input) = + // Use our example system as a starting point. + let (collection, input, blueprint1) = example(&logctx.log, TEST_NAME, DEFAULT_N_SLEDS); - // Build the initial blueprint. - let blueprint1 = - BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "the_test", - (TEST_NAME, "bp1"), - ) - .expect("failed to create initial blueprint"); - // This blueprint should only have 3 Nexus zones: one on each sled. assert_eq!(blueprint1.blueprint_zones.len(), 3); for sled_config in blueprint1.blueprint_zones.values() { @@ -811,25 +768,14 @@ mod test { "planner_nexus_allocation_skips_nonprovisionable_sleds"; let logctx = test_setup_log(TEST_NAME); - // Use our example inventory collection as a starting point. + // Use our example system as a starting point. // // Request two extra sleds here so we test non-provisionable, expunged, // and decommissioned sleds. (When we add more kinds of // non-provisionable states in the future, we'll have to add more // sleds.) - let (collection, input) = example(&logctx.log, TEST_NAME, 5); - - // Build the initial blueprint. - let blueprint1 = - BlueprintBuilder::build_initial_from_collection_seeded( - &collection, - Generation::new(), - Generation::new(), - input.all_sled_ids(SledFilter::All), - "the_test", - (TEST_NAME, "bp1"), - ) - .expect("failed to create initial blueprint"); + let (collection, input, blueprint1) = + example(&logctx.log, TEST_NAME, 5); // This blueprint should only have 5 Nexus zones: one on each sled. assert_eq!(blueprint1.blueprint_zones.len(), 5); diff --git a/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt b/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt index fe56567f65..b421b8f383 100644 --- a/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt +++ b/nexus/reconfigurator/planning/tests/output/blueprint_builder_initial_diff.txt @@ -1,5 +1,5 @@ from: collection 094d362b-7d79-49e7-a244-134276cca8fe -to: blueprint 9d2c007b-46f1-4ff2-8b4c-8a5767030f76 +to: blueprint e4aeb3b3-272f-4967-be34-2d34daa46aa1 ------------------------------------------------------------------------------------------------------ zone type zone ID disposition underlay IP status diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt index 005d963475..ecc5b125d9 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt @@ -1,4 +1,4 @@ -from: blueprint 55502b1b-e255-438b-a16a-2680a4b5f962 +from: blueprint 4d4e6c38-cd95-4c4e-8f45-6af4d686964b to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 -------------------------------------------------------------------------------------------------------- diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt index aa4da01852..623bf0a756 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt @@ -1,5 +1,5 @@ blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 -parent: 55502b1b-e255-438b-a16a-2680a4b5f962 +parent: 4d4e6c38-cd95-4c4e-8f45-6af4d686964b -------------------------------------------------------------------------------------------- zone type zone ID disposition underlay IP diff --git a/nexus/src/app/deployment.rs b/nexus/src/app/deployment.rs index 5f2d316efd..4d17cf43b0 100644 --- a/nexus/src/app/deployment.rs +++ b/nexus/src/app/deployment.rs @@ -7,7 +7,6 @@ use nexus_db_model::DnsGroup; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; -use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::Planner; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_types::deployment::Blueprint; @@ -15,7 +14,6 @@ use nexus_types::deployment::BlueprintMetadata; use nexus_types::deployment::BlueprintTarget; use nexus_types::deployment::BlueprintTargetSet; use nexus_types::deployment::PlanningInput; -use nexus_types::deployment::SledFilter; use nexus_types::inventory::Collection; use omicron_common::address::NEXUS_REDUNDANCY; use omicron_common::api::external::CreateResult; @@ -26,7 +24,6 @@ use omicron_common::api::external::InternalContext; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; use omicron_common::api::external::LookupType; -use omicron_uuid_kinds::CollectionUuid; use slog_error_chain::InlineErrorChain; use uuid::Uuid; @@ -205,35 +202,6 @@ impl super::Nexus { self.db_datastore.blueprint_insert(opctx, blueprint).await } - pub async fn blueprint_generate_from_collection( - &self, - opctx: &OpContext, - collection_id: CollectionUuid, - ) -> CreateResult { - let collection = self - .datastore() - .inventory_collection_read(opctx, collection_id) - .await?; - let planning_context = self.blueprint_planning_context(opctx).await?; - let blueprint = BlueprintBuilder::build_initial_from_collection( - &collection, - planning_context.planning_input.internal_dns_version(), - planning_context.planning_input.external_dns_version(), - planning_context.planning_input.all_sled_ids(SledFilter::All), - &planning_context.creator, - ) - .map_err(|error| { - Error::internal_error(&format!( - "error generating initial blueprint from collection {}: {}", - collection_id, - InlineErrorChain::new(&error) - )) - })?; - - self.blueprint_add(&opctx, &blueprint).await?; - Ok(blueprint) - } - pub async fn blueprint_create_regenerate( &self, opctx: &OpContext, diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index 35ec5167f9..c2582daaf4 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -49,7 +49,6 @@ use omicron_common::api::internal::nexus::RepairProgress; use omicron_common::api::internal::nexus::RepairStartInfo; use omicron_common::api::internal::nexus::SledInstanceState; use omicron_common::update::ArtifactId; -use omicron_uuid_kinds::CollectionUuid; use omicron_uuid_kinds::DownstairsKind; use omicron_uuid_kinds::TypedUuid; use omicron_uuid_kinds::UpstairsKind; @@ -102,7 +101,6 @@ pub(crate) fn internal_api() -> NexusApiDescription { api.register(blueprint_target_view)?; api.register(blueprint_target_set)?; api.register(blueprint_target_set_enabled)?; - api.register(blueprint_generate_from_collection)?; api.register(blueprint_regenerate)?; api.register(blueprint_import)?; @@ -956,33 +954,6 @@ async fn blueprint_target_set_enabled( // Generating blueprints -#[derive(Debug, Deserialize, JsonSchema)] -struct CollectionId { - collection_id: CollectionUuid, -} - -/// Generates a new blueprint matching the specified inventory collection -#[endpoint { - method = POST, - path = "/deployment/blueprints/generate-from-collection", -}] -async fn blueprint_generate_from_collection( - rqctx: RequestContext>, - params: TypedBody, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let collection_id = params.into_inner().collection_id; - let result = nexus - .blueprint_generate_from_collection(&opctx, collection_id) - .await?; - Ok(HttpResponseOk(result)) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - /// Generates a new blueprint for the current system, re-evaluating anything /// that's changed since the last one was generated #[endpoint { diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 7dbaf9aa79..3a8e6e4066 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -203,11 +203,7 @@ impl Blueprint { /// /// Note that collections do not include information about zone /// disposition, so it is assumed that all zones in the collection have the - /// [`InService`](BlueprintZoneDisposition::InService) disposition. (This - /// is the same assumption made by - /// [`BlueprintZonesConfig::initial_from_collection`]. The logic here may - /// also be expanded to handle cases where not all zones in the collection - /// are in-service.) + /// [`InService`](BlueprintZoneDisposition::InService) disposition. pub fn diff_since_collection( &self, before: &Collection, @@ -324,37 +320,6 @@ pub struct BlueprintZonesConfig { } impl BlueprintZonesConfig { - /// Constructs a new [`BlueprintZonesConfig`] from a collection's zones. - /// - /// For the initial blueprint, all zones within a collection are assumed to - /// have the [`InService`](BlueprintZoneDisposition::InService) - /// disposition. - pub fn initial_from_collection( - collection: &OmicronZonesConfig, - ) -> Result { - let zones = collection - .zones - .iter() - .map(|z| { - BlueprintZoneConfig::from_omicron_zone_config( - z.clone(), - BlueprintZoneDisposition::InService, - ) - }) - .collect::>()?; - - let mut ret = Self { - // An initial `BlueprintZonesConfig` reuses the generation from - // `OmicronZonesConfig`. - generation: collection.generation, - zones, - }; - // For testing, it's helpful for zones to be in sorted order. - ret.sort(); - - Ok(ret) - } - /// Sorts the list of zones stored in this configuration. /// /// This is not strictly necessary. But for testing (particularly snapshot diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 0383c9cbd2..593a841bfc 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -455,40 +455,6 @@ } } }, - "/deployment/blueprints/generate-from-collection": { - "post": { - "summary": "Generates a new blueprint matching the specified inventory collection", - "operationId": "blueprint_generate_from_collection", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CollectionId" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Blueprint" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/deployment/blueprints/import": { "post": { "summary": "Imports a client-provided blueprint", @@ -3199,17 +3165,6 @@ "key" ] }, - "CollectionId": { - "type": "object", - "properties": { - "collection_id": { - "$ref": "#/components/schemas/TypedUuidForCollectionKind" - } - }, - "required": [ - "collection_id" - ] - }, "Cumulativedouble": { "description": "A cumulative or counter data type.", "type": "object", @@ -7178,10 +7133,6 @@ "SwitchPutResponse": { "type": "object" }, - "TypedUuidForCollectionKind": { - "type": "string", - "format": "uuid" - }, "TypedUuidForDownstairsRegionKind": { "type": "string", "format": "uuid"