Skip to content

Commit 7cedcdb

Browse files
authored
Blippy: Fix checks now that blueprints have sled subnets (#9549)
Blippy has had checks on sled subnet consistency for a while, but they were implemented by inferring each sled's subnet from its zones' IPs. We added explicit sled subnets to blueprints in #9416; this changes blippy's sled-subnet-related checks to use that new field. (This is work that fell out of a larger PR that isn't ready yet, and I'm opening it separately to reduce diff clutter in that PR.)
1 parent 078f636 commit 7cedcdb

File tree

2 files changed

+74
-69
lines changed

2 files changed

+74
-69
lines changed

nexus/reconfigurator/blippy/src/blippy.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ pub enum SledKind {
134134
zone1: BlueprintZoneConfig,
135135
zone2: BlueprintZoneConfig,
136136
},
137-
/// A sled has two zones that are not members of the same sled subnet.
138-
SledWithMixedUnderlaySubnets {
139-
zone1: BlueprintZoneConfig,
140-
zone2: BlueprintZoneConfig,
137+
/// A sled has a zone with an IP that isn't a member of its subnet.
138+
UnderlayIpOnWrongSubnet {
139+
zone: BlueprintZoneConfig,
140+
subnet: Ipv6Subnet<SLED_PREFIX>,
141141
},
142142
/// Two sleds are using the same sled subnet.
143143
ConflictingSledSubnets {
@@ -259,17 +259,14 @@ impl fmt::Display for SledKind {
259259
zone2.id,
260260
)
261261
}
262-
SledKind::SledWithMixedUnderlaySubnets { zone1, zone2 } => {
262+
SledKind::UnderlayIpOnWrongSubnet { zone, subnet } => {
263263
write!(
264264
f,
265-
"zones have underlay IPs on two different sled subnets: \
266-
{:?} {} ({}) and {:?} {} ({})",
267-
zone1.zone_type.kind(),
268-
zone1.id,
269-
zone1.underlay_ip(),
270-
zone2.zone_type.kind(),
271-
zone2.id,
272-
zone2.underlay_ip(),
265+
"{:?} zone {} underlay IP {} is outside the sled subnet {}",
266+
zone.zone_type.kind(),
267+
zone.id,
268+
zone.underlay_ip(),
269+
subnet,
273270
)
274271
}
275272
SledKind::ConflictingSledSubnets { other_sled, subnet } => {

nexus/reconfigurator/blippy/src/checks.rs

Lines changed: 64 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,7 @@ pub(crate) fn perform_all_blueprint_only_checks(blippy: &mut Blippy<'_>) {
5555
fn check_underlay_ips(blippy: &mut Blippy<'_>) {
5656
let mut underlay_ips: BTreeMap<Ipv6Addr, &BlueprintZoneConfig> =
5757
BTreeMap::new();
58-
let mut inferred_sled_subnets_by_sled: BTreeMap<
59-
SledUuid,
60-
(Ipv6Subnet<SLED_PREFIX>, &BlueprintZoneConfig),
61-
> = BTreeMap::new();
62-
let mut inferred_sled_subnets_by_subnet: BTreeMap<
58+
let mut sled_subnets_by_subnet: BTreeMap<
6359
Ipv6Subnet<SLED_PREFIX>,
6460
SledUuid,
6561
> = BTreeMap::new();
@@ -103,10 +99,10 @@ fn check_underlay_ips(blippy: &mut Blippy<'_>) {
10399
);
104100
}
105101
} else {
106-
let subnet = Ipv6Subnet::new(ip);
102+
let subnet = blippy.blueprint().sleds.get(&sled_id).unwrap().subnet;
107103

108104
// Any given subnet should be used by at most one sled.
109-
match inferred_sled_subnets_by_subnet.entry(subnet) {
105+
match sled_subnets_by_subnet.entry(subnet) {
110106
Entry::Vacant(slot) => {
111107
slot.insert(sled_id);
112108
}
@@ -124,27 +120,18 @@ fn check_underlay_ips(blippy: &mut Blippy<'_>) {
124120
}
125121
}
126122

127-
// Any given sled should have IPs within at most one subnet.
128-
//
129-
// The blueprint doesn't store sled subnets explicitly, so we can't
130-
// check that each sled is using the subnet it's supposed to. The
131-
// best we can do is check that the sleds are internally consistent.
132-
match inferred_sled_subnets_by_sled.entry(sled_id) {
133-
Entry::Vacant(slot) => {
134-
slot.insert((subnet, zone));
135-
}
136-
Entry::Occupied(prev) => {
137-
if prev.get().0 != subnet {
138-
blippy.push_sled_note(
139-
sled_id,
140-
Severity::Fatal,
141-
SledKind::SledWithMixedUnderlaySubnets {
142-
zone1: prev.get().1.clone(),
143-
zone2: zone.clone(),
144-
},
145-
);
146-
}
147-
}
123+
// Any underlay IP (other than internal DNS, which we check above)
124+
// from this sled should be within the sled's subnet.
125+
let inferred_subnet = Ipv6Subnet::new(ip);
126+
if subnet != inferred_subnet {
127+
blippy.push_sled_note(
128+
sled_id,
129+
Severity::Fatal,
130+
SledKind::UnderlayIpOnWrongSubnet {
131+
zone: zone.clone(),
132+
subnet,
133+
},
134+
);
148135
}
149136
}
150137
}
@@ -811,33 +798,23 @@ mod tests {
811798
assert_ne!(nexus0_sled_id, nexus1_sled_id);
812799

813800
let dup_ip = nexus0.underlay_ip();
801+
let correct_nexus_ip;
814802
match &mut nexus1.zone_type {
815803
BlueprintZoneType::Nexus(blueprint_zone_type::Nexus {
816804
internal_address,
817805
..
818806
}) => {
807+
correct_nexus_ip = *internal_address.ip();
819808
internal_address.set_ip(dup_ip);
820809
}
821810
_ => unreachable!("this is a Nexus zone"),
822811
};
823812

824-
// This illegal modification should result in at least three notes: a
825-
// duplicate underlay IP, duplicate sled subnets, and sled1 having mixed
826-
// underlay subnets (the details of which depend on the ordering of
827-
// zones, so we'll sort that out here).
813+
// This illegal modification should result in at least two notes: a
814+
// duplicate underlay IP and sled1 having a zone with an underlay IP
815+
// outside its subnet.
828816
let nexus0 = nexus0.into_ref().clone();
829817
let nexus1 = nexus1.into_ref().clone();
830-
let (mixed_underlay_zone1, mixed_underlay_zone2) = {
831-
let mut sled1_zones =
832-
blueprint.sleds.get(&nexus1_sled_id).unwrap().zones.iter();
833-
let sled1_zone1 = sled1_zones.next().expect("at least one zone");
834-
let sled1_zone2 = sled1_zones.next().expect("at least two zones");
835-
if sled1_zone1.id == nexus1.id {
836-
(nexus1.clone(), sled1_zone2.clone())
837-
} else {
838-
(sled1_zone1.clone(), nexus1.clone())
839-
}
840-
};
841818
let expected_notes = [
842819
Note {
843820
severity: Severity::Fatal,
@@ -853,19 +830,9 @@ mod tests {
853830
severity: Severity::Fatal,
854831
kind: Kind::Sled {
855832
sled_id: nexus1_sled_id,
856-
kind: Box::new(SledKind::SledWithMixedUnderlaySubnets {
857-
zone1: mixed_underlay_zone1,
858-
zone2: mixed_underlay_zone2,
859-
}),
860-
},
861-
},
862-
Note {
863-
severity: Severity::Fatal,
864-
kind: Kind::Sled {
865-
sled_id: nexus1_sled_id,
866-
kind: Box::new(SledKind::ConflictingSledSubnets {
867-
other_sled: nexus0_sled_id,
868-
subnet: Ipv6Subnet::new(dup_ip),
833+
kind: Box::new(SledKind::UnderlayIpOnWrongSubnet {
834+
zone: nexus1.clone(),
835+
subnet: Ipv6Subnet::new(correct_nexus_ip),
869836
}),
870837
},
871838
},
@@ -884,6 +851,47 @@ mod tests {
884851
logctx.cleanup_successful();
885852
}
886853

854+
#[test]
855+
fn test_duplicate_sled_subnet() {
856+
static TEST_NAME: &str = "test_duplicate_sled_subnet";
857+
let logctx = test_setup_log(TEST_NAME);
858+
let (_, _, mut blueprint) = example(&logctx.log, TEST_NAME);
859+
860+
// Copy the subnet from one sled to another.
861+
let sled0 = *blueprint.sleds.keys().next().unwrap();
862+
let sled1 = *blueprint.sleds.keys().nth(1).unwrap();
863+
864+
let sled0_subnet = blueprint.sleds.get(&sled0).unwrap().subnet;
865+
blueprint.sleds.get_mut(&sled1).unwrap().subnet = sled0_subnet;
866+
867+
// This illegal modification should result in at least one note: a
868+
// conflicting sled subnet. (There should also be notes about all
869+
// sled1's underlay IPs being outside its subnet, but we check for that
870+
// in another test.)
871+
let expected_notes = [Note {
872+
severity: Severity::Fatal,
873+
kind: Kind::Sled {
874+
sled_id: sled1,
875+
kind: Box::new(SledKind::ConflictingSledSubnets {
876+
other_sled: sled0,
877+
subnet: sled0_subnet,
878+
}),
879+
},
880+
}];
881+
882+
let report = Blippy::new_blueprint_only(&blueprint)
883+
.into_report(BlippyReportSortKey::Kind);
884+
eprintln!("{}", report.display());
885+
for note in expected_notes {
886+
assert!(
887+
report.notes().contains(&note),
888+
"did not find expected note {note:?}"
889+
);
890+
}
891+
892+
logctx.cleanup_successful();
893+
}
894+
887895
#[test]
888896
fn test_bad_internal_dns_subnet() {
889897
static TEST_NAME: &str = "test_bad_internal_dns_subnet";

0 commit comments

Comments
 (0)