Skip to content

Commit f2ea800

Browse files
authored
[Reconfigurator] Introduce PlanningInput that includes external networking information from CRDB (#5344)
The primary change in this PR is that the blueprint planner now wants a `PlanningInput` (which contains a `Policy`) instead of a `Policy`. The bulk of the diff is adding new `DataStore` methods (and tests for them) that fetch all currently-allocated external IPs and NICs for services. Some incidental changes that came along for the ride that I hope are not controversial, but could be backed out if they are: * `nexus_db_model::NetworkInterface::slot` is now a `SqlU8` instead of an `i16`. I didn't have to change the queries here, so I think they're still converting this to an `i16`, which is probably okay? I could make a pass on them if needed though. * I added an `omicron_uuid_kinds::ServiceKind` and started using it in this PR. I did not attempt to make a pass through all service UUIDs to start using this; I think this can be done incrementally? Other notes: * I'm not sure about the name `PlanningInput`. It feels vague; isn't every argument to the planner a kind of "planning input"? But I'm not sure what else to call "`Policy` plus extra CRDB state". * This does not change execution at all. It's possible when I get to that there will need to be some changes here, but I think this is probably close enough that it can be reviewed, and any changes will be small and can be rolled into the execution work.
1 parent e961b0b commit f2ea800

File tree

21 files changed

+793
-168
lines changed

21 files changed

+793
-168
lines changed

Cargo.lock

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-tools/omdb/src/bin/omdb/db.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2120,7 +2120,7 @@ async fn cmd_db_network_list_vnics(
21202120
struct NicRow {
21212121
ip: IpNetwork,
21222122
mac: MacAddr,
2123-
slot: i16,
2123+
slot: u8,
21242124
primary: bool,
21252125
kind: &'static str,
21262126
subnet: String,
@@ -2241,7 +2241,7 @@ async fn cmd_db_network_list_vnics(
22412241
let row = NicRow {
22422242
ip: nic.ip,
22432243
mac: *nic.mac,
2244-
slot: nic.slot,
2244+
slot: *nic.slot,
22452245
primary: nic.primary,
22462246
kind,
22472247
subnet,

dev-tools/reconfigurator-cli/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ nexus-reconfigurator-planning.workspace = true
2020
nexus-reconfigurator-execution.workspace = true
2121
nexus-types.workspace = true
2222
omicron-common.workspace = true
23+
omicron-uuid-kinds.workspace = true
2324
# See omicron-rpaths for more about the "pq-sys" dependency.
2425
pq-sys = "*"
2526
reedline.workspace = true

dev-tools/reconfigurator-cli/src/main.rs

+62-5
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,22 @@ use nexus_reconfigurator_planning::planner::Planner;
2020
use nexus_reconfigurator_planning::system::{
2121
SledBuilder, SledHwInventory, SystemDescription,
2222
};
23+
use nexus_types::deployment::ExternalIp;
24+
use nexus_types::deployment::PlanningInput;
25+
use nexus_types::deployment::ServiceNetworkInterface;
2326
use nexus_types::deployment::{Blueprint, UnstableReconfiguratorState};
2427
use nexus_types::internal_api::params::DnsConfigParams;
2528
use nexus_types::inventory::Collection;
2629
use nexus_types::inventory::OmicronZonesConfig;
2730
use nexus_types::inventory::SledRole;
2831
use omicron_common::api::external::Generation;
2932
use omicron_common::api::external::Name;
33+
use omicron_uuid_kinds::{GenericUuid, OmicronZoneKind, TypedUuid};
3034
use reedline::{Reedline, Signal};
35+
use std::cell::RefCell;
3136
use std::collections::BTreeMap;
3237
use std::io::BufRead;
38+
use std::net::IpAddr;
3339
use swrite::{swriteln, SWrite};
3440
use tabled::Tabled;
3541
use uuid::Uuid;
@@ -50,6 +56,14 @@ struct ReconfiguratorSim {
5056
/// blueprints created by the user
5157
blueprints: IndexMap<Uuid, Blueprint>,
5258

59+
/// external IPs allocated to services
60+
///
61+
/// In the real system, external IPs have IDs, but those IDs only live in
62+
/// CRDB - they're not part of the zone config sent from Reconfigurator to
63+
/// sled-agent. This mimics the minimal bit of the CRDB `external_ip` table
64+
/// we need.
65+
external_ips: RefCell<IndexMap<IpAddr, Uuid>>,
66+
5367
/// internal DNS configurations
5468
internal_dns: BTreeMap<Generation, DnsConfigParams>,
5569
/// external DNS configurations
@@ -92,6 +106,49 @@ impl ReconfiguratorSim {
92106
let _ = entry.or_insert(blueprint);
93107
Ok(())
94108
}
109+
110+
fn planning_input(
111+
&self,
112+
parent_blueprint: &Blueprint,
113+
) -> anyhow::Result<PlanningInput> {
114+
let policy = self.system.to_policy().context("generating policy")?;
115+
let service_external_ips = parent_blueprint
116+
.all_omicron_zones()
117+
.filter_map(|(_, zone)| {
118+
let Ok(Some(ip)) = zone.zone_type.external_ip() else {
119+
return None;
120+
};
121+
let service_id =
122+
TypedUuid::<OmicronZoneKind>::from_untyped_uuid(zone.id);
123+
let external_ip = ExternalIp {
124+
id: *self
125+
.external_ips
126+
.borrow_mut()
127+
.entry(ip)
128+
.or_insert_with(Uuid::new_v4),
129+
ip: ip.into(),
130+
};
131+
Some((service_id, external_ip))
132+
})
133+
.collect();
134+
let service_nics = parent_blueprint
135+
.all_omicron_zones()
136+
.filter_map(|(_, zone)| {
137+
let nic = zone.zone_type.service_vnic()?;
138+
let service_id =
139+
TypedUuid::<OmicronZoneKind>::from_untyped_uuid(zone.id);
140+
let nic = ServiceNetworkInterface {
141+
id: nic.id,
142+
mac: nic.mac,
143+
ip: nic.ip.into(),
144+
slot: nic.slot,
145+
primary: nic.primary,
146+
};
147+
Some((service_id, nic))
148+
})
149+
.collect();
150+
Ok(PlanningInput { policy, service_external_ips, service_nics })
151+
}
95152
}
96153

97154
/// interactive REPL for exploring the planner
@@ -115,6 +172,7 @@ fn main() -> anyhow::Result<()> {
115172
system: SystemDescription::new(),
116173
collections: IndexMap::new(),
117174
blueprints: IndexMap::new(),
175+
external_ips: RefCell::new(IndexMap::new()),
118176
internal_dns: BTreeMap::new(),
119177
external_dns: BTreeMap::new(),
120178
log,
@@ -655,9 +713,8 @@ fn cmd_blueprint_plan(
655713
.collections
656714
.get(&collection_id)
657715
.ok_or_else(|| anyhow!("no such collection: {}", collection_id))?;
658-
let policy = sim.system.to_policy().context("generating policy")?;
659716
let creator = "reconfigurator-sim";
660-
717+
let planning_input = sim.planning_input(parent_blueprint)?;
661718
let planner = Planner::new_based_on(
662719
sim.log.clone(),
663720
parent_blueprint,
@@ -688,7 +745,7 @@ fn cmd_blueprint_plan(
688745
// matter, either. We'll just pick the parent blueprint's.
689746
parent_blueprint.internal_dns_version,
690747
parent_blueprint.external_dns_version,
691-
&policy,
748+
&planning_input,
692749
creator,
693750
collection,
694751
)
@@ -709,13 +766,13 @@ fn cmd_blueprint_edit(
709766
let blueprint_id = args.blueprint_id;
710767
let blueprint = sim.blueprint_lookup(blueprint_id)?;
711768
let creator = args.creator.as_deref().unwrap_or("reconfigurator-cli");
712-
let policy = sim.system.to_policy().context("assembling policy")?;
769+
let planning_input = sim.planning_input(blueprint)?;
713770
let mut builder = BlueprintBuilder::new_based_on(
714771
&sim.log,
715772
&blueprint,
716773
blueprint.internal_dns_version,
717774
blueprint.external_dns_version,
718-
&policy,
775+
&planning_input,
719776
creator,
720777
)
721778
.context("creating blueprint builder")?;

nexus/db-model/src/external_ip.rs

+6
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ pub struct ExternalIp {
130130
pub is_probe: bool,
131131
}
132132

133+
impl From<ExternalIp> for nexus_types::deployment::ExternalIp {
134+
fn from(ext_ip: ExternalIp) -> Self {
135+
Self { id: ext_ip.id, ip: ext_ip.ip }
136+
}
137+
}
138+
133139
/// A view type constructed from `ExternalIp` used to represent Floating IP
134140
/// objects in user-facing APIs.
135141
///

nexus/db-model/src/network_interface.rs

+20-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::schema::instance_network_interface;
88
use crate::schema::network_interface;
99
use crate::schema::service_network_interface;
1010
use crate::Name;
11+
use crate::SqlU8;
1112
use chrono::DateTime;
1213
use chrono::Utc;
1314
use db_macros::Resource;
@@ -59,7 +60,7 @@ pub struct NetworkInterface {
5960
// If neither is specified, auto-assign one of each?
6061
pub ip: ipnetwork::IpNetwork,
6162

62-
pub slot: i16,
63+
pub slot: SqlU8,
6364
#[diesel(column_name = is_primary)]
6465
pub primary: bool,
6566
}
@@ -91,10 +92,10 @@ impl NetworkInterface {
9192
name: self.name().clone(),
9293
ip: self.ip.ip(),
9394
mac: self.mac.into(),
94-
subnet: subnet,
95+
subnet,
9596
vni: external::Vni::try_from(0).unwrap(),
9697
primary: self.primary,
97-
slot: self.slot.try_into().unwrap(),
98+
slot: *self.slot,
9899
}
99100
}
100101
}
@@ -117,7 +118,7 @@ pub struct InstanceNetworkInterface {
117118
pub mac: MacAddr,
118119
pub ip: ipnetwork::IpNetwork,
119120

120-
pub slot: i16,
121+
pub slot: SqlU8,
121122
#[diesel(column_name = is_primary)]
122123
pub primary: bool,
123124
}
@@ -140,11 +141,25 @@ pub struct ServiceNetworkInterface {
140141
pub mac: MacAddr,
141142
pub ip: ipnetwork::IpNetwork,
142143

143-
pub slot: i16,
144+
pub slot: SqlU8,
144145
#[diesel(column_name = is_primary)]
145146
pub primary: bool,
146147
}
147148

149+
impl From<ServiceNetworkInterface>
150+
for nexus_types::deployment::ServiceNetworkInterface
151+
{
152+
fn from(nic: ServiceNetworkInterface) -> Self {
153+
Self {
154+
id: nic.id(),
155+
mac: *nic.mac,
156+
ip: nic.ip,
157+
slot: *nic.slot,
158+
primary: nic.primary,
159+
}
160+
}
161+
}
162+
148163
impl NetworkInterface {
149164
/// Treat this `NetworkInterface` as an `InstanceNetworkInterface`.
150165
///

0 commit comments

Comments
 (0)