@@ -27,6 +27,7 @@ import (
2727 "strings"
2828
2929 "github.com/apache/cloudstack-go/v2/cloudstack"
30+ "github.com/blang/semver/v4"
3031 "k8s.io/klog/v2"
3132
3233 corev1 "k8s.io/api/core/v1"
@@ -44,7 +45,12 @@ const (
4445 // CloudStack >= 4.6 is required for it to work.
4546 ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"
4647 ServiceAnnotationLoadBalancerLoadbalancerHostname = "service.beta.kubernetes.io/cloudstack-load-balancer-hostname"
47- ServiceAnnotationLoadBalancerSourceCidrs = "service.beta.kubernetes.io/cloudstack-load-balancer-source-cidrs"
48+
49+ // ServiceAnnotationLoadBalancerSourceCidrs is the annotation used on the
50+ // service to specify the source CIDR list for a CloudStack load balancer.
51+ // The CIDR list is a comma-separated list of CIDR ranges (e.g., "10.0.0.0/8,192.168.1.0/24").
52+ // If not specified, the default is to allow all sources ("0.0.0.0/0").
53+ ServiceAnnotationLoadBalancerSourceCidrs = "service.beta.kubernetes.io/cloudstack-load-balancer-source-cidrs"
4854)
4955
5056type loadBalancer struct {
@@ -143,15 +149,15 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
143149 lbRuleName := fmt .Sprintf ("%s-%s-%d" , lb .name , protocol , port .Port )
144150
145151 // If the load balancer rule exists and is up-to-date, we move on to the next rule.
146- lbRule , needsUpdate , err := lb .checkLoadBalancerRule (lbRuleName , port , protocol )
152+ lbRule , needsUpdate , err := lb .checkLoadBalancerRule (lbRuleName , port , protocol , service , cs . version )
147153 if err != nil {
148154 return nil , err
149155 }
150156
151157 if lbRule != nil {
152158 if needsUpdate {
153159 klog .V (4 ).Infof ("Updating load balancer rule: %v" , lbRuleName )
154- if err := lb .updateLoadBalancerRule (lbRuleName , protocol ); err != nil {
160+ if err := lb .updateLoadBalancerRule (lbRuleName , protocol , service , cs . version ); err != nil {
155161 return nil , err
156162 }
157163 // Delete the rule from the map, to prevent it being deleted.
@@ -561,37 +567,111 @@ func (lb *loadBalancer) releaseLoadBalancerIP() error {
561567 return nil
562568}
563569
570+ func (lb * loadBalancer ) getCIDRList (service * corev1.Service ) ([]string , error ) {
571+ sourceCIDRs := getStringFromServiceAnnotation (service , ServiceAnnotationLoadBalancerSourceCidrs , defaultAllowedCIDR )
572+ var cidrList []string
573+ if sourceCIDRs != "" {
574+ cidrList = strings .Split (sourceCIDRs , "," )
575+ for i , cidr := range cidrList {
576+ cidr = strings .TrimSpace (cidr )
577+ if _ , _ , err := net .ParseCIDR (cidr ); err != nil {
578+ return nil , fmt .Errorf ("invalid CIDR %s in annotation %s: %w" , cidr , ServiceAnnotationLoadBalancerSourceCidrs , err )
579+ }
580+ cidrList [i ] = cidr
581+ }
582+ }
583+ return cidrList , nil
584+ }
585+
564586// checkLoadBalancerRule checks if the rule already exists and if it does, if it can be updated. If
565587// it does exist but cannot be updated, it will delete the existing rule so it can be created again.
566- func (lb * loadBalancer ) checkLoadBalancerRule (lbRuleName string , port corev1.ServicePort , protocol LoadBalancerProtocol ) (* cloudstack.LoadBalancerRule , bool , error ) {
588+ func (lb * loadBalancer ) checkLoadBalancerRule (lbRuleName string , port corev1.ServicePort , protocol LoadBalancerProtocol , service * corev1. Service , version semver. Version ) (* cloudstack.LoadBalancerRule , bool , error ) {
567589 lbRule , ok := lb .rules [lbRuleName ]
568590 if ! ok {
569591 return nil , false , nil
570592 }
571593
572- // Check if any of the values we cannot update (those that require a new load balancer rule) are changed.
573- if lbRule .Publicip == lb .ipAddr && lbRule .Privateport == strconv .Itoa (int (port .NodePort )) && lbRule .Publicport == strconv .Itoa (int (port .Port )) {
574- updateAlgo := lbRule .Algorithm != lb .algorithm
575- updateProto := lbRule .Protocol != protocol .CSProtocol ()
576- return lbRule , updateAlgo || updateProto , nil
594+ cidrList , err := lb .getCIDRList (service )
595+ if err != nil {
596+ return nil , false , err
577597 }
578598
579- // Delete the load balancer rule so we can create a new one using the new values.
580- if err := lb .deleteLoadBalancerRule (lbRule ); err != nil {
581- return nil , false , err
599+ var lbRuleCidrList []string
600+ if lbRule .Cidrlist != "" {
601+ lbRuleCidrList = strings .Split (lbRule .Cidrlist , " " )
602+ for i , cidr := range lbRuleCidrList {
603+ cidr = strings .TrimSpace (cidr )
604+ lbRuleCidrList [i ] = cidr
605+ }
606+ }
607+
608+ // Check if basic properties match (IP and ports). If not, we need to recreate the rule.
609+ basicPropsMatch := lbRule .Publicip == lb .ipAddr &&
610+ lbRule .Privateport == strconv .Itoa (int (port .NodePort )) &&
611+ lbRule .Publicport == strconv .Itoa (int (port .Port ))
612+
613+ cidrListChanged := len (cidrList ) != len (lbRuleCidrList ) || ! setsEqual (cidrList , lbRuleCidrList )
614+
615+ // Check if CIDR list also changed and version < 4.22, then we must recreate the rule.
616+ if ! basicPropsMatch || (cidrListChanged && version .LT (semver.Version {Major : 4 , Minor : 22 , Patch : 0 })) {
617+ // Delete the load balancer rule so we can create a new one using the new values.
618+ if err := lb .deleteLoadBalancerRule (lbRule ); err != nil {
619+ return nil , false , err
620+ }
621+ return nil , false , nil
622+ }
623+
624+ // Rule can be updated. Check what needs updating.
625+ updateAlgo := lbRule .Algorithm != lb .algorithm
626+ updateProto := lbRule .Protocol != protocol .CSProtocol ()
627+ updateCidrlist := cidrListChanged
628+
629+ return lbRule , updateAlgo || updateProto || updateCidrlist , nil
630+ }
631+
632+ // setsEqual checks if two slices contain the exact same unique elements, regardless of order.
633+ func setsEqual (listA , listB []string ) bool {
634+ createSet := func (list []string ) map [string ]bool {
635+ set := make (map [string ]bool )
636+ for _ , item := range list {
637+ set [item ] = true
638+ }
639+ return set
640+ }
641+
642+ setA := createSet (listA )
643+ setB := createSet (listB )
644+
645+ if len (setA ) != len (setB ) {
646+ return false
647+ }
648+
649+ for item := range setA {
650+ if _ , found := setB [item ]; ! found {
651+ return false
652+ }
582653 }
583654
584- return nil , false , nil
655+ return true
585656}
586657
587658// updateLoadBalancerRule updates a load balancer rule.
588- func (lb * loadBalancer ) updateLoadBalancerRule (lbRuleName string , protocol LoadBalancerProtocol ) error {
659+ func (lb * loadBalancer ) updateLoadBalancerRule (lbRuleName string , protocol LoadBalancerProtocol , service * corev1. Service , version semver. Version ) error {
589660 lbRule := lb .rules [lbRuleName ]
590661
591662 p := lb .LoadBalancer .NewUpdateLoadBalancerRuleParams (lbRule .Id )
592663 p .SetAlgorithm (lb .algorithm )
593664 p .SetProtocol (protocol .CSProtocol ())
594665
666+ // TODO: Uncomment after updating cloudstack-go SDK to support CIDR list update.
667+ // if version.GTE(semver.Version{Major: 4, Minor: 22, Patch: 0}) {
668+ // cidrList, err := lb.getCIDRList(service)
669+ // if err != nil {
670+ // return err
671+ // }
672+ // p.SetCidrlist(strings.Join(cidrList, ","))
673+ // }
674+
595675 _ , err := lb .LoadBalancer .UpdateLoadBalancerRule (p )
596676 return err
597677}
@@ -613,19 +693,9 @@ func (lb *loadBalancer) createLoadBalancerRule(lbRuleName string, port corev1.Se
613693 p .SetOpenfirewall (false )
614694
615695 // Read the source CIDR annotation
616- sourceCIDRs , ok := service .Annotations [ServiceAnnotationLoadBalancerSourceCidrs ]
617- var cidrList []string
618- if ok && sourceCIDRs != "" {
619- cidrList = strings .Split (sourceCIDRs , "," )
620- for i , cidr := range cidrList {
621- cidr = strings .TrimSpace (cidr )
622- if _ , _ , err := net .ParseCIDR (cidr ); err != nil {
623- return nil , fmt .Errorf ("invalid CIDR in annotation %s: %s" , ServiceAnnotationLoadBalancerSourceCidrs , cidr )
624- }
625- cidrList [i ] = cidr
626- }
627- } else {
628- cidrList = []string {defaultAllowedCIDR }
696+ cidrList , err := lb .getCIDRList (service )
697+ if err != nil {
698+ return nil , err
629699 }
630700
631701 // Set the CIDR list in the parameters
0 commit comments