diff --git a/nexus/db-model/src/multicast_group.rs b/nexus/db-model/src/multicast_group.rs index 89094e430ba..380039f3434 100644 --- a/nexus/db-model/src/multicast_group.rs +++ b/nexus/db-model/src/multicast_group.rs @@ -178,28 +178,6 @@ pub struct ExternalMulticastGroup { pub vni: Vni, /// Primary multicast IP address (overlay/external). pub multicast_ip: IpNetwork, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// - /// When specified, this VLAN ID is passed to switches (via DPD) as part of - /// the `ExternalForwarding` configuration to tag multicast packets leaving - /// the rack. This enables multicast traffic to traverse VLAN-segmented - /// upstream networks (e.g., peering with external multicast sources/receivers - /// on specific VLANs). - /// - /// The MVLAN value is sent to switches during group creation/updates and - /// controls VLAN tagging for egress traffic only; it does not affect ingress - /// multicast traffic received by the rack. Switch port selection for egress - /// traffic remains pending (see TODOs in `nexus/src/app/multicast/dataplane.rs`). - /// - /// Valid range when specified: 2-4094 (IEEE 802.1Q; Dendrite requires >= 2). - /// - /// Database Type: i16 (INT2) - this field uses `i16` (INT2) for storage - /// efficiency, unlike other VLAN columns in the schema which use `SqlU16` - /// (forcing INT4). Direct `i16` is appropriate here since VLANs fit in - /// INT2's range. - /// - /// TODO(multicast): Remove mvlan field - being deprecated from multicast groups - pub mvlan: Option, /// Associated underlay group for NAT. /// Initially None in ["Creating"](MulticastGroupState::Creating) state, /// populated by reconciler when group becomes ["Active"](MulticastGroupState::Active). @@ -339,7 +317,6 @@ pub struct IncompleteExternalMulticastGroup { pub ip_pool_id: Uuid, /// Optional address requesting a specific multicast IP be allocated. pub explicit_address: Option, - pub mvlan: Option, pub vni: Vni, } @@ -351,7 +328,6 @@ pub struct IncompleteExternalMulticastGroupParams { pub description: String, pub ip_pool_id: Uuid, pub explicit_address: Option, - pub mvlan: Option, pub vni: Vni, } @@ -365,7 +341,6 @@ impl IncompleteExternalMulticastGroup { time_created: Utc::now(), ip_pool_id: params.ip_pool_id, explicit_address: params.explicit_address.map(|ip| ip.into()), - mvlan: params.mvlan, vni: params.vni, } } diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 28614c75bdf..d139c25d404 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock}; /// /// This must be updated when you change the database schema. Refer to /// schema/crdb/README.adoc in the root of this repository for details. -pub const SCHEMA_VERSION: Version = Version::new(214, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(215, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock> = LazyLock::new(|| { // | leaving the first copy as an example for the next person. // v // KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"), + KnownVersion::new(215, "multicast-drop-mvlan"), KnownVersion::new(214, "multicast-implicit-lifecycle"), KnownVersion::new(213, "fm-cases"), KnownVersion::new(212, "local-storage-disk-type"), diff --git a/nexus/db-queries/src/db/datastore/multicast/groups.rs b/nexus/db-queries/src/db/datastore/multicast/groups.rs index 61410182b35..7cccdd3ed3c 100644 --- a/nexus/db-queries/src/db/datastore/multicast/groups.rs +++ b/nexus/db-queries/src/db/datastore/multicast/groups.rs @@ -38,7 +38,6 @@ use omicron_common::api::external::{ IdentityMetadataCreateParams, ListResultVec, LookupResult, LookupType, ResourceType, UpdateResult, }; -use omicron_common::vlan::VlanID; use omicron_uuid_kinds::{GenericUuid, MulticastGroupUuid}; use super::EnsureUnderlayResult; @@ -57,41 +56,23 @@ use crate::db::update_and_check::{UpdateAndCheck, UpdateStatus}; /// External multicast group with computed source IPs from members. /// /// The `source_ips` field contains the union of all member source IPs, -/// computed via a separate query. This struct enables a clean `TryFrom` +/// computed via a separate query. This struct enables a clean `From` /// conversion to the API view. -/// -// TODO(multicast): Remove mvlan field, being deprecated from multicast groups #[derive(Clone, Debug)] pub struct ExternalMulticastGroupWithSources { pub group: ExternalMulticastGroup, pub source_ips: Vec, } -impl TryFrom for views::MulticastGroup { - type Error = external::Error; - - fn try_from( - value: ExternalMulticastGroupWithSources, - ) -> Result { - let mvlan = value - .group - .mvlan - .map(|vlan| VlanID::new(vlan as u16)) - .transpose() - .map_err(|e| { - external::Error::internal_error(&format!( - "invalid VLAN ID: {e:#}" - )) - })?; - - Ok(views::MulticastGroup { +impl From for views::MulticastGroup { + fn from(value: ExternalMulticastGroupWithSources) -> Self { + views::MulticastGroup { identity: value.group.identity(), multicast_ip: value.group.multicast_ip.ip(), source_ips: value.source_ips, - mvlan, ip_pool_id: value.group.ip_pool_id, state: value.group.state.to_string(), - }) + } } } @@ -104,7 +85,6 @@ pub(crate) struct MulticastGroupAllocationParams { pub identity: IdentityMetadataCreateParams, pub ip: Option, pub pool: Option, - pub mvlan: Option, /// Derived for whether the joining member has source IPs. /// Used for default pool selection -> if true, prefer SSM pool first. pub has_sources: bool, @@ -253,7 +233,6 @@ impl DataStore { identity: params.identity.clone(), ip: params.multicast_ip, pool: authz_pool, - mvlan: params.mvlan, has_sources: params.has_sources, }, ) @@ -573,7 +552,6 @@ impl DataStore { description: params.identity.description.clone(), ip_pool_id: authz_pool.id(), explicit_address: params.ip, - mvlan: params.mvlan.map(|vlan_id| u16::from(vlan_id) as i16), vni, }, ); @@ -956,7 +934,6 @@ mod tests { description: "First group".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; datastore @@ -971,7 +948,6 @@ mod tests { description: "Second group".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; datastore @@ -986,7 +962,6 @@ mod tests { description: "Should fail".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; let res3 = datastore @@ -1059,7 +1034,6 @@ mod tests { description: "Group using default pool".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; @@ -1084,7 +1058,6 @@ mod tests { description: "Group with explicit pool".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; let group_explicit = datastore @@ -1211,7 +1184,6 @@ mod tests { description: "Comprehensive test group".to_string(), }, multicast_ip: Some("224.1.3.3".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -1315,7 +1287,6 @@ mod tests { description: "Group for parent_id testing".to_string(), }, multicast_ip: Some("224.3.1.5".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -1785,7 +1756,6 @@ mod tests { description: "Group for duplicate testing".to_string(), }, multicast_ip: Some("224.3.1.5".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -1918,7 +1888,6 @@ mod tests { .to_string(), }, multicast_ip: None, // Let it allocate from pool - mvlan: None, has_sources: false, }; let group = datastore @@ -2131,7 +2100,6 @@ mod tests { description: "Group for IP reuse test".to_string(), }, multicast_ip: Some(target_ip), - mvlan: None, has_sources: false, }; @@ -2158,7 +2126,6 @@ mod tests { description: "Second group reusing same IP".to_string(), }, multicast_ip: Some(target_ip), - mvlan: None, has_sources: false, }; @@ -2243,7 +2210,6 @@ mod tests { description: "First group to exhaust pool".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; @@ -2260,7 +2226,6 @@ mod tests { description: "Second group should fail".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; @@ -2290,7 +2255,6 @@ mod tests { .to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; @@ -2376,7 +2340,6 @@ mod tests { description: "Group for deallocation testing".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; @@ -2500,8 +2463,7 @@ mod tests { description: "Test group for fetch operations".to_string(), }, multicast_ip: Some("224.100.10.5".parse().unwrap()), - mvlan: None, - has_sources: false, + has_sources: true, }; let group = datastore @@ -2606,7 +2568,6 @@ mod tests { description: "Fleet-wide group 1".to_string(), }, multicast_ip: Some("224.100.20.10".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -2616,7 +2577,6 @@ mod tests { description: "Fleet-wide group 2".to_string(), }, multicast_ip: Some("224.100.20.11".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -2626,7 +2586,6 @@ mod tests { description: "Fleet-wide group 3".to_string(), }, multicast_ip: Some("224.100.20.12".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -2734,7 +2693,6 @@ mod tests { description: "Test group for state transitions".to_string(), }, multicast_ip: Some("224.100.30.5".parse().unwrap()), - mvlan: None, has_sources: false, }; @@ -3073,8 +3031,7 @@ mod tests { description: "Group using ASM pool".to_string(), }, multicast_ip: None, // No explicit IP - triggers pool auto-selection - mvlan: None, - has_sources: false, + has_sources: true, // Has sources }; // This should succeed via ASM pool (no SSM pool exists) @@ -3163,7 +3120,6 @@ mod tests { description: "Should fall back to ASM when no SSM".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: true, }; let fallback_group = datastore @@ -3235,7 +3191,6 @@ mod tests { description: "Should prefer SSM over ASM".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: true, }; let ssm_group = datastore @@ -3258,7 +3213,6 @@ mod tests { description: "has_sources=false should use ASM".to_string(), }, multicast_ip: None, - mvlan: None, has_sources: false, }; let asm_group = datastore @@ -3392,7 +3346,6 @@ mod tests { .to_string(), }, multicast_ip: None, - mvlan: None, has_sources: true, }; let group = datastore @@ -3472,7 +3425,6 @@ mod tests { description: "First group for collision test".to_string(), }, multicast_ip: Some("224.10.1.1".parse().unwrap()), - mvlan: None, has_sources: false, }; let external_group1 = datastore @@ -3487,7 +3439,6 @@ mod tests { description: "Second group for collision test".to_string(), }, multicast_ip: Some("224.10.1.2".parse().unwrap()), - mvlan: None, has_sources: false, }; let external_group2 = datastore @@ -3610,7 +3561,6 @@ mod tests { description: "Group for salt testing".to_string(), }, multicast_ip: Some("224.20.1.1".parse().unwrap()), - mvlan: None, has_sources: false, }; let external_group = datastore diff --git a/nexus/db-queries/src/db/datastore/multicast/members.rs b/nexus/db-queries/src/db/datastore/multicast/members.rs index ce9a2be5666..ef3de42cd9d 100644 --- a/nexus/db-queries/src/db/datastore/multicast/members.rs +++ b/nexus/db-queries/src/db/datastore/multicast/members.rs @@ -880,7 +880,6 @@ mod tests { }, multicast_ip: Some("224.10.1.6".parse().unwrap()), // Pool resolved via authz_pool argument to datastore call - mvlan: None, has_sources: false, }; diff --git a/nexus/db-queries/src/db/pub_test_utils/multicast.rs b/nexus/db-queries/src/db/pub_test_utils/multicast.rs index 51fa7d35d75..23f6fc0277a 100644 --- a/nexus/db-queries/src/db/pub_test_utils/multicast.rs +++ b/nexus/db-queries/src/db/pub_test_utils/multicast.rs @@ -193,7 +193,6 @@ pub async fn create_test_group_with_state( description: format!("Test group: {group_name}"), }, multicast_ip: Some(multicast_ip.parse().unwrap()), - mvlan: None, has_sources: false, }; diff --git a/nexus/db-queries/src/db/queries/external_multicast_group.rs b/nexus/db-queries/src/db/queries/external_multicast_group.rs index f0f00bf7d6c..4f0a2a1c514 100644 --- a/nexus/db-queries/src/db/queries/external_multicast_group.rs +++ b/nexus/db-queries/src/db/queries/external_multicast_group.rs @@ -106,10 +106,6 @@ impl NextExternalMulticastGroup { // The multicast IP comes from the candidates subquery out.push_sql("candidate_ip AS multicast_ip, "); - // MVLAN for external uplink forwarding - out.push_bind_param::, Option>(&self.group.mvlan)?; - out.push_sql(" AS mvlan, "); - out.push_bind_param::, Option>(&None)?; out.push_sql(" AS underlay_group_id, "); @@ -268,10 +264,10 @@ impl QueryFragment for NextExternalMulticastGroup { out.push_sql("INSERT INTO "); schema::multicast_group::table.walk_ast(out.reborrow())?; out.push_sql( - " (id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, mvlan, underlay_group_id, underlay_salt, tag, state, version_added, version_removed) - SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, mvlan, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM next_external_multicast_group + " (id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, underlay_group_id, underlay_salt, tag, state, version_added, version_removed) + SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM next_external_multicast_group WHERE NOT EXISTS (SELECT 1 FROM previously_allocated_group) - RETURNING id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, mvlan, underlay_group_id, underlay_salt, tag, state, version_added, version_removed", + RETURNING id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, underlay_group_id, underlay_salt, tag, state, version_added, version_removed", ); out.push_sql("), "); @@ -282,9 +278,9 @@ impl QueryFragment for NextExternalMulticastGroup { // Return either the newly inserted or previously allocated group out.push_sql( - "SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, mvlan, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM previously_allocated_group + "SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM previously_allocated_group UNION ALL - SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, mvlan, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM multicast_group", + SELECT id, name, description, time_created, time_modified, time_deleted, ip_pool_id, ip_pool_range_id, vni, multicast_ip, underlay_group_id, underlay_salt, tag, state, version_added, version_removed FROM multicast_group", ); Ok(()) diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 2be438e9d32..00e75121339 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -2777,7 +2777,6 @@ table! { ip_pool_range_id -> Uuid, vni -> Int4, multicast_ip -> Inet, - mvlan -> Nullable, underlay_group_id -> Nullable, underlay_salt -> Nullable, tag -> Nullable, diff --git a/nexus/external-api/src/v2025121200.rs b/nexus/external-api/src/v2025121200.rs index 5e6cb3fb0b0..e7e9eee78b1 100644 --- a/nexus/external-api/src/v2025121200.rs +++ b/nexus/external-api/src/v2025121200.rs @@ -168,10 +168,9 @@ pub struct MulticastGroupByIpPath { impl From for InternalMulticastGroupCreate { fn from(old: MulticastGroupCreate) -> Self { // Note: `source_ips` is ignored because it's per-member in new version, - // not per-group. - // - // The old API field is kept for backward compatibility but ignored. - // We still use `has_sources` for pool selection preference. + // not per-group. The old API field is kept for backward compatibility + // but ignored. We still use `has_sources` for pool selection preference. + // Also, `mvlan` is ignored, as it's deprecated from multicast groups. let has_sources = old.source_ips.as_ref().is_some_and(|s| !s.is_empty()); Self { @@ -180,7 +179,6 @@ impl From for InternalMulticastGroupCreate { description: old.description, }, multicast_ip: old.multicast_ip, - mvlan: old.mvlan, has_sources, } } @@ -211,7 +209,7 @@ impl From for MulticastGroup { identity: v.identity, multicast_ip: v.multicast_ip, source_ips: v.source_ips, - mvlan: v.mvlan, + mvlan: None, // mvlan deprecated, always `None` for backward compat ip_pool_id: v.ip_pool_id, state: v.state, } diff --git a/nexus/src/app/background/tasks/multicast/groups.rs b/nexus/src/app/background/tasks/multicast/groups.rs index ff1a9dc3e5c..7226d1df704 100644 --- a/nexus/src/app/background/tasks/multicast/groups.rs +++ b/nexus/src/app/background/tasks/multicast/groups.rs @@ -144,15 +144,6 @@ fn dpd_state_matches_sources( dpd_ips == db_sources_sorted } -/// Check if DPD vlan_id matches database mvlan. -fn dpd_state_matches_mvlan( - dpd_group: &dpd_client::types::MulticastGroupExternalResponse, - db_group: &MulticastGroup, -) -> bool { - let db_mvlan = db_group.mvlan.map(|v| v as u16); - dpd_group.external_forwarding.vlan_id == db_mvlan -} - /// Trait for processing different types of multicast groups trait GroupStateProcessor { /// Process a group in "Creating" state. @@ -665,10 +656,8 @@ impl MulticastGroupReconciler { let tag_matches = dpd_state_matches_tag(&dpd_group, group); let sources_match = dpd_state_matches_sources(&dpd_group, &member_sources); - let mvlan_matches = dpd_state_matches_mvlan(&dpd_group, group); - let needs_update = - !tag_matches || !sources_match || !mvlan_matches; + let needs_update = !tag_matches || !sources_match; if needs_update { debug!( @@ -676,8 +665,7 @@ impl MulticastGroupReconciler { "detected DPD state mismatch for active group"; "group_id" => %group.id(), "tag_matches" => tag_matches, - "sources_match" => sources_match, - "mvlan_matches" => mvlan_matches + "sources_match" => sources_match ); } diff --git a/nexus/src/app/multicast/dataplane.rs b/nexus/src/app/multicast/dataplane.rs index 10d96a5abf4..1d7e111de80 100644 --- a/nexus/src/app/multicast/dataplane.rs +++ b/nexus/src/app/multicast/dataplane.rs @@ -61,7 +61,6 @@ use nexus_db_model::{ExternalMulticastGroup, UnderlayMulticastGroup}; use nexus_types::identity::Resource; use omicron_common::address::is_ssm_address; use omicron_common::api::external::{Error, SwitchLocation}; -use omicron_common::vlan::VlanID; use crate::app::dpd_clients; @@ -389,15 +388,6 @@ impl MulticastDataplaneClient { Error::internal_error("multicast group missing tag") })?; - // Convert MVLAN to u16 for DPD, validating through VlanID - let vlan_id = external_group - .mvlan - .map(|v| VlanID::new(v as u16)) - .transpose() - .map_err(|e| { - Error::internal_error(&format!("invalid VLAN ID: {e:#}")) - })? - .map(u16::from); let underlay_ip_admin = underlay_group.multicast_ip.ip().into_admin_scoped()?; let underlay_ipv6 = match underlay_group.multicast_ip.ip() { @@ -448,9 +438,14 @@ impl MulticastDataplaneClient { ) .await?; + // TODO: `vlan_id` is always `None` since we're not doing + // egress in MVP. When egress support is added, this will + // need to be populated from a dynamic configuration. let external_entry = MulticastGroupCreateExternalEntry { group_ip: external_group_ip, - external_forwarding: ExternalForwarding { vlan_id }, + external_forwarding: ExternalForwarding { + vlan_id: None, + }, internal_forwarding: InternalForwarding { nat_target: Some(nat_target), }, @@ -536,16 +531,6 @@ impl MulticastDataplaneClient { let dpd_clients = &self.dpd_clients; // Pre-compute shared data once - // Convert MVLAN to u16 for DPD, validating through VlanID - let vlan_id = params - .external_group - .mvlan - .map(|v| VlanID::new(v as u16)) - .transpose() - .map_err(|e| { - Error::internal_error(&format!("invalid VLAN ID: {e:#}")) - })? - .map(u16::from); let underlay_ip_admin = params.underlay_group.multicast_ip.ip().into_admin_scoped()?; let underlay_ipv6 = match params.underlay_group.multicast_ip.ip() { @@ -646,7 +631,12 @@ impl MulticastDataplaneClient { })?; // Prepare external update/create entries with pre-computed data - let external_forwarding = ExternalForwarding { vlan_id }; + // + // TODO: `vlan_id` is always `None` since we're not doing + // egress in MVP. When egress support is added, this will + // need to be populated from a dynamic configuration. + let external_forwarding = + ExternalForwarding { vlan_id: None }; let internal_forwarding = InternalForwarding { nat_target: Some(nat_target) }; diff --git a/nexus/src/app/multicast/mod.rs b/nexus/src/app/multicast/mod.rs index 431f0fd1b0b..5789627a4dd 100644 --- a/nexus/src/app/multicast/mod.rs +++ b/nexus/src/app/multicast/mod.rs @@ -46,6 +46,13 @@ //! for collision avoidance. //! //! [`UNDERLAY_MULTICAST_SUBNET`]: omicron_common::address::UNDERLAY_MULTICAST_SUBNET +//! +//! # TODO: Egress Support +//! +//! The current implementation supports **ingress-into-the-rack multicast**: +//! external multicast sources sending traffic to instances within the rack. +//! Egress multicast (instances sending to external receivers out-of-the-rack) +//! is not yet supported. use std::net::IpAddr; use std::sync::Arc; @@ -248,7 +255,7 @@ impl super::Nexus { let source_ips = source_ips_map.get(&group.identity.id).cloned().unwrap_or_default(); - ExternalMulticastGroupWithSources { group, source_ips }.try_into() + Ok(ExternalMulticastGroupWithSources { group, source_ips }.into()) } /// Resolve which multicast pool contains a given IP address. @@ -307,17 +314,16 @@ impl super::Nexus { .multicast_groups_source_ips_union(opctx, &group_ids) .await?; - groups + Ok(groups .into_iter() .map(|group| { let source_ips = source_ips_map .get(&group.identity.id) .cloned() .unwrap_or_default(); - ExternalMulticastGroupWithSources { group, source_ips } - .try_into() + ExternalMulticastGroupWithSources { group, source_ips }.into() }) - .collect() + .collect()) } /// Join an instance to a multicast group by identifier (IP, name, or ID). @@ -440,7 +446,6 @@ impl super::Nexus { ), }, multicast_ip: Some(ip), - mvlan: None, has_sources, }; @@ -518,7 +523,6 @@ impl super::Nexus { .to_string(), }, multicast_ip: None, - mvlan: None, has_sources, }; diff --git a/nexus/src/app/sagas/multicast_group_dpd_ensure.rs b/nexus/src/app/sagas/multicast_group_dpd_ensure.rs index aa051817cbd..dfc985d086e 100644 --- a/nexus/src/app/sagas/multicast_group_dpd_ensure.rs +++ b/nexus/src/app/sagas/multicast_group_dpd_ensure.rs @@ -517,7 +517,6 @@ mod test { description: "Test saga state validation".to_string(), }, multicast_ip: Some(IpAddr::V4(Ipv4Addr::new(224, 70, 0, 100))), - mvlan: None, has_sources: false, }; diff --git a/nexus/tests/integration_tests/multicast/groups.rs b/nexus/tests/integration_tests/multicast/groups.rs index 003b7b60d6a..7e6b45868f9 100644 --- a/nexus/tests/integration_tests/multicast/groups.rs +++ b/nexus/tests/integration_tests/multicast/groups.rs @@ -920,7 +920,6 @@ fn assert_groups_eq(left: &MulticastGroup, right: &MulticastGroup) { assert_eq!(left.identity.description, right.identity.description); assert_eq!(left.multicast_ip, right.multicast_ip); assert_eq!(left.source_ips, right.source_ips); - assert_eq!(left.mvlan, right.mvlan); assert_eq!(left.ip_pool_id, right.ip_pool_id); } diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index a98ab7f8454..f136c200efe 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -19,7 +19,6 @@ use omicron_common::api::external::{ Digest, Error, FailureDomain, IdentityMetadata, InstanceState, Name, Nullable, ObjectIdentity, SimpleIdentity, SimpleIdentityOrName, }; -use omicron_common::vlan::VlanID; use omicron_uuid_kinds::*; use oxnet::{Ipv4Net, Ipv6Net}; use schemars::JsonSchema; @@ -554,10 +553,6 @@ pub struct MulticastGroup { /// reflects all sources that any member is subscribed to. /// Empty array means no members have source filtering enabled. pub source_ips: Vec, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// None means no VLAN tagging on egress. - // TODO(multicast): Remove mvlan field - being deprecated from multicast groups - pub mvlan: Option, /// The ID of the IP pool this resource belongs to. pub ip_pool_id: Uuid, /// Current state of the multicast group. diff --git a/nexus/types/src/multicast.rs b/nexus/types/src/multicast.rs index 1db71084ddd..184d0650e03 100644 --- a/nexus/types/src/multicast.rs +++ b/nexus/types/src/multicast.rs @@ -5,7 +5,6 @@ //! Internal multicast types used by Nexus. use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::vlan::VlanID; use std::net::IpAddr; /// Internal parameters for creating a multicast group. @@ -22,12 +21,6 @@ pub struct MulticastGroupCreate { /// /// If `None`, one will be allocated from the default pool. pub multicast_ip: Option, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// Tags packets leaving the rack to traverse VLAN-segmented upstream networks. - /// - /// Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). - // TODO(multicast): Remove mvlan field - being deprecated from multicast groups - pub mvlan: Option, /// Whether the joining member has source IPs. /// /// Used for default pool selection when `multicast_ip` is `None`: diff --git a/openapi/nexus/nexus-2025121600.0.0-f633d6.json b/openapi/nexus/nexus-2025121600.0.0-87a5ec.json similarity index 99% rename from openapi/nexus/nexus-2025121600.0.0-f633d6.json rename to openapi/nexus/nexus-2025121600.0.0-87a5ec.json index 4703ccf9d66..359c3fe7400 100644 --- a/openapi/nexus/nexus-2025121600.0.0-f633d6.json +++ b/openapi/nexus/nexus-2025121600.0.0-87a5ec.json @@ -22807,13 +22807,6 @@ "type": "string", "format": "ip" }, - "mvlan": { - "nullable": true, - "description": "Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. None means no VLAN tagging on egress.", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, "name": { "description": "unique, mutable, user-controlled identifier for each resource", "allOf": [ diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index c726f9c2a05..69b43b7faf3 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2025121600.0.0-f633d6.json \ No newline at end of file +nexus-2025121600.0.0-87a5ec.json \ No newline at end of file diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 6a72f1468a3..c4d4275ebec 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -7118,11 +7118,6 @@ CREATE TABLE IF NOT EXISTS omicron.public.multicast_group ( ip_pool_range_id UUID NOT NULL, multicast_ip INET NOT NULL, - /* Multicast VLAN (MVLAN) for egress to upstream networks */ - /* Tags packets leaving the rack to traverse VLAN-segmented upstream networks */ - /* Internal rack traffic uses VNI-based underlay forwarding */ - mvlan INT2, - /* Associated underlay group for NAT */ /* We fill this as part of the RPW */ underlay_group_id UUID, @@ -7168,11 +7163,6 @@ CREATE TABLE IF NOT EXISTS omicron.public.multicast_group ( NOT multicast_ip << 'ff01::/16' AND -- Interface-local scope NOT multicast_ip << 'ff02::/16' -- Link-local scope ) - ), - - -- MVLAN validation (Dendrite requires >= 2) - CONSTRAINT mvlan_valid_range CHECK ( - mvlan IS NULL OR (mvlan >= 2 AND mvlan <= 4094) ) ); @@ -7473,7 +7463,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '214.0.0', NULL) + (TRUE, NOW(), NOW(), '215.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; diff --git a/schema/crdb/multicast-drop-mvlan/up01.sql b/schema/crdb/multicast-drop-mvlan/up01.sql new file mode 100644 index 00000000000..a6ed8fbd2b1 --- /dev/null +++ b/schema/crdb/multicast-drop-mvlan/up01.sql @@ -0,0 +1,6 @@ +-- Drop mvlan column and constraint from multicast_group +-- This field was for egress multicast which is not in MVP scope + +-- First drop the constraint, then the column +ALTER TABLE omicron.public.multicast_group DROP CONSTRAINT IF EXISTS mvlan_valid_range; +ALTER TABLE omicron.public.multicast_group DROP COLUMN IF EXISTS mvlan;