Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
642 changes: 642 additions & 0 deletions dev-tools/omdb/src/bin/omdb/db.rs

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions dev-tools/omdb/src/bin/omdb/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use nexus_types::internal_api::background::InstanceReincarnationStatus;
use nexus_types::internal_api::background::InstanceUpdaterStatus;
use nexus_types::internal_api::background::InventoryLoadStatus;
use nexus_types::internal_api::background::LookupRegionPortStatus;
use nexus_types::internal_api::background::MulticastGroupReconcilerStatus;
use nexus_types::internal_api::background::ProbeDistributorStatus;
use nexus_types::internal_api::background::ReadOnlyRegionReplacementStartStatus;
use nexus_types::internal_api::background::RegionReplacementDriverStatus;
Expand Down Expand Up @@ -1193,6 +1194,9 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {
"lookup_region_port" => {
print_task_lookup_region_port(details);
}
"multicast_reconciler" => {
print_task_multicast_reconciler(details);
}
"phantom_disks" => {
print_task_phantom_disks(details);
}
Expand Down Expand Up @@ -2109,6 +2113,76 @@ fn print_task_lookup_region_port(details: &serde_json::Value) {
}
}

fn print_task_multicast_reconciler(details: &serde_json::Value) {
let status = match serde_json::from_value::<MulticastGroupReconcilerStatus>(
details.clone(),
) {
Err(error) => {
eprintln!(
"warning: failed to interpret task details: {error:?}: {details:?}"
);
return;
}
Ok(status) => status,
};

if status.disabled {
println!(" multicast feature is disabled");
return;
}

const GROUPS_CREATED: &str = "groups created (Creating->Active):";
const GROUPS_DELETED: &str = "groups deleted (cleanup):";
const GROUPS_VERIFIED: &str = "groups verified (Active):";
const EMPTY_GROUPS_MARKED: &str = "empty groups marked for deletion:";
const MEMBERS_PROCESSED: &str = "members processed:";
const MEMBERS_DELETED: &str = "members deleted:";
const WIDTH: usize = const_max_len(&[
GROUPS_CREATED,
GROUPS_DELETED,
GROUPS_VERIFIED,
EMPTY_GROUPS_MARKED,
MEMBERS_PROCESSED,
MEMBERS_DELETED,
]) + 1;
const NUM_WIDTH: usize = 3;

if !status.errors.is_empty() {
println!(
" task did not complete successfully! ({} errors)",
status.errors.len()
);
for error in &status.errors {
println!(" > {error}");
}
}

println!(
" {GROUPS_CREATED:<WIDTH$}{:>NUM_WIDTH$}",
status.groups_created
);
println!(
" {GROUPS_DELETED:<WIDTH$}{:>NUM_WIDTH$}",
status.groups_deleted
);
println!(
" {GROUPS_VERIFIED:<WIDTH$}{:>NUM_WIDTH$}",
status.groups_verified
);
println!(
" {EMPTY_GROUPS_MARKED:<WIDTH$}{:>NUM_WIDTH$}",
status.empty_groups_marked
);
println!(
" {MEMBERS_PROCESSED:<WIDTH$}{:>NUM_WIDTH$}",
status.members_processed
);
println!(
" {MEMBERS_DELETED:<WIDTH$}{:>NUM_WIDTH$}",
status.members_deleted
);
}

