@@ -55,11 +55,7 @@ pub(crate) fn perform_all_blueprint_only_checks(blippy: &mut Blippy<'_>) {
5555fn 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