Skip to content

Commit 8c6215e

Browse files
[update] Move API calls behind "experimental" tag and disable runs based on config
Being that we still have OPTE and Maghemite updates to come for statically routed multicast, we gate RPW and Saga actions behind runtime configuration ("on" for tests). API calls are tagged "experimental."
1 parent 04dfa49 commit 8c6215e

File tree

17 files changed

+476
-93
lines changed

17 files changed

+476
-93
lines changed

nexus-config/src/nexus_config.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,22 @@ impl Default for MulticastGroupReconcilerConfig {
853853
}
854854
}
855855

856+
/// TODO: remove this when multicast is implemented end-to-end.
857+
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
858+
pub struct MulticastConfig {
859+
/// Whether multicast functionality is enabled or not.
860+
///
861+
/// When false, multicast API calls remain accessible but no actual
862+
/// multicast operations occur (no switch programming, reconciler disabled).
863+
/// Instance sagas will skip multicast operations. This allows gradual
864+
/// rollout and testing of multicast configuration.
865+
///
866+
/// Default: false (experimental feature, disabled by default)
867+
#[serde(default)]
868+
pub enabled: bool,
869+
}
870+
871+
856872
/// Configuration for a nexus server
857873
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
858874
pub struct PackageConfig {
@@ -884,6 +900,9 @@ pub struct PackageConfig {
884900
pub initial_reconfigurator_config: Option<ReconfiguratorConfig>,
885901
/// Background task configuration
886902
pub background_tasks: BackgroundTaskConfig,
903+
/// Multicast feature configuration
904+
#[serde(default)]
905+
pub multicast: MulticastConfig,
887906
/// Default Crucible region allocation strategy
888907
pub default_region_allocation_strategy: RegionAllocationStrategy,
889908
}
@@ -1382,6 +1401,7 @@ mod test {
13821401
period_secs: Duration::from_secs(60),
13831402
},
13841403
},
1404+
multicast: MulticastConfig { enabled: false },
13851405
default_region_allocation_strategy:
13861406
crate::nexus_config::RegionAllocationStrategy::Random {
13871407
seed: Some(0)

nexus/external-api/output/nexus_tags.txt

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ affinity_group_member_list GET /v1/affinity-groups/{affinity_
4949
affinity_group_update PUT /v1/affinity-groups/{affinity_group}
5050
affinity_group_view GET /v1/affinity-groups/{affinity_group}
5151
instance_affinity_group_list GET /v1/instances/{instance}/affinity-groups
52+
instance_multicast_group_join PUT /v1/instances/{instance}/multicast-groups/{multicast_group}
53+
instance_multicast_group_leave DELETE /v1/instances/{instance}/multicast-groups/{multicast_group}
54+
instance_multicast_group_list GET /v1/instances/{instance}/multicast-groups
55+
lookup_multicast_group_by_ip GET /v1/system/multicast-groups/by-ip/{address}
56+
multicast_group_create POST /v1/multicast-groups
57+
multicast_group_delete DELETE /v1/multicast-groups/{multicast_group}
58+
multicast_group_list GET /v1/multicast-groups
59+
multicast_group_member_add POST /v1/multicast-groups/{multicast_group}/members
60+
multicast_group_member_list GET /v1/multicast-groups/{multicast_group}/members
61+
multicast_group_member_remove DELETE /v1/multicast-groups/{multicast_group}/members/{instance}
62+
multicast_group_update PUT /v1/multicast-groups/{multicast_group}
63+
multicast_group_view GET /v1/multicast-groups/{multicast_group}
5264
probe_create POST /experimental/v1/probes
5365
probe_delete DELETE /experimental/v1/probes/{probe}
5466
probe_list GET /experimental/v1/probes
@@ -96,9 +108,6 @@ instance_ephemeral_ip_attach POST /v1/instances/{instance}/exter
96108
instance_ephemeral_ip_detach DELETE /v1/instances/{instance}/external-ips/ephemeral
97109
instance_external_ip_list GET /v1/instances/{instance}/external-ips
98110
instance_list GET /v1/instances
99-
instance_multicast_group_join PUT /v1/instances/{instance}/multicast-groups/{multicast_group}
100-
instance_multicast_group_leave DELETE /v1/instances/{instance}/multicast-groups/{multicast_group}
101-
instance_multicast_group_list GET /v1/instances/{instance}/multicast-groups
102111
instance_network_interface_create POST /v1/network-interfaces
103112
instance_network_interface_delete DELETE /v1/network-interfaces/{interface}
104113
instance_network_interface_list GET /v1/network-interfaces
@@ -122,18 +131,6 @@ API operations found with tag "metrics"
122131
OPERATION ID METHOD URL PATH
123132
silo_metric GET /v1/metrics/{metric_name}
124133

125-
API operations found with tag "multicast-groups"
126-
OPERATION ID METHOD URL PATH
127-
lookup_multicast_group_by_ip GET /v1/system/multicast-groups/by-ip/{address}
128-
multicast_group_create POST /v1/multicast-groups
129-
multicast_group_delete DELETE /v1/multicast-groups/{multicast_group}
130-
multicast_group_list GET /v1/multicast-groups
131-
multicast_group_member_add POST /v1/multicast-groups/{multicast_group}/members
132-
multicast_group_member_list GET /v1/multicast-groups/{multicast_group}/members
133-
multicast_group_member_remove DELETE /v1/multicast-groups/{multicast_group}/members/{instance}
134-
multicast_group_update PUT /v1/multicast-groups/{multicast_group}
135-
multicast_group_view GET /v1/multicast-groups/{multicast_group}
136-
137134
API operations found with tag "policy"
138135
OPERATION ID METHOD URL PATH
139136
system_policy_update PUT /v1/system/policy

nexus/external-api/src/lib.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ pub trait NexusExternalApi {
10291029
#[endpoint {
10301030
method = GET,
10311031
path = "/v1/multicast-groups",
1032-
tags = ["multicast-groups"],
1032+
tags = ["experimental"],
10331033
}]
10341034
async fn multicast_group_list(
10351035
rqctx: RequestContext<Self::Context>,
@@ -1040,7 +1040,7 @@ pub trait NexusExternalApi {
10401040
#[endpoint {
10411041
method = POST,
10421042
path = "/v1/multicast-groups",
1043-
tags = ["multicast-groups"],
1043+
tags = ["experimental"],
10441044
}]
10451045
async fn multicast_group_create(
10461046
rqctx: RequestContext<Self::Context>,
@@ -1052,7 +1052,7 @@ pub trait NexusExternalApi {
10521052
#[endpoint {
10531053
method = GET,
10541054
path = "/v1/multicast-groups/{multicast_group}",
1055-
tags = ["multicast-groups"],
1055+
tags = ["experimental"],
10561056
}]
10571057
async fn multicast_group_view(
10581058
rqctx: RequestContext<Self::Context>,
@@ -1064,7 +1064,7 @@ pub trait NexusExternalApi {
10641064
#[endpoint {
10651065
method = PUT,
10661066
path = "/v1/multicast-groups/{multicast_group}",
1067-
tags = ["multicast-groups"],
1067+
tags = ["experimental"],
10681068
}]
10691069
async fn multicast_group_update(
10701070
rqctx: RequestContext<Self::Context>,
@@ -1077,7 +1077,7 @@ pub trait NexusExternalApi {
10771077
#[endpoint {
10781078
method = DELETE,
10791079
path = "/v1/multicast-groups/{multicast_group}",
1080-
tags = ["multicast-groups"],
1080+
tags = ["experimental"],
10811081
}]
10821082
async fn multicast_group_delete(
10831083
rqctx: RequestContext<Self::Context>,
@@ -1089,7 +1089,7 @@ pub trait NexusExternalApi {
10891089
#[endpoint {
10901090
method = GET,
10911091
path = "/v1/system/multicast-groups/by-ip/{address}",
1092-
tags = ["multicast-groups"],
1092+
tags = ["experimental"],
10931093
}]
10941094
async fn lookup_multicast_group_by_ip(
10951095
rqctx: RequestContext<Self::Context>,
@@ -1100,7 +1100,7 @@ pub trait NexusExternalApi {
11001100
#[endpoint {
11011101
method = GET,
11021102
path = "/v1/multicast-groups/{multicast_group}/members",
1103-
tags = ["multicast-groups"],
1103+
tags = ["experimental"],
11041104
}]
11051105
async fn multicast_group_member_list(
11061106
rqctx: RequestContext<Self::Context>,
@@ -1112,7 +1112,7 @@ pub trait NexusExternalApi {
11121112
#[endpoint {
11131113
method = POST,
11141114
path = "/v1/multicast-groups/{multicast_group}/members",
1115-
tags = ["multicast-groups"],
1115+
tags = ["experimental"],
11161116
}]
11171117
async fn multicast_group_member_add(
11181118
rqctx: RequestContext<Self::Context>,
@@ -1125,7 +1125,7 @@ pub trait NexusExternalApi {
11251125
#[endpoint {
11261126
method = DELETE,
11271127
path = "/v1/multicast-groups/{multicast_group}/members/{instance}",
1128-
tags = ["multicast-groups"],
1128+
tags = ["experimental"],
11291129
}]
11301130
async fn multicast_group_member_remove(
11311131
rqctx: RequestContext<Self::Context>,
@@ -2350,7 +2350,7 @@ pub trait NexusExternalApi {
23502350
#[endpoint {
23512351
method = GET,
23522352
path = "/v1/instances/{instance}/multicast-groups",
2353-
tags = ["instances"],
2353+
tags = ["experimental"],
23542354
}]
23552355
async fn instance_multicast_group_list(
23562356
rqctx: RequestContext<Self::Context>,
@@ -2365,7 +2365,7 @@ pub trait NexusExternalApi {
23652365
#[endpoint {
23662366
method = PUT,
23672367
path = "/v1/instances/{instance}/multicast-groups/{multicast_group}",
2368-
tags = ["instances"],
2368+
tags = ["experimental"],
23692369
}]
23702370
async fn instance_multicast_group_join(
23712371
rqctx: RequestContext<Self::Context>,
@@ -2377,7 +2377,7 @@ pub trait NexusExternalApi {
23772377
#[endpoint {
23782378
method = DELETE,
23792379
path = "/v1/instances/{instance}/multicast-groups/{multicast_group}",
2380-
tags = ["instances"],
2380+
tags = ["experimental"],
23812381
}]
23822382
async fn instance_multicast_group_leave(
23832383
rqctx: RequestContext<Self::Context>,

nexus/src/app/background/init.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ impl BackgroundTasksInitializer {
10011001
datastore.clone(),
10021002
resolver.clone(),
10031003
sagas.clone(),
1004+
args.multicast_enabled,
10041005
)),
10051006
opctx: opctx.child(BTreeMap::new()),
10061007
watchers: vec![],
@@ -1033,6 +1034,8 @@ pub struct BackgroundTasksData {
10331034
pub datastore: Arc<DataStore>,
10341035
/// background task configuration
10351036
pub config: BackgroundTaskConfig,
1037+
/// whether multicast functionality is enabled (or not)
1038+
pub multicast_enabled: bool,
10361039
/// rack identifier
10371040
pub rack_id: Uuid,
10381041
/// nexus identifier

nexus/src/app/background/tasks/multicast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,16 @@ pub(crate) struct MulticastGroupReconciler {
140140
member_concurrency_limit: usize,
141141
/// Maximum number of groups to process concurrently.
142142
group_concurrency_limit: usize,
143+
/// Whether multicast functionality is enabled (or not).
144+
enabled: bool,
143145
}
144146

145147
impl MulticastGroupReconciler {
146148
pub(crate) fn new(
147149
datastore: Arc<DataStore>,
148150
resolver: Resolver,
149151
sagas: Arc<dyn StartSaga>,
152+
enabled: bool,
150153
) -> Self {
151154
Self {
152155
datastore,
@@ -159,6 +162,7 @@ impl MulticastGroupReconciler {
159162
cache_ttl: Duration::from_secs(3600), // 1 hour - refresh topology mappings regularly
160163
member_concurrency_limit: 100,
161164
group_concurrency_limit: 100,
165+
enabled,
162166
}
163167
}
164168

@@ -178,6 +182,13 @@ impl BackgroundTask for MulticastGroupReconciler {
178182
opctx: &'a OpContext,
179183
) -> BoxFuture<'a, serde_json::Value> {
180184
async move {
185+
if !self.enabled {
186+
info!(opctx.log, "multicast group reconciler not enabled");
187+
let mut status = MulticastGroupReconcilerStatus::default();
188+
status.disabled = true;
189+
return json!(status);
190+
}
191+
181192
trace!(opctx.log, "multicast group reconciler activating");
182193
let status = self.run_reconciliation_pass(opctx).await;
183194

nexus/src/app/instance.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,15 @@ impl super::Nexus {
363363
) -> Result<(), Error> {
364364
let instance_id = authz_instance.id();
365365

366+
// Check if multicast is enabled - if not, skip all multicast operations
367+
if !self.multicast_enabled() {
368+
debug!(opctx.log,
369+
"multicast not enabled, skipping multicast group changes";
370+
"instance_id" => %instance_id,
371+
"requested_groups_count" => multicast_groups.len());
372+
return Ok(());
373+
}
374+
366375
debug!(
367376
opctx.log,
368377
"processing multicast group changes";
@@ -948,13 +957,15 @@ impl super::Nexus {
948957
.await?;
949958

950959
// Update multicast member state for this instance to "Left" and clear
951-
// `sled_id`
952-
self.db_datastore
953-
.multicast_group_members_detach_by_instance(
954-
opctx,
955-
authz_instance.id(),
956-
)
957-
.await?;
960+
// `sled_id` - only if multicast is enabled
961+
if self.multicast_enabled() {
962+
self.db_datastore
963+
.multicast_group_members_detach_by_instance(
964+
opctx,
965+
authz_instance.id(),
966+
)
967+
.await?;
968+
}
958969

959970
// Activate multicast reconciler to handle switch-level changes
960971
self.background_tasks.task_multicast_group_reconciler.activate();

nexus/src/app/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ pub struct Nexus {
224224
/// The tunable parameters from a configuration file
225225
tunables: Tunables,
226226

227+
/// Whether multicast functionality is enabled - used by sagas and API endpoints to check if multicast operations should proceed
228+
multicast_enabled: bool,
229+
227230
/// Operational context used for Instance allocation
228231
opctx_alloc: OpContext,
229232

@@ -500,6 +503,13 @@ impl Nexus {
500503
timeseries_client,
501504
webhook_delivery_client,
502505
tunables: config.pkg.tunables.clone(),
506+
// Whether multicast functionality is enabled.
507+
// This is used by instance-related sagas and API endpoints to check
508+
// if multicast operations should proceed.
509+
//
510+
// NOTE: This is separate from the RPW reconciler timing config, which
511+
// only controls how often the background task runs.
512+
multicast_enabled: config.pkg.multicast.enabled,
503513
opctx_alloc: OpContext::for_background(
504514
log.new(o!("component" => "InstanceAllocator")),
505515
Arc::clone(&authz),
@@ -600,6 +610,7 @@ impl Nexus {
600610
opctx: background_ctx,
601611
datastore: db_datastore,
602612
config: task_config.pkg.background_tasks,
613+
multicast_enabled: task_config.pkg.multicast.enabled,
603614
rack_id,
604615
nexus_id: task_config.deployment.id,
605616
resolver,
@@ -651,6 +662,10 @@ impl Nexus {
651662
&self.authz
652663
}
653664

665+
pub fn multicast_enabled(&self) -> bool {
666+
self.multicast_enabled
667+
}
668+
654669
pub(crate) async fn wait_for_populate(&self) -> Result<(), anyhow::Error> {
655670
let mut my_rx = self.populate_status.clone();
656671
loop {

nexus/src/app/sagas/instance_create.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,15 @@ async fn sic_join_instance_multicast_group(
10061006
);
10071007
let instance_id = repeat_saga_params.instance_id;
10081008

1009+
// Check if multicast is enabled
1010+
if !osagactx.nexus().multicast_enabled() {
1011+
debug!(osagactx.log(),
1012+
"multicast not enabled, skipping multicast group member attachment";
1013+
"instance_id" => %instance_id,
1014+
"group_name_or_id" => ?group_name_or_id);
1015+
return Ok(Some(()));
1016+
}
1017+
10091018
// Look up the multicast group by name or ID using the existing nexus method
10101019
let multicast_group_selector = params::MulticastGroupSelector {
10111020
project: Some(NameOrId::Id(saga_params.project_id)),
@@ -1075,6 +1084,14 @@ async fn sic_join_instance_multicast_group_undo(
10751084
return Ok(());
10761085
};
10771086

1087+
// Check if multicast is enabled - if not, no cleanup needed since we didn't attach
1088+
if !osagactx.nexus().multicast_enabled() {
1089+
debug!(osagactx.log(),
1090+
"multicast not enabled, skipping multicast group member undo";
1091+
"group_name_or_id" => ?group_name_or_id);
1092+
return Ok(());
1093+
}
1094+
10781095
// Look up the multicast group by name or ID using the existing nexus method
10791096
let multicast_group_selector = params::MulticastGroupSelector {
10801097
project: Some(NameOrId::Id(saga_params.project_id)),

nexus/src/app/sagas/instance_delete.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ async fn sid_leave_multicast_groups(
150150

151151
let instance_id = params.authz_instance.id();
152152

153+
// Check if multicast is enabled - if not, no members exist to remove
154+
if !osagactx.nexus().multicast_enabled() {
155+
debug!(osagactx.log(),
156+
"multicast not enabled, skipping multicast group member removal";
157+
"instance_id" => %instance_id);
158+
return Ok(());
159+
}
160+
153161
// Mark all multicast group memberships for this instance as deleted
154162
datastore
155163
.multicast_group_members_mark_for_removal(&opctx, instance_id)

0 commit comments

Comments
 (0)