diff --git a/cloud/scope/powervs_cluster.go b/cloud/scope/powervs_cluster.go index f28e29181..20ec993bd 100644 --- a/cloud/scope/powervs_cluster.go +++ b/cloud/scope/powervs_cluster.go @@ -34,6 +34,7 @@ import ( cosSession "github.com/IBM/ibm-cos-sdk-go/aws/session" "github.com/IBM/ibm-cos-sdk-go/service/s3" tgapiv1 "github.com/IBM/networking-go-sdk/transitgatewayapisv1" + "github.com/IBM/platform-services-go-sdk/globaltaggingv1" "github.com/IBM/platform-services-go-sdk/resourcecontrollerv2" "github.com/IBM/platform-services-go-sdk/resourcemanagerv2" "github.com/IBM/vpc-go-sdk/vpcv1" @@ -51,6 +52,7 @@ import ( 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" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/globaltagging" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/powervs" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcecontroller" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcemanager" @@ -80,6 +82,7 @@ const ( // vpcSubnetIPAddressCount is the total IP Addresses for the subnet. // Support for custom address prefixes will be added at a later time. Currently, we use the ip count for subnet creation. vpcSubnetIPAddressCount int64 = 256 + tagKey = "powervs.cluster.x-k8s.io-resource-owner:" ) // PowerVSClusterScopeParams defines the input parameters used to create a new PowerVSClusterScope. @@ -102,6 +105,7 @@ type ClientFactory struct { TransitGatewayFactory func() (transitgateway.TransitGateway, error) ResourceControllerFactory func() (resourcecontroller.ResourceController, error) ResourceManagerFactory func() (resourcemanager.ResourceManager, error) + GlobalTaggingFactory func() (globaltagging.GlobalTagging, error) } // PowerVSClusterScope defines a scope defined around a Power VS Cluster. @@ -110,6 +114,7 @@ type PowerVSClusterScope struct { patchHelper *patch.Helper IBMPowerVSClient powervs.PowerVS + GlobalTaggingClient globaltagging.GlobalTagging IBMVPCClient vpc.Vpc TransitGatewayClient transitgateway.TransitGateway ResourceClient resourcecontroller.ResourceController @@ -253,6 +258,18 @@ func NewPowerVSClusterScope(params PowerVSClusterScopeParams) (*PowerVSClusterSc Authenticator: auth, } + // Create Global Tagging client. + gtOptions := globaltagging.ServiceOptions{ + GlobalTaggingV1Options: &globaltaggingv1.GlobalTaggingV1Options{ + Authenticator: auth, + }, + } + + globalTaggingClient, err := params.getGlobalTaggingClient(gtOptions) + if err != nil { + return nil, fmt.Errorf("failed to create global tagging client: %w", err) + } + rmClient, err := params.getResourceManagerClient(rcManagerOptions) if err != nil { return nil, fmt.Errorf("failed to create resource manager client: %w", err) @@ -267,6 +284,7 @@ func NewPowerVSClusterScope(params PowerVSClusterScopeParams) (*PowerVSClusterSc IBMPowerVSClient: powerVSClient, IBMVPCClient: vpcClient, TransitGatewayClient: tgClient, + GlobalTaggingClient: globalTaggingClient, ResourceClient: resourceClient, ResourceManagerClient: rmClient, } @@ -294,6 +312,18 @@ func (params PowerVSClusterScopeParams) getPowerVSClient(options powervs.Service return powervs.NewService(options) } +func (params PowerVSClusterScopeParams) getGlobalTaggingClient(gtOptions globaltagging.ServiceOptions) (globaltagging.GlobalTagging, error) { + if params.GlobalTaggingFactory != nil { + return params.GlobalTaggingFactory() + } + + if gtEndpoint := endpoints.FetchEndpoints(string(endpoints.GlobalTagging), params.ServiceEndpoint); gtEndpoint != "" { + params.Logger.V(3).Info("Overriding the default global tagging endpoint", "GlobaTaggingEndpoint", gtEndpoint) + gtOptions.URL = gtEndpoint + } + return globaltagging.NewService(gtOptions) +} + func (params PowerVSClusterScopeParams) getVPCClient() (vpc.Vpc, error) { if params.Logger.V(DEBUGLEVEL).Enabled() { core.SetLoggingLevel(core.LevelDebug) @@ -859,10 +889,13 @@ func (s *PowerVSClusterScope) createServiceInstance(ctx context.Context) (*resou if zone == nil { return nil, fmt.Errorf("PowerVS zone is not set") } + + tag := tagKey + s.Name() serviceInstance, _, err := s.ResourceClient.CreateResourceInstance(&resourcecontrollerv2.CreateResourceInstanceOptions{ Name: s.GetServiceName(infrav1beta2.ResourceTypeServiceInstance), Target: zone, ResourceGroup: &resourceGroupID, + Tags: append(make([]string, 0), tag), ResourcePlanID: ptr.To(resourcecontroller.PowerVSResourcePlanID), }) if err != nil { @@ -1084,6 +1117,24 @@ func (s *PowerVSClusterScope) createDHCPServer(ctx context.Context) (*string, er return dhcpServer.ID, nil } +// TagResource will attach a user Tag to a resource. +func (s *PowerVSClusterScope) TagResource(tagName string, resourceCRN *string) error { + tagOptions := &globaltaggingv1.AttachTagOptions{} + tagOptions.SetResources([]globaltaggingv1.Resource{ + { + ResourceID: resourceCRN, + }, + }) + + tagOptions.SetTagName(tagName) + tagOptions.SetTagType(globaltaggingv1.AttachTagOptionsTagTypeUserConst) + if _, _, err := s.GlobalTaggingClient.AttachTag(tagOptions); err != nil { + return fmt.Errorf("failure tagging resource: %w", err) + } + + return nil +} + // ReconcileVPC reconciles VPC. func (s *PowerVSClusterScope) ReconcileVPC(ctx context.Context) (bool, error) { log := ctrl.LoggerFrom(ctx) @@ -1124,7 +1175,7 @@ func (s *PowerVSClusterScope) ReconcileVPC(ctx context.Context) (bool, error) { // create VPC log.Info("Creating a VPC") - vpcID, err = s.createVPC() + vpcID, err = s.createVPC(ctx) if err != nil { return false, fmt.Errorf("failed to create VPC: %w", err) } @@ -1168,7 +1219,8 @@ func (s *PowerVSClusterScope) getVPCByName() (*vpcv1.VPC, error) { } // createVPC creates VPC. -func (s *PowerVSClusterScope) createVPC() (*string, error) { +func (s *PowerVSClusterScope) createVPC(ctx context.Context) (*string, error) { + log := ctrl.LoggerFrom(ctx) resourceGroupID := s.GetResourceGroupID() if resourceGroupID == "" { return nil, fmt.Errorf("failed to fetch resource group ID for resource group %v, ID is empty", s.ResourceGroup()) @@ -1184,6 +1236,11 @@ func (s *PowerVSClusterScope) createVPC() (*string, error) { return nil, err } + tag := tagKey + s.Name() + if err = s.TagResource(tag, vpcDetails.CRN); err != nil { + log.Error(err, "failed to tag vpc") + } + // set security group for vpc options := &vpcv1.CreateSecurityGroupRuleOptions{} options.SetSecurityGroupID(*vpcDetails.DefaultSecurityGroup.ID) @@ -1270,7 +1327,7 @@ func (s *PowerVSClusterScope) ReconcileVPCSubnets(ctx context.Context) (bool, er subnet.Zone = &vpcZones[index%len(vpcZones)] } log.Info("Creating VPC subnet") - subnetID, err = s.createVPCSubnet(subnet) + subnetID, err = s.createVPCSubnet(ctx, subnet) if err != nil { return false, fmt.Errorf("error creating VPC subnet: %w", err) } @@ -1299,7 +1356,8 @@ func (s *PowerVSClusterScope) checkVPCSubnet(ctx context.Context, subnetName str } // createVPCSubnet creates a VPC subnet. -func (s *PowerVSClusterScope) createVPCSubnet(subnet infrav1beta2.Subnet) (*string, error) { +func (s *PowerVSClusterScope) createVPCSubnet(ctx context.Context, subnet infrav1beta2.Subnet) (*string, error) { + log := ctrl.LoggerFrom(ctx) // TODO(karthik-k-n): consider moving to clusterscope // fetch resource group id resourceGroupID := s.GetResourceGroupID() @@ -1338,6 +1396,12 @@ func (s *PowerVSClusterScope) createVPCSubnet(subnet infrav1beta2.Subnet) (*stri if subnetDetails == nil { return nil, fmt.Errorf("created VPC subnet is nil") } + + tag := tagKey + s.Name() + err = s.TagResource(tag, subnetDetails.CRN) + if err != nil { + log.Error(err, "failed to tag subnet") + } return subnetDetails.ID, nil } @@ -1560,7 +1624,12 @@ func (s *PowerVSClusterScope) createVPCSecurityGroup(ctx context.Context, spec i if err != nil { return nil, fmt.Errorf("error creating VPC security group: %w", err) } - // To-Do: Add tags to VPC security group, need to implement the client for "github.com/IBM/platform-services-go-sdk/globaltaggingv1". + + tag := tagKey + s.Name() + err = s.TagResource(tag, securityGroup.CRN) + if err != nil { + log.Error(err, "failed to tag security group") + } return securityGroup.ID, nil } @@ -1987,6 +2056,7 @@ func (s *PowerVSClusterScope) createTransitGatewayConnections(ctx context.Contex // createTransitGateway creates transit gateway and sets the transit gateway status. func (s *PowerVSClusterScope) createTransitGateway(ctx context.Context) error { + log := ctrl.LoggerFrom(ctx) // fetch resource group id resourceGroupID := s.GetResourceGroupID() if resourceGroupID == "" { @@ -2023,6 +2093,12 @@ func (s *PowerVSClusterScope) createTransitGateway(ctx context.Context) error { return err } + tag := tagKey + s.Name() + err = s.TagResource(tag, tg.Crn) + if err != nil { + log.Error(err, "failed to tag transitGateway") + } + s.SetTransitGatewayStatus(tg.ID, ptr.To(true)) vpcCRN, err := s.fetchVPCCRN() @@ -2243,6 +2319,12 @@ func (s *PowerVSClusterScope) createLoadBalancer(ctx context.Context, lb infrav1 if err != nil { return nil, fmt.Errorf("failed to create load balancer: %w", err) } + + tag := tagKey + s.Name() + if err = s.TagResource(tag, loadBalancer.CRN); err != nil { + log.Error(err, "failed to tag load balancer") + } + lbState := infrav1beta2.VPCLoadBalancerState(*loadBalancer.ProvisioningStatus) return &infrav1beta2.VPCLoadBalancerStatus{ ID: loadBalancer.ID, @@ -2398,12 +2480,14 @@ func (s *PowerVSClusterScope) createCOSServiceInstance() (*resourcecontrollerv2. return nil, fmt.Errorf("failed to fetch resource group ID for resource group %v, ID is empty", s.ResourceGroup()) } + tag := tagKey + s.Name() target := "Global" // create service instance serviceInstance, _, err := s.ResourceClient.CreateResourceInstance(&resourcecontrollerv2.CreateResourceInstanceOptions{ Name: s.GetServiceName(infrav1beta2.ResourceTypeCOSInstance), Target: &target, ResourceGroup: &resourceGroupID, + Tags: append(make([]string, 0), tag), ResourcePlanID: ptr.To(resourcecontroller.CosResourcePlanID), }) if err != nil { diff --git a/cloud/scope/powervs_cluster_test.go b/cloud/scope/powervs_cluster_test.go index beb1a7777..2f1d1a3ab 100644 --- a/cloud/scope/powervs_cluster_test.go +++ b/cloud/scope/powervs_cluster_test.go @@ -35,6 +35,8 @@ import ( tgapiv1 "github.com/IBM/networking-go-sdk/transitgatewayapisv1" infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/globaltagging" + gtmock "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/globaltagging/mock" mockP "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/powervs/mock" tgmock "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/transitgateway/mock" capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -151,6 +153,9 @@ func TestNewPowerVSClusterScope(t *testing.T) { ResourceManagerFactory: func() (resourcemanager.ResourceManager, error) { return nil, nil }, + GlobalTaggingFactory: func() (globaltagging.GlobalTagging, error) { + return nil, nil + }, }, }, expectError: false, @@ -1166,12 +1171,14 @@ func TestGetResourceGroupID(t *testing.T) { func TestReconcileLoadBalancers(t *testing.T) { var ( mockVpc *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(gomock.NewController(t)) mockVpc = mock.NewMockVpc(mockCtrl) } @@ -1491,7 +1498,8 @@ func TestReconcileLoadBalancers(t *testing.T) { clusterAPIServerPort := int32(9090) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVpc, + IBMVPCClient: mockVpc, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -1533,7 +1541,9 @@ func TestReconcileLoadBalancers(t *testing.T) { ID: ptr.To("test-lb-id"), ProvisioningStatus: ptr.To("active"), Hostname: ptr.To("test-lb-hostname"), + CRN: ptr.To("lb-crn"), }, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) loadBalancerReady, err := clusterScope.ReconcileLoadBalancers(ctx) g.Expect(loadBalancerReady).To(BeFalse()) @@ -1550,12 +1560,14 @@ func TestReconcileLoadBalancers(t *testing.T) { func TestCreateLoadbalancer(t *testing.T) { var ( mockVpc *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(gomock.NewController(t)) mockVpc = mock.NewMockVpc(mockCtrl) } @@ -1698,7 +1710,8 @@ func TestCreateLoadbalancer(t *testing.T) { clusterAPIServerPort := int32(9090) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVpc, + IBMVPCClient: mockVpc, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -1748,7 +1761,77 @@ func TestCreateLoadbalancer(t *testing.T) { ID: ptr.To("test-lb-id"), ProvisioningStatus: ptr.To("active"), Hostname: ptr.To("test-lb-hostname"), + CRN: ptr.To("lb-crn"), }, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) + + loadBalancerStatus, err := clusterScope.createLoadBalancer(ctx, lb) + g.Expect(err).To(BeNil()) + g.Expect(loadBalancerStatus.State).To(BeEquivalentTo(infrav1beta2.VPCLoadBalancerStateActive)) + g.Expect(loadBalancerStatus.ControllerCreated).To(Equal(ptr.To(true))) + g.Expect(loadBalancerStatus.Hostname).To(Equal(ptr.To("test-lb-hostname"))) + }) + t.Run("When IBMVPCClient client CreateLoadBalancer successfully creates load balancer in cloud but tagging load balancer fails", func(*testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + + clusterAPIServerPort := int32(9090) + clusterScope := PowerVSClusterScope{ + IBMVPCClient: mockVpc, + GlobalTaggingClient: mockgt, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + + ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ + ID: ptr.To("test-resource-gid"), + }, + LoadBalancers: []infrav1beta2.VPCLoadBalancerSpec{ + { + Name: "test-lb", + ID: nil, + }, + }, + VPCSubnets: []infrav1beta2.Subnet{ + { + Name: ptr.To("test-subnet"), + ID: ptr.To("test-subnetid"), + }, + }, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + VPCSubnet: map[string]infrav1beta2.ResourceReference{ + "test-subnet": { + ID: ptr.To("test-resource-reference-id"), + }, + }, + }, + }, + Cluster: &capiv1beta1.Cluster{ + Spec: capiv1beta1.ClusterSpec{ + ClusterNetwork: &capiv1beta1.ClusterNetwork{ + APIServerPort: &clusterAPIServerPort, + }, + }, + }, + } + + lb := infrav1beta2.VPCLoadBalancerSpec{ + Name: "test-lb", + AdditionalListeners: []infrav1beta2.AdditionalListenerSpec{ + { + Port: int64(9090), + }, + }, + } + + mockVpc.EXPECT().CreateLoadBalancer(gomock.Any()).Return(&vpcv1.LoadBalancer{ + ID: ptr.To("test-lb-id"), + ProvisioningStatus: ptr.To("active"), + Hostname: ptr.To("test-lb-hostname"), + CRN: ptr.To("lb-crn"), + }, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, fmt.Errorf("failed to tag load balancer")) loadBalancerStatus, err := clusterScope.createLoadBalancer(ctx, lb) g.Expect(err).To(BeNil()) @@ -2178,6 +2261,7 @@ func TestReconcilePowerVSServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -2203,6 +2287,7 @@ func TestReconcilePowerVSServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -2459,6 +2544,7 @@ func TestCreateServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -2482,6 +2568,7 @@ func TestCreateServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -2504,12 +2591,14 @@ func TestCreateServiceInstance(t *testing.T) { func TestReconcileVPC(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -2552,14 +2641,16 @@ func TestReconcileVPC(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, - Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}}}, } - vpcOutput := &vpcv1.VPC{Name: ptr.To("VPCName"), ID: ptr.To("vpcID"), DefaultSecurityGroup: &vpcv1.SecurityGroupReference{ID: ptr.To("DefaultSecurityGroupID")}} + vpcOutput := &vpcv1.VPC{Name: ptr.To("VPCName"), ID: ptr.To("vpcID"), CRN: ptr.To("vpc-crn"), DefaultSecurityGroup: &vpcv1.SecurityGroupReference{ID: ptr.To("DefaultSecurityGroupID")}} mockVPC.EXPECT().GetVPCByName(gomock.Any()).Return(nil, nil) mockVPC.EXPECT().CreateVPC(gomock.Any()).Return(vpcOutput, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().CreateSecurityGroupRule(gomock.Any()).Return(nil, nil, nil) requeue, err := clusterScope.ReconcileVPC(ctx) g.Expect(err).To(BeNil()) @@ -2678,12 +2769,14 @@ func TestReconcileVPC(t *testing.T) { func TestPowerVSScopeCreateVPC(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -2699,7 +2792,7 @@ func TestPowerVSScopeCreateVPC(t *testing.T) { IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{}, } - vpcID, err := clusterScope.createVPC() + vpcID, err := clusterScope.createVPC(ctx) g.Expect(err).ToNot(BeNil()) g.Expect(vpcID).To(BeNil()) }) @@ -2709,16 +2802,40 @@ func TestPowerVSScopeCreateVPC(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, - Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}}}, } vpcOutput := &vpcv1.VPC{Name: ptr.To("VPCName"), ID: ptr.To("vpcID"), DefaultSecurityGroup: &vpcv1.SecurityGroupReference{ID: ptr.To("DefaultSecurityGroupID")}} mockVPC.EXPECT().CreateVPC(gomock.Any()).Return(vpcOutput, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().CreateSecurityGroupRule(gomock.Any()).Return(nil, nil, nil) - vpcID, err := clusterScope.createVPC() + vpcID, err := clusterScope.createVPC(ctx) + g.Expect(err).To(BeNil()) + g.Expect(vpcID).To(Equal(vpcOutput.ID)) + }) + + t.Run("When create VPC is successful but tagging vpc fails", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + + clusterScope := PowerVSClusterScope{ + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{Spec: infrav1beta2.IBMPowerVSClusterSpec{ + ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}}}, + } + vpcOutput := &vpcv1.VPC{Name: ptr.To("VPCName"), ID: ptr.To("vpcID"), DefaultSecurityGroup: &vpcv1.SecurityGroupReference{ID: ptr.To("DefaultSecurityGroupID")}} + mockVPC.EXPECT().CreateVPC(gomock.Any()).Return(vpcOutput, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, fmt.Errorf("AttachTag returns error")) + mockVPC.EXPECT().CreateSecurityGroupRule(gomock.Any()).Return(nil, nil, nil) + + vpcID, err := clusterScope.createVPC(ctx) g.Expect(err).To(BeNil()) g.Expect(vpcID).To(Equal(vpcOutput.ID)) }) @@ -2729,15 +2846,17 @@ func TestPowerVSScopeCreateVPC(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, - Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + Cluster: &capiv1beta1.Cluster{Spec: capiv1beta1.ClusterSpec{ClusterNetwork: nil}}, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}}}, } vpcOutput := &vpcv1.VPC{Name: ptr.To("VPCName"), ID: ptr.To("vpcID"), DefaultSecurityGroup: &vpcv1.SecurityGroupReference{ID: ptr.To("DefaultSecurityGroupID")}} mockVPC.EXPECT().CreateVPC(gomock.Any()).Return(vpcOutput, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().CreateSecurityGroupRule(gomock.Any()).Return(nil, nil, fmt.Errorf("CreateSecurityGroupRule returns error")) - vpcID, err := clusterScope.createVPC() + vpcID, err := clusterScope.createVPC(ctx) g.Expect(err).ToNot(BeNil()) g.Expect(vpcID).To(BeNil()) }) @@ -3537,11 +3656,13 @@ func TestReconcileNetwork(t *testing.T) { func TestReconcileVPCSubnets(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -3583,7 +3704,9 @@ func TestReconcileVPCSubnets(t *testing.T) { var subnetZone *string clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ ObjectMeta: metav1.ObjectMeta{Name: "ClusterName"}, Status: infrav1beta2.IBMPowerVSClusterStatus{ @@ -3628,6 +3751,7 @@ func TestReconcileVPCSubnets(t *testing.T) { }) subnetDetails := &vpcv1.Subnet{ID: ptr.To(fmt.Sprintf("subnet%dID", i+1)), Name: ptr.To(fmt.Sprintf("subnet%dName", i+1))} mockVPC.EXPECT().CreateSubnet(subnet1Options).Return(subnetDetails, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) } requeue, err := clusterScope.ReconcileVPCSubnets(ctx) g.Expect(requeue).To(BeTrue()) @@ -3644,7 +3768,9 @@ func TestReconcileVPCSubnets(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ ObjectMeta: metav1.ObjectMeta{Name: "ClusterName"}, Status: infrav1beta2.IBMPowerVSClusterStatus{ @@ -3680,6 +3806,7 @@ func TestReconcileVPCSubnets(t *testing.T) { }) subnetDetails := &vpcv1.Subnet{ID: ptr.To(fmt.Sprintf("subnet%dID", i+1)), Name: ptr.To(fmt.Sprintf("subnet%dName", i+1))} mockVPC.EXPECT().CreateSubnet(subnet1Options).Return(subnetDetails, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) } requeue, err := clusterScope.ReconcileVPCSubnets(ctx) g.Expect(requeue).To(BeTrue()) @@ -3695,7 +3822,9 @@ func TestReconcileVPCSubnets(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ ObjectMeta: metav1.ObjectMeta{Name: "ClusterName"}, Status: infrav1beta2.IBMPowerVSClusterStatus{ @@ -3709,6 +3838,7 @@ func TestReconcileVPCSubnets(t *testing.T) { subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1")} mockVPC.EXPECT().GetVPCSubnetByName(gomock.Any()).Return(nil, nil).Times(3) mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(subnet1Details, nil, nil).Times(3) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil).Times(3) requeue, err := clusterScope.ReconcileVPCSubnets(ctx) g.Expect(requeue).To(BeTrue()) g.Expect(err).To(BeNil()) @@ -3723,7 +3853,9 @@ func TestReconcileVPCSubnets(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ ObjectMeta: metav1.ObjectMeta{Name: "ClusterName"}, Status: infrav1beta2.IBMPowerVSClusterStatus{ @@ -3735,9 +3867,10 @@ func TestReconcileVPCSubnets(t *testing.T) { VPCSubnets: []infrav1beta2.Subnet{{Name: ptr.To("subnet1Name")}}}, }, } - subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("subnet1Name")} + subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("subnet1Name"), CRN: ptr.To("subnet-crn")} mockVPC.EXPECT().GetVPCSubnetByName(gomock.Any()).Return(nil, nil) mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(subnet1Details, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) requeue, err := clusterScope.ReconcileVPCSubnets(ctx) g.Expect(requeue).To(BeTrue()) @@ -3783,7 +3916,9 @@ func TestReconcileVPCSubnets(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ ObjectMeta: metav1.ObjectMeta{Name: "ClusterName"}, Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -4001,11 +4136,13 @@ func TestCheckVPCSubnet(t *testing.T) { func TestCreateVPCSubnet(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(gomock.NewController(t)) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -4017,7 +4154,9 @@ func TestCreateVPCSubnet(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -4026,10 +4165,36 @@ func TestCreateVPCSubnet(t *testing.T) { }, } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), Zone: ptr.To("eu-de-1")} - subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1")} + subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), CRN: ptr.To("subnet-crn")} + + mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(subnet1Details, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) + g.Expect(subnetID).To(Equal(subnet1Details.ID)) + g.Expect(err).To(BeNil()) + }) + t.Run("When createVPCSubnet returns success but tagging subnet fails", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + + clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{VPC: &infrav1beta2.ResourceReference{ID: ptr.To("vpcID")}}, + }, + } + subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), Zone: ptr.To("eu-de-1")} + subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), CRN: ptr.To("subnet-crn")} mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(subnet1Details, nil, nil) - subnetID, err := clusterScope.createVPCSubnet(subnet) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, fmt.Errorf("error attching subnet")) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(Equal(subnet1Details.ID)) g.Expect(err).To(BeNil()) }) @@ -4039,7 +4204,11 @@ func TestCreateVPCSubnet(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test-cluster"}, + }, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -4049,10 +4218,11 @@ func TestCreateVPCSubnet(t *testing.T) { }, } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1")} - subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1")} + subnet1Details := &vpcv1.Subnet{ID: ptr.To("subnet1ID"), Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), CRN: ptr.To("subnet-crn")} mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(subnet1Details, nil, nil) - subnetID, err := clusterScope.createVPCSubnet(subnet) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(Equal(subnet1Details.ID)) g.Expect(err).To(BeNil()) }) @@ -4067,7 +4237,7 @@ func TestCreateVPCSubnet(t *testing.T) { IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{Spec: infrav1beta2.IBMPowerVSClusterSpec{}}, } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1")} - subnetID, err := clusterScope.createVPCSubnet(subnet) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(BeNil()) g.Expect(err).ToNot(BeNil()) }) @@ -4083,7 +4253,7 @@ func TestCreateVPCSubnet(t *testing.T) { Spec: infrav1beta2.IBMPowerVSClusterSpec{ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}}}, } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), Zone: ptr.To("eu-de-1")} - subnetID, err := clusterScope.createVPCSubnet(subnet) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(BeNil()) g.Expect(err).ToNot(BeNil()) }) @@ -4101,7 +4271,7 @@ func TestCreateVPCSubnet(t *testing.T) { } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), Zone: ptr.To("eu-de-1")} mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(nil, nil, fmt.Errorf("error creating subnet")) - subnetID, err := clusterScope.createVPCSubnet(subnet) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(BeNil()) g.Expect(err).ToNot(BeNil()) }) @@ -4118,7 +4288,7 @@ func TestCreateVPCSubnet(t *testing.T) { } subnet := infrav1beta2.Subnet{Name: ptr.To("ClusterName-vpcsubnet-eu-de-1"), Zone: ptr.To("eu-de-1")} mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(nil, nil, nil) - subnetID, err := clusterScope.createVPCSubnet(subnet) + subnetID, err := clusterScope.createVPCSubnet(ctx, subnet) g.Expect(subnetID).To(BeNil()) g.Expect(err).ToNot(BeNil()) }) @@ -5821,6 +5991,7 @@ func TestReconcileCOSInstance(t *testing.T) { defer os.Unsetenv("IBMCLOUD_APIKEY") clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -5862,6 +6033,7 @@ func TestReconcileCOSInstance(t *testing.T) { defer os.Unsetenv("IBMCLOUD_APIKEY") clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -5931,6 +6103,7 @@ func TestReconcileCOSInstance(t *testing.T) { defer os.Unsetenv("IBMCLOUD_APIKEY") clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -5969,6 +6142,7 @@ func TestReconcileCOSInstance(t *testing.T) { defer os.Unsetenv("IBMCLOUD_APIKEY") clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -6373,6 +6547,7 @@ func TestCreateCOSServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -6401,6 +6576,7 @@ func TestCreateCOSServiceInstance(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, ResourceClient: mockResourceController, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ @@ -6431,6 +6607,7 @@ func TestReconcileTransitGateway(t *testing.T) { mockResourceController *mockRC.MockResourceController mockVPC *mock.MockVpc mockTransitGateway *tgmock.MockTransitGateway + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) @@ -6439,6 +6616,7 @@ func TestReconcileTransitGateway(t *testing.T) { mockCtrl = gomock.NewController(t) mockTransitGateway = tgmock.NewMockTransitGateway(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockResourceController = mockRC.NewMockResourceController(mockCtrl) } teardown := func() { @@ -6613,9 +6791,11 @@ func TestReconcileTransitGateway(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, TransitGatewayClient: mockTransitGateway, IBMVPCClient: mockVPC, ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -6635,6 +6815,7 @@ func TestReconcileTransitGateway(t *testing.T) { mockTransitGateway.EXPECT().GetTransitGatewayByName(gomock.Any()).Return(nil, nil) mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To(string(infrav1beta2.TransitGatewayStateAvailable))}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{CRN: ptr.To("crn")}, nil, nil) mockResourceController.EXPECT().GetResourceInstance(gomock.Any()).Return(&resourcecontrollerv2.ResourceInstance{CRN: ptr.To("crn")}, nil, nil) mockTransitGateway.EXPECT().CreateTransitGatewayConnection(gomock.Any()).Return(&tgapiv1.TransitGatewayConnectionCust{ID: ptr.To("pvs-connID")}, nil, nil) @@ -6905,6 +7086,7 @@ func TestCreateTransitGateway(t *testing.T) { mockResourceController *mockRC.MockResourceController mockVPC *mock.MockVpc mockTransitGateway *tgmock.MockTransitGateway + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) @@ -6913,6 +7095,7 @@ func TestCreateTransitGateway(t *testing.T) { mockCtrl = gomock.NewController(t) mockTransitGateway = tgmock.NewMockTransitGateway(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockResourceController = mockRC.NewMockResourceController(mockCtrl) } teardown := func() { @@ -7010,9 +7193,11 @@ func TestCreateTransitGateway(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, TransitGatewayClient: mockTransitGateway, IBMVPCClient: mockVPC, ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -7030,7 +7215,8 @@ func TestCreateTransitGateway(t *testing.T) { }, } - mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To("pending")}, nil, nil) + mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Crn: ptr.To("tg-crn"), Status: ptr.To("pending")}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(nil, nil, errors.New("failed to get vpc")) err := clusterScope.createTransitGateway(ctx) g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.ID).To(BeEquivalentTo("transitGatewayID")) @@ -7044,9 +7230,11 @@ func TestCreateTransitGateway(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, TransitGatewayClient: mockTransitGateway, IBMVPCClient: mockVPC, ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -7065,6 +7253,7 @@ func TestCreateTransitGateway(t *testing.T) { } mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To(string(infrav1beta2.TransitGatewayStateAvailable))}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{CRN: ptr.To("crn")}, nil, nil) mockResourceController.EXPECT().GetResourceInstance(gomock.Any()).Return(nil, nil, errors.New("failed to get power vs instance")) err := clusterScope.createTransitGateway(ctx) @@ -7079,9 +7268,11 @@ func TestCreateTransitGateway(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, TransitGatewayClient: mockTransitGateway, IBMVPCClient: mockVPC, ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, @@ -7100,6 +7291,7 @@ func TestCreateTransitGateway(t *testing.T) { } mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To(string(infrav1beta2.TransitGatewayStateAvailable))}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{CRN: ptr.To("crn")}, nil, nil) mockResourceController.EXPECT().GetResourceInstance(gomock.Any()).Return(&resourcecontrollerv2.ResourceInstance{CRN: ptr.To("crn")}, nil, nil) mockTransitGateway.EXPECT().CreateTransitGatewayConnection(gomock.Any()).Return(&tgapiv1.TransitGatewayConnectionCust{ID: ptr.To("pvs-connID")}, nil, nil) @@ -7152,9 +7344,11 @@ func TestCreateTransitGateway(t *testing.T) { t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, TransitGatewayClient: mockTransitGateway, IBMVPCClient: mockVPC, ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ TransitGateway: &infrav1beta2.TransitGateway{ @@ -7176,6 +7370,54 @@ func TestCreateTransitGateway(t *testing.T) { } mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To(string(infrav1beta2.TransitGatewayStateAvailable))}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) + mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{CRN: ptr.To("crn")}, nil, nil) + mockResourceController.EXPECT().GetResourceInstance(gomock.Any()).Return(&resourcecontrollerv2.ResourceInstance{CRN: ptr.To("crn")}, nil, nil) + mockTransitGateway.EXPECT().CreateTransitGatewayConnection(gomock.Any()).Return(&tgapiv1.TransitGatewayConnectionCust{ID: ptr.To("pvs-connID")}, nil, nil) + mockTransitGateway.EXPECT().CreateTransitGatewayConnection(gomock.Any()).Return(&tgapiv1.TransitGatewayConnectionCust{ID: ptr.To("vpc-connID")}, nil, nil) + err := clusterScope.createTransitGateway(ctx) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.ID).To(BeEquivalentTo("transitGatewayID")) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.ControllerCreated).To(BeTrue()) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.PowerVSConnection.ID).To(BeEquivalentTo("pvs-connID")) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.PowerVSConnection.ControllerCreated).To(BeTrue()) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.VPCConnection.ID).To(BeEquivalentTo("vpc-connID")) + g.Expect(*clusterScope.IBMPowerVSCluster.Status.TransitGateway.VPCConnection.ControllerCreated).To(BeTrue()) + g.Expect(err).To(BeNil()) + }) + + t.Run("When TransitGateway is created but tagging TransitGateway fails", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + + clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, + TransitGatewayClient: mockTransitGateway, + IBMVPCClient: mockVPC, + ResourceClient: mockResourceController, + GlobalTaggingClient: mockgt, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + TransitGateway: &infrav1beta2.TransitGateway{ + GlobalRouting: ptr.To(true), + }, + ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ID: ptr.To("resourceGroupID")}, + Zone: ptr.To("zone-ID"), + VPC: &infrav1beta2.VPCResourceReference{Region: ptr.To("region")}, + }, + Status: infrav1beta2.IBMPowerVSClusterStatus{ + ServiceInstance: &infrav1beta2.ResourceReference{ + ID: ptr.To("serviceInstanceID"), + }, + VPC: &infrav1beta2.ResourceReference{ + ID: ptr.To("vpcID"), + }, + }, + }, + } + + mockTransitGateway.EXPECT().CreateTransitGateway(gomock.Any()).Return(&tgapiv1.TransitGateway{ID: ptr.To("transitGatewayID"), Name: ptr.To("transitGatewayName"), Status: ptr.To(string(infrav1beta2.TransitGatewayStateAvailable))}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, fmt.Errorf("failed to tag transitGateway")) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{CRN: ptr.To("crn")}, nil, nil) mockResourceController.EXPECT().GetResourceInstance(gomock.Any()).Return(&resourcecontrollerv2.ResourceInstance{CRN: ptr.To("crn")}, nil, nil) mockTransitGateway.EXPECT().CreateTransitGatewayConnection(gomock.Any()).Return(&tgapiv1.TransitGatewayConnectionCust{ID: ptr.To("pvs-connID")}, nil, nil) @@ -7217,6 +7459,7 @@ func makePowerVSClusterScope(mockTransitGateway *tgmock.MockTransitGateway, mock func TestReconcileVPCSecurityGroups(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) securityGroupID := "securityGroupID" @@ -7225,6 +7468,7 @@ func TestReconcileVPCSecurityGroups(t *testing.T) { setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -7276,7 +7520,9 @@ func TestReconcileVPCSecurityGroups(t *testing.T) { setup(t) t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ VPCSecurityGroups: append([]infrav1beta2.VPCSecurityGroup{}, infrav1beta2.VPCSecurityGroup{ @@ -7287,7 +7533,8 @@ func TestReconcileVPCSecurityGroups(t *testing.T) { } mockVPC.EXPECT().GetSecurityGroupByName(gomock.Any()).Return(nil, nil) - mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID")}, nil, nil) + mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID"), CRN: ptr.To("sg-crn")}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) err := clusterScope.ReconcileVPCSecurityGroups(ctx) g.Expect(err).To(BeNil()) }) @@ -7406,7 +7653,9 @@ func TestReconcileVPCSecurityGroups(t *testing.T) { } clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Status: infrav1beta2.IBMPowerVSClusterStatus{ VPC: &infrav1beta2.ResourceReference{ @@ -7424,6 +7673,7 @@ func TestReconcileVPCSecurityGroups(t *testing.T) { mockVPC.EXPECT().GetSecurityGroupByName(gomock.Any()).Return(nil, nil) mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID")}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) mockVPC.EXPECT().CreateSecurityGroupRule(gomock.Any()).Return(nil, nil, errors.New("failed to create security group rule")) err := clusterScope.ReconcileVPCSecurityGroups(ctx) g.Expect(err).ToNot(BeNil()) @@ -9501,11 +9751,13 @@ func TestCreateVPCSecurityGroupRulesAndSetStatus(t *testing.T) { func TestCreateVPCSecurityGroup(t *testing.T) { var ( mockVPC *mock.MockVpc + mockgt *gtmock.MockGlobalTagging mockCtrl *gomock.Controller ) setup := func(t *testing.T) { t.Helper() mockCtrl = gomock.NewController(t) + mockgt = gtmock.NewMockGlobalTagging(mockCtrl) mockVPC = mock.NewMockVpc(mockCtrl) } teardown := func() { @@ -9516,7 +9768,9 @@ func TestCreateVPCSecurityGroup(t *testing.T) { setup(t) t.Cleanup(teardown) clusterScope := PowerVSClusterScope{ - IBMVPCClient: mockVPC, + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ VPCSecurityGroups: append([]infrav1beta2.VPCSecurityGroup{}, infrav1beta2.VPCSecurityGroup{ @@ -9529,7 +9783,35 @@ func TestCreateVPCSecurityGroup(t *testing.T) { }, } - mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID")}, nil, nil) + mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID"), CRN: ptr.To("sg-crn")}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil) + sg, err := clusterScope.createVPCSecurityGroup(ctx, clusterScope.IBMPowerVSCluster.Spec.VPCSecurityGroups[0]) + g.Expect(*sg).To(BeEquivalentTo("securityGroupID")) + g.Expect(err).To(BeNil()) + }) + + t.Run("When SecurityGroup is created successfully but tagging SecurityGroup fails", func(t *testing.T) { + g := NewWithT(t) + setup(t) + t.Cleanup(teardown) + clusterScope := PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, + IBMVPCClient: mockVPC, + GlobalTaggingClient: mockgt, + IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ + Spec: infrav1beta2.IBMPowerVSClusterSpec{ + VPCSecurityGroups: append([]infrav1beta2.VPCSecurityGroup{}, infrav1beta2.VPCSecurityGroup{ + Name: ptr.To("securityGroupName"), + }), + ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ + ID: ptr.To("resourceGroupID"), + }, + }, + }, + } + + mockVPC.EXPECT().CreateSecurityGroup(gomock.Any()).Return(&vpcv1.SecurityGroup{ID: ptr.To("securityGroupID"), CRN: ptr.To("sg-crn")}, nil, nil) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, fmt.Errorf("failed to tag SecurityGroup")) sg, err := clusterScope.createVPCSecurityGroup(ctx, clusterScope.IBMPowerVSCluster.Spec.VPCSecurityGroups[0]) g.Expect(*sg).To(BeEquivalentTo("securityGroupID")) g.Expect(err).To(BeNil()) diff --git a/controllers/ibmpowervscluster_controller_test.go b/controllers/ibmpowervscluster_controller_test.go index 80a2f2b05..3f5076571 100644 --- a/controllers/ibmpowervscluster_controller_test.go +++ b/controllers/ibmpowervscluster_controller_test.go @@ -49,6 +49,7 @@ import ( infrav1beta2 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" "sigs.k8s.io/cluster-api-provider-ibmcloud/cloud/scope" + gtmock "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/globaltagging/mock" "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/powervs" powervsmock "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/powervs/mock" resourceclientmock "sigs.k8s.io/cluster-api-provider-ibmcloud/pkg/cloud/services/resourcecontroller/mock" @@ -1415,6 +1416,7 @@ func TestReconcileVPCResources(t *testing.T) { name: "when Reconciling VPC subnets returns requeue as true", powerVSClusterScopeFunc: func() *scope.PowerVSClusterScope { clusterScope := &scope.PowerVSClusterScope{ + Cluster: &capiv1beta1.Cluster{}, IBMPowerVSCluster: &infrav1beta2.IBMPowerVSCluster{ Spec: infrav1beta2.IBMPowerVSClusterSpec{ ResourceGroup: &infrav1beta2.IBMPowerVSResourceReference{ @@ -1433,10 +1435,13 @@ func TestReconcileVPCResources(t *testing.T) { } vpcZones, _ := regionUtil.VPCZonesForVPCRegion("us-south") mockVPC := vpcmock.NewMockVpc(gomock.NewController(t)) + mockgt := gtmock.NewMockGlobalTagging(gomock.NewController(t)) mockVPC.EXPECT().GetVPC(gomock.Any()).Return(&vpcv1.VPC{Status: ptr.To("active")}, nil, nil) mockVPC.EXPECT().GetVPCSubnetByName(gomock.Any()).Return(nil, nil).Times(len(vpcZones)) - mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(&vpcv1.Subnet{Status: ptr.To("active")}, nil, nil).Times(len(vpcZones)) + mockVPC.EXPECT().CreateSubnet(gomock.Any()).Return(&vpcv1.Subnet{Status: ptr.To("active"), CRN: ptr.To("subnet-crn")}, nil, nil).Times(len(vpcZones)) + mockgt.EXPECT().AttachTag(gomock.Any()).Return(nil, nil, nil).AnyTimes() clusterScope.IBMVPCClient = mockVPC + clusterScope.GlobalTaggingClient = mockgt return clusterScope }, reconcileResult: reconcileResult{