@@ -428,5 +428,183 @@ var _ = Describe("ENI Subnet Discovery Enhanced Tests", func() {
428428 time .Sleep (time .Second * 90 )
429429 })
430430 })
431+
432+ Context ("when security group tags change after ENI creation (automatic refresh)" , func () {
433+ var (
434+ refreshTestSGID string
435+ testENIID string
436+ )
437+
438+ BeforeEach (func () {
439+ By ("Creating custom security group for refresh testing (initially untagged)" )
440+ createSecurityGroupOutput , err := f .CloudServices .EC2 ().
441+ CreateSecurityGroup (context .TODO (), "cni-refresh-test-sg" , "Test SG for automatic refresh" , f .Options .AWSVPCID )
442+ Expect (err ).ToNot (HaveOccurred ())
443+ refreshTestSGID = * createSecurityGroupOutput .GroupId
444+
445+ By ("Tagging secondary subnet to enable ENI creation there" )
446+ _ , err = f .CloudServices .EC2 ().
447+ CreateTags (
448+ context .TODO (),
449+ []string {createdSubnet },
450+ []ec2types.Tag {
451+ {
452+ Key : aws .String ("kubernetes.io/role/cni" ),
453+ Value : aws .String ("1" ),
454+ },
455+ },
456+ )
457+ Expect (err ).ToNot (HaveOccurred ())
458+ })
459+
460+ AfterEach (func () {
461+ By ("Cleaning up refresh test security group" )
462+ if refreshTestSGID != "" {
463+ err := f .CloudServices .EC2 ().DeleteSecurityGroup (context .TODO (), refreshTestSGID )
464+ if err != nil {
465+ GinkgoWriter .Printf ("Warning: Failed to delete refresh test SG %s: %v\n " , refreshTestSGID , err )
466+ }
467+ }
468+
469+ By ("Removing tags from secondary subnet" )
470+ _ , err = f .CloudServices .EC2 ().
471+ DeleteTags (
472+ context .TODO (),
473+ []string {createdSubnet },
474+ []ec2types.Tag {
475+ {
476+ Key : aws .String ("kubernetes.io/role/cni" ),
477+ Value : aws .String ("1" ),
478+ },
479+ },
480+ )
481+ Expect (err ).ToNot (HaveOccurred ())
482+ })
483+
484+ It ("should automatically apply newly tagged custom security groups to existing secondary ENIs" , func () {
485+ By ("creating deployment to force secondary ENI creation" )
486+ container := manifest .NewNetCatAlpineContainer (f .Options .TestImageRegistry ).
487+ Command ([]string {"sleep" }).
488+ Args ([]string {"3600" }).
489+ Build ()
490+
491+ deploymentBuilder := manifest .NewBusyBoxDeploymentBuilder (f .Options .TestImageRegistry ).
492+ Container (container ).
493+ Replicas (25 ). // Enough to require secondary ENIs
494+ PodLabel ("refresh-test" , "sg-auto-refresh" ).
495+ NodeName (* primaryInstance .PrivateDnsName ).
496+ Build ()
497+
498+ deployment , err = f .K8sResourceManagers .DeploymentManager ().
499+ CreateAndWaitTillDeploymentIsReady (deploymentBuilder , utils .DefaultDeploymentReadyTimeout )
500+ Expect (err ).ToNot (HaveOccurred ())
501+
502+ defer func () {
503+ err = f .K8sResourceManagers .DeploymentManager ().DeleteAndWaitTillDeploymentIsDeleted (deployment )
504+ Expect (err ).ToNot (HaveOccurred ())
505+ }()
506+
507+ // Allow deployment to stabilize and ENIs to be created
508+ time .Sleep (15 * time .Second )
509+
510+ By ("finding secondary ENI created in the tagged secondary subnet" )
511+ var secondaryENIs []string
512+ Eventually (func () bool {
513+ instance , err := f .CloudServices .EC2 ().DescribeInstance (context .TODO (), * primaryInstance .InstanceId )
514+ if err != nil {
515+ return false
516+ }
517+
518+ secondaryENIs = []string {}
519+ for _ , nwInterface := range instance .NetworkInterfaces {
520+ if ! common .IsPrimaryENI (nwInterface , instance .PrivateIpAddress ) && * nwInterface .SubnetId == createdSubnet {
521+ secondaryENIs = append (secondaryENIs , * nwInterface .NetworkInterfaceId )
522+ if testENIID == "" {
523+ testENIID = * nwInterface .NetworkInterfaceId
524+ }
525+ }
526+ }
527+ return len (secondaryENIs ) > 0
528+ }, time .Minute * 2 , time .Second * 10 ).Should (BeTrue (), "Should create at least one secondary ENI in tagged subnet" )
529+
530+ By ("verifying secondary ENI initially uses primary security groups" )
531+ var primarySGs []string
532+ instance , err := f .CloudServices .EC2 ().DescribeInstance (context .TODO (), * primaryInstance .InstanceId )
533+ Expect (err ).ToNot (HaveOccurred ())
534+
535+ // Get primary ENI security groups
536+ for _ , nwInterface := range instance .NetworkInterfaces {
537+ if common .IsPrimaryENI (nwInterface , instance .PrivateIpAddress ) {
538+ for _ , sg := range nwInterface .Groups {
539+ primarySGs = append (primarySGs , * sg .GroupId )
540+ }
541+ break
542+ }
543+ }
544+
545+ // Verify secondary ENI has primary SGs initially (and not the refresh test SG)
546+ Eventually (func () []string {
547+ eni , err := f .CloudServices .EC2 ().DescribeNetworkInterface (context .TODO (), []string {testENIID })
548+ if err != nil || len (eni .NetworkInterfaces ) == 0 {
549+ return nil
550+ }
551+ var sgIDs []string
552+ for _ , sg := range eni .NetworkInterfaces [0 ].Groups {
553+ sgIDs = append (sgIDs , * sg .GroupId )
554+ }
555+ return sgIDs
556+ }, time .Second * 30 , time .Second * 5 ).Should (And (
557+ ContainElements (primarySGs ),
558+ Not (ContainElement (refreshTestSGID )),
559+ ), "Secondary ENI should initially have primary security groups" )
560+
561+ By ("tagging custom security group with kubernetes.io/role/cni=1 to trigger refresh" )
562+ _ , err = f .CloudServices .EC2 ().
563+ CreateTags (
564+ context .TODO (),
565+ []string {refreshTestSGID },
566+ []ec2types.Tag {
567+ {
568+ Key : aws .String ("kubernetes.io/role/cni" ),
569+ Value : aws .String ("1" ),
570+ },
571+ },
572+ )
573+ Expect (err ).ToNot (HaveOccurred ())
574+
575+ By ("waiting for automatic refresh to detect and apply the new custom security group" )
576+ Eventually (func () []string {
577+ eni , err := f .CloudServices .EC2 ().DescribeNetworkInterface (context .TODO (), []string {testENIID })
578+ if err != nil || len (eni .NetworkInterfaces ) == 0 {
579+ GinkgoWriter .Printf ("Error describing ENI %s: %v\n " , testENIID , err )
580+ return nil
581+ }
582+ var sgIDs []string
583+ for _ , sg := range eni .NetworkInterfaces [0 ].Groups {
584+ sgIDs = append (sgIDs , * sg .GroupId )
585+ }
586+ GinkgoWriter .Printf ("Current ENI %s security groups: %v\n " , testENIID , sgIDs )
587+ return sgIDs
588+ }, time .Second * 50 , time .Second * 5 ).Should (And (
589+ ContainElement (refreshTestSGID ),
590+ Not (ContainElements (primarySGs )),
591+ ), "Custom security group should be automatically applied within 50 seconds" )
592+
593+ By ("verifying the change persists after another refresh cycle" )
594+ time .Sleep (35 * time .Second )
595+
596+ eni , err := f .CloudServices .EC2 ().DescribeNetworkInterface (context .TODO (), []string {testENIID })
597+ Expect (err ).ToNot (HaveOccurred ())
598+ Expect (len (eni .NetworkInterfaces )).To (BeNumerically (">" , 0 ))
599+
600+ var finalSGIDs []string
601+ for _ , sg := range eni .NetworkInterfaces [0 ].Groups {
602+ finalSGIDs = append (finalSGIDs , * sg .GroupId )
603+ }
604+
605+ Expect (finalSGIDs ).To (ContainElement (refreshTestSGID ), "Custom SG should persist after additional refresh cycle" )
606+ Expect (finalSGIDs ).ToNot (ContainElements (primarySGs ), "Primary SGs should remain replaced" )
607+ })
608+ })
431609 })
432610})
0 commit comments