Skip to content

Commit d7ecbff

Browse files
committed
Merge branch 'main' into kubernetes-1.31
2 parents a3b9b2b + b2831f0 commit d7ecbff

33 files changed

Lines changed: 899 additions & 277 deletions

.github/local_action/start_ccm_e2e/action.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,10 @@ runs:
9696
profile="{\"default\":{\"access_key\":\"${{ inputs.osc_access_key }}\",\"secret_key\":\"${{ inputs.osc_secret_key }}\"}}"
9797
kubectl create secret generic osc-secret-file --from-literal=config.json=$profile -n kube-system
9898
echo "updating CCM config"
99-
helm upgrade --wait k8s-osc-ccm deploy/k8s-osc-ccm --set oscSecretName=osc-secret-file --set oscCredentialsFromFile=true --set oscSecretFormat=v1 --set image.repository=${{ inputs.registry }}/outscale/cloud-provider-osc --set image.tag=${{ inputs.version }}\
99+
helm upgrade --wait k8s-osc-ccm deploy/k8s-osc-ccm --set oscSecretName=osc-secret-file --set oscCredentialsFromFile=true --set image.repository=${{ inputs.registry }}/outscale/cloud-provider-osc --set image.tag=${{ inputs.version }}\
100100
--set extraLoadBalancerTags.clikey=clivalue
101-
echo "waiting for pods"
102-
kubectl wait --for=condition=Ready po --all -A --timeout=900s
103-
kubectl get po -A
101+
sleep 5
102+
kubectl get pod -n kube-system -l "app=osc-cloud-controller-manager"
104103
shell: bash
105104
env:
106105
KUBECONFIG: "${{ github.workspace }}/${{ steps.caposc.outputs.KUBECONFIG }}"

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- name: golangci-lint
3232
uses: golangci/golangci-lint-action@v9
3333
with:
34-
version: v2.1
34+
version: v2.7.1
3535
args: --timeout=300s
3636
only-new-issues: true
3737
- name: Test

.github/workflows/e2e_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: ⬇️ Install kubectl
3131
uses: azure/setup-kubectl@v4
3232
with:
33-
version: v1.31.12
33+
version: v1.34.2
3434
- name: ⬇️ Install Helm
3535
uses: azure/setup-helm@v4
3636
- name: ⬇️ Install Python
@@ -39,7 +39,7 @@ jobs:
3939
uses: actions/setup-go@v6
4040
with:
4141
go-version-file: 'go.mod'
42-
- name: 🔐 Set ak/sk name based on runner region
42+
- name: 🔐 Set AK/SK name based on runner region
4343
run: .github/scripts/runneraksk.sh
4444
- name: 🧹 Frieza
4545
uses: outscale/frieza-github-actions/frieza-clean@master

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
## BUILD ARGS ##
1515
################################################################################
1616
# This build arg allows the specification of a custom Golang image.
17-
ARG GOLANG_IMAGE=golang:1.25.4
17+
ARG GOLANG_IMAGE=golang:1.25.5
1818

1919
# The distroless image on which the CPI manager image is built.
2020
#

