Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle region snapshot replacement volume deletes #7046

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
92da0a9
Handle region snapshot replacement volume deletes
jmpesp Oct 9, 2024
bee3cca
slow CI machines may not have started the sagas after running backgro…
jmpesp Nov 14, 2024
309742d
THIS WAS A TREMENDOUS OVERSIGHT
jmpesp Nov 15, 2024
c9455ec
emit VolumeReplaceResult as saga node data
jmpesp Nov 15, 2024
4630e03
fix a bunch of tests that were locking non-existent volumes
jmpesp Nov 15, 2024
4c729e6
fix a bunch more tests that were locking non-existent volumes
jmpesp Nov 15, 2024
1c50f37
fix another test that was locking non-existent volumes
jmpesp Nov 15, 2024
63cb56e
the TREMENDOUS oversight continues
jmpesp Nov 18, 2024
0ce2029
Merge branch 'main' into region_snapshot_replacement_account_for_dele…
jmpesp Nov 19, 2024
79f8ad9
fix after merge
jmpesp Nov 19, 2024
8b0a677
account for relocking a volume
jmpesp Nov 22, 2024
b2a740f
add comment about validating volume exists
jmpesp Nov 22, 2024
18efdd3
disambiguate between soft and hard delete
jmpesp Nov 22, 2024
640e678
cover the case that the region snapshot finish saga kicks off in the …
jmpesp Nov 22, 2024
1a60b15
wait for state to transition to complete
jmpesp Nov 22, 2024
9fc46ab
just an optimization
jmpesp Nov 25, 2024
2cd7881
add missing unwind edge
jmpesp Nov 25, 2024
a78cabf
remove likely
jmpesp Nov 25, 2024
c869cad
handle if region snapshot is hard deleted while replacement request i…
jmpesp Nov 27, 2024
231764d
Merge branch 'main' into region_snapshot_replacement_account_for_dele…
jmpesp Dec 9, 2024
7b93e2e
fmt
jmpesp Dec 9, 2024
cb1c2fe
fix compile time errors from merge
jmpesp Dec 9, 2024
fdf800a
conflicts are not errors!
jmpesp Dec 10, 2024
abf522e
add dummy region snapshot
jmpesp Dec 10, 2024
715c9f7
fmt
jmpesp Dec 10, 2024
e4d0844
comment for volume_repair_insert_in_txn
jmpesp Dec 17, 2024
518a86c
cargo fmt missed this one!
jmpesp Dec 17, 2024
ec9bb9a
when completing a region snapshot replacement from the start background
jmpesp Dec 18, 2024
34db989
address the unfinished comment
jmpesp Dec 18, 2024
4931f58
rework wait_for_request_state to be clearer
jmpesp Dec 18, 2024
2a37e9c
mroe -> more!
jmpesp Dec 19, 2024
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
41 changes: 25 additions & 16 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2563,39 +2563,48 @@ async fn cmd_db_region_used_by(
async fn cmd_db_region_find_deleted(
datastore: &DataStore,
) -> Result<(), anyhow::Error> {
let datasets_regions_volumes =
let freed_crucible_resources =
datastore.find_deleted_volume_regions().await?;

#[derive(Tabled)]
struct Row {
struct RegionRow {
dataset_id: DatasetUuid,
region_id: Uuid,
volume_id: String,
}

let rows: Vec<Row> = datasets_regions_volumes
.into_iter()
#[derive(Tabled)]
struct VolumeRow {
volume_id: Uuid,
}

let region_rows: Vec<RegionRow> = freed_crucible_resources
.datasets_and_regions
.iter()
.map(|row| {
let (dataset, region, volume) = row;
let (dataset, region) = row;

Row {
dataset_id: dataset.id(),
region_id: region.id(),
volume_id: if let Some(volume) = volume {
volume.id().to_string()
} else {
String::from("")
},
}
RegionRow { dataset_id: dataset.id(), region_id: region.id() }
})
.collect();

let table = tabled::Table::new(rows)
let table = tabled::Table::new(region_rows)
.with(tabled::settings::Style::psql())
.to_string();

println!("{}", table);

let volume_rows: Vec<VolumeRow> = freed_crucible_resources
.volumes
.iter()
.map(|volume_id| VolumeRow { volume_id: *volume_id })
.collect();

let volume_table = tabled::Table::new(volume_rows)
.with(tabled::settings::Style::psql())
.to_string();

println!("{}", volume_table);

Ok(())
}

Expand Down
23 changes: 20 additions & 3 deletions dev-tools/omdb/src/bin/omdb/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,14 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {
println!(" > {line}");
}

println!(
" total requests completed ok: {}",
status.requests_completed_ok.len(),
);
for line in &status.requests_completed_ok {
println!(" > {line}");
}

println!(" errors: {}", status.errors.len());
for line in &status.errors {
println!(" > {line}");
Expand Down Expand Up @@ -1720,6 +1728,14 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {
println!(" > {line}");
}

println!(
" total steps set to volume_deleted ok: {}",
status.step_set_volume_deleted_ok.len(),
);
for line in &status.step_set_volume_deleted_ok {
println!(" > {line}");
}

println!(" errors: {}", status.errors.len());
for line in &status.errors {
println!(" > {line}");
Expand Down Expand Up @@ -1831,10 +1847,11 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {

Ok(status) => {
println!(
" total records transitioned to done: {}",
status.records_set_to_done.len(),
" region snapshot replacement finish sagas started \
ok: {}",
status.finish_invoked_ok.len()
);
for line in &status.records_set_to_done {
for line in &status.finish_invoked_ok {
println!(" > {line}");
}

Expand Down
8 changes: 6 additions & 2 deletions dev-tools/omdb/tests/successes.out
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ task: "region_snapshot_replacement_finish"
currently executing: no
last completed activation: <REDACTED ITERATIONS>, triggered by a periodic timer firing
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
total records transitioned to done: 0
region snapshot replacement finish sagas started ok: 0
errors: 0

task: "region_snapshot_replacement_garbage_collection"
Expand All @@ -645,6 +645,7 @@ task: "region_snapshot_replacement_start"
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
total requests created ok: 0
total start saga invoked ok: 0
total requests completed ok: 0
errors: 0

task: "region_snapshot_replacement_step"
Expand All @@ -655,6 +656,7 @@ task: "region_snapshot_replacement_step"
total step records created ok: 0
total step garbage collect saga invoked ok: 0
total step saga invoked ok: 0
total steps set to volume_deleted ok: 0
errors: 0

task: "saga_recovery"
Expand Down Expand Up @@ -1070,7 +1072,7 @@ task: "region_snapshot_replacement_finish"
currently executing: no
last completed activation: <REDACTED ITERATIONS>, triggered by a periodic timer firing
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
total records transitioned to done: 0
region snapshot replacement finish sagas started ok: 0
errors: 0

task: "region_snapshot_replacement_garbage_collection"
Expand All @@ -1088,6 +1090,7 @@ task: "region_snapshot_replacement_start"
started at <REDACTED_TIMESTAMP> (<REDACTED DURATION>s ago) and ran for <REDACTED DURATION>ms
total requests created ok: 0
total start saga invoked ok: 0
total requests completed ok: 0
errors: 0

task: "region_snapshot_replacement_step"
Expand All @@ -1098,6 +1101,7 @@ task: "region_snapshot_replacement_step"
total step records created ok: 0
total step garbage collect saga invoked ok: 0
total step saga invoked ok: 0
total steps set to volume_deleted ok: 0
errors: 0

task: "saga_recovery"
Expand Down
18 changes: 16 additions & 2 deletions nexus/db-model/src/region_snapshot_replacement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl_enum_type!(
ReplacementDone => b"replacement_done"
DeletingOldVolume => b"deleting_old_volume"
Running => b"running"
Completing => b"completing"
Complete => b"complete"
);

Expand All @@ -46,6 +47,7 @@ impl std::str::FromStr for RegionSnapshotReplacementState {
Ok(RegionSnapshotReplacementState::DeletingOldVolume)
}
"running" => Ok(RegionSnapshotReplacementState::Running),
"completing" => Ok(RegionSnapshotReplacementState::Completing),
"complete" => Ok(RegionSnapshotReplacementState::Complete),
_ => Err(format!("unrecognized value {} for enum", s)),
}
Expand Down Expand Up @@ -80,8 +82,13 @@ impl std::str::FromStr for RegionSnapshotReplacementState {
/// v ---
/// ---
/// Running |
/// | set in region snapshot replacement
/// | | finish background task
/// |
/// | |
/// v |
/// | responsibility of region snapshot
/// Completing | replacement finish saga
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: there's another version of this diagram (in the saga comments, I think) that has a back-edge from completing to running, though I think this is only if it unwinds, and I'm not sure if you're including those edges here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not a nit, that's important! added in 2cd7881

/// |
/// | |
/// v |
/// |
/// Complete ---
Expand Down Expand Up @@ -133,6 +140,12 @@ pub struct RegionSnapshotReplacement {
pub replacement_state: RegionSnapshotReplacementState,

pub operating_saga_id: Option<Uuid>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Not related to this PR, but a doc comment explaining the use of this field would be a welcome addition for slow-on-the-uptake readers like me :)


/// In order for the newly created region not to be deleted inadvertently,
/// an additional reference count bump is required. This volume should live
/// as long as this request so that all necessary replacements can be
/// completed.
pub new_region_volume_id: Option<Uuid>,
}

impl RegionSnapshotReplacement {
Expand All @@ -157,6 +170,7 @@ impl RegionSnapshotReplacement {
old_snapshot_id,
old_snapshot_volume_id: None,
new_region_id: None,
new_region_volume_id: None,
replacement_state: RegionSnapshotReplacementState::Requested,
operating_saga_id: None,
}
Expand Down
1 change: 1 addition & 0 deletions nexus/db-model/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,7 @@ table! {
new_region_id -> Nullable<Uuid>,
replacement_state -> crate::RegionSnapshotReplacementStateEnum,
operating_saga_id -> Nullable<Uuid>,
new_region_volume_id -> Nullable<Uuid>,
leftwo marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::collections::BTreeMap;
///
/// 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: SemverVersion = SemverVersion::new(114, 0, 0);
pub const SCHEMA_VERSION: SemverVersion = SemverVersion::new(115, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -29,6 +29,7 @@ static KNOWN_VERSIONS: Lazy<Vec<KnownVersion>> = Lazy::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(115, "add-completing-and-new-region-volume"),
KnownVersion::new(114, "crucible-ref-count-records"),
KnownVersion::new(113, "add-tx-eq"),
KnownVersion::new(112, "blueprint-dataset"),
Expand Down
11 changes: 1 addition & 10 deletions nexus/db-queries/src/db/datastore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,7 @@ pub use sled::TransitionError;
pub use switch_port::SwitchPortSettingsCombinedResult;
pub use virtual_provisioning_collection::StorageType;
pub use vmm::VmmStateUpdateResult;
pub use volume::read_only_resources_associated_with_volume;
pub use volume::CrucibleResources;
pub use volume::CrucibleTargets;
pub use volume::ExistingTarget;
pub use volume::ReplacementTarget;
pub use volume::VolumeCheckoutReason;
pub use volume::VolumeReplaceResult;
pub use volume::VolumeReplacementParams;
pub use volume::VolumeToDelete;
pub use volume::VolumeWithTarget;
pub use volume::*;

// Number of unique datasets required to back a region.
// TODO: This should likely turn into a configuration option.
Expand Down
80 changes: 69 additions & 11 deletions nexus/db-queries/src/db/datastore/region_replacement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::db::pagination::Paginator;
use crate::db::update_and_check::UpdateAndCheck;
use crate::db::update_and_check::UpdateStatus;
use crate::db::TransactionError;
use crate::transaction_retry::OptionalError;
use async_bb8_diesel::AsyncConnection;
use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::prelude::*;
Expand Down Expand Up @@ -52,24 +53,38 @@ impl DataStore {
opctx: &OpContext,
request: RegionReplacement,
) -> Result<(), Error> {
let err = OptionalError::new();
self.pool_connection_authorized(opctx)
.await?
.transaction_async(|conn| async move {
use db::schema::region_replacement::dsl;

Self::volume_repair_insert_query(request.volume_id, request.id)
.execute_async(&conn)
.transaction_async(|conn| {
let err = err.clone();
async move {
use db::schema::region_replacement::dsl;

Self::volume_repair_insert_in_txn(
&conn,
err,
request.volume_id,
request.id,
)
.await?;

diesel::insert_into(dsl::region_replacement)
.values(request)
.execute_async(&conn)
.await?;
diesel::insert_into(dsl::region_replacement)
.values(request)
.execute_async(&conn)
.await?;

Ok(())
Ok(())
}
})
.await
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
.map_err(|e| {
if let Some(err) = err.take() {
err
} else {
public_error_from_diesel(e, ErrorHandler::Server)
}
})
}

pub async fn get_region_replacement_request_by_id(
Expand Down Expand Up @@ -897,6 +912,7 @@ mod test {

use crate::db::pub_test_utils::TestDatabase;
use omicron_test_utils::dev;
use sled_agent_client::types::VolumeConstructionRequest;

#[tokio::test]
async fn test_one_replacement_per_volume() {
Expand All @@ -908,6 +924,20 @@ mod test {
let region_2_id = Uuid::new_v4();
let volume_id = Uuid::new_v4();

datastore
.volume_create(nexus_db_model::Volume::new(
volume_id,
serde_json::to_string(&VolumeConstructionRequest::Volume {
id: volume_id,
block_size: 512,
sub_volumes: vec![],
read_only_parent: None,
})
.unwrap(),
))
.await
.unwrap();

let request_1 = RegionReplacement::new(region_1_id, volume_id);
let request_2 = RegionReplacement::new(region_2_id, volume_id);

Expand Down Expand Up @@ -939,6 +969,20 @@ mod test {
let region_id = Uuid::new_v4();
let volume_id = Uuid::new_v4();

datastore
.volume_create(nexus_db_model::Volume::new(
volume_id,
serde_json::to_string(&VolumeConstructionRequest::Volume {
id: volume_id,
block_size: 512,
sub_volumes: vec![],
read_only_parent: None,
})
.unwrap(),
))
.await
.unwrap();

let request = {
let mut request = RegionReplacement::new(region_id, volume_id);
request.replacement_state = RegionReplacementState::Running;
Expand Down Expand Up @@ -1031,6 +1075,20 @@ mod test {
let region_id = Uuid::new_v4();
let volume_id = Uuid::new_v4();

datastore
.volume_create(nexus_db_model::Volume::new(
volume_id,
serde_json::to_string(&VolumeConstructionRequest::Volume {
id: volume_id,
block_size: 512,
sub_volumes: vec![],
read_only_parent: None,
})
.unwrap(),
))
.await
.unwrap();

let request = {
let mut request = RegionReplacement::new(region_id, volume_id);
request.replacement_state = RegionReplacementState::ReplacementDone;
Expand Down
Loading
Loading