Skip to content

Commit 9a1f6bc

Browse files
authored
nexus: don't require sled ID to delete v2p mappings (#4139)
Nexus's `delete_instance_v2p_mappings` fetches the target instance's DB record to get the instance's current sled ID. This allows this function to (a) skip the sled when deleting V2P mappings, and (b) obtain the sled's physical host IP to pass into a `SetVirtualNetworkInterfaceHost` parameter block to pass to sled agent. Change `delete_instance_v2p_mappings` not to need the sled ID for either of these purposes: 1. It's already safe to ask a sled to delete a V2P mapping that its XDE driver has already deleted. (Currently, this is true because explicitly asking to destroy a V2P mapping is actually a no-op. But even if it weren't, Nexus already assumes this request is idempotent, since it can be invoked from the instance deletion saga.) 2. The physical host IP is not necessary for the `oxide-vpc` lib to identify a V2P mapping for removal; only the VNI and virtual IP are needed (see `VpcMappings::del` in the OPTE repo). This helps to clear a path for upcoming changes that will make an instance's sled ID optional (i.e. it is possible for an instance not to have one, or for the last-known location of a running instance to be more challenging to reason about than this routine presupposes). Tested: cargo test, including integration test updates for tests that are affected by this behavior; ran an instance on a dev cluster, verified it was externally accessible, verified the expected sled agent calls were seen when stopping/deleting the instance, and verified a new instance that reused the previous instance's external IP was also reachable.
1 parent 3019a1b commit 9a1f6bc

File tree

9 files changed

+72
-43
lines changed

9 files changed

+72
-43
lines changed

illumos-utils/src/opte/params.rs

+12
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,15 @@ pub struct SetVirtualNetworkInterfaceHost {
3838
pub physical_host_ip: Ipv6Addr,
3939
pub vni: external::Vni,
4040
}
41+
42+
/// The data needed to identify a virtual IP for which a sled maintains an OPTE
43+
/// virtual-to-physical mapping such that that mapping can be deleted.
44+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
45+
pub struct DeleteVirtualNetworkInterfaceHost {
46+
/// The virtual IP whose mapping should be deleted.
47+
pub virtual_ip: IpAddr,
48+
49+
/// The VNI for the network containing the virtual IP whose mapping should
50+
/// be deleted.
51+
pub vni: external::Vni,
52+
}