fn print_task_phantom_disks(details: &serde_json::Value) {
#[derive(Deserialize)]
struct TaskSuccess {
Expand Down
44 changes: 42 additions & 2 deletions dev-tools/omdb/tests/successes.out
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ stderr:
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
note: database schema version matches expected (<redacted database version>)
=============================================
EXECUTING COMMAND: omdb ["db", "multicast", "groups"]
termination: Exited(0)
---------------------------------------------
stdout:
ID NAME STATE MULTICAST_IP UNDERLAY_IP SOURCES VNI CREATED
---------------------------------------------
stderr:
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
note: database schema version matches expected (<redacted database version>)
=============================================
EXECUTING COMMAND: omdb ["db", "multicast", "members"]
termination: Exited(0)
---------------------------------------------
stdout:
ID GROUP_NAME PARENT_ID STATE MULTICAST_IP SLED_ID CREATED
---------------------------------------------
stderr:
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
note: database schema version matches expected (<redacted database version>)
=============================================
EXECUTING COMMAND: omdb ["db", "multicast", "pools"]
termination: Exited(0)
---------------------------------------------
stdout:
no multicast IP pools found
---------------------------------------------
stderr:
note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable
note: database schema version matches expected (<redacted database version>)
=============================================
EXECUTING COMMAND: omdb ["db", "sleds"]
termination: Exited(0)
---------------------------------------------
Expand Down Expand Up @@ -713,7 +743,12 @@ task: "multicast_reconciler"
configured period: every <REDACTED_DURATION>m
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
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)})
groups created (Creating->Active): 0
groups deleted (cleanup): 0
groups verified (Active): 0
empty groups marked for deletion: 0
members processed: 0
members deleted: 0

task: "phantom_disks"
configured period: every <REDACTED_DURATION>s
Expand Down Expand Up @@ -1281,7 +1316,12 @@ task: "multicast_reconciler"
configured period: every <REDACTED_DURATION>m
last completed activation: <REDACTED ITERATIONS>, triggered by <TRIGGERED_BY_REDACTED>
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
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)})
groups created (Creating->Active): 0
groups deleted (cleanup): 0
groups verified (Active): 0
empty groups marked for deletion: 0
members processed: 0
members deleted: 0

task: "phantom_disks"
configured period: every <REDACTED_DURATION>s
Expand Down
4 changes: 4 additions & 0 deletions dev-tools/omdb/tests/test_all_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ async fn test_omdb_usage_errors() {
&["db", "saga"],
&["db", "snapshots"],
&["db", "network"],
&["db", "multicast"],
&["mgs"],
&["nexus"],
&["nexus", "background-tasks"],
Expand Down Expand Up @@ -198,6 +199,9 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) {
&["db", "dns", "diff", "external", "2"],
&["db", "dns", "names", "external", "2"],
&["db", "instances"],
&["db", "multicast", "groups"],
&["db", "multicast", "members"],
&["db", "multicast", "pools"],
&["db", "sleds"],
&["db", "sleds", "-F", "discretionary"],
&["mgs", "inventory"],
Expand Down
37 changes: 37 additions & 0 deletions dev-tools/omdb/tests/usage_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ Commands:
instances Alias to `omdb instance list`
network Print information about the network
migrations Print information about migrations
multicast Print information about multicast groups
snapshots Print information about snapshots
validate Validate the contents of the database
volumes Print information about volumes
Expand Down Expand Up @@ -205,6 +206,7 @@ Commands:
instances Alias to `omdb instance list`
network Print information about the network
migrations Print information about migrations
multicast Print information about multicast groups
snapshots Print information about snapshots
validate Validate the contents of the database
volumes Print information about volumes
Expand Down Expand Up @@ -847,6 +849,41 @@ Database Options:
--include-deleted whether to include soft-deleted records when enumerating objects
that can be soft-deleted

Safety Options:
-w, --destructive Allow potentially-destructive subcommands
=============================================
EXECUTING COMMAND: omdb ["db", "multicast"]
termination: Exited(2)
---------------------------------------------
stdout:
---------------------------------------------
stderr:
Print information about multicast groups

Usage: omdb db multicast [OPTIONS] <COMMAND>

Commands:
groups List all multicast groups
members List all multicast group members
pools List multicast IP pools and their ranges
info Get info for a multicast group by IP address or name
help Print this message or the help of the given subcommand(s)

Options:
--log-level <LOG_LEVEL> log level filter [env: LOG_LEVEL=] [default: warn]
--color <COLOR> Color output [default: auto] [possible values: auto, always, never]
-h, --help Print help

Connection Options:
--db-url <DB_URL> URL of the database SQL interface [env: OMDB_DB_URL=]
--dns-server <DNS_SERVER> [env: OMDB_DNS_SERVER=]

Database Options:
--fetch-limit <FETCH_LIMIT> limit to apply to queries that fetch rows [env:
OMDB_FETCH_LIMIT=] [default: 500]
--include-deleted whether to include soft-deleted records when enumerating objects
that can be soft-deleted

Safety Options:
-w, --destructive Allow potentially-destructive subcommands
=============================================
Expand Down
88 changes: 77 additions & 11 deletions nexus/db-model/src/multicast_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,24 +120,90 @@ impl_enum_type!(
Left => b"left"
);

impl MulticastGroupState {
pub const ALL_STATES: &'static [Self] =
&[Self::Creating, Self::Active, Self::Deleting, Self::Deleted];

pub fn label(&self) -> &'static str {
match self {
Self::Creating => "Creating",
Self::Active => "Active",
Self::Deleting => "Deleting",
Self::Deleted => "Deleted",
}
}
}

