Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1ede6a9
add expunged_and_unreferenced
jgallagher Dec 11, 2025
73da04c
input/builder helper methods
jgallagher Jan 13, 2026
4a4f0a0
initial collecting "expunged and unreferenced"
jgallagher Jan 14, 2026
97d3b23
flesh out oximeter reference check
jgallagher Jan 16, 2026
c2eb0e3
flesh out is_nexus_referenced()
jgallagher Jan 27, 2026
5cad9b2
docs on static_check_all_reasons_handled()
jgallagher Jan 27, 2026
1315325
cleanup and comments
jgallagher Jan 27, 2026
c80d7e6
cargo fmt
jgallagher Jan 27, 2026
9cb4c51
Merge branch 'main' into john/planning-input-pruneable
jgallagher Jan 30, 2026
70a506f
comments
jgallagher Jan 30, 2026
b5bf895
WIP: initial runtime reason coverage checking
jgallagher Jan 30, 2026
fe3d0bf
wip
jgallagher Jan 30, 2026
b0ca4f4
new caching helpers
jgallagher Jan 30, 2026
f0b31ba
reorg into PruneableZones
jgallagher Jan 30, 2026
6c51581
initial test (failing)
jgallagher Jan 30, 2026
1e3dd23
cleanup and clarify lazy sets
jgallagher Jan 30, 2026
ad69b2c
s/referenced/pruneable/ (and invert function return values)
jgallagher Jan 30, 2026
17772e6
add boundary NTP support to ExampleSystemBuilder
jgallagher Jan 30, 2026
8d0a365
flesh out test_pruneable_zones_reason_checker()
jgallagher Jan 30, 2026
b2dc8f2
add test_oximeter_pruneable_reasons()
jgallagher Jan 30, 2026
864dade
add test_boundary_ntp_pruneable_reasons()
jgallagher Jan 31, 2026
c51cd3f
initial remaining tests
jgallagher Jan 31, 2026
51298b4
flesh out Nexus test
jgallagher Feb 2, 2026
06febb9
minor cleanup (comments, enum variant name)
jgallagher Feb 2, 2026
ff208bc
module rename
jgallagher Feb 2, 2026
dcdc772
expunged_and_unreferenced -> pruneable
jgallagher Feb 2, 2026
28329f1
Merge remote-tracking branch 'origin/main' into john/planning-input-p…
jgallagher Feb 2, 2026
a5697a5
Merge branch 'main' into john/planning-input-pruneable
jgallagher Feb 4, 2026
9534f8a
PR feedback
jgallagher Feb 4, 2026
40916cd
more robust construction of support_bundle_states_needing_cleanup
jgallagher Feb 4, 2026
File filter

Filter by extension

Filter by extension


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

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

