Skip to content

Commit 03fd530

Browse files
authored
[1/n] [reconfigurator-cli] add log and switch commands (#9363)
Add commands to show a log of states, and to switch to a different simulator state. This allows for rewinding and branching off states. To render a graphical log, we use the sapling-renderdag library used by Sapling and Jujutsu.
1 parent aa16dff commit 03fd530

File tree

16 files changed

+1224
-15
lines changed

16 files changed

+1224
-15
lines changed

Cargo.lock

Lines changed: 13 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ zone = { version = "0.3.1", default-features = false, features = ["async"] }
799799
# the kinds). However, uses of omicron-uuid-kinds _within omicron_ will have
800800
# std and the other features enabled because they'll refer to it via
801801
# omicron-uuid-kinds.workspace = true.
802-
newtype-uuid = { version = "1.3.1", default-features = false }
802+
newtype-uuid = { version = "1.3.2", default-features = false }
803803
newtype-uuid-macros = "0.1.0"
804804
omicron-uuid-kinds = { path = "uuid-kinds", features = ["serde", "schemars08", "uuid-v4"] }
805805

dev-tools/reconfigurator-cli/src/lib.rs

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ use nexus_reconfigurator_planning::planner::Planner;
2828
use nexus_reconfigurator_planning::system::{
2929
RotStateOverrides, SledBuilder, SledInventoryVisibility, SystemDescription,
3030
};
31-
use nexus_reconfigurator_simulation::{BlueprintId, CollectionId, SimState};
31+
use nexus_reconfigurator_simulation::{
32+
BlueprintId, CollectionId, GraphRenderOptions, GraphStartingState,
33+
ReconfiguratorSimId, SimState,
34+
};
3235
use nexus_reconfigurator_simulation::{SimStateBuilder, SimTufRepoSource};
3336
use nexus_reconfigurator_simulation::{SimTufRepoDescription, Simulator};
3437
use nexus_sled_agent_shared::inventory::ZoneKind;
@@ -345,6 +348,8 @@ fn process_command(
345348
Commands::LoadExample(args) => cmd_load_example(sim, args),
346349
Commands::FileContents(args) => cmd_file_contents(args),
347350
Commands::Save(args) => cmd_save(sim, args),
351+
Commands::State(StateArgs::Log(args)) => cmd_state_log(sim, args),
352+
Commands::State(StateArgs::Switch(args)) => cmd_state_switch(sim, args),
348353
Commands::Wipe(args) => cmd_wipe(sim, args),
349354
};
350355

@@ -443,6 +448,9 @@ enum Commands {
443448
LoadExample(LoadExampleArgs),
444449
/// show information about what's in a saved file
445450
FileContents(FileContentsArgs),
451+
/// state-related commands
452+
#[command(flatten)]
453+
State(StateArgs),
446454
/// reset the state of the REPL
447455
Wipe(WipeArgs),
448456
}
@@ -1015,6 +1023,45 @@ impl From<CollectionIdOpt> for CollectionId {
10151023
}
10161024
}
10171025

1026+
#[derive(Clone, Debug)]
1027+
enum ReconfiguratorSimIdOpt {
1028+
/// use a specific reconfigurator sim state by full UUID
1029+
Id(ReconfiguratorSimUuid),
1030+
/// use a reconfigurator sim state by UUID prefix
1031+
Prefix(String),
1032+
}
1033+
1034+
impl FromStr for ReconfiguratorSimIdOpt {
1035+
type Err = Infallible;
1036+
1037+
fn from_str(s: &str) -> Result<Self, Self::Err> {
1038+
match s.parse::<ReconfiguratorSimUuid>() {
1039+
Ok(id) => Ok(ReconfiguratorSimIdOpt::Id(id)),
1040+
Err(_) => Ok(ReconfiguratorSimIdOpt::Prefix(s.to_owned())),
1041+
}
1042+
}
1043+
}
1044+
1045+
impl fmt::Display for ReconfiguratorSimIdOpt {
1046+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047+
match self {
1048+
ReconfiguratorSimIdOpt::Id(id) => id.fmt(f),
1049+
ReconfiguratorSimIdOpt::Prefix(prefix) => prefix.fmt(f),
1050+
}
1051+
}
1052+
}
1053+
1054+
impl From<ReconfiguratorSimIdOpt> for ReconfiguratorSimId {
1055+
fn from(value: ReconfiguratorSimIdOpt) -> Self {
1056+
match value {
1057+
ReconfiguratorSimIdOpt::Id(id) => ReconfiguratorSimId::Id(id),
1058+
ReconfiguratorSimIdOpt::Prefix(prefix) => {
1059+
ReconfiguratorSimId::Prefix(prefix)
1060+
}
1061+
}
1062+
}
1063+
}
1064+
10181065
/// Clap field for an optional mupdate override UUID.
10191066
///
10201067
/// This structure is similar to `Option`, but is specified separately to:
@@ -1455,6 +1502,40 @@ struct SaveArgs {
14551502
filename: Utf8PathBuf,
14561503
}
14571504

