Skip to content

Commit 5fcc1bd

Browse files
committed
when adding a zone, check and mark its underlay IP as allocated
1 parent 8aa1f4f commit 5fcc1bd

File tree

2 files changed

+53
-26
lines changed

2 files changed

+53
-26
lines changed

nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ pub enum SledEditError {
117117
ZoneOnNonexistentZpool { zone_id: OmicronZoneUuid, zpool: ZpoolName },
118118
#[error("ran out of underlay IP addresses")]
119119
OutOfUnderlayIps,
120+
#[error("cannot add zone with invalid underlay IP")]
121+
AddZoneBadUnderlayIp(#[source] SledUnderlayIpOutOfRange),
120122
}
121123

122124
#[derive(Debug)]
@@ -350,18 +352,9 @@ impl ActiveSledEditor {
350352
// dispositions. If a zone has been fully removed from the blueprint
351353
// some time after expungement, we may reuse its IP; reconfigurator must
352354
// know that's safe prior to pruning the expunged zone.
353-
let zone_ips = zones.zones(BlueprintZoneFilter::All).filter_map(|z| {
354-
// Internal DNS zone's IPs are (intentionally!) outside the sled
355-
// subnet, and must be allocated across the rack as a whole. We'll
356-
// ignore any internal DNS zones when constructing our underlay IP
357-
// allocator (as `SledUnderlayIpAllocator` will fail if we tell it a
358-
// zone is using an IP outside of the sled subnet).
359-
if z.zone_type.is_internal_dns() {
360-
None
361-
} else {
362-
Some((z.zone_type.kind(), z.underlay_ip()))
363-
}
364-
});
355+
let zone_ips = zones
356+
.zones(BlueprintZoneFilter::All)
357+
.map(|z| (z.zone_type.kind(), z.underlay_ip()));
365358

366359
Ok(Self {
367360
underlay_ip_allocator: SledUnderlayIpAllocator::new(
@@ -542,6 +535,13 @@ impl ActiveSledEditor {
542535
// Ensure we can construct the configs for the datasets for this zone.
543536
let datasets = ZoneDatasetConfigs::new(&self.disks, &zone)?;
544537

538+
// Ensure this zone's IP is within our subnet and that future IP
539+
// allocations take it into account. (Unless `zone` is an internal DNS
540+
// zone, in which case validation is higher-level planner's job.)
541+
self.underlay_ip_allocator
542+
.mark_as_allocated(zone.zone_type.kind(), zone.underlay_ip())
543+
.map_err(SledEditError::AddZoneBadUnderlayIp)?;
544+
545545
// Actually add the zone and its datasets.
546546
self.zones.add_zone(zone)?;
547547
datasets.ensure_in_service(&mut self.datasets, rng);

nexus/reconfigurator/planning/src/blueprint_editor/sled_editor/underlay_ip_allocator.rs

+41-14
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub struct SledUnderlayIpOutOfRange {
3333
// general enough to use here, though this one could potentially be used there.
3434
#[derive(Debug)]
3535
pub(crate) struct SledUnderlayIpAllocator {
36+
minimum: Ipv6Addr,
3637
last: Ipv6Addr,
3738
maximum: Ipv6Addr,
3839
}
@@ -68,22 +69,48 @@ impl SledUnderlayIpAllocator {
6869
assert!(sled_subnet.net().contains(minimum));
6970
assert!(sled_subnet.net().contains(maximum));
7071

71-
let mut last = minimum;
72-
for (zone_kind, reserved_ip) in in_use_zone_ips {
73-
if reserved_ip < minimum || reserved_ip > maximum {
74-
return Err(SledUnderlayIpOutOfRange {
75-
zone_kind,
76-
ip: reserved_ip,
77-
low: minimum,
78-
high: maximum,
79-
});
80-
}
81-
last = Ipv6Addr::max(last, reserved_ip);
72+
let mut slf = Self { minimum, last: minimum, maximum };
73+
for (zone_kind, ip) in in_use_zone_ips {
74+
slf.mark_as_allocated(zone_kind, ip)?;
8275
}
83-
assert!(minimum <= last);
84-
assert!(last <= maximum);
76+
assert!(slf.minimum <= slf.last);
77+
assert!(slf.last <= slf.maximum);
8578

86-
Ok(Self { last, maximum })
79+
Ok(slf)
80+
}
81+
82+
/// Mark an address as used.
83+
///
84+
/// Marking an address that has already been handed out by this allocator
85+
/// (or could have been handed out by this allocator) is allowed and does
86+
/// nothing.
87+
///
88+
/// # Errors
89+
///
90+
/// Fails if `ip` is outside the subnet of this sled.
91+
pub fn mark_as_allocated(
92+
&mut self,
93+
zone_kind: ZoneKind,
94+
ip: Ipv6Addr,
95+
) -> Result<(), SledUnderlayIpOutOfRange> {
96+
// We intentionally ignore any internal DNS underlay IPs; they live
97+
// outside the sled subnet and are allocated separately.
98+
if zone_kind == ZoneKind::InternalDns {
99+
return Ok(());
100+
}
101+
102+
if ip < self.minimum || ip > self.maximum {
103+
return Err(SledUnderlayIpOutOfRange {
104+
zone_kind,
105+
ip,
106+
low: self.minimum,
107+
high: self.maximum,
108+
});
109+
}
110+
111+
self.last = Ipv6Addr::max(self.last, ip);
112+
113+
Ok(())
87114
}
88115

89116
/// Allocate an unused address from this allocator's range

0 commit comments

Comments
 (0)