diff --git a/clients/sled-agent-client/src/lib.rs b/clients/sled-agent-client/src/lib.rs index 17d1225c6e..0426982d3e 100644 --- a/clients/sled-agent-client/src/lib.rs +++ b/clients/sled-agent-client/src/lib.rs @@ -114,6 +114,24 @@ impl types::OmicronZoneType { } } + /// Identifies whether this a Crucible (not Crucible pantry) zone + pub fn is_crucible(&self) -> bool { + match self { + types::OmicronZoneType::Crucible { .. } => true, + + types::OmicronZoneType::BoundaryNtp { .. } + | types::OmicronZoneType::InternalNtp { .. } + | types::OmicronZoneType::Clickhouse { .. } + | types::OmicronZoneType::ClickhouseKeeper { .. } + | types::OmicronZoneType::CockroachDb { .. } + | types::OmicronZoneType::CruciblePantry { .. } + | types::OmicronZoneType::ExternalDns { .. } + | types::OmicronZoneType::InternalDns { .. } + | types::OmicronZoneType::Nexus { .. } + | types::OmicronZoneType::Oximeter { .. } => false, + } + } + /// This zone's external IP pub fn external_ip(&self) -> anyhow::Result> { match self { diff --git a/dev-tools/reconfigurator-cli/src/main.rs b/dev-tools/reconfigurator-cli/src/main.rs index 82b71cff49..b59fc96703 100644 --- a/dev-tools/reconfigurator-cli/src/main.rs +++ b/dev-tools/reconfigurator-cli/src/main.rs @@ -547,8 +547,7 @@ fn cmd_blueprint_diff_inventory( .get(&blueprint_id) .ok_or_else(|| anyhow!("no such blueprint: {}", blueprint_id))?; - let zones = collection.all_omicron_zones().map(|z| z.id).collect(); - let diff = blueprint.diff_sleds_from_collection(&collection, &zones); + let diff = blueprint.diff_sleds_from_collection(&collection); Ok(Some(diff.display().to_string())) } diff --git a/dev-tools/reconfigurator-cli/tests/output/cmd-complex-stdout b/dev-tools/reconfigurator-cli/tests/output/cmd-complex-stdout deleted file mode 100644 index 0c8c164d6f..0000000000 --- a/dev-tools/reconfigurator-cli/tests/output/cmd-complex-stdout +++ /dev/null @@ -1,313 +0,0 @@ -> sled-list -ID NZPOOLS SUBNET - -> - -> inventory-list -ID NERRORS TIME_DONE - -> - -> blueprint-list -ID - -> - -> file-contents tests/input/complex.json -sled: 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b (subnet: fd00:1122:3344:103::/64, zpools: 5) -sled: 67a1268b-52d3-4159-b3ed-7a0777f1f340 (subnet: fd00:1122:3344:104::/64, zpools: 5) -sled: 77851655-10a5-4e80-88b0-a001473cab7e (subnet: fd00:1122:3344:102::/64, zpools: 5) -sled: f122b3a2-e383-4e42-b2a5-74a69a9d110a (subnet: fd00:1122:3344:101::/64, zpools: 5) -collection: 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 (errors: 0, completed at: 2024-03-12T18:59:34.397Z) -collection: 37c212f1-bfdd-4cfd-a17c-e65bd93bea5f (errors: 0, completed at: 2024-03-12T19:07:28.472Z) -collection: 3700dcd4-64bb-4997-bfdb-dcfc7cbbc998 (errors: 0, completed at: 2024-03-12T19:07:28.728Z) -collection: 5e1d6ae0-c5b4-4884-ac41-680cd4f5762d (errors: 0, completed at: 2024-03-12T19:09:37.116Z) -blueprint: 649e444d-d00f-4067-a915-12e15647bf37 (created at: 2024-03-12 19:09:41.953854 UTC) -blueprint: cd34e87b-d821-4768-b32f-f41c28db835e (created at: 2024-03-12 19:11:49.876621 UTC) - - -> - -> load tests/input/complex.json 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 -using collection 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 as source of sled inventory data -sled 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b loaded -sled 67a1268b-52d3-4159-b3ed-7a0777f1f340 loaded -sled 77851655-10a5-4e80-88b0-a001473cab7e loaded -sled f122b3a2-e383-4e42-b2a5-74a69a9d110a loaded -collection 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 loaded -collection 37c212f1-bfdd-4cfd-a17c-e65bd93bea5f loaded -collection 3700dcd4-64bb-4997-bfdb-dcfc7cbbc998 loaded -collection 5e1d6ae0-c5b4-4884-ac41-680cd4f5762d loaded -blueprint 649e444d-d00f-4067-a915-12e15647bf37 loaded -blueprint cd34e87b-d821-4768-b32f-f41c28db835e loaded -loaded data from "tests/input/complex.json" - - -> - -> sled-list -ID NZPOOLS SUBNET -1f34b5cc-6dac-40cc-9ea1-95f32c33e59b 5 fd00:1122:3344:103::/64 -67a1268b-52d3-4159-b3ed-7a0777f1f340 5 fd00:1122:3344:104::/64 -77851655-10a5-4e80-88b0-a001473cab7e 5 fd00:1122:3344:102::/64 -f122b3a2-e383-4e42-b2a5-74a69a9d110a 5 fd00:1122:3344:101::/64 - -> - -> inventory-list -ID NERRORS TIME_DONE -3f61b723-cfe7-40ec-b22c-e3e1f35325f9 0 2024-03-12T18:59:34.397Z -37c212f1-bfdd-4cfd-a17c-e65bd93bea5f 0 2024-03-12T19:07:28.472Z -3700dcd4-64bb-4997-bfdb-dcfc7cbbc998 0 2024-03-12T19:07:28.728Z -5e1d6ae0-c5b4-4884-ac41-680cd4f5762d 0 2024-03-12T19:09:37.116Z - -> - -> blueprint-list -ID -649e444d-d00f-4067-a915-12e15647bf37 -cd34e87b-d821-4768-b32f-f41c28db835e - -> - -> blueprint-show cd34e87b-d821-4768-b32f-f41c28db835e -blueprint cd34e87b-d821-4768-b32f-f41c28db835e -parent: -created by 0d459323-e414-4f32-9944-76c7331da622 (likely a Nexus instance) -created at 2024-03-12T19:11:49.876Z -internal DNS version: 1 -comment: from collection 5e1d6ae0-c5b4-4884-ac41-680cd4f5762d -zones: - - sled 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b: Omicron zones at generation 5 - 0db88baf-911d-47bb-af71-3aebd7c96934 in service nexus - 213d7dd3-95a4-45a7-99ca-e4899991dab4 in service crucible - 218f36a5-9e51-4b88-ad1a-da4d85c246c4 in service internal_dns - 40c47ead-3be8-4724-a8ef-b66233fc5c65 in service crucible - 425d9d2f-72ea-4f53-80b6-04e002771cdb in service crucible - 46a050b6-37b8-47b9-b4ae-ccb736ecd6d5 in service crucible - 4ea8104f-2cfc-4599-9d2c-bcfd8f0b71fd in service clickhouse - 7611a549-effc-41b6-b6cb-c03e0c70a31f in service internal_ntp - af7ccead-2bd8-4a8a-9fbf-3b788d180139 in service cockroach_db - d4428e41-ad7b-41f0-bde6-7c7c955f20c1 in service crucible - sled 67a1268b-52d3-4159-b3ed-7a0777f1f340: Omicron zones at generation 5 - 1ebea0c3-1bfd-472c-9f84-ac9e23b32707 in service crucible - 2e9dfa8f-5371-48e5-b7f7-62a6af8283c1 in service cockroach_db - 57c712a6-6429-44b7-a9b2-9957cb9054d4 in service crucible - 59c4c9f0-bea9-46b7-ae9d-fb6dc1d658b9 in service crucible - 73916098-788f-48d8-b745-e13fdcfb771b in service internal_ntp - 9bb513e3-7505-416c-aa70-b26f1152bbdc in service crucible - 9c14c879-137b-4f5b-8068-1292bffc83ed in service cockroach_db - 9cf90589-5da1-4b42-b09b-d784e4134637 in service nexus - d48b2dfa-c749-47dd-98b6-3f478cd63934 in service crucible_pantry - fd003bee-818a-4c95-aae5-e378ac601522 in service crucible - sled 77851655-10a5-4e80-88b0-a001473cab7e: Omicron zones at generation 5 - 084cac6e-44b5-4333-a2ff-b0993b534898 in service crucible - 11c527c5-0598-428e-9270-be5b74d3461b in service cockroach_db - 20be9299-495d-428d-b315-694c2c4b64af in service crucible_pantry - 2b29cb95-f8c6-4aff-acc0-1c0a627be392 in service crucible - 8d452bdd-e8b8-4108-a690-46fc59197db0 in service crucible - 953019c0-8e85-4417-b231-d42820470125 in service oximeter - 99c635b9-8e97-414a-8316-8261d2d767d0 in service crucible - cbe1abfa-0063-44b2-9ce7-7ad76b403a94 in service internal_dns - d52e797a-8fff-4e1e-a5ff-0ab8c8293ded in service boundary_ntp - dc7f703a-27d1-4802-b85d-5629ddb2d67c in service crucible - f0ed9838-6f1b-4646-a478-9f74f2e6e802 in service external_dns - sled f122b3a2-e383-4e42-b2a5-74a69a9d110a: Omicron zones at generation 5 - 0d459323-e414-4f32-9944-76c7331da622 in service nexus - 2902972b-3ae4-489d-b937-6480352e214a in service crucible - 2ee832de-af75-47ac-abac-f91304857ca4 in service external_dns - 30050a7e-7fc3-4965-b07b-1635212393eb in service crucible_pantry - 8494befe-6df6-42d1-9baa-599ba4228430 in service boundary_ntp - 92cba955-3a5a-4d82-b0f2-30e89d4556d1 in service crucible - 9ba4cda7-d2e4-4c44-b1a6-8ddfe676098b in service cockroach_db - e668e3f5-5457-4689-97da-b2af537787ac in service crucible - f32fd972-7f40-4c58-9e00-7fc5c44273f4 in service crucible - f8c79ba4-194a-4cfe-8090-5315e297b780 in service internal_dns - fa5e0176-b4a8-4598-aa31-40fd562d246c in service crucible - - -> - -> blueprint-diff-inventory 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 cd34e87b-d821-4768-b32f-f41c28db835e -diff collection 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 blueprint cd34e87b-d821-4768-b32f-f41c28db835e ---- collection 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 -+++ blueprint cd34e87b-d821-4768-b32f-f41c28db835e - sled 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b - zone config generation 5 - zone 0db88baf-911d-47bb-af71-3aebd7c96934 type nexus underlay IP fd00:1122:3344:103::4 (unchanged) - zone 213d7dd3-95a4-45a7-99ca-e4899991dab4 type crucible underlay IP fd00:1122:3344:103::6 (unchanged) - zone 218f36a5-9e51-4b88-ad1a-da4d85c246c4 type internal_dns underlay IP fd00:1122:3344:3::1 (unchanged) - zone 40c47ead-3be8-4724-a8ef-b66233fc5c65 type crucible underlay IP fd00:1122:3344:103::7 (unchanged) - zone 425d9d2f-72ea-4f53-80b6-04e002771cdb type crucible underlay IP fd00:1122:3344:103::9 (unchanged) - zone 46a050b6-37b8-47b9-b4ae-ccb736ecd6d5 type crucible underlay IP fd00:1122:3344:103::8 (unchanged) - zone 4ea8104f-2cfc-4599-9d2c-bcfd8f0b71fd type clickhouse underlay IP fd00:1122:3344:103::5 (unchanged) - zone 7611a549-effc-41b6-b6cb-c03e0c70a31f type internal_ntp underlay IP fd00:1122:3344:103::b (unchanged) - zone af7ccead-2bd8-4a8a-9fbf-3b788d180139 type cockroach_db underlay IP fd00:1122:3344:103::3 (unchanged) - zone d4428e41-ad7b-41f0-bde6-7c7c955f20c1 type crucible underlay IP fd00:1122:3344:103::a (unchanged) - sled 67a1268b-52d3-4159-b3ed-7a0777f1f340 - zone config generation 5 - zone 1ebea0c3-1bfd-472c-9f84-ac9e23b32707 type crucible underlay IP fd00:1122:3344:104::b (unchanged) - zone 2e9dfa8f-5371-48e5-b7f7-62a6af8283c1 type cockroach_db underlay IP fd00:1122:3344:104::4 (unchanged) - zone 57c712a6-6429-44b7-a9b2-9957cb9054d4 type crucible underlay IP fd00:1122:3344:104::a (unchanged) - zone 59c4c9f0-bea9-46b7-ae9d-fb6dc1d658b9 type crucible underlay IP fd00:1122:3344:104::7 (unchanged) - zone 73916098-788f-48d8-b745-e13fdcfb771b type internal_ntp underlay IP fd00:1122:3344:104::c (unchanged) - zone 9bb513e3-7505-416c-aa70-b26f1152bbdc type crucible underlay IP fd00:1122:3344:104::9 (unchanged) - zone 9c14c879-137b-4f5b-8068-1292bffc83ed type cockroach_db underlay IP fd00:1122:3344:104::3 (unchanged) - zone 9cf90589-5da1-4b42-b09b-d784e4134637 type nexus underlay IP fd00:1122:3344:104::5 (unchanged) - zone d48b2dfa-c749-47dd-98b6-3f478cd63934 type crucible_pantry underlay IP fd00:1122:3344:104::6 (unchanged) - zone fd003bee-818a-4c95-aae5-e378ac601522 type crucible underlay IP fd00:1122:3344:104::8 (unchanged) - sled 77851655-10a5-4e80-88b0-a001473cab7e - zone config generation 5 - zone 084cac6e-44b5-4333-a2ff-b0993b534898 type crucible underlay IP fd00:1122:3344:102::8 (unchanged) - zone 11c527c5-0598-428e-9270-be5b74d3461b type cockroach_db underlay IP fd00:1122:3344:102::3 (unchanged) - zone 20be9299-495d-428d-b315-694c2c4b64af type crucible_pantry underlay IP fd00:1122:3344:102::6 (unchanged) - zone 2b29cb95-f8c6-4aff-acc0-1c0a627be392 type crucible underlay IP fd00:1122:3344:102::b (unchanged) - zone 8d452bdd-e8b8-4108-a690-46fc59197db0 type crucible underlay IP fd00:1122:3344:102::a (unchanged) - zone 953019c0-8e85-4417-b231-d42820470125 type oximeter underlay IP fd00:1122:3344:102::5 (unchanged) - zone 99c635b9-8e97-414a-8316-8261d2d767d0 type crucible underlay IP fd00:1122:3344:102::7 (unchanged) - zone cbe1abfa-0063-44b2-9ce7-7ad76b403a94 type internal_dns underlay IP fd00:1122:3344:2::1 (unchanged) - zone d52e797a-8fff-4e1e-a5ff-0ab8c8293ded type boundary_ntp underlay IP fd00:1122:3344:102::c (unchanged) - zone dc7f703a-27d1-4802-b85d-5629ddb2d67c type crucible underlay IP fd00:1122:3344:102::9 (unchanged) - zone f0ed9838-6f1b-4646-a478-9f74f2e6e802 type external_dns underlay IP fd00:1122:3344:102::4 (unchanged) - sled f122b3a2-e383-4e42-b2a5-74a69a9d110a - zone config generation 5 - zone 0d459323-e414-4f32-9944-76c7331da622 type nexus underlay IP fd00:1122:3344:101::5 (unchanged) - zone 2902972b-3ae4-489d-b937-6480352e214a type crucible underlay IP fd00:1122:3344:101::8 (unchanged) - zone 2ee832de-af75-47ac-abac-f91304857ca4 type external_dns underlay IP fd00:1122:3344:101::4 (unchanged) - zone 30050a7e-7fc3-4965-b07b-1635212393eb type crucible_pantry underlay IP fd00:1122:3344:101::6 (unchanged) - zone 8494befe-6df6-42d1-9baa-599ba4228430 type boundary_ntp underlay IP fd00:1122:3344:101::c (unchanged) - zone 92cba955-3a5a-4d82-b0f2-30e89d4556d1 type crucible underlay IP fd00:1122:3344:101::7 (unchanged) - zone 9ba4cda7-d2e4-4c44-b1a6-8ddfe676098b type cockroach_db underlay IP fd00:1122:3344:101::3 (unchanged) - zone e668e3f5-5457-4689-97da-b2af537787ac type crucible underlay IP fd00:1122:3344:101::b (unchanged) - zone f32fd972-7f40-4c58-9e00-7fc5c44273f4 type crucible underlay IP fd00:1122:3344:101::9 (unchanged) - zone f8c79ba4-194a-4cfe-8090-5315e297b780 type internal_dns underlay IP fd00:1122:3344:1::1 (unchanged) - zone fa5e0176-b4a8-4598-aa31-40fd562d246c type crucible underlay IP fd00:1122:3344:101::a (unchanged) - - -> - -> blueprint-diff 649e444d-d00f-4067-a915-12e15647bf37 cd34e87b-d821-4768-b32f-f41c28db835e -diff blueprint 649e444d-d00f-4067-a915-12e15647bf37 blueprint cd34e87b-d821-4768-b32f-f41c28db835e ---- blueprint 649e444d-d00f-4067-a915-12e15647bf37 -+++ blueprint cd34e87b-d821-4768-b32f-f41c28db835e - sled 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b - zone config generation 5 - zone 0db88baf-911d-47bb-af71-3aebd7c96934 type nexus underlay IP fd00:1122:3344:103::4 (unchanged) - zone 213d7dd3-95a4-45a7-99ca-e4899991dab4 type crucible underlay IP fd00:1122:3344:103::6 (unchanged) - zone 218f36a5-9e51-4b88-ad1a-da4d85c246c4 type internal_dns underlay IP fd00:1122:3344:3::1 (unchanged) - zone 40c47ead-3be8-4724-a8ef-b66233fc5c65 type crucible underlay IP fd00:1122:3344:103::7 (unchanged) - zone 425d9d2f-72ea-4f53-80b6-04e002771cdb type crucible underlay IP fd00:1122:3344:103::9 (unchanged) - zone 46a050b6-37b8-47b9-b4ae-ccb736ecd6d5 type crucible underlay IP fd00:1122:3344:103::8 (unchanged) - zone 4ea8104f-2cfc-4599-9d2c-bcfd8f0b71fd type clickhouse underlay IP fd00:1122:3344:103::5 (unchanged) - zone 7611a549-effc-41b6-b6cb-c03e0c70a31f type internal_ntp underlay IP fd00:1122:3344:103::b (unchanged) - zone af7ccead-2bd8-4a8a-9fbf-3b788d180139 type cockroach_db underlay IP fd00:1122:3344:103::3 (unchanged) - zone d4428e41-ad7b-41f0-bde6-7c7c955f20c1 type crucible underlay IP fd00:1122:3344:103::a (unchanged) - sled 67a1268b-52d3-4159-b3ed-7a0777f1f340 - zone config generation 5 - zone 1ebea0c3-1bfd-472c-9f84-ac9e23b32707 type crucible underlay IP fd00:1122:3344:104::b (unchanged) - zone 2e9dfa8f-5371-48e5-b7f7-62a6af8283c1 type cockroach_db underlay IP fd00:1122:3344:104::4 (unchanged) - zone 57c712a6-6429-44b7-a9b2-9957cb9054d4 type crucible underlay IP fd00:1122:3344:104::a (unchanged) - zone 59c4c9f0-bea9-46b7-ae9d-fb6dc1d658b9 type crucible underlay IP fd00:1122:3344:104::7 (unchanged) - zone 73916098-788f-48d8-b745-e13fdcfb771b type internal_ntp underlay IP fd00:1122:3344:104::c (unchanged) - zone 9bb513e3-7505-416c-aa70-b26f1152bbdc type crucible underlay IP fd00:1122:3344:104::9 (unchanged) - zone 9c14c879-137b-4f5b-8068-1292bffc83ed type cockroach_db underlay IP fd00:1122:3344:104::3 (unchanged) - zone 9cf90589-5da1-4b42-b09b-d784e4134637 type nexus underlay IP fd00:1122:3344:104::5 (unchanged) - zone d48b2dfa-c749-47dd-98b6-3f478cd63934 type crucible_pantry underlay IP fd00:1122:3344:104::6 (unchanged) - zone fd003bee-818a-4c95-aae5-e378ac601522 type crucible underlay IP fd00:1122:3344:104::8 (unchanged) - sled 77851655-10a5-4e80-88b0-a001473cab7e - zone config generation 5 - zone 084cac6e-44b5-4333-a2ff-b0993b534898 type crucible underlay IP fd00:1122:3344:102::8 (unchanged) - zone 11c527c5-0598-428e-9270-be5b74d3461b type cockroach_db underlay IP fd00:1122:3344:102::3 (unchanged) - zone 20be9299-495d-428d-b315-694c2c4b64af type crucible_pantry underlay IP fd00:1122:3344:102::6 (unchanged) - zone 2b29cb95-f8c6-4aff-acc0-1c0a627be392 type crucible underlay IP fd00:1122:3344:102::b (unchanged) - zone 8d452bdd-e8b8-4108-a690-46fc59197db0 type crucible underlay IP fd00:1122:3344:102::a (unchanged) - zone 953019c0-8e85-4417-b231-d42820470125 type oximeter underlay IP fd00:1122:3344:102::5 (unchanged) - zone 99c635b9-8e97-414a-8316-8261d2d767d0 type crucible underlay IP fd00:1122:3344:102::7 (unchanged) - zone cbe1abfa-0063-44b2-9ce7-7ad76b403a94 type internal_dns underlay IP fd00:1122:3344:2::1 (unchanged) - zone d52e797a-8fff-4e1e-a5ff-0ab8c8293ded type boundary_ntp underlay IP fd00:1122:3344:102::c (unchanged) - zone dc7f703a-27d1-4802-b85d-5629ddb2d67c type crucible underlay IP fd00:1122:3344:102::9 (unchanged) - zone f0ed9838-6f1b-4646-a478-9f74f2e6e802 type external_dns underlay IP fd00:1122:3344:102::4 (unchanged) - sled f122b3a2-e383-4e42-b2a5-74a69a9d110a - zone config generation 5 - zone 0d459323-e414-4f32-9944-76c7331da622 type nexus underlay IP fd00:1122:3344:101::5 (unchanged) - zone 2902972b-3ae4-489d-b937-6480352e214a type crucible underlay IP fd00:1122:3344:101::8 (unchanged) - zone 2ee832de-af75-47ac-abac-f91304857ca4 type external_dns underlay IP fd00:1122:3344:101::4 (unchanged) - zone 30050a7e-7fc3-4965-b07b-1635212393eb type crucible_pantry underlay IP fd00:1122:3344:101::6 (unchanged) - zone 8494befe-6df6-42d1-9baa-599ba4228430 type boundary_ntp underlay IP fd00:1122:3344:101::c (unchanged) - zone 92cba955-3a5a-4d82-b0f2-30e89d4556d1 type crucible underlay IP fd00:1122:3344:101::7 (unchanged) - zone 9ba4cda7-d2e4-4c44-b1a6-8ddfe676098b type cockroach_db underlay IP fd00:1122:3344:101::3 (unchanged) - zone e668e3f5-5457-4689-97da-b2af537787ac type crucible underlay IP fd00:1122:3344:101::b (unchanged) - zone f32fd972-7f40-4c58-9e00-7fc5c44273f4 type crucible underlay IP fd00:1122:3344:101::9 (unchanged) - zone f8c79ba4-194a-4cfe-8090-5315e297b780 type internal_dns underlay IP fd00:1122:3344:1::1 (unchanged) - zone fa5e0176-b4a8-4598-aa31-40fd562d246c type crucible underlay IP fd00:1122:3344:101::a (unchanged) - - -> - -> blueprint-diff cd34e87b-d821-4768-b32f-f41c28db835e 649e444d-d00f-4067-a915-12e15647bf37 -diff blueprint cd34e87b-d821-4768-b32f-f41c28db835e blueprint 649e444d-d00f-4067-a915-12e15647bf37 ---- blueprint cd34e87b-d821-4768-b32f-f41c28db835e -+++ blueprint 649e444d-d00f-4067-a915-12e15647bf37 - sled 1f34b5cc-6dac-40cc-9ea1-95f32c33e59b - zone config generation 5 - zone 0db88baf-911d-47bb-af71-3aebd7c96934 type nexus underlay IP fd00:1122:3344:103::4 (unchanged) - zone 213d7dd3-95a4-45a7-99ca-e4899991dab4 type crucible underlay IP fd00:1122:3344:103::6 (unchanged) - zone 218f36a5-9e51-4b88-ad1a-da4d85c246c4 type internal_dns underlay IP fd00:1122:3344:3::1 (unchanged) - zone 40c47ead-3be8-4724-a8ef-b66233fc5c65 type crucible underlay IP fd00:1122:3344:103::7 (unchanged) - zone 425d9d2f-72ea-4f53-80b6-04e002771cdb type crucible underlay IP fd00:1122:3344:103::9 (unchanged) - zone 46a050b6-37b8-47b9-b4ae-ccb736ecd6d5 type crucible underlay IP fd00:1122:3344:103::8 (unchanged) - zone 4ea8104f-2cfc-4599-9d2c-bcfd8f0b71fd type clickhouse underlay IP fd00:1122:3344:103::5 (unchanged) - zone 7611a549-effc-41b6-b6cb-c03e0c70a31f type internal_ntp underlay IP fd00:1122:3344:103::b (unchanged) - zone af7ccead-2bd8-4a8a-9fbf-3b788d180139 type cockroach_db underlay IP fd00:1122:3344:103::3 (unchanged) - zone d4428e41-ad7b-41f0-bde6-7c7c955f20c1 type crucible underlay IP fd00:1122:3344:103::a (unchanged) - sled 67a1268b-52d3-4159-b3ed-7a0777f1f340 - zone config generation 5 - zone 1ebea0c3-1bfd-472c-9f84-ac9e23b32707 type crucible underlay IP fd00:1122:3344:104::b (unchanged) - zone 2e9dfa8f-5371-48e5-b7f7-62a6af8283c1 type cockroach_db underlay IP fd00:1122:3344:104::4 (unchanged) - zone 57c712a6-6429-44b7-a9b2-9957cb9054d4 type crucible underlay IP fd00:1122:3344:104::a (unchanged) - zone 59c4c9f0-bea9-46b7-ae9d-fb6dc1d658b9 type crucible underlay IP fd00:1122:3344:104::7 (unchanged) - zone 73916098-788f-48d8-b745-e13fdcfb771b type internal_ntp underlay IP fd00:1122:3344:104::c (unchanged) - zone 9bb513e3-7505-416c-aa70-b26f1152bbdc type crucible underlay IP fd00:1122:3344:104::9 (unchanged) - zone 9c14c879-137b-4f5b-8068-1292bffc83ed type cockroach_db underlay IP fd00:1122:3344:104::3 (unchanged) - zone 9cf90589-5da1-4b42-b09b-d784e4134637 type nexus underlay IP fd00:1122:3344:104::5 (unchanged) - zone d48b2dfa-c749-47dd-98b6-3f478cd63934 type crucible_pantry underlay IP fd00:1122:3344:104::6 (unchanged) - zone fd003bee-818a-4c95-aae5-e378ac601522 type crucible underlay IP fd00:1122:3344:104::8 (unchanged) - sled 77851655-10a5-4e80-88b0-a001473cab7e - zone config generation 5 - zone 084cac6e-44b5-4333-a2ff-b0993b534898 type crucible underlay IP fd00:1122:3344:102::8 (unchanged) - zone 11c527c5-0598-428e-9270-be5b74d3461b type cockroach_db underlay IP fd00:1122:3344:102::3 (unchanged) - zone 20be9299-495d-428d-b315-694c2c4b64af type crucible_pantry underlay IP fd00:1122:3344:102::6 (unchanged) - zone 2b29cb95-f8c6-4aff-acc0-1c0a627be392 type crucible underlay IP fd00:1122:3344:102::b (unchanged) - zone 8d452bdd-e8b8-4108-a690-46fc59197db0 type crucible underlay IP fd00:1122:3344:102::a (unchanged) - zone 953019c0-8e85-4417-b231-d42820470125 type oximeter underlay IP fd00:1122:3344:102::5 (unchanged) - zone 99c635b9-8e97-414a-8316-8261d2d767d0 type crucible underlay IP fd00:1122:3344:102::7 (unchanged) - zone cbe1abfa-0063-44b2-9ce7-7ad76b403a94 type internal_dns underlay IP fd00:1122:3344:2::1 (unchanged) - zone d52e797a-8fff-4e1e-a5ff-0ab8c8293ded type boundary_ntp underlay IP fd00:1122:3344:102::c (unchanged) - zone dc7f703a-27d1-4802-b85d-5629ddb2d67c type crucible underlay IP fd00:1122:3344:102::9 (unchanged) - zone f0ed9838-6f1b-4646-a478-9f74f2e6e802 type external_dns underlay IP fd00:1122:3344:102::4 (unchanged) - sled f122b3a2-e383-4e42-b2a5-74a69a9d110a - zone config generation 5 - zone 0d459323-e414-4f32-9944-76c7331da622 type nexus underlay IP fd00:1122:3344:101::5 (unchanged) - zone 2902972b-3ae4-489d-b937-6480352e214a type crucible underlay IP fd00:1122:3344:101::8 (unchanged) - zone 2ee832de-af75-47ac-abac-f91304857ca4 type external_dns underlay IP fd00:1122:3344:101::4 (unchanged) - zone 30050a7e-7fc3-4965-b07b-1635212393eb type crucible_pantry underlay IP fd00:1122:3344:101::6 (unchanged) - zone 8494befe-6df6-42d1-9baa-599ba4228430 type boundary_ntp underlay IP fd00:1122:3344:101::c (unchanged) - zone 92cba955-3a5a-4d82-b0f2-30e89d4556d1 type crucible underlay IP fd00:1122:3344:101::7 (unchanged) - zone 9ba4cda7-d2e4-4c44-b1a6-8ddfe676098b type cockroach_db underlay IP fd00:1122:3344:101::3 (unchanged) - zone e668e3f5-5457-4689-97da-b2af537787ac type crucible underlay IP fd00:1122:3344:101::b (unchanged) - zone f32fd972-7f40-4c58-9e00-7fc5c44273f4 type crucible underlay IP fd00:1122:3344:101::9 (unchanged) - zone f8c79ba4-194a-4cfe-8090-5315e297b780 type internal_dns underlay IP fd00:1122:3344:1::1 (unchanged) - zone fa5e0176-b4a8-4598-aa31-40fd562d246c type crucible underlay IP fd00:1122:3344:101::a (unchanged) - - -> - -> sled-add dde1c0e2-b10d-4621-b420-f179f7a7a00a -added sled - -> - -> blueprint-plan cd34e87b-d821-4768-b32f-f41c28db835e 3f61b723-cfe7-40ec-b22c-e3e1f35325f9 -generated blueprint REDACTED_UUID based on parent blueprint cd34e87b-d821-4768-b32f-f41c28db835e - diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index e34148bede..e56c8bff54 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -15,6 +15,9 @@ use crate::{ipv6, Generation, MacAddr, Name, SqlU16, SqlU32, SqlU8}; use chrono::{DateTime, Utc}; use ipnetwork::IpNetwork; use nexus_types::deployment::BlueprintTarget; +use nexus_types::deployment::BlueprintZoneConfig; +use nexus_types::deployment::BlueprintZoneDisposition; +use nexus_types::deployment::BlueprintZonesConfig; use omicron_common::api::internal::shared::NetworkInterface; use uuid::Uuid; @@ -103,7 +106,7 @@ impl BpSledOmicronZones { pub fn new( blueprint_id: Uuid, sled_id: Uuid, - zones_config: &nexus_types::deployment::OmicronZonesConfig, + zones_config: &BlueprintZonesConfig, ) -> Self { Self { blueprint_id, @@ -144,9 +147,9 @@ impl BpOmicronZone { pub fn new( blueprint_id: Uuid, sled_id: Uuid, - zone: &nexus_types::inventory::OmicronZoneConfig, + zone: &BlueprintZoneConfig, ) -> Result { - let zone = OmicronZone::new(sled_id, zone)?; + let zone = OmicronZone::new(sled_id, &zone.config)?; Ok(Self { blueprint_id, sled_id: zone.sled_id, @@ -172,10 +175,11 @@ impl BpOmicronZone { }) } - pub fn into_omicron_zone_config( + pub fn into_blueprint_zone_config( self, nic_row: Option, - ) -> Result { + disposition: BlueprintZoneDisposition, + ) -> Result { let zone = OmicronZone { sled_id: self.sled_id, id: self.id, @@ -198,7 +202,9 @@ impl BpOmicronZone { snat_first_port: self.snat_first_port, snat_last_port: self.snat_last_port, }; - zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from)) + let config = + zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from))?; + Ok(BlueprintZoneConfig { config, disposition }) } } @@ -234,9 +240,9 @@ impl From for OmicronZoneNic { impl BpOmicronZoneNic { pub fn new( blueprint_id: Uuid, - zone: &nexus_types::inventory::OmicronZoneConfig, + zone: &BlueprintZoneConfig, ) -> Result, anyhow::Error> { - let zone_nic = OmicronZoneNic::new(zone)?; + let zone_nic = OmicronZoneNic::new(&zone.config)?; Ok(zone_nic.map(|nic| Self { blueprint_id, id: nic.id, diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 88c0897be2..02645ca4f6 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -43,7 +43,9 @@ use nexus_db_model::BpTarget; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintMetadata; use nexus_types::deployment::BlueprintTarget; -use nexus_types::deployment::OmicronZonesConfig; +use nexus_types::deployment::BlueprintZoneDisposition; +use nexus_types::deployment::BlueprintZoneFilter; +use nexus_types::deployment::BlueprintZonesConfig; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; @@ -105,52 +107,67 @@ impl DataStore { // so that we can produce the `Error` type that we want here. let row_blueprint = DbBlueprint::from(blueprint); let blueprint_id = row_blueprint.id; + + // `Blueprint` stores the policy for each zone next to the zone itself. + // This would ideally be represented as a simple column in + // bp_omicron_zone. + // + // But historically, `Blueprint` used to store the set of zones in + // service in a BTreeSet. Since most zones are expected to be in + // service, we store the set of zones NOT in service (which we expect + // to be much smaller, often empty). Build that inverted set here. + // + // This will soon be replaced with an extra column in the + // `bp_omicron_zone` table, coupled with other data migrations. + let omicron_zones_not_in_service = blueprint + .all_blueprint_zones(BlueprintZoneFilter::All) + .filter_map(|(_, zone)| { + // This is going to go away soon when we change the database + // representation to store the zone disposition enum next to + // each zone. For now, do an exhaustive match so that this + // fails if we add a new variant. + match zone.disposition { + BlueprintZoneDisposition::InService => None, + BlueprintZoneDisposition::Quiesced => { + Some(BpOmicronZoneNotInService { + blueprint_id, + bp_omicron_zone_id: zone.config.id, + }) + } + } + }) + .collect::>(); + let sled_omicron_zones = blueprint - .omicron_zones + .blueprint_zones .iter() .map(|(sled_id, zones_config)| { BpSledOmicronZones::new(blueprint_id, *sled_id, zones_config) }) .collect::>(); let omicron_zones = blueprint - .omicron_zones + .blueprint_zones .iter() .flat_map(|(sled_id, zones_config)| { - zones_config.zones.iter().map(|zone| { + zones_config.zones.iter().map(move |zone| { BpOmicronZone::new(blueprint_id, *sled_id, zone) .map_err(|e| Error::internal_error(&format!("{:#}", e))) }) }) .collect::, Error>>()?; let omicron_zone_nics = blueprint - .omicron_zones + .blueprint_zones .values() .flat_map(|zones_config| { zones_config.zones.iter().filter_map(|zone| { BpOmicronZoneNic::new(blueprint_id, zone) - .with_context(|| format!("zone {:?}", zone.id)) + .with_context(|| format!("zone {:?}", zone.config.id)) .map_err(|e| Error::internal_error(&format!("{:#}", e))) .transpose() }) }) .collect::, _>>()?; - // `Blueprint` stores a set of zones in service, but in the database we - // store the set of zones NOT in service (which we expect to be much - // smaller, often empty). Build that inverted set here. - let omicron_zones_not_in_service = { - let mut zones_not_in_service = Vec::new(); - for zone in &omicron_zones { - if !blueprint.zones_in_service.contains(&zone.id) { - zones_not_in_service.push(BpOmicronZoneNotInService { - blueprint_id, - bp_omicron_zone_id: zone.id, - }); - } - } - zones_not_in_service - }; - // This implementation inserts all records associated with the // blueprint in one transaction. This is required: we don't want // any planner or executor to see a half-inserted blueprint, nor do we @@ -273,10 +290,10 @@ impl DataStore { // the `OmicronZonesConfig` generation number for each sled that is a // part of this blueprint. Construct the BTreeMap we ultimately need, // but all the `zones` vecs will be empty until our next query below. - let mut omicron_zones: BTreeMap = { + let mut blueprint_zones: BTreeMap = { use db::schema::bp_sled_omicron_zones::dsl; - let mut omicron_zones = BTreeMap::new(); + let mut blueprint_zones = BTreeMap::new(); let mut paginator = Paginator::new(SQL_BATCH_SIZE); while let Some(p) = paginator.next() { let batch = paginated( @@ -295,9 +312,9 @@ impl DataStore { paginator = p.found_batch(&batch, &|s| s.sled_id); for s in batch { - let old = omicron_zones.insert( + let old = blueprint_zones.insert( s.sled_id, - OmicronZonesConfig { + BlueprintZonesConfig { generation: *s.generation, zones: Vec::new(), }, @@ -310,7 +327,7 @@ impl DataStore { } } - omicron_zones + blueprint_zones }; // Assemble a mutable map of all the NICs found, by NIC id. As we @@ -391,11 +408,6 @@ impl DataStore { omicron_zones_not_in_service }; - // Create the in-memory list of zones _in_ service, which we'll - // calculate below as we load zones. (Any zone that isn't present in - // `omicron_zones_not_in_service` is considered in service.) - let mut zones_in_service = BTreeSet::new(); - // Load all the zones for each sled. { use db::schema::bp_omicron_zone::dsl; @@ -438,8 +450,9 @@ impl DataStore { }) }) .transpose()?; - let sled_zones = - omicron_zones.get_mut(&z.sled_id).ok_or_else(|| { + let sled_zones = blueprint_zones + .get_mut(&z.sled_id) + .ok_or_else(|| { // This error means that we found a row in // bp_omicron_zone with no associated record in // bp_sled_omicron_zones. This should be @@ -451,8 +464,14 @@ impl DataStore { )) })?; let zone_id = z.id; + let disposition = + if omicron_zones_not_in_service.remove(&zone_id) { + BlueprintZoneDisposition::Quiesced + } else { + BlueprintZoneDisposition::InService + }; let zone = z - .into_omicron_zone_config(nic_row) + .into_blueprint_zone_config(nic_row, disposition) .with_context(|| { format!("zone {:?}: parse from database", zone_id) }) @@ -463,14 +482,6 @@ impl DataStore { )) })?; sled_zones.zones.push(zone); - - // If we can remove `zone_id` from - // `omicron_zones_not_in_service`, then the zone is not in - // service. Otherwise, add it to the list of in-service - // zones. - if !omicron_zones_not_in_service.remove(&zone_id) { - zones_in_service.insert(zone_id); - } } } } @@ -488,8 +499,7 @@ impl DataStore { Ok(Blueprint { id: blueprint_id, - omicron_zones, - zones_in_service, + blueprint_zones, parent_blueprint_id, internal_dns_version, external_dns_version, @@ -1359,10 +1369,8 @@ mod tests { [blueprint1.id] ); - // There ought to be no sleds or zones in service, and no parent - // blueprint. - assert_eq!(blueprint1.omicron_zones.len(), 0); - assert_eq!(blueprint1.zones_in_service.len(), 0); + // There ought to be no sleds or zones, and no parent blueprint. + assert_eq!(blueprint1.blueprint_zones.len(), 0); assert_eq!(blueprint1.parent_blueprint_id, None); // Trying to insert the same blueprint again should fail. @@ -1407,9 +1415,9 @@ mod tests { ); // Check the number of blueprint elements against our collection. - assert_eq!(blueprint1.omicron_zones.len(), policy.sleds.len()); + assert_eq!(blueprint1.blueprint_zones.len(), policy.sleds.len()); assert_eq!( - blueprint1.omicron_zones.len(), + blueprint1.blueprint_zones.len(), collection.omicron_zones.len() ); assert_eq!( @@ -1417,10 +1425,7 @@ mod tests { collection.all_omicron_zones().count() ); // All zones should be in service. - assert_eq!( - blueprint1.zones_in_service.len(), - blueprint1.all_omicron_zones().count() - ); + assert_all_zones_in_service(&blueprint1); assert_eq!(blueprint1.parent_blueprint_id, None); // Set blueprint1 as the current target, and ensure that we cannot @@ -1489,8 +1494,8 @@ mod tests { // Check that we added the new sled and its zones. assert_eq!( - blueprint1.omicron_zones.len() + 1, - blueprint2.omicron_zones.len() + blueprint1.blueprint_zones.len() + 1, + blueprint2.blueprint_zones.len() ); assert_eq!( blueprint1.all_omicron_zones().count() + num_new_sled_zones, @@ -1498,10 +1503,7 @@ mod tests { ); // All zones should be in service. - assert_eq!( - blueprint2.zones_in_service.len(), - blueprint2.all_omicron_zones().count() - ); + assert_all_zones_in_service(&blueprint2); assert_eq!(blueprint2.parent_blueprint_id, Some(blueprint1.id)); // Check that we can write it to the DB and read it back. @@ -1869,4 +1871,18 @@ mod tests { db.cleanup().await.unwrap(); logctx.cleanup_successful(); } + + fn assert_all_zones_in_service(blueprint: &Blueprint) { + let not_in_service = blueprint + .all_blueprint_zones(BlueprintZoneFilter::All) + .filter(|(_, z)| { + z.disposition != BlueprintZoneDisposition::InService + }) + .collect::>(); + assert!( + not_in_service.is_empty(), + "expected all zones to be in service, \ + found these zones not in service: {not_in_service:?}" + ); + } } diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index c86c002c33..94e033ec3c 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -952,7 +952,7 @@ mod test { }; use omicron_common::api::internal::shared::SourceNatConfig; use omicron_test_utils::dev; - use std::collections::{BTreeMap, BTreeSet, HashMap}; + use std::collections::{BTreeMap, HashMap}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6}; use std::num::NonZeroU32; @@ -965,8 +965,7 @@ mod test { rack_subnet: nexus_test_utils::RACK_SUBNET.parse().unwrap(), blueprint: Blueprint { id: Uuid::new_v4(), - omicron_zones: BTreeMap::new(), - zones_in_service: BTreeSet::new(), + blueprint_zones: BTreeMap::new(), parent_blueprint_id: None, internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 8be21348bf..dd05498038 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -43,6 +43,8 @@ use diesel::prelude::*; use diesel::result::DatabaseErrorKind; use diesel::result::Error as DieselError; use ipnetwork::IpNetwork; +use nexus_types::deployment::BlueprintZoneDisposition; +use nexus_types::deployment::BlueprintZoneFilter; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; @@ -694,6 +696,44 @@ impl DataStore { // ... and we also need to query for the current target blueprint to // support systems that _are_ under Reconfigurator control. + + { + // Ideally this would do something like: + // + // .filter(bp_omicron_zone::disposition.eq_any( + // BlueprintZoneDisposition::all_matching( + // BlueprintZoneFilter::VpcFirewall, + // ), + // ) + // + // But that doesn't quite work today because we currently don't + // store the disposition enum next to each zone. Instead, this code + // makes its decision to select which sleds to return by just + // ignoring the zones_in_service table today. + // + // The purpose of this otherwise pointless block is to ensure that + // it is correct to ensure that the expressed logic by + // `BlueprintZoneFilter::VpcFirewall` matches the actual + // implementation. It will hopefully soon be replaced with storing + // the disposition in the bp_omicron_zone table and using the + // filter directly. + + let mut matching = BlueprintZoneDisposition::all_matching( + BlueprintZoneFilter::VpcFirewall, + ) + .collect::>(); + matching.sort(); + let mut all = BlueprintZoneDisposition::all_matching( + BlueprintZoneFilter::All, + ) + .collect::>(); + all.sort(); + debug_assert_eq!( + matching, all, + "vpc firewall dispositions should match all dispositions" + ); + } + let reconfig_service_query = service_network_interface::table .inner_join(bp_omicron_zone::table.on( bp_omicron_zone::id.eq(service_network_interface::service_id), @@ -1252,9 +1292,11 @@ mod tests { use nexus_test_utils::db::test_setup_database; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintTarget; + use nexus_types::deployment::BlueprintZoneConfig; + use nexus_types::deployment::BlueprintZoneDisposition; + use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::deployment::OmicronZoneConfig; use nexus_types::deployment::OmicronZoneType; - use nexus_types::deployment::OmicronZonesConfig; use nexus_types::external_api::params; use nexus_types::identity::Asset; use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; @@ -1268,7 +1310,6 @@ mod tests { use omicron_test_utils::dev; use slog::info; use std::collections::BTreeMap; - use std::collections::BTreeSet; use std::net::IpAddr; // Test that we detect the right error condition and return None when we @@ -1570,11 +1611,11 @@ mod tests { }) } - fn omicron_zone_configs( + fn blueprint_zone_configs( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.db_services().map(|(service, nic)| { - let zone_config = OmicronZoneConfig { + let config = OmicronZoneConfig { id: service.id(), underlay_address: "::1".parse().unwrap(), zone_type: OmicronZoneType::Nexus { @@ -1599,6 +1640,10 @@ mod tests { external_dns_servers: Vec::new(), }, }; + let zone_config = BlueprintZoneConfig { + config, + disposition: BlueprintZoneDisposition::InService, + }; (service.sled_id, zone_config) }) } @@ -1652,15 +1697,15 @@ mod tests { // Create a blueprint that has a Nexus on our third sled. (This // blueprint is completely invalid in many ways, but all we care about // here is inserting relevant records in `bp_omicron_zone`.) - let bp1_omicron_zones = { + let bp1_zones = { let (sled_id, zone_config) = harness - .omicron_zone_configs() + .blueprint_zone_configs() .nth(2) .expect("fewer than 3 services in test harness"); let mut zones = BTreeMap::new(); zones.insert( sled_id, - OmicronZonesConfig { + BlueprintZonesConfig { generation: Generation::new(), zones: vec![zone_config], }, @@ -1670,8 +1715,7 @@ mod tests { let bp1_id = Uuid::new_v4(); let bp1 = Blueprint { id: bp1_id, - omicron_zones: bp1_omicron_zones, - zones_in_service: BTreeSet::new(), + blueprint_zones: bp1_zones, parent_blueprint_id: None, internal_dns_version: Generation::new(), external_dns_version: Generation::new(), @@ -1722,8 +1766,7 @@ mod tests { let bp2_id = Uuid::new_v4(); let bp2 = Blueprint { id: bp2_id, - omicron_zones: BTreeMap::new(), - zones_in_service: BTreeSet::new(), + blueprint_zones: BTreeMap::new(), parent_blueprint_id: Some(bp1_id), internal_dns_version: Generation::new(), external_dns_version: Generation::new(), @@ -1765,15 +1808,15 @@ mod tests { // Create a blueprint that has a Nexus on our fourth sled. This // shouldn't change our VPC resolution. - let bp3_omicron_zones = { + let bp3_zones = { let (sled_id, zone_config) = harness - .omicron_zone_configs() + .blueprint_zone_configs() .nth(3) .expect("fewer than 3 services in test harness"); let mut zones = BTreeMap::new(); zones.insert( sled_id, - OmicronZonesConfig { + BlueprintZonesConfig { generation: Generation::new(), zones: vec![zone_config], }, @@ -1783,8 +1826,7 @@ mod tests { let bp3_id = Uuid::new_v4(); let bp3 = Blueprint { id: bp3_id, - omicron_zones: bp3_omicron_zones, - zones_in_service: BTreeSet::new(), + blueprint_zones: bp3_zones, parent_blueprint_id: Some(bp2_id), internal_dns_version: Generation::new(), external_dns_version: Generation::new(), @@ -1819,13 +1861,14 @@ mod tests { // Finally, create a blueprint that includes our third and fourth sleds, // make it the target, and ensure we resolve to all four sleds. - let bp4_omicron_zones = { + let bp4_zones = { let mut zones = BTreeMap::new(); - for (sled_id, zone_config) in harness.omicron_zone_configs().skip(2) + for (sled_id, zone_config) in + harness.blueprint_zone_configs().skip(2) { zones.insert( sled_id, - OmicronZonesConfig { + BlueprintZonesConfig { generation: Generation::new(), zones: vec![zone_config], }, @@ -1836,8 +1879,7 @@ mod tests { let bp4_id = Uuid::new_v4(); let bp4 = Blueprint { id: bp4_id, - omicron_zones: bp4_omicron_zones, - zones_in_service: BTreeSet::new(), + blueprint_zones: bp4_zones, parent_blueprint_id: Some(bp3_id), internal_dns_version: Generation::new(), external_dns_version: Generation::new(), diff --git a/nexus/reconfigurator/execution/src/datasets.rs b/nexus/reconfigurator/execution/src/datasets.rs index 6da24e4279..1d08f3b294 100644 --- a/nexus/reconfigurator/execution/src/datasets.rs +++ b/nexus/reconfigurator/execution/src/datasets.rs @@ -19,11 +19,11 @@ use slog_error_chain::InlineErrorChain; use std::collections::BTreeSet; use std::net::SocketAddrV6; -/// For each crucible zone in `blueprint`, ensure that a corresponding dataset -/// record exists in `datastore` +/// For each crucible zone in `all_omicron_zones`, ensure that a corresponding +/// dataset record exists in `datastore` /// -/// Does not modify any existing dataset records. Returns the number of datasets -/// inserted. +/// Does not modify any existing dataset records. Returns the number of +/// datasets inserted. pub(crate) async fn ensure_crucible_dataset_records_exist( opctx: &OpContext, datastore: &DataStore, diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index c06db2c23e..7d7e24b6cf 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -18,6 +18,7 @@ use nexus_db_queries::db::datastore::DnsVersionUpdateBuilder; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO_ID; use nexus_db_queries::db::DataStore; use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::OmicronZoneType; use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsConfigParams; @@ -248,19 +249,18 @@ pub fn blueprint_internal_dns_config( .map(|addr| addr.port()) } - for (_, omicron_zone) in blueprint.all_omicron_zones() { - if !blueprint.zones_in_service.contains(&omicron_zone.id) { - continue; - } - + for (_, zone) in + blueprint.all_blueprint_zones(BlueprintZoneFilter::InternalDns) + { let context = || { format!( "parsing {} zone with id {}", - omicron_zone.zone_type.label(), - omicron_zone.id + zone.config.zone_type.label(), + zone.config.id ) }; - let (service_name, port) = match &omicron_zone.zone_type { + + let (service_name, port) = match &zone.config.zone_type { OmicronZoneType::BoundaryNtp { address, .. } => { let port = parse_port(&address).with_context(context)?; (ServiceName::BoundaryNtp, port) @@ -288,7 +288,7 @@ pub fn blueprint_internal_dns_config( } OmicronZoneType::Crucible { address, .. } => { let port = parse_port(address).with_context(context)?; - (ServiceName::Crucible(omicron_zone.id), port) + (ServiceName::Crucible(zone.config.id), port) } OmicronZoneType::CruciblePantry { address } => { let port = parse_port(address).with_context(context)?; @@ -312,8 +312,8 @@ pub fn blueprint_internal_dns_config( // the same zone id twice, which should not be possible here. dns_builder .host_zone_with_one_backend( - omicron_zone.id, - omicron_zone.underlay_address, + zone.config.id, + zone.config.underlay_address, service_name, port, ) @@ -469,6 +469,8 @@ mod test { use nexus_test_utils_macros::nexus_test; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintTarget; + use nexus_types::deployment::BlueprintZoneConfig; + use nexus_types::deployment::BlueprintZoneDisposition; use nexus_types::deployment::OmicronZoneConfig; use nexus_types::deployment::OmicronZoneType; use nexus_types::deployment::Policy; @@ -608,24 +610,27 @@ mod test { // not currently in service. let out_of_service_id = Uuid::new_v4(); let out_of_service_addr = Ipv6Addr::LOCALHOST; - blueprint.omicron_zones.values_mut().next().unwrap().zones.push( - OmicronZoneConfig { - id: out_of_service_id, - underlay_address: out_of_service_addr, - zone_type: OmicronZoneType::Oximeter { - address: SocketAddrV6::new( - out_of_service_addr, - 12345, - 0, - 0, - ) - .to_string(), + blueprint.blueprint_zones.values_mut().next().unwrap().zones.push( + BlueprintZoneConfig { + config: OmicronZoneConfig { + id: out_of_service_id, + underlay_address: out_of_service_addr, + zone_type: OmicronZoneType::Oximeter { + address: SocketAddrV6::new( + out_of_service_addr, + 12345, + 0, + 0, + ) + .to_string(), + }, }, + disposition: BlueprintZoneDisposition::Quiesced, }, ); // To generate the blueprint's DNS config, we need to make up a - // different set of information about the sleds in our fake system. + // different set of information about the Quiesced fake system. let sleds_by_id = policy .sleds .iter() diff --git a/nexus/reconfigurator/execution/src/lib.rs b/nexus/reconfigurator/execution/src/lib.rs index 8da4e7d44c..1373c9a31f 100644 --- a/nexus/reconfigurator/execution/src/lib.rs +++ b/nexus/reconfigurator/execution/src/lib.rs @@ -98,7 +98,7 @@ where resource_allocation::ensure_zone_resources_allocated( &opctx, datastore, - &blueprint.omicron_zones, + blueprint.all_omicron_zones().map(|(_sled_id, zone)| zone), ) .await .map_err(|err| vec![err])?; @@ -111,8 +111,12 @@ where .into_iter() .map(|db_sled| (db_sled.id(), Sled::from(db_sled))) .collect(); - omicron_zones::deploy_zones(&opctx, &sleds_by_id, &blueprint.omicron_zones) - .await?; + omicron_zones::deploy_zones( + &opctx, + &sleds_by_id, + &blueprint.blueprint_zones, + ) + .await?; // After deploying omicron zones, we may need to refresh OPTE service // firewall rules. This is an idempotent operation, so we don't attempt diff --git a/nexus/reconfigurator/execution/src/omicron_zones.rs b/nexus/reconfigurator/execution/src/omicron_zones.rs index 6054a40f8a..0150c40e9e 100644 --- a/nexus/reconfigurator/execution/src/omicron_zones.rs +++ b/nexus/reconfigurator/execution/src/omicron_zones.rs @@ -10,7 +10,8 @@ use anyhow::Context; use futures::stream; use futures::StreamExt; use nexus_db_queries::context::OpContext; -use nexus_types::deployment::OmicronZonesConfig; +use nexus_types::deployment::BlueprintZoneFilter; +use nexus_types::deployment::BlueprintZonesConfig; use slog::info; use slog::warn; use std::collections::BTreeMap; @@ -21,7 +22,7 @@ use uuid::Uuid; pub(crate) async fn deploy_zones( opctx: &OpContext, sleds_by_id: &BTreeMap, - zones: &BTreeMap, + zones: &BTreeMap, ) -> Result<(), Vec> { let errors: Vec<_> = stream::iter(zones) .filter_map(|(sled_id, config)| async move { @@ -33,14 +34,21 @@ pub(crate) async fn deploy_zones( return Some(err); } }; + let client = nexus_networking::sled_client_from_address( *sled_id, db_sled.sled_agent_address, &opctx.log, ); - let result = - client.omicron_zones_put(&config).await.with_context(|| { - format!("Failed to put {config:#?} to sled {sled_id}") + let omicron_zones = config + .to_omicron_zones_config(BlueprintZoneFilter::SledAgentPut); + let result = client + .omicron_zones_put(&omicron_zones) + .await + .with_context(|| { + format!( + "Failed to put {omicron_zones:#?} to sled {sled_id}" + ) }); match result { Err(error) => { @@ -78,22 +86,23 @@ mod test { use nexus_db_queries::context::OpContext; use nexus_test_utils_macros::nexus_test; use nexus_types::deployment::OmicronZonesConfig; - use nexus_types::deployment::{Blueprint, BlueprintTarget}; + use nexus_types::deployment::{ + Blueprint, BlueprintTarget, BlueprintZoneConfig, + BlueprintZoneDisposition, BlueprintZonesConfig, + }; use nexus_types::inventory::{ OmicronZoneConfig, OmicronZoneDataset, OmicronZoneType, }; use omicron_common::api::external::Generation; use std::collections::BTreeMap; - use std::collections::BTreeSet; use std::net::SocketAddr; - use std::sync::Arc; use uuid::Uuid; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; fn create_blueprint( - omicron_zones: BTreeMap, + blueprint_zones: BTreeMap, ) -> (BlueprintTarget, Blueprint) { let id = Uuid::new_v4(); ( @@ -104,8 +113,7 @@ mod test { }, Blueprint { id, - omicron_zones, - zones_in_service: BTreeSet::new(), + blueprint_zones, parent_blueprint_id: None, internal_dns_version: Generation::new(), external_dns_version: Generation::new(), @@ -149,31 +157,34 @@ mod test { // Get a success result back when the blueprint has an empty set of // zones. - let blueprint = Arc::new(create_blueprint(BTreeMap::new())); - deploy_zones(&opctx, &sleds_by_id, &blueprint.1.omicron_zones) + let (_, blueprint) = create_blueprint(BTreeMap::new()); + deploy_zones(&opctx, &sleds_by_id, &blueprint.blueprint_zones) .await .expect("failed to deploy no zones"); // Zones are updated in a particular order, but each request contains // the full set of zones that must be running. // See `rack_setup::service::ServiceInner::run` for more details. - fn make_zones() -> OmicronZonesConfig { - OmicronZonesConfig { + fn make_zones() -> BlueprintZonesConfig { + BlueprintZonesConfig { generation: Generation::new(), - zones: vec![OmicronZoneConfig { - id: Uuid::new_v4(), - underlay_address: "::1".parse().unwrap(), - zone_type: OmicronZoneType::InternalDns { - dataset: OmicronZoneDataset { - pool_name: format!("oxp_{}", Uuid::new_v4()) - .parse() - .unwrap(), + zones: vec![BlueprintZoneConfig { + config: OmicronZoneConfig { + id: Uuid::new_v4(), + underlay_address: "::1".parse().unwrap(), + zone_type: OmicronZoneType::InternalDns { + dataset: OmicronZoneDataset { + pool_name: format!("oxp_{}", Uuid::new_v4()) + .parse() + .unwrap(), + }, + dns_address: "oh-hello-internal-dns".into(), + gz_address: "::1".parse().unwrap(), + gz_address_index: 0, + http_address: "some-ipv6-address".into(), }, - dns_address: "oh-hello-internal-dns".into(), - gz_address: "::1".parse().unwrap(), - gz_address_index: 0, - http_address: "some-ipv6-address".into(), }, + disposition: BlueprintZoneDisposition::InService, }], } } @@ -183,10 +194,10 @@ mod test { // matter for this test. let mut zones1 = make_zones(); let mut zones2 = make_zones(); - let blueprint = Arc::new(create_blueprint(BTreeMap::from([ + let (_, blueprint) = create_blueprint(BTreeMap::from([ (sled_id1, zones1.clone()), (sled_id2, zones2.clone()), - ]))); + ])); // Set expectations for the initial requests sent to the fake // sled-agents. @@ -205,7 +216,7 @@ mod test { } // Execute it. - deploy_zones(&opctx, &sleds_by_id, &blueprint.1.omicron_zones) + deploy_zones(&opctx, &sleds_by_id, &blueprint.blueprint_zones) .await .expect("failed to deploy initial zones"); @@ -222,7 +233,7 @@ mod test { .respond_with(status_code(204)), ); } - deploy_zones(&opctx, &sleds_by_id, &blueprint.1.omicron_zones) + deploy_zones(&opctx, &sleds_by_id, &blueprint.blueprint_zones) .await .expect("failed to deploy same zones"); s1.verify_and_clear(); @@ -246,7 +257,7 @@ mod test { ); let errors = - deploy_zones(&opctx, &sleds_by_id, &blueprint.1.omicron_zones) + deploy_zones(&opctx, &sleds_by_id, &blueprint.blueprint_zones) .await .expect_err("unexpectedly succeeded in deploying zones"); @@ -259,26 +270,35 @@ mod test { s2.verify_and_clear(); // Add an `InternalNtp` zone for our next update - fn append_zone(zones: &mut OmicronZonesConfig) { + fn append_zone( + zones: &mut BlueprintZonesConfig, + disposition: BlueprintZoneDisposition, + ) { zones.generation = zones.generation.next(); - zones.zones.push(OmicronZoneConfig { - id: Uuid::new_v4(), - underlay_address: "::1".parse().unwrap(), - zone_type: OmicronZoneType::InternalNtp { - address: "::1".into(), - dns_servers: vec!["::1".parse().unwrap()], - domain: None, - ntp_servers: vec!["some-ntp-server-addr".into()], + zones.zones.push(BlueprintZoneConfig { + config: OmicronZoneConfig { + id: Uuid::new_v4(), + underlay_address: "::1".parse().unwrap(), + zone_type: OmicronZoneType::InternalNtp { + address: "::1".into(), + dns_servers: vec!["::1".parse().unwrap()], + domain: None, + ntp_servers: vec!["some-ntp-server-addr".into()], + }, }, + disposition, }); } - append_zone(&mut zones1); - append_zone(&mut zones2); - let blueprint = Arc::new(create_blueprint(BTreeMap::from([ + // Both in-service and quiesced zones should be deployed. + // + // TODO: add expunged zones to the test (should not be deployed). + append_zone(&mut zones1, BlueprintZoneDisposition::InService); + append_zone(&mut zones2, BlueprintZoneDisposition::Quiesced); + let (_, blueprint) = create_blueprint(BTreeMap::from([ (sled_id1, zones1), (sled_id2, zones2), - ]))); + ])); // Set our new expectations for s in [&mut s1, &mut s2] { @@ -296,7 +316,7 @@ mod test { } // Activate the task - deploy_zones(&opctx, &sleds_by_id, &blueprint.1.omicron_zones) + deploy_zones(&opctx, &sleds_by_id, &blueprint.blueprint_zones) .await .expect("failed to deploy last round of zones"); s1.verify_and_clear(); diff --git a/nexus/reconfigurator/execution/src/resource_allocation.rs b/nexus/reconfigurator/execution/src/resource_allocation.rs index 5872cee0f9..92262ce133 100644 --- a/nexus/reconfigurator/execution/src/resource_allocation.rs +++ b/nexus/reconfigurator/execution/src/resource_allocation.rs @@ -16,14 +16,13 @@ use nexus_db_queries::db::fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use nexus_db_queries::db::fixed_data::vpc_subnet::NTP_VPC_SUBNET; use nexus_db_queries::db::DataStore; use nexus_types::deployment::OmicronZoneType; -use nexus_types::deployment::OmicronZonesConfig; use nexus_types::deployment::SourceNatConfig; +use nexus_types::inventory::OmicronZoneConfig; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::internal::shared::NetworkInterface; use omicron_common::api::internal::shared::NetworkInterfaceKind; use slog::info; use slog::warn; -use std::collections::BTreeMap; use std::net::IpAddr; use std::net::SocketAddr; use uuid::Uuid; @@ -31,47 +30,45 @@ use uuid::Uuid; pub(crate) async fn ensure_zone_resources_allocated( opctx: &OpContext, datastore: &DataStore, - zones: &BTreeMap, + all_omicron_zones: impl Iterator, ) -> anyhow::Result<()> { let allocator = ResourceAllocator { opctx, datastore }; - for config in zones.values() { - for z in &config.zones { - match &z.zone_type { - OmicronZoneType::Nexus { external_ip, nic, .. } => { - allocator - .ensure_nexus_external_networking_allocated( - z.id, - *external_ip, - nic, - ) - .await?; - } - OmicronZoneType::ExternalDns { dns_address, nic, .. } => { - allocator - .ensure_external_dns_external_networking_allocated( - z.id, - dns_address, - nic, - ) - .await?; - } - OmicronZoneType::BoundaryNtp { snat_cfg, nic, .. } => { - allocator - .ensure_boundary_ntp_external_networking_allocated( - z.id, snat_cfg, nic, - ) - .await?; - } - OmicronZoneType::InternalNtp { .. } - | OmicronZoneType::Clickhouse { .. } - | OmicronZoneType::ClickhouseKeeper { .. } - | OmicronZoneType::CockroachDb { .. } - | OmicronZoneType::Crucible { .. } - | OmicronZoneType::CruciblePantry { .. } - | OmicronZoneType::InternalDns { .. } - | OmicronZoneType::Oximeter { .. } => (), + for z in all_omicron_zones { + match &z.zone_type { + OmicronZoneType::Nexus { external_ip, nic, .. } => { + allocator + .ensure_nexus_external_networking_allocated( + z.id, + *external_ip, + nic, + ) + .await?; + } + OmicronZoneType::ExternalDns { dns_address, nic, .. } => { + allocator + .ensure_external_dns_external_networking_allocated( + z.id, + dns_address, + nic, + ) + .await?; } + OmicronZoneType::BoundaryNtp { snat_cfg, nic, .. } => { + allocator + .ensure_boundary_ntp_external_networking_allocated( + z.id, snat_cfg, nic, + ) + .await?; + } + OmicronZoneType::InternalNtp { .. } + | OmicronZoneType::Clickhouse { .. } + | OmicronZoneType::ClickhouseKeeper { .. } + | OmicronZoneType::CockroachDb { .. } + | OmicronZoneType::Crucible { .. } + | OmicronZoneType::CruciblePantry { .. } + | OmicronZoneType::InternalDns { .. } + | OmicronZoneType::Oximeter { .. } => (), } } @@ -510,7 +507,6 @@ mod tests { use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_common::address::NTP_OPTE_IPV4_SUBNET; use omicron_common::address::NUM_SOURCE_NAT_PORTS; - use omicron_common::api::external::Generation; use omicron_common::api::external::IpNet; use omicron_common::api::external::MacAddr; use omicron_common::api::external::Vni; @@ -619,65 +615,56 @@ mod tests { // Build the `zones` map needed by `ensure_zone_resources_allocated`, // with an arbitrary sled_id. - let mut zones = BTreeMap::new(); - let sled_id = Uuid::new_v4(); - zones.insert( - sled_id, - OmicronZonesConfig { - generation: Generation::new().next(), - zones: vec![ - OmicronZoneConfig { - id: nexus_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::Nexus { - internal_address: Ipv6Addr::LOCALHOST.to_string(), - external_ip: nexus_external_ip, - nic: nexus_nic.clone(), - external_tls: false, - external_dns_servers: Vec::new(), - }, - }, - OmicronZoneConfig { - id: dns_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::ExternalDns { - dataset: OmicronZoneDataset { - pool_name: format!("oxp_{}", Uuid::new_v4()) - .parse() - .expect("bad name"), - }, - http_address: SocketAddrV6::new( - Ipv6Addr::LOCALHOST, - 0, - 0, - 0, - ) - .to_string(), - dns_address: SocketAddr::new(dns_external_ip, 0) - .to_string(), - nic: dns_nic.clone(), - }, - }, - OmicronZoneConfig { - id: ntp_id, - underlay_address: Ipv6Addr::LOCALHOST, - zone_type: OmicronZoneType::BoundaryNtp { - address: SocketAddr::new(dns_external_ip, 0) - .to_string(), - ntp_servers: Vec::new(), - dns_servers: Vec::new(), - domain: None, - nic: ntp_nic.clone(), - snat_cfg: ntp_snat, - }, + let zones = vec![ + OmicronZoneConfig { + id: nexus_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::Nexus { + internal_address: Ipv6Addr::LOCALHOST.to_string(), + external_ip: nexus_external_ip, + nic: nexus_nic.clone(), + external_tls: false, + external_dns_servers: Vec::new(), + }, + }, + OmicronZoneConfig { + id: dns_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::ExternalDns { + dataset: OmicronZoneDataset { + pool_name: format!("oxp_{}", Uuid::new_v4()) + .parse() + .expect("bad name"), }, - ], + http_address: SocketAddrV6::new( + Ipv6Addr::LOCALHOST, + 0, + 0, + 0, + ) + .to_string(), + dns_address: SocketAddr::new(dns_external_ip, 0) + .to_string(), + nic: dns_nic.clone(), + }, }, - ); + OmicronZoneConfig { + id: ntp_id, + underlay_address: Ipv6Addr::LOCALHOST, + zone_type: OmicronZoneType::BoundaryNtp { + address: SocketAddr::new(dns_external_ip, 0).to_string(), + ntp_servers: Vec::new(), + dns_servers: Vec::new(), + domain: None, + nic: ntp_nic.clone(), + snat_cfg: ntp_snat, + }, + }, + ]; // Initialize resource allocation: this should succeed and create all // the relevant db records. - ensure_zone_resources_allocated(&opctx, datastore, &zones) + ensure_zone_resources_allocated(&opctx, datastore, zones.iter()) .await .with_context(|| format!("{zones:#?}")) .unwrap(); @@ -761,7 +748,7 @@ mod tests { // We should be able to run the function again with the same inputs, and // it should succeed without inserting any new records. - ensure_zone_resources_allocated(&opctx, datastore, &zones) + ensure_zone_resources_allocated(&opctx, datastore, zones.iter()) .await .with_context(|| format!("{zones:#?}")) .unwrap(); @@ -814,8 +801,8 @@ mod tests { let bogus_ip = external_ips.next().expect("exhausted external_ips"); for mutate_zones_fn in [ // non-matching IP on Nexus - (&|config: &mut OmicronZonesConfig| { - for zone in &mut config.zones { + (&|zones: &mut [OmicronZoneConfig]| { + for zone in zones { if let OmicronZoneType::Nexus { ref mut external_ip, .. } = &mut zone.zone_type @@ -827,11 +814,12 @@ mod tests { ); } } + panic!("didn't find expected zone"); - }) as &dyn Fn(&mut OmicronZonesConfig) -> String, + }) as &dyn Fn(&mut [OmicronZoneConfig]) -> String, // non-matching IP on External DNS - &|config| { - for zone in &mut config.zones { + &|zones| { + for zone in zones { if let OmicronZoneType::ExternalDns { ref mut dns_address, .. @@ -847,8 +835,8 @@ mod tests { panic!("didn't find expected zone"); }, // non-matching SNAT port range on Boundary NTP - &|config| { - for zone in &mut config.zones { + &|zones| { + for zone in zones { if let OmicronZoneType::BoundaryNtp { ref mut snat_cfg, .. @@ -866,24 +854,24 @@ mod tests { }, ] { // Run `mutate_zones_fn` on our config... - let mut config = - zones.remove(&sled_id).expect("missing zone config"); - let orig_config = config.clone(); - let expected_error = mutate_zones_fn(&mut config); - zones.insert(sled_id, config); - - // ... check that we get the error we expect - let err = - ensure_zone_resources_allocated(&opctx, datastore, &zones) - .await - .expect_err("unexpected success"); + let (mutated_zones, expected_error) = { + let mut zones = zones.clone(); + let expected_error = mutate_zones_fn(&mut zones); + (zones, expected_error) + }; + + // and check that we get the error we expect. + let err = ensure_zone_resources_allocated( + &opctx, + datastore, + mutated_zones.iter(), + ) + .await + .expect_err("unexpected success"); assert!( err.to_string().contains(&expected_error), "expected {expected_error:?}, got {err:#}" ); - - // ... and restore the original, valid config before iterating. - zones.insert(sled_id, orig_config); } // Also try some requests that ought to fail because the request @@ -922,11 +910,7 @@ mod tests { ] { // Try this NIC mutation on Nexus... let mut mutated_zones = zones.clone(); - for zone in &mut mutated_zones - .get_mut(&sled_id) - .expect("missing sled") - .zones - { + for zone in &mut mutated_zones { if let OmicronZoneType::Nexus { ref mut nic, .. } = &mut zone.zone_type { @@ -935,7 +919,7 @@ mod tests { let err = ensure_zone_resources_allocated( &opctx, datastore, - &mutated_zones, + mutated_zones.iter(), ) .await .expect_err("unexpected success"); @@ -951,11 +935,7 @@ mod tests { // ... and again on ExternalDns let mut mutated_zones = zones.clone(); - for zone in &mut mutated_zones - .get_mut(&sled_id) - .expect("missing sled") - .zones - { + for zone in &mut mutated_zones { if let OmicronZoneType::ExternalDns { ref mut nic, .. } = &mut zone.zone_type { @@ -964,7 +944,7 @@ mod tests { let err = ensure_zone_resources_allocated( &opctx, datastore, - &mutated_zones, + mutated_zones.iter(), ) .await .expect_err("unexpected success"); @@ -980,11 +960,7 @@ mod tests { // ... and again on BoundaryNtp let mut mutated_zones = zones.clone(); - for zone in &mut mutated_zones - .get_mut(&sled_id) - .expect("missing sled") - .zones - { + for zone in &mut mutated_zones { if let OmicronZoneType::BoundaryNtp { ref mut nic, .. } = &mut zone.zone_type { @@ -993,7 +969,7 @@ mod tests { let err = ensure_zone_resources_allocated( &opctx, datastore, - &mutated_zones, + mutated_zones.iter(), ) .await .expect_err("unexpected success"); diff --git a/nexus/reconfigurator/planning/src/blueprint_builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder.rs index c9cd79ea14..0b0d422916 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder.rs @@ -13,10 +13,12 @@ use ipnet::IpAdd; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_inventory::now_db_precision; use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintZoneConfig; +use nexus_types::deployment::BlueprintZoneDisposition; +use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::deployment::OmicronZoneConfig; use nexus_types::deployment::OmicronZoneDataset; use nexus_types::deployment::OmicronZoneType; -use nexus_types::deployment::OmicronZonesConfig; use nexus_types::deployment::Policy; use nexus_types::deployment::SledResources; use nexus_types::deployment::ZpoolName; @@ -41,7 +43,6 @@ use rand::SeedableRng; use slog::o; use slog::Logger; use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::collections::HashSet; use std::hash::Hash; use std::net::IpAddr; @@ -118,8 +119,7 @@ pub struct BlueprintBuilder<'a> { // These fields will become part of the final blueprint. See the // corresponding fields in `Blueprint`. - zones: BlueprintZones<'a>, - zones_in_service: BTreeSet, + zones: BlueprintZonesBuilder<'a>, creator: String, comments: Vec, @@ -141,10 +141,10 @@ 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: &'a Collection, + collection: &Collection, internal_dns_version: Generation, external_dns_version: Generation, - policy: &'a Policy, + policy: &Policy, creator: &str, ) -> Result { Self::build_initial_impl( @@ -160,10 +160,10 @@ impl<'a> BlueprintBuilder<'a> { /// A version of [`Self::build_initial_from_collection`] that allows the /// blueprint ID to be generated from a random seed. pub fn build_initial_from_collection_seeded( - collection: &'a Collection, + collection: &Collection, internal_dns_version: Generation, external_dns_version: Generation, - policy: &'a Policy, + policy: &Policy, creator: &str, seed: H, ) -> Result { @@ -180,21 +180,21 @@ impl<'a> BlueprintBuilder<'a> { } fn build_initial_impl( - collection: &'a Collection, + collection: &Collection, internal_dns_version: Generation, external_dns_version: Generation, - policy: &'a Policy, + policy: &Policy, creator: &str, mut rng: BlueprintBuilderRng, ) -> Result { - let omicron_zones = policy + let blueprint_zones = policy .sleds .keys() .map(|sled_id| { - let mut zones = collection + let zones = collection .omicron_zones .get(sled_id) - .map(|z| z.zones.clone()) + .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 @@ -216,19 +216,15 @@ impl<'a> BlueprintBuilder<'a> { )) })?; - // This is not strictly necessary. But for testing, it's - // helpful for things to be in sorted order. - zones.zones.sort_by_key(|zone| zone.id); - - Ok((*sled_id, zones)) + Ok(( + *sled_id, + BlueprintZonesConfig::initial_from_collection(&zones), + )) }) .collect::>()?; - let zones_in_service = - collection.all_omicron_zones().map(|z| z.id).collect(); Ok(Blueprint { id: rng.blueprint_rng.next_uuid(), - omicron_zones, - zones_in_service, + blueprint_zones, parent_blueprint_id: None, internal_dns_version, external_dns_version, @@ -291,7 +287,8 @@ impl<'a> BlueprintBuilder<'a> { let mut used_macs: HashSet = HashSet::new(); for (_, z) in parent_blueprint.all_omicron_zones() { - if let OmicronZoneType::Nexus { nic, .. } = &z.zone_type { + let zone_type = &z.zone_type; + if let OmicronZoneType::Nexus { nic, .. } = zone_type { match nic.ip { IpAddr::V4(ip) => { if !existing_nexus_v4_ips.insert(ip) { @@ -305,7 +302,8 @@ impl<'a> BlueprintBuilder<'a> { } } } - if let Some(external_ip) = z.zone_type.external_ip()? { + + if let Some(external_ip) = zone_type.external_ip()? { // For the test suite, ignore localhost. It gets reused many // times and that's okay. We don't expect to see localhost // outside the test suite. @@ -315,7 +313,7 @@ impl<'a> BlueprintBuilder<'a> { bail!("duplicate external IP: {external_ip}"); } } - if let Some(nic) = z.zone_type.service_vnic() { + if let Some(nic) = zone_type.service_vnic() { if !used_macs.insert(nic.mac) { bail!("duplicate service vNIC MAC: {}", nic.mac); } @@ -360,8 +358,7 @@ impl<'a> BlueprintBuilder<'a> { external_dns_version, policy, sled_ip_allocators: BTreeMap::new(), - zones: BlueprintZones::new(parent_blueprint), - zones_in_service: parent_blueprint.zones_in_service.clone(), + zones: BlueprintZonesBuilder::new(parent_blueprint), creator: creator.to_owned(), comments: Vec::new(), nexus_v4_ips, @@ -375,12 +372,11 @@ impl<'a> BlueprintBuilder<'a> { /// Assemble a final [`Blueprint`] based on the contents of the builder pub fn build(mut self) -> Blueprint { // Collect the Omicron zones config for each in-service sled. - let omicron_zones = - self.zones.into_omicron_zones(self.policy.sleds.keys().copied()); + let blueprint_zones = + self.zones.into_zones_map(self.policy.sleds.keys().copied()); Blueprint { id: self.rng.blueprint_rng.next_uuid(), - omicron_zones, - zones_in_service: self.zones_in_service, + blueprint_zones, parent_blueprint_id: Some(self.parent_blueprint.id), internal_dns_version: self.internal_dns_version, external_dns_version: self.external_dns_version, @@ -418,7 +414,7 @@ impl<'a> BlueprintBuilder<'a> { let has_ntp = self .zones .current_sled_zones(sled_id) - .any(|z| z.zone_type.is_ntp()); + .any(|z| z.config.zone_type.is_ntp()); if has_ntp { return Ok(Ensure::NotNeeded); } @@ -465,6 +461,10 @@ impl<'a> BlueprintBuilder<'a> { domain: None, }, }; + let zone = BlueprintZoneConfig { + config: zone, + disposition: BlueprintZoneDisposition::InService, + }; self.sled_add_zone(sled_id, zone)?; Ok(Ensure::Added) @@ -479,7 +479,7 @@ impl<'a> BlueprintBuilder<'a> { let has_crucible_on_this_pool = self.zones.current_sled_zones(sled_id).any(|z| { matches!( - &z.zone_type, + &z.config.zone_type, OmicronZoneType::Crucible { dataset, .. } if dataset.pool_name == pool_name ) @@ -509,6 +509,11 @@ impl<'a> BlueprintBuilder<'a> { dataset: OmicronZoneDataset { pool_name }, }, }; + + let zone = BlueprintZoneConfig { + config: zone, + disposition: BlueprintZoneDisposition::InService, + }; self.sled_add_zone(sled_id, zone)?; Ok(Ensure::Added) } @@ -521,7 +526,7 @@ impl<'a> BlueprintBuilder<'a> { pub fn sled_num_nexus_zones(&self, sled_id: Uuid) -> usize { self.zones .current_sled_zones(sled_id) - .filter(|z| z.zone_type.is_nexus()) + .filter(|z| z.config.zone_type.is_nexus()) .count() } @@ -543,17 +548,14 @@ impl<'a> BlueprintBuilder<'a> { // settings should be part of `Policy` instead? let (external_tls, external_dns_servers) = self .parent_blueprint - .omicron_zones - .values() - .find_map(|sled_zones| { - sled_zones.zones.iter().find_map(|z| match &z.zone_type { - OmicronZoneType::Nexus { - external_tls, - external_dns_servers, - .. - } => Some((*external_tls, external_dns_servers.clone())), - _ => None, - }) + .all_omicron_zones() + .find_map(|(_, z)| match &z.zone_type { + OmicronZoneType::Nexus { + external_tls, + external_dns_servers, + .. + } => Some((*external_tls, external_dns_servers.clone())), + _ => None, }) .ok_or(Error::NoNexusZonesInParentBlueprint)?; self.sled_ensure_zone_multiple_nexus_with_config( @@ -642,6 +644,10 @@ impl<'a> BlueprintBuilder<'a> { external_dns_servers: external_dns_servers.clone(), }, }; + let zone = BlueprintZoneConfig { + config: zone, + disposition: BlueprintZoneDisposition::InService, + }; self.sled_add_zone(sled_id, zone)?; } @@ -651,19 +657,20 @@ impl<'a> BlueprintBuilder<'a> { fn sled_add_zone( &mut self, sled_id: Uuid, - zone: OmicronZoneConfig, + zone: BlueprintZoneConfig, ) -> Result<(), Error> { // Check the sled id and return an appropriate error if it's invalid. let _ = self.sled_resources(sled_id)?; - if !self.zones_in_service.insert(zone.id) { + let sled_zones = self.zones.change_sled_zones(sled_id); + // A sled should have a small number (< 20) of zones so a linear search + // should be very fast. + if sled_zones.zones.iter().any(|z| z.config.id == zone.config.id) { return Err(Error::Planner(anyhow!( "attempted to add zone that already exists: {}", - zone.id + zone.config.id ))); } - - let sled_zones = self.zones.change_sled_zones(sled_id); sled_zones.zones.push(zone); Ok(()) } @@ -698,7 +705,7 @@ impl<'a> BlueprintBuilder<'a> { // Record each of the sled's zones' underlay addresses as // allocated. for z in self.zones.current_sled_zones(sled_id) { - allocator.reserve(z.underlay_address); + allocator.reserve(z.config.underlay_address); } allocator @@ -790,19 +797,19 @@ impl UuidRng { /// Tracking the set of zones is slightly non-trivial because we need to bump /// the per-sled generation number iff the zones are changed. So we need to /// keep track of whether we've changed the zones relative to the parent -/// blueprint. We do this by keeping a copy of any `OmicronZonesConfig` that -/// we've changed and a _reference_ to the parent blueprint's zones. This +/// blueprint. We do this by keeping a copy of any [`BlueprintZonesConfig`] +/// that we've changed and a _reference_ to the parent blueprint's zones. This /// struct makes it easy for callers iterate over the right set of zones. -struct BlueprintZones<'a> { - changed_zones: BTreeMap, - parent_zones: &'a BTreeMap, +struct BlueprintZonesBuilder<'a> { + changed_zones: BTreeMap, + parent_zones: &'a BTreeMap, } -impl<'a> BlueprintZones<'a> { - pub fn new(parent_blueprint: &'a Blueprint) -> BlueprintZones { - BlueprintZones { +impl<'a> BlueprintZonesBuilder<'a> { + pub fn new(parent_blueprint: &'a Blueprint) -> BlueprintZonesBuilder { + BlueprintZonesBuilder { changed_zones: BTreeMap::new(), - parent_zones: &parent_blueprint.omicron_zones, + parent_zones: &parent_blueprint.blueprint_zones, } } @@ -813,19 +820,18 @@ impl<'a> BlueprintZones<'a> { pub fn change_sled_zones( &mut self, sled_id: Uuid, - ) -> &mut OmicronZonesConfig { + ) -> &mut BlueprintZonesConfig { self.changed_zones.entry(sled_id).or_insert_with(|| { if let Some(old_sled_zones) = self.parent_zones.get(&sled_id) { - OmicronZonesConfig { + BlueprintZonesConfig { generation: old_sled_zones.generation.next(), zones: old_sled_zones.zones.clone(), } } else { - // The first generation is reserved to mean the one - // containing no zones. See - // OmicronZonesConfig::INITIAL_GENERATION. So we start with the - // next one. - OmicronZonesConfig { + // The first generation is reserved to mean the one containing + // no zones. See OmicronZonesConfig::INITIAL_GENERATION. So + // we start with the next one. + BlueprintZonesConfig { generation: Generation::new().next(), zones: vec![], } @@ -838,7 +844,7 @@ impl<'a> BlueprintZones<'a> { pub fn current_sled_zones( &self, sled_id: Uuid, - ) -> Box + '_> { + ) -> Box + '_> { if let Some(sled_zones) = self .changed_zones .get(&sled_id) @@ -851,10 +857,10 @@ impl<'a> BlueprintZones<'a> { } /// Produces an owned map of zones for the requested sleds - pub fn into_omicron_zones( + pub fn into_zones_map( mut self, sled_ids: impl Iterator, - ) -> BTreeMap { + ) -> BTreeMap { sled_ids .map(|sled_id| { // Start with self.changed_zones, which contains entries for any @@ -868,14 +874,12 @@ impl<'a> BlueprintZones<'a> { // If it's not there either, then this must be a new sled // and we haven't added any zones to it yet. Use the // standard initial config. - .unwrap_or_else(|| OmicronZonesConfig { + .unwrap_or_else(|| BlueprintZonesConfig { generation: Generation::new(), zones: vec![], }); - // This is not strictly necessary. But for testing, it's - // helpful for things to be in sorted order. - zones.zones.sort_by_key(|zone| zone.id); + zones.sort(); (sled_id, zones) }) @@ -892,6 +896,7 @@ pub mod test { use omicron_common::address::IpRange; use omicron_test_utils::dev::test_setup_log; use sled_agent_client::types::{OmicronZoneConfig, OmicronZoneType}; + use std::collections::BTreeSet; pub const DEFAULT_N_SLEDS: usize = 3; @@ -899,17 +904,15 @@ pub mod test { pub fn verify_blueprint(blueprint: &Blueprint) { let mut underlay_ips: BTreeMap = BTreeMap::new(); - for sled_zones in blueprint.omicron_zones.values() { - for zone in &sled_zones.zones { - if let Some(previous) = - underlay_ips.insert(zone.underlay_address, zone) - { - panic!( - "found duplicate underlay IP {} in zones {} and \ + for (_, zone) in blueprint.all_omicron_zones() { + if let Some(previous) = + underlay_ips.insert(zone.underlay_address, zone) + { + panic!( + "found duplicate underlay IP {} in zones {} and \ {}\n\nblueprint: {:#?}", - zone.underlay_address, zone.id, previous.id, blueprint - ); - } + zone.underlay_address, zone.id, previous.id, blueprint + ); } } } @@ -934,11 +937,7 @@ pub mod test { .expect("failed to create initial blueprint"); verify_blueprint(&blueprint_initial); - // Since collections don't include what was in service, we have to - // provide that ourselves. For our purposes though we don't care. - let zones_in_service = blueprint_initial.zones_in_service.clone(); - let diff = blueprint_initial - .diff_sleds_from_collection(&collection, &zones_in_service); + let diff = blueprint_initial.diff_sleds_from_collection(&collection); println!( "collection -> initial blueprint (expected no changes):\n{}", diff.display() @@ -1057,13 +1056,15 @@ pub mod test { assert!(new_sled_resources .subnet .net() - .contains(z.underlay_address)); + .contains(z.config.underlay_address)); } // Check for an NTP zone. Its sockaddr's IP should also be on the // sled's subnet. assert!(new_sled_zones.zones.iter().any(|z| { - if let OmicronZoneType::InternalNtp { address, .. } = &z.zone_type { + if let OmicronZoneType::InternalNtp { address, .. } = + &z.config.zone_type + { let sockaddr = address.parse::().unwrap(); assert!(new_sled_resources .subnet @@ -1079,7 +1080,7 @@ pub mod test { .iter() .filter_map(|z| { if let OmicronZoneType::Crucible { address, dataset } = - &z.zone_type + &z.config.zone_type { let sockaddr = address.parse::().unwrap(); let ip = sockaddr.ip(); diff --git a/nexus/reconfigurator/planning/src/example.rs b/nexus/reconfigurator/planning/src/example.rs index c8c1bb380b..23df35e9ae 100644 --- a/nexus/reconfigurator/planning/src/example.rs +++ b/nexus/reconfigurator/planning/src/example.rs @@ -9,6 +9,7 @@ use crate::blueprint_builder::UuidRng; use crate::system::SledBuilder; use crate::system::SystemDescription; use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::Policy; use nexus_types::inventory::Collection; use omicron_common::api::external::Generation; @@ -108,14 +109,16 @@ impl ExampleSystem { system.to_collection_builder().expect("failed to build collection"); for sled_id in blueprint.sleds() { - let Some(zones) = blueprint.omicron_zones.get(&sled_id) else { + let Some(zones) = blueprint.blueprint_zones.get(&sled_id) else { continue; }; builder .found_sled_omicron_zones( "fake sled agent", sled_id, - zones.clone(), + zones.to_omicron_zones_config( + BlueprintZoneFilter::SledAgentPut, + ), ) .unwrap(); } diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 819e56b5d0..60eef225d3 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -340,6 +340,7 @@ mod test { use crate::system::SledBuilder; use expectorate::assert_contents; use nexus_inventory::now_db_precision; + use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::external_api::views::SledPolicy; use nexus_types::external_api::views::SledProvisionPolicy; use nexus_types::external_api::views::SledState; @@ -439,7 +440,7 @@ mod test { assert_eq!(sled_id, new_sled_id); assert_eq!(sled_zones.zones.len(), 1); assert!(matches!( - sled_zones.zones[0].zone_type, + sled_zones.zones[0].config.zone_type, OmicronZoneType::InternalNtp { .. } )); assert_eq!(diff.sleds_removed().count(), 0); @@ -480,10 +481,12 @@ mod test { source: String::from("test suite"), sled_id: new_sled_id, zones: blueprint4 - .omicron_zones + .blueprint_zones .get(&new_sled_id) - .cloned() - .expect("blueprint should contain zones for new sled"), + .expect("blueprint should contain zones for new sled") + .to_omicron_zones_config( + BlueprintZoneFilter::SledAgentPut + ) } ) .is_none()); @@ -524,9 +527,9 @@ mod test { let zones = sled_changes.zones_added().collect::>(); assert_eq!(zones.len(), 10); for zone in &zones { - let OmicronZoneType::Crucible { .. } = zone.zone_type else { + if !zone.config.zone_type.is_crucible() { panic!("unexpectedly added a non-Crucible zone: {zone:?}"); - }; + } } verify_blueprint(&blueprint5); @@ -599,15 +602,15 @@ mod test { // This blueprint should only have 1 Nexus instance on the one sled we // kept. - assert_eq!(blueprint1.omicron_zones.len(), 1); + assert_eq!(blueprint1.blueprint_zones.len(), 1); assert_eq!( blueprint1 - .omicron_zones + .blueprint_zones .get(&sled_id) .expect("missing kept sled") .zones .iter() - .filter(|z| z.zone_type.is_nexus()) + .filter(|z| z.config.zone_type.is_nexus()) .count(), 1 ); @@ -642,9 +645,9 @@ mod test { let zones = sled_changes.zones_added().collect::>(); assert_eq!(zones.len(), policy.target_nexus_zone_count - 1); for zone in &zones { - let OmicronZoneType::Nexus { .. } = zone.zone_type else { + if !zone.config.zone_type.is_nexus() { panic!("unexpectedly added a non-Nexus zone: {zone:?}"); - }; + } } logctx.cleanup_successful(); @@ -675,13 +678,13 @@ mod test { .expect("failed to create initial blueprint"); // This blueprint should only have 3 Nexus zones: one on each sled. - assert_eq!(blueprint1.omicron_zones.len(), 3); - for sled_config in blueprint1.omicron_zones.values() { + assert_eq!(blueprint1.blueprint_zones.len(), 3); + for sled_config in blueprint1.blueprint_zones.values() { assert_eq!( sled_config .zones .iter() - .filter(|z| z.zone_type.is_nexus()) + .filter(|z| z.config.zone_type.is_nexus()) .count(), 1 ); @@ -727,9 +730,9 @@ mod test { } } for zone in &zones { - let OmicronZoneType::Nexus { .. } = zone.zone_type else { - panic!("unexpectedly added a non-Crucible zone: {zone:?}"); - }; + if !zone.config.zone_type.is_nexus() { + panic!("unexpectedly added a non-Nexus zone: {zone:?}"); + } } } assert_eq!(total_new_nexus_zones, 11); @@ -766,13 +769,13 @@ mod test { .expect("failed to create initial blueprint"); // This blueprint should only have 5 Nexus zones: one on each sled. - assert_eq!(blueprint1.omicron_zones.len(), 5); - for sled_config in blueprint1.omicron_zones.values() { + assert_eq!(blueprint1.blueprint_zones.len(), 5); + for sled_config in blueprint1.blueprint_zones.values() { assert_eq!( sled_config .zones .iter() - .filter(|z| z.zone_type.is_nexus()) + .filter(|z| z.config.zone_type.is_nexus()) .count(), 1 ); @@ -857,7 +860,8 @@ mod test { } } for zone in &zones { - let OmicronZoneType::Nexus { .. } = zone.zone_type else { + let OmicronZoneType::Nexus { .. } = zone.config.zone_type + else { panic!("unexpectedly added a non-Crucible zone: {zone:?}"); }; } diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt index edad186e0c..9f7cab737f 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt @@ -3,46 +3,46 @@ diff blueprint 979ef428-0bdd-4622-8a72-0719e942b415 blueprint 4171ad05-89dd-474b +++ blueprint 4171ad05-89dd-474b-846b-b007e4346366 sled 41f45d9f-766e-4ca6-a881-61ee45c80f57 zone config generation 2 - zone 267ed614-92af-4b9d-bdba-c2881c2e43a2 type internal_ntp underlay IP fd00:1122:3344:103::21 (unchanged) - zone 322ee9f1-8903-4542-a0a8-a54cefabdeca type crucible underlay IP fd00:1122:3344:103::24 (unchanged) - zone 4ab1650f-32c5-447f-939d-64b8103a7645 type crucible underlay IP fd00:1122:3344:103::2a (unchanged) - zone 64aa65f8-1ccb-4cd6-9953-027aebdac8ff type crucible underlay IP fd00:1122:3344:103::27 (unchanged) - zone 6e811d86-8aa7-4660-935b-84b4b7721b10 type crucible underlay IP fd00:1122:3344:103::2b (unchanged) - zone 747d2426-68bf-4c22-8806-41d290b5d5f5 type crucible underlay IP fd00:1122:3344:103::25 (unchanged) - zone 7fbd2c38-5dc3-48c4-b061-558a2041d70f type crucible underlay IP fd00:1122:3344:103::2c (unchanged) - zone 8e9e923e-62b1-4cbc-9f59-d6397e338b6b type crucible underlay IP fd00:1122:3344:103::29 (unchanged) - zone b14d5478-1a0e-4b90-b526-36b06339dfc4 type crucible underlay IP fd00:1122:3344:103::28 (unchanged) - zone b40f7c7b-526c-46c8-ae33-67280c280eb7 type crucible underlay IP fd00:1122:3344:103::23 (unchanged) - zone be97b92b-38d6-422a-8c76-d37060f75bd2 type crucible underlay IP fd00:1122:3344:103::26 (unchanged) - zone cc816cfe-3869-4dde-b596-397d41198628 type nexus underlay IP fd00:1122:3344:103::22 (unchanged) + 267ed614-92af-4b9d-bdba-c2881c2e43a2 in service internal_ntp [underlay IP fd00:1122:3344:103::21] (unchanged) + 322ee9f1-8903-4542-a0a8-a54cefabdeca in service crucible [underlay IP fd00:1122:3344:103::24] (unchanged) + 4ab1650f-32c5-447f-939d-64b8103a7645 in service crucible [underlay IP fd00:1122:3344:103::2a] (unchanged) + 64aa65f8-1ccb-4cd6-9953-027aebdac8ff in service crucible [underlay IP fd00:1122:3344:103::27] (unchanged) + 6e811d86-8aa7-4660-935b-84b4b7721b10 in service crucible [underlay IP fd00:1122:3344:103::2b] (unchanged) + 747d2426-68bf-4c22-8806-41d290b5d5f5 in service crucible [underlay IP fd00:1122:3344:103::25] (unchanged) + 7fbd2c38-5dc3-48c4-b061-558a2041d70f in service crucible [underlay IP fd00:1122:3344:103::2c] (unchanged) + 8e9e923e-62b1-4cbc-9f59-d6397e338b6b in service crucible [underlay IP fd00:1122:3344:103::29] (unchanged) + b14d5478-1a0e-4b90-b526-36b06339dfc4 in service crucible [underlay IP fd00:1122:3344:103::28] (unchanged) + b40f7c7b-526c-46c8-ae33-67280c280eb7 in service crucible [underlay IP fd00:1122:3344:103::23] (unchanged) + be97b92b-38d6-422a-8c76-d37060f75bd2 in service crucible [underlay IP fd00:1122:3344:103::26] (unchanged) + cc816cfe-3869-4dde-b596-397d41198628 in service nexus [underlay IP fd00:1122:3344:103::22] (unchanged) sled 43677374-8d2f-4deb-8a41-eeea506db8e0 zone config generation 2 - zone 02acbe6a-1c88-47e3-94c3-94084cbde098 type crucible underlay IP fd00:1122:3344:101::27 (unchanged) - zone 07c3c805-8888-4fe5-9543-3d2479dbe6f3 type crucible underlay IP fd00:1122:3344:101::26 (unchanged) - zone 08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb type internal_ntp underlay IP fd00:1122:3344:101::21 (unchanged) - zone 10d98a73-ec88-4aff-a7e8-7db6a87880e6 type crucible underlay IP fd00:1122:3344:101::24 (unchanged) - zone 2a455c35-eb3c-4c73-ab6c-d0a706e25316 type crucible underlay IP fd00:1122:3344:101::29 (unchanged) - zone 3eda924f-22a9-4f3e-9a1b-91d1c47601ab type crucible underlay IP fd00:1122:3344:101::23 (unchanged) - zone 587be699-a320-4c79-b320-128d9ecddc0b type crucible underlay IP fd00:1122:3344:101::2b (unchanged) - zone 6fa06115-4959-4913-8e7b-dd70d7651f07 type crucible underlay IP fd00:1122:3344:101::2c (unchanged) - zone 8f3a1cc5-9195-4a30-ad02-b804278fe639 type crucible underlay IP fd00:1122:3344:101::28 (unchanged) - zone a1696cd4-588c-484a-b95b-66e824c0ce05 type crucible underlay IP fd00:1122:3344:101::25 (unchanged) - zone a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 type crucible underlay IP fd00:1122:3344:101::2a (unchanged) - zone c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 type nexus underlay IP fd00:1122:3344:101::22 (unchanged) + 02acbe6a-1c88-47e3-94c3-94084cbde098 in service crucible [underlay IP fd00:1122:3344:101::27] (unchanged) + 07c3c805-8888-4fe5-9543-3d2479dbe6f3 in service crucible [underlay IP fd00:1122:3344:101::26] (unchanged) + 08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb in service internal_ntp [underlay IP fd00:1122:3344:101::21] (unchanged) + 10d98a73-ec88-4aff-a7e8-7db6a87880e6 in service crucible [underlay IP fd00:1122:3344:101::24] (unchanged) + 2a455c35-eb3c-4c73-ab6c-d0a706e25316 in service crucible [underlay IP fd00:1122:3344:101::29] (unchanged) + 3eda924f-22a9-4f3e-9a1b-91d1c47601ab in service crucible [underlay IP fd00:1122:3344:101::23] (unchanged) + 587be699-a320-4c79-b320-128d9ecddc0b in service crucible [underlay IP fd00:1122:3344:101::2b] (unchanged) + 6fa06115-4959-4913-8e7b-dd70d7651f07 in service crucible [underlay IP fd00:1122:3344:101::2c] (unchanged) + 8f3a1cc5-9195-4a30-ad02-b804278fe639 in service crucible [underlay IP fd00:1122:3344:101::28] (unchanged) + a1696cd4-588c-484a-b95b-66e824c0ce05 in service crucible [underlay IP fd00:1122:3344:101::25] (unchanged) + a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 in service crucible [underlay IP fd00:1122:3344:101::2a] (unchanged) + c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 in service nexus [underlay IP fd00:1122:3344:101::22] (unchanged) sled 590e3034-d946-4166-b0e5-2d0034197a07 zone config generation 2 - zone 18f8fe40-646e-4962-b17a-20e201f3a6e5 type crucible underlay IP fd00:1122:3344:102::2a (unchanged) - zone 47199d48-534c-4267-a654-d2d90e64b498 type internal_ntp underlay IP fd00:1122:3344:102::21 (unchanged) - zone 56d5d7cf-db2c-40a3-a775-003241ad4820 type crucible underlay IP fd00:1122:3344:102::29 (unchanged) - zone 6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 type crucible underlay IP fd00:1122:3344:102::2b (unchanged) - zone 704e1fed-f8d6-4cfa-a470-bad27fdc06d1 type nexus underlay IP fd00:1122:3344:102::22 (unchanged) - zone 7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 type crucible underlay IP fd00:1122:3344:102::26 (unchanged) - zone 93f2f40c-5616-4d8d-8519-ec6debdcede0 type crucible underlay IP fd00:1122:3344:102::2c (unchanged) - zone ab7ba6df-d401-40bd-940e-faf57c57aa2a type crucible underlay IP fd00:1122:3344:102::28 (unchanged) - zone af322036-371f-437c-8c08-7f40f3f1403b type crucible underlay IP fd00:1122:3344:102::23 (unchanged) - zone d637264f-6f40-44c2-8b7e-a179430210d2 type crucible underlay IP fd00:1122:3344:102::25 (unchanged) - zone dce226c9-7373-4bfa-8a94-79dc472857a6 type crucible underlay IP fd00:1122:3344:102::27 (unchanged) - zone edabedf3-839c-488d-ad6f-508ffa864674 type crucible underlay IP fd00:1122:3344:102::24 (unchanged) + 18f8fe40-646e-4962-b17a-20e201f3a6e5 in service crucible [underlay IP fd00:1122:3344:102::2a] (unchanged) + 47199d48-534c-4267-a654-d2d90e64b498 in service internal_ntp [underlay IP fd00:1122:3344:102::21] (unchanged) + 56d5d7cf-db2c-40a3-a775-003241ad4820 in service crucible [underlay IP fd00:1122:3344:102::29] (unchanged) + 6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 in service crucible [underlay IP fd00:1122:3344:102::2b] (unchanged) + 704e1fed-f8d6-4cfa-a470-bad27fdc06d1 in service nexus [underlay IP fd00:1122:3344:102::22] (unchanged) + 7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 in service crucible [underlay IP fd00:1122:3344:102::26] (unchanged) + 93f2f40c-5616-4d8d-8519-ec6debdcede0 in service crucible [underlay IP fd00:1122:3344:102::2c] (unchanged) + ab7ba6df-d401-40bd-940e-faf57c57aa2a in service crucible [underlay IP fd00:1122:3344:102::28] (unchanged) + af322036-371f-437c-8c08-7f40f3f1403b in service crucible [underlay IP fd00:1122:3344:102::23] (unchanged) + d637264f-6f40-44c2-8b7e-a179430210d2 in service crucible [underlay IP fd00:1122:3344:102::25] (unchanged) + dce226c9-7373-4bfa-8a94-79dc472857a6 in service crucible [underlay IP fd00:1122:3344:102::27] (unchanged) + edabedf3-839c-488d-ad6f-508ffa864674 in service crucible [underlay IP fd00:1122:3344:102::24] (unchanged) + sled b59ec570-2abb-4017-80ce-129d94e7a025 (added) + zone config generation 2 -+ zone 2d73d30e-ca47-46a8-9c12-917d4ab824b6 type internal_ntp underlay IP fd00:1122:3344:104::21 (added) ++ 2d73d30e-ca47-46a8-9c12-917d4ab824b6 in service internal_ntp [underlay IP fd00:1122:3344:104::21] (added) diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt index 6ff8080165..9d98daac36 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt @@ -3,57 +3,57 @@ diff blueprint 4171ad05-89dd-474b-846b-b007e4346366 blueprint f432fcd5-1284-4058 +++ blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 sled 41f45d9f-766e-4ca6-a881-61ee45c80f57 zone config generation 2 - zone 267ed614-92af-4b9d-bdba-c2881c2e43a2 type internal_ntp underlay IP fd00:1122:3344:103::21 (unchanged) - zone 322ee9f1-8903-4542-a0a8-a54cefabdeca type crucible underlay IP fd00:1122:3344:103::24 (unchanged) - zone 4ab1650f-32c5-447f-939d-64b8103a7645 type crucible underlay IP fd00:1122:3344:103::2a (unchanged) - zone 64aa65f8-1ccb-4cd6-9953-027aebdac8ff type crucible underlay IP fd00:1122:3344:103::27 (unchanged) - zone 6e811d86-8aa7-4660-935b-84b4b7721b10 type crucible underlay IP fd00:1122:3344:103::2b (unchanged) - zone 747d2426-68bf-4c22-8806-41d290b5d5f5 type crucible underlay IP fd00:1122:3344:103::25 (unchanged) - zone 7fbd2c38-5dc3-48c4-b061-558a2041d70f type crucible underlay IP fd00:1122:3344:103::2c (unchanged) - zone 8e9e923e-62b1-4cbc-9f59-d6397e338b6b type crucible underlay IP fd00:1122:3344:103::29 (unchanged) - zone b14d5478-1a0e-4b90-b526-36b06339dfc4 type crucible underlay IP fd00:1122:3344:103::28 (unchanged) - zone b40f7c7b-526c-46c8-ae33-67280c280eb7 type crucible underlay IP fd00:1122:3344:103::23 (unchanged) - zone be97b92b-38d6-422a-8c76-d37060f75bd2 type crucible underlay IP fd00:1122:3344:103::26 (unchanged) - zone cc816cfe-3869-4dde-b596-397d41198628 type nexus underlay IP fd00:1122:3344:103::22 (unchanged) + 267ed614-92af-4b9d-bdba-c2881c2e43a2 in service internal_ntp [underlay IP fd00:1122:3344:103::21] (unchanged) + 322ee9f1-8903-4542-a0a8-a54cefabdeca in service crucible [underlay IP fd00:1122:3344:103::24] (unchanged) + 4ab1650f-32c5-447f-939d-64b8103a7645 in service crucible [underlay IP fd00:1122:3344:103::2a] (unchanged) + 64aa65f8-1ccb-4cd6-9953-027aebdac8ff in service crucible [underlay IP fd00:1122:3344:103::27] (unchanged) + 6e811d86-8aa7-4660-935b-84b4b7721b10 in service crucible [underlay IP fd00:1122:3344:103::2b] (unchanged) + 747d2426-68bf-4c22-8806-41d290b5d5f5 in service crucible [underlay IP fd00:1122:3344:103::25] (unchanged) + 7fbd2c38-5dc3-48c4-b061-558a2041d70f in service crucible [underlay IP fd00:1122:3344:103::2c] (unchanged) + 8e9e923e-62b1-4cbc-9f59-d6397e338b6b in service crucible [underlay IP fd00:1122:3344:103::29] (unchanged) + b14d5478-1a0e-4b90-b526-36b06339dfc4 in service crucible [underlay IP fd00:1122:3344:103::28] (unchanged) + b40f7c7b-526c-46c8-ae33-67280c280eb7 in service crucible [underlay IP fd00:1122:3344:103::23] (unchanged) + be97b92b-38d6-422a-8c76-d37060f75bd2 in service crucible [underlay IP fd00:1122:3344:103::26] (unchanged) + cc816cfe-3869-4dde-b596-397d41198628 in service nexus [underlay IP fd00:1122:3344:103::22] (unchanged) sled 43677374-8d2f-4deb-8a41-eeea506db8e0 zone config generation 2 - zone 02acbe6a-1c88-47e3-94c3-94084cbde098 type crucible underlay IP fd00:1122:3344:101::27 (unchanged) - zone 07c3c805-8888-4fe5-9543-3d2479dbe6f3 type crucible underlay IP fd00:1122:3344:101::26 (unchanged) - zone 08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb type internal_ntp underlay IP fd00:1122:3344:101::21 (unchanged) - zone 10d98a73-ec88-4aff-a7e8-7db6a87880e6 type crucible underlay IP fd00:1122:3344:101::24 (unchanged) - zone 2a455c35-eb3c-4c73-ab6c-d0a706e25316 type crucible underlay IP fd00:1122:3344:101::29 (unchanged) - zone 3eda924f-22a9-4f3e-9a1b-91d1c47601ab type crucible underlay IP fd00:1122:3344:101::23 (unchanged) - zone 587be699-a320-4c79-b320-128d9ecddc0b type crucible underlay IP fd00:1122:3344:101::2b (unchanged) - zone 6fa06115-4959-4913-8e7b-dd70d7651f07 type crucible underlay IP fd00:1122:3344:101::2c (unchanged) - zone 8f3a1cc5-9195-4a30-ad02-b804278fe639 type crucible underlay IP fd00:1122:3344:101::28 (unchanged) - zone a1696cd4-588c-484a-b95b-66e824c0ce05 type crucible underlay IP fd00:1122:3344:101::25 (unchanged) - zone a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 type crucible underlay IP fd00:1122:3344:101::2a (unchanged) - zone c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 type nexus underlay IP fd00:1122:3344:101::22 (unchanged) + 02acbe6a-1c88-47e3-94c3-94084cbde098 in service crucible [underlay IP fd00:1122:3344:101::27] (unchanged) + 07c3c805-8888-4fe5-9543-3d2479dbe6f3 in service crucible [underlay IP fd00:1122:3344:101::26] (unchanged) + 08c7f8aa-1ea9-469b-8cac-2fdbfc11ebcb in service internal_ntp [underlay IP fd00:1122:3344:101::21] (unchanged) + 10d98a73-ec88-4aff-a7e8-7db6a87880e6 in service crucible [underlay IP fd00:1122:3344:101::24] (unchanged) + 2a455c35-eb3c-4c73-ab6c-d0a706e25316 in service crucible [underlay IP fd00:1122:3344:101::29] (unchanged) + 3eda924f-22a9-4f3e-9a1b-91d1c47601ab in service crucible [underlay IP fd00:1122:3344:101::23] (unchanged) + 587be699-a320-4c79-b320-128d9ecddc0b in service crucible [underlay IP fd00:1122:3344:101::2b] (unchanged) + 6fa06115-4959-4913-8e7b-dd70d7651f07 in service crucible [underlay IP fd00:1122:3344:101::2c] (unchanged) + 8f3a1cc5-9195-4a30-ad02-b804278fe639 in service crucible [underlay IP fd00:1122:3344:101::28] (unchanged) + a1696cd4-588c-484a-b95b-66e824c0ce05 in service crucible [underlay IP fd00:1122:3344:101::25] (unchanged) + a2079cbc-a69e-41a1-b1e0-fbcb972d03f6 in service crucible [underlay IP fd00:1122:3344:101::2a] (unchanged) + c66ab6d5-ff7a-46d1-9fd0-70cefa352d25 in service nexus [underlay IP fd00:1122:3344:101::22] (unchanged) sled 590e3034-d946-4166-b0e5-2d0034197a07 zone config generation 2 - zone 18f8fe40-646e-4962-b17a-20e201f3a6e5 type crucible underlay IP fd00:1122:3344:102::2a (unchanged) - zone 47199d48-534c-4267-a654-d2d90e64b498 type internal_ntp underlay IP fd00:1122:3344:102::21 (unchanged) - zone 56d5d7cf-db2c-40a3-a775-003241ad4820 type crucible underlay IP fd00:1122:3344:102::29 (unchanged) - zone 6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 type crucible underlay IP fd00:1122:3344:102::2b (unchanged) - zone 704e1fed-f8d6-4cfa-a470-bad27fdc06d1 type nexus underlay IP fd00:1122:3344:102::22 (unchanged) - zone 7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 type crucible underlay IP fd00:1122:3344:102::26 (unchanged) - zone 93f2f40c-5616-4d8d-8519-ec6debdcede0 type crucible underlay IP fd00:1122:3344:102::2c (unchanged) - zone ab7ba6df-d401-40bd-940e-faf57c57aa2a type crucible underlay IP fd00:1122:3344:102::28 (unchanged) - zone af322036-371f-437c-8c08-7f40f3f1403b type crucible underlay IP fd00:1122:3344:102::23 (unchanged) - zone d637264f-6f40-44c2-8b7e-a179430210d2 type crucible underlay IP fd00:1122:3344:102::25 (unchanged) - zone dce226c9-7373-4bfa-8a94-79dc472857a6 type crucible underlay IP fd00:1122:3344:102::27 (unchanged) - zone edabedf3-839c-488d-ad6f-508ffa864674 type crucible underlay IP fd00:1122:3344:102::24 (unchanged) + 18f8fe40-646e-4962-b17a-20e201f3a6e5 in service crucible [underlay IP fd00:1122:3344:102::2a] (unchanged) + 47199d48-534c-4267-a654-d2d90e64b498 in service internal_ntp [underlay IP fd00:1122:3344:102::21] (unchanged) + 56d5d7cf-db2c-40a3-a775-003241ad4820 in service crucible [underlay IP fd00:1122:3344:102::29] (unchanged) + 6af7f4d6-33b6-4eb3-a146-d8e9e4ae9d66 in service crucible [underlay IP fd00:1122:3344:102::2b] (unchanged) + 704e1fed-f8d6-4cfa-a470-bad27fdc06d1 in service nexus [underlay IP fd00:1122:3344:102::22] (unchanged) + 7a9f60d3-2b66-4547-9b63-7d4f7a8b6382 in service crucible [underlay IP fd00:1122:3344:102::26] (unchanged) + 93f2f40c-5616-4d8d-8519-ec6debdcede0 in service crucible [underlay IP fd00:1122:3344:102::2c] (unchanged) + ab7ba6df-d401-40bd-940e-faf57c57aa2a in service crucible [underlay IP fd00:1122:3344:102::28] (unchanged) + af322036-371f-437c-8c08-7f40f3f1403b in service crucible [underlay IP fd00:1122:3344:102::23] (unchanged) + d637264f-6f40-44c2-8b7e-a179430210d2 in service crucible [underlay IP fd00:1122:3344:102::25] (unchanged) + dce226c9-7373-4bfa-8a94-79dc472857a6 in service crucible [underlay IP fd00:1122:3344:102::27] (unchanged) + edabedf3-839c-488d-ad6f-508ffa864674 in service crucible [underlay IP fd00:1122:3344:102::24] (unchanged) sled b59ec570-2abb-4017-80ce-129d94e7a025 - zone config generation 2 + zone config generation 3 - zone 2d73d30e-ca47-46a8-9c12-917d4ab824b6 type internal_ntp underlay IP fd00:1122:3344:104::21 (unchanged) -+ zone 1a20ee3c-f66e-4fca-ab85-2a248aa3d79d type crucible underlay IP fd00:1122:3344:104::2b (added) -+ zone 28852beb-d0e5-4cba-9adb-e7f0cd4bb864 type crucible underlay IP fd00:1122:3344:104::29 (added) -+ zone 45556184-7092-4a3d-873f-637976bb133b type crucible underlay IP fd00:1122:3344:104::22 (added) -+ zone 8215bf7a-10d6-4f40-aeb7-27a196307c37 type crucible underlay IP fd00:1122:3344:104::25 (added) -+ zone 9d75abfe-47ab-434a-93dd-af50dc0dddde type crucible underlay IP fd00:1122:3344:104::23 (added) -+ zone a36d291c-7f68-462f-830e-bc29e5841ce2 type crucible underlay IP fd00:1122:3344:104::27 (added) -+ zone b3a4d434-aaee-4752-8c99-69d88fbcb8c5 type crucible underlay IP fd00:1122:3344:104::2a (added) -+ zone cf5b636b-a505-4db6-bc32-baf9f53f4371 type crucible underlay IP fd00:1122:3344:104::28 (added) -+ zone f6125d45-b9cc-4721-ba60-ed4dbb177e41 type crucible underlay IP fd00:1122:3344:104::26 (added) -+ zone f86e19d2-9145-41cf-be89-6aaa34a73873 type crucible underlay IP fd00:1122:3344:104::24 (added) + 2d73d30e-ca47-46a8-9c12-917d4ab824b6 in service internal_ntp [underlay IP fd00:1122:3344:104::21] (unchanged) ++ 1a20ee3c-f66e-4fca-ab85-2a248aa3d79d in service crucible [underlay IP fd00:1122:3344:104::2b] (added) ++ 28852beb-d0e5-4cba-9adb-e7f0cd4bb864 in service crucible [underlay IP fd00:1122:3344:104::29] (added) ++ 45556184-7092-4a3d-873f-637976bb133b in service crucible [underlay IP fd00:1122:3344:104::22] (added) ++ 8215bf7a-10d6-4f40-aeb7-27a196307c37 in service crucible [underlay IP fd00:1122:3344:104::25] (added) ++ 9d75abfe-47ab-434a-93dd-af50dc0dddde in service crucible [underlay IP fd00:1122:3344:104::23] (added) ++ a36d291c-7f68-462f-830e-bc29e5841ce2 in service crucible [underlay IP fd00:1122:3344:104::27] (added) ++ b3a4d434-aaee-4752-8c99-69d88fbcb8c5 in service crucible [underlay IP fd00:1122:3344:104::2a] (added) ++ cf5b636b-a505-4db6-bc32-baf9f53f4371 in service crucible [underlay IP fd00:1122:3344:104::28] (added) ++ f6125d45-b9cc-4721-ba60-ed4dbb177e41 in service crucible [underlay IP fd00:1122:3344:104::26] (added) ++ f86e19d2-9145-41cf-be89-6aaa34a73873 in service crucible [underlay IP fd00:1122:3344:104::24] (added) 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 b63a92ad94..17d3db6228 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt @@ -3,84 +3,84 @@ diff blueprint 55502b1b-e255-438b-a16a-2680a4b5f962 blueprint 9f71f5d3-a272-4382 +++ blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 sled 2d1cb4f2-cf44-40fc-b118-85036eb732a9 zone config generation 2 - zone 19fbc4f8-a683-4f22-8f5a-e74782b935be type crucible underlay IP fd00:1122:3344:105::26 (unchanged) - zone 4f1ce8a2-d3a5-4a38-be4c-9817de52db37 type crucible underlay IP fd00:1122:3344:105::2c (unchanged) - zone 6b53ab2e-d98c-485f-87a3-4d5df595390f type crucible underlay IP fd00:1122:3344:105::27 (unchanged) - zone 6dff7633-66bb-4924-a6ff-2c896e66964b type nexus underlay IP fd00:1122:3344:105::22 (unchanged) - zone 7f4e9f9f-08f8-4d14-885d-e977c05525ad type internal_ntp underlay IP fd00:1122:3344:105::21 (unchanged) - zone 93b137a1-a1d6-4b5b-b2cb-21a9f11e2883 type crucible underlay IP fd00:1122:3344:105::23 (unchanged) - zone 9f0abbad-dbd3-4d43-9675-78092217ffd9 type crucible underlay IP fd00:1122:3344:105::25 (unchanged) - zone b0c63f48-01ea-4aae-bb26-fb0dd59d1662 type crucible underlay IP fd00:1122:3344:105::28 (unchanged) - zone c406da50-34b9-4bb4-a460-8f49875d2a6a type crucible underlay IP fd00:1122:3344:105::24 (unchanged) - zone d660d7ed-28c0-45ae-9ace-dc3ecf7e8786 type crucible underlay IP fd00:1122:3344:105::2a (unchanged) - zone e98cc0de-abf6-4da4-a20d-d05c7a9bb1d7 type crucible underlay IP fd00:1122:3344:105::2b (unchanged) - zone f55e6aaf-e8fc-4913-9e3c-8cd1bd4bdad3 type crucible underlay IP fd00:1122:3344:105::29 (unchanged) + 19fbc4f8-a683-4f22-8f5a-e74782b935be in service crucible [underlay IP fd00:1122:3344:105::26] (unchanged) + 4f1ce8a2-d3a5-4a38-be4c-9817de52db37 in service crucible [underlay IP fd00:1122:3344:105::2c] (unchanged) + 6b53ab2e-d98c-485f-87a3-4d5df595390f in service crucible [underlay IP fd00:1122:3344:105::27] (unchanged) + 6dff7633-66bb-4924-a6ff-2c896e66964b in service nexus [underlay IP fd00:1122:3344:105::22] (unchanged) + 7f4e9f9f-08f8-4d14-885d-e977c05525ad in service internal_ntp [underlay IP fd00:1122:3344:105::21] (unchanged) + 93b137a1-a1d6-4b5b-b2cb-21a9f11e2883 in service crucible [underlay IP fd00:1122:3344:105::23] (unchanged) + 9f0abbad-dbd3-4d43-9675-78092217ffd9 in service crucible [underlay IP fd00:1122:3344:105::25] (unchanged) + b0c63f48-01ea-4aae-bb26-fb0dd59d1662 in service crucible [underlay IP fd00:1122:3344:105::28] (unchanged) + c406da50-34b9-4bb4-a460-8f49875d2a6a in service crucible [underlay IP fd00:1122:3344:105::24] (unchanged) + d660d7ed-28c0-45ae-9ace-dc3ecf7e8786 in service crucible [underlay IP fd00:1122:3344:105::2a] (unchanged) + e98cc0de-abf6-4da4-a20d-d05c7a9bb1d7 in service crucible [underlay IP fd00:1122:3344:105::2b] (unchanged) + f55e6aaf-e8fc-4913-9e3c-8cd1bd4bdad3 in service crucible [underlay IP fd00:1122:3344:105::29] (unchanged) sled 48d95fef-bc9f-4f50-9a53-1e075836291d zone config generation 2 - zone 094f27af-1acb-4d1e-ba97-1fc1377d4bf2 type crucible underlay IP fd00:1122:3344:103::2c (unchanged) - zone 0dcfdfc5-481e-4153-b97c-11cf02b648ea type crucible underlay IP fd00:1122:3344:103::25 (unchanged) - zone 2aa0ea4f-3561-4989-a98c-9ab7d9a240fb type nexus underlay IP fd00:1122:3344:103::22 (unchanged) - zone 2f5e8010-a94d-43a4-9c5c-3f52832f5f7f type crucible underlay IP fd00:1122:3344:103::27 (unchanged) - zone 4a9a0a9d-87f0-4f1d-9181-27f6b435e637 type crucible underlay IP fd00:1122:3344:103::28 (unchanged) - zone 56ac1706-9e2a-49ba-bd6f-a99c44cb2ccb type crucible underlay IP fd00:1122:3344:103::24 (unchanged) - zone 67622d61-2df4-414d-aa0e-d1277265f405 type crucible underlay IP fd00:1122:3344:103::23 (unchanged) - zone 67d913e0-0005-4599-9b28-0abbf6cc2916 type internal_ntp underlay IP fd00:1122:3344:103::21 (unchanged) - zone b91b271d-8d80-4f49-99a0-34006ae86063 type crucible underlay IP fd00:1122:3344:103::2a (unchanged) - zone d6ee1338-3127-43ec-9aaa-b973ccf05496 type crucible underlay IP fd00:1122:3344:103::26 (unchanged) - zone e39d7c9e-182b-48af-af87-58079d723583 type crucible underlay IP fd00:1122:3344:103::29 (unchanged) - zone f69f92a1-5007-4bb0-a85b-604dc217154b type crucible underlay IP fd00:1122:3344:103::2b (unchanged) + 094f27af-1acb-4d1e-ba97-1fc1377d4bf2 in service crucible [underlay IP fd00:1122:3344:103::2c] (unchanged) + 0dcfdfc5-481e-4153-b97c-11cf02b648ea in service crucible [underlay IP fd00:1122:3344:103::25] (unchanged) + 2aa0ea4f-3561-4989-a98c-9ab7d9a240fb in service nexus [underlay IP fd00:1122:3344:103::22] (unchanged) + 2f5e8010-a94d-43a4-9c5c-3f52832f5f7f in service crucible [underlay IP fd00:1122:3344:103::27] (unchanged) + 4a9a0a9d-87f0-4f1d-9181-27f6b435e637 in service crucible [underlay IP fd00:1122:3344:103::28] (unchanged) + 56ac1706-9e2a-49ba-bd6f-a99c44cb2ccb in service crucible [underlay IP fd00:1122:3344:103::24] (unchanged) + 67622d61-2df4-414d-aa0e-d1277265f405 in service crucible [underlay IP fd00:1122:3344:103::23] (unchanged) + 67d913e0-0005-4599-9b28-0abbf6cc2916 in service internal_ntp [underlay IP fd00:1122:3344:103::21] (unchanged) + b91b271d-8d80-4f49-99a0-34006ae86063 in service crucible [underlay IP fd00:1122:3344:103::2a] (unchanged) + d6ee1338-3127-43ec-9aaa-b973ccf05496 in service crucible [underlay IP fd00:1122:3344:103::26] (unchanged) + e39d7c9e-182b-48af-af87-58079d723583 in service crucible [underlay IP fd00:1122:3344:103::29] (unchanged) + f69f92a1-5007-4bb0-a85b-604dc217154b in service crucible [underlay IP fd00:1122:3344:103::2b] (unchanged) sled 68d24ac5-f341-49ea-a92a-0381b52ab387 zone config generation 2 - zone 01d58626-e1b0-480f-96be-ac784863c7dc type nexus underlay IP fd00:1122:3344:102::22 (unchanged) - zone 3b3c14b6-a8e2-4054-a577-8d96cb576230 type crucible underlay IP fd00:1122:3344:102::2c (unchanged) - zone 47a87c6e-ef45-4d52-9a3e-69cdd96737cc type crucible underlay IP fd00:1122:3344:102::23 (unchanged) - zone 6464d025-4652-4948-919e-740bec5699b1 type crucible underlay IP fd00:1122:3344:102::24 (unchanged) - zone 6939ce48-b17c-4616-b176-8a419a7697be type crucible underlay IP fd00:1122:3344:102::29 (unchanged) - zone 878dfddd-3113-4197-a3ea-e0d4dbe9b476 type crucible underlay IP fd00:1122:3344:102::25 (unchanged) - zone 8d4d2b28-82bb-4e36-80da-1408d8c35d82 type crucible underlay IP fd00:1122:3344:102::2b (unchanged) - zone 9fd52961-426f-4e62-a644-b70871103fca type crucible underlay IP fd00:1122:3344:102::26 (unchanged) - zone b44cdbc0-0ce0-46eb-8b21-a09e113aa1d0 type crucible underlay IP fd00:1122:3344:102::27 (unchanged) - zone b6b759d0-f60d-42b7-bbbc-9d61c9e895a9 type crucible underlay IP fd00:1122:3344:102::28 (unchanged) - zone c407795c-6c8b-428e-8ab8-b962913c447f type crucible underlay IP fd00:1122:3344:102::2a (unchanged) - zone f3f2e4f3-0985-4ef6-8336-ce479382d05d type internal_ntp underlay IP fd00:1122:3344:102::21 (unchanged) + 01d58626-e1b0-480f-96be-ac784863c7dc in service nexus [underlay IP fd00:1122:3344:102::22] (unchanged) + 3b3c14b6-a8e2-4054-a577-8d96cb576230 in service crucible [underlay IP fd00:1122:3344:102::2c] (unchanged) + 47a87c6e-ef45-4d52-9a3e-69cdd96737cc in service crucible [underlay IP fd00:1122:3344:102::23] (unchanged) + 6464d025-4652-4948-919e-740bec5699b1 in service crucible [underlay IP fd00:1122:3344:102::24] (unchanged) + 6939ce48-b17c-4616-b176-8a419a7697be in service crucible [underlay IP fd00:1122:3344:102::29] (unchanged) + 878dfddd-3113-4197-a3ea-e0d4dbe9b476 in service crucible [underlay IP fd00:1122:3344:102::25] (unchanged) + 8d4d2b28-82bb-4e36-80da-1408d8c35d82 in service crucible [underlay IP fd00:1122:3344:102::2b] (unchanged) + 9fd52961-426f-4e62-a644-b70871103fca in service crucible [underlay IP fd00:1122:3344:102::26] (unchanged) + b44cdbc0-0ce0-46eb-8b21-a09e113aa1d0 in service crucible [underlay IP fd00:1122:3344:102::27] (unchanged) + b6b759d0-f60d-42b7-bbbc-9d61c9e895a9 in service crucible [underlay IP fd00:1122:3344:102::28] (unchanged) + c407795c-6c8b-428e-8ab8-b962913c447f in service crucible [underlay IP fd00:1122:3344:102::2a] (unchanged) + f3f2e4f3-0985-4ef6-8336-ce479382d05d in service internal_ntp [underlay IP fd00:1122:3344:102::21] (unchanged) sled 75bc286f-2b4b-482c-9431-59272af529da - zone config generation 2 + zone config generation 3 - zone 15bb9def-69b8-4d2e-b04f-9fee1143387c type crucible underlay IP fd00:1122:3344:104::25 (unchanged) - zone 23a8fa2b-ef3e-4017-a43f-f7a83953bd7c type crucible underlay IP fd00:1122:3344:104::2c (unchanged) - zone 57b96d5c-b71e-43e4-8869-7d514003d00d type internal_ntp underlay IP fd00:1122:3344:104::21 (unchanged) - zone 621509d6-3772-4009-aca1-35eefd1098fb type crucible underlay IP fd00:1122:3344:104::28 (unchanged) - zone 85b8c68a-160d-461d-94dd-1baf175fa75c type crucible underlay IP fd00:1122:3344:104::2a (unchanged) - zone 996d7570-b0df-46d5-aaa4-0c97697cf484 type crucible underlay IP fd00:1122:3344:104::26 (unchanged) - zone a732c489-d29a-4f75-b900-5966385943af type crucible underlay IP fd00:1122:3344:104::29 (unchanged) - zone b1783e95-9598-451d-b6ba-c50b52b428c3 type crucible underlay IP fd00:1122:3344:104::24 (unchanged) - zone b4947d31-f70e-4ee0-8817-0ca6cea9b16b type nexus underlay IP fd00:1122:3344:104::22 (unchanged) - zone c6dd531e-2d1d-423b-acc8-358533dab78c type crucible underlay IP fd00:1122:3344:104::27 (unchanged) - zone e4b3e159-3dbe-48cb-8497-e3da92a90e5a type crucible underlay IP fd00:1122:3344:104::23 (unchanged) - zone f0ff59e8-4105-4980-a4bb-a1f4c58de1e3 type crucible underlay IP fd00:1122:3344:104::2b (unchanged) -+ zone 2ec75441-3d7d-4b4b-9614-af03de5a3666 type nexus underlay IP fd00:1122:3344:104::2d (added) -+ zone 3ca5292f-8a59-4475-bb72-0f43714d0fff type nexus underlay IP fd00:1122:3344:104::31 (added) -+ zone 508abd03-cbfe-4654-9a6d-7f15a1ad32e5 type nexus underlay IP fd00:1122:3344:104::2e (added) -+ zone 59950bc8-1497-44dd-8cbf-b6502ba921b2 type nexus underlay IP fd00:1122:3344:104::2f (added) -+ zone 99f6d544-8599-4e2b-a55a-82d9e0034662 type nexus underlay IP fd00:1122:3344:104::30 (added) -+ zone c26b3bda-5561-44a1-a69f-22103fe209a1 type nexus underlay IP fd00:1122:3344:104::32 (added) + 15bb9def-69b8-4d2e-b04f-9fee1143387c in service crucible [underlay IP fd00:1122:3344:104::25] (unchanged) + 23a8fa2b-ef3e-4017-a43f-f7a83953bd7c in service crucible [underlay IP fd00:1122:3344:104::2c] (unchanged) + 57b96d5c-b71e-43e4-8869-7d514003d00d in service internal_ntp [underlay IP fd00:1122:3344:104::21] (unchanged) + 621509d6-3772-4009-aca1-35eefd1098fb in service crucible [underlay IP fd00:1122:3344:104::28] (unchanged) + 85b8c68a-160d-461d-94dd-1baf175fa75c in service crucible [underlay IP fd00:1122:3344:104::2a] (unchanged) + 996d7570-b0df-46d5-aaa4-0c97697cf484 in service crucible [underlay IP fd00:1122:3344:104::26] (unchanged) + a732c489-d29a-4f75-b900-5966385943af in service crucible [underlay IP fd00:1122:3344:104::29] (unchanged) + b1783e95-9598-451d-b6ba-c50b52b428c3 in service crucible [underlay IP fd00:1122:3344:104::24] (unchanged) + b4947d31-f70e-4ee0-8817-0ca6cea9b16b in service nexus [underlay IP fd00:1122:3344:104::22] (unchanged) + c6dd531e-2d1d-423b-acc8-358533dab78c in service crucible [underlay IP fd00:1122:3344:104::27] (unchanged) + e4b3e159-3dbe-48cb-8497-e3da92a90e5a in service crucible [underlay IP fd00:1122:3344:104::23] (unchanged) + f0ff59e8-4105-4980-a4bb-a1f4c58de1e3 in service crucible [underlay IP fd00:1122:3344:104::2b] (unchanged) ++ 2ec75441-3d7d-4b4b-9614-af03de5a3666 in service nexus [underlay IP fd00:1122:3344:104::2d] (added) ++ 3ca5292f-8a59-4475-bb72-0f43714d0fff in service nexus [underlay IP fd00:1122:3344:104::31] (added) ++ 508abd03-cbfe-4654-9a6d-7f15a1ad32e5 in service nexus [underlay IP fd00:1122:3344:104::2e] (added) ++ 59950bc8-1497-44dd-8cbf-b6502ba921b2 in service nexus [underlay IP fd00:1122:3344:104::2f] (added) ++ 99f6d544-8599-4e2b-a55a-82d9e0034662 in service nexus [underlay IP fd00:1122:3344:104::30] (added) ++ c26b3bda-5561-44a1-a69f-22103fe209a1 in service nexus [underlay IP fd00:1122:3344:104::32] (added) sled affab35f-600a-4109-8ea0-34a067a4e0bc - zone config generation 2 + zone config generation 3 - zone 0dfbf374-9ef9-430f-b06d-f271bf7f84c4 type crucible underlay IP fd00:1122:3344:101::27 (unchanged) - zone 15c103f0-ac63-423b-ba5d-1b5fcd563ba3 type nexus underlay IP fd00:1122:3344:101::22 (unchanged) - zone 3aa07966-5899-4789-ace5-f8eeb375c6c3 type crucible underlay IP fd00:1122:3344:101::24 (unchanged) - zone 4ad0e9da-08f8-4d40-b4d3-d17e711b5bbf type crucible underlay IP fd00:1122:3344:101::29 (unchanged) - zone 72c5a909-077d-4ec1-a9d5-ae64ef9d716e type crucible underlay IP fd00:1122:3344:101::26 (unchanged) - zone 95482c25-1e7f-43e8-adf1-e3548a1b3ae0 type crucible underlay IP fd00:1122:3344:101::23 (unchanged) - zone a1c03689-fc62-4ea5-bb72-4d01f5138614 type crucible underlay IP fd00:1122:3344:101::2a (unchanged) - zone a568e92e-4fbd-4b69-acd8-f16277073031 type crucible underlay IP fd00:1122:3344:101::2c (unchanged) - zone bf79a56a-97af-4cc4-94a5-8b20d64c2cda type crucible underlay IP fd00:1122:3344:101::28 (unchanged) - zone c60379ba-4e30-4628-a79a-0ae509aef4c5 type crucible underlay IP fd00:1122:3344:101::25 (unchanged) - zone d47f4996-fac0-4657-bcea-01b1fee6404d type crucible underlay IP fd00:1122:3344:101::2b (unchanged) - zone f1a7b9a7-fc6a-4b23-b829-045ff33117ff type internal_ntp underlay IP fd00:1122:3344:101::21 (unchanged) -+ zone 6f86d5cb-17d7-424b-9d4c-39f670532cbe type nexus underlay IP fd00:1122:3344:101::2e (added) -+ zone 87c299eb-470e-4b6d-b8c7-6759694e66b6 type nexus underlay IP fd00:1122:3344:101::30 (added) -+ zone c72b7930-0580-4f00-93b9-8cba2c8d344e type nexus underlay IP fd00:1122:3344:101::2d (added) -+ zone d0095508-bdb8-4faf-b091-964276a20b15 type nexus underlay IP fd00:1122:3344:101::31 (added) -+ zone ff422442-4b31-4ade-a11a-9e5a25f0404c type nexus underlay IP fd00:1122:3344:101::2f (added) + 0dfbf374-9ef9-430f-b06d-f271bf7f84c4 in service crucible [underlay IP fd00:1122:3344:101::27] (unchanged) + 15c103f0-ac63-423b-ba5d-1b5fcd563ba3 in service nexus [underlay IP fd00:1122:3344:101::22] (unchanged) + 3aa07966-5899-4789-ace5-f8eeb375c6c3 in service crucible [underlay IP fd00:1122:3344:101::24] (unchanged) + 4ad0e9da-08f8-4d40-b4d3-d17e711b5bbf in service crucible [underlay IP fd00:1122:3344:101::29] (unchanged) + 72c5a909-077d-4ec1-a9d5-ae64ef9d716e in service crucible [underlay IP fd00:1122:3344:101::26] (unchanged) + 95482c25-1e7f-43e8-adf1-e3548a1b3ae0 in service crucible [underlay IP fd00:1122:3344:101::23] (unchanged) + a1c03689-fc62-4ea5-bb72-4d01f5138614 in service crucible [underlay IP fd00:1122:3344:101::2a] (unchanged) + a568e92e-4fbd-4b69-acd8-f16277073031 in service crucible [underlay IP fd00:1122:3344:101::2c] (unchanged) + bf79a56a-97af-4cc4-94a5-8b20d64c2cda in service crucible [underlay IP fd00:1122:3344:101::28] (unchanged) + c60379ba-4e30-4628-a79a-0ae509aef4c5 in service crucible [underlay IP fd00:1122:3344:101::25] (unchanged) + d47f4996-fac0-4657-bcea-01b1fee6404d in service crucible [underlay IP fd00:1122:3344:101::2b] (unchanged) + f1a7b9a7-fc6a-4b23-b829-045ff33117ff in service internal_ntp [underlay IP fd00:1122:3344:101::21] (unchanged) ++ 6f86d5cb-17d7-424b-9d4c-39f670532cbe in service nexus [underlay IP fd00:1122:3344:101::2e] (added) ++ 87c299eb-470e-4b6d-b8c7-6759694e66b6 in service nexus [underlay IP fd00:1122:3344:101::30] (added) ++ c72b7930-0580-4f00-93b9-8cba2c8d344e in service nexus [underlay IP fd00:1122:3344:101::2d] (added) ++ d0095508-bdb8-4faf-b091-964276a20b15 in service nexus [underlay IP fd00:1122:3344:101::31] (added) ++ ff422442-4b31-4ade-a11a-9e5a25f0404c in service nexus [underlay IP fd00:1122:3344:101::2f] (added) diff --git a/nexus/src/app/background/blueprint_execution.rs b/nexus/src/app/background/blueprint_execution.rs index c65a49ec24..7db59bc966 100644 --- a/nexus/src/app/background/blueprint_execution.rs +++ b/nexus/src/app/background/blueprint_execution.rs @@ -38,64 +38,72 @@ impl BlueprintExecutor { pub fn watcher(&self) -> watch::Receiver { self.tx.subscribe() } -} -impl BackgroundTask for BlueprintExecutor { - fn activate<'a>( - &'a mut self, - opctx: &'a OpContext, - ) -> BoxFuture<'a, serde_json::Value> { - async { - // Get the latest blueprint, cloning to prevent holding a read lock - // on the watch. - let update = self.rx_blueprint.borrow_and_update().clone(); + /// Implementation for `BackgroundTask::activate` for `BlueprintExecutor`, + /// added here to produce better compile errors. + /// + /// The presence of `boxed()` in `BackgroundTask::activate` has caused some + /// confusion with compilation errors in the past. So separate this method + /// out. + async fn activate_impl<'a>( + &mut self, + opctx: &OpContext, + ) -> serde_json::Value { + // Get the latest blueprint, cloning to prevent holding a read lock + // on the watch. + let update = self.rx_blueprint.borrow_and_update().clone(); - let Some(update) = update else { - warn!(&opctx.log, + let Some(update) = update else { + warn!(&opctx.log, "Blueprint execution: skipped"; "reason" => "no blueprint"); - return json!({"error": "no blueprint" }); - }; + return json!({"error": "no blueprint" }); + }; - let (bp_target, blueprint) = &*update; - if !bp_target.enabled { - warn!(&opctx.log, + let (bp_target, blueprint) = &*update; + if !bp_target.enabled { + warn!(&opctx.log, "Blueprint execution: skipped"; "reason" => "blueprint disabled", "target_id" => %blueprint.id); - return json!({ - "target_id": blueprint.id.to_string(), - "error": "blueprint disabled" - }); - } + return json!({ + "target_id": blueprint.id.to_string(), + "error": "blueprint disabled" + }); + } - let result = nexus_reconfigurator_execution::realize_blueprint( - opctx, - &self.datastore, - blueprint, - &self.nexus_label, - ) - .await; + let result = nexus_reconfigurator_execution::realize_blueprint( + opctx, + &self.datastore, + blueprint, + &self.nexus_label, + ) + .await; - // Trigger anybody waiting for this to finish. - self.tx.send_modify(|count| *count = *count + 1); + // Trigger anybody waiting for this to finish. + self.tx.send_modify(|count| *count = *count + 1); - // Return the result as a `serde_json::Value` - match result { - Ok(()) => json!({}), - Err(errors) => { - let errors: Vec<_> = errors - .into_iter() - .map(|e| format!("{:#}", e)) - .collect(); - json!({ - "target_id": blueprint.id.to_string(), - "errors": errors - }) - } + // Return the result as a `serde_json::Value` + match result { + Ok(()) => json!({}), + Err(errors) => { + let errors: Vec<_> = + errors.into_iter().map(|e| format!("{:#}", e)).collect(); + json!({ + "target_id": blueprint.id.to_string(), + "errors": errors + }) } } - .boxed() + } +} + +impl BackgroundTask for BlueprintExecutor { + fn activate<'a>( + &'a mut self, + opctx: &'a OpContext, + ) -> BoxFuture<'a, serde_json::Value> { + self.activate_impl(opctx).boxed() } } @@ -112,8 +120,10 @@ mod test { use nexus_db_queries::authn; use nexus_db_queries::context::OpContext; use nexus_test_utils_macros::nexus_test; - use nexus_types::deployment::OmicronZonesConfig; - use nexus_types::deployment::{Blueprint, BlueprintTarget}; + use nexus_types::deployment::{ + Blueprint, BlueprintTarget, BlueprintZoneConfig, + BlueprintZoneDisposition, BlueprintZonesConfig, + }; use nexus_types::inventory::{ OmicronZoneConfig, OmicronZoneDataset, OmicronZoneType, }; @@ -121,7 +131,6 @@ mod test { use serde::Deserialize; use serde_json::json; use std::collections::BTreeMap; - use std::collections::BTreeSet; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::watch; @@ -131,7 +140,7 @@ mod test { nexus_test_utils::ControlPlaneTestContext; fn create_blueprint( - omicron_zones: BTreeMap, + blueprint_zones: BTreeMap, dns_version: Generation, ) -> (BlueprintTarget, Blueprint) { let id = Uuid::new_v4(); @@ -143,8 +152,7 @@ mod test { }, Blueprint { id, - omicron_zones, - zones_in_service: BTreeSet::new(), + blueprint_zones, parent_blueprint_id: None, internal_dns_version: dns_version, external_dns_version: dns_version, @@ -229,31 +237,41 @@ mod test { // Create a non-empty blueprint describing two servers and verify that // the task correctly winds up making requests to both of them and // reporting success. - fn make_zones() -> OmicronZonesConfig { - OmicronZonesConfig { + fn make_zones( + disposition: BlueprintZoneDisposition, + ) -> BlueprintZonesConfig { + BlueprintZonesConfig { generation: Generation::new(), - zones: vec![OmicronZoneConfig { - id: Uuid::new_v4(), - underlay_address: "::1".parse().unwrap(), - zone_type: OmicronZoneType::InternalDns { - dataset: OmicronZoneDataset { - pool_name: format!("oxp_{}", Uuid::new_v4()) - .parse() - .unwrap(), + zones: vec![BlueprintZoneConfig { + config: OmicronZoneConfig { + id: Uuid::new_v4(), + underlay_address: "::1".parse().unwrap(), + zone_type: OmicronZoneType::InternalDns { + dataset: OmicronZoneDataset { + pool_name: format!("oxp_{}", Uuid::new_v4()) + .parse() + .unwrap(), + }, + dns_address: "oh-hello-internal-dns".into(), + gz_address: "::1".parse().unwrap(), + gz_address_index: 0, + http_address: "[::1]:12345".into(), }, - dns_address: "oh-hello-internal-dns".into(), - gz_address: "::1".parse().unwrap(), - gz_address_index: 0, - http_address: "some-ipv6-address".into(), }, + disposition, }], } } + let generation = generation.next(); + + // Both in-service and quiesced zones should be deployed. + // + // TODO: add expunged zones to the test (should not be deployed). let mut blueprint = create_blueprint( BTreeMap::from([ - (sled_id1, make_zones()), - (sled_id2, make_zones()), + (sled_id1, make_zones(BlueprintZoneDisposition::InService)), + (sled_id2, make_zones(BlueprintZoneDisposition::Quiesced)), ]), generation, ); diff --git a/nexus/src/app/background/blueprint_load.rs b/nexus/src/app/background/blueprint_load.rs index 614c59ff9b..2afe2d2f97 100644 --- a/nexus/src/app/background/blueprint_load.rs +++ b/nexus/src/app/background/blueprint_load.rs @@ -210,7 +210,7 @@ mod test { use nexus_types::deployment::{Blueprint, BlueprintTarget}; use omicron_common::api::external::Generation; use serde::Deserialize; - use std::collections::{BTreeMap, BTreeSet}; + use std::collections::BTreeMap; use uuid::Uuid; type ControlPlaneTestContext = @@ -228,8 +228,7 @@ mod test { }, Blueprint { id, - omicron_zones: BTreeMap::new(), - zones_in_service: BTreeSet::new(), + blueprint_zones: BTreeMap::new(), parent_blueprint_id: Some(parent_blueprint_id), internal_dns_version: Generation::new(), external_dns_version: Generation::new(), diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 0358cf523c..cc9c8c43df 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -26,6 +26,9 @@ use nexus_config::NexusConfig; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_test_interface::NexusServer; use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintZoneConfig; +use nexus_types::deployment::BlueprintZoneDisposition; +use nexus_types::deployment::BlueprintZonesConfig; use nexus_types::external_api::params::UserId; use nexus_types::internal_api::params::Certificate; use nexus_types::internal_api::params::DatasetCreateRequest; @@ -57,7 +60,6 @@ use oximeter_producer::LogConfig; use oximeter_producer::Server as ProducerServer; use slog::{debug, error, o, Logger}; use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::collections::HashMap; use std::fmt::Debug; use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6}; @@ -790,29 +792,34 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { }; let blueprint = { - let mut omicron_zones = BTreeMap::new(); - let mut zones_in_service = BTreeSet::new(); + let mut blueprint_zones = BTreeMap::new(); for (maybe_sled_agent, zones) in [ (self.sled_agent.as_ref(), &self.omicron_zones), (self.sled_agent2.as_ref(), &self.omicron_zones2), ] { if let Some(sa) = maybe_sled_agent { - omicron_zones.insert( + blueprint_zones.insert( sa.sled_agent.id, - OmicronZonesConfig { + BlueprintZonesConfig { generation: Generation::new().next(), - zones: zones.clone(), + zones: zones + .iter() + .map(|z| { + BlueprintZoneConfig { + config: z.clone(), + // All initial zones are in-service + disposition: + BlueprintZoneDisposition::InService, + } + }) + .collect(), }, ); - for z in zones { - zones_in_service.insert(z.id); - } } } Blueprint { id: Uuid::new_v4(), - omicron_zones, - zones_in_service, + blueprint_zones, parent_blueprint_id: None, internal_dns_version: dns_config .generation diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index a6b53272eb..b435964b53 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -31,6 +31,8 @@ use serde::Serialize; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::fmt; +use strum::EnumIter; +use strum::IntoEnumIterator; use uuid::Uuid; /// Fleet-wide deployment policy @@ -138,14 +140,12 @@ pub struct Blueprint { /// unique identifier for this blueprint pub id: Uuid, - /// mapping: sled id -> zones deployed on each sled + /// A map of sled id -> zones deployed on each sled, along with the + /// [`BlueprintZoneDisposition`] for each zone. + /// /// A sled is considered part of the control plane cluster iff it has an /// entry in this map. - pub omicron_zones: BTreeMap, - - /// Omicron zones considered in-service (which generally means that they - /// should appear in DNS) - pub zones_in_service: BTreeSet, + pub blueprint_zones: BTreeMap, /// which blueprint this blueprint is based on pub parent_blueprint_id: Option, @@ -169,19 +169,32 @@ pub struct Blueprint { } impl Blueprint { - /// Iterate over all the Omicron zones in the blueprint, along with - /// associated sled id + /// Iterate over the [`BlueprintZoneConfig`] instances in the blueprint + /// that match the provided filter, along with the associated sled id. + pub fn all_blueprint_zones( + &self, + filter: BlueprintZoneFilter, + ) -> impl Iterator { + self.blueprint_zones.iter().flat_map(move |(sled_id, z)| { + z.zones.iter().filter_map(move |z| { + z.disposition.matches(filter).then_some((*sled_id, z)) + }) + }) + } + + /// Iterate over all the [`OmicronZoneConfig`] instances in the blueprint, + /// along with the associated sled id. pub fn all_omicron_zones( &self, ) -> impl Iterator { - self.omicron_zones - .iter() - .flat_map(|(sled_id, z)| z.zones.iter().map(|z| (*sled_id, z))) + self.blueprint_zones.iter().flat_map(|(sled_id, z)| { + z.zones.iter().map(|z| (*sled_id, &z.config)) + }) } /// Iterate over the ids of all sleds in the blueprint pub fn sleds(&self) -> impl Iterator + '_ { - self.omicron_zones.keys().copied() + self.blueprint_zones.keys().copied() } /// Summarize the difference between sleds and zones between two blueprints @@ -191,43 +204,58 @@ impl Blueprint { ) -> OmicronZonesDiff<'a> { OmicronZonesDiff { before_label: format!("blueprint {}", self.id), - before_zones: self.omicron_zones.clone(), - before_zones_in_service: &self.zones_in_service, + before_zones: self.blueprint_zones.clone(), after_label: format!("blueprint {}", other.id), - after_zones: &other.omicron_zones, - after_zones_in_service: &other.zones_in_service, + after_zones: &other.blueprint_zones, } } /// Summarize the differences in sleds and zones between a collection and a /// blueprint /// - /// This gives an idea about what would change about a running system if one - /// were to execute the blueprint. + /// This gives an idea about what would change about a running system if + /// one were to execute the blueprint. /// /// Note that collections do not currently include information about what - /// zones are in-service, so the caller must provide that information. - pub fn diff_sleds_from_collection<'a>( - &'a self, - collection: &'a Collection, - before_zones_in_service: &'a BTreeSet, - ) -> OmicronZonesDiff<'a> { + /// zones are in-service, so it is assumed that all zones in the collection + /// are in-service. (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.) + pub fn diff_sleds_from_collection( + &self, + collection: &Collection, + ) -> OmicronZonesDiff<'_> { let before_zones = collection .omicron_zones .iter() - .map(|(sled_id, zones_found)| (*sled_id, zones_found.zones.clone())) + .map(|(sled_id, zones_found)| { + let zones = zones_found + .zones + .zones + .iter() + .map(|z| BlueprintZoneConfig { + config: z.clone(), + disposition: BlueprintZoneDisposition::InService, + }) + .collect(); + let zones = BlueprintZonesConfig { + generation: zones_found.zones.generation, + zones, + }; + (*sled_id, zones) + }) .collect(); OmicronZonesDiff { before_label: format!("collection {}", collection.id), before_zones, - before_zones_in_service, after_label: format!("blueprint {}", self.id), - after_zones: &self.omicron_zones, - after_zones_in_service: &self.zones_in_service, + after_zones: &self.blueprint_zones, } } - /// Return a struct that can be displayed. + /// Return a struct that can be displayed to present information about the + /// blueprint. pub fn display(&self) -> BlueprintDisplay<'_> { BlueprintDisplay { blueprint: self } } @@ -237,6 +265,7 @@ impl Blueprint { /// /// Returned by [`Blueprint::display()`]. #[derive(Clone, Debug)] +#[must_use = "this struct does nothing unless displayed"] pub struct BlueprintDisplay<'a> { blueprint: &'a Blueprint, // TODO: add colorization with a stylesheet @@ -271,24 +300,15 @@ impl<'a> fmt::Display for BlueprintDisplay<'a> { writeln!(f, "internal DNS version: {}", b.internal_dns_version)?; writeln!(f, "comment: {}", b.comment)?; writeln!(f, "zones:\n")?; - for (sled_id, sled_zones) in &b.omicron_zones { + + for (sled_id, sled_zones) in &b.blueprint_zones { writeln!( f, " sled {}: Omicron zones at generation {}", sled_id, sled_zones.generation )?; for z in &sled_zones.zones { - writeln!( - f, - " {} {} {}", - z.id, - if b.zones_in_service.contains(&z.id) { - "in service " - } else { - "not in service" - }, - z.zone_type.label(), - )?; + writeln!(f, " {}", z.display())?; } } @@ -296,6 +316,236 @@ impl<'a> fmt::Display for BlueprintDisplay<'a> { } } +/// Information about an Omicron zone as recorded in a blueprint. +/// +/// Currently, this is similar to [`OmicronZonesConfig`], but also contains a +/// per-zone [`BlueprintZoneDisposition`]. +/// +/// Part of [`Blueprint`]. +#[derive(Debug, Clone, Eq, PartialEq, JsonSchema, Deserialize, Serialize)] +pub struct BlueprintZonesConfig { + /// Generation number of this configuration. + /// + /// This generation number is owned by the control plane. See + /// [`OmicronZonesConfig::generation`] for more details. + pub generation: Generation, + + /// The list of running zones. + pub zones: Vec, +} + +impl BlueprintZonesConfig { + /// Constructs a new [`BlueprintZonesConfig`] from a collection's zones. + /// + /// For the initial blueprint, all zones within a collection are assumed to + /// be in-service. + pub fn initial_from_collection(collection: &OmicronZonesConfig) -> Self { + let zones = collection + .zones + .iter() + .map(|z| BlueprintZoneConfig { + config: z.clone(), + disposition: 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(); + + ret + } + + /// Sorts the list of zones stored in this configuration. + /// + /// This is not strictly necessary. But for testing, it's helpful for + /// zones to be in sorted order. + pub fn sort(&mut self) { + self.zones.sort_unstable_by_key(|z| z.config.id); + } + + /// Converts self to an [`OmicronZonesConfig`], applying the provided + /// [`BlueprintZoneFilter`]. + /// + /// The filter controls which zones should be exported into the resulting + /// [`OmicronZonesConfig`]. + pub fn to_omicron_zones_config( + &self, + filter: BlueprintZoneFilter, + ) -> OmicronZonesConfig { + OmicronZonesConfig { + generation: self.generation, + zones: self + .zones + .iter() + .filter_map(|z| { + z.disposition.matches(filter).then(|| z.config.clone()) + }) + .collect(), + } + } +} + +/// Describes one Omicron-managed zone in a blueprint. +/// +/// This is a wrapper around an [`OmicronZoneConfig`] that also includes a +/// [`BlueprintZoneDisposition`]. +/// +/// Part of [`BlueprintZonesConfig`]. +#[derive(Debug, Clone, Eq, PartialEq, JsonSchema, Deserialize, Serialize)] +pub struct BlueprintZoneConfig { + /// The underlying zone configuration. + pub config: OmicronZoneConfig, + + /// The disposition (desired state) of this zone recorded in the blueprint. + pub disposition: BlueprintZoneDisposition, +} + +impl BlueprintZoneConfig { + /// Return a struct that can be displayed to present information about the + /// zone. + pub fn display(&self) -> BlueprintZoneConfigDisplay<'_> { + BlueprintZoneConfigDisplay { zone: self } + } +} + +/// A wrapper to allow a [`BlueprintZoneConfig`] to be displayed with +/// information. +/// +/// Returned by [`BlueprintZoneConfig::display()`]. +#[derive(Clone, Debug)] +#[must_use = "this struct does nothing unless displayed"] +pub struct BlueprintZoneConfigDisplay<'a> { + zone: &'a BlueprintZoneConfig, +} + +impl<'a> fmt::Display for BlueprintZoneConfigDisplay<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let z = self.zone; + write!( + f, + "{} {: bool { + // This code could be written in three ways: + // + // 1. match self { match filter { ... } } + // 2. match filter { match self { ... } } + // 3. match (self, filter) { ... } + // + // We choose 1 here because we expect many filters and just a few + // dispositions, and 1 is the easiest form to represent that. + match self { + Self::InService => match filter { + BlueprintZoneFilter::All => true, + BlueprintZoneFilter::SledAgentPut => true, + BlueprintZoneFilter::InternalDns => true, + BlueprintZoneFilter::VpcFirewall => true, + }, + Self::Quiesced => match filter { + BlueprintZoneFilter::All => true, + + // Quiesced zones should not be exposed in DNS. + BlueprintZoneFilter::InternalDns => false, + + // Quiesced zones are expected to be deployed by sled-agent. + BlueprintZoneFilter::SledAgentPut => true, + + // Quiesced zones should get firewall rules. + BlueprintZoneFilter::VpcFirewall => true, + }, + } + } + + /// Returns all zone dispositions that match the given filter. + pub fn all_matching( + filter: BlueprintZoneFilter, + ) -> impl Iterator { + BlueprintZoneDisposition::iter().filter(move |&d| d.matches(filter)) + } +} + +impl fmt::Display for BlueprintZoneDisposition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + // Neither `write!(f, "...")` nor `f.write_str("...")` obey fill + // and alignment (used above), but this does. + BlueprintZoneDisposition::InService => "in service".fmt(f), + BlueprintZoneDisposition::Quiesced => "quiesced".fmt(f), + } + } +} + +/// Filters that apply to blueprint zones. +/// +/// This logic lives here rather than within the individual components making +/// decisions, so that this is easier to read. +/// +/// The meaning of a particular filter should not be overloaded -- each time a +/// new use case wants to make a decision based on the zone disposition, a new +/// variant should be added to this enum. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum BlueprintZoneFilter { + // --- + // Prefer to keep this list in alphabetical order. + // --- + /// All zones. + All, + + /// Filter by zones that should be in internal DNS. + InternalDns, + + /// Filter by zones that we should tell sled-agent to deploy. + SledAgentPut, + + /// Filter by zones that should be sent VPC firewall rules. + VpcFirewall, +} + /// Describe high-level metadata about a blueprint // These fields are a subset of [`Blueprint`], and include only the data we can // quickly fetch from the main blueprint table (e.g., when listing all @@ -348,11 +598,9 @@ pub struct OmicronZonesDiff<'a> { before_label: String, // We store an owned copy of "before_zones" to make it easier to support // collections here, where we need to assemble this map ourselves. - before_zones: BTreeMap, - before_zones_in_service: &'a BTreeSet, + before_zones: BTreeMap, after_label: String, - after_zones: &'a BTreeMap, - after_zones_in_service: &'a BTreeSet, + after_zones: &'a BTreeMap, } /// Describes a sled that appeared on both sides of a diff (possibly changed) @@ -364,8 +612,8 @@ pub struct DiffSledCommon<'a> { pub generation_before: Generation, /// generation of the "zones" configuration on the right side pub generation_after: Generation, - zones_added: Vec<&'a OmicronZoneConfig>, - zones_removed: Vec<&'a OmicronZoneConfig>, + zones_added: Vec<&'a BlueprintZoneConfig>, + zones_removed: Vec<&'a BlueprintZoneConfig>, zones_common: Vec>, } @@ -373,14 +621,14 @@ impl<'a> DiffSledCommon<'a> { /// Iterate over zones added between the blueprints pub fn zones_added( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.zones_added.iter().copied() } /// Iterate over zones removed between the blueprints pub fn zones_removed( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.zones_removed.iter().copied() } @@ -395,8 +643,7 @@ impl<'a> DiffSledCommon<'a> { pub fn zones_changed( &self, ) -> impl Iterator> + '_ { - self.zones_in_common() - .filter(|z| z.changed_how != DiffZoneChangedHow::NoChanges) + self.zones_in_common().filter(|z| z.is_changed()) } } @@ -404,24 +651,34 @@ impl<'a> DiffSledCommon<'a> { #[derive(Debug, Copy, Clone)] pub struct DiffZoneCommon<'a> { /// full zone configuration before - pub zone_before: &'a OmicronZoneConfig, + pub zone_before: &'a BlueprintZoneConfig, /// full zone configuration after - pub zone_after: &'a OmicronZoneConfig, - /// summary of what changed, if anything - pub changed_how: DiffZoneChangedHow, + pub zone_after: &'a BlueprintZoneConfig, } -/// Describes how a zone changed across two blueprints, if at all -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum DiffZoneChangedHow { - /// the zone did not change between these two blueprints - NoChanges, - /// the zone details are the same, but it was brought into service - AddedToService, - /// the zone details are the same, but it was removed from service - RemovedFromService, - /// the zone's details (i.e., configuration) changed - DetailsChanged, +impl<'a> DiffZoneCommon<'a> { + /// Returns true if there are any differences between `zone_before` and + /// `zone_after`. + /// + /// This is equivalent to `config_changed() || disposition_changed()`. + #[inline] + pub fn is_changed(&self) -> bool { + // state is smaller and easier to compare than config. + self.disposition_changed() || self.config_changed() + } + + /// Returns true if the zone configuration (excluding the disposition) + /// changed. + #[inline] + pub fn config_changed(&self) -> bool { + self.zone_before.config != self.zone_after.config + } + + /// Returns true if the [`BlueprintZoneDisposition`] for the zone changed. + #[inline] + pub fn disposition_changed(&self) -> bool { + self.zone_before.disposition != self.zone_after.disposition + } } impl<'a> OmicronZonesDiff<'a> { @@ -436,7 +693,7 @@ impl<'a> OmicronZonesDiff<'a> { /// Iterate over sleds only present in the second blueprint of a diff pub fn sleds_added( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { let sled_ids = self .sleds_after() .difference(&self.sleds_before()) @@ -451,7 +708,7 @@ impl<'a> OmicronZonesDiff<'a> { /// Iterate over sleds only present in the first blueprint of a diff pub fn sleds_removed( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { let sled_ids = self .sleds_before() .difference(&self.sleds_after()) @@ -476,71 +733,33 @@ impl<'a> OmicronZonesDiff<'a> { let b2sledzones = self.after_zones.get(&sled_id).unwrap(); // Assemble separate summaries of the zones, indexed by zone id. - #[derive(Debug)] - struct ZoneInfo<'a> { - zone: &'a OmicronZoneConfig, - in_service: bool, - } - - let b1zones: BTreeMap = b1sledzones + let b1_zones: BTreeMap = b1sledzones .zones .iter() - .map(|zone| { - ( - zone.id, - ZoneInfo { - zone, - in_service: self - .before_zones_in_service - .contains(&zone.id), - }, - ) - }) - .collect(); - let mut b2zones: BTreeMap = b2sledzones - .zones - .iter() - .map(|zone| { - ( - zone.id, - ZoneInfo { - zone, - in_service: self - .after_zones_in_service - .contains(&zone.id), - }, - ) - }) + .map(|zone| (zone.config.id, zone)) .collect(); + let mut b2_zones: BTreeMap = + b2sledzones + .zones + .iter() + .map(|zone| (zone.config.id, zone)) + .collect(); let mut zones_removed = vec![]; - let mut zones_changed = vec![]; + let mut zones_common = vec![]; // Now go through each zone and compare them. - for (zone_id, b1z_info) in &b1zones { - if let Some(b2z_info) = b2zones.remove(zone_id) { - let changed_how = if b1z_info.zone != b2z_info.zone { - DiffZoneChangedHow::DetailsChanged - } else if b1z_info.in_service && !b2z_info.in_service { - DiffZoneChangedHow::RemovedFromService - } else if !b1z_info.in_service && b2z_info.in_service { - DiffZoneChangedHow::AddedToService - } else { - DiffZoneChangedHow::NoChanges - }; - zones_changed.push(DiffZoneCommon { - zone_before: b1z_info.zone, - zone_after: b2z_info.zone, - changed_how, - }); + for (zone_id, zone_before) in &b1_zones { + if let Some(zone_after) = b2_zones.remove(zone_id) { + zones_common + .push(DiffZoneCommon { zone_before, zone_after }); } else { - zones_removed.push(b1z_info.zone); + zones_removed.push(*zone_before); } } // Since we removed common zones above, anything else exists only in // b2 and was therefore added. - let zones_added = - b2zones.into_values().map(|b2z_info| b2z_info.zone).collect(); + let zones_added = b2_zones.into_values().collect(); ( sled_id, @@ -550,7 +769,7 @@ impl<'a> OmicronZonesDiff<'a> { generation_after: b2sledzones.generation, zones_added, zones_removed, - zones_common: zones_changed, + zones_common, }, ) }) @@ -573,7 +792,12 @@ impl<'a> OmicronZonesDiff<'a> { } } +/// Wrapper to allow a [`OmicronZonesDiff`] to be displayed in a unified +/// `diff(1)`-like format. +/// +/// Returned by [`OmicronZonesDiff::display()`]. #[derive(Clone, Debug)] +#[must_use = "this struct does nothing unless displayed"] pub struct OmicronZonesDiffDisplay<'diff, 'a> { diff: &'diff OmicronZonesDiff<'a>, // TODO: add colorization with a stylesheet @@ -590,7 +814,7 @@ impl<'diff, 'a> OmicronZonesDiffDisplay<'diff, 'a> { f: &mut fmt::Formatter<'_>, prefix: char, label: &str, - bbsledzones: &OmicronZonesConfig, + bbsledzones: &BlueprintZonesConfig, sled_id: Uuid, ) -> fmt::Result { writeln!(f, "{} sled {} ({})", prefix, sled_id, label)?; @@ -600,15 +824,7 @@ impl<'diff, 'a> OmicronZonesDiffDisplay<'diff, 'a> { prefix, bbsledzones.generation )?; for z in &bbsledzones.zones { - writeln!( - f, - "{} zone {} type {} underlay IP {} ({})", - prefix, - z.id, - z.zone_type.label(), - z.underlay_address, - label - )?; + writeln!(f, "{prefix} {} ({label})", z.display())?; } Ok(()) @@ -650,94 +866,43 @@ impl<'diff, 'a> fmt::Display for OmicronZonesDiffDisplay<'diff, 'a> { } for zone in sled_changes.zones_removed() { - writeln!( - f, - "- zone {} type {} (removed)", - zone.id, - zone.zone_type.label(), - )?; + writeln!(f, "- {} (removed)", zone.display())?; } for zone_changes in sled_changes.zones_in_common() { - let zone_id = zone_changes.zone_before.id; - let zone_type = zone_changes.zone_before.zone_type.label(); - let zone2_type = zone_changes.zone_after.zone_type.label(); - match zone_changes.changed_how { - DiffZoneChangedHow::DetailsChanged => { - writeln!( - f, - "- zone {} type {} underlay IP {} \ - (changed)", - zone_id, - zone_type, - zone_changes.zone_before.underlay_address, - )?; - writeln!( - f, - "+ zone {} type {} underlay IP {} \ - (changed)", - zone_id, - zone2_type, - zone_changes.zone_after.underlay_address, - )?; - } - DiffZoneChangedHow::RemovedFromService => { - writeln!( - f, - "- zone {} type {} underlay IP {} \ - (in service)", - zone_id, - zone_type, - zone_changes.zone_before.underlay_address, - )?; - writeln!( - f, - "+ zone {} type {} underlay IP {} \ - (removed from service)", - zone_id, - zone2_type, - zone_changes.zone_after.underlay_address, - )?; - } - DiffZoneChangedHow::AddedToService => { - writeln!( - f, - "- zone {} type {} underlay IP {} \ - (not in service)", - zone_id, - zone_type, - zone_changes.zone_before.underlay_address, - )?; - writeln!( - f, - "+ zone {} type {} underlay IP {} \ - (added to service)", - zone_id, - zone2_type, - zone_changes.zone_after.underlay_address, - )?; - } - DiffZoneChangedHow::NoChanges => { - writeln!( - f, - " zone {} type {} underlay IP {} \ - (unchanged)", - zone_id, - zone_type, - zone_changes.zone_before.underlay_address, - )?; - } + if zone_changes.config_changed() { + writeln!( + f, + "- {} (changed)", + zone_changes.zone_before.display(), + )?; + writeln!( + f, + "+ {} (changed)", + zone_changes.zone_after.display(), + )?; + } else if zone_changes.disposition_changed() { + writeln!( + f, + "- {} (disposition changed)", + zone_changes.zone_before.display(), + )?; + writeln!( + f, + "+ {} (disposition changed)", + zone_changes.zone_after.display(), + )?; + } else { + writeln!( + f, + " {} (unchanged)", + zone_changes.zone_before.display(), + )?; } } for zone in sled_changes.zones_added() { - writeln!( - f, - "+ zone {} type {} underlay IP {} (added)", - zone.id, - zone.zone_type.label(), - zone.underlay_address, - )?; + writeln!(f, "+ {} (added)", zone.display())?; } } diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 0beab820a1..cba8063b7e 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2589,6 +2589,13 @@ "description": "Describes a complete set of software and configuration for the system", "type": "object", "properties": { + "blueprint_zones": { + "description": "A map of sled id -> zones deployed on each sled, along with the [`BlueprintZoneDisposition`] for each zone.\n\nA sled is considered part of the control plane cluster iff it has an entry in this map.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintZonesConfig" + } + }, "comment": { "description": "human-readable string describing why this blueprint was created (for debugging)", "type": "string" @@ -2618,13 +2625,6 @@ } ] }, - "omicron_zones": { - "description": "mapping: sled id -> zones deployed on each sled A sled is considered part of the control plane cluster iff it has an entry in this map.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/OmicronZonesConfig" - } - }, "parent_blueprint_id": { "nullable": true, "description": "which blueprint this blueprint is based on", @@ -2635,26 +2635,16 @@ "description": "when this blueprint was generated (for debugging)", "type": "string", "format": "date-time" - }, - "zones_in_service": { - "description": "Omicron zones considered in-service (which generally means that they should appear in DNS)", - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true } }, "required": [ + "blueprint_zones", "comment", "creator", "external_dns_version", "id", "internal_dns_version", - "omicron_zones", - "time_created", - "zones_in_service" + "time_created" ] }, "BlueprintMetadata": { @@ -2774,6 +2764,76 @@ "target_id" ] }, + "BlueprintZoneConfig": { + "description": "Describes one Omicron-managed zone in a blueprint.\n\nThis is a wrapper around an [`OmicronZoneConfig`] that also includes a [`BlueprintZoneDisposition`].\n\nPart of [`BlueprintZonesConfig`].", + "type": "object", + "properties": { + "config": { + "description": "The underlying zone configuration.", + "allOf": [ + { + "$ref": "#/components/schemas/OmicronZoneConfig" + } + ] + }, + "disposition": { + "description": "The disposition (desired state) of this zone recorded in the blueprint.", + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintZoneDisposition" + } + ] + } + }, + "required": [ + "config", + "disposition" + ] + }, + "BlueprintZoneDisposition": { + "description": "The desired state of an Omicron-managed zone in a blueprint.\n\nPart of [`BlueprintZoneConfig`].", + "oneOf": [ + { + "description": "The zone is in-service.", + "type": "string", + "enum": [ + "in_service" + ] + }, + { + "description": "The zone is not in service.", + "type": "string", + "enum": [ + "quiesced" + ] + } + ] + }, + "BlueprintZonesConfig": { + "description": "Information about an Omicron zone as recorded in a blueprint.\n\nCurrently, this is similar to [`OmicronZonesConfig`], but also contains a per-zone [`BlueprintZoneDisposition`].\n\nPart of [`Blueprint`].", + "type": "object", + "properties": { + "generation": { + "description": "Generation number of this configuration.\n\nThis generation number is owned by the control plane. See [`OmicronZonesConfig::generation`] for more details.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "zones": { + "description": "The list of running zones.", + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + } + }, + "required": [ + "generation", + "zones" + ] + }, "ByteCount": { "description": "Byte count to express memory or storage capacity.", "type": "integer", @@ -5835,31 +5895,6 @@ } ] }, - "OmicronZonesConfig": { - "description": "Describes the set of Omicron-managed zones running on a sled\n\n
JSON schema\n\n```json { \"description\": \"Describes the set of Omicron-managed zones running on a sled\", \"type\": \"object\", \"required\": [ \"generation\", \"zones\" ], \"properties\": { \"generation\": { \"description\": \"generation number of this configuration\\n\\nThis generation number is owned by the control plane (i.e., RSS or Nexus, depending on whether RSS-to-Nexus handoff has happened). It should not be bumped within Sled Agent.\\n\\nSled Agent rejects attempts to set the configuration to a generation older than the one it's currently running.\", \"allOf\": [ { \"$ref\": \"#/components/schemas/Generation\" } ] }, \"zones\": { \"description\": \"list of running zones\", \"type\": \"array\", \"items\": { \"$ref\": \"#/components/schemas/OmicronZoneConfig\" } } } } ```
", - "type": "object", - "properties": { - "generation": { - "description": "generation number of this configuration\n\nThis generation number is owned by the control plane (i.e., RSS or Nexus, depending on whether RSS-to-Nexus handoff has happened). It should not be bumped within Sled Agent.\n\nSled Agent rejects attempts to set the configuration to a generation older than the one it's currently running.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "zones": { - "description": "list of running zones", - "type": "array", - "items": { - "$ref": "#/components/schemas/OmicronZoneConfig" - } - } - }, - "required": [ - "generation", - "zones" - ] - }, "OximeterInfo": { "description": "Message used to notify Nexus that this oximeter instance is up and running.", "type": "object", diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index 8c678daf30..587625fe7b 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -91,7 +91,10 @@ use internal_dns::ServiceName; use nexus_client::{ types as NexusTypes, Client as NexusClient, Error as NexusError, }; -use nexus_types::deployment::Blueprint; +use nexus_types::deployment::{ + Blueprint, BlueprintZoneConfig, BlueprintZoneDisposition, + BlueprintZonesConfig, +}; use omicron_common::address::get_sled_address; use omicron_common::api::external::Generation; use omicron_common::api::internal::shared::ExternalPortDiscovery; @@ -1158,35 +1161,37 @@ pub(crate) fn build_initial_blueprint_from_sled_configs( sled_configs: BTreeMap, internal_dns_version: Generation, ) -> Blueprint { - let mut omicron_zones = BTreeMap::new(); - let mut zones_in_service = BTreeSet::new(); + let mut blueprint_zones = BTreeMap::new(); for (sled_id, sled_config) in sled_configs { - for zone in &sled_config.zones { - zones_in_service.insert(zone.id); - } - let zones_config = sled_agent_client::types::OmicronZonesConfig::from( - OmicronZonesConfig { - // This is a bit of a hack. We only construct a blueprint after - // completing RSS, so we need to know the final generation value - // sent to all sleds. Arguably, we should record this in the - // serialized RSS plan; however, we have already deployed - // systems that did not. We know that every such system used - // `V5_EVERYTHING` as the final generation count, so we can just - // use that value here. If we ever change this, in particular in - // a way where newly-deployed systems will have a different - // value, we will need to revisit storing this in the serialized - // RSS plan. - generation: DeployStepVersion::V5_EVERYTHING, - zones: sled_config.zones, - }, - ); - omicron_zones.insert(sled_id, zones_config); + let zones_config = BlueprintZonesConfig { + // This is a bit of a hack. We only construct a blueprint after + // completing RSS, so we need to know the final generation value + // sent to all sleds. Arguably, we should record this in the + // serialized RSS plan; however, we have already deployed + // systems that did not. We know that every such system used + // `V5_EVERYTHING` as the final generation count, so we can just + // use that value here. If we ever change this, in particular in + // a way where newly-deployed systems will have a different + // value, we will need to revisit storing this in the serialized + // RSS plan. + generation: DeployStepVersion::V5_EVERYTHING, + zones: sled_config + .zones + .into_iter() + .map(|z| BlueprintZoneConfig { + config: z.into(), + // All initial zones are in-service. + disposition: BlueprintZoneDisposition::InService, + }) + .collect(), + }; + + blueprint_zones.insert(sled_id, zones_config); } Blueprint { id: Uuid::new_v4(), - omicron_zones, - zones_in_service, + blueprint_zones, parent_blueprint_id: None, internal_dns_version, // We don't configure external DNS during RSS, so set it to an initial