diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 5fc17c88d0..501af6e244 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -79,6 +79,17 @@ impl DataStore { &self, opctx: &OpContext, blueprint: &Blueprint, + ) -> Result<(), Error> { + let conn = self.pool_connection_authorized(opctx).await?; + Self::blueprint_insert_on_connection(&conn, opctx, blueprint).await + } + + /// Variant of [Self::blueprint_insert] which may be called from a + /// transaction context. + pub(crate) async fn blueprint_insert_on_connection( + conn: &async_bb8_diesel::Connection, + opctx: &OpContext, + blueprint: &Blueprint, ) -> Result<(), Error> { opctx .authorize(authz::Action::Modify, &authz::BLUEPRINT_CONFIG) @@ -152,8 +163,7 @@ impl DataStore { // batch rather than making a bunch of round-trips to the database. // We'd do that if we had an interface for doing that with bound // parameters, etc. See oxidecomputer/omicron#973. - let pool = self.pool_connection_authorized(opctx).await?; - pool.transaction_async(|conn| async move { + conn.transaction_async(|conn| async move { // Insert the row for the blueprint. { use db::schema::blueprint::dsl; @@ -620,6 +630,18 @@ impl DataStore { &self, opctx: &OpContext, target: BlueprintTarget, + ) -> Result<(), Error> { + let conn = self.pool_connection_authorized(opctx).await?; + Self::blueprint_target_set_current_on_connection(&conn, opctx, target) + .await + } + + /// Variant of [Self::blueprint_target_set_current] which may be called from + /// a transaction context. + pub(crate) async fn blueprint_target_set_current_on_connection( + conn: &async_bb8_diesel::Connection, + opctx: &OpContext, + target: BlueprintTarget, ) -> Result<(), Error> { opctx .authorize(authz::Action::Modify, &authz::BLUEPRINT_CONFIG) @@ -631,10 +653,8 @@ impl DataStore { time_made_target: target.time_made_target, }; - let conn = self.pool_connection_authorized(opctx).await?; - query - .execute_async(&*conn) + .execute_async(conn) .await .map_err(|e| Error::from(query.decode_error(e)))?; diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index 3224f36b8d..bc962e4b4d 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -44,6 +44,8 @@ use nexus_db_model::PasswordHashString; use nexus_db_model::SiloUser; use nexus_db_model::SiloUserPasswordHash; use nexus_db_model::SledUnderlaySubnetAllocation; +use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintTarget; use nexus_types::external_api::params as external_params; use nexus_types::external_api::shared; use nexus_types::external_api::shared::IdentityType; @@ -68,6 +70,7 @@ use uuid::Uuid; pub struct RackInit { pub rack_id: Uuid, pub rack_subnet: IpNetwork, + pub blueprint: Blueprint, pub services: Vec, pub datasets: Vec, pub service_ip_pool_ranges: Vec, @@ -85,6 +88,8 @@ pub struct RackInit { enum RackInitError { AddingIp(Error), AddingNic(Error), + BlueprintInsert(Error), + BlueprintTargetSet(Error), ServiceInsert(Error), DatasetInsert { err: AsyncInsertError, zpool_id: Uuid }, RackUpdate { err: DieselError, rack_id: Uuid }, @@ -126,6 +131,12 @@ impl From for Error { RackInitError::ServiceInsert(err) => Error::internal_error( &format!("failed to insert Service record: {:#}", err), ), + RackInitError::BlueprintInsert(err) => Error::internal_error( + &format!("failed to insert Blueprint: {:#}", err), + ), + RackInitError::BlueprintTargetSet(err) => Error::internal_error( + &format!("failed to insert set target Blueprint: {:#}", err), + ), RackInitError::RackUpdate { err, rack_id } => { public_error_from_diesel( err, @@ -583,6 +594,7 @@ impl DataStore { let service_pool = service_pool.clone(); async move { let rack_id = rack_init.rack_id; + let blueprint = rack_init.blueprint; let services = rack_init.services; let datasets = rack_init.datasets; let service_ip_pool_ranges = rack_init.service_ip_pool_ranges; @@ -608,7 +620,7 @@ impl DataStore { return Ok::<_, DieselError>(rack); } - // Otherwise, insert services and datasets. + // Otherwise, insert blueprint and datasets. // Set up the IP pool for internal services. for range in service_ip_pool_ranges { @@ -629,6 +641,46 @@ impl DataStore { })?; } + // Insert the RSS-generated blueprint. + Self::blueprint_insert_on_connection( + &conn, + opctx, + &blueprint, + ).await + .map_err(|e| { + warn!( + log, + "Initializing Rack: Failed to insert blueprint" + ); + err.set(RackInitError::BlueprintInsert(e)).unwrap(); + DieselError::RollbackTransaction + })?; + + // Mark the RSS-generated blueprint as the current target, + // DISABLED. We may change this to enabled in the future + // when more of Reconfigurator is automated, but for now we + // require a support operation to enable it. + Self::blueprint_target_set_current_on_connection( + &conn, + opctx, + BlueprintTarget { + target_id: blueprint.id, + enabled: false, + time_made_target: Utc::now(), + }, + ) + .await + .map_err(|e| { + warn!( + log, + "Initializing Rack: \ + Failed to set blueprint as target" + ); + err.set(RackInitError::BlueprintTargetSet(e)) + .unwrap(); + DieselError::RollbackTransaction + })?; + // Allocate records for all services. for service in services { self.rack_populate_service_records( @@ -882,7 +934,7 @@ mod test { }; use omicron_common::api::internal::shared::SourceNatConfig; use omicron_test_utils::dev; - use std::collections::HashMap; + use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6}; use std::num::NonZeroU32; @@ -893,6 +945,16 @@ mod test { RackInit { rack_id: Uuid::parse_str(nexus_test_utils::RACK_UUID).unwrap(), rack_subnet: nexus_test_utils::RACK_SUBNET.parse().unwrap(), + blueprint: Blueprint { + id: Uuid::new_v4(), + omicron_zones: BTreeMap::new(), + zones_in_service: BTreeSet::new(), + parent_blueprint_id: None, + internal_dns_version: Generation::new().next(), + time_created: Utc::now(), + creator: "test suite".to_string(), + comment: "test suite".to_string(), + }, services: vec![], datasets: vec![], service_ip_pool_ranges: vec![], diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 4030fce31d..079578619a 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -221,6 +221,7 @@ impl super::Nexus { RackInit { rack_subnet: rack_network_config.rack_subnet.into(), rack_id, + blueprint: request.blueprint, services: request.services, datasets, service_ip_pool_ranges,