impl std::fmt::Display for MulticastGroupState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
MulticastGroupState::Creating => "Creating",
MulticastGroupState::Active => "Active",
MulticastGroupState::Deleting => "Deleting",
MulticastGroupState::Deleted => "Deleted",
})
f.write_str(self.label())
}
}

/// Error returned when parsing a `MulticastGroupState` from a string.
#[derive(Debug)]
pub struct MulticastGroupStateParseError(());

impl std::fmt::Display for MulticastGroupStateParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid multicast group state")
}
}

impl std::error::Error for MulticastGroupStateParseError {}

impl std::str::FromStr for MulticastGroupState {
type Err = MulticastGroupStateParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for &v in Self::ALL_STATES {
if s.eq_ignore_ascii_case(v.label()) {
return Ok(v);
}
}
Err(MulticastGroupStateParseError(()))
}
}

impl MulticastGroupMemberState {
pub const ALL_STATES: &'static [Self] =
&[Self::Joining, Self::Joined, Self::Left];

pub fn label(&self) -> &'static str {
match self {
Self::Joining => "Joining",
Self::Joined => "Joined",
Self::Left => "Left",
}
}
}

impl std::fmt::Display for MulticastGroupMemberState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
MulticastGroupMemberState::Joining => "Joining",
MulticastGroupMemberState::Joined => "Joined",
MulticastGroupMemberState::Left => "Left",
})
f.write_str(self.label())
}
}

/// Error returned when parsing a `MulticastGroupMemberState` from a string.
#[derive(Debug)]
pub struct MulticastGroupMemberStateParseError(());

impl std::fmt::Display for MulticastGroupMemberStateParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid multicast group member state")
}
}

impl std::error::Error for MulticastGroupMemberStateParseError {}

impl std::str::FromStr for MulticastGroupMemberState {
type Err = MulticastGroupMemberStateParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for &v in Self::ALL_STATES {
if s.eq_ignore_ascii_case(v.label()) {
return Ok(v);
}
}
Err(MulticastGroupMemberStateParseError(()))
}
}

Expand Down
6 changes: 5 additions & 1 deletion nexus/db-schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2816,8 +2816,12 @@ table! {
}
}

// Allow multicast tables to appear together for NOT EXISTS subqueries
// Allow multicast tables to appear together for JOINs and NOT EXISTS subqueries
allow_tables_to_appear_in_same_query!(multicast_group, multicast_group_member);
allow_tables_to_appear_in_same_query!(
multicast_group,
underlay_multicast_group
);

allow_tables_to_appear_in_same_query!(user_data_export, snapshot, image);

Expand Down
1 change: 1 addition & 0 deletions nexus/tests/integration_tests/multicast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mod failures;
mod groups;
mod instances;
mod networking_integration;
mod omdb;

// Timeout constants for test operations
const POLL_INTERVAL: Duration = Duration::from_millis(80);
Expand Down
Loading
Loading