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..2f8793767a5 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);
+ }
+}