Skip to content

Commit 2dc69d1

Browse files
committed
it's sagas all the way down
sickos dot png
1 parent aa6c268 commit 2dc69d1

File tree

4 files changed

+252
-112
lines changed

4 files changed

+252
-112
lines changed

nexus/db-queries/src/db/datastore/instance.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,54 @@ impl DataStore {
803803
Ok(locked)
804804
}
805805

806+
pub async fn instance_updater_inherit_lock(
807+
&self,
808+
opctx: &OpContext,
809+
instance: &Instance,
810+
child_lock_id: &Uuid,
811+
) -> Result<(), Error> {
812+
use db::schema::instance::dsl;
813+
814+
let current_gen = instance.runtime_state.updater_gen;
815+
let new_gen = Generation(current_gen.0.next());
816+
let instance_id = instance.id();
817+
let parent_id = instance.runtime_state.updater_id.ok_or_else(|| {
818+
Error::internal_error(
819+
"instance must already be locked in order to inherit the lock",
820+
)
821+
})?;
822+
823+
diesel::update(dsl::instance)
824+
.filter(dsl::time_deleted.is_null())
825+
.filter(dsl::id.eq(instance_id))
826+
.filter(dsl::updater_gen.eq(current_gen))
827+
.filter(dsl::updater_id.eq(parent_id))
828+
.set((
829+
dsl::updater_gen.eq(new_gen),
830+
dsl::updater_id.eq(Some(*child_lock_id)),
831+
))
832+
.check_if_exists::<Instance>(instance_id)
833+
.execute_and_check(&*self.pool_connection_authorized(opctx).await?)
834+
.await
835+
.map_err(|e| {
836+
public_error_from_diesel(
837+
e,
838+
ErrorHandler::NotFoundByLookup(
839+
ResourceType::Instance,
840+
LookupType::ById(instance_id),
841+
),
842+
)
843+
})
844+
.and_then(|r| {
845+
match r.status {
846+
UpdateStatus::Updated => Ok(()),
847+
UpdateStatus::NotUpdatedButExists => Err(Error::internal_error(
848+
"cannot inherit lock (another saga probably already has)",
849+
)),
850+
}
851+
})
852+
}
853+
806854
/// Release the instance-updater lock acquired by
807855
/// [`DataStore::instance_updater_try_lock`].
808856
pub async fn instance_updater_unlock(

nexus/src/app/sagas/instance_update/destroyed.rs

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@
55
use super::ActionRegistry;
66
use super::NexusActionContext;
77
use super::NexusSaga;
8-
use super::Params;
98
use super::STATE;
109
use crate::app::sagas::declare_saga_actions;
1110
use crate::app::sagas::ActionError;
1211
use nexus_db_model::Generation;
1312
use nexus_db_model::Instance;
1413
use nexus_db_model::InstanceRuntimeState;
1514
use nexus_db_model::Vmm;
15+
use nexus_db_queries::authn;
16+
use nexus_db_queries::authz;
1617
use nexus_db_queries::db::datastore::InstanceAndVmms;
1718
use nexus_db_queries::db::identity::Resource;
1819
use omicron_common::api::external;
1920
use omicron_common::api::external::Error;
2021
use omicron_common::api::external::InstanceState;
22+
use serde::{Deserialize, Serialize};
2123
use slog::info;
24+
use uuid::Uuid;
2225

23-
// instance update VMM destroyed subsaga: actions
26+
// instance update (active VMM destroyed) subsaga: actions
2427

2528
// This subsaga is responsible for handling an instance update where the
2629
// instance's active VMM has entered the `Destroyed` state. This requires
@@ -63,6 +66,21 @@ declare_saga_actions! {
6366
}
6467
}
6568

