Skip to content

Commit 7bb5c32

Browse files
[multicast] Drop (m)vlan from multicast groups (#9451)
Egress multicast (instances sending to external receivers) is not in MVP scope. The mvlan field for VLAN-tagged upstream traffic is unnecessary since it probably will not be attached specifically to a group. Worth revisiting when egress support lands. This PR also replaces mvlan with has_any_source_member on the multicast group view, which tracks whether any member joined without source filtering, using for source IP logic. The source_ips field now contains only IPs from members that explicitly specified sources, making the semantics clearer. The group-to-view conversion simplifies from TryFrom to From.
1 parent e3994e0 commit 7bb5c32

28 files changed

Lines changed: 711 additions & 286 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,8 +1043,6 @@ opt-level = 3
10431043
# drift = { path = "../drift" }
10441044
# dropshot = { path = "../dropshot/dropshot" }
10451045
# dropshot_endpoint = { path = "../dropshot/dropshot_endpoint" }
1046-
# dropshot-api-manager = { path = "../dropshot-api-manager/crates/dropshot-api-manager" }
1047-
# dropshot-api-manager-types = { path = "../dropshot-api-manager/crates/dropshot-api-manager-types" }
10481046
# progenitor = { path = "../progenitor/progenitor" }
10491047
# progenitor-client = { path = "../progenitor/progenitor-client" }
10501048
# steno = { path = "../steno" }

nexus/db-model/src/multicast_group.rs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -178,28 +178,6 @@ pub struct ExternalMulticastGroup {
178178
pub ip_pool_range_id: Uuid,
179179
/// Primary multicast IP address (overlay/external).
180180
pub multicast_ip: IpNetwork,
181-
/// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks.
182-
///
183-
/// When specified, this VLAN ID is passed to switches (via DPD) as part of
184-
/// the `ExternalForwarding` configuration to tag multicast packets leaving
185-
/// the rack. This enables multicast traffic to traverse VLAN-segmented
186-
/// upstream networks (e.g., peering with external multicast sources/receivers
187-
/// on specific VLANs).
188-
///
189-
/// The MVLAN value is sent to switches during group creation/updates and
190-
/// controls VLAN tagging for egress traffic only; it does not affect ingress
191-
/// multicast traffic received by the rack. Switch port selection for egress
192-
/// traffic remains pending (see TODOs in `nexus/src/app/multicast/dataplane.rs`).
193-
///
194-
/// Valid range when specified: 2-4094 (IEEE 802.1Q; Dendrite requires >= 2).
195-
///
196-
/// Database Type: i16 (INT2) - this field uses `i16` (INT2) for storage
197-
/// efficiency, unlike other VLAN columns in the schema which use `SqlU16`
198-
/// (forcing INT4). Direct `i16` is appropriate here since VLANs fit in
199-
/// INT2's range.
200-
///
201-
/// TODO(multicast): Remove mvlan field - being deprecated from multicast groups
202-
pub mvlan: Option<i16>,
203181
/// Associated underlay group for NAT.
204182
/// Initially None in ["Creating"](MulticastGroupState::Creating) state,
205183
/// populated by reconciler when group becomes ["Active"](MulticastGroupState::Active).
@@ -248,11 +226,11 @@ pub struct MulticastGroupMemberValues {
248226
pub parent_id: Uuid,
249227
pub sled_id: Option<DbTypedUuid<SledKind>>,
250228
pub state: MulticastGroupMemberState,
251-
// version_added and version_removed are omitted - database assigns these
252-
// via DEFAULT nextval()
253229
pub multicast_ip: IpNetwork,
254230
/// Source IPs for source-filtered multicast (optional for ASM, required for SSM).
255231
pub source_ips: Vec<IpNetwork>,
232+
// version_added and version_removed are omitted - database assigns these
233+
// via DEFAULT nextval()
256234
}
257235

258236
/// A member of a multicast group (instance that receives multicast traffic).
@@ -329,9 +307,7 @@ impl TryFrom<MulticastGroupMember> for multicast_types::MulticastGroupMember {
329307

330308
/// An incomplete external multicast group, used to store state required for
331309
/// issuing the database query that selects an available multicast IP and stores
332-
/// the resulting record.
333-
///
334-
/// Note: tag is computed in SQL as `{uuid}:{multicast_ip}`.
310+
/// the resulting record. Tag is computed in SQL as `{uuid}:{multicast_ip}`.
335311
#[derive(Clone, Debug, PartialEq, Eq)]
336312
pub struct IncompleteExternalMulticastGroup {
337313
pub id: Uuid,
@@ -341,7 +317,6 @@ pub struct IncompleteExternalMulticastGroup {
341317
pub ip_pool_id: Uuid,
342318
/// Optional address requesting a specific multicast IP be allocated.
343319
pub explicit_address: Option<IpNetwork>,
344-
pub mvlan: Option<i16>,
345320
pub vni: Vni,
346321
}
347322

@@ -353,7 +328,6 @@ pub struct IncompleteExternalMulticastGroupParams {
353328
pub description: String,
354329
pub ip_pool_id: Uuid,
355330
pub explicit_address: Option<IpAddr>,
356-
pub mvlan: Option<i16>,
357331
pub vni: Vni,
358332
}
359333

@@ -367,7 +341,6 @@ impl IncompleteExternalMulticastGroup {
367341
time_created: Utc::now(),
368342
ip_pool_id: params.ip_pool_id,
369343
explicit_address: params.explicit_address.map(|ip| ip.into()),
370-
mvlan: params.mvlan,
371344
vni: params.vni,
372345
}
373346
}

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
1616
///
1717
/// This must be updated when you change the database schema. Refer to
1818
/// schema/crdb/README.adoc in the root of this repository for details.
19-
pub const SCHEMA_VERSION: Version = Version::new(239, 0, 0);
19+
pub const SCHEMA_VERSION: Version = Version::new(240, 0, 0);
2020

2121
/// List of all past database schema versions, in *reverse* order
2222
///
@@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31+
KnownVersion::new(240, "multicast-drop-mvlan"),
3132
KnownVersion::new(239, "fm-alert-request"),
3233
KnownVersion::new(238, "fewer-nullable-columns"),
3334
KnownVersion::new(237, "switch-slot-enum"),

nexus/db-queries/src/db/datastore/multicast/groups.rs

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ use omicron_common::api::external::{
3838
IdentityMetadataCreateParams, IpVersion, ListResultVec, LookupResult,
3939
LookupType, ResourceType, UpdateResult,
4040
};
41-
use omicron_common::vlan::VlanID;
4241
use omicron_uuid_kinds::{GenericUuid, MulticastGroupUuid};
4342

4443
use super::EnsureUnderlayResult;
@@ -54,44 +53,24 @@ use crate::db::pagination::paginated;
5453
use crate::db::queries::external_multicast_group::NextExternalMulticastGroup;
5554
use crate::db::update_and_check::{UpdateAndCheck, UpdateStatus};
5655

57-
/// External multicast group with computed source IPs from members.
58-
///
59-
/// The `source_ips` field contains the union of all member source IPs,
60-
/// computed via a separate query. This struct enables a clean `TryFrom`
61-
/// conversion to the API view.
62-
///
63-
// TODO(multicast): Remove mvlan field, being deprecated from multicast groups
56+
/// External multicast group with computed source filter state from members.
6457
#[derive(Clone, Debug)]
6558
pub struct ExternalMulticastGroupWithSources {
6659
pub group: ExternalMulticastGroup,
6760
pub source_ips: Vec<IpAddr>,
61+
pub has_any_source_member: bool,
6862
}
6963

70-
impl TryFrom<ExternalMulticastGroupWithSources> for views::MulticastGroup {
71-
type Error = external::Error;
72-
73-
fn try_from(
74-
value: ExternalMulticastGroupWithSources,
75-
) -> Result<Self, Self::Error> {
76-
let mvlan = value
77-
.group
78-
.mvlan
79-
.map(|vlan| VlanID::new(vlan as u16))
80-
.transpose()
81-
.map_err(|e| {
82-
external::Error::internal_error(&format!(
83-
"invalid VLAN ID: {e:#}"
84-
))
85-
})?;
86-
87-
Ok(views::MulticastGroup {
64+
impl From<ExternalMulticastGroupWithSources> for views::MulticastGroup {
65+
fn from(value: ExternalMulticastGroupWithSources) -> Self {
66+
views::MulticastGroup {
8867
identity: value.group.identity(),
8968
multicast_ip: value.group.multicast_ip.ip(),
9069
source_ips: value.source_ips,
91-
mvlan,
70+
has_any_source_member: value.has_any_source_member,
9271
ip_pool_id: value.group.ip_pool_id,
9372
state: value.group.state.to_string(),
94-
})
73+
}
9574
}
9675
}
9776

@@ -104,7 +83,6 @@ pub(crate) struct MulticastGroupAllocationParams {
10483
pub identity: IdentityMetadataCreateParams,
10584
/// How to allocate the multicast IP address.
10685
pub ip_allocation: MulticastIpAllocation,
107-
pub mvlan: Option<VlanID>,
10886
/// Derived for whether the joining member has source IPs.
10987
/// Used for default pool selection -> if true, prefer SSM pool first.
11088
pub has_sources: bool,
@@ -283,7 +261,6 @@ impl DataStore {
283261
MulticastGroupAllocationParams {
284262
identity: params.identity.clone(),
285263
ip_allocation,
286-
mvlan: params.mvlan,
287264
has_sources: params.has_sources,
288265
},
289266
)
@@ -344,12 +321,21 @@ impl DataStore {
344321
let filter_state_map = self
345322
.multicast_groups_source_filter_state(opctx, &[group_id])
346323
.await?;
347-
let source_ips = filter_state_map
324+
let (source_ips, has_any_source_member) = filter_state_map
348325
.get(&group_id.into_untyped_uuid())
349-
.map(|state| state.specific_sources.iter().copied().collect())
326+
.map(|state| {
327+
(
328+
state.specific_sources.iter().copied().collect(),
329+
state.has_any_source_member,
330+
)
331+
})
350332
.unwrap_or_default();
351333

352-
Ok(ExternalMulticastGroupWithSources { group, source_ips })
334+
Ok(ExternalMulticastGroupWithSources {
335+
group,
336+
source_ips,
337+
has_any_source_member,
338+
})
353339
}
354340

355341
/// Lookup an external multicast group by IP address.
@@ -643,7 +629,6 @@ impl DataStore {
643629
description: params.identity.description.clone(),
644630
ip_pool_id: authz_pool.id(),
645631
explicit_address: explicit_ip,
646-
mvlan: params.mvlan.map(|vlan_id| u16::from(vlan_id) as i16),
647632
vni,
648633
},
649634
);
@@ -1013,7 +998,6 @@ mod tests {
1013998
description: "First group".to_string(),
1014999
},
10151000
multicast_ip: None,
1016-
mvlan: None,
10171001
has_sources: false,
10181002
ip_version: None,
10191003
};
@@ -1031,7 +1015,6 @@ mod tests {
10311015
description: "Second group".to_string(),
10321016
},
10331017
multicast_ip: None,
1034-
mvlan: None,
10351018
has_sources: false,
10361019
ip_version: None,
10371020
};
@@ -1049,7 +1032,6 @@ mod tests {
10491032
description: "Should fail".to_string(),
10501033
},
10511034
multicast_ip: None,
1052-
mvlan: None,
10531035
has_sources: false,
10541036
ip_version: None,
10551037
};
@@ -1078,7 +1060,6 @@ mod tests {
10781060
description: "Should reuse freed IP".to_string(),
10791061
},
10801062
multicast_ip: None,
1081-
mvlan: None,
10821063
has_sources: false,
10831064
ip_version: None,
10841065
};
@@ -1161,7 +1142,6 @@ mod tests {
11611142
description: "Group using default pool".to_string(),
11621143
},
11631144
multicast_ip: None,
1164-
mvlan: None,
11651145
has_sources: false,
11661146
ip_version: None,
11671147
};
@@ -1187,7 +1167,6 @@ mod tests {
11871167
description: "Second group from default pool".to_string(),
11881168
},
11891169
multicast_ip: None,
1190-
mvlan: None,
11911170
has_sources: false,
11921171
ip_version: None,
11931172
};
@@ -1315,7 +1294,6 @@ mod tests {
13151294
description: "Comprehensive test group".to_string(),
13161295
},
13171296
multicast_ip: Some("224.1.3.3".parse().unwrap()),
1318-
mvlan: None,
13191297
has_sources: false,
13201298
ip_version: None,
13211299
};
@@ -1416,7 +1394,6 @@ mod tests {
14161394
description: "Group for IP reuse test".to_string(),
14171395
},
14181396
multicast_ip: Some(target_ip),
1419-
mvlan: None,
14201397
has_sources: false,
14211398
ip_version: None,
14221399
};
@@ -1444,7 +1421,6 @@ mod tests {
14441421
description: "Second group reusing same IP".to_string(),
14451422
},
14461423
multicast_ip: Some(target_ip),
1447-
mvlan: None,
14481424
has_sources: false,
14491425
ip_version: None,
14501426
};
@@ -1525,7 +1501,6 @@ mod tests {
15251501
description: "Group for deallocation testing".to_string(),
15261502
},
15271503
multicast_ip: None,
1528-
mvlan: None,
15291504
has_sources: false,
15301505
ip_version: None,
15311506
};
@@ -1650,7 +1625,6 @@ mod tests {
16501625
description: "Test group for fetch operations".to_string(),
16511626
},
16521627
multicast_ip: Some("224.100.10.5".parse().unwrap()),
1653-
mvlan: None,
16541628
has_sources: false,
16551629
ip_version: None,
16561630
};
@@ -1757,7 +1731,6 @@ mod tests {
17571731
description: "Fleet-wide group 1".to_string(),
17581732
},
17591733
multicast_ip: Some("224.100.20.10".parse().unwrap()),
1760-
mvlan: None,
17611734
has_sources: false,
17621735
ip_version: None,
17631736
};
@@ -1768,7 +1741,6 @@ mod tests {
17681741
description: "Fleet-wide group 2".to_string(),
17691742
},
17701743
multicast_ip: Some("224.100.20.11".parse().unwrap()),
1771-
mvlan: None,
17721744
has_sources: false,
17731745
ip_version: None,
17741746
};
@@ -1779,7 +1751,6 @@ mod tests {
17791751
description: "Fleet-wide group 3".to_string(),
17801752
},
17811753
multicast_ip: Some("224.100.20.12".parse().unwrap()),
1782-
mvlan: None,
17831754
has_sources: false,
17841755
ip_version: None,
17851756
};
@@ -1888,7 +1859,6 @@ mod tests {
18881859
description: "Test group for state transitions".to_string(),
18891860
},
18901861
multicast_ip: Some("224.100.30.5".parse().unwrap()),
1891-
mvlan: None,
18921862
has_sources: false,
18931863
ip_version: None,
18941864
};
@@ -2225,7 +2195,6 @@ mod tests {
22252195
description: "Group using ASM pool".to_string(),
22262196
},
22272197
multicast_ip: None, // No explicit IP -> triggers pool auto-selection
2228-
mvlan: None,
22292198
has_sources: false,
22302199
ip_version: None,
22312200
};
@@ -2316,7 +2285,6 @@ mod tests {
23162285
description: "Should fall back to ASM when no SSM".to_string(),
23172286
},
23182287
multicast_ip: None,
2319-
mvlan: None,
23202288
has_sources: true,
23212289
ip_version: None,
23222290
};
@@ -2389,7 +2357,6 @@ mod tests {
23892357
description: "Should prefer SSM over ASM".to_string(),
23902358
},
23912359
multicast_ip: None,
2392-
mvlan: None,
23932360
has_sources: true,
23942361
ip_version: None,
23952362
};
@@ -2413,7 +2380,6 @@ mod tests {
24132380
description: "has_sources=false should use ASM".to_string(),
24142381
},
24152382
multicast_ip: None,
2416-
mvlan: None,
24172383
has_sources: false,
24182384
ip_version: None,
24192385
};
@@ -2493,7 +2459,6 @@ mod tests {
24932459
description: "First group for collision test".to_string(),
24942460
},
24952461
multicast_ip: Some("224.10.1.1".parse().unwrap()),
2496-
mvlan: None,
24972462
has_sources: false,
24982463
ip_version: None,
24992464
};
@@ -2509,7 +2474,6 @@ mod tests {
25092474
description: "Second group for collision test".to_string(),
25102475
},
25112476
multicast_ip: Some("224.10.1.2".parse().unwrap()),
2512-
mvlan: None,
25132477
has_sources: false,
25142478
ip_version: None,
25152479
};
@@ -2633,7 +2597,6 @@ mod tests {
26332597
description: "Group for salt testing".to_string(),
26342598
},
26352599
multicast_ip: Some("224.20.1.1".parse().unwrap()),
2636-
mvlan: None,
26372600
has_sources: false,
26382601
ip_version: None,
26392602
};

nexus/db-queries/src/db/datastore/multicast/members.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,6 @@ mod tests {
929929
description: "Creating test group".to_string(),
930930
},
931931
multicast_ip: Some("224.10.1.6".parse().unwrap()),
932-
mvlan: None,
933932
has_sources: false,
934933
ip_version: None,
935934
};

0 commit comments

Comments
 (0)