Skip to content

Commit 25e2f8d

Browse files
[multicast] add omdb mcast commands for groups, members, pools
This PR adds omdb commands to inspect multicast state: - `omdb db multicast groups` - list multicast groups with optional state and pool name filters - `omdb db multicast members` - list group members with filters for group-id, group-name, group-ip, state, and sled-id - `omdb db multicast info` - show detailed info for a specific group - `omdb db multicast pools` - list multicast IP pools We also include: - Background task status display for multicast_reconciler - Integration tests for all multicast omdb commands Follows the multicast lifecycle work in #9450.
1 parent 655e602 commit 25e2f8d

File tree

10 files changed

+1329
-14
lines changed

10 files changed

+1329
-14
lines changed

dev-tools/omdb/src/bin/omdb/db.rs

Lines changed: 642 additions & 0 deletions
Large diffs are not rendered by default.

dev-tools/omdb/src/bin/omdb/nexus.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use nexus_types::internal_api::background::InstanceReincarnationStatus;
6060
use nexus_types::internal_api::background::InstanceUpdaterStatus;
6161
use nexus_types::internal_api::background::InventoryLoadStatus;
6262
use nexus_types::internal_api::background::LookupRegionPortStatus;
63+
use nexus_types::internal_api::background::MulticastGroupReconcilerStatus;
6364
use nexus_types::internal_api::background::ProbeDistributorStatus;
6465
use nexus_types::internal_api::background::ReadOnlyRegionReplacementStartStatus;
6566
use nexus_types::internal_api::background::RegionReplacementDriverStatus;
@@ -1191,6 +1192,9 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {
11911192
"lookup_region_port" => {
11921193
print_task_lookup_region_port(details);
11931194
}
1195+
"multicast_reconciler" => {
1196+
print_task_multicast_reconciler(details);
1197+
}
11941198
"phantom_disks" => {
11951199
print_task_phantom_disks(details);
11961200
}
@@ -2107,6 +2111,76 @@ fn print_task_lookup_region_port(details: &serde_json::Value) {
21072111
}
21082112
}
21092113

