@@ -172,20 +172,20 @@ enum Command {
172172 /// Ask SP for its current state.
173173 State ,
174174
175- /// Get the ignition state for a single target port (only valid if the SP is
176- /// an ignition controller).
175+ /// Get the ignition state for a port (only valid if the SP is an ignition
176+ /// controller).
177177 Ignition {
178- #[ clap(
179- help = "integer of a target, or 'all' for all targets" ,
180- value_parser = IgnitionLinkEventsTarget :: parse,
181- ) ]
182- target : IgnitionLinkEventsTarget ,
178+ #[ clap( flatten) ]
179+ sel : IgnitionBulkSelectorWithCompatibilityShim ,
183180 } ,
184181
185182 /// Send an ignition command for a single target port (only valid if the SP
186183 /// is an ignition controller).
187184 IgnitionCommand {
188- target : u8 ,
185+ // We can't use the compatibility shim here because it has an optional
186+ // positional argument, which can't be followed by `command`
187+ #[ clap( flatten) ]
188+ sel : IgnitionSingleSelector ,
189189 #[ clap(
190190 help = "'power-on', 'power-off', or 'power-reset'" ,
191191 value_parser = ignition_command_from_str,
@@ -196,21 +196,17 @@ enum Command {
196196 /// Get bulk ignition link events (only valid if the SP is an ignition
197197 /// controller).
198198 IgnitionLinkEvents {
199- #[ clap(
200- help = "integer of a target, or 'all' for all targets" ,
201- value_parser = IgnitionLinkEventsTarget :: parse,
202- ) ]
203- target : IgnitionLinkEventsTarget ,
199+ #[ clap( flatten) ]
200+ sel : IgnitionBulkSelectorWithCompatibilityShim ,
204201 } ,
205202
206203 /// Clear all ignition link events (only valid if the SP is an ignition
207204 /// controller).
208205 ClearIgnitionLinkEvents {
209- #[ clap(
210- help = "integer of a target, or 'all' for all targets" ,
211- value_parser = IgnitionLinkEventsTarget :: parse,
212- ) ]
213- target : IgnitionLinkEventsTarget ,
206+ // We can't use the compatibility shim here because it has an optional
207+ // positional argument, which can't be followed by `transceiver_select`
208+ #[ clap( flatten) ]
209+ sel : IgnitionBulkSelector ,
214210 #[ clap(
215211 help = "'controller', 'target-link0', 'target-link1', or 'all'" ,
216212 value_parser = IgnitionLinkEventsTransceiverSelect :: parse,
@@ -643,6 +639,223 @@ impl std::fmt::Display for CfpaSlot {
643639 }
644640}
645641
642+ #[ derive( Copy , Clone , Debug , clap:: Args ) ]
643+ pub struct IgnitionSelector {
644+ /// Ignition target
645+ #[ clap( short, long) ]
646+ target : Option < u8 > ,
647+
648+ /// Sled cubby (0-31)
649+ #[ clap( short, long, conflicts_with_all = [ "target" ] ) ]
650+ cubby : Option < u8 > ,
651+
652+ /// Sidecar ('local' or 'peer')
653+ #[ clap(
654+ short, long, conflicts_with_all = [ "target" , "cubby" ] ,
655+ value_parser = parse_sidecar_selector
656+ ) ]
657+ sidecar : Option < SidecarSelector > ,
658+
659+ /// PSC index (0-1)
660+ #[ clap( short, long, conflicts_with_all = [ "target" , "cubby" , "sidecar" ] ) ]
661+ psc : Option < u8 > ,
662+ }
663+
664+ #[ derive( Clone , Debug , clap:: Args ) ]
665+ #[ clap( group( clap:: ArgGroup :: new( "select" )
666+ . required( true )
667+ . args( & [ "target" , "cubby" , "sidecar" , "psc" ] ) )
668+ ) ]
669+ pub struct IgnitionSingleSelector {
670+ #[ clap( flatten) ]
671+ sel : IgnitionSelector ,
672+ }
673+
674+ impl IgnitionSingleSelector {
675+ fn get_target ( & self , log : & Logger ) -> Result < u8 > {
676+ // presence of at least one is enforced by clap
677+ Ok ( self . sel . get_target ( log) ?. unwrap ( ) )
678+ }
679+ }
680+
681+ impl IgnitionSelector {
682+ /// Decodes CLI arguments to an ignition target
683+ fn get_target ( & self , log : & Logger ) -> Result < Option < u8 > > {
684+ // At this point, we assume that the various `Option` values are
685+ // mutually exclusive (enforced by `clap`).
686+ let t = if let Some ( t) = self . target {
687+ t
688+ } else if let Some ( c) = self . cubby {
689+ // See RFD 144 § 6.1 (Switch Port Map) for this mapping
690+ let t = match c {
691+ 0 => 14 ,
692+ 1 => 30 ,
693+ 2 => 15 ,
694+ 3 => 31 ,
695+ 4 => 13 ,
696+ 5 => 29 ,
697+ 6 => 12 ,
698+ 7 => 28 ,
699+ 8 => 10 ,
700+ 9 => 26 ,
701+ 10 => 11 ,
702+ 11 => 27 ,
703+ 12 => 9 ,
704+ 13 => 25 ,
705+ 14 => 8 ,
706+ 15 => 24 ,
707+ 16 => 4 ,
708+ 17 => 20 ,
709+ 18 => 5 ,
710+ 19 => 21 ,
711+ 20 => 7 ,
712+ 22 => 6 ,
713+ 24 => 0 ,
714+ 25 => 16 ,
715+ 26 => 1 ,
716+ 27 => 17 ,
717+ 28 => 3 ,
718+ 29 => 19 ,
719+ 30 => 2 ,
720+ 31 => 18 ,
721+ i => bail ! ( "cubby must be in the range 0-31, not {i}" ) ,
722+ } ;
723+ debug ! ( log, "decoded cubby {c} => target {t}" ) ;
724+ t
725+ } else if let Some ( s) = self . sidecar {
726+ let t = match s {
727+ SidecarSelector :: Peer => 34 ,
728+ SidecarSelector :: Local => 35 ,
729+ } ;
730+ debug ! ( log, "decoded {s:?} => target {t}" ) ;
731+ t
732+ } else if let Some ( p) = self . psc {
733+ let t = match p {
734+ 0 => 32 ,
735+ 1 => 33 ,
736+ i => bail ! ( "psc must be in the range 0-1, not {i}" ) ,
737+ } ;
738+ debug ! ( log, "decoded psc {p} => target {t}" ) ;
739+ t
740+ } else {
741+ return Ok ( None ) ;
742+ } ;
743+ Ok ( Some ( t) )
744+ }
745+ }
746+
747+ /// `IgnitionSelector` that also supports `--all`
748+ #[ derive( Clone , Debug , clap:: Args ) ]
749+ #[ clap( group( clap:: ArgGroup :: new( "select" )
750+ . required( true )
751+ . args( & [ "target" , "cubby" , "sidecar" , "psc" , "all" ] ) )
752+ ) ]
753+ pub struct IgnitionBulkSelector {
754+ #[ clap( flatten) ]
755+ sel : IgnitionSelector ,
756+
757+ /// All targets
758+ #[ clap( short, long) ]
759+ all : bool ,
760+ }
761+
762+ impl IgnitionBulkSelector {
763+ fn get_targets ( & self , log : & Logger ) -> Result < IgnitionBulkTargets > {
764+ // Delegate to the fancier representation
765+ IgnitionBulkSelectorWithCompatibilityShim {
766+ sel : self . sel ,
767+ all : self . all ,
768+ compat : None ,
769+ }
770+ . get_targets ( log)
771+ }
772+ }
773+
774+ /// `IgnitionBulkSelector` that also supports a positional argument
775+ ///
776+ /// This is for backwards compatibility. However, it can't be used in
777+ /// combination with later positional arguments, because optional positional
778+ /// arguments are only allowed at the end of an argument list.
779+ #[ derive( Clone , Debug , clap:: Args ) ]
780+ #[ clap( group( clap:: ArgGroup :: new( "select" )
781+ . required( true )
782+ . args( & [ "target" , "cubby" , "sidecar" , "psc" , "all" , "compat" ] ) )
783+ ) ]
784+ pub struct IgnitionBulkSelectorWithCompatibilityShim {
785+ #[ clap( flatten) ]
786+ sel : IgnitionSelector ,
787+
788+ /// All targets
789+ #[ clap( short, long) ]
790+ all : bool ,
791+
792+ /// 'all' or a target number
793+ /// (deprecated, present for backwards-compatibility)
794+ #[ clap( value_parser = parse_bulk_targets_shim) ]
795+ compat : Option < IgnitionBulkTargets > ,
796+ }
797+
798+ impl IgnitionBulkSelectorWithCompatibilityShim {
799+ fn get_targets ( & self , log : & Logger ) -> Result < IgnitionBulkTargets > {
800+ match ( self . sel . get_target ( log) ?, self . all , self . compat ) {
801+ ( Some ( _) , true , _) | ( _, true , Some ( _) ) => {
802+ bail ! ( "cannot specify a target and `--all`" )
803+ }
804+ ( Some ( _) , _, Some ( _) ) => {
805+ bail ! (
806+ "cannot specify a target with both flags \
807+ and positional arguments"
808+ )
809+ }
810+ ( Some ( target) , false , None ) => {
811+ Ok ( IgnitionBulkTargets :: Single ( target) )
812+ }
813+ ( None , false , Some ( t) ) => {
814+ match t {
815+ IgnitionBulkTargets :: All => warn ! (
816+ log,
817+ "positional `all` argument is deprecated, \
818+ please switch to `--all`"
819+ ) ,
820+ IgnitionBulkTargets :: Single ( ..) => warn ! (
821+ log,
822+ "positional target argument is deprecated, \
823+ please switch to `--target`, `--cubby`, \
824+ `--sidecar`, or `--psc`"
825+ ) ,
826+ }
827+ Ok ( t)
828+ }
829+ ( None , true , None ) => Ok ( IgnitionBulkTargets :: All ) ,
830+ ( None , false , None ) => {
831+ // enforced by clap
832+ panic ! ( "must specify either a target or `--all`" )
833+ }
834+ }
835+ }
836+ }
837+
838+ #[ derive( Copy , Clone , Debug ) ]
839+ enum IgnitionBulkTargets {
840+ Single ( u8 ) ,
841+ All ,
842+ }
843+
844+ #[ derive( Copy , Clone , Debug ) ]
845+ enum SidecarSelector {
846+ Local ,
847+ Peer ,
848+ }
849+
850+ fn parse_sidecar_selector ( s : & str ) -> Result < SidecarSelector > {
851+ let out = match s. to_ascii_lowercase ( ) . as_str ( ) {
852+ "local" => SidecarSelector :: Local ,
853+ "peer" => SidecarSelector :: Peer ,
854+ _ => bail ! ( "invalid sidecar selector '{s}', must be 'local' or 'peer'" ) ,
855+ } ;
856+ Ok ( out)
857+ }
858+
646859impl Command {
647860 // If the user didn't specify a listening port, what should we use? We
648861 // allow this to vary by command so that client commands (most of them) can
@@ -674,19 +887,14 @@ fn parse_sp_component(component: &str) -> Result<SpComponent> {
674887 . map_err ( |_| anyhow ! ( "invalid component name: {component}" ) )
675888}
676889
677- #[ derive( Debug , Clone , Copy ) ]
678- struct IgnitionLinkEventsTarget ( Option < u8 > ) ;
679-
680- impl IgnitionLinkEventsTarget {
681- fn parse ( s : & str ) -> Result < Self > {
682- match s {
683- "all" | "ALL" => Ok ( Self ( None ) ) ,
684- _ => {
685- let target = s
686- . parse ( )
687- . with_context ( || "must be an integer (0..256) or 'all'" ) ?;
688- Ok ( Self ( Some ( target) ) )
689- }
890+ fn parse_bulk_targets_shim ( s : & str ) -> Result < IgnitionBulkTargets > {
891+ match s {
892+ "all" | "ALL" => Ok ( IgnitionBulkTargets :: All ) ,
893+ _ => {
894+ let target = s
895+ . parse ( )
896+ . with_context ( || "must be an integer (0..256) or 'all'" ) ?;
897+ Ok ( IgnitionBulkTargets :: Single ( target) )
690898 }
691899 }
692900}
@@ -1212,17 +1420,20 @@ async fn run_command(
12121420 Ok ( Output :: Lines ( lines) )
12131421 }
12141422 }
1215- Command :: Ignition { target } => {
1423+ Command :: Ignition { sel } => {
12161424 let mut by_target = BTreeMap :: new ( ) ;
1217- if let Some ( target) = target. 0 {
1218- let state = sp. ignition_state ( target) . await ?;
1219- by_target. insert ( usize:: from ( target) , state) ;
1220- } else {
1221- let states = sp. bulk_ignition_state ( ) . await ?;
1222- for ( i, state) in states. into_iter ( ) . enumerate ( ) {
1223- by_target. insert ( i, state) ;
1425+ match sel. get_targets ( & log) ? {
1426+ IgnitionBulkTargets :: Single ( target) => {
1427+ let state = sp. ignition_state ( target) . await ?;
1428+ by_target. insert ( usize:: from ( target) , state) ;
12241429 }
1225- }
1430+ IgnitionBulkTargets :: All => {
1431+ let states = sp. bulk_ignition_state ( ) . await ?;
1432+ for ( i, state) in states. into_iter ( ) . enumerate ( ) {
1433+ by_target. insert ( i, state) ;
1434+ }
1435+ }
1436+ } ;
12261437 if json {
12271438 Ok ( Output :: Json ( serde_json:: to_value ( by_target) . unwrap ( ) ) )
12281439 } else {
@@ -1233,7 +1444,8 @@ async fn run_command(
12331444 Ok ( Output :: Lines ( lines) )
12341445 }
12351446 }
1236- Command :: IgnitionCommand { target, command } => {
1447+ Command :: IgnitionCommand { sel, command } => {
1448+ let target = sel. get_target ( & log) ?;
12371449 sp. ignition_command ( target, command) . await ?;
12381450 info ! ( log, "ignition command {command:?} send to target {target}" ) ;
12391451 if json {
@@ -1244,15 +1456,18 @@ async fn run_command(
12441456 ) ] ) )
12451457 }
12461458 }
1247- Command :: IgnitionLinkEvents { target } => {
1459+ Command :: IgnitionLinkEvents { sel } => {
12481460 let mut by_target = BTreeMap :: new ( ) ;
1249- if let Some ( target) = target. 0 {
1250- let events = sp. ignition_link_events ( target) . await ?;
1251- by_target. insert ( usize:: from ( target) , events) ;
1252- } else {
1253- let events = sp. bulk_ignition_link_events ( ) . await ?;
1254- for ( i, events) in events. into_iter ( ) . enumerate ( ) {
1255- by_target. insert ( i, events) ;
1461+ match sel. get_targets ( & log) ? {
1462+ IgnitionBulkTargets :: Single ( target) => {
1463+ let events = sp. ignition_link_events ( target) . await ?;
1464+ by_target. insert ( usize:: from ( target) , events) ;
1465+ }
1466+ IgnitionBulkTargets :: All => {
1467+ let events = sp. bulk_ignition_link_events ( ) . await ?;
1468+ for ( i, events) in events. into_iter ( ) . enumerate ( ) {
1469+ by_target. insert ( i, events) ;
1470+ }
12561471 }
12571472 }
12581473 if json {
@@ -1265,9 +1480,12 @@ async fn run_command(
12651480 Ok ( Output :: Lines ( lines) )
12661481 }
12671482 }
1268- Command :: ClearIgnitionLinkEvents { target, transceiver_select } => {
1269- sp. clear_ignition_link_events ( target. 0 , transceiver_select. 0 )
1270- . await ?;
1483+ Command :: ClearIgnitionLinkEvents { sel, transceiver_select } => {
1484+ let target = match sel. get_targets ( & log) ? {
1485+ IgnitionBulkTargets :: Single ( t) => Some ( t) ,
1486+ IgnitionBulkTargets :: All => None ,
1487+ } ;
1488+ sp. clear_ignition_link_events ( target, transceiver_select. 0 ) . await ?;
12711489 info ! ( log, "ignition link events cleared" ) ;
12721490 if json {
12731491 Ok ( Output :: Json ( json ! ( { "ack" : "cleared" } ) ) )
0 commit comments