diff --git a/illumos-utils/src/running_zone.rs b/illumos-utils/src/running_zone.rs index ea80a6d34b6..e739e4c20d2 100644 --- a/illumos-utils/src/running_zone.rs +++ b/illumos-utils/src/running_zone.rs @@ -915,6 +915,10 @@ impl RunningZone { &self.inner.links } + pub fn links_mut(&mut self) -> &mut Vec { + &mut self.inner.links + } + /// Return the running processes associated with all the SMF services this /// zone is intended to run. pub fn service_processes( diff --git a/nexus/db-model/src/external_ip.rs b/nexus/db-model/src/external_ip.rs index d8decf2f334..31415d574b2 100644 --- a/nexus/db-model/src/external_ip.rs +++ b/nexus/db-model/src/external_ip.rs @@ -130,6 +130,7 @@ pub struct IncompleteExternalIp { time_created: DateTime, kind: IpKind, is_service: bool, + is_probe: bool, parent_id: Option, pool_id: Uuid, project_id: Option, @@ -152,6 +153,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::SNat, is_service: false, + is_probe: false, parent_id: Some(instance_id), pool_id, project_id: None, @@ -168,6 +170,28 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::Ephemeral, is_service: false, + is_probe: false, + parent_id: Some(instance_id), + pool_id, + project_id: None, + explicit_ip: None, + explicit_port_range: None, + } + } + + pub fn for_ephemeral_probe( + id: Uuid, + instance_id: Uuid, + pool_id: Uuid, + ) -> Self { + Self { + id, + name: None, + description: None, + time_created: Utc::now(), + kind: IpKind::Ephemeral, + is_service: false, + is_probe: true, parent_id: Some(instance_id), pool_id, project_id: None, @@ -190,6 +214,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::Floating, is_service: false, + is_probe: false, parent_id: None, pool_id, project_id: Some(project_id), @@ -213,6 +238,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::Floating, is_service: false, + is_probe: false, parent_id: None, pool_id, project_id: Some(project_id), @@ -236,6 +262,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::Floating, is_service: true, + is_probe: false, parent_id: Some(service_id), pool_id, project_id: None, @@ -265,6 +292,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::SNat, is_service: true, + is_probe: false, parent_id: Some(service_id), pool_id, project_id: None, @@ -287,6 +315,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::Floating, is_service: true, + is_probe: false, parent_id: Some(service_id), pool_id, project_id: None, @@ -303,6 +332,7 @@ impl IncompleteExternalIp { time_created: Utc::now(), kind: IpKind::SNat, is_service: true, + is_probe: false, parent_id: Some(service_id), pool_id, project_id: None, @@ -335,6 +365,10 @@ impl IncompleteExternalIp { &self.is_service } + pub fn is_probe(&self) -> &bool { + &self.is_probe + } + pub fn parent_id(&self) -> &Option { &self.parent_id } diff --git a/nexus/db-model/src/network_interface.rs b/nexus/db-model/src/network_interface.rs index a8ee460b963..93fa56d06e0 100644 --- a/nexus/db-model/src/network_interface.rs +++ b/nexus/db-model/src/network_interface.rs @@ -61,7 +61,33 @@ impl Into for NetworkInterface { fn into(self) -> omicron_common::api::internal::shared::NetworkInterface { - todo!() + omicron_common::api::internal::shared::NetworkInterface { + id: self.id(), + kind: match self.kind { + NetworkInterfaceKind::Instance => + omicron_common::api::internal::shared::NetworkInterfaceKind::Instance { + id: self.parent_id + }, + NetworkInterfaceKind::Service => + omicron_common::api::internal::shared::NetworkInterfaceKind::Service { + id: self.parent_id + }, + NetworkInterfaceKind::Probe => + omicron_common::api::internal::shared::NetworkInterfaceKind::Probe { + id: self.parent_id + }, + }, + name: self.name().clone(), + ip: self.ip.ip(), + mac: self.mac.into(), + subnet: ipnetwork::IpNetwork::new( + self.ip.network(), + 24, //TODO + ).unwrap().into(), + vni: omicron_common::api::external::Vni::try_from(0).unwrap(), //TODO + primary: self.primary, + slot: self.slot.try_into().unwrap(), + } } } diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index 99affaa8f73..c68cdb0255e 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -81,7 +81,7 @@ impl DataStore { let pool_id = pool.identity.id; let data = - IncompleteExternalIp::for_ephemeral(ip_id, probe_id, pool_id); + IncompleteExternalIp::for_ephemeral_probe(ip_id, probe_id, pool_id); self.allocate_external_ip(opctx, data).await } diff --git a/nexus/db-queries/src/db/queries/external_ip.rs b/nexus/db-queries/src/db/queries/external_ip.rs index 4e5f59e79c6..ae803c18edf 100644 --- a/nexus/db-queries/src/db/queries/external_ip.rs +++ b/nexus/db-queries/src/db/queries/external_ip.rs @@ -378,6 +378,12 @@ impl NextExternalIp { out.push_bind_param::, Option>(self.ip.project_id())?; out.push_sql(" AS "); out.push_identifier(dsl::project_id::NAME)?; + out.push_sql(", "); + + // is_probe flag + out.push_bind_param::(self.ip.is_probe())?; + out.push_sql(" AS "); + out.push_identifier(dsl::is_probe::NAME)?; out.push_sql(" FROM ("); self.push_address_sequence_subquery(out.reborrow())?; diff --git a/sled-agent/src/probe_manager.rs b/sled-agent/src/probe_manager.rs index 3f927068ede..d576b822638 100644 --- a/sled-agent/src/probe_manager.rs +++ b/sled-agent/src/probe_manager.rs @@ -17,7 +17,7 @@ use rand::SeedableRng; use sled_storage::dataset::ZONE_DATASET; use sled_storage::manager::StorageHandle; use slog::{error, warn, Logger}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::time::Duration; @@ -41,6 +41,7 @@ pub struct ProbeManagerInner { vnic_allocator: VnicAllocator, storage: StorageHandle, port_manager: PortManager, + running_probes: Mutex>, } impl ProbeManager { @@ -56,6 +57,7 @@ impl ProbeManager { inner: Arc::new(ProbeManagerInner { join_handle: Mutex::new(None), vnic_allocator: VnicAllocator::new("probe", etherstub), + running_probes: Mutex::new(HashMap::new()), nexus_client, log, sled_id, @@ -70,7 +72,7 @@ impl ProbeManager { } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct ProbeState { id: Uuid, status: zone::State, @@ -109,7 +111,7 @@ impl TryFrom for ProbeState { Ok(Self { id: value .name() - .strip_prefix(PROBE_ZONE_PREFIX) + .strip_prefix(&format!("{PROBE_ZONE_PREFIX}_")) .ok_or(String::from("not a probe prefix"))? .parse() .map_err(|e| format!("invalid uuid: {e}"))?, @@ -161,6 +163,7 @@ impl ProbeManagerInner { I: Iterator, { for probe in probes { + info!(self.log, "adding probe {}", probe.id); if let Err(e) = self.add_probe(probe).await { error!(self.log, "add probe: {e}"); } @@ -228,9 +231,12 @@ impl ProbeManagerInner { //TODO(ry) SMF properties? - RunningZone::boot(installed_zone).await?; + let rz = RunningZone::boot(installed_zone).await?; + rz.ensure_address_for_port("overlay", 0).await?; info!(self.log, "started probe {}", probe.id); + self.running_probes.lock().await.insert(probe.id, rz); + Ok(()) } @@ -239,16 +245,27 @@ impl ProbeManagerInner { I: Iterator, { for probe in probes { + info!(self.log, "removing probe {}", probe.id); self.remove_probe(probe.id).await; } } async fn remove_probe(self: &Arc, id: Uuid) { - if let Err(e) = - Zones::halt_and_remove(&format!("{}_{}", PROBE_ZONE_PREFIX, id)) - .await - { - error!(self.log, "remove zone {e}") + match self.running_probes.lock().await.remove(&id) { + Some(mut running_zone) => { + for l in running_zone.links_mut() { + if let Err(e) = l.delete() { + error!(self.log, "delete probe link {}: {e}", l.name()); + } + } + running_zone.release_opte_ports(); + if let Err(e) = running_zone.stop().await { + error!(self.log, "stop probe: {e}") + } + } + None => { + warn!(self.log, "attempt to stop non-running probe: {id}") + } } } @@ -295,3 +312,31 @@ impl ProbeManagerInner { .collect()) } } + +#[cfg(test)] +mod test { + use super::*; + use uuid::Uuid; + + #[test] + fn probe_state_set_ops() { + let a = ProbeState { + id: Uuid::new_v4(), + status: zone::State::Configured, + external_ips: Vec::new(), + interface: None, + }; + + let mut b = a.clone(); + b.status = zone::State::Running; + + let target = HashSet::from([a]); + let current = HashSet::from([b]); + + let to_add = target.difference(¤t); + let to_remove = current.difference(&target); + + assert_eq!(to_add.count(), 0); + assert_eq!(to_remove.count(), 0); + } +}