@@ -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 ;
@@ -1352,149 +1354,158 @@ mod tests {
13521354 const TEST_NAME : & str =
13531355 "test_external_dns_external_ips_specified_by_rack_setup" ;
13541356
1355- // Helper closures to reduce boilerplate below.
1357+ // Helper closure to reduce boilerplate below.
13561358 let make_bp_target = |blueprint_id| BlueprintTarget {
13571359 target_id : blueprint_id,
13581360 enabled : false ,
13591361 time_made_target : Utc :: now ( ) ,
13601362 } ;
1361- let mut opte_ip_iter = DNS_OPTE_IPV4_SUBNET . addr_iter ( ) ;
1362- let mut mac_iter = MacAddr :: iter_system ( ) ;
1363- let mut make_external_dns_zone = |ip, disposition| {
1364- let zone_id = OmicronZoneUuid :: new_v4 ( ) ;
1365- let pool = ZpoolName :: External ( ExternalZpoolUuid :: new_v4 ( ) ) ;
1366- BlueprintZoneConfig {
1367- disposition,
1368- id : zone_id,
1369- filesystem_pool : pool,
1370- zone_type : BlueprintZoneType :: ExternalDns (
1371- blueprint_zone_type:: ExternalDns {
1372- dataset : OmicronZoneDataset { pool_name : pool } ,
1373- http_address : "[::1]:0" . parse ( ) . unwrap ( ) ,
1374- dns_address : OmicronZoneExternalFloatingAddr {
1375- id : ExternalIpUuid :: new_v4 ( ) ,
1376- addr : SocketAddr :: new ( ip, 0 ) ,
1377- } ,
1378- nic : NetworkInterface {
1379- id : Uuid :: new_v4 ( ) ,
1380- kind : NetworkInterfaceKind :: Service {
1381- id : zone_id. into_untyped_uuid ( ) ,
1382- } ,
1383- name : "test-external-dns" . parse ( ) . unwrap ( ) ,
1384- ip : opte_ip_iter. next ( ) . unwrap ( ) . into ( ) ,
1385- mac : mac_iter. next ( ) . unwrap ( ) ,
1386- subnet : IpNet :: from ( * DNS_OPTE_IPV4_SUBNET ) ,
1387- vni : Vni :: SERVICES_VNI ,
1388- primary : true ,
1389- slot : 0 ,
1390- transit_ips : vec ! [ ] ,
1391- } ,
1392- } ,
1393- ) ,
1394- image_source : BlueprintZoneImageSource :: InstallDataset ,
1395- }
1396- } ;
13971363
13981364 // Set up.
13991365 let logctx = dev:: test_setup_log ( TEST_NAME ) ;
14001366 let db = TestDatabase :: new_with_datastore ( & logctx. log ) . await ;
14011367 let ( opctx, datastore) = ( db. opctx ( ) , db. datastore ( ) ) ;
14021368
1403- // Create a blueprint with one sled and no zones. Insert it and make it
1404- // the target.
1405- let sled_id = SledUuid :: new_v4 ( ) ;
1406- let bp0 = BlueprintBuilder :: build_empty_with_sleds (
1407- std:: iter:: once ( sled_id) ,
1408- TEST_NAME ,
1409- ) ;
1410- datastore. blueprint_insert ( opctx, & bp0) . await . expect ( "inserted bp0" ) ;
1411- datastore
1412- . blueprint_target_set_current ( opctx, make_bp_target ( bp0. id ) )
1413- . await
1414- . expect ( "made bp0 the target" ) ;
1369+ // Create a blueprint with no external DNS zones.
1370+ let ( example_system, bp1) =
1371+ ExampleSystemBuilder :: new ( & opctx. log , TEST_NAME )
1372+ . external_dns_count ( 0 )
1373+ . expect ( "external DNS count can be 0" )
1374+ . build ( ) ;
1375+
1376+ // Insert the example system's initial empty blueprint and the one we
1377+ // want to test. Advance the target to point to `blueprint`.
1378+ let bp0 = & example_system. initial_blueprint ;
1379+ for bp in [ bp0, & bp1] {
1380+ datastore. blueprint_insert ( opctx, bp) . await . expect ( "inserted bp" ) ;
1381+ datastore
1382+ . blueprint_target_set_current ( opctx, make_bp_target ( bp. id ) )
1383+ . await
1384+ . expect ( "made bp the target" ) ;
1385+ }
14151386
14161387 // No external DNS zones => no external DNS IPs.
1388+ assert ! (
1389+ example_system
1390+ . input
1391+ . external_ip_policy( )
1392+ . external_dns_ips( )
1393+ . is_empty( )
1394+ ) ;
14171395 let external_dns_ips = datastore
14181396 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14191397 . await
14201398 . expect ( "got external DNS IPs" ) ;
14211399 assert_eq ! ( external_dns_ips, BTreeSet :: new( ) ) ;
14221400
1423- // Create a blueprint with three in-service external DNS zones. We
1424- // should get their IPs back.
1425- let expected_ips = [ "192.168.1.1" , "192.168.1.2" , "192.168.1.3" ]
1426- . into_iter ( )
1427- . map ( |ip| ip. parse :: < IpAddr > ( ) . unwrap ( ) )
1428- . collect :: < BTreeSet < _ > > ( ) ;
1429- let mut bp1 = bp0. clone ( ) ;
1430- bp1. id = BlueprintUuid :: new_v4 ( ) ;
1431- bp1. parent_blueprint_id = Some ( bp0. id ) ;
1432- for & ip in & expected_ips {
1433- bp1. sleds
1434- . get_mut ( & sled_id)
1435- . unwrap ( )
1436- . zones
1437- . insert_unique ( make_external_dns_zone (
1438- ip,
1439- BlueprintZoneDisposition :: InService ,
1440- ) )
1441- . expect ( "freshly generated zone IDs are unique" ) ;
1401+ // Extend the external IP policy to allow for external DNS.
1402+ let all_external_dns_ips = IpRange :: V4 ( Ipv4Range {
1403+ first : "192.168.1.1" . parse ( ) . unwrap ( ) ,
1404+ last : "192.168.1.5" . parse ( ) . unwrap ( ) ,
1405+ } ) ;
1406+ let input = {
1407+ let mut policy_builder = example_system
1408+ . input
1409+ . external_ip_policy ( )
1410+ . clone ( )
1411+ . into_builder ( ) ;
1412+ policy_builder
1413+ . push_service_pool_range ( all_external_dns_ips)
1414+ . expect ( "valid range" ) ;
1415+ for ip in all_external_dns_ips. iter ( ) {
1416+ policy_builder. add_external_dns_ip ( ip) . expect ( "valid IP" ) ;
1417+ }
1418+
1419+ let mut input_builder = example_system. input . into_builder ( ) ;
1420+ input_builder. policy_mut ( ) . external_ips = policy_builder. build ( ) ;
1421+ input_builder. build ( )
1422+ } ;
1423+
1424+ // Add an in-service external DNS zone for each IP.
1425+ let mut builder = BlueprintBuilder :: new_based_on (
1426+ & opctx. log ,
1427+ & bp1,
1428+ & input,
1429+ TEST_NAME ,
1430+ PlannerRng :: from_entropy ( ) ,
1431+ )
1432+ . expect ( "created builder" ) ;
1433+
1434+ let mut external_networking_alloc =
1435+ ExternalNetworkingAllocator :: from_current_zones (
1436+ & builder,
1437+ input. external_ip_policy ( ) ,
1438+ )
1439+ . expect ( "created allocator" ) ;
1440+
1441+ let sled_id = bp1. sleds ( ) . next ( ) . expect ( "at least 1 sled exists" ) ;
1442+ for _ in all_external_dns_ips. iter ( ) {
1443+ builder
1444+ . sled_add_zone_external_dns (
1445+ sled_id,
1446+ BlueprintZoneImageSource :: InstallDataset ,
1447+ external_networking_alloc
1448+ . for_new_external_dns ( )
1449+ . expect ( "got IP for external DNS" ) ,
1450+ )
1451+ . expect ( "added external DNS" ) ;
14421452 }
14431453
1444- // Insert bp1 and make it the target. Confirm we get back the expected
1454+ // Insert bp2 and make it the target. Confirm we get back the expected
14451455 // external DNS IPs.
1446- datastore. blueprint_insert ( opctx, & bp1) . await . expect ( "inserted bp1" ) ;
1456+ let bp2 = builder. build ( BlueprintSource :: Test ) ;
1457+ let expected_ips = all_external_dns_ips. iter ( ) . collect :: < BTreeSet < _ > > ( ) ;
1458+ datastore. blueprint_insert ( opctx, & bp2) . await . expect ( "inserted bp2" ) ;
14471459 datastore
1448- . blueprint_target_set_current ( opctx, make_bp_target ( bp1 . id ) )
1460+ . blueprint_target_set_current ( opctx, make_bp_target ( bp2 . id ) )
14491461 . await
1450- . expect ( "made bp1 the target" ) ;
1462+ . expect ( "made bp2 the target" ) ;
14511463 let external_dns_ips = datastore
14521464 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14531465 . await
14541466 . expect ( "got external DNS IPs" ) ;
14551467 assert_eq ! ( external_dns_ips, expected_ips) ;
14561468
1457- // Create a third blueprint with multiple expunged external DNS zones
1458- // covering a couple additional IPs. Those should also be returned.
1459- let extra_ips = [ "192.168.1.4" , "192.168.1.5" ]
1460- . into_iter ( )
1461- . map ( |ip| ip. parse :: < IpAddr > ( ) . unwrap ( ) )
1469+ // Create a new blueprint that expunges two of those zones.
1470+ let mut builder = BlueprintBuilder :: new_based_on (
1471+ & opctx. log ,
1472+ & bp2,
1473+ & input,
1474+ TEST_NAME ,
1475+ PlannerRng :: from_entropy ( ) ,
1476+ )
1477+ . expect ( "created builder" ) ;
1478+
1479+ let to_expunge = builder
1480+ . current_zones ( BlueprintZoneDisposition :: is_in_service)
1481+ . filter_map ( |( sled_id, zone) | {
1482+ if zone. zone_type . is_external_dns ( ) {
1483+ Some ( ( sled_id, zone. id ) )
1484+ } else {
1485+ None
1486+ }
1487+ } )
1488+ . take ( 2 )
14621489 . collect :: < BTreeSet < _ > > ( ) ;
1463- assert_eq ! ( expected_ips. intersection( & extra_ips) . count( ) , 0 ) ;
1464-
1465- let mut bp2 = bp1. clone ( ) ;
1466- bp2. id = BlueprintUuid :: new_v4 ( ) ;
1467- bp2. parent_blueprint_id = Some ( bp1. id ) ;
1468- for & ip in & extra_ips {
1469- for i in 0 ..4 {
1470- bp2. sleds
1471- . get_mut ( & sled_id)
1472- . unwrap ( )
1473- . zones
1474- . insert_unique ( make_external_dns_zone (
1475- ip,
1476- BlueprintZoneDisposition :: Expunged {
1477- as_of_generation : Generation :: new ( ) ,
1478- ready_for_cleanup : i % 2 == 0 ,
1479- } ,
1480- ) )
1481- . expect ( "freshly generated zone IDs are unique" ) ;
1482- }
1490+ assert_eq ! ( to_expunge. len( ) , 2 ) ;
1491+
1492+ for ( sled_id, zone_id) in to_expunge {
1493+ builder. sled_expunge_zone ( sled_id, zone_id) . expect ( "expunged zone" ) ;
14831494 }
14841495
1485- // Insert bp1 and make it the target. Confirm we get back the expected
1496+ // Insert bp3 and make it the target. Confirm we still get back all five
14861497 // external DNS IPs.
1487- datastore. blueprint_insert ( opctx, & bp2) . await . expect ( "inserted bp2" ) ;
1498+ let bp3 = builder. build ( BlueprintSource :: Test ) ;
1499+ let expected_ips = all_external_dns_ips. iter ( ) . collect :: < BTreeSet < _ > > ( ) ;
1500+ datastore. blueprint_insert ( opctx, & bp3) . await . expect ( "inserted bp3" ) ;
14881501 datastore
1489- . blueprint_target_set_current ( opctx, make_bp_target ( bp2 . id ) )
1502+ . blueprint_target_set_current ( opctx, make_bp_target ( bp3 . id ) )
14901503 . await
1491- . expect ( "made bp2 the target" ) ;
1504+ . expect ( "made bp3 the target" ) ;
14921505 let external_dns_ips = datastore
14931506 . external_dns_external_ips_specified_by_rack_setup ( opctx)
14941507 . await
14951508 . expect ( "got external DNS IPs" ) ;
1496- let expected_ips =
1497- expected_ips. union ( & extra_ips) . copied ( ) . collect :: < BTreeSet < _ > > ( ) ;
14981509 assert_eq ! ( external_dns_ips, expected_ips) ;
14991510
15001511 // Clean up.
0 commit comments