2114+
fn print_task_multicast_reconciler(details: &serde_json::Value) {
2115+
let status = match serde_json::from_value::<MulticastGroupReconcilerStatus>(
2116+
details.clone(),
2117+
) {
2118+
Err(error) => {
2119+
eprintln!(
2120+
"warning: failed to interpret task details: {error:?}: {details:?}"
2121+
);
2122+
return;
2123+
}
2124+
Ok(status) => status,
2125+
};
2126+
2127+
if status.disabled {
2128+
println!(" multicast feature is disabled");
2129+
return;
2130+
}
2131+
2132+
const GROUPS_CREATED: &str = "groups created (Creating->Active):";
2133+
const GROUPS_DELETED: &str = "groups deleted (cleanup):";
2134+
const GROUPS_VERIFIED: &str = "groups verified (Active):";
2135+
const EMPTY_GROUPS_MARKED: &str = "empty groups marked for deletion:";
2136+
const MEMBERS_PROCESSED: &str = "members processed:";
2137+
const MEMBERS_DELETED: &str = "members deleted:";
2138+
const WIDTH: usize = const_max_len(&[
2139+
GROUPS_CREATED,
2140+
GROUPS_DELETED,
2141+
GROUPS_VERIFIED,
2142+
EMPTY_GROUPS_MARKED,
2143+
MEMBERS_PROCESSED,
2144+
MEMBERS_DELETED,
2145+
]) + 1;
2146+
const NUM_WIDTH: usize = 3;
2147+
2148+
if !status.errors.is_empty() {
2149+
println!(
2150+
" task did not complete successfully! ({} errors)",
2151+
status.errors.len()
2152+
);
2153+
for error in &status.errors {
2154+
println!(" > {error}");
2155+
}
2156+
}
2157+
2158+
println!(
2159+
" {GROUPS_CREATED:<WIDTH$}{:>NUM_WIDTH$}",
2160+
status.groups_created
2161+
);
2162+
println!(
2163+
" {GROUPS_DELETED:<WIDTH$}{:>NUM_WIDTH$}",
2164+
status.groups_deleted
2165+
);
2166+
println!(
2167+
" {GROUPS_VERIFIED:<WIDTH$}{:>NUM_WIDTH$}",
2168+
status.groups_verified
2169+
);
2170+
println!(
2171+
" {EMPTY_GROUPS_MARKED:<WIDTH$}{:>NUM_WIDTH$}",
2172+
status.empty_groups_marked
2173+
);
2174+
println!(
2175+
" {MEMBERS_PROCESSED:<WIDTH$}{:>NUM_WIDTH$}",
2176+
status.members_processed
2177+
);
2178+
println!(
2179+
" {MEMBERS_DELETED:<WIDTH$}{:>NUM_WIDTH$}",
2180+
status.members_deleted
2181+
);
2182+
}
2183+
21102184
fn print_task_phantom_disks(details: &serde_json::Value) {
21112185
#[derive(Deserialize)]
21122186
struct TaskSuccess {

dev-tools/omdb/tests/successes.out

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,36 @@ stderr:
8585
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
8686
note: database schema version matches expected (<redacted database version>)
8787
=============================================
88+
EXECUTING COMMAND: omdb ["db", "multicast", "groups"]
89+
termination: Exited(0)
90+
---------------------------------------------
91+
stdout:
92+
ID NAME STATE MULTICAST_IP UNDERLAY_IP SOURCES VNI CREATED
93+
---------------------------------------------
94+
stderr:
95+
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
96+
note: database schema version matches expected (<redacted database version>)
97+
=============================================
98+
EXECUTING COMMAND: omdb ["db", "multicast", "members"]
99+
termination: Exited(0)
100+
---------------------------------------------
101+
stdout:
102+
ID GROUP_NAME PARENT_ID STATE MULTICAST_IP SLED_ID CREATED
103+
---------------------------------------------
104+
stderr:
105+
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
106+
note: database schema version matches expected (<redacted database version>)
107+
=============================================
108+
EXECUTING COMMAND: omdb ["db", "multicast", "pools"]
109+
termination: Exited(0)
110+
---------------------------------------------
111+
stdout:
112+
no multicast IP pools found
113+
---------------------------------------------
114+
stderr:
115+
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
116+
note: database schema version matches expected (<redacted database version>)
117+
=============================================
88118
EXECUTING COMMAND: omdb ["db", "sleds"]
89119
termination: Exited(0)
90120
---------------------------------------------
@@ -713,7 +743,12 @@ task: "multicast_reconciler"
713743
configured period: every <REDACTED_DURATION>m
714744
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
715745
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
716-
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "empty_groups_marked": Number(0), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})
746+
groups created (Creating->Active): 0
747+
groups deleted (cleanup): 0
748+
groups verified (Active): 0
749+
empty groups marked for deletion: 0
750+
members processed: 0
751+
members deleted: 0
717752

