Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize blueprints in the database #4899

Merged
merged 25 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6845680
add blueprints to database schema and models
jgallagher Jan 18, 2024
07e2cef
add queries to read/write/delete blueprints from the db
jgallagher Jan 22, 2024
8d3cc25
Add queries for getting and setting the current blueprint target
jgallagher Jan 24, 2024
d5c3d24
doc comment fixes
jgallagher Jan 25, 2024
bfe97bb
sort zones in blueprint builder for testing niceness
jgallagher Jan 25, 2024
eaafb23
move schema to rebase onto main
jgallagher Jan 25, 2024
2bf00ff
replace nexus in-memory blueprints with database
jgallagher Jan 25, 2024
db84a5d
remove duplicate authz checks
jgallagher Jan 25, 2024
c5e4718
minor cleanup, mostly comments
jgallagher Jan 25, 2024
0681a8d
remove unused method
jgallagher Jan 25, 2024
e9d4bb1
fix nondeterministic test
jgallagher Jan 25, 2024
cfe797a
add missing migration file
jgallagher Jan 25, 2024
afc7243
remove "one we store blueprints in the db" comments
jgallagher Jan 26, 2024
dccd0d2
restore placeholder comment for blueprint execution
jgallagher Jan 26, 2024
ff88b37
comment cleanup
jgallagher Jan 26, 2024
14e7d5a
rename time_set -> time_made_target
jgallagher Jan 26, 2024
02d0540
use SqlU32 instead of not-really-signed i64
jgallagher Jan 26, 2024
f2f445c
fix OpenAPI spec
jgallagher Jan 26, 2024
0c14431
add clarifying comment
jgallagher Jan 26, 2024
fe8dcb0
typo fix
jgallagher Jan 26, 2024
45d0421
address "open question" comments
jgallagher Jan 26, 2024
bf84a0e
remove extra-cautious primary key check
jgallagher Jan 26, 2024
17abc3f
cargo fmt
jgallagher Jan 26, 2024
b3799f2
Merge remote-tracking branch 'origin/main' into john/blueprint-databa…
jgallagher Jan 26, 2024
df8a9e3
catch rename in omdb
jgallagher Jan 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

263 changes: 263 additions & 0 deletions nexus/db-model/src/deployment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Types for representing the deployed software and configuration in the
//! database

use crate::inventory::ZoneType;
use crate::omicron_zone_config::{OmicronZone, OmicronZoneNic};
use crate::schema::{
blueprint, bp_omicron_zone, bp_omicron_zone_nic,
bp_omicron_zones_not_in_service, bp_sled_omicron_zones, bp_target,
};
use crate::{ipv6, Generation, MacAddr, Name, SqlU16, SqlU32, SqlU8};
use chrono::{DateTime, Utc};
use ipnetwork::IpNetwork;
use nexus_types::deployment::BlueprintTarget;
use uuid::Uuid;

/// See [`nexus_types::deployment::Blueprint`].
#[derive(Queryable, Insertable, Clone, Debug, Selectable)]
#[diesel(table_name = blueprint)]
pub struct Blueprint {
pub id: Uuid,
pub parent_blueprint_id: Option<Uuid>,
pub time_created: DateTime<Utc>,
pub creator: String,
pub comment: String,
}

impl From<&'_ nexus_types::deployment::Blueprint> for Blueprint {
fn from(bp: &'_ nexus_types::deployment::Blueprint) -> Self {
Self {
id: bp.id,
parent_blueprint_id: bp.parent_blueprint_id,
time_created: bp.time_created,
creator: bp.creator.clone(),
comment: bp.comment.clone(),
}
}
}

impl From<Blueprint> for nexus_types::deployment::BlueprintMetadata {
fn from(value: Blueprint) -> Self {
Self {
id: value.id,
parent_blueprint_id: value.parent_blueprint_id,
time_created: value.time_created,
creator: value.creator,
comment: value.comment,
}
}
}

/// See [`nexus_types::deployment::BlueprintTarget`].
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_target)]
pub struct BpTarget {
pub version: i64, // i64 only for db serialization; should never be negative
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use one of the SqlU* types instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, switched to SqlU32. I wasn't 100% sure that was big enough to never worry about exhausting it, but I think I've convinced myself it's fine. Even if runaway automation set a new target every second (which seems pretty unlikely, since that first requires generating and inserting a new blueprint in its entirety), u32 is fine for 100+ years.

pub blueprint_id: Uuid,
pub enabled: bool,
pub time_made_target: DateTime<Utc>,
}

impl BpTarget {
pub fn new(version: i64, target: BlueprintTarget) -> Self {
Self {
version,
blueprint_id: target.target_id,
enabled: target.enabled,
time_made_target: target.time_set,
}
}
}

