Skip to content

Commit 80cc001

Browse files
authored
Serialize blueprints in the database (#4899)
This replaces the in-memory blueprint storage added as a placeholder in #4804 with cockroachdb-backed tables. Both the tables and related queries are _heavily_ derived from the similar tables in the inventory system (particularly serializing omicron zones and their related properties). The tables are effectively identical as of this PR, but we opted to keep the separate because we expect them to diverge some over time (e.g., inventory might start collecting additional per-zone properties that don't exist for blueprints, such as uptime). The big exception to "basically the same as inventory" is the `bp_target` table which tracks the current (and past) target blueprint. Inserting into this table has some subtleties, and we use a CTE to check and enforce the invariants. This is the first diesel/CTE I've written; it's based on other similar CTEs in Nexus, but I'd still appreciate a particularly careful look there. Fixes #4793.
1 parent a3a9844 commit 80cc001

File tree

26 files changed

+2884
-592
lines changed

26 files changed

+2884
-592
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ async fn cmd_nexus_blueprints_target_show(
866866
.await
867867
.context("fetching target blueprint")?;
868868
println!("target blueprint: {}", target.target_id);
869-
println!("set at: {}", target.time_set);
869+
println!("made target at: {}", target.time_made_target);
870870
println!("enabled: {}", target.enabled);
871871
Ok(())
872872
}

nexus/db-model/src/deployment.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! Types for representing the deployed software and configuration in the
6+
//! database
7+
8+
use crate::inventory::ZoneType;
9+
use crate::omicron_zone_config::{OmicronZone, OmicronZoneNic};
10+
use crate::schema::{
11+
blueprint, bp_omicron_zone, bp_omicron_zone_nic,
12+
bp_omicron_zones_not_in_service, bp_sled_omicron_zones, bp_target,
13+
};
14+
use crate::{ipv6, Generation, MacAddr, Name, SqlU16, SqlU32, SqlU8};
15+
use chrono::{DateTime, Utc};
16+
use ipnetwork::IpNetwork;
17+
use nexus_types::deployment::BlueprintTarget;
18+
use uuid::Uuid;
19+
20+
/// See [`nexus_types::deployment::Blueprint`].
21+
#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
22+
#[diesel(table_name = blueprint)]
23+
pub struct Blueprint {
24+
pub id: Uuid,
25+
pub parent_blueprint_id: Option<Uuid>,
26+
pub time_created: DateTime<Utc>,
27+
pub creator: String,
28+
pub comment: String,
29+
}
30+
31+
impl From<&'_ nexus_types::deployment::Blueprint> for Blueprint {
32+
fn from(bp: &'_ nexus_types::deployment::Blueprint) -> Self {
33+
Self {
34+
id: bp.id,
35+
parent_blueprint_id: bp.parent_blueprint_id,
36+
time_created: bp.time_created,
37+
creator: bp.creator.clone(),
38+
comment: bp.comment.clone(),
39+
}
40+
}
41+
}
42+
43+
impl From<Blueprint> for nexus_types::deployment::BlueprintMetadata {
44+
fn from(value: Blueprint) -> Self {
45+
Self {
46+
id: value.id,
47+
parent_blueprint_id: value.parent_blueprint_id,
48+
time_created: value.time_created,
49+
creator: value.creator,
50+
comment: value.comment,
51+
}
52+
}
53+
}
54+
55+
/// See [`nexus_types::deployment::BlueprintTarget`].
56+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
57+
#[diesel(table_name = bp_target)]
58+
pub struct BpTarget {
59+
pub version: SqlU32,
60+
pub blueprint_id: Uuid,
61+
pub enabled: bool,
62+
pub time_made_target: DateTime<Utc>,
63+
}
64+
65+
impl BpTarget {
66+
pub fn new(version: u32, target: BlueprintTarget) -> Self {
67+
Self {
68+
version: version.into(),
69+
blueprint_id: target.target_id,
70+
enabled: target.enabled,
71+
time_made_target: target.time_made_target,
72+
}
73+
}
74+
}
75+
76+
impl From<BpTarget> for nexus_types::deployment::BlueprintTarget {
77+
fn from(value: BpTarget) -> Self {
78+
Self {
79+
target_id: value.blueprint_id,
80+
enabled: value.enabled,
81+
time_made_target: value.time_made_target,
82+
}
83+
}
84+
}
85+
86+
/// See [`nexus_types::deployment::OmicronZonesConfig`].
87+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
88+
#[diesel(table_name = bp_sled_omicron_zones)]
89+
pub struct BpSledOmicronZones {
90+
pub blueprint_id: Uuid,
91+
pub sled_id: Uuid,
92+
pub generation: Generation,
93+
}
94+
95+
impl BpSledOmicronZones {
96+
pub fn new(
97+
blueprint_id: Uuid,
98+
sled_id: Uuid,
99+
zones_config: &nexus_types::deployment::OmicronZonesConfig,
100+
) -> Self {
101+
Self {
102+
blueprint_id,
103+
sled_id,
104+
generation: Generation(zones_config.generation),
105+
}
106+
}
107+
}
108+
109+
/// See [`nexus_types::deployment::OmicronZoneConfig`].
110+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
111+
#[diesel(table_name = bp_omicron_zone)]
112+
pub struct BpOmicronZone {
113+
pub blueprint_id: Uuid,
114+
pub sled_id: Uuid,
115+
pub id: Uuid,
116+
pub underlay_address: ipv6::Ipv6Addr,
117+
pub zone_type: ZoneType,
118+
pub primary_service_ip: ipv6::Ipv6Addr,
119+
pub primary_service_port: SqlU16,
120+
pub second_service_ip: Option<IpNetwork>,
121+
pub second_service_port: Option<SqlU16>,
122+
pub dataset_zpool_name: Option<String>,
123+
pub bp_nic_id: Option<Uuid>,
124+
pub dns_gz_address: Option<ipv6::Ipv6Addr>,
125+
pub dns_gz_address_index: Option<SqlU32>,
126+
pub ntp_ntp_servers: Option<Vec<String>>,
127+
pub ntp_dns_servers: Option<Vec<IpNetwork>>,
128+
pub ntp_domain: Option<String>,
129+
pub nexus_external_tls: Option<bool>,
130+
pub nexus_external_dns_servers: Option<Vec<IpNetwork>>,
131+
pub snat_ip: Option<IpNetwork>,
132+
pub snat_first_port: Option<SqlU16>,
133+
pub snat_last_port: Option<SqlU16>,
134+
}
135+
136+
impl BpOmicronZone {
137+
pub fn new(
138+
blueprint_id: Uuid,
139+
sled_id: Uuid,
140+
zone: &nexus_types::inventory::OmicronZoneConfig,
141+
) -> Result<Self, anyhow::Error> {
142+
let zone = OmicronZone::new(sled_id, zone)?;
143+
Ok(Self {
144+
blueprint_id,
145+
sled_id: zone.sled_id,
146+
id: zone.id,
147+
underlay_address: zone.underlay_address,
148+
zone_type: zone.zone_type,
149+
primary_service_ip: zone.primary_service_ip,
150+
primary_service_port: zone.primary_service_port,
151+
second_service_ip: zone.second_service_ip,
152+
second_service_port: zone.second_service_port,
153+
dataset_zpool_name: zone.dataset_zpool_name,
154+
bp_nic_id: zone.nic_id,
155+
dns_gz_address: zone.dns_gz_address,
156+
dns_gz_address_index: zone.dns_gz_address_index,
157+
ntp_ntp_servers: zone.ntp_ntp_servers,
158+
ntp_dns_servers: zone.ntp_dns_servers,
159+
ntp_domain: zone.ntp_domain,
160+
nexus_external_tls: zone.nexus_external_tls,
161+
nexus_external_dns_servers: zone.nexus_external_dns_servers,
162+
snat_ip: zone.snat_ip,
163+
snat_first_port: zone.snat_first_port,
164+
snat_last_port: zone.snat_last_port,
165+
})
166+
}
167+
168+
pub fn into_omicron_zone_config(
169+
self,
170+
nic_row: Option<BpOmicronZoneNic>,
171+
) -> Result<nexus_types::inventory::OmicronZoneConfig, anyhow::Error> {
172+
let zone = OmicronZone {
173+
sled_id: self.sled_id,
174+
id: self.id,
175+
underlay_address: self.underlay_address,
176+
zone_type: self.zone_type,
177+
primary_service_ip: self.primary_service_ip,
178+
primary_service_port: self.primary_service_port,
179+
second_service_ip: self.second_service_ip,
180+
second_service_port: self.second_service_port,
181+
dataset_zpool_name: self.dataset_zpool_name,
182+
nic_id: self.bp_nic_id,
183+
dns_gz_address: self.dns_gz_address,
184+
dns_gz_address_index: self.dns_gz_address_index,
185+
ntp_ntp_servers: self.ntp_ntp_servers,
186+
ntp_dns_servers: self.ntp_dns_servers,
187+
ntp_domain: self.ntp_domain,
188+
nexus_external_tls: self.nexus_external_tls,
189+
nexus_external_dns_servers: self.nexus_external_dns_servers,
190+
snat_ip: self.snat_ip,
191+
snat_first_port: self.snat_first_port,
192+
snat_last_port: self.snat_last_port,
193+
};
194+
zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from))
195+
}
196+
}
197+
198+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
199+
#[diesel(table_name = bp_omicron_zone_nic)]
200+
pub struct BpOmicronZoneNic {
201+
blueprint_id: Uuid,
202+
pub id: Uuid,
203+
name: Name,
204+
ip: IpNetwork,
205+
mac: MacAddr,
206+
subnet: IpNetwork,
207+
vni: SqlU32,
208+
is_primary: bool,
209+
slot: SqlU8,
210+
}
211+
212+
impl From<BpOmicronZoneNic> for OmicronZoneNic {
213+
fn from(value: BpOmicronZoneNic) -> Self {
214+
OmicronZoneNic {
215+
id: value.id,
216+
name: value.name,
217+
ip: value.ip,
218+
mac: value.mac,
219+
subnet: value.subnet,
220+
vni: value.vni,
221+
is_primary: value.is_primary,
222+
slot: value.slot,
223+
}
224+
}
225+
}
226+
227+
impl BpOmicronZoneNic {
228+
pub fn new(
229+
blueprint_id: Uuid,
230+
zone: &nexus_types::inventory::OmicronZoneConfig,
231+
) -> Result<Option<BpOmicronZoneNic>, anyhow::Error> {
232+
let zone_nic = OmicronZoneNic::new(zone)?;
233+
Ok(zone_nic.map(|nic| Self {
234+
blueprint_id,
235+
id: nic.id,
236+
name: nic.name,
237+
ip: nic.ip,
238+
mac: nic.mac,
239+
subnet: nic.subnet,
240+
vni: nic.vni,
241+
is_primary: nic.is_primary,
242+
slot: nic.slot,
243+
}))
244+
}
245+
246+
pub fn into_network_interface_for_zone(
247+
self,
248+
zone_id: Uuid,
249+
) -> Result<nexus_types::inventory::NetworkInterface, anyhow::Error> {
250+
let zone_nic = OmicronZoneNic::from(self);
251+
zone_nic.into_network_interface_for_zone(zone_id)
252+
}
253+
}
254+
255+
/// Nexus wants to think in terms of "zones in service", but since most zones of
256+
/// most blueprints are in service, we store the zones NOT in service in the
257+
/// database. We handle that inversion internally in the db-queries layer.
258+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
259+
#[diesel(table_name = bp_omicron_zones_not_in_service)]
260+
pub struct BpOmicronZoneNotInService {
261+
pub blueprint_id: Uuid,
262+
pub bp_omicron_zone_id: Uuid,
263+
}

0 commit comments

Comments
 (0)