Skip to content

Commit 5ecc899

Browse files
authored
[reconfigurator] Add RoT options for reconfigurator-sp-updater (#8193)
Related: #7989 Closes: #8198
1 parent 186302d commit 5ecc899

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-tools/reconfigurator-sp-updater/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ chrono.workspace = true
1414
dropshot.workspace = true
1515
futures.workspace = true
1616
gateway-client.workspace = true
17+
gateway-types.workspace = true
1718
humantime.workspace = true
1819
internal-dns-resolver.workspace = true
1920
internal-dns-types.workspace = true

dev-tools/reconfigurator-sp-updater/src/main.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ use clap::Subcommand;
1313
use futures::StreamExt;
1414
use gateway_client::types::SpIgnition;
1515
use gateway_client::types::SpType;
16+
use gateway_types::rot::RotSlot;
1617
use internal_dns_types::names::ServiceName;
1718
use nexus_mgs_updates::ArtifactCache;
1819
use nexus_mgs_updates::MgsUpdateDriver;
20+
use nexus_types::deployment::ExpectedActiveRotSlot;
1921
use nexus_types::deployment::ExpectedVersion;
2022
use nexus_types::deployment::PendingMgsUpdate;
2123
use nexus_types::deployment::PendingMgsUpdateDetails;
@@ -118,6 +120,7 @@ impl ReconfiguratorSpUpdater {
118120
.expect("we just waited for this condition");
119121
format!("http://{}", mgs_backend.address)
120122
};
123+
121124
let mgs_client = gateway_client::Client::new(
122125
&mgs_url,
123126
log.new(o!("mgs_url" => mgs_url.clone())),
@@ -374,9 +377,40 @@ struct SetArgs {
374377
#[derive(Clone, Debug, Subcommand)]
375378
enum Component {
376379
Sp {
380+
/// expected version of the active slot
381+
#[arg(long, short = 'a')]
377382
expected_active_version: ArtifactVersion,
383+
/// expected version of the inactive slot
384+
#[arg(long, short = 'i')]
378385
expected_inactive_version: ExpectedVersion,
379386
},
387+
Rot {
388+
/// whether we expect the "A" or "B" slot to be active
389+
#[arg(long, short = 's')]
390+
expected_active_slot: RotSlot,
391+
/// expected version of the "A" slot
392+
#[arg(long, short = 'a')]
393+
expected_slot_a_version: ExpectedVersion,
394+
/// expected version of the "B" slot
395+
#[arg(long, short = 'b')]
396+
expected_slot_b_version: ExpectedVersion,
397+
/// the expected persistent boot preference written into the current
398+
/// authoritative CFPA page (ping or pong).
399+
/// Will default to the value of expected_active_version when not set
400+
#[arg(long, short = 'p')]
401+
expected_persistent_boot_preference: Option<RotSlot>,
402+
/// the expected persistent boot preference written into the CFPA scratch
403+
/// page that will become the persistent boot preference in the authoritative
404+
/// CFPA page upon reboot, unless CFPA update of the authoritative page fails
405+
/// for some reason
406+
#[arg(long, short = 'x')]
407+
expected_pending_persistent_boot_preference: Option<RotSlot>,
408+
// this field is not in use yet.
409+
//
410+
/// override persistent preference selection for a single boot
411+
#[arg(long, short = 't')]
412+
expected_transient_boot_preference: Option<RotSlot>,
413+
},
380414
}
381415

382416
fn cmd_set(
@@ -397,6 +431,46 @@ fn cmd_set(
397431
expected_active_version,
398432
expected_inactive_version,
399433
},
434+
Component::Rot {
435+
expected_active_slot,
436+
expected_slot_a_version,
437+
expected_slot_b_version,
438+
expected_persistent_boot_preference,
439+
expected_pending_persistent_boot_preference,
440+
expected_transient_boot_preference,
441+
} => {
442+
let (active_version, expected_inactive_version) =
443+
match expected_active_slot {
444+
RotSlot::A => {
445+
(expected_slot_a_version, expected_slot_b_version)
446+
}
447+
RotSlot::B => {
448+
(expected_slot_b_version, expected_slot_a_version)
449+
}
450+
};
451+
452+
let expected_active_version = match active_version {
453+
ExpectedVersion::Version(v) => v,
454+
ExpectedVersion::NoValidVersion => {
455+
return Err(anyhow!(
456+
"the expected active slot version must have a valid version"
457+
));
458+
}
459+
};
460+
461+
PendingMgsUpdateDetails::Rot {
462+
expected_active_slot: ExpectedActiveRotSlot {
463+
slot: expected_active_slot,
464+
version: expected_active_version,
465+
},
466+
expected_inactive_version,
467+
expected_persistent_boot_preference:
468+
expected_persistent_boot_preference
469+
.unwrap_or(expected_active_slot),
470+
expected_pending_persistent_boot_preference,
471+
expected_transient_boot_preference,
472+
}
473+
}
400474
},
401475
artifact_hash: args.artifact_hash,
402476
artifact_version: ArtifactVersion::new(args.version)

gateway-types/src/rot.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use daft::Diffable;
66
use schemars::JsonSchema;
77
use serde::{Deserialize, Serialize};
8+
use std::str::FromStr;
89

910
#[derive(
1011
Debug,
@@ -199,6 +200,22 @@ impl RotSlot {
199200
}
200201
}
201202

203+
impl FromStr for RotSlot {
204+
type Err = String;
205+
206+
fn from_str(s: &str) -> Result<Self, Self::Err> {
207+
match s {
208+
"a" | "A" => Ok(RotSlot::A),
209+
"b" | "B" => Ok(RotSlot::B),
210+
_ => Err(format!(
211+
"unrecognized value {} for RoT slot. \
212+
Must be one of `a`, `A`, `b`, or `B`",
213+
s
214+
)),
215+
}
216+
}
217+
}
218+
202219
impl From<gateway_messages::RotSlotId> for RotSlot {
203220
fn from(slot: gateway_messages::RotSlotId) -> Self {
204221
match slot {

nexus/mgs-updates/src/rot_updater.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,28 @@ impl SpComponentUpdateHelper for ReconfiguratorRotUpdater {
399399
) -> BoxFuture<'a, Result<(), GatewayClientError>> {
400400
mgs_clients
401401
.try_all_serially(log, move |mgs_client| async move {
402+
// We want to set the slot we've just updated as the active one
403+
debug!(log, "attempting to set active slot");
404+
let inactive_slot = match &update.details {
405+
PendingMgsUpdateDetails::Rot { expected_active_slot, .. } => {
406+
expected_active_slot.slot().toggled().to_u16()
407+
},
408+
PendingMgsUpdateDetails::Sp { .. } => unreachable!(
409+
"pending MGS update details within ReconfiguratorRotUpdater \
410+
will always be for the RoT"
411+
)
412+
};
413+
let persist = true;
414+
mgs_client
415+
.sp_component_active_slot_set(
416+
update.sp_type,
417+
update.slot_id,
418+
&SpComponent::ROT.to_string(),
419+
persist,
420+
&SpComponentFirmwareSlot { slot: inactive_slot }
421+
)
422+
.await?;
423+
402424
debug!(log, "attempting to reset device");
403425
mgs_client
404426
.sp_component_reset(

0 commit comments

Comments
 (0)