From 8f738bb46e572755051ca8f59baee8b7ace5813a Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 17 Nov 2025 18:31:26 +0000 Subject: [PATCH 1/4] [fix] Follow correct versioning pattern for `vmm_register`/`instance_ensure_registered` My previous multicast-support integration PR flipped outdated/updated versioning. This work fixes that, following proper versioning design. For consistency, we now include prefix version naming conventions (i.e. `v1_`). We also place the v1 versions of `InstanceEnsureBody` and `InstanceSledLocalConfig` in "sled-agent/types/src/v1.rs" instead of in the API directory. --- .../src/test_util/host_phase_2_test_state.rs | 11 ++- sled-agent/api/src/lib.rs | 20 +++--- sled-agent/src/http_entrypoints.rs | 20 +++--- sled-agent/src/instance.rs | 8 +-- sled-agent/src/instance_manager.rs | 2 +- sled-agent/src/sim/http_entrypoints.rs | 12 ++-- sled-agent/src/sim/sled_agent.rs | 59 ++++++++-------- sled-agent/src/sled_agent.rs | 68 +++++++++---------- .../tests/multicast_cross_version_test.rs | 58 ++++++++-------- sled-agent/types/src/instance.rs | 19 ++++++ sled-agent/types/src/lib.rs | 1 + sled-agent/{api/src/v7.rs => types/src/v1.rs} | 29 ++------ 12 files changed, 151 insertions(+), 156 deletions(-) rename sled-agent/{api/src/v7.rs => types/src/v1.rs} (71%) 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..17787b496d8 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,13 @@ 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::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,18 +530,18 @@ mod api_impl { unimplemented!() } - async fn vmm_register_v1( + async fn v1_vmm_register( _rqctx: RequestContext, _path_params: Path, - _body: TypedBody, + _body: TypedBody, ) -> Result, HttpError> { unimplemented!() } - async fn vmm_register_v7( + async fn vmm_register( _rqctx: RequestContext, _path_params: Path, - _body: TypedBody, + _body: TypedBody, ) -> Result, HttpError> { unimplemented!() } diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index d37f406af54..1bd2356d2b6 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, + InstanceExternalIpBody, InstanceMulticastBody, VmmPutStateBody, + VmmPutStateResponse, VmmUnregisterResponse, }, sled::AddSledRequest, zone_bundle::{ @@ -55,8 +55,6 @@ 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; api_versions!([ // WHEN CHANGING THE API (part 1 of 2): @@ -363,9 +361,9 @@ pub trait SledAgentApi { method = PUT, path = "/vmms/{propolis_id}", operation_id = "vmm_register", - versions = VERSION_INITIAL..VERSION_MULTICAST_SUPPORT + versions = VERSION_MULTICAST_SUPPORT.. }] - async fn vmm_register_v1( + async fn vmm_register( rqctx: RequestContext, path_params: Path, body: TypedBody, @@ -375,12 +373,12 @@ pub trait SledAgentApi { method = PUT, path = "/vmms/{propolis_id}", operation_id = "vmm_register", - versions = VERSION_MULTICAST_SUPPORT.. + versions = ..VERSION_MULTICAST_SUPPORT }] - async fn vmm_register_v7( + async fn v1_vmm_register( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result, HttpError>; #[endpoint { @@ -439,7 +437,7 @@ pub trait SledAgentApi { async fn vmm_join_multicast_group( rqctx: RequestContext, path_params: Path, - body: TypedBody, + body: TypedBody, ) -> Result; #[endpoint { @@ -450,7 +448,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/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index a3c6a0ec92f..34d0b091a69 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, + InstanceExternalIpBody, InstanceMulticastBody, VmmPutStateBody, + VmmPutStateResponse, VmmUnregisterResponse, }; use sled_agent_types::probes::ProbeSet; use sled_agent_types::sled::AddSledRequest; @@ -489,29 +489,29 @@ impl SledAgentApi for SledAgentImpl { Ok(HttpResponseOk(sa.get_role())) } - async fn vmm_register_v1( + async fn v1_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?, + sa.v1_instance_ensure_registered(propolis_id, body_args).await?, )) } - async fn vmm_register_v7( + 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_v7(propolis_id, body_args).await?, + sa.instance_ensure_registered(propolis_id, body_args).await?, )) } @@ -571,7 +571,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 +583,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..1a269949cbe 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -37,11 +37,11 @@ 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::instance::{ + InstanceMulticastMembership, InstanceSledLocalConfig, +}; use sled_agent_types::zone_bundle::ZoneBundleCause; use sled_agent_zone_images::ramdisk_file_source; use slog::Logger; @@ -2491,11 +2491,11 @@ mod tests { use propolis_client::types::{ InstanceMigrateStatusResponse, InstanceStateMonitorResponse, }; - use sled_agent_api::v7::InstanceEnsureBody; use sled_agent_config_reconciler::{ CurrentlyManagedZpoolsReceiver, InternalDiskDetails, InternalDisksReceiver, }; + use sled_agent_types::instance::InstanceEnsureBody; use sled_agent_types::zone_bundle::CleanupContext; use sled_storage::config::MountConfig; use std::net::SocketAddrV6; diff --git a/sled-agent/src/instance_manager.rs b/sled-agent/src/instance_manager.rs index 540b71195a7..042db5e97d2 100644 --- a/sled-agent/src/instance_manager.rs +++ b/sled-agent/src/instance_manager.rs @@ -20,10 +20,10 @@ 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::*; +use sled_agent_types::instance::{InstanceEnsureBody, InstanceMulticastBody}; use slog::Logger; use std::collections::BTreeMap; use std::sync::Arc; diff --git a/sled-agent/src/sim/http_entrypoints.rs b/sled-agent/src/sim/http_entrypoints.rs index 94d0903f7a7..fdd7d3208af 100644 --- a/sled-agent/src/sim/http_entrypoints.rs +++ b/sled-agent/src/sim/http_entrypoints.rs @@ -36,13 +36,13 @@ 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::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 +82,23 @@ enum SledAgentSimImpl {} impl SledAgentApi for SledAgentSimImpl { type Context = Arc; - async fn vmm_register_v1( + async fn v1_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_register_v1(propolis_id, body_args).await?, + sa.v1_instance_register(propolis_id, body_args).await?, )) } - async fn vmm_register_v7( + 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; diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index efda700b747..0f3c05420b3 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -56,11 +56,12 @@ 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::InstanceMulticastMembership; +use sled_agent_types::instance::InstanceSledLocalConfig; use sled_agent_types::instance::{ InstanceExternalIpBody, VmmPutStateResponse, VmmStateRequested, VmmUnregisterResponse, @@ -202,41 +203,41 @@ 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( + /// (described by `target`). This delegates to the newest version + /// of [`sled_agent_types::instance::InstanceEnsureBody`]. + pub async fn v1_instance_register( self: &Arc, propolis_id: PropolisUuid, - instance: sled_agent_types::instance::InstanceEnsureBody, + instance: sled_agent_types::v1::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 + let upgraded_instance = + sled_agent_types::instance::InstanceEnsureBody { + vmm_spec: instance.vmm_spec, + local_config: 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(), + 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, upgraded_instance).await } pub async fn instance_register( self: &Arc, propolis_id: PropolisUuid, - instance: sled_agent_api::v7::InstanceEnsureBody, + instance: sled_agent_types::instance::InstanceEnsureBody, ) -> Result { - let sled_agent_api::v7::InstanceEnsureBody { + let sled_agent_types::instance::InstanceEnsureBody { vmm_spec, local_config, instance_id, @@ -722,7 +723,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 +742,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..75c88d88528 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, @@ -62,6 +61,8 @@ use sled_agent_config_reconciler::{ }; use sled_agent_types::disk::DiskStateRequested; use sled_agent_types::early_networking::EarlyNetworkConfig; +use sled_agent_types::instance::InstanceSledLocalConfig; +use sled_agent_types::instance::{InstanceEnsureBody, InstanceMulticastBody}; use sled_agent_types::instance::{ InstanceExternalIpBody, VmmPutStateResponse, VmmStateRequested, VmmUnregisterResponse, @@ -842,42 +843,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, @@ -889,6 +855,34 @@ impl SledAgent { .map_err(|e| Error::Instance(e)) } + /// V1 compatibility shim for instance registration (before multicast support). + pub async fn v1_instance_ensure_registered( + &self, + propolis_id: PropolisUuid, + instance: sled_agent_types::v1::InstanceEnsureBody, + ) -> Result { + let upgraded_instance = + sled_agent_types::instance::InstanceEnsureBody { + vmm_spec: instance.vmm_spec, + local_config: 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(), + 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(propolis_id, upgraded_instance).await + } + /// Idempotently ensures that the specified instance is no longer registered /// on this sled. /// diff --git a/sled-agent/tests/multicast_cross_version_test.rs b/sled-agent/tests/multicast_cross_version_test.rs index b869e5a76e9..25fa732491f 100644 --- a/sled-agent/tests/multicast_cross_version_test.rs +++ b/sled-agent/tests/multicast_cross_version_test.rs @@ -4,20 +4,22 @@ //! 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. +//! This test verifies that v6 and v7 instance configurations work correctly +//! together, specifically around multicast 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; +use sled_agent_types::instance::{ + InstanceMulticastMembership, InstanceSledLocalConfig, +}; -// Generate v5 client from v5 OpenAPI spec (with enhanced multicast support) -mod v5_client { +// Generate v6 client from v6 OpenAPI spec (before multicast support) +mod v6_client { progenitor::generate_api!( - spec = "../openapi/sled-agent/sled-agent-5.0.0-253577.json", + spec = "../openapi/sled-agent/sled-agent-6.0.0-d37dd7.json", interface = Positional, inner_type = slog::Logger, derives = [schemars::JsonSchema, Clone, Eq, PartialEq], @@ -34,8 +36,8 @@ mod v5_client { ); } -// 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). +// A v7 server can productively handle requests from a v6 client, and a v6 +// client can provide instance configurations to a v7 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> { @@ -47,9 +49,9 @@ pub async fn multicast_cross_version_works() -> Result<(), anyhow::Error> { // 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", + // Create v6 local config JSON (won't have multicast_groups field) + let v6_local_config_json = serde_json::json!({ + "hostname": "test-v6", "nics": [], "source_nat": { "ip": "10.1.1.1", @@ -66,9 +68,9 @@ pub async fn multicast_cross_version_works() -> Result<(), anyhow::Error> { } }); - // Create v5 local config with multicast_groups - let v5_local_config = v7::InstanceSledLocalConfig { - hostname: omicron_common::api::external::Hostname::try_from("test-v5") + // Create v7 local config with multicast_groups + let v7_local_config = InstanceSledLocalConfig { + hostname: omicron_common::api::external::Hostname::try_from("test-v7") .unwrap(), nics: vec![], source_nat: nexus_types::deployment::SourceNatConfig::new( @@ -79,7 +81,7 @@ pub async fn multicast_cross_version_works() -> Result<(), anyhow::Error> { .unwrap(), ephemeral_ip: None, floating_ips: vec![], - multicast_groups: vec![v7::InstanceMulticastMembership { + multicast_groups: vec![InstanceMulticastMembership { group_ip: multicast_addr, sources: vec![source_addr], }], @@ -91,26 +93,26 @@ pub async fn multicast_cross_version_works() -> Result<(), anyhow::Error> { }, }; - // 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)?; + // Test that v6 can be parsed by v7 (with empty multicast_groups) + let v6_as_v7_json = serde_json::to_string(&v6_local_config_json)?; + let v7_json = serde_json::to_string(&v7_local_config)?; - // v4 should NOT have multicast_groups in the JSON + // v6 should NOT have multicast_groups in the JSON assert!( - !v4_as_v5_json.contains("multicast_groups"), - "v4 InstanceSledLocalConfig should not contain multicast_groups field" + !v6_as_v7_json.contains("multicast_groups"), + "v6 InstanceSledLocalConfig should not contain multicast_groups field" ); - // v5 should HAVE multicast_groups in the JSON + // v7 should HAVE multicast_groups in the JSON assert!( - v5_json.contains("multicast_groups"), - "v5 InstanceSledLocalConfig should contain multicast_groups field" + v7_json.contains("multicast_groups"), + "v7 InstanceSledLocalConfig should contain multicast_groups field" ); - // Verify v5 has the multicast group we added + // Verify v7 has the multicast group we added assert!( - v5_json.contains(&format!("\"group_ip\":\"{multicast_addr}\"")), - "v5 should contain the multicast group IP" + v7_json.contains(&format!("\"group_ip\":\"{multicast_addr}\"")), + "v7 should contain the multicast group IP" ); logctx.cleanup_successful(); diff --git a/sled-agent/types/src/instance.rs b/sled-agent/types/src/instance.rs index b2de8311dbb..b4acb39ce5d 100644 --- a/sled-agent/types/src/instance.rs +++ b/sled-agent/types/src/instance.rs @@ -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 diff --git a/sled-agent/types/src/lib.rs b/sled-agent/types/src/lib.rs index 90679b09661..31c6a944e55 100644 --- a/sled-agent/types/src/lib.rs +++ b/sled-agent/types/src/lib.rs @@ -15,5 +15,6 @@ pub mod rack_init; pub mod rack_ops; pub mod sled; pub mod support_bundle; +pub mod v1; pub mod zone_bundle; pub mod zone_images; diff --git a/sled-agent/api/src/v7.rs b/sled-agent/types/src/v1.rs similarity index 71% rename from sled-agent/api/src/v7.rs rename to sled-agent/types/src/v1.rs index 4e096e5415f..c6a31bb687b 100644 --- a/sled-agent/api/src/v7.rs +++ b/sled-agent/types/src/v1.rs @@ -2,9 +2,9 @@ // 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/. -//! Sled agent API types (version 7) +//! Sled agent types (version 1) //! -//! Version 7 adds support for multicast group management on instances. +//! Version 1 types (before multicast support was added). use std::net::{IpAddr, SocketAddr}; @@ -23,10 +23,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use sled_agent_types::instance::{InstanceMetadata, VmmSpec}; +use crate::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 (version 1, before multicast support). #[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 (version 1, before multicast). #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct InstanceSledLocalConfig { pub hostname: Hostname, @@ -66,25 +66,6 @@ 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), -} From d434b638978826e904e02eaa932efc71211b3868 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 17 Nov 2025 19:39:42 +0000 Subject: [PATCH 2/4] [review] v1->v6 (pre mcast version) + default conversion We move the v6 (pre-multicast version) of code back to `sled-agent/api/src`, but implement the default conversion / shim at the endpoint. --- .../src/test_util/host_phase_2_test_state.rs | 8 ---- sled-agent/api/src/lib.rs | 39 +++++++++++++++--- sled-agent/{types/src/v1.rs => api/src/v6.rs} | 11 +++-- sled-agent/src/http_entrypoints.rs | 13 ------ sled-agent/src/sim/http_entrypoints.rs | 13 ------ sled-agent/src/sim/sled_agent.rs | 41 ++----------------- sled-agent/src/sled_agent.rs | 34 +-------------- sled-agent/types/src/lib.rs | 1 - 8 files changed, 45 insertions(+), 115 deletions(-) rename sled-agent/{types/src/v1.rs => api/src/v6.rs} (89%) 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 17787b496d8..54409f6a805 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 @@ -530,14 +530,6 @@ mod api_impl { unimplemented!() } - async fn v1_vmm_register( - _rqctx: RequestContext, - _path_params: Path, - _body: TypedBody, - ) -> Result, HttpError> { - unimplemented!() - } - async fn vmm_register( _rqctx: RequestContext, _path_params: Path, diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index 1bd2356d2b6..be4352bbb7e 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, InstanceMulticastBody, VmmPutStateBody, - VmmPutStateResponse, VmmUnregisterResponse, + InstanceExternalIpBody, InstanceMulticastBody, InstanceSledLocalConfig, + VmmPutStateBody, VmmPutStateResponse, VmmUnregisterResponse, }, sled::AddSledRequest, zone_bundle::{ @@ -55,6 +55,8 @@ use uuid::Uuid; /// Copies of data types that changed between v3 and v4. mod v3; +/// Copies of data types that changed between v6 and v7. +mod v6; api_versions!([ // WHEN CHANGING THE API (part 1 of 2): @@ -375,11 +377,38 @@ pub trait SledAgentApi { operation_id = "vmm_register", versions = ..VERSION_MULTICAST_SUPPORT }] - async fn v1_vmm_register( + async fn v6_vmm_register( rqctx: RequestContext, path_params: Path, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError> { + // Convert v6 to v7 by adding empty multicast_groups + Self::vmm_register( + rqctx, + path_params, + body.map(|v6_body| { + sled_agent_types::instance::InstanceEnsureBody { + vmm_spec: v6_body.vmm_spec, + local_config: InstanceSledLocalConfig { + hostname: v6_body.local_config.hostname, + nics: v6_body.local_config.nics, + source_nat: v6_body.local_config.source_nat, + ephemeral_ip: v6_body.local_config.ephemeral_ip, + floating_ips: v6_body.local_config.floating_ips, + multicast_groups: Vec::new(), + firewall_rules: v6_body.local_config.firewall_rules, + dhcp_config: v6_body.local_config.dhcp_config, + }, + vmm_runtime: v6_body.vmm_runtime, + instance_id: v6_body.instance_id, + migration_id: v6_body.migration_id, + propolis_addr: v6_body.propolis_addr, + metadata: v6_body.metadata, + } + }), + ) + .await + } #[endpoint { method = DELETE, diff --git a/sled-agent/types/src/v1.rs b/sled-agent/api/src/v6.rs similarity index 89% rename from sled-agent/types/src/v1.rs rename to sled-agent/api/src/v6.rs index c6a31bb687b..73f418c3d14 100644 --- a/sled-agent/types/src/v1.rs +++ b/sled-agent/api/src/v6.rs @@ -2,9 +2,9 @@ // 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/. -//! Sled agent types (version 1) +//! Sled agent types (version 6) //! -//! Version 1 types (before multicast support was added). +//! Version 6 types (before multicast support was added in version 7). use std::net::{IpAddr, SocketAddr}; @@ -21,12 +21,11 @@ use omicron_common::api::{ use omicron_uuid_kinds::InstanceUuid; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use sled_agent_types::instance::{InstanceMetadata, VmmSpec}; use uuid::Uuid; -use crate::instance::{InstanceMetadata, VmmSpec}; - /// The body of a request to ensure that a instance and VMM are known to a sled -/// agent (version 1, before multicast support). +/// agent (version 6, before multicast support). #[derive(Serialize, Deserialize, JsonSchema)] pub struct InstanceEnsureBody { /// The virtual hardware configuration this virtual machine should have when @@ -56,7 +55,7 @@ pub struct InstanceEnsureBody { } /// Describes sled-local configuration that a sled-agent must establish to make -/// the instance's virtual hardware fully functional (version 1, before multicast). +/// the instance's virtual hardware fully functional (version 6, before multicast). #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub struct InstanceSledLocalConfig { pub hostname: Hostname, diff --git a/sled-agent/src/http_entrypoints.rs b/sled-agent/src/http_entrypoints.rs index 34d0b091a69..e772c8e90c7 100644 --- a/sled-agent/src/http_entrypoints.rs +++ b/sled-agent/src/http_entrypoints.rs @@ -489,19 +489,6 @@ impl SledAgentApi for SledAgentImpl { Ok(HttpResponseOk(sa.get_role())) } - async fn v1_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.v1_instance_ensure_registered(propolis_id, body_args).await?, - )) - } - async fn vmm_register( rqctx: RequestContext, path_params: Path, diff --git a/sled-agent/src/sim/http_entrypoints.rs b/sled-agent/src/sim/http_entrypoints.rs index fdd7d3208af..bcb729afba4 100644 --- a/sled-agent/src/sim/http_entrypoints.rs +++ b/sled-agent/src/sim/http_entrypoints.rs @@ -82,19 +82,6 @@ enum SledAgentSimImpl {} impl SledAgentApi for SledAgentSimImpl { type Context = Arc; - async fn v1_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.v1_instance_register(propolis_id, body_args).await?, - )) - } - async fn vmm_register( rqctx: RequestContext, path_params: Path, diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index 0f3c05420b3..201509bf02c 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -60,11 +60,9 @@ use sled_agent_types::disk::DiskStateRequested; use sled_agent_types::early_networking::{ EarlyNetworkConfig, EarlyNetworkConfigBody, }; -use sled_agent_types::instance::InstanceMulticastMembership; -use sled_agent_types::instance::InstanceSledLocalConfig; use sled_agent_types::instance::{ - InstanceExternalIpBody, VmmPutStateResponse, VmmStateRequested, - VmmUnregisterResponse, + InstanceEnsureBody, InstanceExternalIpBody, InstanceMulticastMembership, + VmmPutStateResponse, VmmStateRequested, VmmUnregisterResponse, }; use slog::Logger; @@ -201,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`). This delegates to the newest version - /// of [`sled_agent_types::instance::InstanceEnsureBody`]. - pub async fn v1_instance_register( - self: &Arc, - propolis_id: PropolisUuid, - instance: sled_agent_types::v1::InstanceEnsureBody, - ) -> Result { - let upgraded_instance = - sled_agent_types::instance::InstanceEnsureBody { - vmm_spec: instance.vmm_spec, - local_config: 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(), - 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, upgraded_instance).await - } - pub async fn instance_register( self: &Arc, propolis_id: PropolisUuid, - instance: sled_agent_types::instance::InstanceEnsureBody, + instance: InstanceEnsureBody, ) -> Result { - let sled_agent_types::instance::InstanceEnsureBody { + let InstanceEnsureBody { vmm_spec, local_config, instance_id, diff --git a/sled-agent/src/sled_agent.rs b/sled-agent/src/sled_agent.rs index 75c88d88528..94ea67f271d 100644 --- a/sled-agent/src/sled_agent.rs +++ b/sled-agent/src/sled_agent.rs @@ -61,11 +61,9 @@ use sled_agent_config_reconciler::{ }; use sled_agent_types::disk::DiskStateRequested; use sled_agent_types::early_networking::EarlyNetworkConfig; -use sled_agent_types::instance::InstanceSledLocalConfig; -use sled_agent_types::instance::{InstanceEnsureBody, InstanceMulticastBody}; 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}; @@ -855,34 +853,6 @@ impl SledAgent { .map_err(|e| Error::Instance(e)) } - /// V1 compatibility shim for instance registration (before multicast support). - pub async fn v1_instance_ensure_registered( - &self, - propolis_id: PropolisUuid, - instance: sled_agent_types::v1::InstanceEnsureBody, - ) -> Result { - let upgraded_instance = - sled_agent_types::instance::InstanceEnsureBody { - vmm_spec: instance.vmm_spec, - local_config: 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(), - 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(propolis_id, upgraded_instance).await - } - /// Idempotently ensures that the specified instance is no longer registered /// on this sled. /// diff --git a/sled-agent/types/src/lib.rs b/sled-agent/types/src/lib.rs index 31c6a944e55..90679b09661 100644 --- a/sled-agent/types/src/lib.rs +++ b/sled-agent/types/src/lib.rs @@ -15,6 +15,5 @@ pub mod rack_init; pub mod rack_ops; pub mod sled; pub mod support_bundle; -pub mod v1; pub mod zone_bundle; pub mod zone_images; From 72f60ae0591542e8735c9fbd61e12d15401c7725 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 17 Nov 2025 19:47:34 +0000 Subject: [PATCH 3/4] [cleanup] .. --- sled-agent/api/src/lib.rs | 46 +++++++++++++++++++++++++++----------- sled-agent/src/instance.rs | 3 --- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index be4352bbb7e..f9cd306f2ac 100644 --- a/sled-agent/api/src/lib.rs +++ b/sled-agent/api/src/lib.rs @@ -387,23 +387,43 @@ pub trait SledAgentApi { rqctx, path_params, body.map(|v6_body| { + let v6::InstanceEnsureBody { + vmm_spec, + local_config, + vmm_runtime, + instance_id, + migration_id, + propolis_addr, + metadata, + } = v6_body; + + let v6::InstanceSledLocalConfig { + hostname, + nics, + source_nat, + ephemeral_ip, + floating_ips, + firewall_rules, + dhcp_config, + } = local_config; + sled_agent_types::instance::InstanceEnsureBody { - vmm_spec: v6_body.vmm_spec, + vmm_spec, local_config: InstanceSledLocalConfig { - hostname: v6_body.local_config.hostname, - nics: v6_body.local_config.nics, - source_nat: v6_body.local_config.source_nat, - ephemeral_ip: v6_body.local_config.ephemeral_ip, - floating_ips: v6_body.local_config.floating_ips, + hostname, + nics, + source_nat, + ephemeral_ip, + floating_ips, multicast_groups: Vec::new(), - firewall_rules: v6_body.local_config.firewall_rules, - dhcp_config: v6_body.local_config.dhcp_config, + firewall_rules, + dhcp_config, }, - vmm_runtime: v6_body.vmm_runtime, - instance_id: v6_body.instance_id, - migration_id: v6_body.migration_id, - propolis_addr: v6_body.propolis_addr, - metadata: v6_body.metadata, + vmm_runtime, + instance_id, + migration_id, + propolis_addr, + metadata, } }), ) diff --git a/sled-agent/src/instance.rs b/sled-agent/src/instance.rs index 1a269949cbe..be4800c9b21 100644 --- a/sled-agent/src/instance.rs +++ b/sled-agent/src/instance.rs @@ -39,9 +39,6 @@ use rand::SeedableRng; use rand::prelude::IteratorRandom; use sled_agent_config_reconciler::AvailableDatasetsReceiver; use sled_agent_types::instance::*; -use sled_agent_types::instance::{ - InstanceMulticastMembership, InstanceSledLocalConfig, -}; use sled_agent_types::zone_bundle::ZoneBundleCause; use sled_agent_zone_images::ramdisk_file_source; use slog::Logger; From 600aac4d442f801226de82f8ade9728bb8dad605 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Mon, 17 Nov 2025 19:58:30 +0000 Subject: [PATCH 4/4] [review] updates/cleanup --- sled-agent/api/src/lib.rs | 51 +------- sled-agent/api/src/v6.rs | 53 ++++++++ .../tests/multicast_cross_version_test.rs | 120 ------------------ 3 files changed, 56 insertions(+), 168 deletions(-) delete mode 100644 sled-agent/tests/multicast_cross_version_test.rs diff --git a/sled-agent/api/src/lib.rs b/sled-agent/api/src/lib.rs index f9cd306f2ac..2465e504fd1 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, InstanceMulticastBody, InstanceSledLocalConfig, - VmmPutStateBody, VmmPutStateResponse, VmmUnregisterResponse, + InstanceExternalIpBody, InstanceMulticastBody, VmmPutStateBody, + VmmPutStateResponse, VmmUnregisterResponse, }, sled::AddSledRequest, zone_bundle::{ @@ -382,52 +382,7 @@ pub trait SledAgentApi { path_params: Path, body: TypedBody, ) -> Result, HttpError> { - // Convert v6 to v7 by adding empty multicast_groups - Self::vmm_register( - rqctx, - path_params, - body.map(|v6_body| { - let v6::InstanceEnsureBody { - vmm_spec, - local_config, - vmm_runtime, - instance_id, - migration_id, - propolis_addr, - metadata, - } = v6_body; - - let v6::InstanceSledLocalConfig { - hostname, - nics, - source_nat, - ephemeral_ip, - floating_ips, - firewall_rules, - dhcp_config, - } = local_config; - - sled_agent_types::instance::InstanceEnsureBody { - vmm_spec, - local_config: InstanceSledLocalConfig { - hostname, - nics, - source_nat, - ephemeral_ip, - floating_ips, - multicast_groups: Vec::new(), - firewall_rules, - dhcp_config, - }, - vmm_runtime, - instance_id, - migration_id, - propolis_addr, - metadata, - } - }), - ) - .await + Self::vmm_register(rqctx, path_params, body.map(Into::into)).await } #[endpoint { diff --git a/sled-agent/api/src/v6.rs b/sled-agent/api/src/v6.rs index 73f418c3d14..41adf5a464a 100644 --- a/sled-agent/api/src/v6.rs +++ b/sled-agent/api/src/v6.rs @@ -68,3 +68,56 @@ pub struct InstanceSledLocalConfig { pub firewall_rules: Vec, pub dhcp_config: DhcpConfig, } + +impl From + for sled_agent_types::instance::InstanceSledLocalConfig +{ + fn from(v6: InstanceSledLocalConfig) -> Self { + let InstanceSledLocalConfig { + hostname, + nics, + source_nat, + ephemeral_ip, + floating_ips, + firewall_rules, + dhcp_config, + } = v6; + + Self { + hostname, + nics, + source_nat, + ephemeral_ip, + floating_ips, + multicast_groups: Vec::new(), + firewall_rules, + dhcp_config, + } + } +} + +impl From + for sled_agent_types::instance::InstanceEnsureBody +{ + fn from(v6: InstanceEnsureBody) -> Self { + let InstanceEnsureBody { + vmm_spec, + local_config, + vmm_runtime, + instance_id, + migration_id, + propolis_addr, + metadata, + } = v6; + + Self { + vmm_spec, + local_config: local_config.into(), + vmm_runtime, + instance_id, + migration_id, + propolis_addr, + metadata, + } + } +} 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 25fa732491f..00000000000 --- a/sled-agent/tests/multicast_cross_version_test.rs +++ /dev/null @@ -1,120 +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 v6 and v7 instance configurations work correctly -//! together, specifically around multicast 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_types::instance::{ - InstanceMulticastMembership, InstanceSledLocalConfig, -}; - -// Generate v6 client from v6 OpenAPI spec (before multicast support) -mod v6_client { - progenitor::generate_api!( - spec = "../openapi/sled-agent/sled-agent-6.0.0-d37dd7.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 v7 server can productively handle requests from a v6 client, and a v6 -// client can provide instance configurations to a v7 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 v6 local config JSON (won't have multicast_groups field) - let v6_local_config_json = serde_json::json!({ - "hostname": "test-v6", - "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 v7 local config with multicast_groups - let v7_local_config = InstanceSledLocalConfig { - hostname: omicron_common::api::external::Hostname::try_from("test-v7") - .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![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 v6 can be parsed by v7 (with empty multicast_groups) - let v6_as_v7_json = serde_json::to_string(&v6_local_config_json)?; - let v7_json = serde_json::to_string(&v7_local_config)?; - - // v6 should NOT have multicast_groups in the JSON - assert!( - !v6_as_v7_json.contains("multicast_groups"), - "v6 InstanceSledLocalConfig should not contain multicast_groups field" - ); - - // v7 should HAVE multicast_groups in the JSON - assert!( - v7_json.contains("multicast_groups"), - "v7 InstanceSledLocalConfig should contain multicast_groups field" - ); - - // Verify v7 has the multicast group we added - assert!( - v7_json.contains(&format!("\"group_ip\":\"{multicast_addr}\"")), - "v7 should contain the multicast group IP" - ); - - logctx.cleanup_successful(); - Ok(()) -}