Skip to content

Commit e5094dc

Browse files
authored
[reconfigurator] use tabled to display blueprints and diffs (#5270)
While developing #5238, I noticed that the output was getting significantly busier and less aligned. I decided to prototype out using `tabled` to display outputs, and I really liked the results. Examples that cover all of the cases are included in the PR. In the future I'd also like to add color support on the CLI, and expand it to inventory and `omdb` (it's similar except it doesn't have the zone policy table). Some other changes that are bundled into this PR: * Sort by (zone type, zone ID) rather than zone ID, to keep zones of the same type grouped together. * Moved unchanged data to the top to allow users to see less scrollback. * Moved metadata to the bottom for the same reason. * Add information about the zone config being changed. * Change `Blueprint::diff_sleds` and `Blueprint::diff_sleds_from_collection` to `Blueprint::diff_since_blueprint` and `diff_since_collection` recently. * Reordered `diff_since_blueprint`'s arguments so that `self` is after and the argument is before, to align with `diff_since_collection`. (I found that surprising!) * Renamed the diff type from `OmicronZonesDiff` to `BlueprintDiff`, since it's going to contain a lot more than zones. * Return an error from the diff methods, specifically if the before and after have the same zone ID but different types. Depends on #5238 and #5341.
1 parent 7484017 commit e5094dc

File tree

20 files changed

+2123
-612
lines changed

20 files changed

+2123
-612
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/sled-agent-client/src/lib.rs

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use anyhow::Context;
88
use async_trait::async_trait;
99
use omicron_common::api::internal::shared::NetworkInterface;
1010
use std::convert::TryFrom;
11+
use std::fmt;
1112
use std::hash::Hash;
1213
use std::net::IpAddr;
1314
use std::net::SocketAddr;
@@ -56,25 +57,65 @@ impl Eq for types::OmicronZoneConfig {}
5657
impl Eq for types::OmicronZoneType {}
5758
impl Eq for types::OmicronZoneDataset {}
5859

60+
/// Like [`types::OmicronZoneType`], but without any associated data.
61+
///
62+
/// We have a few enums of this form floating around. This particular one is
63+
/// meant to correspond exactly 1:1 with `OmicronZoneType`.
64+
///
65+
/// The [`fmt::Display`] impl for this type is a human-readable label, meant
66+
/// for testing and reporting.
67+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
68+
pub enum ZoneKind {
69+
BoundaryNtp,
70+
Clickhouse,
71+
ClickhouseKeeper,
72+
CockroachDb,
73+
Crucible,
74+
CruciblePantry,
75+
ExternalDns,
76+
InternalDns,
77+
InternalNtp,
78+
Nexus,
79+
Oximeter,
80+
}
81+
82+
impl fmt::Display for ZoneKind {
83+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84+
match self {
85+
ZoneKind::BoundaryNtp => write!(f, "boundary_ntp"),
86+
ZoneKind::Clickhouse => write!(f, "clickhouse"),
87+
ZoneKind::ClickhouseKeeper => write!(f, "clickhouse_keeper"),
88+
ZoneKind::CockroachDb => write!(f, "cockroach_db"),
89+
ZoneKind::Crucible => write!(f, "crucible"),
90+
ZoneKind::CruciblePantry => write!(f, "crucible_pantry"),
91+
ZoneKind::ExternalDns => write!(f, "external_dns"),
92+
ZoneKind::InternalDns => write!(f, "internal_dns"),
93+
ZoneKind::InternalNtp => write!(f, "internal_ntp"),
94+
ZoneKind::Nexus => write!(f, "nexus"),
95+
ZoneKind::Oximeter => write!(f, "oximeter"),
96+
}
97+
}
98+
}
99+
59100
impl types::OmicronZoneType {
60-
/// Human-readable label describing what kind of zone this is
61-
///
62-
/// This is just use for testing and reporting.
63-
pub fn label(&self) -> impl std::fmt::Display {
101+
/// Returns the [`ZoneKind`] corresponding to this variant.
102+
pub fn kind(&self) -> ZoneKind {
64103
match self {
65-
types::OmicronZoneType::BoundaryNtp { .. } => "boundary_ntp",
66-
types::OmicronZoneType::Clickhouse { .. } => "clickhouse",
104+
types::OmicronZoneType::BoundaryNtp { .. } => ZoneKind::BoundaryNtp,
105+
types::OmicronZoneType::Clickhouse { .. } => ZoneKind::Clickhouse,
67106
types::OmicronZoneType::ClickhouseKeeper { .. } => {
68-
"clickhouse_keeper"
107+
ZoneKind::ClickhouseKeeper
108+
}
109+
types::OmicronZoneType::CockroachDb { .. } => ZoneKind::CockroachDb,
110+
types::OmicronZoneType::Crucible { .. } => ZoneKind::Crucible,
111+
types::OmicronZoneType::CruciblePantry { .. } => {
112+
ZoneKind::CruciblePantry
69113
}
70-
types::OmicronZoneType::CockroachDb { .. } => "cockroach_db",
71-
types::OmicronZoneType::Crucible { .. } => "crucible",
72-
types::OmicronZoneType::CruciblePantry { .. } => "crucible_pantry",
73-
types::OmicronZoneType::ExternalDns { .. } => "external_dns",
74-
types::OmicronZoneType::InternalDns { .. } => "internal_dns",
75-
types::OmicronZoneType::InternalNtp { .. } => "internal_ntp",
76-
types::OmicronZoneType::Nexus { .. } => "nexus",
77-
types::OmicronZoneType::Oximeter { .. } => "oximeter",
114+
types::OmicronZoneType::ExternalDns { .. } => ZoneKind::ExternalDns,
115+
types::OmicronZoneType::InternalDns { .. } => ZoneKind::InternalDns,
116+
types::OmicronZoneType::InternalNtp { .. } => ZoneKind::InternalNtp,
117+
types::OmicronZoneType::Nexus { .. } => ZoneKind::Nexus,
118+
types::OmicronZoneType::Oximeter { .. } => ZoneKind::Oximeter,
78119
}
79120
}
80121

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3244,7 +3244,7 @@ fn inv_collection_print_sleds(collection: &Collection) {
32443244

32453245
println!(" ZONES FOUND");
32463246
for z in &zones.zones.zones {
3247-
println!(" zone {} (type {})", z.id, z.zone_type.label());
3247+
println!(" zone {} (type {})", z.id, z.zone_type.kind());
32483248
}
32493249
} else {
32503250
println!(" warning: no zone information found");

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,8 @@ async fn cmd_nexus_blueprints_diff(
972972
let b2 = client.blueprint_view(&args.blueprint2_id).await.with_context(
973973
|| format!("fetching blueprint {}", args.blueprint2_id),
974974
)?;
975-
println!("{}", b1.diff_sleds(&b2).display());
975+
let diff = b2.diff_since_blueprint(&b1).context("diffing blueprints")?;
976+
println!("{}", diff.display());
976977
Ok(())
977978
}
978979

dev-tools/reconfigurator-cli/src/main.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,8 +669,10 @@ fn cmd_blueprint_diff(
669669
.get(&blueprint2_id)
670670
.ok_or_else(|| anyhow!("no such blueprint: {}", blueprint2_id))?;
671671

672-
let sled_diff = blueprint1.diff_sleds(&blueprint2).display().to_string();
673-
swriteln!(rv, "{}", sled_diff);
672+
let sled_diff = blueprint2
673+
.diff_since_blueprint(&blueprint1)
674+
.context("failed to diff blueprints")?;
675+
swriteln!(rv, "{}", sled_diff.display());
674676

675677
// Diff'ing DNS is a little trickier. First, compute what DNS should be for
676678
// each blueprint. To do that we need to construct a list of sleds suitable
@@ -795,7 +797,9 @@ fn cmd_blueprint_diff_inventory(
795797
.get(&blueprint_id)
796798
.ok_or_else(|| anyhow!("no such blueprint: {}", blueprint_id))?;
797799

798-
let diff = blueprint.diff_sleds_from_collection(&collection);
800+
let diff = blueprint
801+
.diff_since_collection(&collection)
802+
.context("failed to diff blueprint from inventory collection")?;
799803
Ok(Some(diff.display().to_string()))
800804
}
801805

nexus/db-queries/src/db/datastore/deployment.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ impl DataStore {
486486
}
487487
}
488488

489+
// Sort all zones to match what blueprint builders do.
490+
for (_, zones_config) in blueprint_zones.iter_mut() {
491+
zones_config.sort();
492+
}
493+
489494
bail_unless!(
490495
omicron_zone_nics.is_empty(),
491496
"found extra Omicron zone NICs: {:?}",
@@ -1185,6 +1190,7 @@ mod tests {
11851190
use omicron_common::address::Ipv6Subnet;
11861191
use omicron_common::api::external::Generation;
11871192
use omicron_test_utils::dev;
1193+
use pretty_assertions::assert_eq;
11881194
use rand::thread_rng;
11891195
use rand::Rng;
11901196
use std::mem;
@@ -1515,7 +1521,10 @@ mod tests {
15151521
.blueprint_read(&opctx, &authz_blueprint2)
15161522
.await
15171523
.expect("failed to read collection back");
1518-
println!("diff: {}", blueprint2.diff_sleds(&blueprint_read).display());
1524+
let diff = blueprint_read
1525+
.diff_since_blueprint(&blueprint2)
1526+
.expect("failed to diff blueprints");
1527+
println!("diff: {}", diff.display());
15191528
assert_eq!(blueprint2, blueprint_read);
15201529
assert_eq!(blueprint2.internal_dns_version, new_internal_dns_version);
15211530
assert_eq!(blueprint2.external_dns_version, new_external_dns_version);

nexus/inventory/src/collector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ mod test {
490490
&mut s,
491491
" zone {} type {}\n",
492492
zone.id,
493-
zone.zone_type.label(),
493+
zone.zone_type.kind(),
494494
)
495495
.unwrap();
496496
}

nexus/reconfigurator/execution/src/dns.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ pub fn blueprint_internal_dns_config(
281281
let context = || {
282282
format!(
283283
"parsing {} zone with id {}",
284-
zone.config.zone_type.label(),
284+
zone.config.zone_type.kind(),
285285
zone.config.id
286286
)
287287
};

nexus/reconfigurator/planning/src/blueprint_builder.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ pub mod test {
860860
use crate::example::example;
861861
use crate::example::ExampleSystem;
862862
use crate::system::SledBuilder;
863+
use expectorate::assert_contents;
863864
use omicron_common::address::IpRange;
864865
use omicron_test_utils::dev::test_setup_log;
865866
use sled_agent_client::types::{OmicronZoneConfig, OmicronZoneType};
@@ -904,14 +905,23 @@ pub mod test {
904905
.expect("failed to create initial blueprint");
905906
verify_blueprint(&blueprint_initial);
906907

907-
let diff = blueprint_initial.diff_sleds_from_collection(&collection);
908+
let diff =
909+
blueprint_initial.diff_since_collection(&collection).unwrap();
910+
// There are some differences with even a no-op diff between a
911+
// collection and a blueprint, such as new data being added to
912+
// blueprints like DNS generation numbers.
908913
println!(
909-
"collection -> initial blueprint (expected no changes):\n{}",
914+
"collection -> initial blueprint \
915+
(expected no non-trivial changes):\n{}",
910916
diff.display()
911917
);
912-
assert_eq!(diff.sleds_added().count(), 0);
913-
assert_eq!(diff.sleds_removed().count(), 0);
914-
assert_eq!(diff.sleds_changed().count(), 0);
918+
assert_contents(
919+
"tests/output/blueprint_builder_initial_diff.txt",
920+
&diff.display().to_string(),
921+
);
922+
assert_eq!(diff.sleds_added().len(), 0);
923+
assert_eq!(diff.sleds_removed().len(), 0);
924+
assert_eq!(diff.sleds_modified().count(), 0);
915925

916926
// Test a no-op blueprint.
917927
let builder = BlueprintBuilder::new_based_on(
@@ -925,14 +935,14 @@ pub mod test {
925935
.expect("failed to create builder");
926936
let blueprint = builder.build();
927937
verify_blueprint(&blueprint);
928-
let diff = blueprint_initial.diff_sleds(&blueprint);
938+
let diff = blueprint.diff_since_blueprint(&blueprint_initial).unwrap();
929939
println!(
930940
"initial blueprint -> next blueprint (expected no changes):\n{}",
931941
diff.display()
932942
);
933-
assert_eq!(diff.sleds_added().count(), 0);
934-
assert_eq!(diff.sleds_removed().count(), 0);
935-
assert_eq!(diff.sleds_changed().count(), 0);
943+
assert_eq!(diff.sleds_added().len(), 0);
944+
assert_eq!(diff.sleds_removed().len(), 0);
945+
assert_eq!(diff.sleds_modified().count(), 0);
936946

937947
logctx.cleanup_successful();
938948
}
@@ -970,14 +980,14 @@ pub mod test {
970980

971981
let blueprint2 = builder.build();
972982
verify_blueprint(&blueprint2);
973-
let diff = blueprint1.diff_sleds(&blueprint2);
983+
let diff = blueprint2.diff_since_blueprint(&blueprint1).unwrap();
974984
println!(
975985
"initial blueprint -> next blueprint (expected no changes):\n{}",
976986
diff.display()
977987
);
978-
assert_eq!(diff.sleds_added().count(), 0);
979-
assert_eq!(diff.sleds_removed().count(), 0);
980-
assert_eq!(diff.sleds_changed().count(), 0);
988+
assert_eq!(diff.sleds_added().len(), 0);
989+
assert_eq!(diff.sleds_removed().len(), 0);
990+
assert_eq!(diff.sleds_modified().count(), 0);
981991

982992
// The next step is adding these zones to a new sled.
983993
let new_sled_id = example.sled_rng.next();
@@ -1003,12 +1013,12 @@ pub mod test {
10031013

10041014
let blueprint3 = builder.build();
10051015
verify_blueprint(&blueprint3);
1006-
let diff = blueprint2.diff_sleds(&blueprint3);
1016+
let diff = blueprint3.diff_since_blueprint(&blueprint2).unwrap();
10071017
println!("expecting new NTP and Crucible zones:\n{}", diff.display());
10081018

10091019
// No sleds were changed or removed.
1010-
assert_eq!(diff.sleds_changed().count(), 0);
1011-
assert_eq!(diff.sleds_removed().count(), 0);
1020+
assert_eq!(diff.sleds_modified().count(), 0);
1021+
assert_eq!(diff.sleds_removed().len(), 0);
10121022

10131023
// One sled was added.
10141024
let sleds: Vec<_> = diff.sleds_added().collect();

0 commit comments

Comments
 (0)