cloud-controller-manager/osc/cloud/cloud.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package cloud
33
import (
44
"context"
55
"fmt"
6+
"slices"
67

78
"github.com/outscale/cloud-provider-osc/cloud-controller-manager/osc/oapi"
9+
"github.com/outscale/cloud-provider-osc/cloud-controller-manager/utils"
10+
"github.com/outscale/osc-sdk-go/v2"
811
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
912
clientset "k8s.io/client-go/kubernetes"
1013
"k8s.io/klog/v2"
@@ -14,7 +17,7 @@ type Cloud struct {
1417
api oapi.Clienter
1518
Metadata oapi.Metadata
1619
Self *VM
17-
clusterID string
20+
clusterID []string
1821
}
1922

2023
func New(ctx context.Context, clusterID string) (*Cloud, error) {
@@ -30,21 +33,42 @@ func New(ctx context.Context, clusterID string) (*Cloud, error) {
3033
if err := api.OAPI().CheckCredentials(ctx); err != nil {
3134
return nil, fmt.Errorf("init cloud: %w", err)
3235
}
33-
c := &Cloud{api: api, Metadata: metadata, clusterID: clusterID}
36+
c := &Cloud{api: api, Metadata: metadata}
3437

3538
id := c.Metadata.InstanceID
3639
self, err := c.GetVMByID(ctx, id)
3740
if err != nil {
3841
return nil, fmt.Errorf("error finding self: %w", err)
3942
}
4043
c.Self = self
41-
if c.clusterID == "" {
42-
c.clusterID = self.ClusterID()
44+
if clusterID != "" {
45+
c.clusterID = []string{clusterID}
46+
} else {
47+
// primary cluster ID (CAPOSC v1)
48+
c.clusterID = []string{self.ClusterID()}
49+
if self.SubnetID != "" {
50+
// alternate cluster ID (CAPOSC v0)
51+
subs, err := api.OAPI().ReadSubnets(ctx, osc.ReadSubnetsRequest{
52+
Filters: &osc.FiltersSubnet{SubnetIds: &[]string{self.SubnetID}},
53+
})
54+
if err != nil {
55+
return nil, fmt.Errorf("error finding self subnets: %w", err)
56+
}
57+
if len(subs) == 1 {
58+
clusterID := getClusterIDFromTags(subs[0].GetTags())
59+
if clusterID != "" && !slices.Contains(c.clusterID, clusterID) {
60+
c.clusterID = append(c.clusterID, clusterID)
61+
}
62+
}
63+
}
64+
}
65+
if len(c.clusterID) > 0 {
66+
klog.FromContext(ctx).V(3).Info(fmt.Sprintf("Allowed cluster IDs: %v", c.clusterID))
4367
}
4468
return c, nil
4569
}
4670

47-
func NewWith(api oapi.Clienter, self *VM, clusterID string) *Cloud {
71+
func NewWith(api oapi.Clienter, self *VM, clusterID []string) *Cloud {
4872
return &Cloud{
4973
api: api,
5074
Metadata: oapi.Metadata{
@@ -58,7 +82,7 @@ func NewWith(api oapi.Clienter, self *VM, clusterID string) *Cloud {
5882
}
5983

6084
func (c *Cloud) Initialize(ctx context.Context, kube clientset.Interface) {
61-
if c.clusterID != "" {
85+
if len(c.clusterID) > 0 {
6286
return
6387
}
6488
logger := klog.FromContext(ctx)
@@ -68,6 +92,30 @@ func (c *Cloud) Initialize(ctx context.Context, kube clientset.Interface) {
6892
logger.V(3).Error(err, "Unable to infer cluster ID")
6993
return
7094
}
71-
c.clusterID = string(ns.UID)
72-
logger.V(3).Info("Inferred cluster ID: " + c.clusterID)
95+
c.clusterID = []string{string(ns.UID)}
96+
logger.V(3).Info("Inferred cluster ID: " + string(ns.UID))
97+
}
98+
99+
func (c *Cloud) sameCluster(tags []osc.ResourceTag) bool {
100+
return slices.Contains(c.clusterID, getClusterIDFromTags(tags))
101+
}
102+
103+
func (c *Cloud) mainSGTagKey() string {
104+
if len(c.clusterID) == 0 {
105+
return ""
106+
}
107+
return mainSGTagKey(c.clusterID[0])
108+
}
109+
110+
func (c *Cloud) clusterIDTagKey() string {
111+
if len(c.clusterID) == 0 {
112+
return ""
113+
}
114+
return clusterIDTagKey(c.clusterID[0])
115+
}
116+
117+
func (c *Cloud) clusterIDTagKeys() []string {
118+
return utils.Map(c.clusterID, func(c string) (string, bool) {
119+
return clusterIDTagKey(c), true
120+
})
73121
}

cloud-controller-manager/osc/cloud/loadbalancer.go

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
163163
var 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

Comments
 (0)