From e4f009b46f388c5bc3d6fe86bc4ddf1ef7978ea9 Mon Sep 17 00:00:00 2001 From: Shilpa-Gokul Date: Wed, 29 Jan 2025 15:19:04 +0530 Subject: [PATCH] Add listener based on the machine label and listener label --- api/v1beta2/ibmvpccluster_types.go | 2 + api/v1beta2/zz_generated.deepcopy.go | 1 + cloud/scope/powervs_machine.go | 54 ++- cloud/scope/powervs_machine_test.go | 331 ++++++++++++++++++ ...e.cluster.x-k8s.io_ibmpowervsclusters.yaml | 50 +++ ...r.x-k8s.io_ibmpowervsclustertemplates.yaml | 51 +++ ...cture.cluster.x-k8s.io_ibmvpcclusters.yaml | 101 ++++++ ...uster.x-k8s.io_ibmvpcclustertemplates.yaml | 102 ++++++ controllers/ibmpowervsmachine_controller.go | 9 +- pkg/cloud/services/vpc/mock/vpc_generated.go | 16 + pkg/cloud/services/vpc/service.go | 5 + pkg/cloud/services/vpc/vpc.go | 1 + 12 files changed, 715 insertions(+), 8 deletions(-) diff --git a/api/v1beta2/ibmvpccluster_types.go b/api/v1beta2/ibmvpccluster_types.go index b4ed7037b..8c481c3ed 100644 --- a/api/v1beta2/ibmvpccluster_types.go +++ b/api/v1beta2/ibmvpccluster_types.go @@ -127,6 +127,8 @@ type AdditionalListenerSpec struct { // Will default to TCP protocol if not specified. // +optional Protocol *VPCLoadBalancerListenerProtocol `json:"protocol,omitempty"` + + Selector metav1.LabelSelector `json:"selector"` } // VPCLoadBalancerBackendPoolSpec defines the desired configuration of a VPC Load Balancer Backend Pool. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 8ed7ea8dd..1266161b6 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -39,6 +39,7 @@ func (in *AdditionalListenerSpec) DeepCopyInto(out *AdditionalListenerSpec) { *out = new(VPCLoadBalancerListenerProtocol) **out = **in } + in.Selector.DeepCopyInto(&out.Selector) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalListenerSpec. diff --git a/cloud/scope/powervs_machine.go b/cloud/scope/powervs_machine.go index 10e44bd92..d120cb2cf 100644 --- a/cloud/scope/powervs_machine.go +++ b/cloud/scope/powervs_machine.go @@ -44,6 +44,8 @@ import ( "github.com/IBM/vpc-go-sdk/vpcv1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/cache" @@ -56,6 +58,7 @@ import ( "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/patch" + "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/authenticator" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/cos" @@ -1020,7 +1023,35 @@ func (m *PowerVSMachineScope) CreateVPCLoadBalancerPoolMember() (*vpcv1.LoadBala internalIP := m.GetMachineInternalIP() + // TODO:SHILPA- handle multiple lbs as well // Update each LoadBalancer pool + loadBalancerListeners := map[string]v1beta2.AdditionalListenerSpec{} + for _, additionalListener := range lb.AdditionalListeners { + // if additionalListener.Selector.MatchLabels == nil { + // continue + // } + // TODO:SHILPA- protocol is added irrespective of whats provided in the additionalListener protocol, need to handle this + if additionalListener.Protocol == nil { + additionalListener.Protocol = &v1beta2.VPCLoadBalancerListenerProtocolTCP + } + loadBalancerListeners[fmt.Sprintf("%d-%s", additionalListener.Port, *additionalListener.Protocol)] = additionalListener + } + for _, listener := range loadBalancer.Listeners { + listenerOptions := &vpcv1.GetLoadBalancerListenerOptions{} + listenerOptions.SetLoadBalancerID(*loadBalancer.ID) + listenerOptions.SetID(*listener.ID) + loadBalancerListener, _, err := m.IBMVPCClient.GetLoadBalancerListener(listenerOptions) + if err != nil { + return nil, fmt.Errorf("failed to list %s load balancer listener: %v", *listener.ID, err) + } + fmt.Println(*loadBalancerListener.Port, *loadBalancerListener.Protocol) + if additionalListener, ok := loadBalancerListeners[fmt.Sprintf("%d-%s", *loadBalancerListener.Port, *loadBalancerListener.Protocol)]; ok { + if loadBalancerListener.DefaultPool != nil { + loadBalancerListeners[*loadBalancerListener.DefaultPool.Name] = additionalListener + } + } + } + fmt.Println(loadBalancerListeners) for _, pool := range loadBalancer.Pools { m.V(3).Info("Updating LoadBalancer pool member", "pool", *pool.Name, "loadbalancer", *loadBalancer.Name, "ip", internalIP) listOptions := &vpcv1.ListLoadBalancerPoolMembersOptions{} @@ -1031,7 +1062,27 @@ func (m *PowerVSMachineScope) CreateVPCLoadBalancerPoolMember() (*vpcv1.LoadBala return nil, fmt.Errorf("failed to list %s VPC load balancer pool error: %v", *pool.Name, err) } var targetPort int64 - var alreadyRegistered bool + var alreadyRegistered, skipListener bool + + if loadBalancerListener, ok := loadBalancerListeners[*pool.Name]; ok { + selector, err := metav1.LabelSelectorAsSelector(&loadBalancerListener.Selector) + if err != nil { + m.V(5).Info("Skipping listener addition, failed to get label selector from spec selector") + continue + } + + if selector.Empty() && !util.IsControlPlaneMachine(m.Machine) { + continue + } + // Skip adding the listener if the selector does not match + if !selector.Empty() && !selector.Matches(labels.Set(m.IBMPowerVSMachine.Labels)) { + skipListener = true + } + } + if skipListener { + m.V(3).Info("Skip adding listener, machine label doesn't match with the listener label selector", "pool", *pool.Name, "targetip", internalIP, "machine", m.IBMPowerVSMachine.Name, "clusterName", m.IBMPowerVSCluster.Name) + continue + } if len(listLoadBalancerPoolMembers.Members) == 0 { // For adding the first member to the pool we depend on the pool name to get the target port @@ -1059,6 +1110,7 @@ func (m *PowerVSMachineScope) CreateVPCLoadBalancerPoolMember() (*vpcv1.LoadBala } } } + if alreadyRegistered { m.V(3).Info("PoolMember already exist", "pool", *pool.Name, "targetip", internalIP, "port", targetPort) continue diff --git a/cloud/scope/powervs_machine_test.go b/cloud/scope/powervs_machine_test.go index ea1fb3ad5..facad9616 100644 --- a/cloud/scope/powervs_machine_test.go +++ b/cloud/scope/powervs_machine_test.go @@ -1534,6 +1534,337 @@ func TestCreateVPCLoadBalancerPoolMemberPowerVSMachine(t *testing.T) { nodeAddress := "10.0.0.1" loadBalancerID := "xyz-xyz-xyz" + t.Run("Skip adding listener if the machine label and listener label doesnot match", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + loadBalancerName := "load-balancer-0" + loadBalancers := &vpcv1.LoadBalancer{ + ID: ptr.To(loadBalancerID), + Name: ptr.To(loadBalancerName), + ProvisioningStatus: (*string)(&infrav1beta2.VPCLoadBalancerStateActive), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: ptr.To("pool-id-0"), + Name: ptr.To("pool-22"), + }, + }, + Listeners: []vpcv1.LoadBalancerListenerReference{ + { + ID: ptr.To("pool-id-0"), + }, + }, + } + loadBalancerListener := &vpcv1.LoadBalancerListener{ + DefaultPool: &vpcv1.LoadBalancerPoolReference{ + Name: ptr.To("pool-22"), + }, + ID: ptr.To("pool-id-0"), + Port: ptr.To(int64(22)), + Protocol: ptr.To("tcp"), + } + mockClient := vpcmock.NewMockVpc(mockCtrl) + + scope := PowerVSMachineScope{ + Machine: &capiv1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{}, + }, + IBMVPCClient: mockClient, + IBMPowerVSMachine: &infrav1beta2.IBMPowerVSMachine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": "bootstrap", + }, + }, + }, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + LoadBalancers: []infrav1beta2.VPCLoadBalancerSpec{ + { + Name: loadBalancerName, + ID: ptr.To(loadBalancerID), + AdditionalListeners: []infrav1beta2.AdditionalListenerSpec{ + { + Port: 22, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "master", + }, + }, + }, + }, + }, + }, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + LoadBalancers: map[string]infrav1beta2.VPCLoadBalancerStatus{ + loadBalancerName: { + ID: ptr.To(loadBalancerID), + }, + }, + }, + }, + } + + mockClient.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerOptions{})).Return(loadBalancers, nil, nil).AnyTimes() + mockClient.EXPECT().GetLoadBalancerListener(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerListenerOptions{})).Return(loadBalancerListener, nil, nil).AnyTimes() + mockClient.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancerPoolMembersOptions{})).Return(&vpcv1.LoadBalancerPoolMemberCollection{}, nil, nil).AnyTimes() + result, err := scope.CreateVPCLoadBalancerPoolMember() + + g.Expect(err).To(BeNil()) + g.Expect(result).To(BeNil()) + }) + + t.Run("Add listener if the machine label and listener label matches", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + loadBalancerName := "load-balancer-0" + loadBalancers := &vpcv1.LoadBalancer{ + ID: ptr.To(loadBalancerID), + Name: ptr.To(loadBalancerName), + ProvisioningStatus: (*string)(&infrav1beta2.VPCLoadBalancerStateActive), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: ptr.To("pool-id-0"), + Name: ptr.To("pool-22"), + }, + }, + Listeners: []vpcv1.LoadBalancerListenerReference{ + { + ID: ptr.To("pool-id-0"), + }, + { + ID: ptr.To("pool-id-1"), + }, + }, + } + loadBalancerListener := &vpcv1.LoadBalancerListener{ + DefaultPool: &vpcv1.LoadBalancerPoolReference{ + Name: ptr.To("pool-22"), + }, + ID: ptr.To("pool-id-0"), + Port: ptr.To(int64(22)), + Protocol: ptr.To("tcp"), + } + mockClient := vpcmock.NewMockVpc(mockCtrl) + + scope := PowerVSMachineScope{ + Machine: &capiv1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{}, + }, + IBMVPCClient: mockClient, + IBMPowerVSMachine: &infrav1beta2.IBMPowerVSMachine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": "bootstrap", + }, + }, + }, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + LoadBalancers: []infrav1beta2.VPCLoadBalancerSpec{ + { + Name: loadBalancerName, + ID: ptr.To(loadBalancerID), + AdditionalListeners: []infrav1beta2.AdditionalListenerSpec{ + { + Port: 22, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": "bootstrap", + }, + }, + }, + }, + }, + }, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + LoadBalancers: map[string]infrav1beta2.VPCLoadBalancerStatus{ + loadBalancerName: { + ID: ptr.To(loadBalancerID), + }, + }, + }, + }, + } + + mockClient.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerOptions{})).Return(loadBalancers, nil, nil).AnyTimes() + mockClient.EXPECT().GetLoadBalancerListener(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerListenerOptions{})).Return(loadBalancerListener, nil, nil).AnyTimes() + mockClient.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancerPoolMembersOptions{})).Return(&vpcv1.LoadBalancerPoolMemberCollection{}, nil, nil).AnyTimes() + expectedLoadBalancerPoolMemberID := "pool-member-2" + expectedLoadBalancerPoolMember := &vpcv1.LoadBalancerPoolMember{ID: ptr.To(expectedLoadBalancerPoolMemberID)} + mockClient.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(&vpcv1.CreateLoadBalancerPoolMemberOptions{})).Return(expectedLoadBalancerPoolMember, nil, nil).AnyTimes() + result, err := scope.CreateVPCLoadBalancerPoolMember() + + g.Expect(err).To(BeNil()) + g.Expect(*result.ID).To(Equal(expectedLoadBalancerPoolMemberID)) + }) + + t.Run("Skip adding non control plane nodes if there is no selector", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + loadBalancerName := "load-balancer-0" + loadBalancers := &vpcv1.LoadBalancer{ + ID: ptr.To(loadBalancerID), + Name: ptr.To(loadBalancerName), + ProvisioningStatus: (*string)(&infrav1beta2.VPCLoadBalancerStateActive), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: ptr.To("pool-id-0"), + Name: ptr.To("pool-6443"), + }, + }, + Listeners: []vpcv1.LoadBalancerListenerReference{ + { + ID: ptr.To("pool-id-0"), + }, + { + ID: ptr.To("pool-id-1"), + }, + }, + } + loadBalancerListener := &vpcv1.LoadBalancerListener{ + DefaultPool: &vpcv1.LoadBalancerPoolReference{ + Name: ptr.To("pool-6443"), + }, + ID: ptr.To("pool-id-0"), + Port: ptr.To(int64(6443)), + Protocol: ptr.To("tcp"), + } + mockClient := vpcmock.NewMockVpc(mockCtrl) + + scope := PowerVSMachineScope{ + Machine: &capiv1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{}, + }, + IBMVPCClient: mockClient, + IBMPowerVSMachine: &infrav1beta2.IBMPowerVSMachine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": "bootstrap", + }, + }, + }, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + LoadBalancers: []infrav1beta2.VPCLoadBalancerSpec{ + { + Name: loadBalancerName, + ID: ptr.To(loadBalancerID), + AdditionalListeners: []infrav1beta2.AdditionalListenerSpec{ + { + Port: 6443, + }, + }, + }, + }, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + LoadBalancers: map[string]infrav1beta2.VPCLoadBalancerStatus{ + loadBalancerName: { + ID: ptr.To(loadBalancerID), + }, + }, + }, + }, + } + + mockClient.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerOptions{})).Return(loadBalancers, nil, nil).AnyTimes() + mockClient.EXPECT().GetLoadBalancerListener(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerListenerOptions{})).Return(loadBalancerListener, nil, nil).AnyTimes() + mockClient.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancerPoolMembersOptions{})).Return(&vpcv1.LoadBalancerPoolMemberCollection{}, nil, nil).AnyTimes() + result, err := scope.CreateVPCLoadBalancerPoolMember() + + g.Expect(err).To(BeNil()) + g.Expect(result).To(BeNil()) + }) + t.Run("Adding control plane nodes even if there is no selector", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + loadBalancerName := "load-balancer-0" + loadBalancers := &vpcv1.LoadBalancer{ + ID: ptr.To(loadBalancerID), + Name: ptr.To(loadBalancerName), + ProvisioningStatus: (*string)(&infrav1beta2.VPCLoadBalancerStateActive), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: ptr.To("pool-id-0"), + Name: ptr.To("pool-6443"), + }, + }, + Listeners: []vpcv1.LoadBalancerListenerReference{ + { + ID: ptr.To("pool-id-0"), + }, + { + ID: ptr.To("pool-id-1"), + }, + }, + } + loadBalancerListener := &vpcv1.LoadBalancerListener{ + DefaultPool: &vpcv1.LoadBalancerPoolReference{ + Name: ptr.To("pool-6443"), + }, + ID: ptr.To("pool-id-0"), + Port: ptr.To(int64(6443)), + Protocol: ptr.To("tcp"), + } + mockClient := vpcmock.NewMockVpc(mockCtrl) + + scope := PowerVSMachineScope{ + Machine: &capiv1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "cluster.x-k8s.io/control-plane": "true", + }, + }, + }, + IBMVPCClient: mockClient, + IBMPowerVSMachine: &infrav1beta2.IBMPowerVSMachine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": "bootstrap", + }, + }, + }, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + LoadBalancers: []infrav1beta2.VPCLoadBalancerSpec{ + { + Name: loadBalancerName, + ID: ptr.To(loadBalancerID), + AdditionalListeners: []infrav1beta2.AdditionalListenerSpec{ + { + Port: 6443, + }, + }, + }, + }, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + LoadBalancers: map[string]infrav1beta2.VPCLoadBalancerStatus{ + loadBalancerName: { + ID: ptr.To(loadBalancerID), + }, + }, + }, + }, + } + + mockClient.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerOptions{})).Return(loadBalancers, nil, nil).AnyTimes() + mockClient.EXPECT().GetLoadBalancerListener(gomock.AssignableToTypeOf(&vpcv1.GetLoadBalancerListenerOptions{})).Return(loadBalancerListener, nil, nil).AnyTimes() + mockClient.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancerPoolMembersOptions{})).Return(&vpcv1.LoadBalancerPoolMemberCollection{}, nil, nil).AnyTimes() + expectedLoadBalancerPoolMemberID := "pool-member-2" + expectedLoadBalancerPoolMember := &vpcv1.LoadBalancerPoolMember{ID: ptr.To(expectedLoadBalancerPoolMemberID)} + mockClient.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(&vpcv1.CreateLoadBalancerPoolMemberOptions{})).Return(expectedLoadBalancerPoolMember, nil, nil).Times(1) + result, err := scope.CreateVPCLoadBalancerPoolMember() + + g.Expect(err).To(BeNil()) + g.Expect(*result.ID).To(Equal(expectedLoadBalancerPoolMemberID)) + }) t.Run("Create VPC Load Balancer Pool Member", func(t *testing.T) { t.Run("No load balancers present in status", func(t *testing.T) { g := NewWithT(t) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclusters.yaml index 7094b9e1a..e001700b7 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclusters.yaml @@ -299,8 +299,58 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclustertemplates.yaml index 149570c39..578d78c3f 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmpowervsclustertemplates.yaml @@ -330,8 +330,59 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of + label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml index f5db2f578..533e93c0d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml @@ -284,8 +284,58 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: @@ -557,8 +607,59 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclustertemplates.yaml index 3865b6bb6..e39331215 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclustertemplates.yaml @@ -133,8 +133,59 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: @@ -414,8 +465,59 @@ spec: - tcp - udp type: string + selector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic required: - port + - selector type: object type: array x-kubernetes-list-map-keys: diff --git a/controllers/ibmpowervsmachine_controller.go b/controllers/ibmpowervsmachine_controller.go index b2cabe5e8..289decc5f 100644 --- a/controllers/ibmpowervsmachine_controller.go +++ b/controllers/ibmpowervsmachine_controller.go @@ -303,13 +303,8 @@ func (r *IBMPowerVSMachineReconciler) reconcileNormal(machineScope *scope.PowerV return ctrl.Result{}, nil } - if util.IsControlPlaneMachine(machineScope.Machine) { - machineScope.Info("Configuring loadbalancer configuration for control plane machine", "machineName", machineScope.IBMPowerVSMachine.Name) - return r.handleLoadBalancerPoolMemberConfiguration(machineScope) - } - machineScope.Info("skipping loadbalancer configuration for worker machine", "machineName", machineScope.IBMPowerVSMachine.Name) - - return ctrl.Result{}, nil + machineScope.Info("Configuring loadbalancer configuration for the machine if applicable", "machineName", machineScope.IBMPowerVSMachine.Name) + return r.handleLoadBalancerPoolMemberConfiguration(machineScope) } // IBMPowerVSClusterToIBMPowerVSMachines is a handler.ToRequestsFunc to be used to enqeue requests for reconciliation diff --git a/pkg/cloud/services/vpc/mock/vpc_generated.go b/pkg/cloud/services/vpc/mock/vpc_generated.go index f8225dd59..d08485f32 100644 --- a/pkg/cloud/services/vpc/mock/vpc_generated.go +++ b/pkg/cloud/services/vpc/mock/vpc_generated.go @@ -413,6 +413,22 @@ func (mr *MockVpcMockRecorder) GetLoadBalancerByName(loadBalancerName any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancerByName", reflect.TypeOf((*MockVpc)(nil).GetLoadBalancerByName), loadBalancerName) } +// GetLoadBalancerListener mocks base method. +func (m *MockVpc) GetLoadBalancerListener(options *vpcv1.GetLoadBalancerListenerOptions) (*vpcv1.LoadBalancerListener, *core.DetailedResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLoadBalancerListener", options) + ret0, _ := ret[0].(*vpcv1.LoadBalancerListener) + ret1, _ := ret[1].(*core.DetailedResponse) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetLoadBalancerListener indicates an expected call of GetLoadBalancerListener. +func (mr *MockVpcMockRecorder) GetLoadBalancerListener(options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancerListener", reflect.TypeOf((*MockVpc)(nil).GetLoadBalancerListener), options) +} + // GetLoadBalancerPoolByName mocks base method. func (m *MockVpc) GetLoadBalancerPoolByName(loadBalancerID, poolName string) (*vpcv1.LoadBalancerPool, error) { m.ctrl.T.Helper() diff --git a/pkg/cloud/services/vpc/service.go b/pkg/cloud/services/vpc/service.go index a1abce385..e5a89756b 100644 --- a/pkg/cloud/services/vpc/service.go +++ b/pkg/cloud/services/vpc/service.go @@ -199,6 +199,11 @@ func (s *Service) ListLoadBalancerPoolMembers(options *vpcv1.ListLoadBalancerPoo return s.vpcService.ListLoadBalancerPoolMembers(options) } +// ListLoadBalancerPoolMembers returns members of a load balancer pool. +func (s *Service) GetLoadBalancerListener(options *vpcv1.GetLoadBalancerListenerOptions) (*vpcv1.LoadBalancerListener, *core.DetailedResponse, error) { + return s.vpcService.GetLoadBalancerListener(options) +} + // ListKeys returns list of keys in a region. func (s *Service) ListKeys(options *vpcv1.ListKeysOptions) (*vpcv1.KeyCollection, *core.DetailedResponse, error) { return s.vpcService.ListKeys(options) diff --git a/pkg/cloud/services/vpc/vpc.go b/pkg/cloud/services/vpc/vpc.go index a15847859..5c3d0fcd9 100644 --- a/pkg/cloud/services/vpc/vpc.go +++ b/pkg/cloud/services/vpc/vpc.go @@ -51,6 +51,7 @@ type Vpc interface { CreateLoadBalancerPoolMember(options *vpcv1.CreateLoadBalancerPoolMemberOptions) (*vpcv1.LoadBalancerPoolMember, *core.DetailedResponse, error) DeleteLoadBalancerPoolMember(options *vpcv1.DeleteLoadBalancerPoolMemberOptions) (*core.DetailedResponse, error) ListLoadBalancerPoolMembers(options *vpcv1.ListLoadBalancerPoolMembersOptions) (*vpcv1.LoadBalancerPoolMemberCollection, *core.DetailedResponse, error) + GetLoadBalancerListener(options *vpcv1.GetLoadBalancerListenerOptions) (*vpcv1.LoadBalancerListener, *core.DetailedResponse, error) ListKeys(options *vpcv1.ListKeysOptions) (*vpcv1.KeyCollection, *core.DetailedResponse, error) CreateImage(options *vpcv1.CreateImageOptions) (*vpcv1.Image, *core.DetailedResponse, error) ListImages(options *vpcv1.ListImagesOptions) (*vpcv1.ImageCollection, *core.DetailedResponse, error)