diff --git a/cloud/scope/cluster.go b/cloud/scope/cluster.go index e7092a2747..b91b9e5297 100644 --- a/cloud/scope/cluster.go +++ b/cloud/scope/cluster.go @@ -406,7 +406,7 @@ func (s *ClusterScope) CreateLoadBalancer() (*vpcv1.LoadBalancer, error) { } options.Subnets = append(options.Subnets, subnet) } else { - return nil, errors.Wrap(err, "Error subnet required for load balancer creation") + return nil, fmt.Errorf("error subnet required for load balancer creation") } options.SetPools([]vpcv1.LoadBalancerPoolPrototype{ diff --git a/cloud/scope/cluster_test.go b/cloud/scope/cluster_test.go index 472c6b27ca..2a0648c47e 100644 --- a/cloud/scope/cluster_test.go +++ b/cloud/scope/cluster_test.go @@ -189,7 +189,7 @@ func TestCreateVPC(t *testing.T) { t.Cleanup(teardown) scope := setupClusterScope(clusterName, mockvpc) scope.IBMVPCCluster.Spec = vpcCluster.Spec - mockvpc.EXPECT().ListVpcs(gomock.AssignableToTypeOf(listVpcsOptions)).Return(vpcCollection, detailedResponse, errors.New("Error when deleting subnet")) + mockvpc.EXPECT().ListVpcs(gomock.AssignableToTypeOf(listVpcsOptions)).Return(vpcCollection, detailedResponse, errors.New("Failed to list VPC")) _, err := scope.CreateVPC() g.Expect(err).To(Not(BeNil())) }) @@ -201,7 +201,7 @@ func TestCreateVPC(t *testing.T) { scope := setupClusterScope(clusterName, mockvpc) scope.IBMVPCCluster.Spec = vpcCluster.Spec mockvpc.EXPECT().ListVpcs(gomock.AssignableToTypeOf(listVpcsOptions)).Return(vpcCollection, detailedResponse, nil) - mockvpc.EXPECT().CreateVPC(gomock.AssignableToTypeOf(createVPCOptions)).Return(vpc, detailedResponse, errors.New("Error when deleting subnet")) + mockvpc.EXPECT().CreateVPC(gomock.AssignableToTypeOf(createVPCOptions)).Return(vpc, detailedResponse, errors.New("Failed to create VPC")) _, err := scope.CreateVPC() g.Expect(err).To(Not(BeNil())) }) @@ -713,3 +713,204 @@ func TestDeleteSubnet(t *testing.T) { }) }) } + +func TestCreateLoadBalancer(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + } + teardown := func() { + mockCtrl.Finish() + } + + vpcCluster := infrav1beta1.IBMVPCCluster{ + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "foo-load-balancer", + }, + }, + Status: infrav1beta1.IBMVPCClusterStatus{ + Subnet: infrav1beta1.Subnet{ + ID: core.StringPtr("foo-subnet-id"), + }, + }} + expectedOutput := &vpcv1.LoadBalancer{ + Name: core.StringPtr("foo-load-balancer"), + } + + t.Run("Create LoadBalancer", func(t *testing.T) { + listLoadBalancersOptions := &vpcv1.ListLoadBalancersOptions{} + createLoadBalancerOptions := &vpcv1.CreateLoadBalancerOptions{} + loadBalancerCollection := &vpcv1.LoadBalancerCollection{ + LoadBalancers: []vpcv1.LoadBalancer{ + { + Name: core.StringPtr("foo-load-balancer-1"), + }, + }, + } + loadBalancer := &vpcv1.LoadBalancer{ + Name: core.StringPtr("foo-load-balancer"), + } + detailedResponse := &core.DetailedResponse{} + + t.Run("Should create LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + mockvpc.EXPECT().CreateLoadBalancer(gomock.AssignableToTypeOf(createLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + out, err := scope.CreateLoadBalancer() + g.Expect(err).To(BeNil()) + require.Equal(t, expectedOutput, out) + }) + + t.Run("Return exsisting LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + vpcClusterCustom := infrav1beta1.IBMVPCCluster{ + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "foo-load-balancer-1", + }, + }} + expectedOutput := &vpcv1.LoadBalancer{ + Name: core.StringPtr("foo-load-balancer-1"), + } + + scope.IBMVPCCluster.Spec = vpcClusterCustom.Spec + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + out, err := scope.CreateLoadBalancer() + g.Expect(err).To(BeNil()) + require.Equal(t, expectedOutput, out) + }) + + t.Run("Error when listing LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, errors.New("Failed to list LoadBalancer")) + _, err := scope.CreateLoadBalancer() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when creating LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + mockvpc.EXPECT().CreateLoadBalancer(gomock.AssignableToTypeOf(createLoadBalancerOptions)).Return(loadBalancer, detailedResponse, errors.New("Failed to create LoadBalancer")) + _, err := scope.CreateLoadBalancer() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when subnet is nil", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + scope.IBMVPCCluster.Status.Subnet.ID = nil + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + _, err := scope.CreateLoadBalancer() + g.Expect(err).To(Not(BeNil())) + }) + }) +} + +func TestDeleteLoadBalancer(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + } + teardown := func() { + mockCtrl.Finish() + } + + vpcCluster := infrav1beta1.IBMVPCCluster{ + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "foo-load-balancer", + }, + }, + Status: infrav1beta1.IBMVPCClusterStatus{ + VPCEndpoint: infrav1beta1.VPCEndpoint{ + LBID: core.StringPtr("foo-load-balancer-id"), + }, + }} + + t.Run("Delete LoadBalancer", func(t *testing.T) { + listLoadBalancersOptions := &vpcv1.ListLoadBalancersOptions{} + loadBalancerCollection := &vpcv1.LoadBalancerCollection{ + LoadBalancers: []vpcv1.LoadBalancer{ + { + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + }, + }, + } + deleteLoadBalancerOptions := &vpcv1.DeleteLoadBalancerOptions{} + detailedResponse := &core.DetailedResponse{} + + t.Run("Should delete LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + mockvpc.EXPECT().DeleteLoadBalancer(gomock.AssignableToTypeOf(deleteLoadBalancerOptions)).Return(detailedResponse, nil) + _, err := scope.DeleteLoadBalancer() + g.Expect(err).To(BeNil()) + }) + + t.Run("Error when listing LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, errors.New("Failed to list LoadBalancer")) + _, err := scope.DeleteLoadBalancer() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error while deleting LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupClusterScope(clusterName, mockvpc) + scope.IBMVPCCluster.Spec = vpcCluster.Spec + scope.IBMVPCCluster.Status = vpcCluster.Status + mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(listLoadBalancersOptions)).Return(loadBalancerCollection, detailedResponse, nil) + mockvpc.EXPECT().DeleteLoadBalancer(gomock.AssignableToTypeOf(deleteLoadBalancerOptions)).Return(detailedResponse, errors.New("Could not delete LoadBalancer")) + _, err := scope.DeleteLoadBalancer() + g.Expect(err).To(Not(BeNil())) + }) + }) +} diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go index c3a40eba52..e87d64f5f6 100644 --- a/cloud/scope/machine.go +++ b/cloud/scope/machine.go @@ -18,6 +18,7 @@ package scope import ( "context" + "fmt" "github.com/IBM/go-sdk-core/v5/core" "github.com/IBM/vpc-go-sdk/vpcv1" @@ -200,11 +201,11 @@ func (m *MachineScope) CreateVPCLoadBalancerPoolMember(internalIP *string, targe } if *loadBalancer.ProvisioningStatus != string(infrav1beta1.VPCLoadBalancerStateActive) { - return nil, errors.Wrap(err, "load balancer is not in active state") + return nil, fmt.Errorf("load balancer is not in active state") } if len(loadBalancer.Pools) == 0 { - return nil, errors.Wrap(err, "no pools exist for the load balancer") + return nil, fmt.Errorf("no pools exist for the load balancer") } options := &vpcv1.CreateLoadBalancerPoolMemberOptions{} @@ -273,7 +274,7 @@ func (m *MachineScope) DeleteVPCLoadBalancerPoolMember() error { mtarget := member.Target.(*vpcv1.LoadBalancerPoolMemberTarget) if *mtarget.Address == *instance.PrimaryNetworkInterface.PrimaryIP.Address { if *loadBalancer.ProvisioningStatus != string(infrav1beta1.VPCLoadBalancerStateActive) { - return errors.Wrap(err, "load balancer is not in active state") + return fmt.Errorf("load balancer is not in active state") } deleteOptions := &vpcv1.DeleteLoadBalancerPoolMemberOptions{} diff --git a/cloud/scope/machine_test.go b/cloud/scope/machine_test.go index 9c7133c3df..df42a93fb3 100644 --- a/cloud/scope/machine_test.go +++ b/cloud/scope/machine_test.go @@ -289,3 +289,362 @@ func TestDeleteMachine(t *testing.T) { }) }) } + +func TestCreateVPCLoadBalancerPoolMember(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + } + teardown := func() { + mockCtrl.Finish() + } + + vpcMachine := infrav1beta1.IBMVPCMachine{ + Spec: infrav1beta1.IBMVPCMachineSpec{ + Name: "foo-machine", + }, + Status: infrav1beta1.IBMVPCMachineStatus{ + Addresses: []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: "192.168.1.1", + }, + }, + }, + } + expectedOutput := &vpcv1.LoadBalancerPoolMember{ + ID: core.StringPtr("foo-load-balancer-pool-member-id"), + Port: core.Int64Ptr(6443), + } + + t.Run("Create VPCLoadBalancerPoolMember", func(t *testing.T) { + getLoadBalancerOptions := &vpcv1.GetLoadBalancerOptions{} + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: core.StringPtr("foo-load-balancer-pool-id"), + }, + }, + } + detailedResponse := &core.DetailedResponse{} + listLoadBalancerPoolMembersOptions := &vpcv1.ListLoadBalancerPoolMembersOptions{} + loadBalancerPoolMemberCollection := &vpcv1.LoadBalancerPoolMemberCollection{ + Members: make([]vpcv1.LoadBalancerPoolMember, 0), + } + createLoadBalancerPoolMemberOptions := &vpcv1.CreateLoadBalancerPoolMemberOptions{} + loadBalancerPoolMember := &vpcv1.LoadBalancerPoolMember{ + ID: core.StringPtr("foo-load-balancer-pool-member-id"), + Port: core.Int64Ptr(6443), + } + + t.Run("Should create VPCLoadBalancerPoolMember", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + mockvpc.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(createLoadBalancerPoolMemberOptions)).Return(loadBalancerPoolMember, detailedResponse, nil) + out, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(BeNil()) + require.Equal(t, expectedOutput, out) + }) + + t.Run("PoolMember already exist", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + customloadBalancerPoolMemberCollection := &vpcv1.LoadBalancerPoolMemberCollection{ + Members: []vpcv1.LoadBalancerPoolMember{ + { + Port: core.Int64Ptr(6443), + Target: &vpcv1.LoadBalancerPoolMemberTarget{ + Address: core.StringPtr("192.168.1.1"), + }, + }, + }, + } + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(customloadBalancerPoolMemberCollection, detailedResponse, nil) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(BeNil()) + }) + + t.Run("Error when fetching LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, errors.New("Could not fetch LoadBalancer")) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when LoadBalancer is not active", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("pending"), + } + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when no pool exist", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{}, + } + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when listing LoadBalancerPoolMembers", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, errors.New("Failed to list LoadBalancerPoolMembers")) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(6443)) + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when creating LoadBalancerPoolMember", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + mockvpc.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(createLoadBalancerPoolMemberOptions)).Return(loadBalancerPoolMember, detailedResponse, errors.New("Failed to create LoadBalancerPoolMember")) + _, err := scope.CreateVPCLoadBalancerPoolMember(&scope.IBMVPCMachine.Status.Addresses[0].Address, int64(64)) + g.Expect(err).To(Not(BeNil())) + }) + }) +} + +func TestDeleteVPCLoadBalancerPoolMember(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + } + teardown := func() { + mockCtrl.Finish() + } + + vpcMachine := infrav1beta1.IBMVPCMachine{ + Spec: infrav1beta1.IBMVPCMachineSpec{ + Name: "foo-machine", + }, + Status: infrav1beta1.IBMVPCMachineStatus{ + InstanceID: "foo-instance-id", + Addresses: []corev1.NodeAddress{ + { + Type: corev1.NodeInternalIP, + Address: "192.168.1.1", + }, + }, + }, + } + + t.Run("Delete VPCLoadBalancerPoolMember", func(t *testing.T) { + getLoadBalancerOptions := &vpcv1.GetLoadBalancerOptions{} + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: core.StringPtr("foo-load-balancer-pool-id"), + }, + }, + } + detailedResponse := &core.DetailedResponse{} + getInstanceOptions := &vpcv1.GetInstanceOptions{} + instance := &vpcv1.Instance{ + PrimaryNetworkInterface: &vpcv1.NetworkInterfaceInstanceContextReference{ + PrimaryIP: &vpcv1.ReservedIPReference{ + Address: core.StringPtr("192.168.1.1"), + }, + }, + } + listLoadBalancerPoolMembersOptions := &vpcv1.ListLoadBalancerPoolMembersOptions{} + loadBalancerPoolMemberCollection := &vpcv1.LoadBalancerPoolMemberCollection{ + Members: []vpcv1.LoadBalancerPoolMember{ + { + ID: core.StringPtr("foo-lb-pool-member-id"), + Port: core.Int64Ptr(6443), + Target: &vpcv1.LoadBalancerPoolMemberTarget{ + Address: core.StringPtr("192.168.1.1"), + }, + }, + }, + } + deleteLoadBalancerPoolMemberOptions := &vpcv1.DeleteLoadBalancerPoolMemberOptions{} + + t.Run("Should delete load balancer pool", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + mockvpc.EXPECT().DeleteLoadBalancerPoolMember(gomock.AssignableToTypeOf(deleteLoadBalancerPoolMemberOptions)).Return(detailedResponse, nil) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(BeNil()) + }) + + t.Run("Error when load balancer is not in active state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("pending"), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: core.StringPtr("foo-load-balancer-pool-id"), + }, + }, + } + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when deleting load balancer pool member", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + mockvpc.EXPECT().DeleteLoadBalancerPoolMember(gomock.AssignableToTypeOf(deleteLoadBalancerPoolMemberOptions)).Return(detailedResponse, errors.New("Failed to delete LoadBalancerPoolMember")) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(Not(BeNil())) + }) + + loadBalancerPoolMemberCollection = &vpcv1.LoadBalancerPoolMemberCollection{ + Members: make([]vpcv1.LoadBalancerPoolMember, 0), + } + t.Run("No members in load balancer pool", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, nil) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(BeNil()) + }) + + t.Run("Error when fetching LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, errors.New("Could not fetch LoadBalancer")) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("No pools associated with load balancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("foo-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{}, + } + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(BeNil()) + }) + + t.Run("Error when fetching Instance", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, errors.New("Failed to fetch Instance")) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(Not(BeNil())) + }) + + t.Run("Error when listing LoadBalancerPoolMembers", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + scope := setupMachineScope(clusterName, machineName, mockvpc) + scope.IBMVPCMachine.Spec = vpcMachine.Spec + scope.IBMVPCMachine.Status = vpcMachine.Status + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, detailedResponse, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, detailedResponse, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, detailedResponse, errors.New("Failed to list LoadBalancerPoolMembers")) + err := scope.DeleteVPCLoadBalancerPoolMember() + g.Expect(err).To(Not(BeNil())) + }) + }) +} diff --git a/controllers/ibmvpccluster_controller_test.go b/controllers/ibmvpccluster_controller_test.go index b002662929..903922168b 100644 --- a/controllers/ibmvpccluster_controller_test.go +++ b/controllers/ibmvpccluster_controller_test.go @@ -284,6 +284,150 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { }) } +func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + clusterScope *scope.ClusterScope + reconciler IBMVPCClusterReconciler + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + reconciler = IBMVPCClusterReconciler{ + Client: testEnv.Client, + Log: klogr.New(), + } + clusterScope = &scope.ClusterScope{ + IBMVPCClient: mockvpc, + Cluster: &capiv1beta1.Cluster{}, + Logger: klogr.New(), + IBMVPCCluster: &infrav1beta1.IBMVPCCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vpc-cluster", + }, + Spec: infrav1beta1.IBMVPCClusterSpec{ + VPC: "capi-vpc", + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "vpc-load-balancer", + }, + }, + }, + } + } + teardown := func() { + mockCtrl.Finish() + } + + t.Run("Reconciling creating IBMVPCCluster with LoadBalancer", func(t *testing.T) { + listVpcsOptions := &vpcv1.ListVpcsOptions{} + response := &core.DetailedResponse{} + vpclist := &vpcv1.VPCCollection{} + vpclist.Vpcs = []vpcv1.VPC{ + { + Name: pointer.String("capi-vpc"), + ID: pointer.String("capi-vpc-id"), + }, + } + options := &vpcv1.ListSubnetsOptions{} + subnets := &vpcv1.SubnetCollection{} + subnets.Subnets = []vpcv1.Subnet{ + { + ID: pointer.String("capi-subnet-id"), + Name: pointer.String("vpc-cluster-subnet"), + Zone: &vpcv1.ZoneReference{ + Name: pointer.String("foo"), + }, + }, + } + listLoadBalancersOptions := &vpcv1.ListLoadBalancersOptions{} + loadBalancerCollection := &vpcv1.LoadBalancerCollection{ + LoadBalancers: []vpcv1.LoadBalancer{ + { + Name: core.StringPtr("vpc-load-balancer"), + ID: core.StringPtr("vpc-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Hostname: core.StringPtr("foo"), + OperatingStatus: core.StringPtr("online"), + }, + }, + } + + t.Run("Should fail to create LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope.IBMVPCCluster.Finalizers = []string{infrav1beta1.ClusterFinalizer} + mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) + mockvpc.EXPECT().ListSubnets(options).Return(subnets, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, errors.New("Failed to list the LoadBalancers")) + _, err := reconciler.reconcile(clusterScope) + g.Expect(err).To(Not(BeNil())) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + }) + t.Run("Should successfully reconcile IBMVPCCluster with default port for the apiserver and set cluster status as Ready when LoadBalancer is in active state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope.IBMVPCCluster.Finalizers = []string{infrav1beta1.ClusterFinalizer} + mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) + mockvpc.EXPECT().ListSubnets(options).Return(subnets, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, nil) + _, err := reconciler.reconcile(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) + g.Expect(clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Port).To(Equal(int32(6443))) + }) + t.Run("Should successfully reconcile IBMVPCCluster with user supplied port for the apiserver and set cluster status as Ready when LoadBalancer is in active state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope.IBMVPCCluster.Finalizers = []string{infrav1beta1.ClusterFinalizer} + port := int32(412) + clusterScope.Cluster.Spec.ClusterNetwork = &capiv1beta1.ClusterNetwork{APIServerPort: &port} + mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) + mockvpc.EXPECT().ListSubnets(options).Return(subnets, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, nil) + _, err := reconciler.reconcile(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) + g.Expect(clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Port).To(Equal(port)) + }) + t.Run("Should successfully reconcile IBMVPCCluster and set cluster status as NotReady when LoadBalancer is create state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope.IBMVPCCluster.Finalizers = []string{infrav1beta1.ClusterFinalizer} + loadBalancerCollection.LoadBalancers[0].ProvisioningStatus = core.StringPtr("create_pending") + mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) + mockvpc.EXPECT().ListSubnets(options).Return(subnets, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, nil) + _, err := reconciler.reconcile(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(false)) + }) + t.Run("Should successfully reconcile IBMVPCCluster and set cluster status as NotReady when LoadBalancer is in undefined state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope.IBMVPCCluster.Finalizers = []string{infrav1beta1.ClusterFinalizer} + loadBalancerCollection.LoadBalancers[0].ProvisioningStatus = core.StringPtr("update_pending") + mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) + mockvpc.EXPECT().ListSubnets(options).Return(subnets, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, nil) + _, err := reconciler.reconcile(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(false)) + }) + }) +} + func TestIBMVPCClusterReconciler_delete(t *testing.T) { var ( mockvpc *mock.MockVpc @@ -418,6 +562,115 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { }) } +func TestIBMVPCClusterLBReconciler_delete(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + clusterScope *scope.ClusterScope + reconciler IBMVPCClusterReconciler + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + reconciler = IBMVPCClusterReconciler{ + Client: testEnv.Client, + Log: klogr.New(), + } + clusterScope = &scope.ClusterScope{ + IBMVPCClient: mockvpc, + Logger: klogr.New(), + IBMVPCCluster: &infrav1beta1.IBMVPCCluster{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{infrav1beta1.ClusterFinalizer}, + }, + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "vpc-load-balancer", + }, + }, + Status: infrav1beta1.IBMVPCClusterStatus{ + VPCEndpoint: infrav1beta1.VPCEndpoint{ + LBID: pointer.String("vpc-load-balancer-id"), + }, + Subnet: infrav1beta1.Subnet{ + ID: pointer.String("capi-subnet-id"), + }, + VPC: infrav1beta1.VPC{ + ID: "capi-vpc-id", + }, + }, + }, + } + } + teardown := func() { + mockCtrl.Finish() + } + + listVSIOpts := &vpcv1.ListInstancesOptions{ + VPCID: pointer.String("capi-vpc-id"), + } + response := &core.DetailedResponse{} + instancelist := &vpcv1.InstanceCollection{} + + t.Run("Reconciling deleting IBMVPCCluster with LoadBalancer", func(t *testing.T) { + listLoadBalancersOptions := &vpcv1.ListLoadBalancersOptions{} + loadBalancerCollection := &vpcv1.LoadBalancerCollection{} + getPGWOptions := &vpcv1.GetSubnetPublicGatewayOptions{ID: pointer.String("capi-subnet-id")} + pgw := &vpcv1.PublicGateway{ID: pointer.String("capi-pgw-id")} + unsetPGWOptions := &vpcv1.UnsetSubnetPublicGatewayOptions{ID: pointer.String("capi-subnet-id")} + deleteSubnetOptions := &vpcv1.DeleteSubnetOptions{ID: pointer.String("capi-subnet-id")} + deletePGWOptions := &vpcv1.DeletePublicGatewayOptions{ID: pgw.ID} + instancelist.TotalCount = pointer.Int64(0) + deleteVpcOptions := &vpcv1.DeleteVPCOptions{ID: pointer.String("capi-vpc-id")} + + t.Run("Should fail deleting the LoadBalancer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + mockvpc.EXPECT().ListInstances(listVSIOpts).Return(instancelist, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, errors.New("failed to list LoadBalancers")) + _, err := reconciler.reconcileDelete(clusterScope) + g.Expect(err).To(Not(BeNil())) + }) + t.Run("Should skip deleting other resources if LoadBalancer is still present", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + customloadBalancerCollection := &vpcv1.LoadBalancerCollection{ + LoadBalancers: []vpcv1.LoadBalancer{ + { + Name: core.StringPtr("vpc-load-balancer"), + ID: core.StringPtr("vpc-load-balancer-id"), + ProvisioningStatus: core.StringPtr("delete_pending"), + }, + }, + } + mockvpc.EXPECT().ListInstances(listVSIOpts).Return(instancelist, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(customloadBalancerCollection, response, nil) + _, err := reconciler.reconcileDelete(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1beta1.ClusterFinalizer)) + }) + t.Run("Should successfully delete IBMVPCCluster and remove the finalizer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + mockvpc.EXPECT().ListInstances(listVSIOpts).Return(instancelist, response, nil) + mockvpc.EXPECT().ListLoadBalancers(listLoadBalancersOptions).Return(loadBalancerCollection, response, nil) + mockvpc.EXPECT().GetSubnetPublicGateway(getPGWOptions).Return(pgw, response, nil) + mockvpc.EXPECT().UnsetSubnetPublicGateway(unsetPGWOptions).Return(response, nil) + mockvpc.EXPECT().DeletePublicGateway(deletePGWOptions).Return(response, nil) + mockvpc.EXPECT().DeleteSubnet(deleteSubnetOptions).Return(response, nil) + mockvpc.EXPECT().DeleteVPC(deleteVpcOptions).Return(response, nil) + _, err := reconciler.reconcileDelete(clusterScope) + g.Expect(err).To(BeNil()) + g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(Not(ContainElement(infrav1beta1.ClusterFinalizer))) + }) + }) +} + func createVPCCluster(g *WithT, vpcCluster *infrav1beta1.IBMVPCCluster, namespace string) { if vpcCluster != nil { vpcCluster.Namespace = namespace diff --git a/controllers/ibmvpcmachine_controller.go b/controllers/ibmvpcmachine_controller.go index de96b42a35..00fe9aad69 100644 --- a/controllers/ibmvpcmachine_controller.go +++ b/controllers/ibmvpcmachine_controller.go @@ -182,7 +182,7 @@ func (r *IBMVPCMachineReconciler) reconcileNormal(machineScope *scope.MachineSco }) } else { if instance.PrimaryNetworkInterface.PrimaryIP.Address == nil || *instance.PrimaryNetworkInterface.PrimaryIP.Address == "0.0.0.0" { - return ctrl.Result{}, errors.Wrapf(err, "invalid primary ip address") + return ctrl.Result{}, fmt.Errorf("invalid primary ip address") } internalIP := instance.PrimaryNetworkInterface.PrimaryIP.Address port := int64(6443) diff --git a/controllers/ibmvpcmachine_controller_test.go b/controllers/ibmvpcmachine_controller_test.go index a2724f3334..6ffc7c77d4 100644 --- a/controllers/ibmvpcmachine_controller_test.go +++ b/controllers/ibmvpcmachine_controller_test.go @@ -299,6 +299,164 @@ func TestIBMVPCMachineReconciler_reconcile(t *testing.T) { }) }) } +func TestIBMVPCMachineLBReconciler_reconcile(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + machineScope *scope.MachineScope + reconciler IBMVPCMachineReconciler + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + reconciler = IBMVPCMachineReconciler{ + Client: testEnv.Client, + Log: klogr.New(), + } + machineScope = &scope.MachineScope{ + Logger: klogr.New(), + IBMVPCMachine: &infrav1beta1.IBMVPCMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "capi-machine", + Labels: map[string]string{ + capiv1beta1.MachineControlPlaneLabelName: "capi-control-plane-machine", + }, + }, + }, + Machine: &capiv1beta1.Machine{ + Spec: capiv1beta1.MachineSpec{ + ClusterName: "vpc-cluster", + }, + }, + IBMVPCCluster: &infrav1beta1.IBMVPCCluster{ + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "vpc-load-balancer", + }, + }, + Status: infrav1beta1.IBMVPCClusterStatus{ + VPCEndpoint: infrav1beta1.VPCEndpoint{ + LBID: core.StringPtr("vpc-load-balancer-id"), + }, + }, + }, + IBMVPCClient: mockvpc, + } + } + teardown := func() { + mockCtrl.Finish() + } + + t.Run("Reconcile creating IBMVPCMachine associated with LoadBalancer", func(t *testing.T) { + options := &vpcv1.ListInstancesOptions{} + response := &core.DetailedResponse{} + instancelist := &vpcv1.InstanceCollection{} + instancelist.Instances = []vpcv1.Instance{ + { + Name: pointer.String("capi-machine"), + ID: pointer.String("capi-machine-id"), + PrimaryNetworkInterface: &vpcv1.NetworkInterfaceInstanceContextReference{ + PrimaryIP: &vpcv1.ReservedIPReference{ + Address: pointer.String("192.129.11.50"), + }, + ID: pointer.String("capi-net"), + }, + }} + getLoadBalancerOptions := &vpcv1.GetLoadBalancerOptions{} + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("vpc-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: core.StringPtr("foo-pool-id"), + }, + }, + } + listLoadBalancerPoolMembersOptions := &vpcv1.ListLoadBalancerPoolMembersOptions{} + loadBalancerPoolMemberCollection := &vpcv1.LoadBalancerPoolMemberCollection{ + Members: make([]vpcv1.LoadBalancerPoolMember, 0), + } + createLoadBalancerPoolMemberOptions := &vpcv1.CreateLoadBalancerPoolMemberOptions{} + loadBalancerPoolMember := &vpcv1.LoadBalancerPoolMember{ + ID: core.StringPtr("foo-member-id"), + ProvisioningStatus: core.StringPtr("active"), + } + + t.Run("Invalid primary ip address", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + machineScope.Machine.Spec.Bootstrap.DataSecretName = pointer.String("capi-machine") + machineScope.IBMVPCCluster.Status.Subnet.ID = pointer.String("capi-subnet-id") + customInstancelist := &vpcv1.InstanceCollection{} + customInstancelist.Instances = []vpcv1.Instance{ + { + Name: pointer.String("capi-machine"), + ID: pointer.String("capi-machine-id"), + PrimaryNetworkInterface: &vpcv1.NetworkInterfaceInstanceContextReference{ + PrimaryIP: &vpcv1.ReservedIPReference{ + Address: pointer.String("0.0.0.0"), + }, + ID: pointer.String("capi-net"), + }, + }} + mockvpc.EXPECT().ListInstances(options).Return(customInstancelist, response, nil) + _, err := reconciler.reconcileNormal(machineScope) + g.Expect(err).To((Not(BeNil()))) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(ContainElement(infrav1beta1.MachineFinalizer)) + }) + t.Run("Should fail to bind loadBalancer IP to control plane", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + machineScope.Machine.Spec.Bootstrap.DataSecretName = pointer.String("capi-machine") + machineScope.IBMVPCCluster.Status.Subnet.ID = pointer.String("capi-subnet-id") + mockvpc.EXPECT().ListInstances(options).Return(instancelist, response, nil) + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, response, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, response, errors.New("failed to list loadBalancerPoolMembers")) + _, err := reconciler.reconcileNormal(machineScope) + g.Expect(err).To(Not(BeNil())) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(ContainElement(infrav1beta1.MachineFinalizer)) + }) + t.Run("Should successfully reconcile IBMVPCMachine and set machine status as NotReady when PoolMember is not in active state", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + machineScope.Machine.Spec.Bootstrap.DataSecretName = pointer.String("capi-machine") + machineScope.IBMVPCCluster.Status.Subnet.ID = pointer.String("capi-subnet-id") + customloadBalancerPoolMember := &vpcv1.LoadBalancerPoolMember{ + ID: core.StringPtr("foo-member-id"), + ProvisioningStatus: core.StringPtr("create_pending"), + } + mockvpc.EXPECT().ListInstances(options).Return(instancelist, response, nil) + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, response, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, response, nil) + mockvpc.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(createLoadBalancerPoolMemberOptions)).Return(customloadBalancerPoolMember, response, nil) + _, err := reconciler.reconcileNormal(machineScope) + g.Expect(err).To(BeNil()) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(ContainElement(infrav1beta1.MachineFinalizer)) + g.Expect(machineScope.IBMVPCMachine.Status.Ready).To(Equal(false)) + }) + t.Run("Should successfully reconcile IBMVPCMachine", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + machineScope.Machine.Spec.Bootstrap.DataSecretName = pointer.String("capi-machine") + machineScope.IBMVPCCluster.Status.Subnet.ID = pointer.String("capi-subnet-id") + mockvpc.EXPECT().ListInstances(options).Return(instancelist, response, nil) + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, response, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, response, nil) + mockvpc.EXPECT().CreateLoadBalancerPoolMember(gomock.AssignableToTypeOf(createLoadBalancerPoolMemberOptions)).Return(loadBalancerPoolMember, response, nil) + _, err := reconciler.reconcileNormal(machineScope) + g.Expect(err).To(BeNil()) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(ContainElement(infrav1beta1.MachineFinalizer)) + g.Expect(machineScope.IBMVPCMachine.Status.Ready).To(Equal(true)) + }) + }) +} + func TestIBMVPCMachineReconciler_Delete(t *testing.T) { var ( mockvpc *mock.MockVpc @@ -356,3 +514,98 @@ func TestIBMVPCMachineReconciler_Delete(t *testing.T) { }) }) } + +func TestIBMVPCMachineLBReconciler_Delete(t *testing.T) { + var ( + mockvpc *mock.MockVpc + mockCtrl *gomock.Controller + machineScope *scope.MachineScope + reconciler IBMVPCMachineReconciler + ) + + setup := func(t *testing.T) { + t.Helper() + mockCtrl = gomock.NewController(t) + mockvpc = mock.NewMockVpc(mockCtrl) + reconciler = IBMVPCMachineReconciler{ + Client: testEnv.Client, + Log: klogr.New(), + } + machineScope = &scope.MachineScope{ + Logger: klogr.New(), + IBMVPCMachine: &infrav1beta1.IBMVPCMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "capi-machine", + Finalizers: []string{infrav1beta1.MachineFinalizer}, + Labels: map[string]string{ + capiv1beta1.MachineControlPlaneLabelName: "capi-control-plane-machine", + }, + }, + Status: infrav1beta1.IBMVPCMachineStatus{ + InstanceID: "capi-machine-id", + }, + }, + IBMVPCClient: mockvpc, + IBMVPCCluster: &infrav1beta1.IBMVPCCluster{ + Spec: infrav1beta1.IBMVPCClusterSpec{ + ControlPlaneLoadBalancer: &infrav1beta1.VPCLoadBalancerSpec{ + Name: "vpc-load-balancer", + }, + }, + Status: infrav1beta1.IBMVPCClusterStatus{ + VPCEndpoint: infrav1beta1.VPCEndpoint{ + LBID: core.StringPtr("vpc-load-balancer-id"), + }, + }, + }, + } + } + teardown := func() { + mockCtrl.Finish() + } + + t.Run("Reconciling deleting IBMVPCMachine associated with LoadBalancer", func(t *testing.T) { + getLoadBalancerOptions := &vpcv1.GetLoadBalancerOptions{} + loadBalancer := &vpcv1.LoadBalancer{ + ID: core.StringPtr("vpc-load-balancer-id"), + ProvisioningStatus: core.StringPtr("active"), + Pools: []vpcv1.LoadBalancerPoolReference{ + { + ID: core.StringPtr("foo-pool-id"), + }, + }, + } + response := &core.DetailedResponse{} + getInstanceOptions := &vpcv1.GetInstanceOptions{} + instance := &vpcv1.Instance{} + listLoadBalancerPoolMembersOptions := &vpcv1.ListLoadBalancerPoolMembersOptions{} + loadBalancerPoolMemberCollection := &vpcv1.LoadBalancerPoolMemberCollection{ + Members: make([]vpcv1.LoadBalancerPoolMember, 0), + } + options := &vpcv1.DeleteInstanceOptions{ID: pointer.String("capi-instance-id")} + + t.Run("Should fail to delete VPC LoadBalancerPoolMember", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, response, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, response, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, response, errors.New("failed to list LoadBalancerPoolMembers")) + _, err := reconciler.reconcileDelete(machineScope) + g.Expect(err).To((Not(BeNil()))) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(ContainElement(infrav1beta1.MachineFinalizer)) + }) + t.Run("Should successfully delete VPC machine and remove the finalizer", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + mockvpc.EXPECT().GetLoadBalancer(gomock.AssignableToTypeOf(getLoadBalancerOptions)).Return(loadBalancer, response, nil) + mockvpc.EXPECT().GetInstance(gomock.AssignableToTypeOf(getInstanceOptions)).Return(instance, response, nil) + mockvpc.EXPECT().ListLoadBalancerPoolMembers(gomock.AssignableToTypeOf(listLoadBalancerPoolMembersOptions)).Return(loadBalancerPoolMemberCollection, response, nil) + mockvpc.EXPECT().DeleteInstance(gomock.AssignableToTypeOf(options)).Return(response, nil) + _, err := reconciler.reconcileDelete(machineScope) + g.Expect(err).To(BeNil()) + g.Expect(machineScope.IBMVPCMachine.Finalizers).To(Not(ContainElement(infrav1beta1.MachineFinalizer))) + }) + }) +}