@@ -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,110 @@ 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+
628+ return lbRule , updateAlgo || updateProto || cidrListChanged , nil
629+ }
630+
631+ // setsEqual checks if two slices contain the exact same unique elements, regardless of order.
632+ func setsEqual (listA , listB []string ) bool {
633+ createSet := func (list []string ) map [string ]bool {
634+ set := make (map [string ]bool )
635+ for _ , item := range list {
636+ set [item ] = true
637+ }
638+ return set
639+ }
640+
641+ setA := createSet (listA )
642+ setB := createSet (listB )
643+
644+ if len (setA ) != len (setB ) {
645+ return false
582646 }
583647
584- return nil , false , nil
648+ for item := range setA {
649+ if _ , found := setB [item ]; ! found {
650+ return false
651+ }
652+ }
653+
654+ return true
585655}
586656
587657// updateLoadBalancerRule updates a load balancer rule.
588- func (lb * loadBalancer ) updateLoadBalancerRule (lbRuleName string , protocol LoadBalancerProtocol ) error {
658+ func (lb * loadBalancer ) updateLoadBalancerRule (lbRuleName string , protocol LoadBalancerProtocol , service * corev1. Service , version semver. Version ) error {
589659 lbRule := lb .rules [lbRuleName ]
590660
591661 p := lb .LoadBalancer .NewUpdateLoadBalancerRuleParams (lbRule .Id )
592662 p .SetAlgorithm (lb .algorithm )
593663 p .SetProtocol (protocol .CSProtocol ())
594664
665+ // If version >= 4.22, we can update the CIDR list.
666+ if version .GTE (semver.Version {Major : 4 , Minor : 22 , Patch : 0 }) {
667+ cidrList , err := lb .getCIDRList (service )
668+ if err != nil {
669+ return err
670+ }
671+ p .SetCidrlist (cidrList )
672+ }
673+
595674 _ , err := lb .LoadBalancer .UpdateLoadBalancerRule (p )
596675 return err
597676}
@@ -613,19 +692,9 @@ func (lb *loadBalancer) createLoadBalancerRule(lbRuleName string, port corev1.Se
613692 p .SetOpenfirewall (false )
614693
615694 // 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 }
695+ cidrList , err := lb .getCIDRList (service )
696+ if err != nil {
697+ return nil , err
629698 }
630699
631700 // Set the CIDR list in the parameters
0 commit comments