From cd1b54cd6d2fed0ec1860086a2edb06b6f638ac0 Mon Sep 17 00:00:00 2001 From: James MacMahon Date: Mon, 17 Nov 2025 19:33:05 +0000 Subject: [PATCH] Swap how to refer to the new instance ensure body Instead of using a v7 module to refer to the new instance ensure body, use a v6 module to refer to the old one - eventually we'll want to deprecate and remove the old instance ensure body type, and it's easier to refer to the old type in the few places that it's required to. This commit also removes `multicast_cross_version_works` because I don't think it's necessary now that we're sending (and routing based on!) the api version header. --- .../src/test_util/host_phase_2_test_state.rs | 14 +-- sled-agent/api/src/lib.rs | 22 ++-- sled-agent/api/src/{v7.rs => v6.rs} | 46 ++++--- sled-agent/src/http_entrypoints.rs | 27 ++-- sled-agent/src/instance.rs | 4 - sled-agent/src/instance_manager.rs | 1 - sled-agent/src/sim/http_entrypoints.rs | 20 +-- sled-agent/src/sim/sled_agent.rs | 44 +------ sled-agent/src/sled_agent.rs | 45 +------ .../tests/multicast_cross_version_test.rs | 118 ------------------ sled-agent/types/src/instance.rs | 23 +++- 11 files changed, 83 insertions(+), 281 deletions(-) rename sled-agent/api/src/{v7.rs => v6.rs} (69%) delete mode 100644 sled-agent/tests/multicast_cross_version_test.rs diff --git a/nexus/mgs-updates/src/test_util/host_phase_2_test_state.rs b/nexus/mgs-updates/src/test_util/host_phase_2_test_state.rs index 80082e01377..16f39025729 100644 --- a/nexus/mgs-updates/src/test_util/host_phase_2_test_state.rs +++ b/nexus/mgs-updates/src/test_util/host_phase_2_test_state.rs @@ -220,14 +220,14 @@ mod api_impl { use omicron_common::api::internal::shared::{ ResolvedVpcRouteSet, ResolvedVpcRouteState, SwitchPorts, }; - use sled_agent_api::v7::InstanceEnsureBody; - use sled_agent_api::v7::InstanceMulticastBody; use sled_agent_api::*; use sled_agent_types::bootstore::BootstoreStatus; use sled_agent_types::disk::DiskEnsureBody; use sled_agent_types::early_networking::EarlyNetworkConfig; use sled_agent_types::firewall_rules::VpcFirewallRulesEnsureBody; + use sled_agent_types::instance::InstanceEnsureBody; use sled_agent_types::instance::InstanceExternalIpBody; + use sled_agent_types::instance::InstanceMulticastBody; use sled_agent_types::instance::VmmPutStateBody; use sled_agent_types::instance::VmmPutStateResponse; use sled_agent_types::instance::VmmUnregisterResponse; @@ -531,15 +531,7 @@ mod api_impl { unimplemented!() } - async fn vmm_register_v1( - _rqctx: RequestContext, - _path_params: Path, - _body: TypedBody, - ) -> Result, HttpError> { - unimplemented!() - } - - async fn vmm_register_v7( + async fn vmm_register( _rqctx: RequestContext, _path_params: Path, _body: TypedBody, diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index d37f406af54..b375d326749 100644 --- a/sled-agent/api/src/lib.rs +++ b/sled-agent/api/src/lib.rs @@ -40,8 +40,8 @@ use sled_agent_types::{ early_networking::EarlyNetworkConfig, firewall_rules::VpcFirewallRulesEnsureBody, instance::{ - InstanceExternalIpBody, VmmPutStateBody, VmmPutStateResponse, - VmmUnregisterResponse, + InstanceEnsureBody, InstanceExternalIpBody, InstanceMulticastBody, + VmmPutStateBody, VmmPutStateResponse, VmmUnregisterResponse, }, sled::AddSledRequest, zone_bundle::{ @@ -55,8 +55,8 @@ use uuid::Uuid; /// Copies of data types that changed between v3 and v4. mod v3; -/// Copies of data types that changed between previous versions and v7. -pub mod v7; +/// Copies of data types that changed between previous versions and v6. +mod v6; api_versions!([ // WHEN CHANGING THE API (part 1 of 2): @@ -368,8 +368,10 @@ pub trait SledAgentApi { async fn vmm_register_v1( rqctx: RequestContext, path_params: Path, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError> { + Self::vmm_register(rqctx, path_params, body.map(Into::into)).await + } #[endpoint { method = PUT, @@ -377,10 +379,10 @@ pub trait SledAgentApi { operation_id = "vmm_register", versions = VERSION_MULTICAST_SUPPORT.. }] - async fn vmm_register_v7( + async fn vmm_register( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result, HttpError>; #[endpoint { @@ -439,7 +441,7 @@ pub trait SledAgentApi { async fn vmm_join_multicast_group( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result; #[endpoint { @@ -450,7 +452,7 @@ pub trait SledAgentApi { async fn vmm_leave_multicast_group( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result; #[endpoint { diff --git a/sled-agent/api/src/v7.rs b/sled-agent/api/src/v6.rs similarity index 69% rename from sled-agent/api/src/v7.rs rename to sled-agent/api/src/v6.rs index 4e096e5415f..06e6b995d7e 100644 --- a/sled-agent/api/src/v7.rs +++ b/sled-agent/api/src/v6.rs @@ -26,7 +26,7 @@ use uuid::Uuid; use sled_agent_types::instance::{InstanceMetadata, VmmSpec}; /// The body of a request to ensure that a instance and VMM are known to a sled -/// agent (version 7, with multicast support). +/// agent. #[derive(Serialize, Deserialize, JsonSchema)] pub struct InstanceEnsureBody { /// The virtual hardware configuration this virtual machine should have when @@ -56,7 +56,7 @@ pub struct InstanceEnsureBody { } /// Describes sled-local configuration that a sled-agent must establish to make -/// the instance's virtual hardware fully functional (version 7, with multicast). +/// the instance's virtual hardware fully functional. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct InstanceSledLocalConfig { pub hostname: Hostname, @@ -66,25 +66,33 @@ pub struct InstanceSledLocalConfig { /// provided to an instance to allow inbound connectivity. pub ephemeral_ip: Option, pub floating_ips: Vec, - pub multicast_groups: Vec, pub firewall_rules: Vec, pub dhcp_config: DhcpConfig, } -/// Represents a multicast group membership for an instance. -#[derive( - Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, -)] -pub struct InstanceMulticastMembership { - pub group_ip: IpAddr, - // For Source-Specific Multicast (SSM) - pub sources: Vec, -} - -/// Request body for multicast group operations. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum InstanceMulticastBody { - Join(InstanceMulticastMembership), - Leave(InstanceMulticastMembership), +impl From + for sled_agent_types::instance::InstanceEnsureBody +{ + fn from( + v6: InstanceEnsureBody, + ) -> sled_agent_types::instance::InstanceEnsureBody { + sled_agent_types::instance::InstanceEnsureBody { + vmm_spec: v6.vmm_spec, + local_config: sled_agent_types::instance::InstanceSledLocalConfig { + hostname: v6.local_config.hostname, + nics: v6.local_config.nics, + source_nat: v6.local_config.source_nat, + ephemeral_ip: v6.local_config.ephemeral_ip, + floating_ips: v6.local_config.floating_ips, + multicast_groups: Vec::new(), + firewall_rules: v6.local_config.firewall_rules, + dhcp_config: v6.local_config.dhcp_config, + }, + vmm_runtime: v6.vmm_runtime, + instance_id: v6.instance_id, + migration_id: v6.migration_id, + propolis_addr: v6.propolis_addr, + metadata: v6.metadata, + } + } } diff --git a/sled-agent/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index a3c6a0ec92f..64dc41becc0 100644 --- a/sled-agent/src/http_entrypoints.rs +++ b/sled-agent/src/http_entrypoints.rs @@ -32,8 +32,8 @@ use sled_agent_types::disk::DiskEnsureBody; use sled_agent_types::early_networking::EarlyNetworkConfig; use sled_agent_types::firewall_rules::VpcFirewallRulesEnsureBody; use sled_agent_types::instance::{ - InstanceExternalIpBody, VmmPutStateBody, VmmPutStateResponse, - VmmUnregisterResponse, + InstanceEnsureBody, InstanceExternalIpBody, InstanceMulticastBody, + VmmPutStateBody, VmmPutStateResponse, VmmUnregisterResponse, }; use sled_agent_types::probes::ProbeSet; use sled_agent_types::sled::AddSledRequest; @@ -489,29 +489,16 @@ impl SledAgentApi for SledAgentImpl { Ok(HttpResponseOk(sa.get_role())) } - async fn vmm_register_v1( + async fn vmm_register( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result, HttpError> { let sa = rqctx.context(); let propolis_id = path_params.into_inner().propolis_id; let body_args = body.into_inner(); Ok(HttpResponseOk( - sa.instance_ensure_registered_v1(propolis_id, body_args).await?, - )) - } - - async fn vmm_register_v7( - rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError> { - let sa = rqctx.context(); - let propolis_id = path_params.into_inner().propolis_id; - let body_args = body.into_inner(); - Ok(HttpResponseOk( - sa.instance_ensure_registered_v7(propolis_id, body_args).await?, + sa.instance_ensure_registered(propolis_id, body_args).await?, )) } @@ -571,7 +558,7 @@ impl SledAgentApi for SledAgentImpl { async fn vmm_join_multicast_group( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result { let sa = rqctx.context(); let id = path_params.into_inner().propolis_id; @@ -583,7 +570,7 @@ impl SledAgentApi for SledAgentImpl { async fn vmm_leave_multicast_group( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result { let sa = rqctx.context(); let id = path_params.into_inner().propolis_id; diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index 9bb95964804..4f90ac7c4d0 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -37,9 +37,6 @@ use propolis_client::Client as PropolisClient; use propolis_client::instance_spec::{ComponentV0, SpecKey}; use rand::SeedableRng; use rand::prelude::IteratorRandom; -use sled_agent_api::v7::{ - InstanceMulticastMembership, InstanceSledLocalConfig, -}; use sled_agent_config_reconciler::AvailableDatasetsReceiver; use sled_agent_types::instance::*; use sled_agent_types::zone_bundle::ZoneBundleCause; @@ -2491,7 +2488,6 @@ mod tests { use propolis_client::types::{ InstanceMigrateStatusResponse, InstanceStateMonitorResponse, }; - use sled_agent_api::v7::InstanceEnsureBody; use sled_agent_config_reconciler::{ CurrentlyManagedZpoolsReceiver, InternalDiskDetails, InternalDisksReceiver, diff --git a/sled-agent/src/instance_manager.rs b/sled-agent/src/instance_manager.rs index 540b71195a7..e10702a1193 100644 --- a/sled-agent/src/instance_manager.rs +++ b/sled-agent/src/instance_manager.rs @@ -20,7 +20,6 @@ use omicron_common::api::external::ByteCount; use omicron_common::api::internal::nexus::SledVmmState; use omicron_common::api::internal::shared::SledIdentifiers; use omicron_uuid_kinds::PropolisUuid; -use sled_agent_api::v7::{InstanceEnsureBody, InstanceMulticastBody}; use sled_agent_config_reconciler::AvailableDatasetsReceiver; use sled_agent_config_reconciler::CurrentlyManagedZpoolsReceiver; use sled_agent_types::instance::*; diff --git a/sled-agent/src/sim/http_entrypoints.rs b/sled-agent/src/sim/http_entrypoints.rs index 94d0903f7a7..9b7ea9b0f1f 100644 --- a/sled-agent/src/sim/http_entrypoints.rs +++ b/sled-agent/src/sim/http_entrypoints.rs @@ -36,13 +36,14 @@ use omicron_common::api::internal::shared::{ ResolvedVpcRouteSet, ResolvedVpcRouteState, SwitchPorts, }; use range_requests::PotentialRange; -use sled_agent_api::v7::InstanceMulticastBody; use sled_agent_api::*; use sled_agent_types::bootstore::BootstoreStatus; use sled_agent_types::disk::DiskEnsureBody; use sled_agent_types::early_networking::EarlyNetworkConfig; use sled_agent_types::firewall_rules::VpcFirewallRulesEnsureBody; +use sled_agent_types::instance::InstanceEnsureBody; use sled_agent_types::instance::InstanceExternalIpBody; +use sled_agent_types::instance::InstanceMulticastBody; use sled_agent_types::instance::VmmPutStateBody; use sled_agent_types::instance::VmmPutStateResponse; use sled_agent_types::instance::VmmUnregisterResponse; @@ -82,23 +83,10 @@ enum SledAgentSimImpl {} impl SledAgentApi for SledAgentSimImpl { type Context = Arc; - async fn vmm_register_v1( + async fn vmm_register( rqctx: RequestContext, path_params: Path, - body: TypedBody, - ) -> Result, HttpError> { - let sa = rqctx.context(); - let propolis_id = path_params.into_inner().propolis_id; - let body_args = body.into_inner(); - Ok(HttpResponseOk( - sa.instance_register_v1(propolis_id, body_args).await?, - )) - } - - async fn vmm_register_v7( - rqctx: RequestContext, - path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result, HttpError> { let sa = rqctx.context(); let propolis_id = path_params.into_inner().propolis_id; diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index efda700b747..201509bf02c 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -56,14 +56,13 @@ use propolis_client::{ }; use range_requests::PotentialRange; use sled_agent_api::SupportBundleMetadata; -use sled_agent_api::v7::InstanceMulticastMembership; use sled_agent_types::disk::DiskStateRequested; use sled_agent_types::early_networking::{ EarlyNetworkConfig, EarlyNetworkConfigBody, }; use sled_agent_types::instance::{ - InstanceExternalIpBody, VmmPutStateResponse, VmmStateRequested, - VmmUnregisterResponse, + InstanceEnsureBody, InstanceExternalIpBody, InstanceMulticastMembership, + VmmPutStateResponse, VmmStateRequested, VmmUnregisterResponse, }; use slog::Logger; @@ -200,43 +199,12 @@ impl SledAgent { }) } - /// Idempotently ensures that the given API Instance (described by - /// `api_instance`) exists on this server in the given runtime state - /// (described by `target`). - // Keep the v1 method for compatibility but it just delegates to v2 - pub async fn instance_register_v1( - self: &Arc, - propolis_id: PropolisUuid, - instance: sled_agent_types::instance::InstanceEnsureBody, - ) -> Result { - // Convert v1 to v7 for internal processing - let v5_instance = sled_agent_api::v7::InstanceEnsureBody { - vmm_spec: instance.vmm_spec, - local_config: sled_agent_api::v7::InstanceSledLocalConfig { - hostname: instance.local_config.hostname, - nics: instance.local_config.nics, - source_nat: instance.local_config.source_nat, - ephemeral_ip: instance.local_config.ephemeral_ip, - floating_ips: instance.local_config.floating_ips, - multicast_groups: Vec::new(), // v1 doesn't support multicast - firewall_rules: instance.local_config.firewall_rules, - dhcp_config: instance.local_config.dhcp_config, - }, - vmm_runtime: instance.vmm_runtime, - instance_id: instance.instance_id, - migration_id: instance.migration_id, - propolis_addr: instance.propolis_addr, - metadata: instance.metadata, - }; - self.instance_register(propolis_id, v5_instance).await - } - pub async fn instance_register( self: &Arc, propolis_id: PropolisUuid, - instance: sled_agent_api::v7::InstanceEnsureBody, + instance: InstanceEnsureBody, ) -> Result { - let sled_agent_api::v7::InstanceEnsureBody { + let InstanceEnsureBody { vmm_spec, local_config, instance_id, @@ -722,7 +690,7 @@ impl SledAgent { pub async fn instance_join_multicast_group( &self, propolis_id: PropolisUuid, - membership: &sled_agent_api::v7::InstanceMulticastMembership, + membership: &InstanceMulticastMembership, ) -> Result<(), Error> { if !self.vmms.contains_key(&propolis_id.into_untyped_uuid()).await { return Err(Error::internal_error( @@ -741,7 +709,7 @@ impl SledAgent { pub async fn instance_leave_multicast_group( &self, propolis_id: PropolisUuid, - membership: &sled_agent_api::v7::InstanceMulticastMembership, + membership: &InstanceMulticastMembership, ) -> Result<(), Error> { if !self.vmms.contains_key(&propolis_id.into_untyped_uuid()).await { return Err(Error::internal_error( diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index d9a3e7c89b2..e2dbcffc57d 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -54,7 +54,6 @@ use omicron_ddm_admin_client::Client as DdmAdminClient; use omicron_uuid_kinds::{ GenericUuid, MupdateOverrideUuid, PropolisUuid, SledUuid, }; -use sled_agent_api::v7::{InstanceEnsureBody, InstanceMulticastBody}; use sled_agent_config_reconciler::{ ConfigReconcilerHandle, ConfigReconcilerSpawnToken, InternalDisks, InternalDisksReceiver, LedgerNewConfigError, LedgerTaskError, @@ -63,8 +62,8 @@ use sled_agent_config_reconciler::{ use sled_agent_types::disk::DiskStateRequested; use sled_agent_types::early_networking::EarlyNetworkConfig; use sled_agent_types::instance::{ - InstanceExternalIpBody, VmmPutStateResponse, VmmStateRequested, - VmmUnregisterResponse, + InstanceEnsureBody, InstanceExternalIpBody, InstanceMulticastBody, + VmmPutStateResponse, VmmStateRequested, VmmUnregisterResponse, }; use sled_agent_types::probes::ProbeCreate; use sled_agent_types::sled::{BaseboardId, StartSledAgentRequest}; @@ -839,45 +838,7 @@ impl SledAgent { } } - /// Idempotently ensures that a given instance is registered with this sled, - /// i.e., that it can be addressed by future calls to - /// [`Self::instance_ensure_state`]. - pub async fn instance_ensure_registered_v1( - &self, - propolis_id: PropolisUuid, - instance: sled_agent_types::instance::InstanceEnsureBody, - ) -> Result { - // Convert v1 to v7 - let v5_instance = sled_agent_api::v7::InstanceEnsureBody { - vmm_spec: instance.vmm_spec, - local_config: sled_agent_api::v7::InstanceSledLocalConfig { - hostname: instance.local_config.hostname, - nics: instance.local_config.nics, - source_nat: instance.local_config.source_nat, - ephemeral_ip: instance.local_config.ephemeral_ip, - floating_ips: instance.local_config.floating_ips, - multicast_groups: Vec::new(), // v1 doesn't support multicast - firewall_rules: instance.local_config.firewall_rules, - dhcp_config: instance.local_config.dhcp_config, - }, - vmm_runtime: instance.vmm_runtime, - instance_id: instance.instance_id, - migration_id: instance.migration_id, - propolis_addr: instance.propolis_addr, - metadata: instance.metadata, - }; - self.instance_ensure_registered_v7(propolis_id, v5_instance).await - } - - pub async fn instance_ensure_registered_v7( - &self, - propolis_id: PropolisUuid, - instance: InstanceEnsureBody, - ) -> Result { - self.instance_ensure_registered(propolis_id, instance).await - } - - async fn instance_ensure_registered( + pub async fn instance_ensure_registered( &self, propolis_id: PropolisUuid, instance: InstanceEnsureBody, diff --git a/sled-agent/tests/multicast_cross_version_test.rs b/sled-agent/tests/multicast_cross_version_test.rs deleted file mode 100644 index b869e5a76e9..00000000000 --- a/sled-agent/tests/multicast_cross_version_test.rs +++ /dev/null @@ -1,118 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Cross-version compatibility tests for sled-agent multicast APIs. -//! -//! This test verifies that v4 and v5 instance configurations work correctly -//! together, specifically around multicast group support. It follows the same -//! pattern as the DNS cross-version tests. - -use anyhow::Result; -use std::net::IpAddr; - -use omicron_common::api::internal::shared::DhcpConfig; -use sled_agent_api::v7; - -// Generate v5 client from v5 OpenAPI spec (with enhanced multicast support) -mod v5_client { - progenitor::generate_api!( - spec = "../openapi/sled-agent/sled-agent-5.0.0-253577.json", - interface = Positional, - inner_type = slog::Logger, - derives = [schemars::JsonSchema, Clone, Eq, PartialEq], - pre_hook = (|log: &slog::Logger, request: &reqwest::Request| { - slog::debug!(log, "client request"; - "method" => %request.method(), - "uri" => %request.url(), - "body" => ?&request.body(), - ); - }), - post_hook = (|log: &slog::Logger, result: &Result<_, _>| { - slog::debug!(log, "client response"; "result" => ?result); - }) - ); -} - -// A v5 server can productively handle requests from a v4 client, and a v4 -// client can provide instance configurations to a v5 server (backwards compatible). -// This follows the same pattern as DNS cross-version compatibility. -#[tokio::test] -pub async fn multicast_cross_version_works() -> Result<(), anyhow::Error> { - use omicron_test_utils::dev::test_setup_log; - let logctx = test_setup_log("multicast_cross_version_works"); - - let multicast_addr = "239.1.1.1".parse::().unwrap(); - let source_addr = "192.168.1.10".parse::().unwrap(); - - // Focus on the local_config field since that's where multicast_groups lives - - // Create v4 local config JSON (won't have multicast_groups field) - let v4_local_config_json = serde_json::json!({ - "hostname": "test-v4", - "nics": [], - "source_nat": { - "ip": "10.1.1.1", - "first_port": 0, - "last_port": 16383 - }, - "ephemeral_ip": null, - "floating_ips": [], - "firewall_rules": [], - "dhcp_config": { - "dns_servers": [], - "host_domain": null, - "search_domains": [] - } - }); - - // Create v5 local config with multicast_groups - let v5_local_config = v7::InstanceSledLocalConfig { - hostname: omicron_common::api::external::Hostname::try_from("test-v5") - .unwrap(), - nics: vec![], - source_nat: nexus_types::deployment::SourceNatConfig::new( - "10.1.1.1".parse().unwrap(), - 0, - 16383, - ) - .unwrap(), - ephemeral_ip: None, - floating_ips: vec![], - multicast_groups: vec![v7::InstanceMulticastMembership { - group_ip: multicast_addr, - sources: vec![source_addr], - }], - firewall_rules: vec![], - dhcp_config: DhcpConfig { - dns_servers: vec![], - host_domain: None, - search_domains: vec![], - }, - }; - - // Test that v4 can be parsed by v5 (with empty multicast_groups) - let v4_as_v5_json = serde_json::to_string(&v4_local_config_json)?; - let v5_json = serde_json::to_string(&v5_local_config)?; - - // v4 should NOT have multicast_groups in the JSON - assert!( - !v4_as_v5_json.contains("multicast_groups"), - "v4 InstanceSledLocalConfig should not contain multicast_groups field" - ); - - // v5 should HAVE multicast_groups in the JSON - assert!( - v5_json.contains("multicast_groups"), - "v5 InstanceSledLocalConfig should contain multicast_groups field" - ); - - // Verify v5 has the multicast group we added - assert!( - v5_json.contains(&format!("\"group_ip\":\"{multicast_addr}\"")), - "v5 should contain the multicast group IP" - ); - - logctx.cleanup_successful(); - Ok(()) -} diff --git a/sled-agent/types/src/instance.rs b/sled-agent/types/src/instance.rs index b2de8311dbb..a825f9e2690 100644 --- a/sled-agent/types/src/instance.rs +++ b/sled-agent/types/src/instance.rs @@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; /// The body of a request to ensure that a instance and VMM are known to a sled -/// agent. +/// agent (version 7, with multicast support). #[derive(Serialize, Deserialize, JsonSchema)] pub struct InstanceEnsureBody { /// The virtual hardware configuration this virtual machine should have when @@ -58,7 +58,7 @@ pub struct InstanceEnsureBody { } /// Describes sled-local configuration that a sled-agent must establish to make -/// the instance's virtual hardware fully functional. +/// the instance's virtual hardware fully functional (version 7, with multicast). #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct InstanceSledLocalConfig { pub hostname: Hostname, @@ -68,10 +68,29 @@ pub struct InstanceSledLocalConfig { /// provided to an instance to allow inbound connectivity. pub ephemeral_ip: Option, pub floating_ips: Vec, + pub multicast_groups: Vec, pub firewall_rules: Vec, pub dhcp_config: DhcpConfig, } +/// Represents a multicast group membership for an instance. +#[derive( + Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, Hash, +)] +pub struct InstanceMulticastMembership { + pub group_ip: IpAddr, + // For Source-Specific Multicast (SSM) + pub sources: Vec, +} + +/// Request body for multicast group operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum InstanceMulticastBody { + Join(InstanceMulticastMembership), + Leave(InstanceMulticastMembership), +} + /// Metadata used to track statistics about an instance. /// // NOTE: The instance ID is not here, since it's already provided in other