@@ -3305,14 +3305,6 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
33053305 mockAddress .EXPECT ().NewDisassociateIpAddressParams ("ip-orphan" ).Return (& cloudstack.DisassociateIpAddressParams {})
33063306 mockAddress .EXPECT ().DisassociateIpAddress (gomock .Any ()).Return (& cloudstack.DisassociateIpAddressResponse {}, nil )
33073307
3308- cs := & CSCloud {
3309- client : & cloudstack.CloudStackClient {
3310- LoadBalancer : mockLB ,
3311- Address : mockAddress ,
3312- },
3313- eventRecorder : record .NewFakeRecorder (10 ),
3314- }
3315-
33163308 service := & corev1.Service {
33173309 ObjectMeta : metav1.ObjectMeta {
33183310 Name : "foo" ,
@@ -3323,6 +3315,15 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
33233315 },
33243316 }
33253317
3318+ cs := & CSCloud {
3319+ client : & cloudstack.CloudStackClient {
3320+ LoadBalancer : mockLB ,
3321+ Address : mockAddress ,
3322+ },
3323+ kclient : fake .NewSimpleClientset (service ),
3324+ eventRecorder : record .NewFakeRecorder (10 ),
3325+ }
3326+
33263327 err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
33273328 if err != nil {
33283329 t .Fatalf ("unexpected error: %v" , err )
@@ -3336,19 +3337,21 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
33363337 mockLB := cloudstack .NewMockLoadBalancerServiceIface (ctrl )
33373338 setupGetLoadBalancerByNameEmpty (mockLB )
33383339
3339- cs := & CSCloud {
3340- client : & cloudstack.CloudStackClient {
3341- LoadBalancer : mockLB ,
3342- },
3343- }
3344-
33453340 service := & corev1.Service {
33463341 ObjectMeta : metav1.ObjectMeta {
33473342 Name : "foo" ,
33483343 Namespace : "default" ,
33493344 },
33503345 }
33513346
3347+ cs := & CSCloud {
3348+ client : & cloudstack.CloudStackClient {
3349+ LoadBalancer : mockLB ,
3350+ },
3351+ kclient : fake .NewSimpleClientset (service ),
3352+ eventRecorder : record .NewFakeRecorder (10 ),
3353+ }
3354+
33523355 err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
33533356 if err != nil {
33543357 t .Fatalf ("unexpected error: %v" , err )
@@ -3370,13 +3373,6 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
33703373 Count : 0 , PublicIpAddresses : []* cloudstack.PublicIpAddress {},
33713374 }, nil )
33723375
3373- cs := & CSCloud {
3374- client : & cloudstack.CloudStackClient {
3375- LoadBalancer : mockLB ,
3376- Address : mockAddress ,
3377- },
3378- }
3379-
33803376 service := & corev1.Service {
33813377 ObjectMeta : metav1.ObjectMeta {
33823378 Name : "foo" ,
@@ -3387,6 +3383,15 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
33873383 },
33883384 }
33893385
3386+ cs := & CSCloud {
3387+ client : & cloudstack.CloudStackClient {
3388+ LoadBalancer : mockLB ,
3389+ Address : mockAddress ,
3390+ },
3391+ kclient : fake .NewSimpleClientset (service ),
3392+ eventRecorder : record .NewFakeRecorder (10 ),
3393+ }
3394+
33903395 err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
33913396 if err != nil {
33923397 t .Fatalf ("unexpected error: %v" , err )
@@ -3421,13 +3426,6 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
34213426 mockAddress .EXPECT ().NewDisassociateIpAddressParams ("ip-orphan" ).Return (& cloudstack.DisassociateIpAddressParams {})
34223427 mockAddress .EXPECT ().DisassociateIpAddress (gomock .Any ()).Return (nil , errors .New ("release failed" ))
34233428
3424- cs := & CSCloud {
3425- client : & cloudstack.CloudStackClient {
3426- LoadBalancer : mockLB ,
3427- Address : mockAddress ,
3428- },
3429- }
3430-
34313429 service := & corev1.Service {
34323430 ObjectMeta : metav1.ObjectMeta {
34333431 Name : "foo" ,
@@ -3438,6 +3436,15 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
34383436 },
34393437 }
34403438
3439+ cs := & CSCloud {
3440+ client : & cloudstack.CloudStackClient {
3441+ LoadBalancer : mockLB ,
3442+ Address : mockAddress ,
3443+ },
3444+ kclient : fake .NewSimpleClientset (service ),
3445+ eventRecorder : record .NewFakeRecorder (10 ),
3446+ }
3447+
34413448 err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
34423449 if err == nil {
34433450 t .Fatalf ("expected error" )
@@ -3466,28 +3473,192 @@ func TestEnsureLoadBalancerDeletedOrphanedIP(t *testing.T) {
34663473 }, nil )
34673474 // shouldReleaseLoadBalancerIP returns false because keep-ip annotation is set
34683475
3476+ service := & corev1.Service {
3477+ ObjectMeta : metav1.ObjectMeta {
3478+ Name : "foo" ,
3479+ Namespace : "default" ,
3480+ Annotations : map [string ]string {
3481+ ServiceAnnotationLoadBalancerAddress : "10.0.0.1" ,
3482+ ServiceAnnotationLoadBalancerKeepIP : "true" ,
3483+ },
3484+ },
3485+ }
3486+
34693487 cs := & CSCloud {
34703488 client : & cloudstack.CloudStackClient {
34713489 LoadBalancer : mockLB ,
34723490 Address : mockAddress ,
34733491 },
3492+ kclient : fake .NewSimpleClientset (service ),
3493+ eventRecorder : record .NewFakeRecorder (10 ),
34743494 }
34753495
3496+ err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
3497+ if err != nil {
3498+ t .Fatalf ("unexpected error: %v" , err )
3499+ }
3500+ })
3501+ }
3502+
3503+ func TestEnsureLoadBalancerDeletedAnnotationCleanup (t * testing.T ) {
3504+ // allLBAnnotations returns a map with all 6 CloudStack LB annotations set.
3505+ allLBAnnotations := func () map [string ]string {
3506+ return map [string ]string {
3507+ ServiceAnnotationLoadBalancerProxyProtocol : "true" ,
3508+ ServiceAnnotationLoadBalancerLoadbalancerHostname : "lb.example.com" ,
3509+ ServiceAnnotationLoadBalancerAddress : "10.0.0.1" ,
3510+ ServiceAnnotationLoadBalancerKeepIP : "false" ,
3511+ ServiceAnnotationLoadBalancerID : "ip-1" ,
3512+ ServiceAnnotationLoadBalancerNetworkID : "net-1" ,
3513+ }
3514+ }
3515+
3516+ lbAnnotationKeys := []string {
3517+ ServiceAnnotationLoadBalancerProxyProtocol ,
3518+ ServiceAnnotationLoadBalancerLoadbalancerHostname ,
3519+ ServiceAnnotationLoadBalancerAddress ,
3520+ ServiceAnnotationLoadBalancerKeepIP ,
3521+ ServiceAnnotationLoadBalancerID ,
3522+ ServiceAnnotationLoadBalancerNetworkID ,
3523+ }
3524+
3525+ t .Run ("annotations removed after successful deletion" , func (t * testing.T ) {
3526+ ctrl := gomock .NewController (t )
3527+ t .Cleanup (ctrl .Finish )
3528+
3529+ mockLB := cloudstack .NewMockLoadBalancerServiceIface (ctrl )
3530+ mockAddress := cloudstack .NewMockAddressServiceIface (ctrl )
3531+ mockFirewall := cloudstack .NewMockFirewallServiceIface (ctrl )
3532+
3533+ // getLoadBalancerByName returns one rule
3534+ mockLB .EXPECT ().NewListLoadBalancerRulesParams ().Return (& cloudstack.ListLoadBalancerRulesParams {})
3535+ mockLB .EXPECT ().ListLoadBalancerRules (gomock .Any ()).Return (& cloudstack.ListLoadBalancerRulesResponse {
3536+ Count : 1 ,
3537+ LoadBalancerRules : []* cloudstack.LoadBalancerRule {
3538+ {
3539+ Id : "rule-1" , Name : "K8s_svc_cluster_default_foo-tcp-80" ,
3540+ Publicip : "10.0.0.1" , Publicipid : "ip-1" , Publicport : "80" ,
3541+ Protocol : "tcp" , Networkid : "net-1" ,
3542+ },
3543+ },
3544+ }, nil )
3545+
3546+ // deleteFirewallRule
3547+ mockFirewall .EXPECT ().NewListFirewallRulesParams ().Return (& cloudstack.ListFirewallRulesParams {})
3548+ mockFirewall .EXPECT ().ListFirewallRules (gomock .Any ()).Return (& cloudstack.ListFirewallRulesResponse {
3549+ Count : 0 , FirewallRules : []* cloudstack.FirewallRule {},
3550+ }, nil )
3551+
3552+ // deleteLoadBalancerRule
3553+ mockLB .EXPECT ().NewDeleteLoadBalancerRuleParams ("rule-1" ).Return (& cloudstack.DeleteLoadBalancerRuleParams {})
3554+ mockLB .EXPECT ().DeleteLoadBalancerRule (gomock .Any ()).Return (& cloudstack.DeleteLoadBalancerRuleResponse {}, nil )
3555+
3556+ // shouldReleaseLoadBalancerIP: no keep-ip, no other rules
3557+ mockLB .EXPECT ().NewListLoadBalancerRulesParams ().Return (& cloudstack.ListLoadBalancerRulesParams {})
3558+ mockLB .EXPECT ().ListLoadBalancerRules (gomock .Any ()).Return (& cloudstack.ListLoadBalancerRulesResponse {
3559+ Count : 0 , LoadBalancerRules : []* cloudstack.LoadBalancerRule {},
3560+ }, nil )
3561+
3562+ // releaseLoadBalancerIP
3563+ mockAddress .EXPECT ().NewDisassociateIpAddressParams ("ip-1" ).Return (& cloudstack.DisassociateIpAddressParams {})
3564+ mockAddress .EXPECT ().DisassociateIpAddress (gomock .Any ()).Return (& cloudstack.DisassociateIpAddressResponse {}, nil )
3565+
34763566 service := & corev1.Service {
34773567 ObjectMeta : metav1.ObjectMeta {
3478- Name : "foo" ,
3479- Namespace : "default" ,
3480- Annotations : map [string ]string {
3481- ServiceAnnotationLoadBalancerAddress : "10.0.0.1" ,
3482- ServiceAnnotationLoadBalancerKeepIP : "true" ,
3483- },
3568+ Name : "foo" ,
3569+ Namespace : "default" ,
3570+ Annotations : allLBAnnotations (),
34843571 },
34853572 }
34863573
3574+ cs := & CSCloud {
3575+ client : & cloudstack.CloudStackClient {
3576+ LoadBalancer : mockLB ,
3577+ Address : mockAddress ,
3578+ Firewall : mockFirewall ,
3579+ },
3580+ kclient : fake .NewSimpleClientset (service ),
3581+ eventRecorder : record .NewFakeRecorder (10 ),
3582+ }
3583+
34873584 err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
34883585 if err != nil {
34893586 t .Fatalf ("unexpected error: %v" , err )
34903587 }
3588+
3589+ for _ , key := range lbAnnotationKeys {
3590+ if _ , ok := service .Annotations [key ]; ok {
3591+ t .Errorf ("annotation %q should have been removed" , key )
3592+ }
3593+ }
3594+ })
3595+
3596+ t .Run ("annotations preserved when deletion fails" , func (t * testing.T ) {
3597+ ctrl := gomock .NewController (t )
3598+ t .Cleanup (ctrl .Finish )
3599+
3600+ mockLB := cloudstack .NewMockLoadBalancerServiceIface (ctrl )
3601+ mockAddress := cloudstack .NewMockAddressServiceIface (ctrl )
3602+ mockFirewall := cloudstack .NewMockFirewallServiceIface (ctrl )
3603+
3604+ // getLoadBalancerByName returns one rule
3605+ mockLB .EXPECT ().NewListLoadBalancerRulesParams ().Return (& cloudstack.ListLoadBalancerRulesParams {}).Times (2 )
3606+ mockLB .EXPECT ().ListLoadBalancerRules (gomock .Any ()).Return (& cloudstack.ListLoadBalancerRulesResponse {
3607+ Count : 1 ,
3608+ LoadBalancerRules : []* cloudstack.LoadBalancerRule {
3609+ {
3610+ Id : "rule-1" , Name : "K8s_svc_cluster_default_foo-tcp-80" ,
3611+ Publicip : "10.0.0.1" , Publicipid : "ip-1" , Publicport : "80" ,
3612+ Protocol : "tcp" , Networkid : "net-1" ,
3613+ },
3614+ },
3615+ }, nil ).Times (1 )
3616+
3617+ // deleteFirewallRule fails
3618+ mockFirewall .EXPECT ().NewListFirewallRulesParams ().Return (& cloudstack.ListFirewallRulesParams {})
3619+ mockFirewall .EXPECT ().ListFirewallRules (gomock .Any ()).Return (nil , errors .New ("firewall error" ))
3620+
3621+ // deleteLoadBalancerRule fails
3622+ mockLB .EXPECT ().NewDeleteLoadBalancerRuleParams ("rule-1" ).Return (& cloudstack.DeleteLoadBalancerRuleParams {})
3623+ mockLB .EXPECT ().DeleteLoadBalancerRule (gomock .Any ()).Return (nil , errors .New ("delete rule error" ))
3624+
3625+ // shouldReleaseLoadBalancerIP is still called (IP cleanup attempted even on rule errors)
3626+ mockLB .EXPECT ().ListLoadBalancerRules (gomock .Any ()).Return (& cloudstack.ListLoadBalancerRulesResponse {
3627+ Count : 0 , LoadBalancerRules : []* cloudstack.LoadBalancerRule {},
3628+ }, nil )
3629+
3630+ // releaseLoadBalancerIP also fails
3631+ mockAddress .EXPECT ().NewDisassociateIpAddressParams ("ip-1" ).Return (& cloudstack.DisassociateIpAddressParams {})
3632+ mockAddress .EXPECT ().DisassociateIpAddress (gomock .Any ()).Return (nil , errors .New ("release failed" ))
3633+
3634+ service := & corev1.Service {
3635+ ObjectMeta : metav1.ObjectMeta {
3636+ Name : "foo" ,
3637+ Namespace : "default" ,
3638+ Annotations : allLBAnnotations (),
3639+ },
3640+ }
3641+
3642+ cs := & CSCloud {
3643+ client : & cloudstack.CloudStackClient {
3644+ LoadBalancer : mockLB ,
3645+ Address : mockAddress ,
3646+ Firewall : mockFirewall ,
3647+ },
3648+ kclient : fake .NewSimpleClientset (service ),
3649+ eventRecorder : record .NewFakeRecorder (10 ),
3650+ }
3651+
3652+ err := cs .EnsureLoadBalancerDeleted (t .Context (), "cluster" , service )
3653+ if err == nil {
3654+ t .Fatalf ("expected error" )
3655+ }
3656+
3657+ for _ , key := range lbAnnotationKeys {
3658+ if _ , ok := service .Annotations [key ]; ! ok {
3659+ t .Errorf ("annotation %q should have been preserved on error" , key )
3660+ }
3661+ }
34913662 })
34923663}
34933664
0 commit comments