718753
task: "phantom_disks"
719754
configured period: every <REDACTED_DURATION>s
@@ -1281,7 +1316,12 @@ task: "multicast_reconciler"
12811316
configured period: every <REDACTED_DURATION>m
12821317
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
12831318
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
1284-
warning: unknown background task: "multicast_reconciler" (don't know how to interpret details: Object {"disabled": Bool(false), "empty_groups_marked": Number(0), "errors": Array [], "groups_created": Number(0), "groups_deleted": Number(0), "groups_verified": Number(0), "members_deleted": Number(0), "members_processed": Number(0)})
1319+
groups created (Creating->Active): 0
1320+
groups deleted (cleanup): 0
1321+
groups verified (Active): 0
1322+
empty groups marked for deletion: 0
1323+
members processed: 0
1324+
members deleted: 0
12851325

12861326
task: "phantom_disks"
12871327
configured period: every <REDACTED_DURATION>s

dev-tools/omdb/tests/test_all_output.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ async fn test_omdb_usage_errors() {
9696
&["db", "saga"],
9797
&["db", "snapshots"],
9898
&["db", "network"],
99+
&["db", "multicast"],
99100
&["mgs"],
100101
&["nexus"],
101102
&["nexus", "background-tasks"],
@@ -198,6 +199,9 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) {
198199
&["db", "dns", "diff", "external", "2"],
199200
&["db", "dns", "names", "external", "2"],
200201
&["db", "instances"],
202+
&["db", "multicast", "groups"],
203+
&["db", "multicast", "members"],
204+
&["db", "multicast", "pools"],
201205
&["db", "sleds"],
202206
&["db", "sleds", "-F", "discretionary"],
203207
&["mgs", "inventory"],

dev-tools/omdb/tests/usage_errors.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ Commands:
140140
instances Alias to `omdb instance list`
141141
network Print information about the network
142142
migrations Print information about migrations
143+
multicast Print information about multicast groups
143144
snapshots Print information about snapshots
144145
validate Validate the contents of the database
145146
volumes Print information about volumes
@@ -204,6 +205,7 @@ Commands:
204205
instances Alias to `omdb instance list`
205206
network Print information about the network
206207
migrations Print information about migrations
208+
multicast Print information about multicast groups
207209
snapshots Print information about snapshots
208210
validate Validate the contents of the database
209211
volumes Print information about volumes
@@ -845,6 +847,41 @@ Database Options:
845847
--include-deleted whether to include soft-deleted records when enumerating objects
846848
that can be soft-deleted
847849

850+
Safety Options:
851+
-w, --destructive Allow potentially-destructive subcommands
852+
=============================================
853+
EXECUTING COMMAND: omdb ["db", "multicast"]
854+
termination: Exited(2)
855+
---------------------------------------------
856+
stdout:
857+
---------------------------------------------
858+
stderr:
859+
Print information about multicast groups
860+
861+
Usage: omdb db multicast [OPTIONS] <COMMAND>
862+
863+
Commands:
864+
groups List all multicast groups
865+
members List all multicast group members
866+
pools List multicast IP pools and their ranges
867+
info Get info for a multicast group by IP address or name
868+
help Print this message or the help of the given subcommand(s)
869+
870+
Options:
871+
--log-level <LOG_LEVEL> log level filter [env: LOG_LEVEL=] [default: warn]
872+
--color <COLOR> Color output [default: auto] [possible values: auto, always, never]
873+
-h, --help Print help
874+
875+
Connection Options:
876+
--db-url <DB_URL> URL of the database SQL interface [env: OMDB_DB_URL=]
877+
--dns-server <DNS_SERVER> [env: OMDB_DNS_SERVER=]
878+
879+
Database Options:
880+
--fetch-limit <FETCH_LIMIT> limit to apply to queries that fetch rows [env:
881+
OMDB_FETCH_LIMIT=] [default: 500]
882+
--include-deleted whether to include soft-deleted records when enumerating objects
883+
that can be soft-deleted
884+
848885
Safety Options:
849886
-w, --destructive Allow potentially-destructive subcommands
850887
=============================================

nexus/db-model/src/multicast_group.rs

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,24 +120,90 @@ impl_enum_type!(
120120
Left => b"left"
121121
);
122122

123+
impl MulticastGroupState {
124+
pub const ALL_STATES: &'static [Self] =
125+
&[Self::Creating, Self::Active, Self::Deleting, Self::Deleted];
126+
127+
pub fn label(&self) -> &'static str {
128+
match self {
129+
Self::Creating => "Creating",
130+
Self::Active => "Active",
131+
Self::Deleting => "Deleting",
132+
Self::Deleted => "Deleted",
133+
}
134+
}
135+
}
136+
123137
impl std::fmt::Display for MulticastGroupState {
124138
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125-
f.write_str(match self {
126-
MulticastGroupState::Creating => "Creating",
127-
MulticastGroupState::Active => "Active",
128-
MulticastGroupState::Deleting => "Deleting",
129-
MulticastGroupState::Deleted => "Deleted",
130-
})
139+
f.write_str(self.label())
140+
}
141+
}
142+
143+
/// Error returned when parsing a `MulticastGroupState` from a string.
144+
#[derive(Debug)]
145+
pub struct MulticastGroupStateParseError(());
146+
147+
impl std::fmt::Display for MulticastGroupStateParseError {
148+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149+
write!(f, "invalid multicast group state")
150+
}
151+
}
152+
153+
impl std::error::Error for MulticastGroupStateParseError {}
154+
155+
impl std::str::FromStr for MulticastGroupState {
156+
type Err = MulticastGroupStateParseError;
157+
fn from_str(s: &str) -> Result<Self, Self::Err> {
158+
for &v in Self::ALL_STATES {
159+
if s.eq_ignore_ascii_case(v.label()) {
160+
return Ok(v);
161+
}
162+
}
163+
Err(MulticastGroupStateParseError(()))
164+
}
165+
}
166+
167+
impl MulticastGroupMemberState {
168+
pub const ALL_STATES: &'static [Self] =
169+
&[Self::Joining, Self::Joined, Self::Left];
170+
171+
pub fn label(&self) -> &'static str {
172+
match self {
173+
Self::Joining => "Joining",
174+
Self::Joined => "Joined",
175+
Self::Left => "Left",
176+
}
131177
}
132178
}
133179

