Skip to content

Commit e51e8ff

Browse files
authored
Improve ignition target selection in faux-mgs (#467)
Ignition commands in faux-mgs require specifying an "ignition target", which has to be looked up in RFD 144 § Switch port map. This isn't the same as cubby number, which is annoying. This PR adds `--target`, `--cubby`, `--sidecar`, and `--psc` selector arguments to specify an ignition target at a high level. The positional argument is still supported when possible (`ignition` and `ignition-link-events`), but is removed from `clear-ignition-link-events` and `ignition-command` because of Clap limitations.
1 parent 83e2165 commit e51e8ff

File tree

1 file changed

+270
-52
lines changed

1 file changed

+270
-52
lines changed

faux-mgs/src/main.rs

Lines changed: 270 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
646859
impl 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

Comments
 (0)