69+
/// Parameters to the instance update (active VMM destroyed) sub-saga.
70+
#[derive(Debug, Deserialize, Serialize)]
71+
pub(super) struct Params {
72+
/// Authentication context to use to fetch the instance's current state from
73+
/// the database.
74+
pub(super) serialized_authn: authn::saga::Serialized,
75+
76+
pub(super) authz_instance: authz::Instance,
77+
78+
/// The UUID of the VMM that was destroyed.
79+
pub(super) vmm_id: Uuid,
80+
81+
pub(super) instance: Instance,
82+
}
83+
6684
#[derive(Debug)]
6785
pub(crate) struct SagaVmmDestroyed;
6886
impl NexusSaga for SagaVmmDestroyed {
@@ -104,14 +122,8 @@ fn get_destroyed_vmm(
104122
async fn siud_release_sled_resources(
105123
sagactx: NexusActionContext,
106124
) -> Result<(), ActionError> {
107-
let Some((_, vmm)) = get_destroyed_vmm(&sagactx)? else {
108-
// if the update we are handling is not an active VMM destroyed update,
109-
// bail --- there's nothing to do here.
110-
return Ok(());
111-
};
112-
113125
let osagactx = sagactx.user_data();
114-
let Params { ref serialized_authn, ref authz_instance } =
126+
let Params { ref serialized_authn, ref authz_instance, vmm_id, .. } =
115127
sagactx.saga_params::<Params>()?;
116128

117129
let opctx =
@@ -121,13 +133,13 @@ async fn siud_release_sled_resources(
121133
osagactx.log(),
122134
"instance update (active VMM destroyed): deallocating sled resource reservation";
123135
"instance_id" => %authz_instance.id(),
124-
"propolis_id" => %vmm.id,
136+
"propolis_id" => %vmm_id,
125137
"instance_update" => %"VMM destroyed",
126138
);
127139

128140
osagactx
129141
.datastore()
130-
.sled_reservation_delete(&opctx, vmm.id)
142+
.sled_reservation_delete(&opctx, vmm_id)
131143
.await
132144
.or_else(|err| {
133145
// Necessary for idempotency
@@ -149,7 +161,7 @@ async fn siud_release_virtual_provisioning(
149161
};
150162

151163
let osagactx = sagactx.user_data();
152-
let Params { ref serialized_authn, ref authz_instance } =
164+
let Params { ref serialized_authn, ref authz_instance, vmm_id, .. } =
153165
sagactx.saga_params::<Params>()?;
154166

155167
let opctx =
@@ -159,7 +171,7 @@ async fn siud_release_virtual_provisioning(
159171
osagactx.log(),
160172
"instance update (VMM destroyed): deallocating virtual provisioning resources";
161173
"instance_id" => %authz_instance.id(),
162-
"propolis_id" => %vmm.id,
174+
"propolis_id" => %vmm_id,
163175
"instance_update" => %"VMM destroyed",
164176
);
165177

@@ -187,11 +199,6 @@ async fn siud_release_virtual_provisioning(
187199
async fn siud_unassign_oximeter_producer(
188200
sagactx: NexusActionContext,
189201
) -> Result<(), ActionError> {
190-
let Some((_, _)) = get_destroyed_vmm(&sagactx)? else {
191-
// if the update we are handling is not an active VMM destroyed update,
192-
// bail --- there's nothing to do here.
193-
return Ok(());
194-
};
195202
let osagactx = sagactx.user_data();
196203
let Params { ref serialized_authn, ref authz_instance, .. } =
197204
sagactx.saga_params::<Params>()?;
@@ -212,17 +219,15 @@ async fn siud_unassign_oximeter_producer(
212219
async fn siud_delete_v2p_mappings(
213220
sagactx: NexusActionContext,
214221
) -> Result<(), ActionError> {
215-
let Some((instance, vmm)) = get_destroyed_vmm(&sagactx)? else {
216-
// if the update we are handling is not an active VMM destroyed update,
217-
// bail --- there's nothing to do here.
218-
return Ok(());
219-
};
222+
let Params { ref authz_instance, vmm_id, .. } =
223+
sagactx.saga_params::<Params>()?;
224+
220225
let osagactx = sagactx.user_data();
221226
info!(
222227
osagactx.log(),
223228
"instance update (VMM destroyed): deleting V2P mappings";
224-
"instance_id" => %instance.id(),
225-
"propolis_id" => %vmm.id,
229+
"instance_id" => %authz_instance.id(),
230+
"propolis_id" => %vmm_id,
226231
"instance_update" => %"VMM destroyed",
227232
);
228233

@@ -234,13 +239,8 @@ async fn siud_delete_v2p_mappings(
234239
async fn siud_delete_nat_entries(
235240
sagactx: NexusActionContext,
236241
) -> Result<(), ActionError> {
237-
let Some((_, vmm)) = get_destroyed_vmm(&sagactx)? else {
238-
// if the update we are handling is not an active VMM destroyed update,
239-
// bail --- there's nothing to do here.
240-
return Ok(());
241-
};
242242
let osagactx = sagactx.user_data();
243-
let Params { ref serialized_authn, ref authz_instance, .. } =
243+
let Params { ref serialized_authn, ref authz_instance, vmm_id, .. } =
244244
sagactx.saga_params::<Params>()?;
245245

246246
let opctx =
@@ -250,7 +250,7 @@ async fn siud_delete_nat_entries(
250250
osagactx.log(),
251251
"instance update (VMM destroyed): deleting NAT entries";
252252
"instance_id" => %authz_instance.id(),
253-
"propolis_id" => %vmm.id,
253+
"propolis_id" => %vmm_id,
254254
"instance_update" => %"VMM destroyed",
255255
);
256256

@@ -265,11 +265,9 @@ async fn siud_delete_nat_entries(
265265
async fn siud_update_instance(
266266
sagactx: NexusActionContext,
267267
) -> Result<(), ActionError> {
268-
let Some((instance, vmm)) = get_destroyed_vmm(&sagactx)? else {
269-
// if the update we are handling is not an active VMM destroyed update,
270-
// bail --- there's nothing to do here.
271-
return Ok(());
272-
};
268+
let Params { ref authz_instance, ref vmm_id, instance, .. } =
269+
sagactx.saga_params::<Params>()?;
270+
273271
let osagactx = sagactx.user_data();
274272
let new_runtime = InstanceRuntimeState {
275273
propolis_id: None,
@@ -281,30 +279,25 @@ async fn siud_update_instance(
281279
info!(
282280
osagactx.log(),
283281
"instance update (VMM destroyed): updating runtime state";
284-
"instance_id" => %instance.id(),
285-
"propolis_id" => %vmm.id,
282+
"instance_id" => %authz_instance.id(),
283+
"propolis_id" => %vmm_id,
286284
"new_runtime_state" => ?new_runtime,
287285
"instance_update" => %"VMM destroyed",
288286
);
289287

290288
// It's okay for this to fail, it just means that the active VMM ID has changed.
291289
let _ = osagactx
292290
.datastore()
293-
.instance_update_runtime(&instance.id(), &new_runtime)
291+
.instance_update_runtime(&authz_instance.id(), &new_runtime)
294292
.await;
295293
Ok(())
296294
}
297295

298296
async fn siud_mark_vmm_deleted(
299297
sagactx: NexusActionContext,
300298
) -> Result<(), ActionError> {
301-
let Some((instance, vmm)) = get_destroyed_vmm(&sagactx)? else {
302-
// if the update we are handling is not an active VMM destroyed update,
303-
// bail --- there's nothing to do here.
304-
return Ok(());
305-
};
306299
let osagactx = sagactx.user_data();
307-
let Params { ref serialized_authn, .. } =
300+
let Params { ref authz_instance, ref vmm_id, ref serialized_authn, .. } =
308301
sagactx.saga_params::<Params>()?;
309302

310303
let opctx =
@@ -313,14 +306,14 @@ async fn siud_mark_vmm_deleted(
313306
info!(
314307
osagactx.log(),
315308
"instance update (VMM destroyed): marking VMM record deleted";
316-
"instance_id" => %instance.id(),
317-
"propolis_id" => %vmm.id,
309+
"instance_id" => %authz_instance.id(),
310+
"propolis_id" => %vmm_id,
318311
"instance_update" => %"VMM destroyed",
319312
);
320313

321314
osagactx
322315
.datastore()
323-
.vmm_mark_deleted(&opctx, &vmm.id)
316+
.vmm_mark_deleted(&opctx, vmm_id)
324317
.await
325318
.map(|_| ())
326319
.map_err(ActionError::action_failed)

0 commit comments

Comments
 (0)