@@ -60,8 +60,9 @@ use nexus_types::deployment::{
6060use nexus_types:: deployment:: { BlueprintPhysicalDiskConfig , BlueprintSource } ;
6161use nexus_types:: deployment:: { BlueprintZoneImageSource , blueprint_zone_type} ;
6262use nexus_types:: deployment:: {
63- OmicronZoneExternalFloatingAddr , OmicronZoneExternalFloatingIp ,
64- OmicronZoneExternalSnatIp ,
63+ OmicronZoneExternalFloatingAddr , OmicronZoneExternalFloatingAddrs ,
64+ OmicronZoneExternalFloatingIp , OmicronZoneExternalFloatingIps ,
65+ OmicronZoneExternalSnat , OmicronZoneExternalSnatIp ,
6566} ;
6667use omicron_common:: address:: Ipv6Subnet ;
6768use omicron_common:: address:: SLED_PREFIX_LENGTH ;
@@ -855,7 +856,7 @@ impl BpOmicronZone {
855856 http_address,
856857 // The external DNS address is stored in the
857858 // `bp_omicron_zone_external_ip` table, not here.
858- dns_address : _,
859+ dns_addresses : _,
859860 nic,
860861 } ,
861862 ) => {
@@ -901,7 +902,7 @@ impl BpOmicronZone {
901902 lockstep_port,
902903 // The external IP is stored in the
903904 // `bp_omicron_zone_external_ip` table, not here.
904- external_ip : _,
905+ external_ips : _,
905906 nic,
906907 external_tls,
907908 external_dns_servers,
@@ -978,18 +979,7 @@ impl BpOmicronZone {
978979 nic_row. map ( Into :: into) ,
979980 ) ?;
980981
981- // The external IP(s) for this zone live in the
982- // `bp_omicron_zone_external_ip` table. Until `BlueprintZoneType` can
983- // handle multiple IPs (#9288), we need zero or exactly one row here,
984- // for the zone types that have external networking.
985- //
986- // NOTE: This returns an error if `external_ip_rows` is empty. That's
987- // fine if the zone doesn't need external networking, so we only
988- // ?-propagate this inside the zone-specific code below.
989- let external_ip = BpOmicronZoneExternalIp :: into_single (
990- external_ip_rows,
991- self . id . into ( ) ,
992- ) ;
982+ let zone_id = self . id . into ( ) ;
993983
994984 // NOTE: this is the *internal* DNS underlay address, held in
995985 // `second_service_ip` / `second_service_port`. External DNS's external
@@ -1011,19 +1001,19 @@ impl BpOmicronZone {
10111001
10121002 let zone_type = match self . zone_type {
10131003 ZoneType :: BoundaryNtp => {
1014- let external_ip = external_ip?;
1015- let snat_cfg = external_ip. to_snat_config ( ) ?;
1004+ let external_ip =
1005+ BpOmicronZoneExternalIp :: into_boundary_ntp_snat (
1006+ external_ip_rows,
1007+ zone_id,
1008+ ) ?;
10161009 BlueprintZoneType :: BoundaryNtp (
10171010 blueprint_zone_type:: BoundaryNtp {
10181011 address : primary_address,
10191012 ntp_servers : ntp_servers?,
10201013 dns_servers : ntp_dns_servers?,
10211014 domain : self . ntp_domain ,
10221015 nic : nic?,
1023- external_ip : OmicronZoneExternalSnatIp {
1024- id : external_ip. external_ip_id . into ( ) ,
1025- snat_cfg,
1026- } ,
1016+ external_ip,
10271017 } ,
10281018 )
10291019 }
@@ -1064,15 +1054,16 @@ impl BpOmicronZone {
10641054 } ,
10651055 ) ,
10661056 ZoneType :: ExternalDns => {
1067- let external_ip = external_ip?;
1057+ let dns_addresses =
1058+ BpOmicronZoneExternalIp :: into_external_dns_addrs (
1059+ external_ip_rows,
1060+ zone_id,
1061+ ) ?;
10681062 BlueprintZoneType :: ExternalDns (
10691063 blueprint_zone_type:: ExternalDns {
10701064 dataset : dataset?,
10711065 http_address : primary_address,
1072- dns_address : OmicronZoneExternalFloatingAddr {
1073- id : external_ip. external_ip_id . into ( ) ,
1074- addr : external_ip. to_floating_addr ( ) ?,
1075- } ,
1066+ dns_addresses,
10761067 nic : nic?,
10771068 } ,
10781069 )
@@ -1099,16 +1090,17 @@ impl BpOmicronZone {
10991090 blueprint_zone_type:: InternalNtp { address : primary_address } ,
11001091 ) ,
11011092 ZoneType :: Nexus => {
1102- let external_ip = external_ip?;
1093+ let external_ips =
1094+ BpOmicronZoneExternalIp :: into_nexus_external_ips (
1095+ external_ip_rows,
1096+ zone_id,
1097+ ) ?;
11031098 BlueprintZoneType :: Nexus ( blueprint_zone_type:: Nexus {
11041099 internal_address : primary_address,
11051100 lockstep_port : * self . nexus_lockstep_port . ok_or_else (
11061101 || anyhow ! ( "expected 'nexus_lockstep_port'" ) ,
11071102 ) ?,
1108- external_ip : OmicronZoneExternalFloatingIp {
1109- id : external_ip. external_ip_id . into ( ) ,
1110- ip : external_ip. ip . ip ( ) ,
1111- } ,
1103+ external_ips,
11121104 nic : nic?,
11131105 external_tls : self
11141106 . nexus_external_tls
@@ -1186,10 +1178,9 @@ pub struct BpOmicronZoneExternalIp {
11861178impl BpOmicronZoneExternalIp {
11871179 /// Build the external IP child rows for a blueprint zone.
11881180 ///
1189- /// Returns one row per external IP. Today the in-memory `BlueprintZoneType`
1190- /// only ever has at most one external IP per zone, so this returns at most
1191- /// one row. In general though, the `bp_omicron_zone_external_ip` table can
1192- /// store any number of rows per zone, so we're returning an array.
1181+ /// Returns one row per external IP: Nexus and external DNS may each have
1182+ /// several, and boundary NTP may have a source-NAT address per IP family
1183+ /// (with at least one address).
11931184 pub fn for_zone (
11941185 blueprint_id : BlueprintUuid ,
11951186 blueprint_zone : & BlueprintZoneConfig ,
@@ -1198,43 +1189,52 @@ impl BpOmicronZoneExternalIp {
11981189 let zone_id = blueprint_zone. id . into ( ) ;
11991190 match & blueprint_zone. zone_type {
12001191 BlueprintZoneType :: Nexus ( blueprint_zone_type:: Nexus {
1201- external_ip ,
1192+ external_ips ,
12021193 ..
1203- } ) => vec ! [ Self {
1204- blueprint_id,
1205- zone_id,
1206- external_ip_id: external_ip. id. into( ) ,
1207- ip: IpNetwork :: from( external_ip. ip) ,
1208- port: None ,
1209- snat_first_port: None ,
1210- snat_last_port: None ,
1211- } ] ,
1212- BlueprintZoneType :: ExternalDns (
1213- blueprint_zone_type:: ExternalDns { dns_address, .. } ,
1214- ) => vec ! [ Self {
1215- blueprint_id,
1216- zone_id,
1217- external_ip_id: dns_address. id. into( ) ,
1218- ip: IpNetwork :: from( dns_address. addr. ip( ) ) ,
1219- port: Some ( SqlU16 :: from( dns_address. addr. port( ) ) ) ,
1220- snat_first_port: None ,
1221- snat_last_port: None ,
1222- } ] ,
1223- BlueprintZoneType :: BoundaryNtp (
1224- blueprint_zone_type:: BoundaryNtp { external_ip, .. } ,
1225- ) => {
1226- let ( first_port, last_port) =
1227- external_ip. snat_cfg . port_range_raw ( ) ;
1228- vec ! [ Self {
1194+ } ) => external_ips
1195+ . iter ( )
1196+ . map ( |external_ip| Self {
12291197 blueprint_id,
12301198 zone_id,
12311199 external_ip_id : external_ip. id . into ( ) ,
1232- ip: IpNetwork :: from( external_ip. snat_cfg . ip) ,
1200+ ip : IpNetwork :: from ( external_ip. ip ) ,
12331201 port : None ,
1234- snat_first_port: Some ( SqlU16 :: from( first_port) ) ,
1235- snat_last_port: Some ( SqlU16 :: from( last_port) ) ,
1236- } ]
1237- }
1202+ snat_first_port : None ,
1203+ snat_last_port : None ,
1204+ } )
1205+ . collect ( ) ,
1206+ BlueprintZoneType :: ExternalDns (
1207+ blueprint_zone_type:: ExternalDns { dns_addresses, .. } ,
1208+ ) => dns_addresses
1209+ . iter ( )
1210+ . map ( |dns_address| Self {
1211+ blueprint_id,
1212+ zone_id,
1213+ external_ip_id : dns_address. id . into ( ) ,
1214+ ip : IpNetwork :: from ( dns_address. addr . ip ( ) ) ,
1215+ port : Some ( SqlU16 :: from ( dns_address. addr . port ( ) ) ) ,
1216+ snat_first_port : None ,
1217+ snat_last_port : None ,
1218+ } )
1219+ . collect ( ) ,
1220+ BlueprintZoneType :: BoundaryNtp (
1221+ blueprint_zone_type:: BoundaryNtp { external_ip, .. } ,
1222+ ) => external_ip
1223+ . iter ( )
1224+ . map ( |snat| {
1225+ let ( first_port, last_port) =
1226+ snat. snat_cfg . port_range_raw ( ) ;
1227+ Self {
1228+ blueprint_id,
1229+ zone_id,
1230+ external_ip_id : snat. id . into ( ) ,
1231+ ip : IpNetwork :: from ( snat. snat_cfg . ip ) ,
1232+ port : None ,
1233+ snat_first_port : Some ( SqlU16 :: from ( first_port) ) ,
1234+ snat_last_port : Some ( SqlU16 :: from ( last_port) ) ,
1235+ }
1236+ } )
1237+ . collect ( ) ,
12381238 BlueprintZoneType :: Clickhouse ( _)
12391239 | BlueprintZoneType :: ClickhouseKeeper ( _)
12401240 | BlueprintZoneType :: ClickhouseServer ( _)
@@ -1247,28 +1247,59 @@ impl BpOmicronZoneExternalIp {
12471247 }
12481248 }
12491249
1250- /// Collapse the external IP rows for a zone into exactly one.
1251- ///
1252- /// NOTE: This is a temporary method until the `BlueprintZoneType` variants
1253- /// with external addresses can handle more than one EIP. Until then, these
1254- /// zones must have exactly one external IP. This returns that single
1255- /// address, or an error if there is any other number of rows.
1256- fn into_single (
1257- mut rows : Vec < Self > ,
1250+ /// Reconstruct a Nexus zone's external IPs from its child rows.
1251+ fn into_nexus_external_ips (
1252+ rows : Vec < Self > ,
12581253 zone_id : OmicronZoneUuid ,
1259- ) -> anyhow:: Result < Self > {
1260- match rows. len ( ) {
1261- 1 => Ok ( rows. pop ( ) . expect ( "length checked to be 1" ) ) ,
1262- 0 => bail ! (
1263- "zone {zone_id} has no external IP, \
1264- but its type requires one"
1265- ) ,
1266- n => bail ! (
1267- "zone {zone_id} has {n} external IPs, but only one is \
1268- supported until the in-memory blueprint type is widened \
1269- (#9288)"
1270- ) ,
1271- }
1254+ ) -> anyhow:: Result < OmicronZoneExternalFloatingIps > {
1255+ let ips = rows
1256+ . into_iter ( )
1257+ . map ( |row| OmicronZoneExternalFloatingIp {
1258+ id : row. external_ip_id . into ( ) ,
1259+ ip : row. ip . ip ( ) ,
1260+ } )
1261+ . collect ( ) ;
1262+ OmicronZoneExternalFloatingIps :: new ( ips) . with_context ( || {
1263+ format ! ( "zone {zone_id} has invalid Nexus external IPs" )
1264+ } )
1265+ }
1266+
1267+ /// Reconstruct an external DNS zone's addresses from its child rows.
1268+ fn into_external_dns_addrs (
1269+ rows : Vec < Self > ,
1270+ zone_id : OmicronZoneUuid ,
1271+ ) -> anyhow:: Result < OmicronZoneExternalFloatingAddrs > {
1272+ let addrs = rows
1273+ . into_iter ( )
1274+ . map ( |row| {
1275+ Ok ( OmicronZoneExternalFloatingAddr {
1276+ id : row. external_ip_id . into ( ) ,
1277+ addr : row. to_floating_addr ( ) ?,
1278+ } )
1279+ } )
1280+ . collect :: < anyhow:: Result < Vec < _ > > > ( ) ?;
1281+ OmicronZoneExternalFloatingAddrs :: new ( addrs) . with_context ( || {
1282+ format ! ( "zone {zone_id} has invalid external DNS addresses" )
1283+ } )
1284+ }
1285+
1286+ /// Reconstruct a boundary NTP zone's SNAT configuration from its child rows.
1287+ fn into_boundary_ntp_snat (
1288+ rows : Vec < Self > ,
1289+ zone_id : OmicronZoneUuid ,
1290+ ) -> anyhow:: Result < OmicronZoneExternalSnat > {
1291+ let snat_ips = rows
1292+ . into_iter ( )
1293+ . map ( |row| {
1294+ Ok ( OmicronZoneExternalSnatIp {
1295+ id : row. external_ip_id . into ( ) ,
1296+ snat_cfg : row. to_snat_config ( ) ?,
1297+ } )
1298+ } )
1299+ . collect :: < anyhow:: Result < Vec < _ > > > ( ) ?;
1300+ OmicronZoneExternalSnat :: from_ips ( snat_ips) . with_context ( || {
1301+ format ! ( "zone {zone_id} has invalid boundary NTP SNAT config" )
1302+ } )
12721303 }
12731304
12741305 /// Interpret this row as a boundary NTP source-NAT configuration.
0 commit comments