134180
impl std::fmt::Display for MulticastGroupMemberState {
135181
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136-
f.write_str(match self {
137-
MulticastGroupMemberState::Joining => "Joining",
138-
MulticastGroupMemberState::Joined => "Joined",
139-
MulticastGroupMemberState::Left => "Left",
140-
})
182+
f.write_str(self.label())
183+
}
184+
}
185+
186+
/// Error returned when parsing a `MulticastGroupMemberState` from a string.
187+
#[derive(Debug)]
188+
pub struct MulticastGroupMemberStateParseError(());
189+
190+
impl std::fmt::Display for MulticastGroupMemberStateParseError {
191+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192+
write!(f, "invalid multicast group member state")
193+
}
194+
}
195+
196+
impl std::error::Error for MulticastGroupMemberStateParseError {}
197+
198+
impl std::str::FromStr for MulticastGroupMemberState {
199+
type Err = MulticastGroupMemberStateParseError;
200+
fn from_str(s: &str) -> Result<Self, Self::Err> {
201+
for &v in Self::ALL_STATES {
202+
if s.eq_ignore_ascii_case(v.label()) {
203+
return Ok(v);
204+
}
205+
}
206+
Err(MulticastGroupMemberStateParseError(()))
141207
}
142208
}
143209

nexus/db-schema/src/schema.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2811,8 +2811,12 @@ table! {
28112811
}
28122812
}
28132813

2814-
// Allow multicast tables to appear together for NOT EXISTS subqueries
2814+
// Allow multicast tables to appear together for JOINs and NOT EXISTS subqueries
28152815
allow_tables_to_appear_in_same_query!(multicast_group, multicast_group_member);
2816+
allow_tables_to_appear_in_same_query!(
2817+
multicast_group,
2818+
underlay_multicast_group
2819+
);
28162820

28172821
allow_tables_to_appear_in_same_query!(user_data_export, snapshot, image);
28182822

nexus/test-utils/src/nexus_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::starter::PopulateCrdb;
1010
use crate::starter::setup_with_config_impl;
1111
#[cfg(feature = "omicron-dev")]
1212
use anyhow::Context;
13+
#[cfg(feature = "omicron-dev")]
1314
use anyhow::Result;
1415
use camino::Utf8Path;
1516
use camino::Utf8PathBuf;

nexus/tests/integration_tests/multicast/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ mod failures;
6161
mod groups;
6262
mod instances;
6363
mod networking_integration;
64+
mod omdb;
6465

6566
// Timeout constants for test operations
6667
const POLL_INTERVAL: Duration = Duration::from_millis(80);

0 commit comments

Comments
 (0)