From 23a3335313f94141bdb44555215e3b8f753425c4 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 27 Aug 2025 10:27:01 -0700 Subject: [PATCH 1/6] (1/N) db_metadata_nexus schema changes, db queries. Populate the tables --- nexus/db-model/src/db_metadata.rs | 50 ++ nexus/db-model/src/schema_versions.rs | 6 +- .../src/db/datastore/db_metadata.rs | 635 +++++++++++++++++- nexus/db-queries/src/db/datastore/rack.rs | 17 + nexus/db-schema/src/enums.rs | 1 + nexus/db-schema/src/schema.rs | 8 + .../reconfigurator/execution/src/database.rs | 24 + nexus/reconfigurator/execution/src/lib.rs | 31 + .../execution/src/omicron_zones.rs | 9 +- nexus/tests/integration_tests/schema.rs | 156 ++++- nexus/types/src/deployment/execution/spec.rs | 1 + schema/crdb/dbinit.sql | 82 ++- .../crdb/populate-db-metadata-nexus/up01.sql | 6 + .../crdb/populate-db-metadata-nexus/up02.sql | 11 + .../crdb/populate-db-metadata-nexus/up03.sql | 4 + .../crdb/populate-db-metadata-nexus/up04.sql | 16 + 16 files changed, 1023 insertions(+), 34 deletions(-) create mode 100644 nexus/reconfigurator/execution/src/database.rs create mode 100644 schema/crdb/populate-db-metadata-nexus/up01.sql create mode 100644 schema/crdb/populate-db-metadata-nexus/up02.sql create mode 100644 schema/crdb/populate-db-metadata-nexus/up03.sql create mode 100644 schema/crdb/populate-db-metadata-nexus/up04.sql diff --git a/nexus/db-model/src/db_metadata.rs b/nexus/db-model/src/db_metadata.rs index de7e2862eb7..080da4d423c 100644 --- a/nexus/db-model/src/db_metadata.rs +++ b/nexus/db-model/src/db_metadata.rs @@ -3,8 +3,14 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::SemverVersion; +use crate::impl_enum_type; +use crate::typed_uuid::DbTypedUuid; use chrono::{DateTime, Utc}; use nexus_db_schema::schema::db_metadata; +use nexus_db_schema::schema::db_metadata_nexus; +use omicron_uuid_kinds::{ + BlueprintKind, BlueprintUuid, OmicronZoneKind, OmicronZoneUuid, +}; use serde::{Deserialize, Serialize}; /// Internal database metadata @@ -33,3 +39,47 @@ impl DbMetadata { &self.version } } + +impl_enum_type!( + DbMetadataNexusStateEnum: + + #[derive(Clone, Copy, Debug, AsExpression, FromSqlRow, PartialEq, Serialize, Deserialize)] + pub enum DbMetadataNexusState; + + // Enum values + Active => b"active" + NotYet => b"not_yet" + Quiesced => b"quiesced" +); + +#[derive( + Queryable, Insertable, Debug, Clone, Selectable, Serialize, Deserialize, +)] +#[diesel(table_name = db_metadata_nexus)] +pub struct DbMetadataNexus { + nexus_id: DbTypedUuid, + last_drained_blueprint_id: Option>, + state: DbMetadataNexusState, +} + +impl DbMetadataNexus { + pub fn new(nexus_id: OmicronZoneUuid, state: DbMetadataNexusState) -> Self { + Self { + nexus_id: nexus_id.into(), + last_drained_blueprint_id: None, + state, + } + } + + pub fn state(&self) -> DbMetadataNexusState { + self.state + } + + pub fn nexus_id(&self) -> OmicronZoneUuid { + self.nexus_id.into() + } + + pub fn last_drained_blueprint_id(&self) -> Option { + self.last_drained_blueprint_id.map(|id| id.into()) + } +} diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 80df59b44f4..c471d07e50d 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock}; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: Version = Version::new(184, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(185, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(185, "populate-db-metadata-nexus"), KnownVersion::new(184, "store-silo-admin-group-name"), KnownVersion::new(183, "add-ip-version-to-pools"), KnownVersion::new(182, "add-tuf-artifact-board"), @@ -228,6 +229,9 @@ static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { /// The earliest supported schema version. pub const EARLIEST_SUPPORTED_VERSION: Version = Version::new(1, 0, 0); +/// The version where "db_metadata_nexus" was added. +pub const DB_METADATA_NEXUS_SCHEMA_VERSION: Version = Version::new(185, 0, 0); + /// Describes one version of the database schema #[derive(Debug, Clone)] struct KnownVersion { diff --git a/nexus/db-queries/src/db/datastore/db_metadata.rs b/nexus/db-queries/src/db/datastore/db_metadata.rs index dbc1de58571..43a60817848 100644 --- a/nexus/db-queries/src/db/datastore/db_metadata.rs +++ b/nexus/db-queries/src/db/datastore/db_metadata.rs @@ -4,18 +4,27 @@ //! [`DataStore`] methods on Database Metadata. -use super::DataStore; +use super::{DataStore, DbConnection}; +use crate::authz; +use crate::context::OpContext; + use anyhow::{Context, bail, ensure}; use async_bb8_diesel::{AsyncRunQueryDsl, AsyncSimpleConnection}; use chrono::Utc; use diesel::prelude::*; +use futures::FutureExt; use nexus_db_errors::ErrorHandler; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::AllSchemaVersions; +use nexus_db_model::DbMetadataNexus; +use nexus_db_model::DbMetadataNexusState; use nexus_db_model::EARLIEST_SUPPORTED_VERSION; use nexus_db_model::SchemaUpgradeStep; use nexus_db_model::SchemaVersion; +use nexus_types::deployment::BlueprintZoneDisposition; use omicron_common::api::external::Error; +use omicron_uuid_kinds::GenericUuid; +use omicron_uuid_kinds::OmicronZoneUuid; use semver::Version; use slog::{Logger, error, info, o}; use std::ops::Bound; @@ -340,6 +349,183 @@ impl DataStore { Ok(()) } + // Returns the access this Nexus has to the database + #[cfg(test)] + async fn database_nexus_access( + &self, + nexus_id: OmicronZoneUuid, + ) -> Result, Error> { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + let nexus_access: Option = dsl::db_metadata_nexus + .filter( + dsl::nexus_id.eq(nexus_db_model::to_db_typed_uuid(nexus_id)), + ) + .first_async(&*self.pool_connection_unauthorized().await?) + .await + .optional() + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(nexus_access) + } + + // Checks if any db_metadata_nexus records exist in the database using an + // existing connection + async fn database_nexus_access_any_exist_on_connection( + conn: &async_bb8_diesel::Connection, + ) -> Result { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + let exists: bool = diesel::select(diesel::dsl::exists( + dsl::db_metadata_nexus.select(dsl::nexus_id), + )) + .get_result_async(conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(exists) + } + + /// Deletes the "db_metadata_nexus" record for a Nexus ID, if it exists. + pub async fn database_nexus_access_delete( + &self, + opctx: &OpContext, + nexus_id: OmicronZoneUuid, + ) -> Result<(), Error> { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + let conn = &*self.pool_connection_authorized(&opctx).await?; + + diesel::delete( + dsl::db_metadata_nexus + .filter(dsl::nexus_id.eq(nexus_id.into_untyped_uuid())), + ) + .execute_async(conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(()) + } + + /// Propagate the nexus records to the database if and only if + /// the blueprint is the current target. + /// + /// If any of these records already exist, they are unmodified. + pub async fn database_nexus_access_create( + &self, + opctx: &OpContext, + blueprint: &nexus_types::deployment::Blueprint, + ) -> Result<(), Error> { + opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; + + // TODO: Without https://github.com/oxidecomputer/omicron/pull/8863, we + // treat all Nexuses as active. Some will become "not_yet", depending on + // the Nexus Generation, once it exists. + let active_nexus_zones = blueprint + .all_omicron_zones(BlueprintZoneDisposition::is_in_service) + .filter_map(|(_sled, zone_cfg)| { + if zone_cfg.zone_type.is_nexus() { + Some(zone_cfg) + } else { + None + } + }); + let new_nexuses = active_nexus_zones + .map(|z| DbMetadataNexus::new(z.id, DbMetadataNexusState::Active)) + .collect::>(); + + let conn = &*self.pool_connection_authorized(&opctx).await?; + self.transaction_if_current_blueprint_is( + &conn, + "database_nexus_access_create", + opctx, + blueprint.id, + |conn| { + let new_nexuses = new_nexuses.clone(); + async move { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + diesel::insert_into(dsl::db_metadata_nexus) + .values(new_nexuses) + .on_conflict(dsl::nexus_id) + .do_nothing() + .execute_async(conn) + .await?; + Ok(()) + } + .boxed() + }, + ) + .await + } + + // Registers a Nexus instance as having active access to the database + #[cfg(test)] + async fn database_nexus_access_insert( + &self, + nexus_id: OmicronZoneUuid, + state: DbMetadataNexusState, + ) -> Result<(), Error> { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + let new_nexus = DbMetadataNexus::new(nexus_id, state); + + diesel::insert_into(dsl::db_metadata_nexus) + .values(new_nexus) + .on_conflict(dsl::nexus_id) + .do_update() + .set(dsl::state.eq(diesel::upsert::excluded(dsl::state))) + .execute_async(&*self.pool_connection_unauthorized().await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(()) + } + + /// Initializes Nexus database access records from a blueprint using an + /// existing connection + /// + /// This function finds all Nexus zones in the given blueprint and creates + /// active database access records for them. Used during RSS rack setup. + /// + /// Returns an error if: + /// - Any db_metadata_nexus records already exist (should only be called + /// during initial setup) + pub async fn initialize_nexus_access_from_blueprint_on_connection( + &self, + conn: &async_bb8_diesel::Connection, + nexus_zone_ids: Vec, + ) -> Result<(), Error> { + use nexus_db_schema::schema::db_metadata_nexus::dsl; + + // Ensure no db_metadata_nexus records already exist + let any_records_exist = + Self::database_nexus_access_any_exist_on_connection(conn).await?; + if any_records_exist { + return Err(Error::conflict( + "Cannot initialize Nexus access from blueprint: \ + db_metadata_nexus records already exist. This function should \ + only be called during initial rack setup.", + )); + } + + // Create db_metadata_nexus records for all Nexus zones + let new_nexuses: Vec = nexus_zone_ids + .iter() + .map(|&nexus_id| { + DbMetadataNexus::new(nexus_id, DbMetadataNexusState::Active) + }) + .collect(); + + diesel::insert_into(dsl::db_metadata_nexus) + .values(new_nexuses) + .execute_async(conn) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(()) + } pub async fn database_schema_version( &self, ) -> Result<(Version, Option), Error> { @@ -497,8 +683,35 @@ mod test { use crate::db::pub_test_utils::TestDatabase; use camino::Utf8Path; use camino_tempfile::Utf8TempDir; + use id_map::IdMap; use nexus_db_model::SCHEMA_VERSION; + use nexus_inventory::now_db_precision; + use nexus_types::deployment::Blueprint; + use nexus_types::deployment::BlueprintHostPhase2DesiredSlots; + use nexus_types::deployment::BlueprintSledConfig; + use nexus_types::deployment::BlueprintTarget; + use nexus_types::deployment::BlueprintZoneConfig; + use nexus_types::deployment::BlueprintZoneDisposition; + use nexus_types::deployment::BlueprintZoneImageSource; + use nexus_types::deployment::BlueprintZoneType; + use nexus_types::deployment::CockroachDbPreserveDowngrade; + use nexus_types::deployment::OximeterReadMode; + use nexus_types::deployment::PendingMgsUpdates; + use nexus_types::deployment::PlanningReport; + use nexus_types::deployment::blueprint_zone_type; + use nexus_types::external_api::views::SledState; + use nexus_types::inventory::NetworkInterface; + use nexus_types::inventory::NetworkInterfaceKind; + use omicron_common::api::external::Generation; + use omicron_common::api::external::MacAddr; + use omicron_common::api::external::Vni; + use omicron_common::zpool_name::ZpoolName; use omicron_test_utils::dev; + use omicron_uuid_kinds::BlueprintUuid; + use omicron_uuid_kinds::ExternalIpUuid; + use omicron_uuid_kinds::SledUuid; + use omicron_uuid_kinds::ZpoolUuid; + use std::collections::BTreeMap; // Confirms that calling the internal "ensure_schema" function can succeed // when the database is already at that version. @@ -768,6 +981,426 @@ mod test { .expect("Failed to get data"); assert_eq!(data, "abcd"); + db.terminate().await; + logctx.cleanup_successful(); + } + fn create_test_blueprint( + nexus_zones: Vec<(OmicronZoneUuid, BlueprintZoneDisposition)>, + ) -> Blueprint { + let blueprint_id = BlueprintUuid::new_v4(); + let sled_id = SledUuid::new_v4(); + + let zones: IdMap = nexus_zones + .into_iter() + .map(|(zone_id, disposition)| BlueprintZoneConfig { + disposition, + id: zone_id, + filesystem_pool: ZpoolName::new_external(ZpoolUuid::new_v4()), + zone_type: BlueprintZoneType::Nexus(blueprint_zone_type::Nexus { + internal_address: "[::1]:0".parse().unwrap(), + external_dns_servers: Vec::new(), + external_ip: nexus_types::deployment::OmicronZoneExternalFloatingIp { + id: ExternalIpUuid::new_v4(), + ip: std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), + }, + external_tls: true, + nic: NetworkInterface { + id: uuid::Uuid::new_v4(), + kind: NetworkInterfaceKind::Service { + id: zone_id.into_untyped_uuid(), + }, + name: "test-nic".parse().unwrap(), + ip: "192.168.1.1".parse().unwrap(), + mac: MacAddr::random_system(), + subnet: ipnetwork::IpNetwork::V4( + "192.168.1.0/24".parse().unwrap() + ).into(), + vni: Vni::try_from(100).unwrap(), + primary: true, + slot: 0, + transit_ips: Vec::new(), + }, + }), + image_source: BlueprintZoneImageSource::InstallDataset, + }) + .collect(); + + let mut sleds = BTreeMap::new(); + sleds.insert( + sled_id, + BlueprintSledConfig { + state: SledState::Active, + sled_agent_generation: Generation::new(), + zones, + disks: IdMap::new(), + datasets: IdMap::new(), + remove_mupdate_override: None, + host_phase_2: BlueprintHostPhase2DesiredSlots::current_contents( + ), + }, + ); + + Blueprint { + id: blueprint_id, + sleds, + pending_mgs_updates: PendingMgsUpdates::new(), + parent_blueprint_id: None, + internal_dns_version: Generation::new(), + external_dns_version: Generation::new(), + target_release_minimum_generation: Generation::new(), + cockroachdb_fingerprint: String::new(), + cockroachdb_setting_preserve_downgrade: + CockroachDbPreserveDowngrade::DoNotModify, + clickhouse_cluster_config: None, + oximeter_read_mode: OximeterReadMode::SingleNode, + oximeter_read_version: Generation::new(), + time_created: now_db_precision(), + creator: "test suite".to_string(), + comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), + } + } + + #[tokio::test] + async fn test_database_nexus_access_create() { + let logctx = dev::test_setup_log("test_database_nexus_access_create"); + let db = TestDatabase::new_with_datastore(&logctx.log).await; + let datastore = db.datastore(); + let opctx = db.opctx(); + + // Create a blueprint with two in-service Nexus zones, + // and one expunged Nexus. + let nexus1_id = OmicronZoneUuid::new_v4(); + let nexus2_id = OmicronZoneUuid::new_v4(); + let expunged_nexus = OmicronZoneUuid::new_v4(); + let blueprint = create_test_blueprint(vec![ + (nexus1_id, BlueprintZoneDisposition::InService), + (nexus2_id, BlueprintZoneDisposition::InService), + ( + expunged_nexus, + BlueprintZoneDisposition::Expunged { + as_of_generation: Generation::new(), + ready_for_cleanup: true, + }, + ), + ]); + + // Insert the blueprint and make it the target + datastore + .blueprint_insert(&opctx, &blueprint) + .await + .expect("Failed to insert blueprint"); + datastore + .blueprint_target_set_current( + &opctx, + BlueprintTarget { + target_id: blueprint.id, + enabled: false, + time_made_target: chrono::Utc::now(), + }, + ) + .await + .expect("Failed to set blueprint target"); + + // Create nexus access records + datastore + .database_nexus_access_create(&opctx, &blueprint) + .await + .expect("Failed to create nexus access"); + + // Verify records were created with Active state + let nexus1_access = datastore + .database_nexus_access(nexus1_id) + .await + .expect("Failed to get nexus1 access"); + let nexus2_access = datastore + .database_nexus_access(nexus2_id) + .await + .expect("Failed to get nexus2 access"); + let expunged_access = datastore + .database_nexus_access(expunged_nexus) + .await + .expect("Failed to get expunged access"); + + assert!(nexus1_access.is_some(), "nexus1 should have access record"); + assert!(nexus2_access.is_some(), "nexus2 should have access record"); + assert!( + expunged_access.is_none(), + "expunged nexus should not have access record" + ); + + let nexus1_record = nexus1_access.unwrap(); + let nexus2_record = nexus2_access.unwrap(); + assert_eq!(nexus1_record.state(), DbMetadataNexusState::Active); + assert_eq!(nexus2_record.state(), DbMetadataNexusState::Active); + + db.terminate().await; + logctx.cleanup_successful(); + } + + #[tokio::test] + async fn test_database_nexus_access_create_idempotent() { + let logctx = + dev::test_setup_log("test_database_nexus_access_create_idempotent"); + let db = TestDatabase::new_with_datastore(&logctx.log).await; + let datastore = db.datastore(); + let opctx = db.opctx(); + + // Create a blueprint with one Nexus zone + let nexus_id = OmicronZoneUuid::new_v4(); + let blueprint = create_test_blueprint(vec![( + nexus_id, + BlueprintZoneDisposition::InService, + )]); + + // Insert the blueprint and make it the target + datastore + .blueprint_insert(&opctx, &blueprint) + .await + .expect("Failed to insert blueprint"); + datastore + .blueprint_target_set_current( + &opctx, + BlueprintTarget { + target_id: blueprint.id, + enabled: false, + time_made_target: chrono::Utc::now(), + }, + ) + .await + .expect("Failed to set blueprint target"); + + // Create nexus access records (first time) + datastore + .database_nexus_access_create(&opctx, &blueprint) + .await + .expect("Failed to create nexus access (first time)"); + + // Verify record was created + async fn confirm_state( + datastore: &DataStore, + nexus_id: OmicronZoneUuid, + expected_state: DbMetadataNexusState, + ) { + let state = datastore + .database_nexus_access(nexus_id) + .await + .expect("Failed to get nexus access after first create") + .expect("Entry for Nexus should have been inserted"); + assert_eq!(state.state(), expected_state); + } + + confirm_state(datastore, nexus_id, DbMetadataNexusState::Active).await; + + // Creating the record again: not an error. + datastore + .database_nexus_access_create(&opctx, &blueprint) + .await + .expect("Failed to create nexus access (first time)"); + confirm_state(datastore, nexus_id, DbMetadataNexusState::Active).await; + + // Manually make the record "Quiesced". + use nexus_db_schema::schema::db_metadata_nexus::dsl; + diesel::update(dsl::db_metadata_nexus) + .filter(dsl::nexus_id.eq(nexus_id.into_untyped_uuid())) + .set(dsl::state.eq(DbMetadataNexusState::Quiesced)) + .execute_async( + &*datastore.pool_connection_unauthorized().await.unwrap(), + ) + .await + .expect("Failed to update record"); + confirm_state(datastore, nexus_id, DbMetadataNexusState::Quiesced) + .await; + + // Create nexus access records another time - should be idempotent, + // but should be "on-conflict, ignore". + datastore + .database_nexus_access_create(&opctx, &blueprint) + .await + .expect("Failed to create nexus access (second time)"); + confirm_state(datastore, nexus_id, DbMetadataNexusState::Quiesced) + .await; + + db.terminate().await; + logctx.cleanup_successful(); + } + + #[tokio::test] + async fn test_database_nexus_access_create_fails_wrong_target_blueprint() { + let logctx = dev::test_setup_log( + "test_database_nexus_access_create_fails_wrong_target_blueprint", + ); + let db = TestDatabase::new_with_datastore(&logctx.log).await; + let datastore = db.datastore(); + let opctx = db.opctx(); + + // Create two different blueprints + let nexus_id = OmicronZoneUuid::new_v4(); + let target_blueprint = create_test_blueprint(vec![( + nexus_id, + BlueprintZoneDisposition::InService, + )]); + let non_target_blueprint = create_test_blueprint(vec![( + nexus_id, + BlueprintZoneDisposition::InService, + )]); + + // Insert both blueprints + datastore + .blueprint_insert(&opctx, &target_blueprint) + .await + .expect("Failed to insert target blueprint"); + datastore + .blueprint_insert(&opctx, &non_target_blueprint) + .await + .expect("Failed to insert non-target blueprint"); + + // Set the first blueprint as the target + datastore + .blueprint_target_set_current( + &opctx, + BlueprintTarget { + target_id: target_blueprint.id, + enabled: false, + time_made_target: chrono::Utc::now(), + }, + ) + .await + .expect("Failed to set target blueprint"); + + // Try to create nexus access records using the non-target blueprint. + // This should fail because the transaction should check if the + // blueprint is the current target + let result = datastore + .database_nexus_access_create(&opctx, &non_target_blueprint) + .await; + assert!( + result.is_err(), + "Creating nexus access with wrong target blueprint should fail" + ); + + // Verify no records were created for the nexus + let access = datastore + .database_nexus_access(nexus_id) + .await + .expect("Failed to get nexus access"); + assert!( + access.is_none(), + "No access record should exist when wrong blueprint is used" + ); + + // Verify that using the correct target blueprint works + datastore + .database_nexus_access_create(&opctx, &target_blueprint) + .await + .expect( + "Creating nexus access with correct blueprint should succeed", + ); + + let access_after_correct = datastore + .database_nexus_access(nexus_id) + .await + .expect("Failed to get nexus access after correct blueprint"); + assert!( + access_after_correct.is_some(), + "Access record should exist after using correct target blueprint" + ); + + db.terminate().await; + logctx.cleanup_successful(); + } + + #[tokio::test] + async fn test_database_nexus_access_delete() { + let logctx = dev::test_setup_log("test_database_nexus_access_delete"); + let db = TestDatabase::new_with_datastore(&logctx.log).await; + let datastore = db.datastore(); + let opctx = db.opctx(); + + // Create test nexus IDs + let nexus1_id = OmicronZoneUuid::new_v4(); + let nexus2_id = OmicronZoneUuid::new_v4(); + + // Insert records directly using the test method + datastore + .database_nexus_access_insert( + nexus1_id, + DbMetadataNexusState::Active, + ) + .await + .expect("Failed to insert nexus1 access"); + datastore + .database_nexus_access_insert( + nexus2_id, + DbMetadataNexusState::NotYet, + ) + .await + .expect("Failed to insert nexus2 access"); + + // Verify records were created + let nexus1_before = datastore + .database_nexus_access(nexus1_id) + .await + .expect("Failed to get nexus1 access"); + let nexus2_before = datastore + .database_nexus_access(nexus2_id) + .await + .expect("Failed to get nexus2 access"); + assert!(nexus1_before.is_some(), "nexus1 should have access record"); + assert!(nexus2_before.is_some(), "nexus2 should have access record"); + + // Delete nexus1 record + datastore + .database_nexus_access_delete(&opctx, nexus1_id) + .await + .expect("Failed to delete nexus1 access"); + + // Verify nexus1 record was deleted, nexus2 record remains + let nexus1_after = datastore + .database_nexus_access(nexus1_id) + .await + .expect("Failed to get nexus1 access after delete"); + let nexus2_after = datastore + .database_nexus_access(nexus2_id) + .await + .expect("Failed to get nexus2 access after delete"); + assert!( + nexus1_after.is_none(), + "nexus1 should not have access record after delete" + ); + assert!( + nexus2_after.is_some(), + "nexus2 should still have access record" + ); + + // Delete nexus2 record + datastore + .database_nexus_access_delete(&opctx, nexus2_id) + .await + .expect("Failed to delete nexus2 access"); + + // Verify nexus2 record was also deleted + let nexus2_final = datastore + .database_nexus_access(nexus2_id) + .await + .expect("Failed to get nexus2 access after final delete"); + assert!( + nexus2_final.is_none(), + "nexus2 should not have access record after delete" + ); + + // Confirm deletion is idempotent + datastore + .database_nexus_access_delete(&opctx, nexus1_id) + .await + .expect("Failed to delete nexus1 access idempotently"); + + // This also means deleting non-existent records should be fine + datastore + .database_nexus_access_delete(&opctx, OmicronZoneUuid::new_v4()) + .await + .expect("Failed to delete non-existent record"); + db.terminate().await; logctx.cleanup_successful(); } diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index a2fcb2c6c82..31e0ec43284 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -727,6 +727,7 @@ impl DataStore { // - Zpools // - Datasets // - A blueprint + // - Nexus database access records // // Which RSS has already allocated during bootstrapping. @@ -793,6 +794,22 @@ impl DataStore { DieselError::RollbackTransaction })?; + // Insert Nexus database access records + self.initialize_nexus_access_from_blueprint_on_connection( + &conn, + blueprint.all_omicron_zones(BlueprintZoneDisposition::is_in_service) + .filter_map(|(_sled, zone_cfg)| { + if zone_cfg.zone_type.is_nexus() { + Some(zone_cfg.id) + } else { + None + } + }).collect(), + ).await.map_err(|e| { + err.set(RackInitError::BlueprintTargetSet(e)).unwrap(); + DieselError::RollbackTransaction + })?; + // Allocate networking records for all services. for (_, zone_config) in blueprint.all_omicron_zones(BlueprintZoneDisposition::is_in_service) { self.rack_populate_service_networking_records( diff --git a/nexus/db-schema/src/enums.rs b/nexus/db-schema/src/enums.rs index 1766054c9ad..bac6d5ae3b4 100644 --- a/nexus/db-schema/src/enums.rs +++ b/nexus/db-schema/src/enums.rs @@ -37,6 +37,7 @@ define_enums! { CabooseWhichEnum => "caboose_which", ClickhouseModeEnum => "clickhouse_mode", DatasetKindEnum => "dataset_kind", + DbMetadataNexusStateEnum => "db_metadata_nexus_state", DnsGroupEnum => "dns_group", DownstairsClientStopRequestReasonEnum => "downstairs_client_stop_request_reason_type", DownstairsClientStoppedReasonEnum => "downstairs_client_stopped_reason_type", diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 28a168d6f76..4f8bd54a7a2 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -2373,6 +2373,14 @@ table! { } } +table! { + db_metadata_nexus (nexus_id) { + nexus_id -> Uuid, + last_drained_blueprint_id -> Nullable, + state -> crate::enums::DbMetadataNexusStateEnum, + } +} + table! { migration (id) { id -> Uuid, diff --git a/nexus/reconfigurator/execution/src/database.rs b/nexus/reconfigurator/execution/src/database.rs new file mode 100644 index 00000000000..9652e53ec1a --- /dev/null +++ b/nexus/reconfigurator/execution/src/database.rs @@ -0,0 +1,24 @@ +// 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/. + +//! Manages deployment of records into the database. + +use anyhow::anyhow; +use nexus_db_queries::context::OpContext; +use nexus_db_queries::db::DataStore; +use nexus_types::deployment::Blueprint; + +/// Idempotently ensure that the Nexus records for the zones are populated +/// in the database. +pub(crate) async fn deploy_db_metadata_nexus_records( + opctx: &OpContext, + datastore: &DataStore, + blueprint: &Blueprint, +) -> Result<(), anyhow::Error> { + datastore + .database_nexus_access_create(opctx, blueprint) + .await + .map_err(|err| anyhow!(err))?; + Ok(()) +} diff --git a/nexus/reconfigurator/execution/src/lib.rs b/nexus/reconfigurator/execution/src/lib.rs index 43c09485557..5060a037e22 100644 --- a/nexus/reconfigurator/execution/src/lib.rs +++ b/nexus/reconfigurator/execution/src/lib.rs @@ -32,8 +32,10 @@ use tokio::sync::watch; use update_engine::StepSuccess; use update_engine::StepWarning; use update_engine::merge_anyhow_list; + mod clickhouse; mod cockroachdb; +mod database; mod dns; mod omicron_physical_disks; mod omicron_sled_config; @@ -196,6 +198,13 @@ pub async fn realize_blueprint( ) .into_shared(); + register_deploy_db_metadata_nexus_records_step( + &engine.for_component(ExecutionComponent::DeployNexusRecords), + &opctx, + datastore, + blueprint, + ); + register_deploy_sled_configs_step( &engine.for_component(ExecutionComponent::SledAgent), &opctx, @@ -390,6 +399,28 @@ fn register_sled_list_step<'a>( .register() } +fn register_deploy_db_metadata_nexus_records_step<'a>( + registrar: &ComponentRegistrar<'_, 'a>, + opctx: &'a OpContext, + datastore: &'a DataStore, + blueprint: &'a Blueprint, +) { + registrar + .new_step( + ExecutionStepId::Ensure, + "Ensure db_metadata_nexus_state records exist", + async move |_cx| { + database::deploy_db_metadata_nexus_records( + opctx, &datastore, &blueprint, + ) + .await + .context("ensuring db_metadata_nexus_state")?; + StepSuccess::new(()).into() + }, + ) + .register(); +} + fn register_deploy_sled_configs_step<'a>( registrar: &ComponentRegistrar<'_, 'a>, opctx: &'a OpContext, diff --git a/nexus/reconfigurator/execution/src/omicron_zones.rs b/nexus/reconfigurator/execution/src/omicron_zones.rs index 28c981fd90e..74e4625358d 100644 --- a/nexus/reconfigurator/execution/src/omicron_zones.rs +++ b/nexus/reconfigurator/execution/src/omicron_zones.rs @@ -72,10 +72,13 @@ async fn clean_up_expunged_zones_impl( )); let result = match &config.zone_type { - // Zones which need no cleanup work after expungement. - BlueprintZoneType::Nexus(_) => None, - // Zones which need cleanup after expungement. + BlueprintZoneType::Nexus(_) => Some( + datastore + .database_nexus_access_delete(&opctx, config.id) + .await + .map_err(|err| anyhow::anyhow!(err)), + ), BlueprintZoneType::CockroachDb(_) => { if decommission_cockroach { Some( diff --git a/nexus/tests/integration_tests/schema.rs b/nexus/tests/integration_tests/schema.rs index 9111e6a0949..db6c0cb6eaf 100644 --- a/nexus/tests/integration_tests/schema.rs +++ b/nexus/tests/integration_tests/schema.rs @@ -2889,6 +2889,157 @@ fn after_171_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { }) } +const NEXUS_ID_185_0: &str = "387433f9-1473-4ca2-b156-9670452985e0"; +const EXPUNGED_NEXUS_ID_185_0: &str = "287433f9-1473-4ca2-b156-9670452985e0"; +const OLD_NEXUS_ID_185_0: &str = "187433f9-1473-4ca2-b156-9670452985e0"; + +const BP_ID_185_0: &str = "5a5ff941-3b5a-403b-9fda-db2049f4c736"; +const OLD_BP_ID_185_0: &str = "4a5ff941-3b5a-403b-9fda-db2049f4c736"; + +fn before_185_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { + Box::pin(async move { + // Create a blueprint which contains a Nexus - we'll use this for the migration. + // + // It also contains an exupnged Nexus, which should be ignored. + ctx.client + .execute( + &format!( + "INSERT INTO omicron.public.bp_target + (version, blueprint_id, enabled, time_made_target) + VALUES + (1, '{BP_ID_185_0}', true, now());", + ), + &[], + ) + .await + .expect("inserted bp_target rows for 182"); + ctx.client + .execute( + &format!( + "INSERT INTO omicron.public.bp_omicron_zone ( + blueprint_id, sled_id, id, zone_type, + primary_service_ip, primary_service_port, + second_service_ip, second_service_port, + dataset_zpool_name, bp_nic_id, + dns_gz_address, dns_gz_address_index, + ntp_ntp_servers, ntp_dns_servers, ntp_domain, + nexus_external_tls, nexus_external_dns_servers, + snat_ip, snat_first_port, snat_last_port, + external_ip_id, filesystem_pool, disposition, + disposition_expunged_as_of_generation, + disposition_expunged_ready_for_cleanup, + image_source, image_artifact_sha256 + ) + VALUES ( + '{BP_ID_185_0}', gen_random_uuid(), '{NEXUS_ID_185_0}', + 'nexus', '192.168.1.10', 8080, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, false, ARRAY[]::INET[], + NULL, NULL, NULL, NULL, gen_random_uuid(), + 'in_service', NULL, false, 'install_dataset', NULL + ), + ( + '{BP_ID_185_0}', gen_random_uuid(), + '{EXPUNGED_NEXUS_ID_185_0}', 'nexus', '192.168.1.11', + 8080, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, false, ARRAY[]::INET[], NULL, NULL, NULL, NULL, + gen_random_uuid(), 'expunged', 1, false, + 'install_dataset', NULL + );" + ), + &[], + ) + .await + .expect("inserted bp_omicron_zone rows for 182"); + + // ALSO create an old blueprint, which isn't the latest target. + // + // We should ignore this one! No rows should be inserted for old data. + ctx.client + .execute( + &format!( + "INSERT INTO omicron.public.bp_target + (version, blueprint_id, enabled, time_made_target) + VALUES + (0, '{OLD_BP_ID_185_0}', true, now());", + ), + &[], + ) + .await + .expect("inserted bp_target rows for 182"); + ctx.client + .execute( + &format!( + "INSERT INTO omicron.public.bp_omicron_zone ( + blueprint_id, sled_id, id, zone_type, + primary_service_ip, primary_service_port, + second_service_ip, second_service_port, + dataset_zpool_name, bp_nic_id, + dns_gz_address, dns_gz_address_index, + ntp_ntp_servers, ntp_dns_servers, ntp_domain, + nexus_external_tls, nexus_external_dns_servers, + snat_ip, snat_first_port, snat_last_port, + external_ip_id, filesystem_pool, disposition, + disposition_expunged_as_of_generation, + disposition_expunged_ready_for_cleanup, + image_source, image_artifact_sha256 + ) + VALUES ( + '{OLD_BP_ID_185_0}', gen_random_uuid(), + '{OLD_NEXUS_ID_185_0}', 'nexus', '192.168.1.10', 8080, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + false, ARRAY[]::INET[], NULL, NULL, NULL, + NULL, gen_random_uuid(), 'in_service', + NULL, false, 'install_dataset', NULL + );" + ), + &[], + ) + .await + .expect("inserted bp_omicron_zone rows for 182"); + }) +} + +fn after_185_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { + Box::pin(async move { + // After the migration, the new row should be created - only for Nexuses + // in the latest blueprint. + // + // Note that "OLD_NEXUS_ID_185_0" doesn't get a row - it's in an old + // blueprint. + let rows = ctx + .client + .query( + "SELECT + nexus_id, + last_drained_blueprint_id, + state + FROM omicron.public.db_metadata_nexus;", + &[], + ) + .await + .expect("queried post-migration inv_sled_config_reconciler"); + + let rows = process_rows(&rows); + assert_eq!(rows.len(), 1); + let row = &rows[0]; + + // Create a new row for the Nexuses in the target blueprint + assert_eq!( + row.values[0].expect("nexus_id").unwrap(), + &AnySqlType::Uuid(NEXUS_ID_185_0.parse().unwrap()) + ); + assert_eq!(row.values[1].expect("last_drained_blueprint_id"), None); + assert_eq!( + row.values[2].expect("state").unwrap(), + &AnySqlType::Enum(SqlEnum::from(( + "db_metadata_nexus_state", + "active" + ))) + ); + }) +} + // Lazily initializes all migration checks. The combination of Rust function // pointers and async makes defining a static table fairly painful, so we're // using lazy initialization instead. @@ -2987,7 +3138,10 @@ fn get_migration_checks() -> BTreeMap { Version::new(171, 0, 0), DataMigrationFns::new().before(before_171_0_0).after(after_171_0_0), ); - + map.insert( + Version::new(185, 0, 0), + DataMigrationFns::new().before(before_185_0_0).after(after_185_0_0), + ); map } diff --git a/nexus/types/src/deployment/execution/spec.rs b/nexus/types/src/deployment/execution/spec.rs index 482355dfee5..27d85b431db 100644 --- a/nexus/types/src/deployment/execution/spec.rs +++ b/nexus/types/src/deployment/execution/spec.rs @@ -31,6 +31,7 @@ pub enum ExecutionComponent { SupportBundles, SledList, SledAgent, + DeployNexusRecords, PhysicalDisks, OmicronZones, FirewallRules, diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index e6e2949c08b..bad381cb5c8 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -5630,29 +5630,6 @@ CREATE INDEX IF NOT EXISTS lookup_region_snapshot_replacement_step_by_state CREATE INDEX IF NOT EXISTS lookup_region_snapshot_replacement_step_by_old_volume_id on omicron.public.region_snapshot_replacement_step (old_snapshot_volume_id); -/* - * Metadata for the schema itself. This version number isn't great, as there's - * nothing to ensure it gets bumped when it should be, but it's a start. - */ -CREATE TABLE IF NOT EXISTS omicron.public.db_metadata ( - -- There should only be one row of this table for the whole DB. - -- It's a little goofy, but filter on "singleton = true" before querying - -- or applying updates, and you'll access the singleton row. - -- - -- We also add a constraint on this table to ensure it's not possible to - -- access the version of this table with "singleton = false". - singleton BOOL NOT NULL PRIMARY KEY, - time_created TIMESTAMPTZ NOT NULL, - time_modified TIMESTAMPTZ NOT NULL, - -- Semver representation of the DB version - version STRING(64) NOT NULL, - - -- (Optional) Semver representation of the DB version to which we're upgrading - target_version STRING(64), - - CHECK (singleton = true) -); - -- An allowlist of IP addresses that can make requests to user-facing services. CREATE TABLE IF NOT EXISTS omicron.public.allow_list ( id UUID PRIMARY KEY, @@ -6553,10 +6530,59 @@ ON omicron.public.host_ereport ( ) WHERE time_deleted IS NULL; -/* - * Keep this at the end of file so that the database does not contain a version - * until it is fully populated. - */ +-- Metadata for the schema itself. +-- +-- This table may be read by Nexuses with different notions of "what the schema should be". +-- Unlike other tables in the database, caution should be taken when upgrading this schema. +CREATE TABLE IF NOT EXISTS omicron.public.db_metadata ( + -- There should only be one row of this table for the whole DB. + -- It's a little goofy, but filter on "singleton = true" before querying + -- or applying updates, and you'll access the singleton row. + -- + -- We also add a constraint on this table to ensure it's not possible to + -- access the version of this table with "singleton = false". + singleton BOOL NOT NULL PRIMARY KEY, + time_created TIMESTAMPTZ NOT NULL, + time_modified TIMESTAMPTZ NOT NULL, + -- Semver representation of the DB version + version STRING(64) NOT NULL, + + -- (Optional) Semver representation of the DB version to which we're upgrading + target_version STRING(64), + + CHECK (singleton = true) +); + +CREATE TYPE IF NOT EXISTS omicron.public.db_metadata_nexus_state AS ENUM ( + -- This Nexus is allowed to access this database + 'active', + + -- This Nexus is not yet allowed to access the database + 'not_yet', + + -- This Nexus has committed to no longer accessing this database + 'quiesced' +); + +-- Nexuses which may be attempting to access the database, and a state +-- which identifies if they should be allowed to do so. +-- +-- This table is used during upgrade implement handoff between old and new +-- Nexus zones. It is read by all Nexuses during initialization to identify +-- if they should have access to the database. +CREATE TABLE IF NOT EXISTS omicron.public.db_metadata_nexus ( + nexus_id UUID NOT NULL PRIMARY KEY, + last_drained_blueprint_id UUID, + state omicron.public.db_metadata_nexus_state NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS lookup_db_metadata_nexus_by_state on omicron.public.db_metadata_nexus ( + state, + nexus_id +); + +-- Keep this at the end of file so that the database does not contain a version +-- until it is fully populated. INSERT INTO omicron.public.db_metadata ( singleton, time_created, @@ -6564,7 +6590,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '184.0.0', NULL) + (TRUE, NOW(), NOW(), '185.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/populate-db-metadata-nexus/up01.sql b/schema/crdb/populate-db-metadata-nexus/up01.sql new file mode 100644 index 00000000000..25c42761e04 --- /dev/null +++ b/schema/crdb/populate-db-metadata-nexus/up01.sql @@ -0,0 +1,6 @@ +CREATE TYPE IF NOT EXISTS omicron.public.db_metadata_nexus_state AS ENUM ( + 'active', + 'not_yet', + 'quiesced' +); + diff --git a/schema/crdb/populate-db-metadata-nexus/up02.sql b/schema/crdb/populate-db-metadata-nexus/up02.sql new file mode 100644 index 00000000000..9fac217eec4 --- /dev/null +++ b/schema/crdb/populate-db-metadata-nexus/up02.sql @@ -0,0 +1,11 @@ +-- Nexuses which may be attempting to access the database, and a state +-- which identifies if they should be allowed to do so. +-- +-- This table is used during upgrade implement handoff between old and new +-- Nexus zones. +CREATE TABLE IF NOT EXISTS omicron.public.db_metadata_nexus ( + nexus_id UUID NOT NULL PRIMARY KEY, + last_drained_blueprint_id UUID, + state omicron.public.db_metadata_nexus_state NOT NULL +); + diff --git a/schema/crdb/populate-db-metadata-nexus/up03.sql b/schema/crdb/populate-db-metadata-nexus/up03.sql new file mode 100644 index 00000000000..42fbf004137 --- /dev/null +++ b/schema/crdb/populate-db-metadata-nexus/up03.sql @@ -0,0 +1,4 @@ +CREATE UNIQUE INDEX IF NOT EXISTS lookup_db_metadata_nexus_by_state on omicron.public.db_metadata_nexus ( + state, + nexus_id +); diff --git a/schema/crdb/populate-db-metadata-nexus/up04.sql b/schema/crdb/populate-db-metadata-nexus/up04.sql new file mode 100644 index 00000000000..36b876b9cdd --- /dev/null +++ b/schema/crdb/populate-db-metadata-nexus/up04.sql @@ -0,0 +1,16 @@ +-- Populate db_metadata_nexus records for all Nexus zones in the current target blueprint +-- +-- This migration handles backfill for existing deployments that are upgrading +-- to include db_metadata_nexus. It finds all Nexus zones in the current +-- target blueprint and marks them as 'active' in the db_metadata_nexus table. + +SET LOCAL disallow_full_table_scans = off; + +INSERT INTO omicron.public.db_metadata_nexus (nexus_id, last_drained_blueprint_id, state) +SELECT bz.id, NULL, 'active' +FROM omicron.public.bp_target bt +JOIN omicron.public.bp_omicron_zone bz ON bt.blueprint_id = bz.blueprint_id +WHERE bz.zone_type = 'nexus' + AND bz.disposition != 'expunged' + AND bt.version = (SELECT MAX(version) FROM omicron.public.bp_target) +ON CONFLICT (nexus_id) DO NOTHING; From 345009a22b4ea6d33ddbac19d026537e2470a27d Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Wed, 27 Aug 2025 11:57:04 -0700 Subject: [PATCH 2/6] (2/N) Add schema for nexus generations --- dev-tools/omdb/tests/successes.out | 3 +++ .../output/cmds-add-sled-no-disks-stdout | 1 + .../tests/output/cmds-example-stdout | 7 +++++ ...ds-expunge-newly-added-external-dns-stdout | 6 +++++ ...ds-expunge-newly-added-internal-dns-stdout | 4 +++ .../output/cmds-host-phase-2-source-stdout | 6 +++++ .../output/cmds-mupdate-update-flow-stdout | 12 +++++++++ .../output/cmds-noop-image-source-stdout | 2 ++ .../tests/output/cmds-set-mgs-updates-stdout | 11 ++++++++ .../cmds-set-remove-mupdate-override-stdout | 4 +++ .../tests/output/cmds-set-zone-images-stdout | 5 ++++ .../tests/output/cmds-target-release-stdout | 24 +++++++++++++++++ nexus/db-model/src/deployment.rs | 11 ++++++++ nexus/db-model/src/schema_versions.rs | 3 ++- .../src/db/datastore/db_metadata.rs | 2 ++ .../db-queries/src/db/datastore/deployment.rs | 4 +++ .../deployment/external_networking.rs | 2 ++ nexus/db-queries/src/db/datastore/rack.rs | 12 +++++++++ nexus/db-schema/src/schema.rs | 3 +++ nexus/reconfigurator/execution/src/dns.rs | 2 ++ .../planning/src/blueprint_builder/builder.rs | 5 ++++ .../example_builder_zone_counts_blueprint.txt | 1 + .../output/planner_basic_add_sled_2_3.txt | 1 + .../output/planner_basic_add_sled_3_5.txt | 1 + ...dataset_settings_modified_in_place_1_2.txt | 1 + .../planner_decommissions_sleds_1_2.txt | 1 + .../planner_decommissions_sleds_bp2.txt | 1 + .../planner_deploy_all_keeper_nodes_1_2.txt | 1 + .../planner_deploy_all_keeper_nodes_3_4.txt | 1 + .../planner_deploy_all_keeper_nodes_4_5.txt | 1 + .../planner_deploy_all_keeper_nodes_5_6.txt | 1 + ...lanner_expunge_clickhouse_clusters_3_4.txt | 1 + ...lanner_expunge_clickhouse_clusters_5_6.txt | 1 + ...ouse_zones_after_policy_is_changed_3_4.txt | 1 + .../output/planner_nonprovisionable_1_2.txt | 1 + .../output/planner_nonprovisionable_2_2a.txt | 4 +++ .../output/planner_nonprovisionable_bp2.txt | 1 + .../output/zone_image_source_change_1.txt | 1 + .../background/tasks/blueprint_execution.rs | 1 + .../app/background/tasks/blueprint_load.rs | 1 + nexus/test-utils/src/lib.rs | 2 ++ nexus/types/src/deployment.rs | 13 +++++++++ nexus/types/src/deployment/blueprint_diff.rs | 7 +++++ .../types/src/deployment/blueprint_display.rs | 1 + nexus/types/src/deployment/zone_type.rs | 5 ++++ openapi/nexus-internal.json | 27 +++++++++++++++++++ schema/crdb/dbinit.sql | 10 +++++-- schema/crdb/nexus-generation/up01.sql | 1 + schema/crdb/nexus-generation/up02.sql | 5 ++++ schema/crdb/nexus-generation/up03.sql | 1 + schema/crdb/nexus-generation/up04.sql | 1 + sled-agent/src/rack_setup/plan/service.rs | 3 ++- sled-agent/src/rack_setup/service.rs | 1 + sled-agent/src/sim/server.rs | 1 + 54 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 schema/crdb/nexus-generation/up01.sql create mode 100644 schema/crdb/nexus-generation/up02.sql create mode 100644 schema/crdb/nexus-generation/up03.sql create mode 100644 schema/crdb/nexus-generation/up04.sql diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index f5f91119769..f0a22c50ad4 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -1517,6 +1517,7 @@ parent: internal DNS version::: 1 external DNS version::: 2 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1640,6 +1641,7 @@ parent: internal DNS version::: 1 external DNS version::: 2 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1665,6 +1667,7 @@ to: blueprint ............. internal DNS version::: 1 (unchanged) external DNS version::: 2 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout index 9591408f905..9ed40ac1396 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout @@ -274,6 +274,7 @@ parent: dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index 92232e4b5e6..f72ba26bf9c 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -399,6 +399,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -518,6 +519,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -633,6 +635,7 @@ to: blueprint 86db3308-f817-4626-8838-4085949a6a41 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -710,6 +713,7 @@ to: blueprint 86db3308-f817-4626-8838-4085949a6a41 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -787,6 +791,7 @@ to: blueprint 02697f74-b14a-4418-90f0-c28b2a3a6aa9 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1023,6 +1028,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1661,6 +1667,7 @@ to: blueprint 86db3308-f817-4626-8838-4085949a6a41 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout index cf22a460ed9..c42a0d87b5c 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout @@ -331,6 +331,7 @@ parent: 06c88262-f435-410e-ba98-101bed41ec27 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -463,6 +464,7 @@ to: blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -826,6 +828,7 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -967,6 +970,7 @@ to: blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1333,6 +1337,7 @@ parent: 366b0b68-d80e-4bc1-abd3-dc69837847e0 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1467,6 +1472,7 @@ to: blueprint 2ac8c740-444d-42ff-8d66-9812a7e51288 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout index d18a5821897..fc85f987f5f 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout @@ -329,6 +329,7 @@ parent: 184f10b3-61cb-41ef-9b93-3489b2bac559 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -461,6 +462,7 @@ to: blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -624,6 +626,7 @@ to: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -775,6 +778,7 @@ to: blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout index 1dd547e695d..83e4a21a79c 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout @@ -150,6 +150,7 @@ to: blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -310,6 +311,7 @@ to: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -469,6 +471,7 @@ parent: 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -620,6 +623,7 @@ to: blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -780,6 +784,7 @@ to: blueprint df06bb57-ad42-4431-9206-abff322896c7 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -939,6 +944,7 @@ parent: af934083-59b5-4bf6-8966-6fb5292c29e1 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout index 39c1f30e8b9..74cedfee3f1 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout @@ -687,6 +687,7 @@ to: blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) * target release min gen: 1 -> 3 + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -798,6 +799,7 @@ to: blueprint 626487fa-7139-45ec-8416-902271fc730b internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 3 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1034,6 +1036,7 @@ to: blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) * target release min gen: 3 -> 4 + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1261,6 +1264,7 @@ parent: c1a0d242-9160-40f4-96ae-61f8f40a0b1b internal DNS version::: 1 external DNS version::: 1 target release min gen: 4 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1338,6 +1342,7 @@ to: blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 4 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1578,6 +1583,7 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 internal DNS version::: 1 external DNS version::: 1 target release min gen: 4 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -1660,6 +1666,7 @@ to: blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 4 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1873,6 +1880,7 @@ parent: ce365dff-2cdb-4f35-a186-b15e20e1e700 internal DNS version::: 1 external DNS version::: 1 target release min gen: 4 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 1 Pending MGS-managed updates (all baseboards): @@ -1906,6 +1914,7 @@ to: blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 4 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2177,6 +2186,7 @@ to: blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 4 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2338,6 +2348,7 @@ to: blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) * target release min gen: 4 -> 5 + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2458,6 +2469,7 @@ to: blueprint 27e755bc-dc10-4647-853c-f89bb3a15a2c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 5 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout index 7c6ef00867e..35ab76aa8c8 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -352,6 +352,7 @@ to: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -483,6 +484,7 @@ to: blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout index 06bc28648a5..c6dc1b11171 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout @@ -205,6 +205,7 @@ parent: 6ccc786b-17f1-4562-958f-5a7d9a5a15fd internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -416,6 +417,7 @@ parent: ad97e762-7bf1-45a6-a98f-60afb7e491c0 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 1 Pending MGS-managed updates (all baseboards): @@ -441,6 +443,7 @@ to: blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -478,6 +481,7 @@ to: blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -707,6 +711,7 @@ parent: cca24b71-09b5-4042-9185-b33e9f2ebba0 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 1 Pending MGS-managed updates (all baseboards): @@ -732,6 +737,7 @@ to: blueprint 5bf974f3-81f9-455b-b24e-3099f765664c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -770,6 +776,7 @@ to: blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1001,6 +1008,7 @@ parent: 5bf974f3-81f9-455b-b24e-3099f765664c internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 2 Pending MGS-managed updates (all baseboards): @@ -1027,6 +1035,7 @@ to: blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1255,6 +1264,7 @@ parent: 1b837a27-3be1-4fcb-8499-a921c839e1d0 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 1 Pending MGS-managed updates (all baseboards): @@ -1280,6 +1290,7 @@ to: blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout index e599ae2bdf7..200385eb7d5 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout @@ -274,6 +274,7 @@ parent: df06bb57-ad42-4431-9206-abff322896c7 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -397,6 +398,7 @@ to: blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -644,6 +646,7 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 internal DNS version::: 1 external DNS version::: 1 target release min gen: 2 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -663,6 +666,7 @@ to: blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) * target release min gen: 1 -> 2 + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout index 14619df2366..97bca366c13 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout @@ -107,6 +107,7 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -225,6 +226,7 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -329,6 +331,7 @@ to: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -455,6 +458,7 @@ parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 @@ -559,6 +563,7 @@ to: blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout index dd49f0c9f0b..6dd5a0ed71d 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -236,6 +236,7 @@ to: blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -296,6 +297,7 @@ to: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -358,6 +360,7 @@ to: blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -429,6 +432,7 @@ to: blueprint df06bb57-ad42-4431-9206-abff322896c7 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -562,6 +566,7 @@ to: blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -622,6 +627,7 @@ to: blueprint 9034c710-3e57-45f3-99e5-4316145e87ac internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -683,6 +689,7 @@ to: blueprint d60afc57-f15d-476c-bd0f-b1071e2bb976 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -743,6 +750,7 @@ to: blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -805,6 +813,7 @@ to: blueprint 626487fa-7139-45ec-8416-902271fc730b internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -878,6 +887,7 @@ to: blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -946,6 +956,7 @@ to: blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1018,6 +1029,7 @@ to: blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1086,6 +1098,7 @@ to: blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1157,6 +1170,7 @@ to: blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1290,6 +1304,7 @@ to: blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1362,6 +1377,7 @@ to: blueprint 27e755bc-dc10-4647-853c-f89bb3a15a2c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1436,6 +1452,7 @@ to: blueprint 9f89efdf-a23e-4137-b7cc-79f4a91cbe1f internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1503,6 +1520,7 @@ to: blueprint 9a9e6c32-5a84-4020-a159-33dceff18d35 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1574,6 +1592,7 @@ to: blueprint 13cfdd24-52ba-4e94-8c83-02e3a48fc746 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1647,6 +1666,7 @@ to: blueprint b82656b0-a9be-433d-83d0-e2bdf371777a internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1717,6 +1737,7 @@ to: blueprint 31c84831-be52-4630-bc3f-128d72cd8f22 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1785,6 +1806,7 @@ to: blueprint 778e3f3a-58b1-4a5e-acff-d23c5d7124c2 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -1913,6 +1935,7 @@ to: blueprint 386a7ec3-7c2e-43cf-8f00-999e91e1d5e6 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2049,6 +2072,7 @@ to: blueprint e54a0836-53e1-4948-a3af-0b77165289b5 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index e3e7cd50ddf..5cd9ca9e500 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -81,6 +81,7 @@ pub struct Blueprint { pub creator: String, pub comment: String, pub target_release_minimum_generation: Generation, + pub nexus_generation: Generation, } impl From<&'_ nexus_types::deployment::Blueprint> for Blueprint { @@ -100,6 +101,7 @@ impl From<&'_ nexus_types::deployment::Blueprint> for Blueprint { target_release_minimum_generation: Generation( bp.target_release_minimum_generation, ), + nexus_generation: Generation(bp.nexus_generation), } } } @@ -113,6 +115,7 @@ impl From for nexus_types::deployment::BlueprintMetadata { external_dns_version: *value.external_dns_version, target_release_minimum_generation: *value .target_release_minimum_generation, + nexus_generation: *value.nexus_generation, cockroachdb_fingerprint: value.cockroachdb_fingerprint, cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade::from_optional_string( @@ -524,6 +527,7 @@ pub struct BpOmicronZone { pub image_source: DbBpZoneImageSource, pub image_artifact_sha256: Option, + pub nexus_generation: Option, } impl BpOmicronZone { @@ -585,6 +589,7 @@ impl BpOmicronZone { snat_ip: None, snat_first_port: None, snat_last_port: None, + nexus_generation: None, }; match &blueprint_zone.zone_type { @@ -716,6 +721,7 @@ impl BpOmicronZone { nic, external_tls, external_dns_servers, + nexus_generation, }) => { // Set the common fields bp_omicron_zone @@ -733,6 +739,8 @@ impl BpOmicronZone { .map(IpNetwork::from) .collect(), ); + bp_omicron_zone.nexus_generation = + Some(Generation::from(*nexus_generation)); } BlueprintZoneType::Oximeter(blueprint_zone_type::Oximeter { address, @@ -938,6 +946,9 @@ impl BpOmicronZone { .into_iter() .map(|i| i.ip()) .collect(), + nexus_generation: *self.nexus_generation.ok_or_else( + || anyhow!("expected 'nexus_generation'"), + )?, }) } ZoneType::Oximeter => { diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index c471d07e50d..5b259c0d7e3 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock}; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: Version = Version::new(185, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(186, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(186, "nexus-generation"), KnownVersion::new(185, "populate-db-metadata-nexus"), KnownVersion::new(184, "store-silo-admin-group-name"), KnownVersion::new(183, "add-ip-version-to-pools"), diff --git a/nexus/db-queries/src/db/datastore/db_metadata.rs b/nexus/db-queries/src/db/datastore/db_metadata.rs index 43a60817848..9e078022961 100644 --- a/nexus/db-queries/src/db/datastore/db_metadata.rs +++ b/nexus/db-queries/src/db/datastore/db_metadata.rs @@ -1020,6 +1020,7 @@ mod test { slot: 0, transit_ips: Vec::new(), }, + nexus_generation: Generation::new(), }), image_source: BlueprintZoneImageSource::InstallDataset, }) @@ -1048,6 +1049,7 @@ mod test { internal_dns_version: Generation::new(), external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade::DoNotModify, diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 0e46092de95..6ce19a93ff7 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -548,6 +548,7 @@ impl DataStore { internal_dns_version, external_dns_version, target_release_minimum_generation, + nexus_generation, cockroachdb_fingerprint, cockroachdb_setting_preserve_downgrade, time_created, @@ -574,6 +575,7 @@ impl DataStore { *blueprint.internal_dns_version, *blueprint.external_dns_version, *blueprint.target_release_minimum_generation, + *blueprint.nexus_generation, blueprint.cockroachdb_fingerprint, blueprint.cockroachdb_setting_preserve_downgrade, blueprint.time_created, @@ -1325,6 +1327,7 @@ impl DataStore { internal_dns_version, external_dns_version, target_release_minimum_generation, + nexus_generation, cockroachdb_fingerprint, cockroachdb_setting_preserve_downgrade, clickhouse_cluster_config, @@ -4271,6 +4274,7 @@ mod tests { }, external_tls: false, external_dns_servers: vec![], + nexus_generation: Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, diff --git a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs index e8cb951f85b..dd525fdbbc9 100644 --- a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs +++ b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs @@ -454,6 +454,7 @@ mod tests { use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_common::address::NTP_OPTE_IPV4_SUBNET; use omicron_common::address::NUM_SOURCE_NAT_PORTS; + use omicron_common::api::external::Generation; use omicron_common::api::external::MacAddr; use omicron_common::api::external::Vni; use omicron_common::zpool_name::ZpoolName; @@ -643,6 +644,7 @@ mod tests { nic: self.nexus_nic.clone(), external_tls: false, external_dns_servers: Vec::new(), + nexus_generation: Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index 31e0ec43284..8b090bd2c69 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -1107,6 +1107,7 @@ mod test { internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), target_release_minimum_generation: *Generation::new(), + nexus_generation: *Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: *Generation::new(), @@ -1531,6 +1532,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -1601,6 +1603,7 @@ mod test { internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), target_release_minimum_generation: *Generation::new(), + nexus_generation: *Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: *Generation::new(), @@ -1789,6 +1792,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -1822,6 +1826,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -1866,6 +1871,7 @@ mod test { internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), target_release_minimum_generation: *Generation::new(), + nexus_generation: *Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: *Generation::new(), @@ -2072,6 +2078,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -2123,6 +2130,7 @@ mod test { creator: "test suite".to_string(), comment: "test blueprint".to_string(), report: PlanningReport::new(blueprint_id), + nexus_generation: *Generation::new(), }; let rack = datastore @@ -2296,6 +2304,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -2314,6 +2323,7 @@ mod test { internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), target_release_minimum_generation: *Generation::new(), + nexus_generation: *Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: *Generation::new(), @@ -2436,6 +2446,7 @@ mod test { slot: 0, transit_ips: vec![], }, + nexus_generation: *Generation::new(), }, ), image_source: BlueprintZoneImageSource::InstallDataset, @@ -2456,6 +2467,7 @@ mod test { internal_dns_version: *Generation::new(), external_dns_version: *Generation::new(), target_release_minimum_generation: *Generation::new(), + nexus_generation: *Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: *Generation::new(), diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 4f8bd54a7a2..23d4aa089e5 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -1970,6 +1970,8 @@ table! { cockroachdb_setting_preserve_downgrade -> Nullable, target_release_minimum_generation -> Int8, + + nexus_generation -> Int8, } } @@ -2070,6 +2072,7 @@ table! { filesystem_pool -> Uuid, image_source -> crate::enums::BpZoneImageSourceEnum, image_artifact_sha256 -> Nullable, + nexus_generation -> Nullable, } } diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 2ce03d66421..1e3cc26ea68 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -597,6 +597,7 @@ mod test { nic, external_tls, external_dns_servers, + nexus_generation: Generation::new(), }) } OmicronZoneType::Oximeter { address } => { @@ -720,6 +721,7 @@ mod test { internal_dns_version: initial_dns_generation, external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: Generation::new(), diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 0601c852fa7..7cf11ac45ea 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -515,6 +515,7 @@ pub struct BlueprintBuilder<'a> { sled_editors: BTreeMap, cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade, target_release_minimum_generation: Generation, + nexus_generation: Generation, report: Option, creator: String, @@ -582,6 +583,7 @@ impl<'a> BlueprintBuilder<'a> { internal_dns_version: Generation::new(), external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade::DoNotModify, @@ -663,6 +665,7 @@ impl<'a> BlueprintBuilder<'a> { pending_mgs_updates: parent_blueprint.pending_mgs_updates.clone(), target_release_minimum_generation: parent_blueprint .target_release_minimum_generation, + nexus_generation: parent_blueprint.nexus_generation, report: None, creator: creator.to_owned(), operations: Vec::new(), @@ -857,6 +860,7 @@ impl<'a> BlueprintBuilder<'a> { external_dns_version: self.input.external_dns_version(), target_release_minimum_generation: self .target_release_minimum_generation, + nexus_generation: self.nexus_generation, cockroachdb_fingerprint: self .input .cockroachdb_settings() @@ -1608,6 +1612,7 @@ impl<'a> BlueprintBuilder<'a> { nic, external_tls, external_dns_servers: external_dns_servers.clone(), + nexus_generation: Generation::new(), }); let filesystem_pool = self.sled_select_zpool(sled_id, zone_type.kind())?; diff --git a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt index 15d2c2d6a77..6125703611f 100644 --- a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt +++ b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt @@ -531,6 +531,7 @@ parent: e35b2fdd-354d-48d9-acb5-703b2c269a54 internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt index 99e950f246e..98982124114 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_2_3.txt @@ -71,6 +71,7 @@ to: blueprint 4171ad05-89dd-474b-846b-b007e4346366 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt index 41df1375187..b724ec830ab 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_basic_add_sled_3_5.txt @@ -101,6 +101,7 @@ to: blueprint f432fcd5-1284-4058-8b4a-9286a3de6163 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt index 0e936766516..b619e3f7ddf 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_dataset_settings_modified_in_place_1_2.txt @@ -122,6 +122,7 @@ to: blueprint fe13be30-94c2-4fa6-aad5-ae3c5028f6bb internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt index 488e3f69d00..e79bf2daf74 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_1_2.txt @@ -387,6 +387,7 @@ to: blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt index 93d346b3170..ea1d786354f 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt @@ -322,6 +322,7 @@ parent: 516e80a3-b362-4fac-bd3c-4559717120dd internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_1_2.txt index 63380e2c1eb..1be47958ab0 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_1_2.txt @@ -320,6 +320,7 @@ to: blueprint 31ef2071-2ec9-49d9-8827-fd83b17a0e3d internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_3_4.txt b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_3_4.txt index b2d2dee5588..af0649e7506 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_3_4.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_3_4.txt @@ -9,6 +9,7 @@ to: blueprint 92fa943c-7dd4-48c3-9447-c9d0665744b6 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_4_5.txt b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_4_5.txt index ea64f823b0a..0dc39530072 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_4_5.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_4_5.txt @@ -223,6 +223,7 @@ to: blueprint 2886dab5-61a2-46b4-87af-bc7aeb44cccb internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_5_6.txt b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_5_6.txt index 6b4c5f48e30..e33a4d4a9f0 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_5_6.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_deploy_all_keeper_nodes_5_6.txt @@ -9,6 +9,7 @@ to: blueprint cb39be9d-5476-44fa-9edf-9938376219ef internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_3_4.txt b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_3_4.txt index 9394b253cc6..64f86ecfe65 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_3_4.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_3_4.txt @@ -408,6 +408,7 @@ to: blueprint 74f2e7fd-687e-4c9e-b5d8-e474a5bb8e7c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_5_6.txt b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_5_6.txt index 744379716fc..f5a11fe0dbc 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_5_6.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_clusters_5_6.txt @@ -9,6 +9,7 @@ to: blueprint df68d4d4-5af4-4b56-95bb-1654a6957d4f internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_zones_after_policy_is_changed_3_4.txt b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_zones_after_policy_is_changed_3_4.txt index 5e439554691..fd72c3cda5c 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_zones_after_policy_is_changed_3_4.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_expunge_clickhouse_zones_after_policy_is_changed_3_4.txt @@ -338,6 +338,7 @@ to: blueprint d895ef50-9978-454c-bdfb-b8dbe2c9a918 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt index 99ccd504aaf..13f6efd866f 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_1_2.txt @@ -373,6 +373,7 @@ to: blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt index 8bd822a364c..162e4c1ad69 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_2_2a.txt @@ -349,6 +349,9 @@ mismatched zone type: after: Nexus( }, external_tls: false, external_dns_servers: [], + nexus_generation: Generation( + 1, + ), }, ) @@ -368,6 +371,7 @@ mismatched zone type: after: InternalNtp( internal DNS version::: 1 (unchanged) * external DNS version::: 1 -> 2 target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt index 77c19780bed..e26d517c3e6 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt @@ -510,6 +510,7 @@ parent: 4d4e6c38-cd95-4c4e-8f45-6af4d686964b internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt b/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt index 440e7e28e51..1f229c2ec10 100644 --- a/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt +++ b/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt @@ -122,6 +122,7 @@ to: blueprint 1481141d-a5cf-4103-8344-738967e0f110 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) diff --git a/nexus/src/app/background/tasks/blueprint_execution.rs b/nexus/src/app/background/tasks/blueprint_execution.rs index 2ac61471e7c..27315202719 100644 --- a/nexus/src/app/background/tasks/blueprint_execution.rs +++ b/nexus/src/app/background/tasks/blueprint_execution.rs @@ -278,6 +278,7 @@ mod test { internal_dns_version: dns_version, external_dns_version: dns_version, target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: Generation::new(), diff --git a/nexus/src/app/background/tasks/blueprint_load.rs b/nexus/src/app/background/tasks/blueprint_load.rs index d2d9c7c380e..7b7f546388d 100644 --- a/nexus/src/app/background/tasks/blueprint_load.rs +++ b/nexus/src/app/background/tasks/blueprint_load.rs @@ -225,6 +225,7 @@ mod test { internal_dns_version: Generation::new(), external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), clickhouse_cluster_config: None, oximeter_read_version: Generation::new(), diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index d4655b3eede..1a5a2847cdd 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -882,6 +882,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { vni: Vni::SERVICES_VNI, transit_ips: vec![], }, + nexus_generation: Generation::new(), }), image_source: BlueprintZoneImageSource::InstallDataset, }); @@ -967,6 +968,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { internal_dns_version: dns_config.generation, external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), cockroachdb_fingerprint: String::new(), cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade::DoNotModify, diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 5928daf9363..a32d8ec2d5d 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -227,6 +227,12 @@ pub struct Blueprint { /// driving the system to the target release. pub target_release_minimum_generation: Generation, + /// The generation of the active group of Nexuses + /// + /// If a Nexus instance notices it has a nexus_generation less than + /// this value, it will start to quiesce (see: RFD 588). + pub nexus_generation: Generation, + /// CockroachDB state fingerprint when this blueprint was created // See `nexus/db-queries/src/db/datastore/cockroachdb_settings.rs` for more // on this. @@ -275,6 +281,7 @@ impl Blueprint { external_dns_version: self.external_dns_version, target_release_minimum_generation: self .target_release_minimum_generation, + nexus_generation: self.nexus_generation, cockroachdb_fingerprint: self.cockroachdb_fingerprint.clone(), cockroachdb_setting_preserve_downgrade: Some( self.cockroachdb_setting_preserve_downgrade, @@ -609,6 +616,7 @@ impl BlueprintDisplay<'_> { .target_release_minimum_generation .to_string(), ), + (NEXUS_GENERATION, self.blueprint.nexus_generation.to_string()), ], ) } @@ -651,6 +659,7 @@ impl fmt::Display for BlueprintDisplay<'_> { // These six fields are handled by `make_metadata_table()`, called // below. target_release_minimum_generation: _, + nexus_generation: _, internal_dns_version: _, external_dns_version: _, time_created: _, @@ -2073,6 +2082,10 @@ pub struct BlueprintMetadata { /// /// See [`Blueprint::target_release_minimum_generation`]. pub target_release_minimum_generation: Generation, + /// The Nexus generation number + /// + /// See [`Blueprint::nexus_generation`]. + pub nexus_generation: Generation, /// CockroachDB state fingerprint when this blueprint was created pub cockroachdb_fingerprint: String, /// Whether to set `cluster.preserve_downgrade_option` and what to set it to diff --git a/nexus/types/src/deployment/blueprint_diff.rs b/nexus/types/src/deployment/blueprint_diff.rs index a29cb57317f..6a1646ddd48 100644 --- a/nexus/types/src/deployment/blueprint_diff.rs +++ b/nexus/types/src/deployment/blueprint_diff.rs @@ -64,6 +64,7 @@ impl<'a> BlueprintDiffSummary<'a> { pending_mgs_updates, clickhouse_cluster_config, target_release_minimum_generation, + nexus_generation, // Metadata fields for which changes don't reflect semantic // changes from one blueprint to the next. id: _, @@ -112,6 +113,11 @@ impl<'a> BlueprintDiffSummary<'a> { return true; } + // Did the nexus generation change? + if nexus_generation.before != nexus_generation.after { + return true; + } + // All fields checked or ignored; if we get here, there are no // meaningful changes. false @@ -1834,6 +1840,7 @@ impl<'diff, 'b> BlueprintDiffDisplay<'diff, 'b> { target_release_minimum_generation, TARGET_RELEASE_MIN_GEN ), + diff_row!(nexus_generation, NEXUS_GENERATION), ], ), ] diff --git a/nexus/types/src/deployment/blueprint_display.rs b/nexus/types/src/deployment/blueprint_display.rs index e0dc0080f95..dec9ce3e699 100644 --- a/nexus/types/src/deployment/blueprint_display.rs +++ b/nexus/types/src/deployment/blueprint_display.rs @@ -44,6 +44,7 @@ pub mod constants { pub const EXTERNAL_DNS_VERSION: &str = "external DNS version"; // Keep this a bit short to not make the key column too wide. pub const TARGET_RELEASE_MIN_GEN: &str = "target release min gen"; + pub const NEXUS_GENERATION: &str = "nexus gen"; pub const COMMENT: &str = "comment"; pub const UNCHANGED_PARENS: &str = "(unchanged)"; diff --git a/nexus/types/src/deployment/zone_type.rs b/nexus/types/src/deployment/zone_type.rs index 31e26c3a994..79cb68fb98a 100644 --- a/nexus/types/src/deployment/zone_type.rs +++ b/nexus/types/src/deployment/zone_type.rs @@ -343,6 +343,7 @@ pub mod blueprint_zone_type { use crate::deployment::OmicronZoneExternalSnatIp; use daft::Diffable; use nexus_sled_agent_shared::inventory::OmicronZoneDataset; + use omicron_common::api::external::Generation; use omicron_common::api::internal::shared::NetworkInterface; use schemars::JsonSchema; use serde::Deserialize; @@ -566,6 +567,10 @@ pub mod blueprint_zone_type { pub external_tls: bool, /// External DNS servers Nexus can use to resolve external hosts. pub external_dns_servers: Vec, + /// Generation number for this Nexus zone. + /// This is used to coordinate handoff between old and new Nexus instances + /// during updates. See RFD 588. + pub nexus_generation: Generation, } #[derive( diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 3d41de3fe62..60664fce254 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2568,6 +2568,14 @@ } ] }, + "nexus_generation": { + "description": "The generation of the active group of Nexuses\n\nIf a Nexus instance notices it has a nexus_generation less than this value, it will start to quiesce (see: RFD 588).", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, "oximeter_read_mode": { "description": "Whether oximeter should read from a single node or a cluster", "allOf": [ @@ -2638,6 +2646,7 @@ "external_dns_version", "id", "internal_dns_version", + "nexus_generation", "oximeter_read_mode", "oximeter_read_version", "pending_mgs_updates", @@ -2862,6 +2871,14 @@ } ] }, + "nexus_generation": { + "description": "The Nexus generation number\n\nSee [`Blueprint::nexus_generation`].", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, "parent_blueprint_id": { "nullable": true, "description": "which blueprint this blueprint is based on", @@ -2892,6 +2909,7 @@ "external_dns_version", "id", "internal_dns_version", + "nexus_generation", "target_release_minimum_generation", "time_created" ] @@ -3517,6 +3535,14 @@ "description": "The address at which the internal nexus server is reachable.", "type": "string" }, + "nexus_generation": { + "description": "Generation number for this Nexus zone. This is used to coordinate handoff between old and new Nexus instances during updates. See RFD 588.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, "nic": { "description": "The service vNIC providing external connectivity using OPTE.", "allOf": [ @@ -3537,6 +3563,7 @@ "external_ip", "external_tls", "internal_address", + "nexus_generation", "nic", "type" ] diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index bad381cb5c8..aedc342e6ea 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4524,7 +4524,10 @@ CREATE TABLE IF NOT EXISTS omicron.public.blueprint ( -- driving the system to the target release. -- -- This is set to 1 by default in application code. - target_release_minimum_generation INT8 NOT NULL + target_release_minimum_generation INT8 NOT NULL, + + -- The generation of the active group of Nexus instances + nexus_generation INT8 NOT NULL ); -- table describing both the current and historical target blueprints of the @@ -4734,6 +4737,9 @@ CREATE TABLE IF NOT EXISTS omicron.public.bp_omicron_zone ( image_source omicron.public.bp_zone_image_source NOT NULL, image_artifact_sha256 STRING(64), + -- Generation for Nexus zones + nexus_generation INT8, + PRIMARY KEY (blueprint_id, id), CONSTRAINT expunged_disposition_properties CHECK ( @@ -6590,7 +6596,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '185.0.0', NULL) + (TRUE, NOW(), NOW(), '186.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/nexus-generation/up01.sql b/schema/crdb/nexus-generation/up01.sql new file mode 100644 index 00000000000..42d87c2f6f7 --- /dev/null +++ b/schema/crdb/nexus-generation/up01.sql @@ -0,0 +1 @@ +ALTER TABLE omicron.public.bp_omicron_zone ADD COLUMN IF NOT EXISTS nexus_generation INT8; diff --git a/schema/crdb/nexus-generation/up02.sql b/schema/crdb/nexus-generation/up02.sql new file mode 100644 index 00000000000..53429df8ebe --- /dev/null +++ b/schema/crdb/nexus-generation/up02.sql @@ -0,0 +1,5 @@ +SET LOCAL disallow_full_table_scans = off; + +UPDATE omicron.public.bp_omicron_zone +SET nexus_generation = 1 +WHERE zone_type = 'nexus'; diff --git a/schema/crdb/nexus-generation/up03.sql b/schema/crdb/nexus-generation/up03.sql new file mode 100644 index 00000000000..d7623a84c80 --- /dev/null +++ b/schema/crdb/nexus-generation/up03.sql @@ -0,0 +1 @@ +ALTER TABLE omicron.public.blueprint ADD COLUMN IF NOT EXISTS nexus_generation INT8 NOT NULL DEFAULT 1; diff --git a/schema/crdb/nexus-generation/up04.sql b/schema/crdb/nexus-generation/up04.sql new file mode 100644 index 00000000000..072231d9b01 --- /dev/null +++ b/schema/crdb/nexus-generation/up04.sql @@ -0,0 +1 @@ +ALTER TABLE omicron.public.blueprint ALTER COLUMN nexus_generation DROP DEFAULT; diff --git a/sled-agent/src/rack_setup/plan/service.rs b/sled-agent/src/rack_setup/plan/service.rs index f7941e9724c..37ea4757d9f 100644 --- a/sled-agent/src/rack_setup/plan/service.rs +++ b/sled-agent/src/rack_setup/plan/service.rs @@ -26,7 +26,7 @@ use omicron_common::address::{ RSS_RESERVED_ADDRESSES, ReservedRackSubnet, SLED_PREFIX, get_sled_address, get_switch_zone_address, }; -use omicron_common::api::external::{MacAddr, Vni}; +use omicron_common::api::external::{Generation, MacAddr, Vni}; use omicron_common::api::internal::shared::{ NetworkInterface, NetworkInterfaceKind, SourceNatConfig, SourceNatConfigError, @@ -570,6 +570,7 @@ impl Plan { // development that it might not be. external_tls: !config.external_certificates.is_empty(), external_dns_servers: config.dns_servers.clone(), + nexus_generation: Generation::new(), }, ), filesystem_pool, diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index e2eaac58500..f18e205b6f7 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -1631,6 +1631,7 @@ pub(crate) fn build_initial_blueprint_from_sled_configs( // (including creating the recovery silo). external_dns_version: Generation::new(), target_release_minimum_generation: Generation::new(), + nexus_generation: Generation::new(), // Nexus will fill in the CockroachDB values during initialization. cockroachdb_fingerprint: String::new(), cockroachdb_setting_preserve_downgrade: diff --git a/sled-agent/src/sim/server.rs b/sled-agent/src/sim/server.rs index 12561713a75..b41bddf85ca 100644 --- a/sled-agent/src/sim/server.rs +++ b/sled-agent/src/sim/server.rs @@ -453,6 +453,7 @@ pub async fn run_standalone_server( }, external_tls: false, external_dns_servers: vec![], + nexus_generation: Generation::new(), }), filesystem_pool: get_random_zpool(), image_source: BlueprintZoneImageSource::InstallDataset, From faa0b5dd4876562ad3e689afb749854959d29129 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 29 Aug 2025 13:47:34 -0700 Subject: [PATCH 3/6] non-fatal step, executioncomponent order --- nexus/reconfigurator/execution/src/lib.rs | 16 ++++++++++------ nexus/types/src/deployment/execution/spec.rs | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/nexus/reconfigurator/execution/src/lib.rs b/nexus/reconfigurator/execution/src/lib.rs index 5060a037e22..adfda2e5958 100644 --- a/nexus/reconfigurator/execution/src/lib.rs +++ b/nexus/reconfigurator/execution/src/lib.rs @@ -409,13 +409,17 @@ fn register_deploy_db_metadata_nexus_records_step<'a>( .new_step( ExecutionStepId::Ensure, "Ensure db_metadata_nexus_state records exist", - async move |_cx| { - database::deploy_db_metadata_nexus_records( - opctx, &datastore, &blueprint, + async move |_cx| match database::deploy_db_metadata_nexus_records( + opctx, &datastore, &blueprint, + ) + .await + { + Ok(()) => StepSuccess::new(()).into(), + Err(err) => StepWarning::new( + (), + err.context("ensuring db_metadata_nexus_state").to_string(), ) - .await - .context("ensuring db_metadata_nexus_state")?; - StepSuccess::new(()).into() + .into(), }, ) .register(); diff --git a/nexus/types/src/deployment/execution/spec.rs b/nexus/types/src/deployment/execution/spec.rs index 27d85b431db..df02d3c58a3 100644 --- a/nexus/types/src/deployment/execution/spec.rs +++ b/nexus/types/src/deployment/execution/spec.rs @@ -30,8 +30,8 @@ pub enum ExecutionComponent { ExternalNetworking, SupportBundles, SledList, - SledAgent, DeployNexusRecords, + SledAgent, PhysicalDisks, OmicronZones, FirewallRules, From eafafe4a8f6dfa4c5e7fa436e675f5956aa4363e Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 29 Aug 2025 14:18:02 -0700 Subject: [PATCH 4/6] add constraint, comment --- nexus/types/src/deployment.rs | 3 ++- schema/crdb/dbinit.sql | 6 ++++++ schema/crdb/nexus-generation/up05.sql | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 schema/crdb/nexus-generation/up05.sql diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index a32d8ec2d5d..2c66a5f8575 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -230,7 +230,8 @@ pub struct Blueprint { /// The generation of the active group of Nexuses /// /// If a Nexus instance notices it has a nexus_generation less than - /// this value, it will start to quiesce (see: RFD 588). + /// this value, it will start to quiesce in preparation for handing off + /// control to the newer generation (see: RFD 588). pub nexus_generation: Generation, /// CockroachDB state fingerprint when this blueprint was created diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index aedc342e6ea..552d3cf3d0e 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4757,6 +4757,12 @@ CREATE TABLE IF NOT EXISTS omicron.public.bp_omicron_zone ( OR (image_source != 'artifact' AND image_artifact_sha256 IS NULL) + ), + + CONSTRAINT nexus_generation_for_nexus_zones CHECK ( + (zone_type = 'nexus' AND nexus_generation IS NOT NULL) + OR + (zone_type != 'nexus' AND nexus_generation IS NULL) ) ); diff --git a/schema/crdb/nexus-generation/up05.sql b/schema/crdb/nexus-generation/up05.sql new file mode 100644 index 00000000000..6818b887a54 --- /dev/null +++ b/schema/crdb/nexus-generation/up05.sql @@ -0,0 +1,5 @@ +ALTER TABLE omicron.public.bp_omicron_zone ADD CONSTRAINT IF NOT EXISTS nexus_generation_for_nexus_zones CHECK ( + (zone_type = 'nexus' AND nexus_generation IS NOT NULL) + OR + (zone_type != 'nexus' AND nexus_generation IS NULL) +); From 17628050d53f49cbc48464ded55b7e88b25a454b Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 29 Aug 2025 17:35:18 -0700 Subject: [PATCH 5/6] fix mismerge --- nexus/db-model/src/schema_versions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 2531290f102..5b259c0d7e3 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -29,6 +29,7 @@ static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), KnownVersion::new(186, "nexus-generation"), + KnownVersion::new(185, "populate-db-metadata-nexus"), KnownVersion::new(184, "store-silo-admin-group-name"), KnownVersion::new(183, "add-ip-version-to-pools"), KnownVersion::new(182, "add-tuf-artifact-board"), From 77576773b7584c9c76348276726ace6b2f6b17f7 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 29 Aug 2025 21:23:55 -0700 Subject: [PATCH 6/6] patch expectorate output --- .../tests/output/cmds-target-release-stdout | 34 +++++++++++++++++++ openapi/nexus-internal.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout index f26eb41207d..bdfc47b82b1 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -2204,6 +2204,7 @@ to: blueprint 459a45a5-616e-421f-873b-2fb08c36205c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2341,6 +2342,7 @@ to: blueprint b2295597-5788-482e-acf9-1731ec63fbd2 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2473,6 +2475,7 @@ to: blueprint 6fad8fd4-e825-433f-b76d-495484e068ce internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2620,6 +2623,7 @@ to: blueprint 24b6e243-100c-428d-8ea6-35b504226f55 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2767,6 +2771,7 @@ to: blueprint 79fff7a2-2495-4c75-8465-4dc01bab48ce internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -2894,6 +2899,7 @@ to: blueprint 3bcc37b2-0c0b-44d0-b4ed-3bcb605e4312 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3044,6 +3050,7 @@ to: blueprint 4d2eb6f3-7eb1-443a-8e76-7ecf05da2f6d internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3195,6 +3202,7 @@ to: blueprint e2125c83-b255-45c9-bc9b-802cff09a812 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3333,6 +3341,7 @@ to: blueprint f4a6848e-d13c-46e1-8c6a-944f886d7ba3 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3471,6 +3480,7 @@ to: blueprint 834e4dbe-3b71-443d-bd4c-20e8253abc0c internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3601,6 +3611,7 @@ to: blueprint d9c5c5e3-c532-4c45-9ef5-22cb00f6a2e1 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3721,6 +3732,7 @@ to: blueprint e2deb7c0-2262-49fe-855f-4250c22afb36 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3841,6 +3853,7 @@ to: blueprint 23ce505c-8991-44a5-8863-f2b906fba9cf internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -3980,6 +3993,7 @@ to: blueprint c0d81ea6-909c-4efb-964e-beff67f6da0d internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4119,6 +4133,7 @@ to: blueprint 60b55d33-5fec-4277-9864-935197eaead7 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4240,6 +4255,7 @@ to: blueprint aa13f40f-41ff-4b68-bee1-df2e1f805544 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4371,6 +4387,7 @@ to: blueprint 316ccd9e-5c53-46c3-a2e9-20c3867b7111 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4503,6 +4520,7 @@ to: blueprint 02078c95-3d58-4b7b-a03f-9b160361c50a internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4647,6 +4665,7 @@ to: blueprint e7a01ffc-6b0e-408b-917b-b1efe18b3110 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4792,6 +4811,7 @@ to: blueprint 880e2ffc-8187-4275-a2f3-1b36aa2f4482 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -4927,6 +4947,7 @@ to: blueprint c4a20bcb-1a71-4e88-97b4-36d16f55daec internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5062,6 +5083,7 @@ to: blueprint a2c6496d-98fc-444d-aa36-99508aa72367 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5182,6 +5204,7 @@ to: blueprint 6ed56354-5941-40d1-a06c-b0e940701d52 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5301,6 +5324,7 @@ to: blueprint 9078c4ba-3a73-4b3f-ac2c-acb501f89cb2 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5428,6 +5452,7 @@ to: blueprint 8763abc1-8a42-4932-b5a7-33109e0e0152 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5555,6 +5580,7 @@ to: blueprint 2b89e0d7-f15b-4474-8ac4-85959ed1bc88 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5676,6 +5702,7 @@ to: blueprint 7f6b7297-c2bc-4f67-b3c0-c8e555ebbdc4 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5814,6 +5841,7 @@ to: blueprint 59630e63-c953-4807-9e84-9e750a79f68e internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -5953,6 +5981,7 @@ to: blueprint e93650dc-b5ba-4ec7-8550-9171c1ada194 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -6086,6 +6115,7 @@ to: blueprint 90650737-8142-47a6-9a48-a10efc487e57 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -6218,6 +6248,7 @@ to: blueprint 2182613d-dc9f-41eb-9c6a-d33801849caa internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -6344,6 +6375,7 @@ to: blueprint e8b088a8-7da0-480b-a2dc-75ffef068ece internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -6487,6 +6519,7 @@ to: blueprint 810ea95a-4730-43dd-867e-1984aeb9d873 internal DNS version::: 1 (unchanged) external DNS version::: 1 (unchanged) target release min gen: 1 (unchanged) + nexus gen:::::::::::::: 1 (unchanged) OXIMETER SETTINGS: generation: 1 (unchanged) @@ -6751,6 +6784,7 @@ parent: e8b088a8-7da0-480b-a2dc-75ffef068ece internal DNS version::: 1 external DNS version::: 1 target release min gen: 1 + nexus gen:::::::::::::: 1 PENDING MGS-MANAGED UPDATES: 0 diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 60664fce254..bc1cd3df567 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2569,7 +2569,7 @@ ] }, "nexus_generation": { - "description": "The generation of the active group of Nexuses\n\nIf a Nexus instance notices it has a nexus_generation less than this value, it will start to quiesce (see: RFD 588).", + "description": "The generation of the active group of Nexuses\n\nIf a Nexus instance notices it has a nexus_generation less than this value, it will start to quiesce in preparation for handing off control to the newer generation (see: RFD 588).", "allOf": [ { "$ref": "#/components/schemas/Generation"