@@ -18,7 +18,7 @@ import (
1818 "github.com/go-viper/mapstructure/v2"
1919 "github.com/outscale/cloud-provider-osc/cloud-controller-manager/osc/oapi"
2020 "github.com/outscale/osc-sdk-go/v2"
21- v1 "k8s.io/api/core/v1"
21+ corev1 "k8s.io/api/core/v1"
2222 "k8s.io/apimachinery/pkg/util/sets"
2323 controllerapi "k8s.io/cloud-provider/api"
2424 servicehelpers "k8s.io/cloud-provider/service/helpers"
@@ -153,8 +153,8 @@ type LoadBalancer struct {
153153 SessionAffinity string
154154 AccessLog AccessLog `annotation:",squash"`
155155 AllowFrom utilnet.IPNetSet
156- IngressAddress IngressAddress `annotation:"osc-load-balancer-ingress-address"`
157- IPMode * v1 .LoadBalancerIPMode `annotation:"osc-load-balancer-ingress-ipmode"`
156+ IngressAddress IngressAddress `annotation:"osc-load-balancer-ingress-address"`
157+ IPMode * corev1 .LoadBalancerIPMode `annotation:"osc-load-balancer-ingress-ipmode"`
158158
159159 lbSecurityGroup * osc.SecurityGroup
160160 targetSecurityGroup * osc.SecurityGroup
@@ -163,8 +163,8 @@ type LoadBalancer struct {
163163var reName = regexp .MustCompile ("^[a-zA-Z0-9-]+$" )
164164
165165// NewLoadBalancer creates a new LoadBalancer instance from a Kubernetes Service.
166- func NewLoadBalancer (svc * v1 .Service , addTags map [string ]string ) (* LoadBalancer , error ) {
167- if svc .Spec .SessionAffinity != v1 .ServiceAffinityNone {
166+ func NewLoadBalancer (svc * corev1 .Service , addTags map [string ]string ) (* LoadBalancer , error ) {
167+ if svc .Spec .SessionAffinity != corev1 .ServiceAffinityNone {
168168 return nil , fmt .Errorf ("unsupported SessionAffinity %q" , svc .Spec .SessionAffinity )
169169 }
170170 if len (svc .Spec .Ports ) == 0 {
@@ -198,7 +198,7 @@ func NewLoadBalancer(svc *v1.Service, addTags map[string]string) (*LoadBalancer,
198198 }
199199
200200 for _ , port := range svc .Spec .Ports {
201- if port .Protocol != v1 .ProtocolTCP {
201+ if port .Protocol != corev1 .ProtocolTCP {
202202 return nil , errors .New ("only TCP load balancers are supported" )
203203 }
204204 if port .NodePort == 0 {
@@ -233,10 +233,15 @@ func NewLoadBalancer(svc *v1.Service, addTags map[string]string) (*LoadBalancer,
233233 lb .HealthCheck .Port = lb .Listeners [0 ].BackendPort
234234 lb .HealthCheck .Protocol = "tcp"
235235 }
236+ // set defaults
236237 err = mergo .Merge (lb , DefaultLoadBalancerConfiguration )
237238 if err != nil {
238239 return nil , fmt .Errorf ("unable to set defaults: %w" , err )
239240 }
241+ if lb .IngressAddress .NeedIP () && lb .IPMode == nil {
242+ lb .IPMode = ptr .To (corev1 .LoadBalancerIPModeProxy )
243+ }
244+
240245 return lb , nil
241246}
242247
@@ -366,10 +371,10 @@ func (c *Cloud) LoadBalancerExists(ctx context.Context, l *LoadBalancer) (bool,
366371 case lb == nil :
367372 return false , nil
368373 }
369- if getLBUClusterID (lb .GetTags ()) != c . clusterID {
374+ if ! c . sameCluster (lb .GetTags ()) {
370375 return false , fmt .Errorf ("%w another cluster" , ErrBelongsToSomeoneElse )
371376 }
372- svcName := getLBUServiceName (lb .GetTags ())
377+ svcName := getServiceNameFromTags (lb .GetTags ())
373378 if svcName != "" && svcName != l .ServiceName {
374379 return false , fmt .Errorf ("%w another service" , ErrBelongsToSomeoneElse )
375380 }
@@ -421,13 +426,21 @@ func (c *Cloud) CreateLoadBalancer(ctx context.Context, l *LoadBalancer, backend
421426 }
422427 switch {
423428 case l .PublicIPID != "" :
424- createRequest .PublicIp = ptr .To (l .PublicIPID )
429+ ip := l .PublicIPID
430+ if strings .HasPrefix (ip , "ipalloc-" ) {
431+ pip , err := c .api .OAPI ().GetPublicIp (ctx , l .PublicIPID )
432+ if err != nil {
433+ return "" , "" , fmt .Errorf ("get ip: %w" , err )
434+ }
435+ ip = * pip .PublicIp
436+ }
437+ createRequest .PublicIp = & ip
425438 case l .PublicIPPool != "" :
426439 ip , err := c .allocateFromPool (ctx , l .PublicIPPool )
427440 if err != nil {
428441 return "" , "" , fmt .Errorf ("allocate ip: %w" , err )
429442 }
430- createRequest .PublicIp = ip .PublicIpId
443+ createRequest .PublicIp = ip .PublicIp
431444 }
432445
433446 // TODO: drop public cloud code ?
@@ -454,7 +467,7 @@ func (c *Cloud) CreateLoadBalancer(ctx context.Context, l *LoadBalancer, backend
454467 tags = map [string ]string {}
455468 }
456469 tags [ServiceNameTagKey ] = l .ServiceName
457- tags [clusterIDTagKey ( c . clusterID )] = ResourceLifecycleOwned
470+ tags [c . clusterIDTagKey ( )] = ResourceLifecycleOwned
458471
459472 ltags := make ([]osc.ResourceTag , 0 , len (tags ))
460473 for k , v := range tags {
@@ -550,7 +563,7 @@ func (c *Cloud) ensureSubnet(ctx context.Context, l *LoadBalancer) error {
550563 }
551564 subnets , err := c .api .OAPI ().ReadSubnets (ctx , osc.ReadSubnetsRequest {
552565 Filters : & osc.FiltersSubnet {
553- TagKeys : & [] string { clusterIDTagKey (c .clusterID )} ,
566+ TagKeys : ptr . To (c .clusterIDTagKeys ()) ,
554567 },
555568 })
556569 if err != nil {
@@ -574,8 +587,37 @@ func (c *Cloud) ensureSubnet(ctx context.Context, l *LoadBalancer) error {
574587 case ensureByTag ("OscK8sRole/service" ):
575588 case ensureByTag ("OscK8sRole/loadbalancer" ):
576589 default :
577- return errors .New ("no subnet found with the correct tag" )
590+ return c .discoverSubnet (ctx , l , subnets )
591+ }
592+ return nil
593+ }
594+
595+ // discoverSubnet tries to find a public or private subnet for the LB.
596+ func (c * Cloud ) discoverSubnet (ctx context.Context , l * LoadBalancer , subnets []osc.Subnet ) error {
597+ rtbls , err := c .api .OAPI ().ReadRouteTables (ctx , osc.ReadRouteTablesRequest {
598+ Filters : & osc.FiltersRouteTable {
599+ NetIds : & []string {subnets [0 ].GetNetId ()},
600+ },
601+ })
602+ if err != nil {
603+ return fmt .Errorf ("discover subnet: %w" , err )
578604 }
605+
606+ // find a public or private subnet, depending on LB type
607+ var discovered * osc.Subnet
608+ for _ , subnet := range subnets {
609+ if oapi .IsSubnetPublic (subnet .GetSubnetId (), rtbls ) == ! l .Internal {
610+ // take the first, in lexical order
611+ if discovered == nil || getName (subnet .GetTags ()) < getName (discovered .GetTags ()) {
612+ discovered = & subnet
613+ }
614+ }
615+ }
616+ if discovered == nil {
617+ return errors .New ("discover subnet: none found" )
618+ }
619+ l .SubnetID = discovered .GetSubnetId ()
620+ l .NetID = discovered .GetNetId ()
579621 return nil
580622}
581623
@@ -584,24 +626,47 @@ func (c *Cloud) ensureSecurityGroup(ctx context.Context, l *LoadBalancer) error
584626 return nil
585627 }
586628 sgName := "k8s-elb-" + l .Name
587- sgDescription := fmt .Sprintf ("Security group for Kubernetes ELB %s (%v )" , l .Name , l .ServiceName )
588- resp , err := c .api .OAPI ().CreateSecurityGroup (ctx , osc.CreateSecurityGroupRequest {
629+ sgDescription := fmt .Sprintf ("Security group for Kubernetes LB %s (%s )" , l .Name , l .ServiceName )
630+ sg , err := c .api .OAPI ().CreateSecurityGroup (ctx , osc.CreateSecurityGroupRequest {
589631 SecurityGroupName : sgName ,
590632 Description : sgDescription ,
591633 NetId : & l .NetID ,
592634 })
593- if err != nil {
635+ switch {
636+ case oapi .ErrorCode (err ) == "9008" : // ErrorDuplicateGroup: the SecurityGroupName '{group_name}' already exists.
637+ // the SG might have been created by a previous request, try to load it
638+ sgs , err := c .api .OAPI ().ReadSecurityGroups (ctx , osc.ReadSecurityGroupsRequest {
639+ Filters : & osc.FiltersSecurityGroup {
640+ SecurityGroupNames : & []string {sgName },
641+ },
642+ })
643+ switch {
644+ case err != nil :
645+ return fmt .Errorf ("read security groups: %w" , err )
646+ case len (sgs ) == 0 : // this has a tiny chance of occurring, but we would not want the CCM to panic
647+ return errors .New ("duplicate SG but none found" )
648+ default :
649+ sg = & sgs [0 ]
650+ }
651+ case err != nil :
594652 return fmt .Errorf ("create SG: %w" , err )
595653 }
596- l .SecurityGroups = []string {resp .GetSecurityGroupId ()}
597- l .lbSecurityGroup = resp
598- err = c .api .OAPI ().CreateTags (ctx , osc.CreateTagsRequest {
599- ResourceIds : l .SecurityGroups ,
600- Tags : []osc.ResourceTag {{Key : clusterIDTagKey (c .clusterID ), Value : ResourceLifecycleOwned }},
601- })
602- if err != nil {
603- return fmt .Errorf ("create SG: %w" , err )
654+ // check clusterID tag
655+ switch {
656+ case c .sameCluster (sg .GetTags ()): // existing SG with valid tag => noop
657+ case getClusterIDFromTags (sg .GetTags ()) == "" : // new SG or existing SG without tag => create
658+ err = c .api .OAPI ().CreateTags (ctx , osc.CreateTagsRequest {
659+ ResourceIds : []string {sg .GetSecurityGroupId ()},
660+ Tags : []osc.ResourceTag {{Key : c .clusterIDTagKey (), Value : ResourceLifecycleOwned }},
661+ })
662+ if err != nil {
663+ return fmt .Errorf ("create SG: %w" , err )
664+ }
665+ default : // existing SG with invalid tag/belonging to another cluster
666+ return errors .New ("a segurity group of the same name already exists" )
604667 }
668+ l .SecurityGroups = []string {sg .GetSecurityGroupId ()}
669+ l .lbSecurityGroup = sg
605670 return nil
606671}
607672
@@ -1029,7 +1094,7 @@ func (c *Cloud) getSecurityGroups(ctx context.Context, l *LoadBalancer, vms []VM
10291094 }
10301095 roleTagCount := math .MaxInt
10311096 for _ , sg := range sgs {
1032- if hasTag (sg .GetTags (), mainSGTagKey ( c . clusterID )) {
1097+ if hasTag (sg .GetTags (), c . mainSGTagKey ( )) {
10331098 klog .FromContext (ctx ).V (4 ).Info ("Found security group having main tag" , "securityGroupId" , sg .GetSecurityGroupId ())
10341099 l .targetSecurityGroup = & sg
10351100 }
@@ -1236,7 +1301,7 @@ func (c *Cloud) RunGarbageCollector(ctx context.Context) error {
12361301 // This is the list of SG we will scan to find rules linking to the SG to be deleted.
12371302 sgs , err := c .api .OAPI ().ReadSecurityGroups (ctx , osc.ReadSecurityGroupsRequest {
12381303 Filters : & osc.FiltersSecurityGroup {
1239- TagKeys : & [] string { clusterIDTagKey (c .clusterID )} ,
1304+ TagKeys : ptr . To (c .clusterIDTagKeys ()) ,
12401305 },
12411306 })
12421307 if err != nil {
0 commit comments