impl From<BpTarget> for nexus_types::deployment::BlueprintTarget {
fn from(value: BpTarget) -> Self {
Self {
target_id: value.blueprint_id,
enabled: value.enabled,
time_set: value.time_made_target,
jgallagher marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// See [`nexus_types::deployment::OmicronZonesConfig`].
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_sled_omicron_zones)]
pub struct BpSledOmicronZones {
pub blueprint_id: Uuid,
pub sled_id: Uuid,
pub generation: Generation,
}

impl BpSledOmicronZones {
pub fn new(
blueprint_id: Uuid,
sled_id: Uuid,
zones_config: &nexus_types::deployment::OmicronZonesConfig,
) -> Self {
Self {
blueprint_id,
sled_id,
generation: Generation(zones_config.generation),
}
}
}

/// See [`nexus_types::deployment::OmicronZoneConfig`].
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_omicron_zone)]
pub struct BpOmicronZone {
pub blueprint_id: Uuid,
pub sled_id: Uuid,
pub id: Uuid,
pub underlay_address: ipv6::Ipv6Addr,
pub zone_type: ZoneType,
pub primary_service_ip: ipv6::Ipv6Addr,
pub primary_service_port: SqlU16,
pub second_service_ip: Option<IpNetwork>,
pub second_service_port: Option<SqlU16>,
pub dataset_zpool_name: Option<String>,
pub bp_nic_id: Option<Uuid>,
pub dns_gz_address: Option<ipv6::Ipv6Addr>,
pub dns_gz_address_index: Option<SqlU32>,
pub ntp_ntp_servers: Option<Vec<String>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ntp_ntp intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is - these are NTP zone's NTP servers.

pub ntp_dns_servers: Option<Vec<IpNetwork>>,
pub ntp_domain: Option<String>,
pub nexus_external_tls: Option<bool>,
pub nexus_external_dns_servers: Option<Vec<IpNetwork>>,
pub snat_ip: Option<IpNetwork>,
pub snat_first_port: Option<SqlU16>,
pub snat_last_port: Option<SqlU16>,
}

impl BpOmicronZone {
pub fn new(
blueprint_id: Uuid,
sled_id: Uuid,
zone: &nexus_types::inventory::OmicronZoneConfig,
) -> Result<Self, anyhow::Error> {
let zone = OmicronZone::new(sled_id, zone)?;
Ok(Self {
blueprint_id,
sled_id: zone.sled_id,
id: zone.id,
underlay_address: zone.underlay_address,
zone_type: zone.zone_type,
primary_service_ip: zone.primary_service_ip,
primary_service_port: zone.primary_service_port,
second_service_ip: zone.second_service_ip,
second_service_port: zone.second_service_port,
dataset_zpool_name: zone.dataset_zpool_name,
bp_nic_id: zone.nic_id,
dns_gz_address: zone.dns_gz_address,
dns_gz_address_index: zone.dns_gz_address_index,
ntp_ntp_servers: zone.ntp_ntp_servers,
ntp_dns_servers: zone.ntp_dns_servers,
ntp_domain: zone.ntp_domain,
nexus_external_tls: zone.nexus_external_tls,
nexus_external_dns_servers: zone.nexus_external_dns_servers,
snat_ip: zone.snat_ip,
snat_first_port: zone.snat_first_port,
snat_last_port: zone.snat_last_port,
})
}

pub fn into_omicron_zone_config(
self,
nic_row: Option<BpOmicronZoneNic>,
) -> Result<nexus_types::inventory::OmicronZoneConfig, anyhow::Error> {
let zone = OmicronZone {
sled_id: self.sled_id,
id: self.id,
underlay_address: self.underlay_address,
zone_type: self.zone_type,
primary_service_ip: self.primary_service_ip,
primary_service_port: self.primary_service_port,
second_service_ip: self.second_service_ip,
second_service_port: self.second_service_port,
dataset_zpool_name: self.dataset_zpool_name,
nic_id: self.bp_nic_id,
dns_gz_address: self.dns_gz_address,
dns_gz_address_index: self.dns_gz_address_index,
ntp_ntp_servers: self.ntp_ntp_servers,
ntp_dns_servers: self.ntp_dns_servers,
ntp_domain: self.ntp_domain,
nexus_external_tls: self.nexus_external_tls,
nexus_external_dns_servers: self.nexus_external_dns_servers,
snat_ip: self.snat_ip,
snat_first_port: self.snat_first_port,
snat_last_port: self.snat_last_port,
};
zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from))
}
}

#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_omicron_zone_nic)]
pub struct BpOmicronZoneNic {
blueprint_id: Uuid,
pub id: Uuid,
name: Name,
ip: IpNetwork,
mac: MacAddr,
subnet: IpNetwork,
vni: SqlU32,
is_primary: bool,
slot: SqlU8,
}

impl From<BpOmicronZoneNic> for OmicronZoneNic {
fn from(value: BpOmicronZoneNic) -> Self {
OmicronZoneNic {
id: value.id,
name: value.name,
ip: value.ip,
mac: value.mac,
subnet: value.subnet,
vni: value.vni,
is_primary: value.is_primary,
slot: value.slot,
}
}
}

impl BpOmicronZoneNic {
pub fn new(
blueprint_id: Uuid,
zone: &nexus_types::inventory::OmicronZoneConfig,
) -> Result<Option<BpOmicronZoneNic>, anyhow::Error> {
let zone_nic = OmicronZoneNic::new(zone)?;
Ok(zone_nic.map(|nic| Self {
blueprint_id,
id: nic.id,
name: nic.name,
ip: nic.ip,
mac: nic.mac,
subnet: nic.subnet,
vni: nic.vni,
is_primary: nic.is_primary,
slot: nic.slot,
}))
}

pub fn into_network_interface_for_zone(
self,
zone_id: Uuid,
) -> Result<nexus_types::inventory::NetworkInterface, anyhow::Error> {
let zone_nic = OmicronZoneNic::from(self);
zone_nic.into_network_interface_for_zone(zone_id)
}
}

/// Nexus wants to think in terms of "zones in service", but since most zones of
/// most blueprints are in service, we store the zones NOT in service in the
/// database. We handle that inversion internally in the db-queries layer.
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_omicron_zones_not_in_service)]
pub struct BpOmicronZoneNotInService {
pub blueprint_id: Uuid,
pub bp_omicron_zone_id: Uuid,
}
Loading
Loading