@@ -599,7 +599,6 @@ async fn test_internal_ipv6_validation() {
599
599
assert_eq ! ( updated. tag, Some ( "updated_tag" . to_string( ) ) ) ;
600
600
assert_eq ! ( updated. ext_fwding. vlan_id, None ) ;
601
601
602
- // Cleanup
603
602
cleanup_test_group ( switch, created. group_ip ) . await ;
604
603
}
605
604
@@ -685,7 +684,6 @@ async fn test_vlan_propagation_to_internal() {
685
684
"Admin-scoped group bitmap should have VLAN 42 from external group"
686
685
) ;
687
686
688
- // Cleanup
689
687
cleanup_test_group ( switch, created_admin. group_ip ) . await ;
690
688
cleanup_test_group ( switch, created_external. group_ip ) . await ;
691
689
}
@@ -695,7 +693,6 @@ async fn test_vlan_propagation_to_internal() {
695
693
async fn test_group_api_lifecycle ( ) {
696
694
let switch = & * get_switch ( ) . await ;
697
695
698
- // Create admin-scoped IPv6 group for underlay replication infrastructure
699
696
let egress1 = PhysPort ( 28 ) ;
700
697
let internal_multicast_ip = IpAddr :: V6 ( MULTICAST_NAT_IP ) ;
701
698
let underlay_group = create_test_multicast_group (
@@ -1330,7 +1327,6 @@ async fn test_api_invalid_combinations() {
1330
1327
) ,
1331
1328
}
1332
1329
1333
- // Cleanup
1334
1330
cleanup_test_group ( switch, created_ipv4. group_ip ) . await ;
1335
1331
cleanup_test_group ( switch, created_non_admin. group_ip ) . await ;
1336
1332
cleanup_test_group ( switch, internal_multicast_ip) . await ;
@@ -2024,7 +2020,6 @@ async fn test_encapped_multicast_geneve_mcast_tag_to_external_members(
2024
2020
. await
2025
2021
. unwrap ( ) ;
2026
2022
2027
- // Run the test
2028
2023
let result = switch. packet_test ( vec ! [ test_pkt] , expected_pkts) ;
2029
2024
2030
2025
check_counter_incremented (
@@ -2163,7 +2158,6 @@ async fn test_encapped_multicast_geneve_mcast_tag_to_underlay_members(
2163
2158
. await
2164
2159
. unwrap ( ) ;
2165
2160
2166
- // Run the test
2167
2161
let result = switch. packet_test ( vec ! [ test_pkt] , expected_pkts) ;
2168
2162
2169
2163
check_counter_incremented (
@@ -2320,7 +2314,6 @@ async fn test_encapped_multicast_geneve_mcast_tag_to_underlay_and_external_membe
2320
2314
. await
2321
2315
. unwrap ( ) ;
2322
2316
2323
- // Run the test
2324
2317
let result = switch. packet_test ( vec ! [ test_pkt] , expected_pkts) ;
2325
2318
2326
2319
check_counter_incremented (
@@ -4335,7 +4328,6 @@ async fn test_external_group_nat_target_validation() {
4335
4328
"External group should have no members"
4336
4329
) ;
4337
4330
4338
- // Cleanup
4339
4331
cleanup_test_group ( switch, created_admin. group_ip ) . await ;
4340
4332
cleanup_test_group ( switch, created_external. group_ip ) . await ;
4341
4333
}
@@ -4510,3 +4502,131 @@ async fn test_ipv6_multicast_scope_validation() {
4510
4502
. await
4511
4503
. ok ( ) ;
4512
4504
}
4505
+
4506
+ #[ tokio:: test]
4507
+ #[ ignore]
4508
+ async fn test_multicast_group_id_recycling ( ) {
4509
+ let switch = & * get_switch ( ) . await ;
4510
+
4511
+ // Use admin-scoped IPv6 addresses that get group IDs assigned
4512
+ let group1_ip = IpAddr :: V6 ( Ipv6Addr :: new ( 0xff05 , 0 , 0 , 0 , 0 , 0 , 0 , 10 ) ) ;
4513
+ let group2_ip = IpAddr :: V6 ( Ipv6Addr :: new ( 0xff05 , 0 , 0 , 0 , 0 , 0 , 0 , 11 ) ) ;
4514
+ let group3_ip = IpAddr :: V6 ( Ipv6Addr :: new ( 0xff05 , 0 , 0 , 0 , 0 , 0 , 0 , 12 ) ) ;
4515
+
4516
+ // Create first group and capture its group IDs
4517
+ let group1 = create_test_multicast_group (
4518
+ switch,
4519
+ group1_ip,
4520
+ Some ( "test_recycling_1" ) ,
4521
+ & [ ( PhysPort ( 11 ) , types:: Direction :: External ) ] ,
4522
+ None ,
4523
+ false ,
4524
+ None ,
4525
+ )
4526
+ . await ;
4527
+
4528
+ let group1_external_id = group1. external_group_id ;
4529
+ assert ! ( group1_external_id. is_some( ) ) ;
4530
+
4531
+ // Create second group and capture its group IDs
4532
+ let group2 = create_test_multicast_group (
4533
+ switch,
4534
+ group2_ip,
4535
+ Some ( "test_recycling_2" ) ,
4536
+ & [ ( PhysPort ( 12 ) , types:: Direction :: External ) ] ,
4537
+ None ,
4538
+ false ,
4539
+ None ,
4540
+ )
4541
+ . await ;
4542
+
4543
+ let group2_external_id = group2. external_group_id ;
4544
+ assert ! ( group2_external_id. is_some( ) ) ;
4545
+ assert_ne ! ( group1_external_id, group2_external_id) ;
4546
+
4547
+ // Delete the first group
4548
+ switch
4549
+ . client
4550
+ . multicast_group_delete ( & group1_ip)
4551
+ . await
4552
+ . expect ( "Should be able to delete first group" ) ;
4553
+
4554
+ // Verify group1 was actually deleted
4555
+ let groups_after_delete1 = switch
4556
+ . client
4557
+ . multicast_groups_list_stream ( None )
4558
+ . try_collect :: < Vec < _ > > ( )
4559
+ . await
4560
+ . expect ( "Should be able to list groups" ) ;
4561
+ assert ! (
4562
+ !groups_after_delete1. iter( ) . any( |g| g. group_ip == group1_ip) ,
4563
+ "Group1 should be deleted"
4564
+ ) ;
4565
+
4566
+ // Create third group - should reuse the first group's ID
4567
+ let group3 = create_test_multicast_group (
4568
+ switch,
4569
+ group3_ip,
4570
+ Some ( "test_recycling_3" ) ,
4571
+ & [ ( PhysPort ( 13 ) , types:: Direction :: External ) ] ,
4572
+ None ,
4573
+ false ,
4574
+ None ,
4575
+ )
4576
+ . await ;
4577
+
4578
+ let group3_external_id = group3. external_group_id ;
4579
+ assert ! ( group3_external_id. is_some( ) ) ;
4580
+
4581
+ // Verify that ID recycling is working - group3 should get an ID that was
4582
+ // previously used
4583
+ assert_ne ! (
4584
+ group2_external_id, group3_external_id,
4585
+ "Third group should get a different ID than the active second group"
4586
+ ) ;
4587
+
4588
+ // Create a fourth group after deleting group2, it should reuse group2's ID
4589
+ switch
4590
+ . client
4591
+ . multicast_group_delete ( & group2_ip)
4592
+ . await
4593
+ . expect ( "Should be able to delete second group" ) ;
4594
+
4595
+ // Verify group2 was actually deleted
4596
+ let groups_after_delete2 = switch
4597
+ . client
4598
+ . multicast_groups_list_stream ( None )
4599
+ . try_collect :: < Vec < _ > > ( )
4600
+ . await
4601
+ . expect ( "Should be able to list groups" ) ;
4602
+ assert ! (
4603
+ !groups_after_delete2. iter( ) . any( |g| g. group_ip == group2_ip) ,
4604
+ "Group2 should be deleted"
4605
+ ) ;
4606
+
4607
+ let group4_ip = IpAddr :: V6 ( Ipv6Addr :: new ( 0xff05 , 0 , 0 , 0 , 0 , 0 , 0 , 13 ) ) ;
4608
+ let group4 = create_test_multicast_group (
4609
+ switch,
4610
+ group4_ip,
4611
+ Some ( "test_recycling_4" ) ,
4612
+ & [ ( PhysPort ( 14 ) , types:: Direction :: External ) ] ,
4613
+ None ,
4614
+ false ,
4615
+ None ,
4616
+ )
4617
+ . await ;
4618
+
4619
+ let group4_external_id = group4. external_group_id ;
4620
+ assert ! ( group4_external_id. is_some( ) ) ;
4621
+
4622
+ // Group4 should reuse group2's recently freed ID due to stack-like
4623
+ // allocation
4624
+ assert_eq ! (
4625
+ group2_external_id, group4_external_id,
4626
+ "Fourth group should reuse second group's recycled ID"
4627
+ ) ;
4628
+
4629
+ // Cleanup - clean up remaining active groups
4630
+ cleanup_test_group ( switch, group3_ip) . await ;
4631
+ cleanup_test_group ( switch, group4_ip) . await ;
4632
+ }
0 commit comments