Skip to content

Commit e7d1504

Browse files
[perf-cleanup] remove redundant tests and update mcast polling
1 parent f7f3c39 commit e7d1504

File tree

6 files changed

+168
-982
lines changed

6 files changed

+168
-982
lines changed

nexus/tests/integration_tests/multicast/api.rs

Lines changed: 6 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,13 @@ async fn test_join_by_ip_ssm_with_sources(cptestctx: &ControlPlaneTestContext) {
461461
}
462462

463463
/// Test SSM join-by-IP without sources should fail.
464+
///
464465
/// SSM addresses (232.0.0.0/8) require source IPs for implicit creation.
466+
///
467+
/// This is the canonical test for SSM source validation. The validation
468+
/// code path is shared regardless of how you join (by IP, name, or ID) -
469+
/// all routes converge on the same `instance_multicast_group_join` logic
470+
/// that checks `is_ssm_address()` and rejects joins without sources.
465471
#[nexus_test]
466472
async fn test_join_by_ip_ssm_without_sources_fails(
467473
cptestctx: &ControlPlaneTestContext,
@@ -516,158 +522,6 @@ async fn test_join_by_ip_ssm_without_sources_fails(
516522
cleanup_instances(cptestctx, client, project_name, &[instance_name]).await;
517523
}
518524

519-
/// Test joining an existing SSM group by ID without sources should fail.
520-
///
521-
/// This tests the SSM validation for join-by-ID path: if an SSM group exists
522-
/// (created by first instance with sources), a second instance cannot join
523-
/// by group ID without providing sources.
524-
#[nexus_test]
525-
async fn test_join_existing_ssm_group_by_id_without_sources_fails(
526-
cptestctx: &ControlPlaneTestContext,
527-
) {
528-
let client = &cptestctx.external_client;
529-
let project_name = "ssm-id-fail-project";
530-
531-
// Setup: SSM pool
532-
let (_, _, _ssm_pool) = ops::join3(
533-
create_project(client, project_name),
534-
create_default_ip_pool(client),
535-
create_multicast_ip_pool_with_range(
536-
client,
537-
"ssm-id-fail-pool",
538-
(232, 40, 0, 1),
539-
(232, 40, 0, 255),
540-
),
541-
)
542-
.await;
543-
544-
create_instance(client, project_name, "ssm-id-inst-1").await;
545-
create_instance(client, project_name, "ssm-id-inst-2").await;
546-
547-
// First instance creates SSM group with sources
548-
let ssm_ip = "232.40.0.100";
549-
let source_ip: IpAddr = "10.40.0.1".parse().unwrap();
550-
let join_url_1 = format!(
551-
"/v1/instances/ssm-id-inst-1/multicast-groups/{ssm_ip}?project={project_name}"
552-
);
553-
554-
let join_body_1 =
555-
InstanceMulticastGroupJoin { source_ips: Some(vec![source_ip]) };
556-
let member_1: MulticastGroupMember =
557-
put_upsert(client, &join_url_1, &join_body_1).await;
558-
559-
let group_id = member_1.multicast_group_id;
560-
561-
// Second instance tries to join by group ID WITHOUT sources - should fail
562-
let join_url_by_id = format!(
563-
"/v1/instances/ssm-id-inst-2/multicast-groups/{group_id}?project={project_name}"
564-
);
565-
566-
let error = NexusRequest::new(
567-
RequestBuilder::new(client, Method::PUT, &join_url_by_id)
568-
.body(Some(&InstanceMulticastGroupJoin {
569-
source_ips: None, // No sources!
570-
}))
571-
.expect_status(Some(StatusCode::BAD_REQUEST)),
572-
)
573-
.authn_as(AuthnMode::PrivilegedUser)
574-
.execute()
575-
.await
576-
.expect("Join by ID without sources should fail for SSM group");
577-
578-
let error_body: dropshot::HttpErrorResponseBody =
579-
error.parsed_body().unwrap();
580-
assert!(
581-
error_body.message.contains("SSM")
582-
|| error_body.message.contains("source"),
583-
"Error should mention SSM or source IPs: {}",
584-
error_body.message
585-
);
586-
587-
let expected_group_name = format!("mcast-{}", ssm_ip.replace('.', "-"));
588-
cleanup_instances(
589-
cptestctx,
590-
client,
591-
project_name,
592-
&["ssm-id-inst-1", "ssm-id-inst-2"],
593-
)
594-
.await;
595-
wait_for_group_deleted(client, &expected_group_name).await;
596-
}
597-
598-
/// Test joining an existing SSM group by NAME without sources should fail.
599-
#[nexus_test]
600-
async fn test_join_existing_ssm_group_by_name_without_sources_fails(
601-
cptestctx: &ControlPlaneTestContext,
602-
) {
603-
let client = &cptestctx.external_client;
604-
let project_name = "ssm-name-fail-project";
605-
606-
// Setup: SSM pool
607-
let (_, _, _ssm_pool) = ops::join3(
608-
create_project(client, project_name),
609-
create_default_ip_pool(client),
610-
create_multicast_ip_pool_with_range(
611-
client,
612-
"ssm-name-fail-pool",
613-
(232, 45, 0, 1),
614-
(232, 45, 0, 100),
615-
),
616-
)
617-
.await;
618-
619-
create_instance(client, project_name, "ssm-name-inst-1").await;
620-
create_instance(client, project_name, "ssm-name-inst-2").await;
621-
622-
// First instance creates SSM group with sources
623-
let ssm_ip = "232.45.0.50";
624-
let join_url = format!(
625-
"/v1/instances/ssm-name-inst-1/multicast-groups/{ssm_ip}?project={project_name}"
626-
);
627-
let join_body = InstanceMulticastGroupJoin {
628-
source_ips: Some(vec!["10.0.0.1".parse().unwrap()]),
629-
};
630-
631-
put_upsert::<_, MulticastGroupMember>(client, &join_url, &join_body).await;
632-
633-
// Get the group's auto-generated name
634-
let expected_group_name = format!("mcast-{}", ssm_ip.replace('.', "-"));
635-
636-
// Second instance tries to join by NAME without sources - should fail
637-
let join_by_name_url = format!(
638-
"/v1/instances/ssm-name-inst-2/multicast-groups/{expected_group_name}?project={project_name}"
639-
);
640-
let join_body_no_sources = InstanceMulticastGroupJoin { source_ips: None };
641-
642-
let error = NexusRequest::new(
643-
RequestBuilder::new(client, Method::PUT, &join_by_name_url)
644-
.body(Some(&join_body_no_sources))
645-
.expect_status(Some(StatusCode::BAD_REQUEST)),
646-
)
647-
.authn_as(AuthnMode::PrivilegedUser)
648-
.execute()
649-
.await
650-
.expect("Join by name without sources should fail for SSM group");
651-
652-
let error_body: dropshot::HttpErrorResponseBody =
653-
error.parsed_body().unwrap();
654-
assert!(
655-
error_body.message.contains("SSM")
656-
|| error_body.message.contains("source"),
657-
"Error should mention SSM or source IPs: {}",
658-
error_body.message
659-
);
660-
661-
cleanup_instances(
662-
cptestctx,
663-
client,
664-
project_name,
665-
&["ssm-name-inst-1", "ssm-name-inst-2"],
666-
)
667-
.await;
668-
wait_for_group_deleted(client, &expected_group_name).await;
669-
}
670-
671525
/// Test that SSM join-by-IP with empty sources array fails.
672526
///
673527
/// `source_ips: Some(vec![])` (empty array) is treated the same as
@@ -725,83 +579,6 @@ async fn test_ssm_with_empty_sources_array_fails(
725579
cleanup_instances(cptestctx, client, project_name, &[instance_name]).await;
726580
}
727581

728-
/// Test joining an existing SSM group by IP without sources fails.
729-
///
730-
/// When an SSM group already exists (created by first instance with sources),
731-
/// a second instance joining by IP should still fail without sources since
732-
/// the group is SSM.
733-
#[nexus_test]
734-
async fn test_join_existing_ssm_group_by_ip_without_sources_fails(
735-
cptestctx: &ControlPlaneTestContext,
736-
) {
737-
let client = &cptestctx.external_client;
738-
let project_name = "ssm-ip-existing-fail-project";
739-
740-
// Setup: SSM pool
741-
let (_, _, _ssm_pool) = ops::join3(
742-
create_project(client, project_name),
743-
create_default_ip_pool(client),
744-
create_multicast_ip_pool_with_range(
745-
client,
746-
"ssm-ip-existing-fail-pool",
747-
(232, 47, 0, 1),
748-
(232, 47, 0, 100),
749-
),
750-
)
751-
.await;
752-
753-
create_instance(client, project_name, "ssm-ip-inst-1").await;
754-
create_instance(client, project_name, "ssm-ip-inst-2").await;
755-
756-
// First instance creates SSM group with sources
757-
let ssm_ip = "232.47.0.50";
758-
let join_url = format!(
759-
"/v1/instances/ssm-ip-inst-1/multicast-groups/{ssm_ip}?project={project_name}"
760-
);
761-
let join_body = InstanceMulticastGroupJoin {
762-
source_ips: Some(vec!["10.0.0.1".parse().unwrap()]),
763-
};
764-
765-
put_upsert::<_, MulticastGroupMember>(client, &join_url, &join_body).await;
766-
767-
let expected_group_name = format!("mcast-{}", ssm_ip.replace('.', "-"));
768-
769-
// Second instance tries to join by IP without sources - should fail
770-
// Even though the group exists, SSM still requires sources
771-
let join_url_2 = format!(
772-
"/v1/instances/ssm-ip-inst-2/multicast-groups/{ssm_ip}?project={project_name}"
773-
);
774-
let join_body_no_sources = InstanceMulticastGroupJoin { source_ips: None };
775-
776-
let error = NexusRequest::new(
777-
RequestBuilder::new(client, Method::PUT, &join_url_2)
778-
.body(Some(&join_body_no_sources))
779-
.expect_status(Some(StatusCode::BAD_REQUEST)),
780-
)
781-
.authn_as(AuthnMode::PrivilegedUser)
782-
.execute()
783-
.await
784-
.expect("Join existing SSM group by IP without sources should fail");
785-
786-
let error_body: dropshot::HttpErrorResponseBody =
787-
error.parsed_body().unwrap();
788-
assert!(
789-
error_body.message.contains("SSM")
790-
|| error_body.message.contains("source"),
791-
"Error should mention SSM or source IPs: {}",
792-
error_body.message
793-
);
794-
795-
cleanup_instances(
796-
cptestctx,
797-
client,
798-
project_name,
799-
&["ssm-ip-inst-1", "ssm-ip-inst-2"],
800-
)
801-
.await;
802-
wait_for_group_deleted(client, &expected_group_name).await;
803-
}
804-
805582
/// Test join-by-IP with IP not in any pool should fail.
806583
#[nexus_test]
807584
async fn test_join_by_ip_not_in_pool_fails(

nexus/tests/integration_tests/multicast/cache_invalidation.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,15 @@ async fn test_cache_ttl_driven_refresh() {
284284
"test_cache_ttl_driven_refresh",
285285
)
286286
.customize_nexus_config(&|config| {
287-
// Set short cache TTLs for testing (2 seconds for sled cache)
287+
// Set short cache TTLs for testing
288288
config.pkg.background_tasks.multicast_reconciler.sled_cache_ttl_secs =
289-
chrono::TimeDelta::seconds(2).to_std().unwrap();
289+
chrono::TimeDelta::milliseconds(500).to_std().unwrap();
290290
config
291291
.pkg
292292
.background_tasks
293293
.multicast_reconciler
294294
.backplane_cache_ttl_secs =
295-
chrono::TimeDelta::seconds(1).to_std().unwrap();
295+
chrono::TimeDelta::milliseconds(250).to_std().unwrap();
296296

297297
// Ensure multicast is enabled
298298
config.pkg.multicast.enabled = true;
@@ -435,9 +435,8 @@ async fn test_cache_ttl_driven_refresh() {
435435
.await
436436
.expect("Should insert new inventory collection");
437437

438-
// Wait for cache TTL to expire (sled_cache_ttl = 1 second)
439-
// Sleep for 1.5 seconds to ensure TTL has expired
440-
tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
438+
// Wait for cache TTL to expire (sled_cache_ttl = 500ms)
439+
tokio::time::sleep(std::time::Duration::from_millis(600)).await;
441440

442441
wait_for_condition_with_reconciler(
443442
&cptestctx.lockstep_client,
@@ -487,19 +486,17 @@ async fn test_backplane_cache_ttl_expiry() {
487486
"test_backplane_cache_ttl_expiry",
488487
)
489488
.customize_nexus_config(&|config| {
490-
// Set backplane cache TTL to 1 second (shorter than sled cache to test
491-
// independently)
489+
// Set backplane cache TTL short (shorter than sled cache to test independently)
492490
config
493491
.pkg
494492
.background_tasks
495493
.multicast_reconciler
496494
.backplane_cache_ttl_secs =
497-
chrono::TimeDelta::seconds(1).to_std().unwrap();
495+
chrono::TimeDelta::milliseconds(250).to_std().unwrap();
498496

499-
// Keep sled cache TTL longer to ensure we're testing backplane cache
500-
// expiry
497+
// Keep sled cache TTL longer to ensure we're testing backplane cache expiry
501498
config.pkg.background_tasks.multicast_reconciler.sled_cache_ttl_secs =
502-
chrono::TimeDelta::seconds(10).to_std().unwrap();
499+
chrono::TimeDelta::seconds(2).to_std().unwrap();
503500

504501
// Ensure multicast is enabled
505502
config.pkg.multicast.enabled = true;
@@ -550,9 +547,8 @@ async fn test_backplane_cache_ttl_expiry() {
550547
.await
551548
.expect("Should verify initial port mapping");
552549

553-
// Wait for backplane cache TTL to expire (500ms) but not sled cache (5 seconds)
554-
// Sleep for 1 second to ensure backplane TTL has expired
555-
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
550+
// Wait for backplane cache TTL to expire (250ms) but not sled cache (2 seconds)
551+
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
556552

557553
// Force cache access by triggering reconciler
558554
// This will cause the reconciler to check backplane cache, find it expired,

0 commit comments

Comments
 (0)