@@ -513,7 +513,11 @@ mod tests {
513513 use nexus_config:: NUM_INITIAL_RESERVED_IP_ADDRESSES ;
514514 use nexus_db_model:: SqlU16 ;
515515 use nexus_reconfigurator_planning:: blueprint_builder:: BlueprintBuilder ;
516+ use nexus_reconfigurator_planning:: blueprint_editor:: ExternalNetworkingAllocator ;
517+ use nexus_reconfigurator_planning:: example:: ExampleSystemBuilder ;
518+ use nexus_reconfigurator_planning:: planner:: PlannerRng ;
516519 use nexus_sled_agent_shared:: inventory:: OmicronZoneDataset ;
520+ use nexus_types:: deployment:: BlueprintSource ;
517521 use nexus_types:: deployment:: BlueprintTarget ;
518522 use nexus_types:: deployment:: BlueprintZoneConfig ;
519523 use nexus_types:: deployment:: BlueprintZoneImageSource ;
@@ -527,6 +531,7 @@ mod tests {
527531 use omicron_common:: address:: DNS_OPTE_IPV4_SUBNET ;
528532 use omicron_common:: address:: IpRange ;
529533 use omicron_common:: address:: IpRangeIter ;
534+ use omicron_common:: address:: Ipv4Range ;
530535 use omicron_common:: address:: NEXUS_OPTE_IPV4_SUBNET ;
531536 use omicron_common:: address:: NTP_OPTE_IPV4_SUBNET ;
532537 use omicron_common:: address:: NUM_SOURCE_NAT_PORTS ;
@@ -535,10 +540,7 @@ mod tests {
535540 use omicron_common:: api:: external:: Vni ;
536541 use omicron_common:: zpool_name:: ZpoolName ;
537542 use omicron_test_utils:: dev;
538- use omicron_uuid_kinds:: BlueprintUuid ;
539543 use omicron_uuid_kinds:: ExternalIpUuid ;
540- use omicron_uuid_kinds:: ExternalZpoolUuid ;
541- use omicron_uuid_kinds:: SledUuid ;
542544 use omicron_uuid_kinds:: ZpoolUuid ;
543545 use oxnet:: IpNet ;
544546 use std:: net:: IpAddr ;
@@ -1348,149 +1350,158 @@ mod tests {
13481350 const TEST_NAME : & str =
13491351 "test_external_dns_external_ips_specified_by_rack_setup" ;
13501352
1351- // Helper closures to reduce boilerplate below.
1353+ // Helper closure to reduce boilerplate below.
13521354 let make_bp_target = |blueprint_id| BlueprintTarget {
13531355 target_id : blueprint_id,
13541356 enabled : false ,
13551357 time_made_target : Utc :: now ( ) ,
13561358 } ;
1357- let mut opte_ip_iter = DNS_OPTE_IPV4_SUBNET . addr_iter ( ) ;
1358- let mut mac_iter = MacAddr :: iter_system ( ) ;
1359- let mut make_external_dns_zone = |ip, disposition| {
1360- let zone_id = OmicronZoneUuid :: new_v4 ( ) ;
1361- let pool = ZpoolName :: External ( ExternalZpoolUuid :: new_v4 ( ) ) ;
1362- BlueprintZoneConfig {
1363- disposition,
1364- id : zone_id,
1365- filesystem_pool : pool,
1366- zone_type : BlueprintZoneType :: ExternalDns (
1367- blueprint_zone_type:: ExternalDns {
1368- dataset : OmicronZoneDataset { pool_name : pool } ,
1369- http_address : "[::1]:0" . parse ( ) . unwrap ( ) ,
1370- dns_address : OmicronZoneExternalFloatingAddr {
1371- id : ExternalIpUuid :: new_v4 ( ) ,
1372- addr : SocketAddr :: new ( ip, 0 ) ,
1373- } ,
1374- nic : NetworkInterface {
1375- id : Uuid :: new_v4 ( ) ,
1376- kind : NetworkInterfaceKind :: Service {
1377- id : zone_id. into_untyped_uuid ( ) ,
1378- } ,
1379- name : "test-external-dns" . parse ( ) . unwrap ( ) ,
1380- ip : opte_ip_iter. next ( ) . unwrap ( ) . into ( ) ,
1381- mac : mac_iter. next ( ) . unwrap ( ) ,
1382- subnet : IpNet :: from ( * DNS_OPTE_IPV4_SUBNET ) ,
1383- vni : Vni :: SERVICES_VNI ,
1384- primary : true ,
1385- slot : 0 ,
1386- transit_ips : vec ! [ ] ,
1387- } ,
1388- } ,
1389- ) ,
1390- image_source : BlueprintZoneImageSource :: InstallDataset ,
1391- }
1392- } ;
13931359
13941360 // Set up.
13951361 let logctx = dev:: test_setup_log ( TEST_NAME ) ;
13961362 let db = TestDatabase :: new_with_datastore ( & logctx. log ) . await ;
13971363 let ( opctx, datastore) = ( db. opctx ( ) , db. datastore ( ) ) ;
13981364
1399- // Create a blueprint with one sled and no zones. Insert it and make it
1400- // the target.
1401- let sled_id = SledUuid :: new_v4 ( ) ;
1402- let bp0 = BlueprintBuilder :: build_empty_with_sleds (
1403- std:: iter:: once ( sled_id) ,
1404- TEST_NAME ,
1405- ) ;
1406- datastore. blueprint_insert ( opctx, & bp0) . await . expect ( "inserted bp0" ) ;
1407- datastore
1408- . blueprint_target_set_current ( opctx, make_bp_target ( bp0. id ) )
1409- . await
1410- . expect ( "made bp0 the target" ) ;
1365+ // Create a blueprint with no external DNS zones.
1366+ let ( example_system, bp1) =
1367+ ExampleSystemBuilder :: new ( & opctx. log , TEST_NAME )
1368+ . external_dns_count ( 0 )
1369+ . expect ( "external DNS count can be 0" )
1370+ . build ( ) ;
1371+
1372+ // Insert the example system's initial empty blueprint and the one we
1373+ // want to test. Advance the target to point to `blueprint`.
1374+ let bp0 = & example_system. initial_blueprint ;
1375+ for bp in [ bp0, & bp1] {
1376+ datastore. blueprint_insert ( opctx, bp) . await . expect ( "inserted bp" ) ;
1377+ datastore
1378+ . blueprint_target_set_current ( opctx, make_bp_target ( bp. id ) )
1379+ . await
1380+ . expect ( "made bp the target" ) ;
1381+ }
14111382
14121383 // No external DNS zones => no external DNS IPs.
1384+ assert ! (
1385+ example_system
1386+ . input
1387+ . external_ip_policy( )
1388+ . external_dns_ips( )
1389+ . is_empty( )
1390+ ) ;
14131391 let external_dns_ips = datastore
14141392 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14151393 . await
14161394 . expect ( "got external DNS IPs" ) ;
14171395 assert_eq ! ( external_dns_ips, BTreeSet :: new( ) ) ;
14181396
1419- // Create a blueprint with three in-service external DNS zones. We
1420- // should get their IPs back.
1421- let expected_ips = [ "192.168.1.1" , "192.168.1.2" , "192.168.1.3" ]
1422- . into_iter ( )
1423- . map ( |ip| ip. parse :: < IpAddr > ( ) . unwrap ( ) )
1424- . collect :: < BTreeSet < _ > > ( ) ;
1425- let mut bp1 = bp0. clone ( ) ;
1426- bp1. id = BlueprintUuid :: new_v4 ( ) ;
1427- bp1. parent_blueprint_id = Some ( bp0. id ) ;
1428- for & ip in & expected_ips {
1429- bp1. sleds
1430- . get_mut ( & sled_id)
1431- . unwrap ( )
1432- . zones
1433- . insert_unique ( make_external_dns_zone (
1434- ip,
1435- BlueprintZoneDisposition :: InService ,
1436- ) )
1437- . expect ( "freshly generated zone IDs are unique" ) ;
1397+ // Extend the external IP policy to allow for external DNS.
1398+ let all_external_dns_ips = IpRange :: V4 ( Ipv4Range {
1399+ first : "192.168.1.1" . parse ( ) . unwrap ( ) ,
1400+ last : "192.168.1.5" . parse ( ) . unwrap ( ) ,
1401+ } ) ;
1402+ let input = {
1403+ let mut policy_builder = example_system
1404+ . input
1405+ . external_ip_policy ( )
1406+ . clone ( )
1407+ . into_builder ( ) ;
1408+ policy_builder
1409+ . push_service_pool_range ( all_external_dns_ips)
1410+ . expect ( "valid range" ) ;
1411+ for ip in all_external_dns_ips. iter ( ) {
1412+ policy_builder. add_external_dns_ip ( ip) . expect ( "valid IP" ) ;
1413+ }
1414+
1415+ let mut input_builder = example_system. input . into_builder ( ) ;
1416+ input_builder. policy_mut ( ) . external_ips = policy_builder. build ( ) ;
1417+ input_builder. build ( )
1418+ } ;
1419+
1420+ // Add an in-service external DNS zone for each IP.
1421+ let mut builder = BlueprintBuilder :: new_based_on (
1422+ & opctx. log ,
1423+ & bp1,
1424+ & input,
1425+ TEST_NAME ,
1426+ PlannerRng :: from_entropy ( ) ,
1427+ )
1428+ . expect ( "created builder" ) ;
1429+
1430+ let mut external_networking_alloc =
1431+ ExternalNetworkingAllocator :: from_current_zones (
1432+ & builder,
1433+ input. external_ip_policy ( ) ,
1434+ )
1435+ . expect ( "created allocator" ) ;
1436+
1437+ let sled_id = bp1. sleds ( ) . next ( ) . expect ( "at least 1 sled exists" ) ;
1438+ for _ in all_external_dns_ips. iter ( ) {
1439+ builder
1440+ . sled_add_zone_external_dns (
1441+ sled_id,
1442+ BlueprintZoneImageSource :: InstallDataset ,
1443+ external_networking_alloc
1444+ . for_new_external_dns ( )
1445+ . expect ( "got IP for external DNS" ) ,
1446+ )
1447+ . expect ( "added external DNS" ) ;
14381448 }
14391449
1440- // Insert bp1 and make it the target. Confirm we get back the expected
1450+ // Insert bp2 and make it the target. Confirm we get back the expected
14411451 // external DNS IPs.
1442- datastore. blueprint_insert ( opctx, & bp1) . await . expect ( "inserted bp1" ) ;
1452+ let bp2 = builder. build ( BlueprintSource :: Test ) ;
1453+ let expected_ips = all_external_dns_ips. iter ( ) . collect :: < BTreeSet < _ > > ( ) ;
1454+ datastore. blueprint_insert ( opctx, & bp2) . await . expect ( "inserted bp2" ) ;
14431455 datastore
1444- . blueprint_target_set_current ( opctx, make_bp_target ( bp1 . id ) )
1456+ . blueprint_target_set_current ( opctx, make_bp_target ( bp2 . id ) )
14451457 . await
1446- . expect ( "made bp1 the target" ) ;
1458+ . expect ( "made bp2 the target" ) ;
14471459 let external_dns_ips = datastore
14481460 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14491461 . await
14501462 . expect ( "got external DNS IPs" ) ;
14511463 assert_eq ! ( external_dns_ips, expected_ips) ;
14521464
1453- // Create a third blueprint with multiple expunged external DNS zones
1454- // covering a couple additional IPs. Those should also be returned.
1455- let extra_ips = [ "192.168.1.4" , "192.168.1.5" ]
1456- . into_iter ( )
1457- . map ( |ip| ip. parse :: < IpAddr > ( ) . unwrap ( ) )
1465+ // Create a new blueprint that expunges two of those zones.
1466+ let mut builder = BlueprintBuilder :: new_based_on (
1467+ & opctx. log ,
1468+ & bp2,
1469+ & input,
1470+ TEST_NAME ,
1471+ PlannerRng :: from_entropy ( ) ,
1472+ )
1473+ . expect ( "created builder" ) ;
1474+
1475+ let to_expunge = builder
1476+ . current_zones ( BlueprintZoneDisposition :: is_in_service)
1477+ . filter_map ( |( sled_id, zone) | {
1478+ if zone. zone_type . is_external_dns ( ) {
1479+ Some ( ( sled_id, zone. id ) )
1480+ } else {
1481+ None
1482+ }
1483+ } )
1484+ . take ( 2 )
14581485 . collect :: < BTreeSet < _ > > ( ) ;
1459- assert_eq ! ( expected_ips. intersection( & extra_ips) . count( ) , 0 ) ;
1460-
1461- let mut bp2 = bp1. clone ( ) ;
1462- bp2. id = BlueprintUuid :: new_v4 ( ) ;
1463- bp2. parent_blueprint_id = Some ( bp1. id ) ;
1464- for & ip in & extra_ips {
1465- for i in 0 ..4 {
1466- bp2. sleds
1467- . get_mut ( & sled_id)
1468- . unwrap ( )
1469- . zones
1470- . insert_unique ( make_external_dns_zone (
1471- ip,
1472- BlueprintZoneDisposition :: Expunged {
1473- as_of_generation : Generation :: new ( ) ,
1474- ready_for_cleanup : i % 2 == 0 ,
1475- } ,
1476- ) )
1477- . expect ( "freshly generated zone IDs are unique" ) ;
1478- }
1486+ assert_eq ! ( to_expunge. len( ) , 2 ) ;
1487+
1488+ for ( sled_id, zone_id) in to_expunge {
1489+ builder. sled_expunge_zone ( sled_id, zone_id) . expect ( "expunged zone" ) ;
14791490 }
14801491
1481- // Insert bp1 and make it the target. Confirm we get back the expected
1492+ // Insert bp3 and make it the target. Confirm we still get back all five
14821493 // external DNS IPs.
1483- datastore. blueprint_insert ( opctx, & bp2) . await . expect ( "inserted bp2" ) ;
1494+ let bp3 = builder. build ( BlueprintSource :: Test ) ;
1495+ let expected_ips = all_external_dns_ips. iter ( ) . collect :: < BTreeSet < _ > > ( ) ;
1496+ datastore. blueprint_insert ( opctx, & bp3) . await . expect ( "inserted bp3" ) ;
14841497 datastore
1485- . blueprint_target_set_current ( opctx, make_bp_target ( bp2 . id ) )
1498+ . blueprint_target_set_current ( opctx, make_bp_target ( bp3 . id ) )
14861499 . await
1487- . expect ( "made bp2 the target" ) ;
1500+ . expect ( "made bp3 the target" ) ;
14881501 let external_dns_ips = datastore
14891502 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14901503 . await
14911504 . expect ( "got external DNS IPs" ) ;
1492- let expected_ips =
1493- expected_ips. union ( & extra_ips) . copied ( ) . collect :: < BTreeSet < _ > > ( ) ;
14941505 assert_eq ! ( external_dns_ips, expected_ips) ;
14951506
14961507 // Clean up.
0 commit comments