1505+
#[derive(Debug, Subcommand)]
1506+
enum StateArgs {
1507+
/// display a log of simulator states
1508+
///
1509+
/// Shows the history of states from the current state back to the root.
1510+
Log(StateLogArgs),
1511+
/// switch to a different state
1512+
///
1513+
/// Changes the current working state to the specified state. All subsequent
1514+
/// commands will operate from this state.
1515+
Switch(StateSwitchArgs),
1516+
}
1517+
1518+
#[derive(Debug, Args)]
1519+
struct StateLogArgs {
1520+
/// Starting state ID (defaults to current state)
1521+
#[clap(long)]
1522+
from: Option<ReconfiguratorSimUuid>,
1523+
1524+
/// Limit number of states to display
1525+
#[clap(long, short = 'n', requires = "from")]
1526+
limit: Option<usize>,
1527+
1528+
/// Show changes in each state (verbose mode)
1529+
#[clap(long, short = 'v')]
1530+
verbose: bool,
1531+
}
1532+
1533+
#[derive(Debug, Args)]
1534+
struct StateSwitchArgs {
1535+
/// The state ID or unique prefix to switch to
1536+
state_id: ReconfiguratorSimIdOpt,
1537+
}
1538+
14581539
#[derive(Debug, Args)]
14591540
struct WipeArgs {
14601541
/// What to wipe
@@ -2801,6 +2882,44 @@ fn cmd_save(
28012882
)))
28022883
}
28032884

2885+
fn cmd_state_log(
2886+
sim: &mut ReconfiguratorSim,
2887+
args: StateLogArgs,
2888+
) -> anyhow::Result<Option<String>> {
2889+
let StateLogArgs { from, limit, verbose } = args;
2890+
2891+
let starting_state = if let Some(start) = from {
2892+
GraphStartingState::State { start, limit }
2893+
} else {
2894+
GraphStartingState::None
2895+
};
2896+
2897+
let options = GraphRenderOptions::new(sim.current)
2898+
.with_verbose(verbose)
2899+
.with_starting_state(starting_state);
2900+
2901+
let output = sim.sim.render_graph(&options);
2902+
2903+
Ok(Some(output))
2904+
}
2905+
2906+
fn cmd_state_switch(
2907+
sim: &mut ReconfiguratorSim,
2908+
args: StateSwitchArgs,
2909+
) -> anyhow::Result<Option<String>> {
2910+
let state = sim.sim.resolve_and_get_state(args.state_id.into())?;
2911+
let target_id = state.id();
2912+
2913+
sim.current = target_id;
2914+
2915+
Ok(Some(format!(
2916+
"switched to state {} (generation {}): {}",
2917+
target_id,
2918+
state.generation(),
2919+
state.description()
2920+
)))
2921+
}
2922+
28042923
fn cmd_wipe(
28052924
sim: &mut ReconfiguratorSim,
28062925
args: WipeArgs,
@@ -2812,7 +2931,7 @@ fn cmd_wipe(
28122931
state.config_mut().wipe();
28132932
state.rng_mut().reset_state();
28142933
format!(
2815-
"- wiped system, reconfigurator-sim config, and RNG state\n
2934+
"- wiped system, reconfigurator-sim config, and RNG state\n\
28162935
- reset seed to {}",
28172936
state.rng_mut().seed()
28182937
)

dev-tools/reconfigurator-cli/tests/input/cmds-example.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,34 @@ set planner-config --add-zones-with-mupdate-override true
6767
# Sled index out of bounds, will error out.
6868
wipe all
6969
load-example --seed test-basic --nsleds 3 --sled-policy 3:non-provisionable
70+
71+
log
72+
log --verbose
73+
74+
# Switch states with a full UUID.
75+
switch 4d8d6725-1ae3-4f88-b9cb-a34db82d7439
76+
log
77+
78+
# Switch states with a unique prefix.
79+
switch 860f
80+
log
81+
82+
# Switch states with an ambiguous prefix (should fail).
83+
switch 4
84+
85+
# Switch states with a non-existent prefix (should fail).
86+
switch zzzz
87+
88+
# Make a new branch.
89+
sled-add
90+
sled-add
91+
92+
# Make an additional branch based on the previous branch.
93+
switch 4d8d6725
94+
sled-add
95+
96+
# Go back to the previous branch.
97+
switch 5c53cf4b
98+
99+
log
100+
log --verbose

0 commit comments

Comments
 (0)