From cf0cd5c60dbba2ae2c3afd035dff19e8ac9e1a82 Mon Sep 17 00:00:00 2001 From: Sara Elzayat Date: Thu, 4 Sep 2025 20:11:52 +0300 Subject: [PATCH 1/2] nexus-db-queries: track blueprint table coverage cumulatively --- .../db-queries/src/db/datastore/deployment.rs | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 55674144a5..b0ee61b952 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -3213,8 +3213,8 @@ mod tests { [blueprint1.id] ); - // Ensure every bp_* table received at least one row for this blueprint (issue #8455). - ensure_blueprint_fully_populated(&datastore, blueprint1.id).await; + // Start tracking cumulative blueprint table coverage. + let mut cumulative_counts = BlueprintTableCounts::new(&datastore, blueprint1.id).await; // Check the number of blueprint elements against our collection. assert_eq!( @@ -3580,6 +3580,9 @@ mod tests { assert_eq!(blueprint2, blueprint_read); assert_eq!(blueprint2.internal_dns_version, new_internal_dns_version); assert_eq!(blueprint2.external_dns_version, new_external_dns_version); + + let blueprint2_counts = BlueprintTableCounts::new(&datastore, blueprint2.id).await; + cumulative_counts.add(&blueprint2_counts); { let mut expected_ids = [blueprint1.id, blueprint2.id]; expected_ids.sort(); @@ -3671,6 +3674,9 @@ mod tests { .await .expect("failed to read collection back") ); + + let blueprint3_counts = BlueprintTableCounts::new(&datastore, blueprint3.id).await; + cumulative_counts.add(&blueprint3_counts); let bp3_target = BlueprintTarget { target_id: blueprint3.id, enabled: true, @@ -3725,6 +3731,9 @@ mod tests { .await .expect("failed to read collection back") ); + + let blueprint4_counts = BlueprintTableCounts::new(&datastore, blueprint4.id).await; + cumulative_counts.add(&blueprint4_counts); let bp4_target = BlueprintTarget { target_id: blueprint4.id, enabled: true, @@ -3783,6 +3792,9 @@ mod tests { .await .expect("failed to read collection back") ); + + let blueprint5_counts = BlueprintTableCounts::new(&datastore, blueprint5.id).await; + cumulative_counts.add(&blueprint5_counts); let bp5_target = BlueprintTarget { target_id: blueprint5.id, enabled: true, @@ -3811,6 +3823,9 @@ mod tests { .blueprint_insert(&opctx, &blueprint6) .await .expect("failed to insert blueprint"); + + let blueprint6_counts = BlueprintTableCounts::new(&datastore, blueprint6.id).await; + cumulative_counts.add(&blueprint6_counts); let bp6_target = BlueprintTarget { target_id: blueprint6.id, enabled: true, @@ -3823,6 +3838,8 @@ mod tests { datastore.blueprint_delete(&opctx, &authz_blueprint5).await.unwrap(); ensure_blueprint_fully_deleted(&datastore, blueprint5.id).await; + ensure_blueprint_fully_populated(&datastore, &cumulative_counts).await; + // Clean up. db.terminate().await; logctx.cleanup_successful(); @@ -4589,6 +4606,13 @@ mod tests { self.counts.values().all(|&count| count == 0) } + /// Add counts from another BlueprintTableCounts to this one. + fn add(&mut self, other: &BlueprintTableCounts) { + for (table, &count) in &other.counts { + *self.counts.entry(table.clone()).or_insert(0) += count; + } + } + /// Returns a list of table names that are empty. fn empty_tables(&self) -> Vec { self.counts @@ -4661,29 +4685,22 @@ mod tests { } } - // Verify that every blueprint-related table contains ≥1 row for `blueprint_id`. + // Verify that every blueprint-related table contains ≥1 row across test blueprints. // Complements `ensure_blueprint_fully_deleted`. async fn ensure_blueprint_fully_populated( - datastore: &DataStore, - blueprint_id: BlueprintUuid, + _datastore: &DataStore, + cumulative_counts: &BlueprintTableCounts, ) { - let counts = BlueprintTableCounts::new(datastore, blueprint_id).await; - - // Exception tables that may be empty in the representative blueprint: - // - MGS update tables: only populated when blueprint includes firmware updates + // Exception tables that may be empty in the test blueprints: // - ClickHouse tables: only populated when blueprint includes ClickHouse configuration let exception_tables = [ - "bp_pending_mgs_update_sp", - "bp_pending_mgs_update_rot", - "bp_pending_mgs_update_rot_bootloader", - "bp_pending_mgs_update_host_phase_1", "bp_clickhouse_cluster_config", - "bp_clickhouse_keeper_zone_id_to_node_id", + "bp_clickhouse_keeper_zone_id_to_node_id", "bp_clickhouse_server_zone_id_to_node_id", ]; // Check that all non-exception tables have at least one row - let empty_tables = counts.empty_tables(); + let empty_tables = cumulative_counts.empty_tables(); let problematic_tables: Vec<_> = empty_tables .into_iter() .filter(|table| !exception_tables.contains(&table.as_str())) @@ -4691,7 +4708,7 @@ mod tests { if !problematic_tables.is_empty() { panic!( - "Expected tables to be populated for blueprint {blueprint_id}: {:?}\n\n\ + "Expected tables to be populated across test blueprints: {:?}\n\n\ If every blueprint should be expected to have a value in this table, then this is a bug. \ Otherwise, you may need to add a table to the exception list in `ensure_blueprint_fully_populated()`. \ If you do this, please ensure that you add a test to `test_representative_blueprint()` that creates a \ From 5afe55fadb407dd4673328212cda9c4775e5a473 Mon Sep 17 00:00:00 2001 From: Sara Elzayat Date: Thu, 4 Sep 2025 20:37:08 +0300 Subject: [PATCH 2/2] Fix formatting issues --- .../db-queries/src/db/datastore/deployment.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index b0ee61b952..06e74bf7e2 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -3214,7 +3214,8 @@ mod tests { ); // Start tracking cumulative blueprint table coverage. - let mut cumulative_counts = BlueprintTableCounts::new(&datastore, blueprint1.id).await; + let mut cumulative_counts = + BlueprintTableCounts::new(&datastore, blueprint1.id).await; // Check the number of blueprint elements against our collection. assert_eq!( @@ -3581,7 +3582,8 @@ mod tests { assert_eq!(blueprint2.internal_dns_version, new_internal_dns_version); assert_eq!(blueprint2.external_dns_version, new_external_dns_version); - let blueprint2_counts = BlueprintTableCounts::new(&datastore, blueprint2.id).await; + let blueprint2_counts = + BlueprintTableCounts::new(&datastore, blueprint2.id).await; cumulative_counts.add(&blueprint2_counts); { let mut expected_ids = [blueprint1.id, blueprint2.id]; @@ -3675,7 +3677,8 @@ mod tests { .expect("failed to read collection back") ); - let blueprint3_counts = BlueprintTableCounts::new(&datastore, blueprint3.id).await; + let blueprint3_counts = + BlueprintTableCounts::new(&datastore, blueprint3.id).await; cumulative_counts.add(&blueprint3_counts); let bp3_target = BlueprintTarget { target_id: blueprint3.id, @@ -3732,7 +3735,8 @@ mod tests { .expect("failed to read collection back") ); - let blueprint4_counts = BlueprintTableCounts::new(&datastore, blueprint4.id).await; + let blueprint4_counts = + BlueprintTableCounts::new(&datastore, blueprint4.id).await; cumulative_counts.add(&blueprint4_counts); let bp4_target = BlueprintTarget { target_id: blueprint4.id, @@ -3793,7 +3797,8 @@ mod tests { .expect("failed to read collection back") ); - let blueprint5_counts = BlueprintTableCounts::new(&datastore, blueprint5.id).await; + let blueprint5_counts = + BlueprintTableCounts::new(&datastore, blueprint5.id).await; cumulative_counts.add(&blueprint5_counts); let bp5_target = BlueprintTarget { target_id: blueprint5.id, @@ -3824,7 +3829,8 @@ mod tests { .await .expect("failed to insert blueprint"); - let blueprint6_counts = BlueprintTableCounts::new(&datastore, blueprint6.id).await; + let blueprint6_counts = + BlueprintTableCounts::new(&datastore, blueprint6.id).await; cumulative_counts.add(&blueprint6_counts); let bp6_target = BlueprintTarget { target_id: blueprint6.id, @@ -4695,7 +4701,7 @@ mod tests { // - ClickHouse tables: only populated when blueprint includes ClickHouse configuration let exception_tables = [ "bp_clickhouse_cluster_config", - "bp_clickhouse_keeper_zone_id_to_node_id", + "bp_clickhouse_keeper_zone_id_to_node_id", "bp_clickhouse_server_zone_id_to_node_id", ];