From 189ec3d1bc4df0a8d7e5fccb2c08a4b15097f7e0 Mon Sep 17 00:00:00 2001 From: Sean Klein Date: Fri, 18 Jul 2025 11:31:43 -0700 Subject: [PATCH] Collect internal DNS generation in inventory --- Cargo.lock | 1 + dev-tools/ls-apis/tests/api_dependencies.out | 2 +- nexus/db-model/src/inventory.rs | 38 ++++++++++- nexus/db-model/src/schema_versions.rs | 3 +- .../db-queries/src/db/datastore/inventory.rs | 56 ++++++++++++++++ nexus/db-schema/src/schema.rs | 8 +++ nexus/inventory/Cargo.toml | 1 + nexus/inventory/src/builder.rs | 18 +++++ nexus/inventory/src/collector.rs | 67 ++++++++++++++++++- nexus/inventory/src/examples.rs | 8 +++ .../background/tasks/inventory_collection.rs | 22 ++++++ nexus/types/src/inventory.rs | 20 ++++++ nexus/types/src/inventory/display.rs | 12 ++++ schema/crdb/dbinit.sql | 9 ++- schema/crdb/inv-internal-dns/up01.sql | 7 ++ 15 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 schema/crdb/inv-internal-dns/up01.sql diff --git a/Cargo.lock b/Cargo.lock index 67c5432690..10903916e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6484,6 +6484,7 @@ dependencies = [ "clickhouse-admin-keeper-client", "clickhouse-admin-server-client", "clickhouse-admin-types", + "dns-service-client", "expectorate", "futures", "gateway-client", diff --git a/dev-tools/ls-apis/tests/api_dependencies.out b/dev-tools/ls-apis/tests/api_dependencies.out index 465e197318..7fde1606ba 100644 --- a/dev-tools/ls-apis/tests/api_dependencies.out +++ b/dev-tools/ls-apis/tests/api_dependencies.out @@ -32,7 +32,7 @@ Maghemite DDM Admin (client: ddm-admin-client) consumed by: wicketd (omicron/wicketd) via 1 path DNS Server (client: dns-service-client) - consumed by: omicron-nexus (omicron/nexus) via 1 path + consumed by: omicron-nexus (omicron/nexus) via 2 paths consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path Dendrite DPD (client: dpd-client) diff --git a/nexus/db-model/src/inventory.rs b/nexus/db-model/src/inventory.rs index f5bf7579bc..682004bfc0 100644 --- a/nexus/db-model/src/inventory.rs +++ b/nexus/db-model/src/inventory.rs @@ -30,7 +30,8 @@ use nexus_db_schema::schema::inv_zone_manifest_zone; use nexus_db_schema::schema::{ hw_baseboard_id, inv_caboose, inv_clickhouse_keeper_membership, inv_cockroachdb_status, inv_collection, inv_collection_error, inv_dataset, - inv_host_phase_1_flash_hash, inv_last_reconciliation_dataset_result, + inv_host_phase_1_flash_hash, inv_internal_dns, + inv_last_reconciliation_dataset_result, inv_last_reconciliation_disk_result, inv_last_reconciliation_orphaned_dataset, inv_last_reconciliation_zone_result, inv_mupdate_override_non_boot, @@ -60,8 +61,9 @@ use nexus_sled_agent_shared::inventory::{ OmicronZoneDataset, OmicronZoneImageSource, OmicronZoneType, }; use nexus_types::inventory::{ - BaseboardId, Caboose, CockroachStatus, Collection, NvmeFirmware, - PowerState, RotPage, RotSlot, TimeSync, + BaseboardId, Caboose, CockroachStatus, Collection, + InternalDnsGenerationStatus, NvmeFirmware, PowerState, RotPage, RotSlot, + TimeSync, }; use omicron_common::api::external; use omicron_common::api::internal::shared::NetworkInterface; @@ -2860,6 +2862,36 @@ impl From for nexus_types::inventory::TimeSync { } } +#[derive(Queryable, Clone, Debug, Selectable, Insertable)] +#[diesel(table_name = inv_internal_dns)] +pub struct InvInternalDns { + pub inv_collection_id: DbTypedUuid, + pub zone_id: DbTypedUuid, + pub generation: Generation, +} + +impl InvInternalDns { + pub fn new( + inv_collection_id: CollectionUuid, + status: &InternalDnsGenerationStatus, + ) -> Result { + Ok(Self { + inv_collection_id: inv_collection_id.into(), + zone_id: status.zone_id.into(), + generation: Generation(status.generation), + }) + } +} + +impl From for InternalDnsGenerationStatus { + fn from(value: InvInternalDns) -> Self { + Self { + zone_id: value.zone_id.into(), + generation: value.generation.into(), + } + } +} + #[cfg(test)] mod test { use nexus_types::inventory::NvmeFirmware; diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 3d54aea3ef..982b846aab 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(170, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(171, 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(171, "inv-internal-dns"), KnownVersion::new(170, "add-pending-mgs-updates-rot-bootloader"), KnownVersion::new(169, "inv-ntp-timesync"), KnownVersion::new(168, "add-inv-host-phase-1-flash-hash"), diff --git a/nexus/db-queries/src/db/datastore/inventory.rs b/nexus/db-queries/src/db/datastore/inventory.rs index a04b2e1b8a..7fd7e3617a 100644 --- a/nexus/db-queries/src/db/datastore/inventory.rs +++ b/nexus/db-queries/src/db/datastore/inventory.rs @@ -41,6 +41,7 @@ use nexus_db_model::InvConfigReconcilerStatus; use nexus_db_model::InvConfigReconcilerStatusKind; use nexus_db_model::InvDataset; use nexus_db_model::InvHostPhase1FlashHash; +use nexus_db_model::InvInternalDns; use nexus_db_model::InvLastReconciliationDatasetResult; use nexus_db_model::InvLastReconciliationDiskResult; use nexus_db_model::InvLastReconciliationOrphanedDataset; @@ -96,6 +97,7 @@ use nexus_sled_agent_shared::inventory::ZoneManifestNonBootInventory; use nexus_types::inventory::BaseboardId; use nexus_types::inventory::CockroachStatus; use nexus_types::inventory::Collection; +use nexus_types::inventory::InternalDnsGenerationStatus; use nexus_types::inventory::PhysicalDiskFirmware; use nexus_types::inventory::SledAgent; use nexus_types::inventory::TimeSync; @@ -398,6 +400,13 @@ impl DataStore { .collect::, _>>() .map_err(|e| Error::internal_error(&e.to_string()))?; + let inv_internal_dns_records: Vec = collection + .internal_dns_generation_status + .iter() + .map(|status| InvInternalDns::new(collection_id, status)) + .collect::, _>>() + .map_err(|e| Error::internal_error(&e.to_string()))?; + // This implementation inserts all records associated with the // collection in one transaction. This is primarily for simplicity. It // means we don't have to worry about other readers seeing a @@ -1516,6 +1525,15 @@ impl DataStore { .await?; } + // Insert the internal DNS generation status we've observed + if !inv_internal_dns_records.is_empty() { + use nexus_db_schema::schema::inv_internal_dns::dsl; + diesel::insert_into(dsl::inv_internal_dns) + .values(inv_internal_dns_records) + .execute_async(&conn) + .await?; + } + // Finally, insert the list of errors. { use nexus_db_schema::schema::inv_collection_error::dsl as errors_dsl; @@ -1807,6 +1825,7 @@ impl DataStore { nclickhouse_keeper_membership: usize, ncockroach_status: usize, nntp_timesync: usize, + ninternal_dns: usize, } let NumRowsDeleted { @@ -1839,6 +1858,7 @@ impl DataStore { nclickhouse_keeper_membership, ncockroach_status, nntp_timesync, + ninternal_dns, } = self.transaction_retry_wrapper("inventory_delete_collection") .transaction(&conn, |conn| async move { @@ -2117,6 +2137,18 @@ impl DataStore { .await? }; + // Remove rows for internal DNS + let ninternal_dns = { + use nexus_db_schema::schema::inv_internal_dns::dsl; + diesel::delete( + dsl::inv_internal_dns.filter( + dsl::inv_collection_id.eq(db_collection_id), + ), + ) + .execute_async(&conn) + .await? + }; + Ok(NumRowsDeleted { ncollections, nsps, @@ -2147,6 +2179,7 @@ impl DataStore { nclickhouse_keeper_membership, ncockroach_status, nntp_timesync, + ninternal_dns, }) }) .await @@ -2189,6 +2222,7 @@ impl DataStore { "nclickhouse_keeper_membership" => nclickhouse_keeper_membership, "ncockroach_status" => ncockroach_status, "nntp_timesync" => nntp_timesync, + "ninternal_dns" => ninternal_dns, ); Ok(()) @@ -3638,6 +3672,27 @@ impl DataStore { .collect::>() }; + // Load internal DNS generation status + let internal_dns_generation_status: IdOrdMap< + InternalDnsGenerationStatus, + > = { + use nexus_db_schema::schema::inv_internal_dns::dsl; + + let records: Vec = dsl::inv_internal_dns + .filter(dsl::inv_collection_id.eq(db_id)) + .select(InvInternalDns::as_select()) + .load_async(&*conn) + .await + .map_err(|e| { + public_error_from_diesel(e, ErrorHandler::Server) + })?; + + records + .into_iter() + .map(|record| InternalDnsGenerationStatus::from(record)) + .collect::>() + }; + // Finally, build up the sled-agent map using the sled agent and // omicron zone rows. A for loop is easier to understand than into_iter // + filter_map + return Result + collect. @@ -3886,6 +3941,7 @@ impl DataStore { clickhouse_keeper_cluster_membership, cockroach_status, ntp_timesync, + internal_dns_generation_status, }) } } diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 36826201fa..c1b719ef84 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -1920,6 +1920,14 @@ table! { } } +table! { + inv_internal_dns (inv_collection_id, zone_id) { + inv_collection_id -> Uuid, + zone_id -> Uuid, + generation -> Int8, + } +} + /* blueprints */ table! { diff --git a/nexus/inventory/Cargo.toml b/nexus/inventory/Cargo.toml index 3d2a220ca5..bed3f8ef61 100644 --- a/nexus/inventory/Cargo.toml +++ b/nexus/inventory/Cargo.toml @@ -13,6 +13,7 @@ base64.workspace = true camino.workspace = true chrono.workspace = true clickhouse-admin-keeper-client.workspace = true +dns-service-client.workspace = true clickhouse-admin-server-client.workspace = true clickhouse-admin-types.workspace = true futures.workspace = true diff --git a/nexus/inventory/src/builder.rs b/nexus/inventory/src/builder.rs index 1eb08c7c27..72ed53ee86 100644 --- a/nexus/inventory/src/builder.rs +++ b/nexus/inventory/src/builder.rs @@ -26,6 +26,7 @@ use nexus_types::inventory::CabooseWhich; use nexus_types::inventory::CockroachStatus; use nexus_types::inventory::Collection; use nexus_types::inventory::HostPhase1FlashHash; +use nexus_types::inventory::InternalDnsGenerationStatus; use nexus_types::inventory::RotPage; use nexus_types::inventory::RotPageFound; use nexus_types::inventory::RotPageWhich; @@ -127,6 +128,7 @@ pub struct CollectionBuilder { BTreeSet, cockroach_status: BTreeMap, ntp_timesync: IdOrdMap, + internal_dns_generation_status: IdOrdMap, // CollectionBuilderRng is taken by value, rather than passed in as a // mutable ref, to encourage a tree-like structure where each RNG is // generally independent. @@ -159,6 +161,7 @@ impl CollectionBuilder { clickhouse_keeper_cluster_membership: BTreeSet::new(), cockroach_status: BTreeMap::new(), ntp_timesync: IdOrdMap::new(), + internal_dns_generation_status: IdOrdMap::new(), rng: CollectionBuilderRng::from_entropy(), } } @@ -184,6 +187,7 @@ impl CollectionBuilder { .clickhouse_keeper_cluster_membership, cockroach_status: self.cockroach_status, ntp_timesync: self.ntp_timesync, + internal_dns_generation_status: self.internal_dns_generation_status, } } @@ -652,6 +656,19 @@ impl CollectionBuilder { metrics.get_metric_unsigned(CockroachMetric::LivenessLiveNodes); self.cockroach_status.insert(node_id, status); } + + /// Record information about internal DNS generation status + pub fn found_internal_dns_generation_status( + &mut self, + status: InternalDnsGenerationStatus, + ) -> Result<(), anyhow::Error> { + self.internal_dns_generation_status + .insert_unique(status) + .map_err(|err| err.into_owned()) + .context( + "Internal DNS server reported generation status multiple times", + ) + } } /// Returns the current time, truncated to the previous microsecond. @@ -712,6 +729,7 @@ mod test { assert!(collection.clickhouse_keeper_cluster_membership.is_empty()); assert!(collection.cockroach_status.is_empty()); assert!(collection.ntp_timesync.is_empty()); + assert!(collection.internal_dns_generation_status.is_empty()); } // Simple test of a single, fairly typical collection that contains just diff --git a/nexus/inventory/src/collector.rs b/nexus/inventory/src/collector.rs index a9e41c2ffd..3b058b2916 100644 --- a/nexus/inventory/src/collector.rs +++ b/nexus/inventory/src/collector.rs @@ -14,6 +14,7 @@ use gateway_client::types::SpType; use gateway_messages::SpComponent; use nexus_types::inventory::CabooseWhich; use nexus_types::inventory::Collection; +use nexus_types::inventory::InternalDnsGenerationStatus; use nexus_types::inventory::RotPage; use nexus_types::inventory::RotPageWhich; use omicron_cockroach_metrics::CockroachClusterAdminClient; @@ -36,17 +37,20 @@ pub struct Collector<'a> { keeper_admin_clients: Vec, cockroach_admin_client: &'a CockroachClusterAdminClient, ntp_admin_clients: Vec<(OmicronZoneUuid, ntp_admin_client::Client)>, + dns_service_clients: Vec<(OmicronZoneUuid, dns_service_client::Client)>, sled_agent_lister: &'a (dyn SledAgentEnumerator + Send + Sync), in_progress: CollectionBuilder, } impl<'a> Collector<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( creator: &str, mgs_clients: Vec, keeper_admin_clients: Vec, cockroach_admin_client: &'a CockroachClusterAdminClient, ntp_admin_clients: Vec<(OmicronZoneUuid, ntp_admin_client::Client)>, + dns_service_clients: Vec<(OmicronZoneUuid, dns_service_client::Client)>, sled_agent_lister: &'a (dyn SledAgentEnumerator + Send + Sync), log: slog::Logger, ) -> Self { @@ -56,6 +60,7 @@ impl<'a> Collector<'a> { keeper_admin_clients, cockroach_admin_client, ntp_admin_clients, + dns_service_clients, sled_agent_lister, in_progress: CollectionBuilder::new(creator), } @@ -87,8 +92,7 @@ impl<'a> Collector<'a> { // TODO(https://github.com/oxidecomputer/omicron/issues/8546): Collect // NTP timesync statuses - // TODO(https://github.com/oxidecomputer/omicron/issues/8544): Collect - // DNS generations + self.collect_all_dns_generations().await; debug!(&self.log, "finished collection"); @@ -553,6 +557,57 @@ impl<'a> Collector<'a> { self.in_progress.found_cockroach_metrics(node_id, metrics); } } + + /// Collect DNS generation status from all internal DNS servers + async fn collect_all_dns_generations(&mut self) { + debug!(&self.log, "begin collection from internal DNS servers"); + + for (zone_id, client) in &self.dns_service_clients { + if let Err(err) = Self::collect_one_dns_generation( + &self.log, + *zone_id, + client, + &mut self.in_progress, + ) + .await + { + error!( + &self.log, + "DNS generation collection error"; + "zone_id" => ?zone_id, + "error" => ?err, + ); + } + } + + debug!(&self.log, "finished collection from internal DNS servers"); + } + + async fn collect_one_dns_generation( + log: &slog::Logger, + zone_id: OmicronZoneUuid, + client: &dns_service_client::Client, + in_progress: &mut CollectionBuilder, + ) -> Result<(), anyhow::Error> { + debug!(&log, "begin collection from DNS server"; + "zone_id" => ?zone_id + ); + + let config = client.dns_config_get().await.with_context(|| { + format!("DNS server {:?}: dns_config_get", client.baseurl()) + })?; + + let generation_status = InternalDnsGenerationStatus { + zone_id, + generation: config.into_inner().generation, + }; + + in_progress.found_internal_dns_generation_status(generation_status)?; + + debug!(&log, "finished collection from DNS server"; "zone_id" => ?zone_id); + + Ok(()) + } } #[cfg(test)] @@ -952,6 +1007,7 @@ mod test { // there would be in providing them at this juncture. let keeper_clients = Vec::new(); let ntp_clients = Vec::new(); + let dns_clients = Vec::new(); // Configure the mock server as a backend for the CockroachDB client let timeout = Duration::from_secs(15); let crdb_cluster = @@ -964,6 +1020,7 @@ mod test { keeper_clients, &crdb_cluster, ntp_clients, + dns_clients, &sled_enum, log.clone(), ); @@ -1037,6 +1094,7 @@ mod test { // there would be in providing them at this juncture. let keeper_clients = Vec::new(); let ntp_clients = Vec::new(); + let dns_clients = Vec::new(); let timeout = Duration::from_secs(15); let crdb_cluster = CockroachClusterAdminClient::new(log.clone(), timeout); @@ -1048,6 +1106,7 @@ mod test { keeper_clients, &crdb_cluster, ntp_clients, + dns_clients, &sled_enum, log.clone(), ); @@ -1092,6 +1151,7 @@ mod test { // there would be in providing them at this juncture. let keeper_clients = Vec::new(); let ntp_clients = Vec::new(); + let dns_clients = Vec::new(); let timeout = Duration::from_secs(15); let crdb_cluster = CockroachClusterAdminClient::new(log.clone(), timeout); @@ -1103,6 +1163,7 @@ mod test { keeper_clients, &crdb_cluster, ntp_clients, + dns_clients, &sled_enum, log.clone(), ); @@ -1152,6 +1213,7 @@ mod test { // there would be in providing them at this juncture. let keeper_clients = Vec::new(); let ntp_clients = Vec::new(); + let dns_clients = Vec::new(); let timeout = Duration::from_secs(15); let crdb_cluster = CockroachClusterAdminClient::new(log.clone(), timeout); @@ -1163,6 +1225,7 @@ mod test { keeper_clients, &crdb_cluster, ntp_clients, + dns_clients, &sled_enum, log.clone(), ); diff --git a/nexus/inventory/src/examples.rs b/nexus/inventory/src/examples.rs index d5bd8a5b2b..fa0bf82d30 100644 --- a/nexus/inventory/src/examples.rs +++ b/nexus/inventory/src/examples.rs @@ -33,6 +33,7 @@ use nexus_sled_agent_shared::inventory::SledRole; use nexus_sled_agent_shared::inventory::ZoneImageResolverInventory; use nexus_types::inventory::BaseboardId; use nexus_types::inventory::CabooseWhich; +use nexus_types::inventory::InternalDnsGenerationStatus; use nexus_types::inventory::RotPage; use nexus_types::inventory::RotPageWhich; use nexus_types::inventory::ZpoolName; @@ -659,6 +660,13 @@ pub fn representative() -> Representative { }) .unwrap(); + builder + .found_internal_dns_generation_status(InternalDnsGenerationStatus { + zone_id: omicron_uuid_kinds::OmicronZoneUuid::new_v4(), + generation: 1.into(), + }) + .unwrap(); + Representative { builder, sleds: [sled1_bb, sled2_bb, sled3_bb, sled4_bb], diff --git a/nexus/src/app/background/tasks/inventory_collection.rs b/nexus/src/app/background/tasks/inventory_collection.rs index 6f21166f68..0f2a754d48 100644 --- a/nexus/src/app/background/tasks/inventory_collection.rs +++ b/nexus/src/app/background/tasks/inventory_collection.rs @@ -241,6 +241,27 @@ async fn inventory_activate( cockroach_admin_client.update_backends(admin_addresses.as_slice()).await; + // Find internal DNS servers if there are any. + let internal_dns_zone_addrs = match resolver + .lookup_all_socket_and_zone_v6(ServiceName::InternalDns) + .await + { + Ok(addrs) => addrs, + Err(err) if err.is_not_found() => vec![], + Err(err) => { + return Err(err).context("looking up internal DNS addresses"); + } + }; + + let dns_service_clients = internal_dns_zone_addrs + .into_iter() + .map(|(zone_uuid, addr)| { + let url = format!("http://{}", addr); + let log = opctx.log.new(o!("dns_service_url" => url.clone())); + (zone_uuid, dns_service_client::Client::new(&url, log)) + }) + .collect(); + // Create an enumerator to find sled agents. let sled_enum = DbSledAgentEnumerator { opctx, datastore }; @@ -251,6 +272,7 @@ async fn inventory_activate( keeper_admin_clients, cockroach_admin_client, ntp_admin_clients, + dns_service_clients, &sled_enum, opctx.log.clone(), ); diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index a7872b869d..7acfe51cd6 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -171,6 +171,8 @@ pub struct Collection { /// The status of time synchronization pub ntp_timesync: IdOrdMap, + /// The generation status of internal DNS servers + pub internal_dns_generation_status: IdOrdMap, } impl Collection { @@ -679,3 +681,21 @@ impl IdOrdItem for TimeSync { } id_upcast!(); } + +#[derive( + Clone, Debug, Diffable, Serialize, Deserialize, JsonSchema, PartialEq, Eq, +)] +pub struct InternalDnsGenerationStatus { + /// Zone ID of the internal DNS server contacted + pub zone_id: OmicronZoneUuid, + /// Generation number of the DNS configuration + pub generation: omicron_common::api::external::Generation, +} + +impl IdOrdItem for InternalDnsGenerationStatus { + type Key<'a> = OmicronZoneUuid; + fn key(&self) -> Self::Key<'_> { + self.zone_id + } + id_upcast!(); +} diff --git a/nexus/types/src/inventory/display.rs b/nexus/types/src/inventory/display.rs index 29d44968c4..91d16b3e53 100644 --- a/nexus/types/src/inventory/display.rs +++ b/nexus/types/src/inventory/display.rs @@ -734,6 +734,18 @@ fn display_sleds( } } } + + let internal_dns_generation_status = + zones.keys().find_map(|zone_id| { + collection.internal_dns_generation_status.get(zone_id) + }); + if let Some(st) = internal_dns_generation_status { + writeln!( + indent2, + "Internal DNS generation: {}", + st.generation + )? + } } } diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index e4a08c623a..97f463d9d4 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4298,6 +4298,13 @@ CREATE TABLE IF NOT EXISTS omicron.public.inv_ntp_timesync ( PRIMARY KEY (inv_collection_id, zone_id) ); +CREATE TABLE IF NOT EXISTS omicron.public.inv_internal_dns ( + inv_collection_id UUID NOT NULL, + zone_id UUID NOT NULL, + generation INT8 NOT NULL, + PRIMARY KEY (inv_collection_id, zone_id) +); + /* * Various runtime configuration switches for reconfigurator * @@ -6295,7 +6302,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '170.0.0', NULL) + (TRUE, NOW(), NOW(), '171.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/inv-internal-dns/up01.sql b/schema/crdb/inv-internal-dns/up01.sql new file mode 100644 index 0000000000..6ab7d14c33 --- /dev/null +++ b/schema/crdb/inv-internal-dns/up01.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS omicron.public.inv_internal_dns ( + inv_collection_id UUID NOT NULL, + zone_id UUID NOT NULL, + generation INT8 NOT NULL, + PRIMARY KEY (inv_collection_id, zone_id) +); +