diff --git a/Cargo.lock b/Cargo.lock index 4ba1e607d32..968f629070f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6788,6 +6788,7 @@ dependencies = [ "dropshot", "expectorate", "gateway-client", + "gateway-types", "id-map", "iddqd", "illumos-utils", @@ -10815,6 +10816,7 @@ dependencies = [ "datatest-stable", "dropshot", "expectorate", + "gateway-types", "humantime", "iddqd", "indent_write", diff --git a/dev-tools/reconfigurator-cli/Cargo.toml b/dev-tools/reconfigurator-cli/Cargo.toml index d71793e732d..a75851890b7 100644 --- a/dev-tools/reconfigurator-cli/Cargo.toml +++ b/dev-tools/reconfigurator-cli/Cargo.toml @@ -23,6 +23,7 @@ iddqd.workspace = true indent_write.workspace = true internal-dns-types.workspace = true itertools.workspace = true +gateway-types.workspace = true newtype-uuid.workspace = true nexus-inventory.workspace = true nexus-reconfigurator-blippy.workspace = true diff --git a/dev-tools/reconfigurator-cli/src/lib.rs b/dev-tools/reconfigurator-cli/src/lib.rs index cee6f6ef4d6..3bd2909cc11 100644 --- a/dev-tools/reconfigurator-cli/src/lib.rs +++ b/dev-tools/reconfigurator-cli/src/lib.rs @@ -235,6 +235,7 @@ fn process_command( Commands::SledUpdateInstallDataset(args) => { cmd_sled_update_install_dataset(sim, args) } + Commands::SledUpdateRot(args) => cmd_sled_update_rot(sim, args), Commands::SledUpdateSp(args) => cmd_sled_update_sp(sim, args), Commands::SledUpdateRotBootloader(args) => { cmd_sled_update_rot_bootlaoder(sim, args) @@ -293,6 +294,8 @@ enum Commands { SledSet(SledSetArgs), /// update the install dataset on a sled, simulating a mupdate SledUpdateInstallDataset(SledUpdateInstallDatasetArgs), + /// simulate updating the sled's RoT versions + SledUpdateRot(SledUpdateRotArgs), /// simulate updating the sled's RoT Bootloader versions SledUpdateRotBootloader(SledUpdateRotBootloaderArgs), /// simulate updating the sled's SP versions @@ -528,6 +531,20 @@ struct SledUpdateSpArgs { inactive: Option, } +#[derive(Debug, Args)] +struct SledUpdateRotArgs { + /// id of the sled + sled_id: SledUuid, + + /// sets the version reported for the RoT slot a + #[clap(long, required_unless_present_any = &["slot_b"])] + slot_a: Option, + + /// sets the version reported for the RoT slot b + #[clap(long, required_unless_present_any = &["slot_a"])] + slot_b: Option, +} + #[derive(Debug, Args)] struct SledSetMupdateOverrideArgs { #[clap(flatten)] @@ -1380,6 +1397,15 @@ fn cmd_sled_show( let stage0_next_version = description.sled_stage0_next_version(sled_id)?; let sp_active_version = description.sled_sp_active_version(sled_id)?; let sp_inactive_version = description.sled_sp_inactive_version(sled_id)?; + let rot_active_slot = description.sled_rot_active_slot(sled_id)?; + let rot_slot_a_version = description.sled_rot_slot_a_version(sled_id)?; + let rot_slot_b_version = description.sled_rot_slot_b_version(sled_id)?; + let rot_persistent_boot_preference = + description.sled_rot_persistent_boot_preference(sled_id)?; + let rot_pending_persistent_boot_preference = + description.sled_rot_pending_persistent_boot_preference(sled_id)?; + let rot_transient_boot_preference = + description.sled_rot_transient_boot_preference(sled_id)?; let planning_input = description .to_planning_input_builder() .context("failed to generate planning_input builder")? @@ -1390,14 +1416,32 @@ fn cmd_sled_show( swriteln!(s, "sled {} ({}, {})", sled_id, sled.policy, sled.state); swriteln!(s, "serial {}", sled.baseboard_id.serial_number); swriteln!(s, "subnet {}", sled_resources.subnet.net()); - swriteln!(s, "RoT bootloader stage 0 version: {:?}", stage0_version); + swriteln!(s, "SP active version: {:?}", sp_active_version); + swriteln!(s, "SP inactive version: {:?}", sp_inactive_version); + swriteln!(s, "RoT bootloader stage 0 version: {:?}", stage0_version); swriteln!( s, "RoT bootloader stage 0 next version: {:?}", stage0_next_version ); - swriteln!(s, "SP active version: {:?}", sp_active_version); - swriteln!(s, "SP inactive version: {:?}", sp_inactive_version); + swriteln!(s, "RoT active slot: {}", rot_active_slot); + swriteln!(s, "RoT slot A version: {:?}", rot_slot_a_version); + swriteln!(s, "RoT slot B version: {:?}", rot_slot_b_version); + swriteln!( + s, + "RoT persistent boot preference: {}", + rot_persistent_boot_preference + ); + swriteln!( + s, + "RoT pending persistent boot preference: {:?}", + rot_pending_persistent_boot_preference + ); + swriteln!( + s, + "RoT transient boot preference: {:?}", + rot_transient_boot_preference + ); swriteln!(s, "zpools ({}):", sled_resources.zpools.len()); for (zpool, disk) in &sled_resources.zpools { swriteln!(s, " {:?}", zpool); @@ -1604,6 +1648,47 @@ fn cmd_sled_update_sp( Ok(Some(format!("set sled {} SP versions: {}", sled_id, labels.join(", ")))) } +fn cmd_sled_update_rot( + sim: &mut ReconfiguratorSim, + args: SledUpdateRotArgs, +) -> anyhow::Result> { + let mut labels = Vec::new(); + + if let Some(slot_a) = &args.slot_a { + labels.push(format!("slot a -> {}", slot_a)); + } + if let Some(slot_b) = &args.slot_b { + labels.push(format!("slot b -> {}", slot_b)); + } + + assert!( + !labels.is_empty(), + "clap configuration requires that at least one argument is specified" + ); + + let mut state = sim.current_state().to_mut(); + state.system_mut().description_mut().sled_update_rot_versions( + args.sled_id, + args.slot_a, + args.slot_b, + )?; + + sim.commit_and_bump( + format!( + "reconfigurator-cli sled-update-rot: {}: {}", + args.sled_id, + labels.join(", "), + ), + state, + ); + + Ok(Some(format!( + "set sled {} RoT settings: {}", + args.sled_id, + labels.join(", ") + ))) +} + fn cmd_inventory_list( sim: &mut ReconfiguratorSim, ) -> anyhow::Result> { diff --git a/dev-tools/reconfigurator-cli/tests/input/cmds-target-release.txt b/dev-tools/reconfigurator-cli/tests/input/cmds-target-release.txt index 068229b5cbf..ffc3e8dcfa0 100644 --- a/dev-tools/reconfigurator-cli/tests/input/cmds-target-release.txt +++ b/dev-tools/reconfigurator-cli/tests/input/cmds-target-release.txt @@ -74,4 +74,4 @@ blueprint-diff 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba 9034c710-3e57-45f3-99e5-4316 # We should continue walking through the update. We need to build out a # reconfigurator-cli subcommand to simulate updated zone image sources (just -# like we have sled-update-sp for simulated SP updates). +# like we have sled-update-sp for simulated SP updates). \ No newline at end of file diff --git a/dev-tools/reconfigurator-cli/tests/input/cmds.txt b/dev-tools/reconfigurator-cli/tests/input/cmds.txt index 713fc0dfafd..5eeb7d07793 100644 --- a/dev-tools/reconfigurator-cli/tests/input/cmds.txt +++ b/dev-tools/reconfigurator-cli/tests/input/cmds.txt @@ -13,6 +13,18 @@ sled-add 90c1102a-b9f5-4d88-92a2-60d54a2d98cc sled-add 04ef3330-c682-4a08-8def-fcc4bef31bcd --policy non-provisionable sled-list +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 1.0.0 +sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-b 2.0.0 +sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 3.0.0 +sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 4.0.0 --slot-b invalid +sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 4.0.0 --slot-b 5.0.0 +sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a + sled-update-sp dde1c0e2-b10d-4621-b420-f179f7a7a00a sled-update-sp dde1c0e2-b10d-4621-b420-f179f7a7a00a --active 1.0.0 sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index 3b9155a5a80..1360fae93e4 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -42,10 +42,16 @@ T ENA ID PARENT sled 2eb69596-f081-4e2d-9425-9994926e0832 (in service, active) serial serial1 subnet fd00:1122:3344:102::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("0.0.1") +SP active version: Some("0.0.1") SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("0.0.2") +RoT slot B version: None +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 055c4910-b641-46d9-b52d-313aae9d9cbf (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-055c4910-b641-46d9-b52d-313aae9d9cbf" }, disk_id: 6a0cb52f-5cc2-48a5-9f44-ac8dea3ac45b (physical_disk), policy: InService, state: Active } @@ -429,10 +435,16 @@ T ENA ID PARENT sled 89d02b1b-478c-401a-8e28-7a26f74fa41b (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("0.0.1") +SP active version: Some("0.0.1") SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("0.0.2") +RoT slot B version: None +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (4): 0477165a-a72e-4814-b8d6-74aa02cb2040 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-0477165a-a72e-4814-b8d6-74aa02cb2040" }, disk_id: 6a5a31ab-4edc-44e0-a7a1-4190bfe582f7 (physical_disk), policy: InService, state: Active } @@ -1037,9 +1049,10 @@ Sled serial0 A 0101010101010101010101010101010101010101010101010101010101010101 B 0202020202020202020202020202020202020202020202020202020202020202 cabooses: - SLOT BOARD NAME VERSION GIT_COMMIT SIGN - SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a - Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a + SLOT BOARD NAME VERSION GIT_COMMIT SIGN + SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a + RotSlotA SimRot SimRot 0.0.2 unknown n/a + Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a RoT pages: SLOT DATA_BASE64 RoT: active slot: slot A @@ -1060,9 +1073,10 @@ Sled serial1 A 0101010101010101010101010101010101010101010101010101010101010101 B 0202020202020202020202020202020202020202020202020202020202020202 cabooses: - SLOT BOARD NAME VERSION GIT_COMMIT SIGN - SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a - Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a + SLOT BOARD NAME VERSION GIT_COMMIT SIGN + SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a + RotSlotA SimRot SimRot 0.0.2 unknown n/a + Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a RoT pages: SLOT DATA_BASE64 RoT: active slot: slot A @@ -1083,9 +1097,10 @@ Sled serial2 A 0101010101010101010101010101010101010101010101010101010101010101 B 0202020202020202020202020202020202020202020202020202020202020202 cabooses: - SLOT BOARD NAME VERSION GIT_COMMIT SIGN - SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a - Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a + SLOT BOARD NAME VERSION GIT_COMMIT SIGN + SpSlot0 SimGimletSp SimGimletSp 0.0.1 unknown n/a + RotSlotA SimRot SimRot 0.0.2 unknown n/a + Stage0 SimRotStage0 SimRotStage0 0.0.1 unknown n/a RoT pages: SLOT DATA_BASE64 RoT: active slot: slot A diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout index 75cb1ff0311..8d19d5d30d5 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout @@ -19,8 +19,8 @@ INFO extracting uploaded archive to INFO created directory to store extracted artifacts, path: INFO added artifact, name: installinator_document, kind: installinator_document, version: 1.0.0, hash: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582, length: 367 INFO added artifact, name: SimGimletSp, kind: gimlet_sp, version: 1.0.0, hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, length: 747 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 INFO added artifact, name: SimRotStage0, kind: gimlet_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 INFO added artifact, name: fake-host, kind: host_phase_1, version: 1.0.0, hash: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89, length: 524288 INFO added artifact, name: fake-host, kind: host_phase_2, version: 1.0.0, hash: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008, length: 1048576 @@ -821,8 +821,8 @@ INFO extracting uploaded archive to INFO created directory to store extracted artifacts, path: INFO added artifact, name: installinator_document, kind: installinator_document, version: 1.0.0, hash: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582, length: 367 INFO added artifact, name: SimGimletSp, kind: gimlet_sp, version: 1.0.0, hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, length: 747 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 INFO added artifact, name: SimRotStage0, kind: gimlet_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 INFO added artifact, name: fake-host, kind: host_phase_1, version: 1.0.0, hash: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89, length: 524288 INFO added artifact, name: fake-host, kind: host_phase_2, version: 1.0.0, hash: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008, length: 1048576 @@ -2136,10 +2136,13 @@ INFO sufficient InternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial0, part_number: model0 WARN cannot configure SP update for board (no matching artifact), serial_number: serial0, part_number: model0 INFO skipping board for MGS-driven update, serial_number: serial0, part_number: model0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial1, part_number: model1 WARN cannot configure SP update for board (no matching artifact), serial_number: serial1, part_number: model1 INFO skipping board for MGS-driven update, serial_number: serial1, part_number: model1 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial2, part_number: model2 WARN cannot configure SP update for board (no matching artifact), serial_number: serial2, part_number: model2 INFO skipping board for MGS-driven update, serial_number: serial2, part_number: model2 INFO ran out of boards for MGS-driven update diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout index 4580f49d96b..5ed5376b83c 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -41,8 +41,8 @@ INFO extracting uploaded archive to INFO created directory to store extracted artifacts, path: INFO added artifact, name: installinator_document, kind: installinator_document, version: 1.0.0, hash: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582, length: 367 INFO added artifact, name: SimGimletSp, kind: gimlet_sp, version: 1.0.0, hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, length: 747 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 INFO added artifact, name: SimRotStage0, kind: gimlet_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 INFO added artifact, name: fake-host, kind: host_phase_1, version: 1.0.0, hash: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89, length: 524288 INFO added artifact, name: fake-host, kind: host_phase_2, version: 1.0.0, hash: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008, length: 1048576 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-stderr b/dev-tools/reconfigurator-cli/tests/output/cmds-stderr index de460c10b5f..3e5983b5a43 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-stderr +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-stderr @@ -1,3 +1,10 @@ +error: the following required arguments were not provided: + --slot-a + --slot-b + +Usage: sled-update-rot --slot-a --slot-b + +For more information, try '--help'. error: the following required arguments were not provided: --active --inactive diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-stdout index 3c70c247bdc..52b0cc3f939 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-stdout @@ -28,10 +28,16 @@ dde1c0e2-b10d-4621-b420-f179f7a7a00a serial0 10 fd00:1122:3344:101::/64 sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("0.0.1") +SP active version: Some("0.0.1") SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("0.0.2") +RoT slot B version: None +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -68,6 +74,209 @@ ID SERIAL NZPOOLS SUBNET dde1c0e2-b10d-4621-b420-f179f7a7a00a serial0 10 fd00:1122:3344:101::/64 +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a + +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 1.0.0 +set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT settings: slot a -> 1.0.0 + +> sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) +serial serial0 +subnet fd00:1122:3344:101::/64 +SP active version: Some("0.0.1") +SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("1.0.0") +RoT slot B version: None +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None +zpools (10): + 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } + 677dd944-6761-4a89-8606-4d7fe485a63c (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-677dd944-6761-4a89-8606-4d7fe485a63c" }, disk_id: fcf54220-3ff4-463e-b4a2-58447f51b68c (physical_disk), policy: InService, state: Active } + 70e81eac-6ed4-4c2d-b16a-fabe2aec56fc (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-70e81eac-6ed4-4c2d-b16a-fabe2aec56fc" }, disk_id: 42643377-e4d1-41a0-ac32-38d6e56cb22a (physical_disk), policy: InService, state: Active } + 7b26c659-bf8f-4c60-ab75-fd2dd8ef5866 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-7b26c659-bf8f-4c60-ab75-fd2dd8ef5866" }, disk_id: 5b2df08c-ea6a-4771-8363-80031249c97b (physical_disk), policy: InService, state: Active } + 8e0008d0-9313-4caf-bc20-305ccce29846 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-8e0008d0-9313-4caf-bc20-305ccce29846" }, disk_id: 0f5e7fc1-8d87-45f8-a00e-f5127b7a3905 (physical_disk), policy: InService, state: Active } + 929e328a-dd25-447d-9af7-6e2216adf4aa (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-929e328a-dd25-447d-9af7-6e2216adf4aa" }, disk_id: f62e3201-e89b-4667-9707-e49f86b9df07 (physical_disk), policy: InService, state: Active } + 9a25ff89-5446-4233-bf58-20a24c80aa58 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-9a25ff89-5446-4233-bf58-20a24c80aa58" }, disk_id: 49b87668-e08b-4939-91f7-a82612e2ebff (physical_disk), policy: InService, state: Active } + a9cd1fe6-f1ba-4227-bff7-978992c3d6ad (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-a9cd1fe6-f1ba-4227-bff7-978992c3d6ad" }, disk_id: dcde393a-3ac6-4e98-8833-012787e73e15 (physical_disk), policy: InService, state: Active } + b3ede1e1-3264-4b21-8c7d-9ea5d3715210 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-b3ede1e1-3264-4b21-8c7d-9ea5d3715210" }, disk_id: 4863117c-b77d-4dbc-996d-d18ddf0f5ff7 (physical_disk), policy: InService, state: Active } + e0f5c287-3296-4a35-b597-7452283ff329 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-e0f5c287-3296-4a35-b597-7452283ff329" }, disk_id: 0f13d3dd-1830-4a06-b664-e6f0473ba704 (physical_disk), policy: InService, state: Active } + + +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-b 2.0.0 +set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT settings: slot b -> 2.0.0 + +> sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) +serial serial0 +subnet fd00:1122:3344:101::/64 +SP active version: Some("0.0.1") +SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("1.0.0") +RoT slot B version: Some("2.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None +zpools (10): + 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } + 677dd944-6761-4a89-8606-4d7fe485a63c (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-677dd944-6761-4a89-8606-4d7fe485a63c" }, disk_id: fcf54220-3ff4-463e-b4a2-58447f51b68c (physical_disk), policy: InService, state: Active } + 70e81eac-6ed4-4c2d-b16a-fabe2aec56fc (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-70e81eac-6ed4-4c2d-b16a-fabe2aec56fc" }, disk_id: 42643377-e4d1-41a0-ac32-38d6e56cb22a (physical_disk), policy: InService, state: Active } + 7b26c659-bf8f-4c60-ab75-fd2dd8ef5866 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-7b26c659-bf8f-4c60-ab75-fd2dd8ef5866" }, disk_id: 5b2df08c-ea6a-4771-8363-80031249c97b (physical_disk), policy: InService, state: Active } + 8e0008d0-9313-4caf-bc20-305ccce29846 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-8e0008d0-9313-4caf-bc20-305ccce29846" }, disk_id: 0f5e7fc1-8d87-45f8-a00e-f5127b7a3905 (physical_disk), policy: InService, state: Active } + 929e328a-dd25-447d-9af7-6e2216adf4aa (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-929e328a-dd25-447d-9af7-6e2216adf4aa" }, disk_id: f62e3201-e89b-4667-9707-e49f86b9df07 (physical_disk), policy: InService, state: Active } + 9a25ff89-5446-4233-bf58-20a24c80aa58 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-9a25ff89-5446-4233-bf58-20a24c80aa58" }, disk_id: 49b87668-e08b-4939-91f7-a82612e2ebff (physical_disk), policy: InService, state: Active } + a9cd1fe6-f1ba-4227-bff7-978992c3d6ad (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-a9cd1fe6-f1ba-4227-bff7-978992c3d6ad" }, disk_id: dcde393a-3ac6-4e98-8833-012787e73e15 (physical_disk), policy: InService, state: Active } + b3ede1e1-3264-4b21-8c7d-9ea5d3715210 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-b3ede1e1-3264-4b21-8c7d-9ea5d3715210" }, disk_id: 4863117c-b77d-4dbc-996d-d18ddf0f5ff7 (physical_disk), policy: InService, state: Active } + e0f5c287-3296-4a35-b597-7452283ff329 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-e0f5c287-3296-4a35-b597-7452283ff329" }, disk_id: 0f13d3dd-1830-4a06-b664-e6f0473ba704 (physical_disk), policy: InService, state: Active } + + +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 3.0.0 +set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT settings: slot a -> 3.0.0 + +> sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) +serial serial0 +subnet fd00:1122:3344:101::/64 +SP active version: Some("0.0.1") +SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("3.0.0") +RoT slot B version: Some("2.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None +zpools (10): + 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } + 677dd944-6761-4a89-8606-4d7fe485a63c (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-677dd944-6761-4a89-8606-4d7fe485a63c" }, disk_id: fcf54220-3ff4-463e-b4a2-58447f51b68c (physical_disk), policy: InService, state: Active } + 70e81eac-6ed4-4c2d-b16a-fabe2aec56fc (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-70e81eac-6ed4-4c2d-b16a-fabe2aec56fc" }, disk_id: 42643377-e4d1-41a0-ac32-38d6e56cb22a (physical_disk), policy: InService, state: Active } + 7b26c659-bf8f-4c60-ab75-fd2dd8ef5866 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-7b26c659-bf8f-4c60-ab75-fd2dd8ef5866" }, disk_id: 5b2df08c-ea6a-4771-8363-80031249c97b (physical_disk), policy: InService, state: Active } + 8e0008d0-9313-4caf-bc20-305ccce29846 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-8e0008d0-9313-4caf-bc20-305ccce29846" }, disk_id: 0f5e7fc1-8d87-45f8-a00e-f5127b7a3905 (physical_disk), policy: InService, state: Active } + 929e328a-dd25-447d-9af7-6e2216adf4aa (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-929e328a-dd25-447d-9af7-6e2216adf4aa" }, disk_id: f62e3201-e89b-4667-9707-e49f86b9df07 (physical_disk), policy: InService, state: Active } + 9a25ff89-5446-4233-bf58-20a24c80aa58 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-9a25ff89-5446-4233-bf58-20a24c80aa58" }, disk_id: 49b87668-e08b-4939-91f7-a82612e2ebff (physical_disk), policy: InService, state: Active } + a9cd1fe6-f1ba-4227-bff7-978992c3d6ad (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-a9cd1fe6-f1ba-4227-bff7-978992c3d6ad" }, disk_id: dcde393a-3ac6-4e98-8833-012787e73e15 (physical_disk), policy: InService, state: Active } + b3ede1e1-3264-4b21-8c7d-9ea5d3715210 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-b3ede1e1-3264-4b21-8c7d-9ea5d3715210" }, disk_id: 4863117c-b77d-4dbc-996d-d18ddf0f5ff7 (physical_disk), policy: InService, state: Active } + e0f5c287-3296-4a35-b597-7452283ff329 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-e0f5c287-3296-4a35-b597-7452283ff329" }, disk_id: 0f13d3dd-1830-4a06-b664-e6f0473ba704 (physical_disk), policy: InService, state: Active } + + +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 4.0.0 --slot-b invalid +set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT settings: slot a -> 4.0.0, slot b -> invalid + +> sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) +serial serial0 +subnet fd00:1122:3344:101::/64 +SP active version: Some("0.0.1") +SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: None +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None +zpools (10): + 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } + 677dd944-6761-4a89-8606-4d7fe485a63c (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-677dd944-6761-4a89-8606-4d7fe485a63c" }, disk_id: fcf54220-3ff4-463e-b4a2-58447f51b68c (physical_disk), policy: InService, state: Active } + 70e81eac-6ed4-4c2d-b16a-fabe2aec56fc (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-70e81eac-6ed4-4c2d-b16a-fabe2aec56fc" }, disk_id: 42643377-e4d1-41a0-ac32-38d6e56cb22a (physical_disk), policy: InService, state: Active } + 7b26c659-bf8f-4c60-ab75-fd2dd8ef5866 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-7b26c659-bf8f-4c60-ab75-fd2dd8ef5866" }, disk_id: 5b2df08c-ea6a-4771-8363-80031249c97b (physical_disk), policy: InService, state: Active } + 8e0008d0-9313-4caf-bc20-305ccce29846 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-8e0008d0-9313-4caf-bc20-305ccce29846" }, disk_id: 0f5e7fc1-8d87-45f8-a00e-f5127b7a3905 (physical_disk), policy: InService, state: Active } + 929e328a-dd25-447d-9af7-6e2216adf4aa (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-929e328a-dd25-447d-9af7-6e2216adf4aa" }, disk_id: f62e3201-e89b-4667-9707-e49f86b9df07 (physical_disk), policy: InService, state: Active } + 9a25ff89-5446-4233-bf58-20a24c80aa58 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-9a25ff89-5446-4233-bf58-20a24c80aa58" }, disk_id: 49b87668-e08b-4939-91f7-a82612e2ebff (physical_disk), policy: InService, state: Active } + a9cd1fe6-f1ba-4227-bff7-978992c3d6ad (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-a9cd1fe6-f1ba-4227-bff7-978992c3d6ad" }, disk_id: dcde393a-3ac6-4e98-8833-012787e73e15 (physical_disk), policy: InService, state: Active } + b3ede1e1-3264-4b21-8c7d-9ea5d3715210 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-b3ede1e1-3264-4b21-8c7d-9ea5d3715210" }, disk_id: 4863117c-b77d-4dbc-996d-d18ddf0f5ff7 (physical_disk), policy: InService, state: Active } + e0f5c287-3296-4a35-b597-7452283ff329 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-e0f5c287-3296-4a35-b597-7452283ff329" }, disk_id: 0f13d3dd-1830-4a06-b664-e6f0473ba704 (physical_disk), policy: InService, state: Active } + + +> sled-update-rot dde1c0e2-b10d-4621-b420-f179f7a7a00a --slot-a 4.0.0 --slot-b 5.0.0 +set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT settings: slot a -> 4.0.0, slot b -> 5.0.0 + +> sled-show dde1c0e2-b10d-4621-b420-f179f7a7a00a +sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) +serial serial0 +subnet fd00:1122:3344:101::/64 +SP active version: Some("0.0.1") +SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None +zpools (10): + 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } + 677dd944-6761-4a89-8606-4d7fe485a63c (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-677dd944-6761-4a89-8606-4d7fe485a63c" }, disk_id: fcf54220-3ff4-463e-b4a2-58447f51b68c (physical_disk), policy: InService, state: Active } + 70e81eac-6ed4-4c2d-b16a-fabe2aec56fc (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-70e81eac-6ed4-4c2d-b16a-fabe2aec56fc" }, disk_id: 42643377-e4d1-41a0-ac32-38d6e56cb22a (physical_disk), policy: InService, state: Active } + 7b26c659-bf8f-4c60-ab75-fd2dd8ef5866 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-7b26c659-bf8f-4c60-ab75-fd2dd8ef5866" }, disk_id: 5b2df08c-ea6a-4771-8363-80031249c97b (physical_disk), policy: InService, state: Active } + 8e0008d0-9313-4caf-bc20-305ccce29846 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-8e0008d0-9313-4caf-bc20-305ccce29846" }, disk_id: 0f5e7fc1-8d87-45f8-a00e-f5127b7a3905 (physical_disk), policy: InService, state: Active } + 929e328a-dd25-447d-9af7-6e2216adf4aa (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-929e328a-dd25-447d-9af7-6e2216adf4aa" }, disk_id: f62e3201-e89b-4667-9707-e49f86b9df07 (physical_disk), policy: InService, state: Active } + 9a25ff89-5446-4233-bf58-20a24c80aa58 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-9a25ff89-5446-4233-bf58-20a24c80aa58" }, disk_id: 49b87668-e08b-4939-91f7-a82612e2ebff (physical_disk), policy: InService, state: Active } + a9cd1fe6-f1ba-4227-bff7-978992c3d6ad (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-a9cd1fe6-f1ba-4227-bff7-978992c3d6ad" }, disk_id: dcde393a-3ac6-4e98-8833-012787e73e15 (physical_disk), policy: InService, state: Active } + b3ede1e1-3264-4b21-8c7d-9ea5d3715210 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-b3ede1e1-3264-4b21-8c7d-9ea5d3715210" }, disk_id: 4863117c-b77d-4dbc-996d-d18ddf0f5ff7 (physical_disk), policy: InService, state: Active } + e0f5c287-3296-4a35-b597-7452283ff329 (zpool) + SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-e0f5c287-3296-4a35-b597-7452283ff329" }, disk_id: 0f13d3dd-1830-4a06-b664-e6f0473ba704 (physical_disk), policy: InService, state: Active } + + + > sled-update-sp dde1c0e2-b10d-4621-b420-f179f7a7a00a > sled-update-sp dde1c0e2-b10d-4621-b420-f179f7a7a00a --active 1.0.0 @@ -77,10 +286,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a SP versions: active -> 1.0.0 sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("1.0.0") +SP active version: Some("1.0.0") SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -111,10 +326,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a SP versions: inactive -> 2.0.0 sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("1.0.0") +SP active version: Some("1.0.0") SP inactive version: Some("2.0.0") +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -145,10 +366,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a SP versions: active -> 3.0.0 sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("3.0.0") +SP active version: Some("3.0.0") SP inactive version: Some("2.0.0") +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -179,10 +406,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a SP versions: active -> 4.0.0, inac sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: None +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -213,10 +446,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a SP versions: active -> 4.0.0, inac sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("0.0.1") -RoT bootloader stage 0 next version: None -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("0.0.1") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -250,10 +489,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT bootloader versions: stage0 -> sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("1.0.0") -RoT bootloader stage 0 next version: None -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("1.0.0") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -284,10 +529,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT bootloader versions: stage0_ne sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("1.0.0") -RoT bootloader stage 0 next version: Some("2.0.0") -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("1.0.0") +RoT bootloader stage 0 next version: Some("2.0.0") +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -318,10 +569,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT bootloader versions: stage0 -> sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("3.0.0") -RoT bootloader stage 0 next version: Some("2.0.0") -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("3.0.0") +RoT bootloader stage 0 next version: Some("2.0.0") +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -352,10 +609,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT bootloader versions: stage0 -> sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("4.0.0") -RoT bootloader stage 0 next version: None -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("4.0.0") +RoT bootloader stage 0 next version: None +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -386,10 +649,16 @@ set sled dde1c0e2-b10d-4621-b420-f179f7a7a00a RoT bootloader versions: stage0 -> sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("4.0.0") -RoT bootloader stage 0 next version: Some("5.0.0") -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("4.0.0") +RoT bootloader stage 0 next version: Some("5.0.0") +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } @@ -452,10 +721,16 @@ result: sled dde1c0e2-b10d-4621-b420-f179f7a7a00a (in service, active) serial serial0 subnet fd00:1122:3344:101::/64 -RoT bootloader stage 0 version: Some("4.0.0") -RoT bootloader stage 0 next version: Some("5.0.0") -SP active version: Some("4.0.0") +SP active version: Some("4.0.0") SP inactive version: Some("5.0.0") +RoT bootloader stage 0 version: Some("4.0.0") +RoT bootloader stage 0 next version: Some("5.0.0") +RoT active slot: A +RoT slot A version: Some("4.0.0") +RoT slot B version: Some("5.0.0") +RoT persistent boot preference: A +RoT pending persistent boot preference: None +RoT transient boot preference: None zpools (10): 674c6591-11be-44f2-9df1-db3bb663ec01 (zpool) SledDisk { disk_identity: DiskIdentity { vendor: "fake-vendor", model: "fake-model", serial: "serial-674c6591-11be-44f2-9df1-db3bb663ec01" }, disk_id: a52a7c57-7fd0-4139-8293-bda299523c53 (physical_disk), policy: InService, state: Active } diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout index e6bc9aa503d..8f9b044396b 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -34,8 +34,8 @@ INFO extracting uploaded archive to INFO created directory to store extracted artifacts, path: INFO added artifact, name: installinator_document, kind: installinator_document, version: 1.0.0, hash: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582, length: 367 INFO added artifact, name: SimGimletSp, kind: gimlet_sp, version: 1.0.0, hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, length: 747 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 -INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: SimRot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 INFO added artifact, name: SimRotStage0, kind: gimlet_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 INFO added artifact, name: fake-host, kind: host_phase_1, version: 1.0.0, hash: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89, length: 524288 INFO added artifact, name: fake-host, kind: host_phase_2, version: 1.0.0, hash: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008, length: 1048576 @@ -73,8 +73,8 @@ target number of Nexus instances: default target release (generation 2): 1.0.0 (system-update-v1.0.0.zip) artifact: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582 installinator_document (installinator_document version 1.0.0) artifact: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670 gimlet_sp (SimGimletSp version 1.0.0) - artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_a (fake-gimlet-rot version 1.0.0) - artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_b (fake-gimlet-rot version 1.0.0) + artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_a (SimRot version 1.0.0) + artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_b (SimRot version 1.0.0) artifact: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236 gimlet_rot_bootloader (SimRotStage0 version 1.0.0) artifact: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89 host_phase_1 (fake-host version 1.0.0) artifact: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008 host_phase_2 (fake-host version 1.0.0) @@ -151,8 +151,8 @@ target number of Nexus instances: default target release (generation 2): 1.0.0 (system-update-v1.0.0.zip) artifact: c6ae866031d1183094c92cde9d9d1fd5f18356abc81a842ce31471b473fd5582 installinator_document (installinator_document version 1.0.0) artifact: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670 gimlet_sp (SimGimletSp version 1.0.0) - artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_a (fake-gimlet-rot version 1.0.0) - artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_b (fake-gimlet-rot version 1.0.0) + artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_a (SimRot version 1.0.0) + artifact: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a gimlet_rot_image_b (SimRot version 1.0.0) artifact: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236 gimlet_rot_bootloader (SimRotStage0 version 1.0.0) artifact: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89 host_phase_1 (fake-host version 1.0.0) artifact: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008 host_phase_2 (fake-host version 1.0.0) @@ -216,6 +216,7 @@ INFO sufficient InternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial0, part_number: model0 INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 0, sp_type: Sled, serial_number: serial0, part_number: model0 INFO reached maximum number of pending MGS-driven updates, max: 1 INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify @@ -588,7 +589,9 @@ INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 INFO MGS-driven update completed (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 0, sp_type: Sled, serial_number: serial0, part_number: model0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial0, part_number: model0 INFO skipping board for MGS-driven update, serial_number: serial0, part_number: model0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial1, part_number: model1 INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending MGS-driven updates, max: 1 INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify @@ -788,6 +791,7 @@ INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 INFO MGS-driven update impossible (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial1, part_number: model1 INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending MGS-driven updates, max: 1 INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify @@ -982,8 +986,11 @@ INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 INFO MGS-driven update completed (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial1, part_number: model1 INFO skipping board for MGS-driven update, serial_number: serial1, part_number: model1 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial0, part_number: model0 INFO skipping board for MGS-driven update, serial_number: serial0, part_number: model0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial2, part_number: model2 INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 2, sp_type: Sled, serial_number: serial2, part_number: model2 INFO ran out of boards for MGS-driven update INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify @@ -1178,8 +1185,11 @@ INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 INFO MGS-driven update completed (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 2, sp_type: Sled, serial_number: serial2, part_number: model2 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial2, part_number: model2 INFO skipping board for MGS-driven update, serial_number: serial2, part_number: model2 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial0, part_number: model0 INFO skipping board for MGS-driven update, serial_number: serial0, part_number: model0 +WARN cannot configure RoT update for board (missing sign in caboose from inventory), serial_number: serial1, part_number: model1 INFO skipping board for MGS-driven update, serial_number: serial1, part_number: model1 INFO ran out of boards for MGS-driven update INFO updating zone image source in-place, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone_id: 353b3b65-20f7-48c3-88f7-495bd5d31545, kind: Clickhouse, image_source: artifact: version 1.0.0 diff --git a/gateway-types/src/rot.rs b/gateway-types/src/rot.rs index 44dd8b8d8d6..77b200b29ca 100644 --- a/gateway-types/src/rot.rs +++ b/gateway-types/src/rot.rs @@ -5,6 +5,7 @@ use daft::Diffable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::fmt::Display; use std::str::FromStr; #[derive( @@ -200,6 +201,16 @@ impl RotSlot { } } +impl Display for RotSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + RotSlot::A => "A", + RotSlot::B => "B", + }; + write!(f, "{s}") + } +} + impl FromStr for RotSlot { type Err = String; diff --git a/nexus/reconfigurator/planning/Cargo.toml b/nexus/reconfigurator/planning/Cargo.toml index ce4a2db858d..417ca9678ae 100644 --- a/nexus/reconfigurator/planning/Cargo.toml +++ b/nexus/reconfigurator/planning/Cargo.toml @@ -13,6 +13,7 @@ chrono.workspace = true debug-ignore.workspace = true daft.workspace = true gateway-client.workspace = true +gateway-types.workspace = true iddqd.workspace = true id-map.workspace = true illumos-utils.workspace = true diff --git a/nexus/reconfigurator/planning/src/mgs_updates/mod.rs b/nexus/reconfigurator/planning/src/mgs_updates/mod.rs index 1ae6f9aca54..885e302dd00 100644 --- a/nexus/reconfigurator/planning/src/mgs_updates/mod.rs +++ b/nexus/reconfigurator/planning/src/mgs_updates/mod.rs @@ -4,6 +4,14 @@ //! Facilities for making choices about MGS-managed updates +mod rot; + +use crate::mgs_updates::rot::RotUpdateState; +use crate::mgs_updates::rot::mgs_update_status_rot; +use crate::mgs_updates::rot::try_make_update_rot; + +use gateway_types::rot::RotSlot; +use nexus_types::deployment::ExpectedActiveRotSlot; use nexus_types::deployment::ExpectedVersion; use nexus_types::deployment::PendingMgsUpdate; use nexus_types::deployment::PendingMgsUpdateDetails; @@ -19,6 +27,7 @@ use std::collections::BTreeSet; use std::sync::Arc; use thiserror::Error; use tufaceous_artifact::ArtifactVersion; +use tufaceous_artifact::ArtifactVersionError; use tufaceous_artifact::KnownArtifactKind; /// How to handle an MGS-driven update that has become impossible due to @@ -200,8 +209,12 @@ enum MgsUpdateStatusError { MissingSpInfo, #[error("no caboose found for active slot in inventory")] MissingActiveCaboose, + #[error("no RoT state found in inventory")] + MissingRotState, #[error("not yet implemented")] NotYetImplemented, + #[error("unable to parse input into ArtifactVersion: {0:?}")] + FailedArtifactVersionParse(ArtifactVersionError), } /// Determine the status of a single MGS-driven update based on what's in @@ -242,8 +255,71 @@ fn mgs_update_status( found_inactive_version, )) } - PendingMgsUpdateDetails::Rot { .. } - | PendingMgsUpdateDetails::RotBootloader { .. } => { + PendingMgsUpdateDetails::Rot { + expected_active_slot, + expected_inactive_version, + expected_persistent_boot_preference, + expected_pending_persistent_boot_preference, + expected_transient_boot_preference, + } => { + let active_caboose_which = match &expected_active_slot.slot { + RotSlot::A => CabooseWhich::RotSlotA, + RotSlot::B => CabooseWhich::RotSlotB, + }; + + let Some(active_caboose) = + inventory.caboose_for(active_caboose_which, baseboard_id) + else { + return Err(MgsUpdateStatusError::MissingActiveCaboose); + }; + + let found_inactive_version = inventory + .caboose_for(active_caboose_which.toggled_slot(), baseboard_id) + .map(|c| c.caboose.version.as_ref()); + + let rot_state = inventory + .rots + .get(baseboard_id) + .ok_or(MgsUpdateStatusError::MissingRotState)?; + + let found_active_version = + ArtifactVersion::new(active_caboose.caboose.version.clone()) + .map_err(|e| { + MgsUpdateStatusError::FailedArtifactVersionParse(e) + })?; + + let found_active_slot = ExpectedActiveRotSlot { + slot: rot_state.active_slot, + version: found_active_version, + }; + + let expected = RotUpdateState { + active_slot: expected_active_slot.clone(), + persistent_boot_preference: + *expected_persistent_boot_preference, + pending_persistent_boot_preference: + *expected_pending_persistent_boot_preference, + transient_boot_preference: *expected_transient_boot_preference, + }; + + let found = RotUpdateState { + active_slot: found_active_slot, + persistent_boot_preference: rot_state + .persistent_boot_preference, + pending_persistent_boot_preference: rot_state + .pending_persistent_boot_preference, + transient_boot_preference: rot_state.transient_boot_preference, + }; + + Ok(mgs_update_status_rot( + desired_version, + expected, + found, + expected_inactive_version, + found_inactive_version, + )) + } + PendingMgsUpdateDetails::RotBootloader { .. } => { return Err(MgsUpdateStatusError::NotYetImplemented); } }; @@ -315,7 +391,16 @@ fn mgs_update_status_sp( // still matches what we saw when we configured this update. If not, then // this update cannot proceed as currently configured. It will fail its // precondition check. - // + mgs_update_status_inactive_versions( + found_inactive_version, + expected_inactive_version, + ) +} + +fn mgs_update_status_inactive_versions( + found_inactive_version: Option<&str>, + expected_inactive_version: &ExpectedVersion, +) -> MgsUpdateStatus { // This logic is more complex than for the active slot because unlike the // active slot, it's possible for both the found contents and the expected // contents to be missing and that's not necessarily an error. @@ -369,11 +454,14 @@ fn try_make_update( inventory: &Collection, current_artifacts: &TufRepoDescription, ) -> Option { - // TODO When we add support for planning RoT, RoT bootloader, and host OS + // TODO When we add support for planning RoT bootloader, and host OS // updates, we'll try these in a hardcoded priority order until any of them // returns `Some`. The order is described in RFD 565 section "Update - // Sequence". For now, we only plan SP updates. - try_make_update_sp(log, baseboard_id, inventory, current_artifacts) + // Sequence". For now, we only plan SP and RoT updates. + try_make_update_rot(log, baseboard_id, inventory, current_artifacts) + .or_else(|| { + try_make_update_sp(log, baseboard_id, inventory, current_artifacts) + }) } /// Determine if the given baseboard needs an SP update and, if so, returns it. @@ -540,6 +628,7 @@ mod test { use std::collections::BTreeSet; use strum::IntoEnumIterator; use tufaceous_artifact::ArtifactHash; + use tufaceous_artifact::ArtifactKind; use tufaceous_artifact::ArtifactVersion; use tufaceous_artifact::KnownArtifactKind; @@ -565,6 +654,18 @@ mod test { const ARTIFACT_HASH_SP_PSC_B: ArtifactHash = ArtifactHash([9; 32]); /// Hash of fake artifact for fake psc-c SP const ARTIFACT_HASH_SP_PSC_C: ArtifactHash = ArtifactHash([10; 32]); + /// Hash of fake artifact for fake gimlet RoT slot A + const ARTIFACT_HASH_ROT_GIMLET_A: ArtifactHash = ArtifactHash([13; 32]); + /// Hash of fake artifact for fake gimlet RoT slot B + const ARTIFACT_HASH_ROT_GIMLET_B: ArtifactHash = ArtifactHash([14; 32]); + /// Hash of fake artifact for fake psc RoT slot A + const ARTIFACT_HASH_ROT_PSC_A: ArtifactHash = ArtifactHash([17; 32]); + /// Hash of fake artifact for fake psc RoT slot B + const ARTIFACT_HASH_ROT_PSC_B: ArtifactHash = ArtifactHash([18; 32]); + /// Hash of fake artifact for fake switch RoT slot A + const ARTIFACT_HASH_ROT_SWITCH_A: ArtifactHash = ArtifactHash([21; 32]); + /// Hash of fake artifact for fake switch RoT slot B + const ARTIFACT_HASH_ROT_SWITCH_B: ArtifactHash = ArtifactHash([22; 32]); // unused artifact hashes @@ -572,6 +673,23 @@ mod test { const ARTIFACT_HASH_NEXUS: ArtifactHash = ArtifactHash([34; 32]); const ARTIFACT_HASH_HOST_OS: ArtifactHash = ArtifactHash([35; 32]); + /// Hash of a fake RoT signing keys + const ROT_SIGN_GIMLET: &str = + "1111111111111111111111111111111111111111111111111111111111111111"; + const ROT_SIGN_PSC: &str = + "2222222222222222222222222222222222222222222222222222222222222222"; + const ROT_SIGN_SWITCH: &str = + "3333333333333333333333333333333333333333333333333333333333333333"; + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + enum MgsUpdateComponent { + Sp, + Rot, + RotBootloader, + #[allow(unused)] + HostOs, + } + fn test_artifact_for_board(board: &str) -> ArtifactHash { match board { "gimlet-d" => ARTIFACT_HASH_SP_GIMLET_D, @@ -584,7 +702,27 @@ mod test { } } - /// Describes the SPs in the environment used in these tests + fn test_artifact_for_artifact_kind(kind: ArtifactKind) -> ArtifactHash { + let artifact_hash = if kind == ArtifactKind::GIMLET_ROT_IMAGE_A { + ARTIFACT_HASH_ROT_GIMLET_A + } else if kind == ArtifactKind::GIMLET_ROT_IMAGE_B { + ARTIFACT_HASH_ROT_GIMLET_B + } else if kind == ArtifactKind::PSC_ROT_IMAGE_A { + ARTIFACT_HASH_ROT_PSC_A + } else if kind == ArtifactKind::PSC_ROT_IMAGE_B { + ARTIFACT_HASH_ROT_PSC_B + } else if kind == ArtifactKind::SWITCH_ROT_IMAGE_A { + ARTIFACT_HASH_ROT_SWITCH_A + } else if kind == ArtifactKind::SWITCH_ROT_IMAGE_B { + ARTIFACT_HASH_ROT_SWITCH_B + } else { + panic!("test bug: no artifact for artifact kind {kind:?}") + }; + + return artifact_hash; + } + + /// Describes the SPs and RoTs in the environment used in these tests /// /// There will be: /// @@ -594,25 +732,79 @@ mod test { /// /// The specific set of hardware (boards) vary and are hardcoded: /// - /// - sled 0: gimlet-d - /// - other sleds: gimlet-e - /// - switch 0: sidecar-b - /// - switch 1: sidecar-c - /// - psc 0: psc-b - /// - psc 1: psc-c - fn test_config() -> BTreeMap<(SpType, u16), (&'static str, &'static str)> { + /// - sled 0: gimlet-d, oxide-rot-1 + /// - other sleds: gimlet-e, oxide-rot-1 + /// - switch 0: sidecar-b, oxide-rot-1 + /// - switch 1: sidecar-c, oxide-rot-1 + /// - psc 0: psc-b, oxide-rot-1 + /// - psc 1: psc-c, oxide-rot-1 + fn test_collection_config() -> BTreeMap< + (SpType, u16), + (&'static str, &'static str, &'static str, &'static str), + > { BTreeMap::from([ - ((SpType::Sled, 0), ("sled_0", "gimlet-d")), - ((SpType::Sled, 1), ("sled_1", "gimlet-e")), - ((SpType::Sled, 2), ("sled_2", "gimlet-e")), - ((SpType::Sled, 3), ("sled_3", "gimlet-e")), - ((SpType::Switch, 0), ("switch_0", "sidecar-b")), - ((SpType::Switch, 1), ("switch_1", "sidecar-c")), - ((SpType::Power, 0), ("power_0", "psc-b")), - ((SpType::Power, 1), ("power_1", "psc-c")), + ( + (SpType::Sled, 0), + ("sled_0", "gimlet-d", "oxide-rot-1", ROT_SIGN_GIMLET), + ), + ( + (SpType::Sled, 1), + ("sled_1", "gimlet-e", "oxide-rot-1", ROT_SIGN_GIMLET), + ), + ( + (SpType::Sled, 2), + ("sled_2", "gimlet-e", "oxide-rot-1", ROT_SIGN_GIMLET), + ), + ( + (SpType::Sled, 3), + ("sled_3", "gimlet-e", "oxide-rot-1", ROT_SIGN_GIMLET), + ), + ( + (SpType::Switch, 0), + ("switch_0", "sidecar-b", "oxide-rot-1", ROT_SIGN_SWITCH), + ), + ( + (SpType::Switch, 1), + ("switch_1", "sidecar-c", "oxide-rot-1", ROT_SIGN_SWITCH), + ), + ( + (SpType::Power, 0), + ("power_0", "psc-b", "oxide-rot-1", ROT_SIGN_PSC), + ), + ( + (SpType::Power, 1), + ("power_1", "psc-c", "oxide-rot-1", ROT_SIGN_PSC), + ), ]) } + /// Describes the SPs and RoTs in the environment used in these tests, but + /// spearated by component for use in sequential testing + fn test_config() + -> BTreeMap<(SpType, u16, MgsUpdateComponent), (&'static str, &'static str)> + { + test_collection_config() + .into_iter() + .flat_map( + |( + (sp_type, slot_id), + (serial, sp_board_name, rot_board_name, ..), + )| { + [ + ( + (sp_type, slot_id, MgsUpdateComponent::Sp), + (serial, sp_board_name), + ), + ( + (sp_type, slot_id, MgsUpdateComponent::Rot), + (serial, rot_board_name), + ), + ] + }, + ) + .collect() + } + /// Returns a TufRepoDescription that we can use to exercise the planning /// code. fn make_tuf_repo() -> TufRepoDescription { @@ -624,48 +816,101 @@ mod test { let artifacts = vec![ make_artifact( "control-plane", - KnownArtifactKind::ControlPlane, + KnownArtifactKind::ControlPlane.into(), ARTIFACT_HASH_CONTROL_PLANE, + None, ), make_artifact( "nexus", - KnownArtifactKind::Zone, + KnownArtifactKind::Zone.into(), ARTIFACT_HASH_NEXUS, + None, ), make_artifact( "host-os", - KnownArtifactKind::Host, + KnownArtifactKind::Host.into(), ARTIFACT_HASH_HOST_OS, + None, ), make_artifact( "gimlet-d", - KnownArtifactKind::GimletSp, + KnownArtifactKind::GimletSp.into(), test_artifact_for_board("gimlet-d"), + None, ), make_artifact( "gimlet-e", - KnownArtifactKind::GimletSp, + KnownArtifactKind::GimletSp.into(), test_artifact_for_board("gimlet-e"), + None, ), make_artifact( "sidecar-b", - KnownArtifactKind::SwitchSp, + KnownArtifactKind::SwitchSp.into(), test_artifact_for_board("sidecar-b"), + None, ), make_artifact( "sidecar-c", - KnownArtifactKind::SwitchSp, + KnownArtifactKind::SwitchSp.into(), test_artifact_for_board("sidecar-c"), + None, ), make_artifact( "psc-b", - KnownArtifactKind::PscSp, + KnownArtifactKind::PscSp.into(), test_artifact_for_board("psc-b"), + None, ), make_artifact( "psc-c", - KnownArtifactKind::PscSp, + KnownArtifactKind::PscSp.into(), test_artifact_for_board("psc-c"), + None, + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::GIMLET_ROT_IMAGE_A, + test_artifact_for_artifact_kind( + ArtifactKind::GIMLET_ROT_IMAGE_A, + ), + Some(ROT_SIGN_GIMLET.into()), + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::GIMLET_ROT_IMAGE_B, + test_artifact_for_artifact_kind( + ArtifactKind::GIMLET_ROT_IMAGE_B, + ), + Some(ROT_SIGN_GIMLET.into()), + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::PSC_ROT_IMAGE_A, + test_artifact_for_artifact_kind(ArtifactKind::PSC_ROT_IMAGE_A), + Some(ROT_SIGN_PSC.into()), + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::PSC_ROT_IMAGE_B, + test_artifact_for_artifact_kind(ArtifactKind::PSC_ROT_IMAGE_B), + Some(ROT_SIGN_PSC.into()), + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::SWITCH_ROT_IMAGE_A, + test_artifact_for_artifact_kind( + ArtifactKind::SWITCH_ROT_IMAGE_A, + ), + Some(ROT_SIGN_SWITCH.into()), + ), + make_artifact( + "oxide-rot-1", + ArtifactKind::SWITCH_ROT_IMAGE_B, + test_artifact_for_artifact_kind( + ArtifactKind::SWITCH_ROT_IMAGE_B, + ), + Some(ROT_SIGN_SWITCH.into()), ), ]; @@ -683,31 +928,38 @@ mod test { fn make_artifact( name: &str, - kind: KnownArtifactKind, + kind: ArtifactKind, hash: ArtifactHash, + sign: Option>, ) -> TufArtifactMeta { TufArtifactMeta { id: ArtifactId { name: name.to_string(), version: ARTIFACT_VERSION_2, - kind: kind.into(), + kind, }, hash, - size: 0, // unused here - sign: None, // unused here + size: 0, // unused here + sign, } } // Construct inventory for an environment suitable for our testing. // - // See test_config() for information about the hardware. All SPs will - // appear to be running version `active_version` except those identified in - // `active_version_exceptions`. All SPs will appear to have - // `inactive_version` in the inactive slot. + // See test_config() for information about the hardware. All SPs and RoTs + // will appear to be running version `active_version` except those + // identified in `active_version_exceptions`. All SPs and RoTs will appear + // to have `inactive_version` in the inactive slot. fn make_collection( active_version: ArtifactVersion, active_version_exceptions: &BTreeMap<(SpType, u16), ArtifactVersion>, inactive_version: ExpectedVersion, + active_rot_version: ArtifactVersion, + active_rot_version_exceptions: &BTreeMap< + (SpType, u16), + ArtifactVersion, + >, + inactive_rot_version: ExpectedVersion, ) -> Collection { let mut builder = nexus_inventory::CollectionBuilder::new( "planning_mgs_updates_basic", @@ -736,8 +988,12 @@ mod test { serial_number: String::from("unused"), }; - let test_config = test_config(); - for ((sp_type, sp_slot), (serial, caboose_board)) in test_config { + let test_config = test_collection_config(); + for ( + (sp_type, sp_slot), + (serial, caboose_sp_board, caboose_rot_board, rkth), + ) in test_config + { let sp_state = SpState { model: format!("dummy_{}", sp_type), serial_number: serial.to_string(), @@ -750,6 +1006,9 @@ mod test { let active_version = active_version_exceptions .get(&(sp_type, sp_slot)) .unwrap_or(&active_version); + let active_rot_version = active_rot_version_exceptions + .get(&(sp_type, sp_slot)) + .unwrap_or(&active_rot_version); builder .found_caboose( @@ -757,16 +1016,32 @@ mod test { CabooseWhich::SpSlot0, "test", SpComponentCaboose { - board: caboose_board.to_string(), + board: caboose_sp_board.to_string(), epoch: None, git_commit: String::from("unused"), - name: caboose_board.to_string(), + name: caboose_sp_board.to_string(), sign: None, version: active_version.as_str().to_string(), }, ) .unwrap(); + builder + .found_caboose( + &baseboard_id, + CabooseWhich::RotSlotA, + "test", + SpComponentCaboose { + board: caboose_rot_board.to_string(), + epoch: None, + git_commit: String::from("unused"), + name: caboose_rot_board.to_string(), + sign: Some(rkth.to_string()), + version: active_rot_version.as_str().to_string(), + }, + ) + .unwrap(); + if let ExpectedVersion::Version(inactive_version) = &inactive_version { @@ -776,26 +1051,47 @@ mod test { CabooseWhich::SpSlot1, "test", SpComponentCaboose { - board: caboose_board.to_string(), + board: caboose_sp_board.to_string(), epoch: None, git_commit: String::from("unused"), - name: caboose_board.to_string(), + name: caboose_sp_board.to_string(), sign: None, version: inactive_version.as_str().to_string(), }, ) .unwrap(); } + + if let ExpectedVersion::Version(inactive_rot_version) = + &inactive_rot_version + { + builder + .found_caboose( + &baseboard_id, + CabooseWhich::RotSlotB, + "test", + SpComponentCaboose { + board: caboose_rot_board.to_string(), + epoch: None, + git_commit: String::from("unused"), + name: caboose_rot_board.to_string(), + sign: Some(rkth.to_string()), + version: inactive_rot_version.as_str().to_string(), + }, + ) + .unwrap(); + } } builder.build() } - // Short hand-rolled update sequence that exercises some basic behavior. + // Short hand-rolled update sequence that exercises some basic behavior for + // SP updates. #[test] - fn test_basic() { + fn test_basic_sp() { let logctx = LogContext::new( - "planning_mgs_updates_basic", + "planning_mgs_updates_basic_sp", &ConfigLogging::StderrTerminal { level: ConfigLoggingLevel::Debug }, ); let log = &logctx.log; @@ -806,6 +1102,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let current_boards = &collection.baseboards; let initial_updates = PendingMgsUpdates::new(); @@ -866,6 +1165,9 @@ mod test { ((SpType::Switch, 1), ARTIFACT_VERSION_1), ]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let later_updates = plan_mgs_updates( log, @@ -886,6 +1188,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Switch, 1), ARTIFACT_VERSION_1)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let later_updates = plan_mgs_updates( log, @@ -912,6 +1217,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::new(), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let later_updates = plan_mgs_updates( log, @@ -930,6 +1238,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let updates = plan_mgs_updates( log, @@ -977,6 +1288,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::Version(ARTIFACT_VERSION_1), + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let new_updates = plan_mgs_updates( log, @@ -1016,6 +1330,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1_5)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let new_updates = plan_mgs_updates( log, @@ -1051,6 +1368,293 @@ mod test { logctx.cleanup_successful(); } + // Short hand-rolled update sequence that exercises some basic behavior for + // RoT updates. + #[test] + fn test_basic_rot() { + let logctx = LogContext::new( + "planning_mgs_updates_basic_rot", + &ConfigLogging::StderrTerminal { level: ConfigLoggingLevel::Debug }, + ); + let log = &logctx.log; + + // Test that with no updates pending and no TUF repo specified, there + // will remain no updates pending. + let collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), + ExpectedVersion::NoValidVersion, + ); + let current_boards = &collection.baseboards; + let initial_updates = PendingMgsUpdates::new(); + let nmax_updates = 1; + let impossible_update_policy = ImpossibleUpdatePolicy::Reevaluate; + let updates = plan_mgs_updates( + log, + &collection, + current_boards, + &initial_updates, + &TargetReleaseDescription::Initial, + nmax_updates, + impossible_update_policy, + ); + assert!(updates.is_empty()); + + // Test that when a TUF repo is specified and one RoT is outdated, then + // it's configured with an update (and the update looks correct). + let repo = make_tuf_repo(); + let updates = plan_mgs_updates( + log, + &collection, + current_boards, + &initial_updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_eq!(updates.len(), 1); + let first_update = updates.iter().next().expect("at least one update"); + assert_eq!(first_update.baseboard_id.serial_number, "sled_0"); + assert_eq!(first_update.sp_type, SpType::Sled); + assert_eq!(first_update.slot_id, 0); + assert_eq!(first_update.artifact_hash, ARTIFACT_HASH_ROT_GIMLET_B); + assert_eq!(first_update.artifact_version, ARTIFACT_VERSION_2); + + // Test that when an update is already pending, and nothing changes + // about the state of the world (i.e., the inventory), then the planner + // makes no changes. + let later_updates = plan_mgs_updates( + log, + &collection, + current_boards, + &updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_eq!(updates, later_updates); + + // Test that when two updates are needed, but one is already pending, + // then the other one is *not* started (because it exceeds + // nmax_updates). + let later_collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([ + ((SpType::Sled, 0), ARTIFACT_VERSION_1), + ((SpType::Switch, 1), ARTIFACT_VERSION_1), + ]), + ExpectedVersion::NoValidVersion, + ); + let later_updates = plan_mgs_updates( + log, + &later_collection, + current_boards, + &updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_eq!(updates, later_updates); + + // At this point, we're ready to test that when the first SpType update + // completes, then the second one *is* started. This tests three + // different things: first that we noticed the first one completed, + // second that we noticed another thing needed an update, and third that + // the planner schedules the updates in the correct order: first RoT, + // and second SP. + let later_collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Switch, 1), ARTIFACT_VERSION_1)]), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Switch, 1), ARTIFACT_VERSION_1)]), + ExpectedVersion::NoValidVersion, + ); + let later_updates = plan_mgs_updates( + log, + &later_collection, + current_boards, + &updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_eq!(later_updates.len(), 1); + let next_update = + later_updates.iter().next().expect("at least one update"); + assert_ne!(first_update, next_update); + assert_eq!(next_update.baseboard_id.serial_number, "switch_1"); + assert_eq!(next_update.sp_type, SpType::Switch); + assert_eq!(next_update.slot_id, 1); + assert_eq!(next_update.artifact_hash, ARTIFACT_HASH_ROT_SWITCH_B); + assert_eq!(next_update.artifact_version, ARTIFACT_VERSION_2); + + // Finally, test that when all components are in spec, then no updates + // are configured. + let updated_collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ); + let later_updates = plan_mgs_updates( + log, + &updated_collection, + current_boards, + &later_updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert!(later_updates.is_empty()); + + // Test that we don't try to update boards that aren't in + // `current_boards`, even if they're in inventory and outdated. + let collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), + ExpectedVersion::NoValidVersion, + ); + let updates = plan_mgs_updates( + log, + &collection, + &BTreeSet::new(), + &PendingMgsUpdates::new(), + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert!(updates.is_empty()); + let updates = plan_mgs_updates( + log, + &collection, + &collection.baseboards, + &PendingMgsUpdates::new(), + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + // We verified most of the details above. Here we're just double + // checking that the baseboard being missing is the only reason that no + // update was generated. + assert_eq!(updates.len(), 1); + + // Verify the precondition details of an ordinary RoT update. + let old_update = + updates.into_iter().next().expect("at least one update"); + let PendingMgsUpdateDetails::Rot { + expected_active_slot: old_expected_active_slot, + expected_inactive_version: old_expected_inactive_version, + .. + } = &old_update.details + else { + panic!("expected RoT update"); + }; + assert_eq!(ARTIFACT_VERSION_1, old_expected_active_slot.version()); + assert_eq!( + ExpectedVersion::NoValidVersion, + *old_expected_inactive_version + ); + + // Test that if the inactive slot contents have changed, then we'll get + // a new update reflecting that. + let collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::Version(ARTIFACT_VERSION_1), + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), + ExpectedVersion::Version(ARTIFACT_VERSION_1), + ); + let new_updates = plan_mgs_updates( + log, + &collection, + &collection.baseboards, + &updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_ne!(updates, new_updates); + assert_eq!(new_updates.len(), 1); + let new_update = + new_updates.into_iter().next().expect("at least one update"); + assert_eq!(old_update.baseboard_id, new_update.baseboard_id); + assert_eq!(old_update.sp_type, new_update.sp_type); + assert_eq!(old_update.slot_id, new_update.slot_id); + assert_eq!(old_update.artifact_hash, new_update.artifact_hash); + assert_eq!(old_update.artifact_version, new_update.artifact_version); + let PendingMgsUpdateDetails::Rot { + expected_active_slot: new_expected_active_slot, + expected_inactive_version: new_expected_inactive_version, + .. + } = &new_update.details + else { + panic!("expected RoT update"); + }; + assert_eq!(ARTIFACT_VERSION_1, new_expected_active_slot.version()); + assert_eq!( + ExpectedVersion::Version(ARTIFACT_VERSION_1), + *new_expected_inactive_version + ); + + // Test that if instead it's the active slot whose contents have changed + // to something other than the new expected version, then we'll also get + // a new update reflecting that. + let collection = make_collection( + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1_5)]), + ExpectedVersion::NoValidVersion, + ); + let new_updates = plan_mgs_updates( + log, + &collection, + &collection.baseboards, + &updates, + &TargetReleaseDescription::TufRepo(repo.clone()), + nmax_updates, + impossible_update_policy, + ); + assert_ne!(updates, new_updates); + assert_eq!(new_updates.len(), 1); + let new_update = + new_updates.into_iter().next().expect("at least one update"); + assert_eq!(old_update.baseboard_id, new_update.baseboard_id); + assert_eq!(old_update.sp_type, new_update.sp_type); + assert_eq!(old_update.slot_id, new_update.slot_id); + assert_eq!(old_update.artifact_hash, new_update.artifact_hash); + assert_eq!(old_update.artifact_version, new_update.artifact_version); + let PendingMgsUpdateDetails::Rot { + expected_active_slot: new_expected_active_slot, + expected_inactive_version: new_expected_inactive_version, + .. + } = &new_update.details + else { + panic!("expected RoT update"); + }; + assert_eq!(ARTIFACT_VERSION_1_5, new_expected_active_slot.version()); + assert_eq!( + ExpectedVersion::NoValidVersion, + *new_expected_inactive_version + ); + + logctx.cleanup_successful(); + } + // Confirm our behavior for impossible updates #[test] fn test_impossible_update_policy() { @@ -1065,6 +1669,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::Version(ARTIFACT_VERSION_1_5), + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::Version(ARTIFACT_VERSION_1_5), ); let current_boards = &collection.baseboards; let initial_updates = PendingMgsUpdates::new(); @@ -1119,6 +1726,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::Version(ARTIFACT_VERSION_1_5), ); // If we plan with `ImpossibleUpdatePolicy::Keep`, we should _not_ @@ -1181,9 +1791,10 @@ mod test { let nmax_updates = 1; let impossible_update_policy = ImpossibleUpdatePolicy::Reevaluate; - // Maintain a map of SPs that we've updated. We'll use this to + // Maintain a map of SPs and RoTs that we've updated. We'll use this to // configure the inventory collection that we create at each step. - let mut exceptions = BTreeMap::new(); + let mut sp_exceptions = BTreeMap::new(); + let mut rot_exceptions = BTreeMap::new(); // We do not control the order of updates. But we expect to update each // of the SPs in this map. When we do, we expect to find the given @@ -1191,7 +1802,16 @@ mod test { let mut expected_updates: BTreeMap<_, _> = test_config() .into_iter() .map(|(k, (serial, board_name))| { - (k, (serial, test_artifact_for_board(board_name))) + if board_name == "oxide-rot-1" { + let kind = match k.0 { + SpType::Sled => ArtifactKind::GIMLET_ROT_IMAGE_B, + SpType::Power => ArtifactKind::PSC_ROT_IMAGE_B, + SpType::Switch => ArtifactKind::SWITCH_ROT_IMAGE_B, + }; + (k, (serial, test_artifact_for_artifact_kind(kind))) + } else { + (k, (serial, test_artifact_for_board(board_name))) + } }) .collect(); @@ -1200,7 +1820,10 @@ mod test { // version 1 except for what we've already updated. let collection = make_collection( ARTIFACT_VERSION_1, - &exceptions, + &sp_exceptions, + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_1, + &rot_exceptions, ExpectedVersion::NoValidVersion, ); @@ -1224,21 +1847,39 @@ mod test { new_updates.iter().next().expect("at least one update"); verify_one_sp_update(&mut expected_updates, update); - // Update `exceptions` for the next iteration. + // Update `exceptions` or `rot_exceptions` for the next iteration. let sp_type = update.sp_type; let sp_slot = update.slot_id; - assert!( - exceptions - .insert((sp_type, sp_slot), ARTIFACT_VERSION_2) - .is_none() - ); + match update.details { + PendingMgsUpdateDetails::Rot { .. } => { + assert!( + rot_exceptions + .insert((sp_type, sp_slot), ARTIFACT_VERSION_2) + .is_none() + ); + } + PendingMgsUpdateDetails::Sp { .. } => { + assert!( + sp_exceptions + .insert((sp_type, sp_slot), ARTIFACT_VERSION_2) + .is_none() + ); + } + PendingMgsUpdateDetails::RotBootloader { .. } => { + unimplemented!() + } + } + latest_updates = new_updates; } // Take one more lap. It should reflect zero updates. let collection = make_collection( ARTIFACT_VERSION_1, - &exceptions, + &sp_exceptions, + ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_1, + &rot_exceptions, ExpectedVersion::NoValidVersion, ); let last_updates = plan_mgs_updates( @@ -1268,7 +1909,16 @@ mod test { let mut expected_updates: BTreeMap<_, _> = test_config() .into_iter() .map(|(k, (serial, board_name))| { - (k, (serial, test_artifact_for_board(board_name))) + if board_name == "oxide-rot-1" { + let kind = match k.0 { + SpType::Sled => ArtifactKind::GIMLET_ROT_IMAGE_B, + SpType::Power => ArtifactKind::PSC_ROT_IMAGE_B, + SpType::Switch => ArtifactKind::SWITCH_ROT_IMAGE_B, + }; + (k, (serial, test_artifact_for_artifact_kind(kind))) + } else { + (k, (serial, test_artifact_for_board(board_name))) + } }) .collect(); @@ -1278,6 +1928,9 @@ mod test { ARTIFACT_VERSION_1, &BTreeMap::new(), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_1, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let all_updates = plan_mgs_updates( log, @@ -1288,7 +1941,11 @@ mod test { usize::MAX, impossible_update_policy, ); - assert_eq!(all_updates.len(), expected_updates.len()); + // `all_updates` counts each update per SpType. This means an update for + // SP and RoT for the same SpType count as a single update. For + // `expected_updates`, each component update counts as an update, so the + // amount of `all_updates` should be half of `expected_updates`. + assert_eq!(all_updates.len(), expected_updates.len() / 2); for update in &all_updates { verify_one_sp_update(&mut expected_updates, update); } @@ -1299,6 +1956,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::new(), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::new(), + ExpectedVersion::NoValidVersion, ); let all_updates_done = plan_mgs_updates( log, @@ -1315,24 +1975,41 @@ mod test { } fn verify_one_sp_update( - expected_updates: &mut BTreeMap<(SpType, u16), (&str, ArtifactHash)>, + expected_updates: &mut BTreeMap< + (SpType, u16, MgsUpdateComponent), + (&str, ArtifactHash), + >, update: &PendingMgsUpdate, ) { let sp_type = update.sp_type; let sp_slot = update.slot_id; + let component = match &update.details { + PendingMgsUpdateDetails::Rot { .. } => MgsUpdateComponent::Rot, + PendingMgsUpdateDetails::RotBootloader { .. } => { + MgsUpdateComponent::RotBootloader + } + PendingMgsUpdateDetails::Sp { .. } => MgsUpdateComponent::Sp, + }; println!("found update: {} slot {}", sp_type, sp_slot); let (expected_serial, expected_artifact) = expected_updates - .remove(&(sp_type, sp_slot)) + .remove(&(sp_type, sp_slot, component)) .expect("unexpected update"); assert_eq!(update.artifact_hash, expected_artifact); assert_eq!(update.artifact_version, ARTIFACT_VERSION_2); assert_eq!(update.baseboard_id.serial_number, *expected_serial); - let PendingMgsUpdateDetails::Sp { - expected_active_version, - expected_inactive_version, - } = &update.details - else { - panic!("expected SP update"); + let (expected_active_version, expected_inactive_version) = match &update + .details + { + PendingMgsUpdateDetails::Rot { + expected_active_slot, + expected_inactive_version, + .. + } => (&expected_active_slot.version, expected_inactive_version), + PendingMgsUpdateDetails::Sp { + expected_active_version, + expected_inactive_version, + } => (expected_active_version, expected_inactive_version), + PendingMgsUpdateDetails::RotBootloader { .. } => unimplemented!(), }; assert_eq!(*expected_active_version, ARTIFACT_VERSION_1); assert_eq!(*expected_inactive_version, ExpectedVersion::NoValidVersion); @@ -1353,6 +2030,9 @@ mod test { ARTIFACT_VERSION_2, &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), ExpectedVersion::NoValidVersion, + ARTIFACT_VERSION_2, + &BTreeMap::from([((SpType::Sled, 0), ARTIFACT_VERSION_1)]), + ExpectedVersion::NoValidVersion, ); let nmax_updates = 1; let impossible_update_policy = ImpossibleUpdatePolicy::Reevaluate; diff --git a/nexus/reconfigurator/planning/src/mgs_updates/rot.rs b/nexus/reconfigurator/planning/src/mgs_updates/rot.rs new file mode 100644 index 00000000000..510d7cd320d --- /dev/null +++ b/nexus/reconfigurator/planning/src/mgs_updates/rot.rs @@ -0,0 +1,289 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Facilities for making choices about RoT updates + +use super::MgsUpdateStatus; +use super::mgs_update_status_inactive_versions; + +use gateway_types::rot::RotSlot; +use nexus_types::deployment::ExpectedActiveRotSlot; +use nexus_types::deployment::ExpectedVersion; +use nexus_types::deployment::PendingMgsUpdate; +use nexus_types::deployment::PendingMgsUpdateDetails; +use nexus_types::inventory::BaseboardId; +use nexus_types::inventory::CabooseWhich; +use nexus_types::inventory::Collection; +use omicron_common::api::external::TufRepoDescription; +use slog::{debug, warn}; +use std::sync::Arc; +use tufaceous_artifact::ArtifactKind; +use tufaceous_artifact::ArtifactVersion; + +/// RoT state that gets checked against preconditions in a `PendingMgsUpdate` +pub struct RotUpdateState { + pub active_slot: ExpectedActiveRotSlot, + pub persistent_boot_preference: RotSlot, + pub pending_persistent_boot_preference: Option, + pub transient_boot_preference: Option, +} + +pub fn mgs_update_status_rot( + desired_version: &ArtifactVersion, + expected: RotUpdateState, + found: RotUpdateState, + expected_inactive_version: &ExpectedVersion, + found_inactive_version: Option<&str>, +) -> MgsUpdateStatus { + let RotUpdateState { + active_slot: found_active_slot, + persistent_boot_preference: found_persistent_boot_preference, + pending_persistent_boot_preference: + found_pending_persistent_boot_preference, + transient_boot_preference: found_transient_boot_preference, + } = found; + + let RotUpdateState { + active_slot: expected_active_slot, + persistent_boot_preference: expected_persistent_boot_preference, + pending_persistent_boot_preference: + expected_pending_persistent_boot_preference, + transient_boot_preference: expected_transient_boot_preference, + } = expected; + + if &found_active_slot.version() == desired_version { + // If we find the desired version in the active slot, we're done. + return MgsUpdateStatus::Done; + } + + // The update hasn't completed. + // + // Check to make sure the contents of the active slot, persistent boot + // preference, pending persistent boot preference, and transient boot + // preference are still what they were when we configured this update. + // If not, then this update cannot proceed as currently configured. + if found_active_slot.version() != expected_active_slot.version() + || found_persistent_boot_preference + != expected_persistent_boot_preference + || found_pending_persistent_boot_preference + != expected_pending_persistent_boot_preference + || found_transient_boot_preference != expected_transient_boot_preference + { + return MgsUpdateStatus::Impossible; + } + + // If there is a mismatch between the found persistent boot preference + // and the found active slot then this means a failed update. We cannot + // proceed. + // It will fail its precondition check. + // Transient boot preference is not in use yet, if we find that it is set we + // should not proceed. Once https://github.com/oxidecomputer/hubris/pull/2050 + // is implemented, we should revist this check + if found_persistent_boot_preference != found_active_slot.slot + || found_transient_boot_preference.is_some() + || expected_transient_boot_preference.is_some() + { + return MgsUpdateStatus::Impossible; + } + + // Similarly, check the contents of the inactive slot to determine if it + // still matches what we saw when we configured this update. If not, then + // this update cannot proceed as currently configured. It will fail its + // precondition check. + mgs_update_status_inactive_versions( + found_inactive_version, + expected_inactive_version, + ) +} + +/// Determine if the given baseboard needs an RoT update and, if so, returns it. +pub fn try_make_update_rot( + log: &slog::Logger, + baseboard_id: &Arc, + inventory: &Collection, + current_artifacts: &TufRepoDescription, +) -> Option { + let Some(sp_info) = inventory.sps.get(baseboard_id) else { + warn!( + log, + "cannot configure RoT update for board \ + (missing SP info from inventory)"; + baseboard_id + ); + return None; + }; + + let Some(rot_state) = inventory.rots.get(baseboard_id) else { + warn!( + log, + "cannot configure RoT update for board \ + (missing RoT state from inventory)"; + baseboard_id + ); + return None; + }; + + let active_slot = rot_state.active_slot; + + let Some(active_caboose) = inventory + .caboose_for(CabooseWhich::from_rot_slot(active_slot), baseboard_id) + else { + warn!( + log, + "cannot configure RoT update for board \ + (missing active slot {active_slot} caboose from inventory)"; + baseboard_id, + ); + return None; + }; + + let Ok(expected_active_version) = active_caboose.caboose.version.parse() + else { + warn!( + log, + "cannot configure RoT update for board \ + (cannot parse current active version as an ArtifactVersion)"; + baseboard_id, + "found_version" => &active_caboose.caboose.version, + ); + return None; + }; + + let board = &active_caboose.caboose.board; + let Some(rkth) = &active_caboose.caboose.sign else { + warn!( + log, + "cannot configure RoT update for board \ + (missing sign in caboose from inventory)"; + baseboard_id + ); + return None; + }; + + let matching_artifacts: Vec<_> = current_artifacts + .artifacts + .iter() + .filter(|a| { + // A matching RoT artifact will have: + // + // - "name" matching the board name (found above from caboose) + // - "kind" matching one of the known RoT kinds + // - "sign" matching the rkth (found above from caboose) + + if a.id.name != *board { + return false; + } + + let Some(artifact_sign) = &a.sign else { + return false; + }; + let Ok(artifact_sign) = String::from_utf8(artifact_sign.to_vec()) + else { + return false; + }; + if artifact_sign != *rkth { + return false; + } + + // We'll be updating the inactive slot, so we choose the artifact + // based on the inactive slot's kind. + match active_slot.toggled() { + RotSlot::A => { + let slot_a_artifacts = [ + ArtifactKind::GIMLET_ROT_IMAGE_A, + ArtifactKind::PSC_ROT_IMAGE_A, + ArtifactKind::SWITCH_ROT_IMAGE_A, + ]; + + if slot_a_artifacts.contains(&a.id.kind) { + return true; + } + } + RotSlot::B => { + let slot_b_artifacts = [ + ArtifactKind::GIMLET_ROT_IMAGE_B, + ArtifactKind::PSC_ROT_IMAGE_B, + ArtifactKind::SWITCH_ROT_IMAGE_B, + ]; + + if slot_b_artifacts.contains(&a.id.kind) { + return true; + } + } + } + + false + }) + .collect(); + if matching_artifacts.is_empty() { + warn!( + log, + "cannot configure RoT update for board (no matching artifact)"; + baseboard_id, + ); + return None; + } + + if matching_artifacts.len() > 1 { + // This should be impossible unless we shipped a TUF repo with more than + // 1 artifact for the same board and root key table hash (RKTH) as + // `SIGN`. But it doesn't prevent us from picking one and proceeding. + // Make a note and proceed. + warn!(log, "found more than one matching artifact for RoT update"); + } + + let artifact = matching_artifacts[0]; + + // If the artifact's version matches what's deployed, then no update is + // needed. + if artifact.id.version == expected_active_version { + debug!(log, "no RoT update needed for board"; baseboard_id); + return None; + } + + let expected_active_slot = ExpectedActiveRotSlot { + slot: active_slot, + version: expected_active_version, + }; + + // Begin configuring an update. + let expected_inactive_version = match inventory + .caboose_for( + CabooseWhich::from_rot_slot(active_slot.toggled()), + baseboard_id, + ) + .map(|c| c.caboose.version.parse::()) + .transpose() + { + Ok(None) => ExpectedVersion::NoValidVersion, + Ok(Some(v)) => ExpectedVersion::Version(v), + Err(_) => { + warn!( + log, + "cannot configure RoT update for board \ + (found inactive slot contents but version was not valid)"; + baseboard_id + ); + return None; + } + }; + + Some(PendingMgsUpdate { + baseboard_id: baseboard_id.clone(), + sp_type: sp_info.sp_type, + slot_id: sp_info.sp_slot, + details: PendingMgsUpdateDetails::Rot { + expected_active_slot, + expected_inactive_version, + expected_persistent_boot_preference: rot_state + .persistent_boot_preference, + expected_pending_persistent_boot_preference: rot_state + .pending_persistent_boot_preference, + expected_transient_boot_preference: rot_state + .transient_boot_preference, + }, + artifact_hash: artifact.hash, + artifact_version: artifact.id.version.clone(), + }) +} diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 27cf9a34d8c..7ddda6014a1 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -1131,6 +1131,7 @@ impl<'a> Planner<'a> { .all_sleds(SledFilter::SpsUpdatedByReconfigurator) .map(|(_sled_id, details)| &details.baseboard_id) .collect(); + let included_baseboards = self.inventory .sps diff --git a/nexus/reconfigurator/planning/src/system.rs b/nexus/reconfigurator/planning/src/system.rs index 53729e0b854..f9a7a45a141 100644 --- a/nexus/reconfigurator/planning/src/system.rs +++ b/nexus/reconfigurator/planning/src/system.rs @@ -585,6 +585,127 @@ impl SystemDescription { Ok(sled.sp_inactive_caboose().map(|c| c.version.as_ref())) } + pub fn sled_sp_state( + &self, + sled_id: SledUuid, + ) -> anyhow::Result> { + let sled = self.get_sled(sled_id)?; + Ok(sled.sp_state()) + } + + /// Update the RoT versions reported for a sled. + /// + /// Where `None` is provided, no changes are made. + pub fn sled_update_rot_versions( + &mut self, + sled_id: SledUuid, + slot_a_version: Option, + slot_b_version: Option, + ) -> anyhow::Result<&mut Self> { + let sled = self.get_sled_mut(sled_id)?; + sled.set_rot_versions(slot_a_version, slot_b_version); + Ok(self) + } + + pub fn sled_rot_active_slot( + &self, + sled_id: SledUuid, + ) -> anyhow::Result { + let sp_state = self.sled_sp_state(sled_id)?; + sp_state + .ok_or_else(|| { + anyhow!("failed to retrieve SP state from sled id: {sled_id}") + }) + .and_then(|(_hw_slot, sp_state)| match sp_state.rot.clone() { + RotState::V2 { active, .. } | RotState::V3 { active, .. } => { + Ok(active) + } + RotState::CommunicationFailed { message } => Err(anyhow!( + "failed to retrieve active RoT slot due to \ + communication failure: {message}" + )), + }) + } + + pub fn sled_rot_persistent_boot_preference( + &self, + sled_id: SledUuid, + ) -> anyhow::Result { + let sp_state = self.sled_sp_state(sled_id)?; + sp_state + .ok_or_else(|| { + anyhow!("failed to retrieve SP state from sled id: {sled_id}") + }) + .and_then(|(_hw_slot, sp_state)| match sp_state.rot.clone() { + RotState::V2 { persistent_boot_preference, .. } + | RotState::V3 { persistent_boot_preference, .. } => { + Ok(persistent_boot_preference) + } + RotState::CommunicationFailed { message } => Err(anyhow!( + "failed to retrieve persistent boot preference slot \ + due to communication failure: {message}" + )), + }) + } + + pub fn sled_rot_pending_persistent_boot_preference( + &self, + sled_id: SledUuid, + ) -> anyhow::Result> { + let sp_state = self.sled_sp_state(sled_id)?; + sp_state + .ok_or_else(|| { + anyhow!("failed to retrieve SP state from sled id: {sled_id}") + }) + .and_then(|(_hw_slot, sp_state)| match sp_state.rot.clone() { + RotState::V2 { pending_persistent_boot_preference, .. } + | RotState::V3 { pending_persistent_boot_preference, .. } => { + Ok(pending_persistent_boot_preference) + } + RotState::CommunicationFailed { message } => Err(anyhow!( + "failed to retrieve pending persistent boot \ + preference slot due to communication failure: {message}" + )), + }) + } + + pub fn sled_rot_transient_boot_preference( + &self, + sled_id: SledUuid, + ) -> anyhow::Result> { + let sp_state = self.sled_sp_state(sled_id)?; + sp_state + .ok_or_else(|| { + anyhow!("failed to retrieve SP state from sled id: {sled_id}") + }) + .and_then(|(_hw_slot, sp_state)| match sp_state.rot.clone() { + RotState::V2 { transient_boot_preference, .. } + | RotState::V3 { transient_boot_preference, .. } => { + Ok(transient_boot_preference) + } + RotState::CommunicationFailed { message } => Err(anyhow!( + "failed to retrieve transient boot preference slot \ + due to communication failure: {message}" + )), + }) + } + + pub fn sled_rot_slot_a_version( + &self, + sled_id: SledUuid, + ) -> anyhow::Result> { + let sled = self.get_sled(sled_id)?; + Ok(sled.rot_slot_a_caboose().map(|c| c.version.as_ref())) + } + + pub fn sled_rot_slot_b_version( + &self, + sled_id: SledUuid, + ) -> anyhow::Result> { + let sled = self.get_sled(sled_id)?; + Ok(sled.rot_slot_b_caboose().map(|c| c.version.as_ref())) + } + /// Set a sled's mupdate override field. /// /// Returns the previous value, or previous error if set. @@ -783,6 +904,42 @@ impl SystemDescription { ) .context("recording SP inactive caboose")?; } + + if let Some(slot_a) = &s.rot_slot_a_caboose() { + builder + .found_caboose( + &baseboard_id, + CabooseWhich::RotSlotA, + "fake MGS 1", + SpComponentCaboose { + board: slot_a.board.clone(), + epoch: None, + git_commit: slot_a.git_commit.clone(), + name: slot_a.name.clone(), + sign: slot_a.sign.clone(), + version: slot_a.version.clone(), + }, + ) + .context("recording RoT slot a caboose")?; + } + + if let Some(slot_b) = &s.rot_slot_b_caboose() { + builder + .found_caboose( + &baseboard_id, + CabooseWhich::RotSlotB, + "fake MGS 1", + SpComponentCaboose { + board: slot_b.board.clone(), + epoch: None, + git_commit: slot_b.git_commit.clone(), + name: slot_b.name.clone(), + sign: slot_b.sign.clone(), + version: slot_b.version.clone(), + }, + ) + .context("recording RoT slot b caboose")?; + } } let sled_inventory = s.sled_agent_inventory(); @@ -984,6 +1141,8 @@ pub struct SledHwInventory<'a> { pub sp_host_phase_1_hash_flash: BTreeMap, pub sp_active: Option>, pub sp_inactive: Option>, + pub rot_slot_a: Option>, + pub rot_slot_b: Option>, } /// Our abstract description of a `Sled` @@ -1004,6 +1163,8 @@ pub struct Sled { sp_host_phase_1_hash_flash: BTreeMap, sp_active_caboose: Option>, sp_inactive_caboose: Option>, + rot_slot_a_caboose: Option>, + rot_slot_b_caboose: Option>, } impl Sled { @@ -1166,6 +1327,10 @@ impl Sled { String::from("0.0.1"), ))), sp_inactive_caboose: None, + rot_slot_a_caboose: Some(Arc::new(Self::default_rot_caboose( + String::from("0.0.2"), + ))), + rot_slot_b_caboose: None, } } @@ -1208,6 +1373,10 @@ impl Sled { inventory_sp.as_ref().and_then(|hw| hw.sp_active.clone()); let sp_inactive_caboose = inventory_sp.as_ref().and_then(|hw| hw.sp_inactive.clone()); + let rot_slot_a_caboose = + inventory_sp.as_ref().and_then(|hw| hw.rot_slot_a.clone()); + let rot_slot_b_caboose = + inventory_sp.as_ref().and_then(|hw| hw.rot_slot_b.clone()); let inventory_sp = inventory_sp.map(|sledhw| { // RotStateV3 unconditionally sets all of these let sp_state = if sledhw.rot.slot_a_sha3_256_digest.is_some() @@ -1322,6 +1491,8 @@ impl Sled { sp_host_phase_1_hash_flash, sp_active_caboose, sp_inactive_caboose, + rot_slot_a_caboose, + rot_slot_b_caboose, } } @@ -1360,6 +1531,14 @@ impl Sled { &self.inventory_sled_agent } + fn rot_slot_a_caboose(&self) -> Option<&Caboose> { + self.rot_slot_a_caboose.as_deref() + } + + fn rot_slot_b_caboose(&self) -> Option<&Caboose> { + self.rot_slot_b_caboose.as_deref() + } + fn sp_active_caboose(&self) -> Option<&Caboose> { self.sp_active_caboose.as_deref() } @@ -1389,7 +1568,6 @@ impl Sled { /// Update the reported RoT bootloader versions /// /// If either field is `None`, that field is _unchanged_. - // Note that this means there's no way to _unset_ the version. fn set_rot_bootloader_versions( &mut self, stage0_version: Option, @@ -1475,6 +1653,56 @@ impl Sled { } } + /// Update the reported RoT versions + /// + /// If either field is `None`, that field is _unchanged_. + // Note that this means there's no way to _unset_ the version. + fn set_rot_versions( + &mut self, + slot_a_version: Option, + slot_b_version: Option, + ) { + if let Some(slot_a_version) = slot_a_version { + match slot_a_version { + ExpectedVersion::NoValidVersion => { + self.rot_slot_a_caboose = None; + } + ExpectedVersion::Version(v) => { + match &mut self.rot_slot_a_caboose { + Some(caboose) => { + Arc::make_mut(caboose).version = v.to_string() + } + new @ None => { + *new = Some(Arc::new(Self::default_rot_caboose( + v.to_string(), + ))); + } + } + } + } + } + + if let Some(slot_b_version) = slot_b_version { + match slot_b_version { + ExpectedVersion::NoValidVersion => { + self.rot_slot_b_caboose = None; + } + ExpectedVersion::Version(v) => { + match &mut self.rot_slot_b_caboose { + Some(caboose) => { + Arc::make_mut(caboose).version = v.to_string() + } + new @ None => { + *new = Some(Arc::new(Self::default_rot_caboose( + v.to_string(), + ))); + } + } + } + } + } + } + fn default_rot_bootloader_caboose(version: String) -> Caboose { let board = sp_sim::SIM_ROT_STAGE0_BOARD.to_string(); Caboose { @@ -1497,6 +1725,17 @@ impl Sled { } } + fn default_rot_caboose(version: String) -> Caboose { + let board = sp_sim::SIM_ROT_BOARD.to_string(); + Caboose { + board: board.clone(), + git_commit: String::from("unknown"), + name: board, + version: version.to_string(), + sign: None, + } + } + /// Set the mupdate override field for a sled, returning the previous value. fn set_mupdate_override( &mut self, diff --git a/nexus/reconfigurator/simulation/src/system.rs b/nexus/reconfigurator/simulation/src/system.rs index c21d3e09424..eca2737ad2a 100644 --- a/nexus/reconfigurator/simulation/src/system.rs +++ b/nexus/reconfigurator/simulation/src/system.rs @@ -794,6 +794,12 @@ impl SimSystemBuilderInner { let sp_inactive = primary_collection .caboose_for(CabooseWhich::SpSlot1, baseboard_id) .map(|c| c.caboose.clone()); + let rot_slot_a = primary_collection + .caboose_for(CabooseWhich::RotSlotA, baseboard_id) + .map(|c| c.caboose.clone()); + let rot_slot_b = primary_collection + .caboose_for(CabooseWhich::RotSlotB, baseboard_id) + .map(|c| c.caboose.clone()); if let (Some(inv_sp), Some(inv_rot)) = (inv_sp, inv_rot) { Some(SledHwInventory { baseboard_id: &baseboard_id, @@ -804,6 +810,8 @@ impl SimSystemBuilderInner { sp_host_phase_1_hash_flash, sp_active, sp_inactive, + rot_slot_a, + rot_slot_b, }) } else { None diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index 7acfe51cd6a..713e074bf2b 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -446,6 +446,26 @@ pub enum CabooseWhich { Stage0Next, } +impl CabooseWhich { + pub fn toggled_slot(&self) -> Self { + match self { + CabooseWhich::RotSlotA => CabooseWhich::RotSlotB, + CabooseWhich::RotSlotB => CabooseWhich::RotSlotA, + CabooseWhich::SpSlot0 => CabooseWhich::SpSlot1, + CabooseWhich::SpSlot1 => CabooseWhich::SpSlot0, + CabooseWhich::Stage0 => CabooseWhich::Stage0Next, + CabooseWhich::Stage0Next => CabooseWhich::Stage0, + } + } + + pub fn from_rot_slot(slot: RotSlot) -> Self { + match slot { + RotSlot::A => CabooseWhich::RotSlotA, + RotSlot::B => CabooseWhich::RotSlotB, + } + } +} + /// Root of trust page contents found during a collection /// /// These are normalized in the database. Each distinct `RotPage` is assigned a diff --git a/update-common/manifests/fake.toml b/update-common/manifests/fake.toml index 4661e4592ea..ccbc2d850b8 100644 --- a/update-common/manifests/fake.toml +++ b/update-common/manifests/fake.toml @@ -10,7 +10,7 @@ version = "1.0.0" source = { kind = "fake", size = "1MiB" } [[artifact.gimlet_rot]] -name = "fake-gimlet-rot" +name = "SimRot" version = "1.0.0" [artifact.gimlet_rot.source] kind = "composite-rot"