Skip to content

Commit 8d13230

Browse files
authored
Unrolling saga actions for external IPs and NICs (#1474)
* Unrolling saga actions for external IPs and NICs Previously, for NICs and external IPs, the saga action that actually created the records and the corresponding undo were not together in a saga node. The undo was associated with a preceding action that created UUIDs for the records. That was because each action may have created multiple records, where the count was not known when we create the saga template, and so the forward action performed multiple fallible steps. To unwind it completely, including reverting the creation of N resources, in the case where the N + 1th failed, we put the undo in the preceding step. That worked, but was certainly confusing and bad for maintenance, since the action and its undo were in different nodes. It also resulted in a ton of extra complexity in some of the downstream queries, which now had to be aware of the fact that we might replay a partially-played saga node. This commit undoes all that. - Unroll the instance external IP address query - Unroll the network interface creation query - Actually delete NICs when we delete the instance, and add test for that - Simplify network interface query. An additional CTE detecting the partial-saga replay case is no longer needed, since we don't run the saga that way. * Review feedback - Create UUIDs in separate saga actions for NICs and external IPs - Remove remaining cruft from NIC-creation query that was required to handle the previous saga implementation.
1 parent 604a35b commit 8d13230

File tree

7 files changed

+407
-650
lines changed

7 files changed

+407
-650
lines changed

nexus/src/app/instance.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
//! Virtual Machine Instances
66
77
use super::MAX_DISKS_PER_INSTANCE;
8+
use super::MAX_EXTERNAL_IPS_PER_INSTANCE;
9+
use super::MAX_NICS_PER_INSTANCE;
810
use crate::app::sagas;
911
use crate::authn;
1012
use crate::authz;
@@ -59,6 +61,37 @@ impl super::Nexus {
5961
MAX_DISKS_PER_INSTANCE
6062
)));
6163
}
64+
if params.external_ips.len() > MAX_EXTERNAL_IPS_PER_INSTANCE {
65+
return Err(Error::invalid_request(&format!(
66+
"An instance may not have more than {} external IP addresses",
67+
MAX_EXTERNAL_IPS_PER_INSTANCE,
68+
)));
69+
}
70+
if let params::InstanceNetworkInterfaceAttachment::Create(ref ifaces) =
71+
params.network_interfaces
72+
{
73+
if ifaces.len() > MAX_NICS_PER_INSTANCE {
74+
return Err(Error::invalid_request(&format!(
75+
"An instance may not have more than {} network interfaces",
76+
MAX_NICS_PER_INSTANCE,
77+
)));
78+
}
79+
// Check that all VPC names are the same.
80+
//
81+
// This isn't strictly necessary, as the queries to create these
82+
// interfaces would fail in the saga, but it's easier to handle here.
83+
if ifaces
84+
.iter()
85+
.map(|iface| &iface.vpc_name)
86+
.collect::<std::collections::BTreeSet<_>>()
87+
.len()
88+
!= 1
89+
{
90+
return Err(Error::invalid_request(
91+
"All interfaces must be in the same VPC",
92+
));
93+
}
94+
}
6295

6396
// Reject instances where the memory is not at least
6497
// MIN_MEMORY_SIZE_BYTES
@@ -217,6 +250,9 @@ impl super::Nexus {
217250
self.db_datastore
218251
.project_delete_instance(opctx, &authz_instance)
219252
.await?;
253+
self.db_datastore
254+
.instance_delete_all_network_interfaces(opctx, &authz_instance)
255+
.await?;
220256
// Ignore the count of addresses deleted
221257
self.db_datastore
222258
.deallocate_instance_external_ip_by_instance_id(
@@ -502,12 +538,12 @@ impl super::Nexus {
502538
.partition(|ip| ip.kind == IpKind::SNat);
503539

504540
// Sanity checks on the number and kind of each IP address.
505-
if external_ips.len() > crate::app::MAX_EPHEMERAL_IPS_PER_INSTANCE {
541+
if external_ips.len() > MAX_EXTERNAL_IPS_PER_INSTANCE {
506542
return Err(Error::internal_error(
507543
format!(
508544
"Expected the number of external IPs to be limited to \
509-
{}, but found {}",
510-
crate::app::MAX_EPHEMERAL_IPS_PER_INSTANCE,
545+
{}, but found {}",
546+
MAX_EXTERNAL_IPS_PER_INSTANCE,
511547
external_ips.len(),
512548
)
513549
.as_str(),

nexus/src/app/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ mod sagas;
5252

5353
pub(crate) const MAX_DISKS_PER_INSTANCE: u32 = 8;
5454

55-
pub(crate) const MAX_NICS_PER_INSTANCE: u32 = 8;
55+
pub(crate) const MAX_NICS_PER_INSTANCE: usize = 8;
5656

57-
// TODO-completness: Support multiple Ephemeral IPs
58-
pub(crate) const MAX_EPHEMERAL_IPS_PER_INSTANCE: usize = 1;
57+
// TODO-completness: Support multiple external IPs
58+
pub(crate) const MAX_EXTERNAL_IPS_PER_INSTANCE: usize = 1;
5959

6060
/// Manages an Oxide fleet -- the heart of the control plane
6161
pub struct Nexus {

0 commit comments

Comments
 (0)