2 changes: 1 addition & 1 deletion nexus/db-model/src/support_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
impl_enum_type!(
SupportBundleStateEnum:

#[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, Serialize, Deserialize, PartialEq)]
#[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, Serialize, Deserialize, PartialEq, strum::EnumIter)]
pub enum SupportBundleState;

// Enum values
Expand Down
47 changes: 30 additions & 17 deletions nexus/db-queries/src/db/datastore/saga.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ use nexus_auth::context::OpContext;
use nexus_db_errors::ErrorHandler;
use nexus_db_errors::public_error_from_diesel;
use nexus_db_model::SagaState;
use omicron_common::api::external::DataPageParams;
use omicron_common::api::external::Error;
use omicron_common::api::external::LookupType;
use omicron_common::api::external::ResourceType;
use std::ops::Add;
use uuid::Uuid;

impl DataStore {
pub async fn saga_create(
Expand Down Expand Up @@ -130,6 +132,26 @@ impl DataStore {
}
}

/// Returns a single page of unfinished sagas assigned to SEC `sec_id`.
pub async fn saga_list_recovery_candidates(
&self,
opctx: &OpContext,
sec_id: db::saga_types::SecId,
pagparams: &DataPageParams<'_, Uuid>,
) -> Result<Vec<db::saga_types::Saga>, Error> {
use nexus_db_schema::schema::saga::dsl;
let conn = self.pool_connection_authorized(opctx).await?;
paginated(dsl::saga, dsl::id, pagparams)
.filter(
dsl::saga_state.eq_any(SagaState::RECOVERY_CANDIDATE_STATES),
)
.filter(dsl::current_sec.eq(sec_id))
.select(db::saga_types::Saga::as_select())
.load_async(&*conn)
.await
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
}

/// Returns a list of unfinished sagas assigned to SEC `sec_id`, making as
/// many queries as needed (in batches) to get them all
pub async fn saga_list_recovery_candidates_batched(
Expand All @@ -142,25 +164,16 @@ impl DataStore {
SQL_BATCH_SIZE,
dropshot::PaginationOrder::Ascending,
);
let conn = self.pool_connection_authorized(opctx).await?;
while let Some(p) = paginator.next() {
use nexus_db_schema::schema::saga::dsl;

let mut batch =
paginated(dsl::saga, dsl::id, &p.current_pagparams())
.filter(
dsl::saga_state
.eq_any(SagaState::RECOVERY_CANDIDATE_STATES),
)
.filter(dsl::current_sec.eq(sec_id))
.select(db::saga_types::Saga::as_select())
.load_async(&*conn)
.await
.map_err(|e| {
public_error_from_diesel(e, ErrorHandler::Server)
})?;
let mut batch = self
.saga_list_recovery_candidates(
opctx,
sec_id,
&p.current_pagparams(),
)
.await?;

paginator = p.found_batch(&batch, &|row| row.id);
paginator = p.found_batch(&batch, &|row| row.id.0.0);
sagas.append(&mut batch);
}
Ok(sagas)
Expand Down
58 changes: 46 additions & 12 deletions nexus/reconfigurator/planning/src/example.rs
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry for racing with the change here. Let me know if you want help merging it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem; it was not a bad merge at all.

Original file line number Diff line number Diff line change
Expand Up @@ -387,13 +387,29 @@ impl ExampleSystemBuilder {

/// Set the number of boundary NTP zones in the example system.
///
/// The default value is 0. A value anywhere between 0 and 30, inclusive, is
/// permitted. (The limit of 30 is primarily to simplify the
/// implementation.)
///
/// Each NTP server is assigned an external SNAT address in the 198.51.100.x
/// range.
///
/// This implicitly controls the number of internal NTP zones in the system.
/// Each sled with no boundary NTP zone gets an internal NTP zone.
///
/// If [`Self::create_zones`] is set to `false`, this is ignored.
pub fn boundary_ntp_count(mut self, boundary_ntp_count: usize) -> Self {
pub fn set_boundary_ntp_count(
mut self,
boundary_ntp_count: usize,
) -> anyhow::Result<Self> {
if boundary_ntp_count > 30 {
anyhow::bail!(
"boundary_ntp_count {} is greater than 30",
boundary_ntp_count,
);
}
self.boundary_ntp_count = ZoneCount(boundary_ntp_count);
self
Ok(self)
}

/// Set the Clickhouse policy for the example system.
Expand Down Expand Up @@ -625,19 +641,34 @@ impl ExampleSystemBuilder {
.unwrap(),
)
.unwrap();
for i in 0..self.external_dns_count.0 {
let lo = (i + 1)
.try_into()
.expect("external_dns_count is always <= 30");
for i in 1..=self.external_dns_count.0 {
let ip = format!("198.51.100.{i}");
builder
.add_external_dns_ip(IpAddr::V4(Ipv4Addr::new(
198, 51, 100, lo,
)))
.add_external_dns_ip(ip.parse().unwrap())
.expect("test IPs are valid service IPs");
}
system.set_external_ip_policy(builder.build());
}

// Also add a 30-ip range for boundary NTP. It's very likely we'll
// actually allocate out of the leftover external DNS range, but if
// someone actually asked for 30 external DNS zones we'll shift up into
// this range.
if self.boundary_ntp_count.0 > 0 {
let mut builder =
system.external_ip_policy().clone().into_builder();
builder
.push_service_pool_ipv4_range(
Ipv4Range::new(
"198.51.100.31".parse::<Ipv4Addr>().unwrap(),
"198.51.100.60".parse::<Ipv4Addr>().unwrap(),
)
.unwrap(),
)
.unwrap();
system.set_external_ip_policy(builder.build());
}

let mut input_builder = system
.to_planning_input_builder(Arc::new(
// Start with an empty blueprint.
Expand Down Expand Up @@ -1302,7 +1333,8 @@ mod tests {
.unwrap()
.oximeter_count(4)
.cockroachdb_count(3)
.boundary_ntp_count(2)
.set_boundary_ntp_count(2)
.unwrap()
.clickhouse_policy(ClickhousePolicy {
version: 0,
mode: ClickhouseMode::Both {
Expand Down Expand Up @@ -1525,7 +1557,8 @@ mod tests {
.nsleds(5)
.oximeter_count(OXIMETER_REDUNDANCY)
.cockroachdb_count(COCKROACHDB_REDUNDANCY)
.boundary_ntp_count(2)
.set_boundary_ntp_count(2)
.unwrap()
.external_dns_count(1)
.unwrap()
.clickhouse_policy(ClickhousePolicy {
Expand Down Expand Up @@ -1572,7 +1605,8 @@ mod tests {
.nsleds(32)
.nexus_count(6)
.cockroachdb_count(5)
.boundary_ntp_count(2)
.set_boundary_ntp_count(2)
.unwrap()
.oximeter_count(1)
.external_dns_count(5)
.expect("expected to be able to set external_dns_count")
Expand Down
13 changes: 13 additions & 0 deletions nexus/reconfigurator/preparation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,18 @@ sled-agent-types.workspace = true
sled-hardware-types.workspace = true
slog.workspace = true
slog-error-chain.workspace = true
strum.workspace = true

omicron-workspace-hack.workspace = true

[dev-dependencies]
chrono.workspace = true
clickhouse-admin-types.workspace = true
nexus-auth.workspace = true
nexus-reconfigurator-planning.workspace = true
nexus-test-utils.workspace = true
omicron-test-utils.workspace = true
serde_json.workspace = true
steno.workspace = true
tokio.workspace = true
uuid.workspace = true
25 changes: 25 additions & 0 deletions nexus/reconfigurator/preparation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ use std::collections::BTreeSet;
use std::net::IpAddr;
use std::sync::Arc;

mod pruneable_zones;

use pruneable_zones::PruneableZones;

/// Given various pieces of database state that go into the blueprint planning
/// process, produce a `PlanningInput` object encapsulating what the planner
/// needs to generate a blueprint
Expand Down Expand Up @@ -92,6 +96,7 @@ pub struct PlanningInputFromDb<'a> {
pub planner_config: PlannerConfig,
pub active_nexus_zones: BTreeSet<OmicronZoneUuid>,
pub not_yet_nexus_zones: BTreeSet<OmicronZoneUuid>,
pub pruneable_zones: BTreeSet<OmicronZoneUuid>,
pub log: &'a Logger,
}

Expand Down Expand Up @@ -269,6 +274,15 @@ impl PlanningInputFromDb<'_> {
active_nexus_zones.into_iter().map(|n| n.nexus_id()).collect();
let not_yet_nexus_zones =
not_yet_nexus_zones.into_iter().map(|n| n.nexus_id()).collect();
let pruneable_zones = PruneableZones::assemble(
opctx,
datastore,
&parent_blueprint,
&external_ip_rows,
&service_nic_rows,
)
.await?
.into_pruneable_zones();

let planning_input = PlanningInputFromDb {
parent_blueprint,
Expand Down Expand Up @@ -297,6 +311,7 @@ impl PlanningInputFromDb<'_> {
planner_config,
active_nexus_zones,
not_yet_nexus_zones,
pruneable_zones,
}
.build()
.internal_context("assembling planning_input")?;
Expand Down Expand Up @@ -456,6 +471,16 @@ impl PlanningInputFromDb<'_> {
})?;
}

for &zone_id in &self.pruneable_zones {
builder.insert_pruneable_zone(zone_id).map_err(|e| {
Error::internal_error(&format!(
"unexpectedly failed to pruneable zone ID {zone_id} \
to planning input: {}",
InlineErrorChain::new(&e),
))
})?;
}

Ok(builder.build())
}
}
Expand Down
Loading
Loading