@@ -28,7 +28,10 @@ use nexus_reconfigurator_planning::planner::Planner;
2828use 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+ } ;
3235use nexus_reconfigurator_simulation:: { SimStateBuilder , SimTufRepoSource } ;
3336use nexus_reconfigurator_simulation:: { SimTufRepoDescription , Simulator } ;
3437use 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 ) ]
14591540struct 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+
28042923fn 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 )
0 commit comments