illumos-utils/src/opte/port_manager.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
use crate::opte::default_boundary_services;
88
use crate::opte::opte_firewall_rules;
9+
use crate::opte::params::DeleteVirtualNetworkInterfaceHost;
910
use crate::opte::params::SetVirtualNetworkInterfaceHost;
1011
use crate::opte::params::VpcFirewallRule;
1112
use crate::opte::Error;
@@ -480,17 +481,18 @@ impl PortManager {
480481
#[cfg(target_os = "illumos")]
481482
pub fn unset_virtual_nic_host(
482483
&self,
483-
_mapping: &SetVirtualNetworkInterfaceHost,
484+
_mapping: &DeleteVirtualNetworkInterfaceHost,
484485
) -> Result<(), Error> {
485486
// TODO requires https://github.com/oxidecomputer/opte/issues/332
487+
486488
slog::warn!(self.inner.log, "unset_virtual_nic_host unimplmented");
487489
Ok(())
488490
}
489491

490492
#[cfg(not(target_os = "illumos"))]
491493
pub fn unset_virtual_nic_host(
492494
&self,
493-
_mapping: &SetVirtualNetworkInterfaceHost,
495+
_mapping: &DeleteVirtualNetworkInterfaceHost,
494496
) -> Result<(), Error> {
495497
info!(self.inner.log, "Ignoring unset of virtual NIC mapping");
496498
Ok(())

nexus/src/app/instance_network.rs

+6-26
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use nexus_db_queries::db::lookup::LookupPath;
1212
use omicron_common::api::external::DataPageParams;
1313
use omicron_common::api::external::Error;
1414
use omicron_common::api::internal::shared::SwitchLocation;
15+
use sled_agent_client::types::DeleteVirtualNetworkInterfaceHost;
1516
use sled_agent_client::types::SetVirtualNetworkInterfaceHost;
1617
use std::collections::HashSet;
1718
use std::str::FromStr;
@@ -186,26 +187,16 @@ impl super::Nexus {
186187
// For every sled that isn't the sled this instance was allocated to, delete
187188
// the virtual to physical mapping for each of this instance's NICs. If
188189
// there isn't a V2P mapping, del_v2p should be a no-op.
189-
let (.., authz_instance, db_instance) =
190-
LookupPath::new(&opctx, &self.db_datastore)
191-
.instance_id(instance_id)
192-
.fetch_for(authz::Action::Read)
193-
.await?;
190+
let (.., authz_instance) = LookupPath::new(&opctx, &self.db_datastore)
191+
.instance_id(instance_id)
192+
.lookup_for(authz::Action::Read)
193+
.await?;
194194

195195
let instance_nics = self
196196
.db_datastore
197197
.derive_guest_network_interface_info(&opctx, &authz_instance)
198198
.await?;
199199

200-
// Lookup the physical host IP of the sled hosting this instance
201-
let instance_sled_id = db_instance.runtime().sled_id;
202-
let physical_host_ip = *self
203-
.sled_lookup(&self.opctx_alloc, &instance_sled_id)?
204-
.fetch()
205-
.await?
206-
.1
207-
.ip;
208-
209200
let mut last_sled_id: Option<Uuid> = None;
210201

211202
loop {
@@ -221,22 +212,11 @@ impl super::Nexus {
221212
Vec::with_capacity(sleds_page.len() * instance_nics.len());
222213

223214
for sled in &sleds_page {
224-
// del_v2p not required for sled instance was allocated to, OPTE
225-
// currently does that automatically
226-
//
227-
// TODO(#3107): Remove this when XDE stops deleting mappings
228-
// implicitly.
229-
if sled.id() == instance_sled_id {
230-
continue;
231-
}
232-
233215
for nic in &instance_nics {
234216
let client = self.sled_client(&sled.id()).await?;
235217
let nic_id = nic.id;
236-
let mapping = SetVirtualNetworkInterfaceHost {
218+
let mapping = DeleteVirtualNetworkInterfaceHost {
237219
virtual_ip: nic.ip,
238-
virtual_mac: nic.mac.clone(),
239-
physical_host_ip,
240220
vni: nic.vni.clone(),
241221
};
242222

nexus/tests/integration_tests/instances.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -3661,12 +3661,7 @@ async fn test_instance_v2p_mappings(cptestctx: &ControlPlaneTestContext) {
36613661
// Validate that every sled no longer has the V2P mapping for this instance
36623662
for sled_agent in &sled_agents {
36633663
let v2p_mappings = sled_agent.v2p_mappings.lock().await;
3664-
if sled_agent.id != db_instance.runtime().sled_id {
3665-
assert!(!v2p_mappings.is_empty());
3666-
assert!(v2p_mappings.get(&nics[0].identity.id).unwrap().is_empty());
3667-
} else {
3668-
assert!(v2p_mappings.is_empty());
3669-
}
3664+
assert!(v2p_mappings.is_empty());
36703665
}
36713666
}
36723667

openapi/sled-agent.json

+24-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@
442442
"content": {
443443
"application/json": {
444444
"schema": {
445-
"$ref": "#/components/schemas/SetVirtualNetworkInterfaceHost"
445+
"$ref": "#/components/schemas/DeleteVirtualNetworkInterfaceHost"
446446
}
447447
}
448448
},
@@ -1171,6 +1171,29 @@
11711171
"service_address"
11721172
]
11731173
},
1174+
"DeleteVirtualNetworkInterfaceHost": {
1175+
"description": "The data needed to identify a virtual IP for which a sled maintains an OPTE virtual-to-physical mapping such that that mapping can be deleted.",
1176+
"type": "object",
1177+
"properties": {
1178+
"virtual_ip": {
1179+
"description": "The virtual IP whose mapping should be deleted.",
1180+
"type": "string",
1181+
"format": "ip"
1182+
},
1183+
"vni": {
1184+
"description": "The VNI for the network containing the virtual IP whose mapping should be deleted.",
1185+
"allOf": [
1186+
{
1187+
"$ref": "#/components/schemas/Vni"
1188+
}
1189+
]
1190+
}
1191+
},
1192+
"required": [
1193+
"virtual_ip",
1194+
"vni"
1195+
]
1196+
},
11741197
"DiskEnsureBody": {
11751198
"description": "Sent from to a sled agent to establish the runtime state of a Disk",
11761199
"type": "object",

sled-agent/src/http_entrypoints.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use dropshot::{
2020
HttpResponseDeleted, HttpResponseHeaders, HttpResponseOk,
2121
HttpResponseUpdatedNoContent, Path, Query, RequestContext, TypedBody,
2222
};
23-
use illumos_utils::opte::params::SetVirtualNetworkInterfaceHost;
23+
use illumos_utils::opte::params::{
24+
DeleteVirtualNetworkInterfaceHost, SetVirtualNetworkInterfaceHost,
25+
};
2426
use omicron_common::api::external::Error;
2527
use omicron_common::api::internal::nexus::DiskRuntimeState;
2628
use omicron_common::api::internal::nexus::InstanceRuntimeState;
@@ -600,7 +602,7 @@ async fn set_v2p(
600602
async fn del_v2p(
601603
rqctx: RequestContext<SledAgent>,
602604
_path_params: Path<V2pPathParam>,
603-
body: TypedBody<SetVirtualNetworkInterfaceHost>,
605+
body: TypedBody<DeleteVirtualNetworkInterfaceHost>,
604606
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
605607
let sa = rqctx.context();
606608
let body_args = body.into_inner();

sled-agent/src/sim/http_entrypoints.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use dropshot::HttpResponseUpdatedNoContent;
1717
use dropshot::Path;
1818
use dropshot::RequestContext;
1919
use dropshot::TypedBody;
20+
use illumos_utils::opte::params::DeleteVirtualNetworkInterfaceHost;
2021
use illumos_utils::opte::params::SetVirtualNetworkInterfaceHost;
2122
use omicron_common::api::internal::nexus::DiskRuntimeState;
2223
use omicron_common::api::internal::nexus::InstanceRuntimeState;
@@ -307,7 +308,7 @@ async fn set_v2p(
307308
async fn del_v2p(
308309
rqctx: RequestContext<Arc<SledAgent>>,
309310
path_params: Path<V2pPathParam>,
310-
body: TypedBody<SetVirtualNetworkInterfaceHost>,
311+
body: TypedBody<DeleteVirtualNetworkInterfaceHost>,
311312
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
312313
let sa = rqctx.context();
313314
let interface_id = path_params.into_inner().interface_id;

sled-agent/src/sim/sled_agent.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ use std::str::FromStr;
3333

3434
use crucible_client_types::VolumeConstructionRequest;
3535
use dropshot::HttpServer;
36-
use illumos_utils::opte::params::SetVirtualNetworkInterfaceHost;
36+
use illumos_utils::opte::params::{
37+
DeleteVirtualNetworkInterfaceHost, SetVirtualNetworkInterfaceHost,
38+
};
3739
use nexus_client::types::PhysicalDiskKind;
3840
use omicron_common::address::PROPOLIS_PORT;
3941
use propolis_client::Client as PropolisClient;
@@ -574,11 +576,21 @@ impl SledAgent {
574576
pub async fn unset_virtual_nic_host(
575577
&self,
576578
interface_id: Uuid,
577-
mapping: &SetVirtualNetworkInterfaceHost,
579+
mapping: &DeleteVirtualNetworkInterfaceHost,
578580
) -> Result<(), Error> {
579581
let mut v2p_mappings = self.v2p_mappings.lock().await;
580582
let vec = v2p_mappings.entry(interface_id).or_default();
581-
vec.retain(|x| x != mapping);
583+
vec.retain(|x| {
584+
x.virtual_ip != mapping.virtual_ip || x.vni != mapping.vni
585+
});
586+
587+
// If the last entry was removed, remove the entire interface ID so that
588+
// tests don't have to distinguish never-created entries from
589+
// previously-extant-but-now-empty entries.
590+
if vec.is_empty() {
591+
v2p_mappings.remove(&interface_id);
592+
}
593+
582594
Ok(())
583595
}
584596

sled-agent/src/sled_agent.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use crate::zone_bundle::BundleError;
2525
use bootstore::schemes::v0 as bootstore;
2626
use camino::Utf8PathBuf;
2727
use dropshot::HttpError;
28-
use illumos_utils::opte::params::SetVirtualNetworkInterfaceHost;
28+
use illumos_utils::opte::params::{
29+
DeleteVirtualNetworkInterfaceHost, SetVirtualNetworkInterfaceHost,
30+
};
2931
use illumos_utils::opte::PortManager;
3032
use illumos_utils::zone::PROPOLIS_ZONE_PREFIX;
3133
use illumos_utils::zone::ZONE_PREFIX;
@@ -878,7 +880,7 @@ impl SledAgent {
878880

879881
pub async fn unset_virtual_nic_host(
880882
&self,
881-
mapping: &SetVirtualNetworkInterfaceHost,
883+
mapping: &DeleteVirtualNetworkInterfaceHost,
882884
) -> Result<(), Error> {
883885
self.inner
884886
.port_manager

0 commit comments

Comments
 (0)