diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 6649d3fd9..752c8540b 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -1307,6 +1307,7 @@ func autoConvert_v1beta2_IBMVPCClusterStatus_To_v1beta1_IBMVPCClusterStatus(in * } out.ControlPlaneLoadBalancerState = VPCLoadBalancerState(in.ControlPlaneLoadBalancerState) out.Conditions = *(*corev1beta1.Conditions)(unsafe.Pointer(&in.Conditions)) + // WARNING: in.V1Beta2 requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1beta2/conditions_consts.go b/api/v1beta2/conditions_consts.go index e1388393e..964daa0a2 100644 --- a/api/v1beta2/conditions_consts.go +++ b/api/v1beta2/conditions_consts.go @@ -198,6 +198,23 @@ const ( IBMPowerVSClusterReadyUnknownV1Beta2Reason = clusterv1beta1.ReadyUnknownV1Beta2Reason ) +// IBMVPCCluster's Ready condition and corresponding reasons that will be used in v1Beta2 API version. +const ( + // IBMVPCClusterReadyV1Beta2Condition is true if the IBMVPCCluster's deletionTimestamp is not set and IBMVPCCluster's + // conditions are true. + IBMVPCClusterReadyV1Beta2Condition = clusterv1beta1.ReadyV1Beta2Condition + + // IBMVPCClusterReadyV1Beta2Reason surfaces when the IBMVPCCluster readiness criteria is met. + IBMVPCClusterReadyV1Beta2Reason = clusterv1beta1.ReadyV1Beta2Reason + + // IBMVPCClusterNotReadyV1Beta2Reason surfaces when the IBMVPCCluster readiness criteria is not met. + IBMVPCClusterNotReadyV1Beta2Reason = clusterv1beta1.NotReadyV1Beta2Reason + + // IBMVPCClusterReadyUnknownV1Beta2Reason surfaces when at least one of the IBMVPCCluster readiness criteria is unknown + // and none of the IBMVPCCluster readiness criteria is met. + IBMVPCClusterReadyUnknownV1Beta2Reason = clusterv1beta1.ReadyUnknownV1Beta2Reason +) + const ( // WorkspaceReadyV1Beta2Condition reports on the successful reconciliation of a PowerVS workspace. WorkspaceReadyV1Beta2Condition = "WorkspaceReady" @@ -283,6 +300,15 @@ const ( // VPCLoadBalancerDeletingV1Beta2Reason surfaces when the VPC LoadBalancer is being deleted. VPCLoadBalancerDeletingV1Beta2Reason = clusterv1beta1.DeletingV1Beta2Reason + // VPCImageReadyV1Beta2Condition reports on the successful reconciliation of a VPC custom image. + VPCImageReadyV1Beta2Condition = "VPCImageReady" + + // VPCImageReadyV1Beta2Reason surfaces when the VPC custom image is ready. + VPCImageReadyV1Beta2Reason = clusterv1beta1.ReadyV1Beta2Reason + + // VPCImageNotReadyV1Beta2Reason surfaces when the VPC custom image is not ready. + VPCImageNotReadyV1Beta2Reason = clusterv1beta1.NotReadyV1Beta2Reason + // COSInstanceReadyV1Beta2Condition reports on the successful reconciliation of a COS instance. COSInstanceReadyV1Beta2Condition = "COSInstanceReady" diff --git a/api/v1beta2/ibmvpccluster_types.go b/api/v1beta2/ibmvpccluster_types.go index 33c23ccdc..848f7f570 100644 --- a/api/v1beta2/ibmvpccluster_types.go +++ b/api/v1beta2/ibmvpccluster_types.go @@ -320,6 +320,21 @@ type IBMVPCClusterStatus struct { // Conditions defines current service state of the load balancer. // +optional Conditions clusterv1beta1.Conditions `json:"conditions,omitempty"` + + // V1beta2 groups all the fields that will be added or modified in IBMVPCCluster's status with the V1Beta2 version. + // +optional + V1Beta2 *IBMVPCClusterV1Beta2Status `json:"v1beta2,omitempty"` +} + +// IBMVPCClusterV1Beta2Status groups all the fields that will be added or modified in IBMVPCClusterStatus with the V1Beta2 version. +// See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context. +type IBMVPCClusterV1Beta2Status struct { + // Conditions represents the observations of a IBMVPCCluster's current state. + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=32 + Conditions []metav1.Condition `json:"conditions,omitempty"` } // VPCNetworkStatus provides details on the status of VPC network resources for extended VPC Infrastructure support. @@ -400,6 +415,22 @@ func (r *IBMVPCCluster) SetConditions(conditions clusterv1beta1.Conditions) { r.Status.Conditions = conditions } +// GetV1Beta2Conditions returns the set of conditions for IBMVPCCluster object. +func (r *IBMVPCCluster) GetV1Beta2Conditions() []metav1.Condition { + if r.Status.V1Beta2 == nil { + return nil + } + return r.Status.V1Beta2.Conditions +} + +// SetV1Beta2Conditions sets conditions for IBMVPCCluster object. +func (r *IBMVPCCluster) SetV1Beta2Conditions(conditions []metav1.Condition) { + if r.Status.V1Beta2 == nil { + r.Status.V1Beta2 = &IBMVPCClusterV1Beta2Status{} + } + r.Status.V1Beta2.Conditions = conditions +} + func init() { objectTypes = append(objectTypes, &IBMVPCCluster{}, &IBMVPCClusterList{}) } diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 1454db67b..2bbd43f5e 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -1038,6 +1038,11 @@ func (in *IBMVPCClusterStatus) DeepCopyInto(out *IBMVPCClusterStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.V1Beta2 != nil { + in, out := &in.V1Beta2, &out.V1Beta2 + *out = new(IBMVPCClusterV1Beta2Status) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IBMVPCClusterStatus. @@ -1141,6 +1146,28 @@ func (in *IBMVPCClusterTemplateSpec) DeepCopy() *IBMVPCClusterTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IBMVPCClusterV1Beta2Status) DeepCopyInto(out *IBMVPCClusterV1Beta2Status) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IBMVPCClusterV1Beta2Status. +func (in *IBMVPCClusterV1Beta2Status) DeepCopy() *IBMVPCClusterV1Beta2Status { + if in == nil { + return nil + } + out := new(IBMVPCClusterV1Beta2Status) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IBMVPCMachine) DeepCopyInto(out *IBMVPCMachine) { *out = *in diff --git a/cloud/scope/cluster.go b/cloud/scope/cluster.go index b477fd984..70d10a6bf 100644 --- a/cloud/scope/cluster.go +++ b/cloud/scope/cluster.go @@ -28,6 +28,7 @@ import ( "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2" @@ -334,7 +335,8 @@ func (s *ClusterScope) ensureSubnetUnique(subnetName string) (*vpcv1.Subnet, err } // DeleteSubnet deletes a subnet associated with subnet id. -func (s *ClusterScope) DeleteSubnet() error { +func (s *ClusterScope) DeleteSubnet(ctx context.Context) error { + log := ctrl.LoggerFrom(ctx) if s.IBMVPCCluster.Status.Subnet.ID == nil { return nil } @@ -377,7 +379,7 @@ func (s *ClusterScope) DeleteSubnet() error { } if !found { - s.Logger.V(3).Info("No subnets found with ID", "Subnet ID", subnetID) + log.V(3).Info("No subnets found with ID", "subnetID", subnetID) return nil } diff --git a/cloud/scope/cluster_test.go b/cloud/scope/cluster_test.go index f6b4544a7..70f34cb62 100644 --- a/cloud/scope/cluster_test.go +++ b/cloud/scope/cluster_test.go @@ -468,7 +468,7 @@ func TestDeleteSubnet(t *testing.T) { mockvpc.EXPECT().UnsetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.UnsetSubnetPublicGatewayOptions{})).Return(&core.DetailedResponse{}, nil) mockvpc.EXPECT().DeletePublicGateway(gomock.AssignableToTypeOf(&vpcv1.DeletePublicGatewayOptions{})).Return(&core.DetailedResponse{}, nil) mockvpc.EXPECT().DeleteSubnet(gomock.AssignableToTypeOf(&vpcv1.DeleteSubnetOptions{})).Return(&core.DetailedResponse{}, nil) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(BeNil()) }) @@ -482,7 +482,7 @@ func TestDeleteSubnet(t *testing.T) { mockvpc.EXPECT().ListSubnets(gomock.AssignableToTypeOf(&vpcv1.ListSubnetsOptions{})).Return(subnet, &core.DetailedResponse{}, nil) mockvpc.EXPECT().GetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.GetSubnetPublicGatewayOptions{})).Return(publicGateway, &core.DetailedResponse{}, nil) mockvpc.EXPECT().UnsetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.UnsetSubnetPublicGatewayOptions{})).Return(&core.DetailedResponse{}, errors.New("Error when unsetting publicgateway for subnet")) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(Not(BeNil())) }) @@ -497,7 +497,7 @@ func TestDeleteSubnet(t *testing.T) { mockvpc.EXPECT().GetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.GetSubnetPublicGatewayOptions{})).Return(publicGateway, &core.DetailedResponse{}, nil) mockvpc.EXPECT().UnsetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.UnsetSubnetPublicGatewayOptions{})).Return(&core.DetailedResponse{}, nil) mockvpc.EXPECT().DeletePublicGateway(gomock.AssignableToTypeOf(&vpcv1.DeletePublicGatewayOptions{})).Return(&core.DetailedResponse{}, errors.New("Error when deleting publicgateway for subnet")) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(Not(BeNil())) }) @@ -513,7 +513,7 @@ func TestDeleteSubnet(t *testing.T) { mockvpc.EXPECT().UnsetSubnetPublicGateway(gomock.AssignableToTypeOf(&vpcv1.UnsetSubnetPublicGatewayOptions{})).Return(&core.DetailedResponse{}, nil) mockvpc.EXPECT().DeletePublicGateway(gomock.AssignableToTypeOf(&vpcv1.DeletePublicGatewayOptions{})).Return(&core.DetailedResponse{}, nil) mockvpc.EXPECT().DeleteSubnet(gomock.AssignableToTypeOf(&vpcv1.DeleteSubnetOptions{})).Return(&core.DetailedResponse{}, errors.New("Error when deleting subnet")) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(Not(BeNil())) }) @@ -525,7 +525,7 @@ func TestDeleteSubnet(t *testing.T) { scope.IBMVPCCluster.Spec = vpcCluster.Spec scope.IBMVPCCluster.Status = vpcCluster.Status mockvpc.EXPECT().ListSubnets(gomock.AssignableToTypeOf(&vpcv1.ListSubnetsOptions{})).Return(nil, &core.DetailedResponse{}, errors.New("Error listing subnets")) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(Not(BeNil())) }) t.Run("Subnet doesn't exist", func(t *testing.T) { @@ -536,7 +536,7 @@ func TestDeleteSubnet(t *testing.T) { scope.IBMVPCCluster.Spec = vpcCluster.Spec scope.IBMVPCCluster.Status = vpcCluster.Status mockvpc.EXPECT().ListSubnets(gomock.AssignableToTypeOf(&vpcv1.ListSubnetsOptions{})).Return(&vpcv1.SubnetCollection{Subnets: []vpcv1.Subnet{}}, &core.DetailedResponse{}, nil) - err := scope.DeleteSubnet() + err := scope.DeleteSubnet(ctx) g.Expect(err).To(BeNil()) }) }) diff --git a/cloud/scope/vpc_cluster.go b/cloud/scope/vpc_cluster.go index 26699fb1e..2d8b3eaa8 100644 --- a/cloud/scope/vpc_cluster.go +++ b/cloud/scope/vpc_cluster.go @@ -34,6 +34,7 @@ import ( "k8s.io/klog/v2/textlogger" "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2" @@ -638,14 +639,15 @@ func (s *VPCClusterScope) TagResource(tagName string, resourceCRN string) error } // ReconcileVPC reconciles the cluster's VPC. -func (s *VPCClusterScope) ReconcileVPC() (bool, error) { +func (s *VPCClusterScope) ReconcileVPC(ctx context.Context) (bool, error) { + log := ctrl.LoggerFrom(ctx) // If VPC id is set, that indicates the VPC already exists. vpcID, err := s.GetVPCID() if err != nil { return false, fmt.Errorf("failed to retrieve vpc id: %w", err) } if vpcID != nil { - s.V(3).Info("VPC id is set", "id", vpcID) + log.V(3).Info("VPC id is set", "id", vpcID) vpcDetails, _, err := s.VPCClient.GetVPC(&vpcv1.GetVPCOptions{ ID: vpcID, }) @@ -654,7 +656,7 @@ func (s *VPCClusterScope) ReconcileVPC() (bool, error) { } else if vpcDetails == nil { return false, fmt.Errorf("failed to retrieve vpc with id: %s", *vpcID) } - s.V(3).Info("Found VPC with provided id", "id", vpcID) + log.V(3).Info("Found VPC with provided id", "id", vpcID) requeue := true if vpcDetails.Status != nil && *vpcDetails.Status == string(vpcv1.VPCStatusAvailableConst) { @@ -672,13 +674,13 @@ func (s *VPCClusterScope) ReconcileVPC() (bool, error) { } // If no VPC id was found, we need to create a new VPC. - s.V(3).Info("Creating a VPC") + log.V(3).Info("Creating a VPC") err = s.createVPC() if err != nil { return false, fmt.Errorf("failed to create vpc: %w", err) } - s.V(3).Info("Successfully created VPC") + log.V(3).Info("Successfully created VPC") return true, nil } @@ -726,7 +728,8 @@ func (s *VPCClusterScope) createVPC() error { } // ReconcileVPCCustomImage reconciles the VPC Custom Image. -func (s *VPCClusterScope) ReconcileVPCCustomImage() (bool, error) { +func (s *VPCClusterScope) ReconcileVPCCustomImage(ctx context.Context) (bool, error) { + log := ctrl.LoggerFrom(ctx) // VPC Custom Image reconciliation is based on the following possibilities. // 1. Check Status for ID or Name, from previous lookup in reconciliation loop. // 2. If no Image spec is provided, assume the image is managed externally, thus no reconciliation required. @@ -741,7 +744,7 @@ func (s *VPCClusterScope) ReconcileVPCCustomImage() (bool, error) { } } else if s.IBMVPCCluster.Spec.Image == nil { // If no Image spec was defined, we expect it is maintained externally and continue without reconciling. For example, using a Catalog Offering Custom Image, which may be in another account, which means it cannot be looked up, but can be used when creating Instances. - s.V(3).Info("No VPC Custom Image defined, skipping reconciliation") + log.V(3).Info("No VPC Custom Image defined, skipping reconciliation") return false, nil } else if s.IBMVPCCluster.Spec.Image.Name != nil { // Attempt to retrieve the image details via the name, if it already exists @@ -787,7 +790,7 @@ func (s *VPCClusterScope) ReconcileVPCCustomImage() (bool, error) { if image == nil { return false, fmt.Errorf("error failed to retrieve vpc custom image with id %s", *imageID) } - s.V(3).Info("Found VPC Custom Image with provided id", "imageID", imageID) + log.V(3).Info("Found VPC Custom Image with provided id", "imageID", imageID) requeue := true if image.Status != nil && *image.Status == string(vpcv1.ImageStatusAvailableConst) { @@ -803,18 +806,18 @@ func (s *VPCClusterScope) ReconcileVPCCustomImage() (bool, error) { } // No VPC Custom Image exists or was found, so create the Custom Image. - s.V(3).Info("Creating a VPC Custom Image") - err := s.createCustomImage() + log.V(3).Info("Creating a VPC Custom Image") + err := s.createCustomImage(ctx) if err != nil { return false, fmt.Errorf("error failure trying to create vpc custom image: %w", err) } - s.V(3).Info("Successfully created VPC Custom Image") + log.V(3).Info("Successfully created VPC Custom Image") return true, nil } // createCustomImage will create a new VPC Custom Image. -func (s *VPCClusterScope) createCustomImage() error { +func (s *VPCClusterScope) createCustomImage(ctx context.Context) error { // TODO(cjschaef): Remove in favor of webhook validation. if s.IBMVPCCluster.Spec.Image.OperatingSystem == nil { return fmt.Errorf("error failed to create vpc custom image due to missing operatingSystem") @@ -843,7 +846,7 @@ func (s *VPCClusterScope) createCustomImage() error { } // Build the COS Object URL using the ImageSpec - fileHRef, err := s.buildCOSObjectHRef() + fileHRef, err := s.buildCOSObjectHRef(ctx) if err != nil { return fmt.Errorf("error building vpc custom image file href: %w", err) } @@ -887,7 +890,8 @@ func (s *VPCClusterScope) createCustomImage() error { } // buildCOSObjectHRef will build the HRef path to a COS Object that can be used for VPC Custom Image creation. -func (s *VPCClusterScope) buildCOSObjectHRef() (*string, error) { +func (s *VPCClusterScope) buildCOSObjectHRef(ctx context.Context) (*string, error) { + log := ctrl.LoggerFrom(ctx) // TODO(cjschaef): Remove in favor of webhook validation. // We need COS details in order to create the Custom Image from. if s.IBMVPCCluster.Spec.Image.COSInstance == nil || s.IBMVPCCluster.Spec.Image.COSBucket == nil || s.IBMVPCCluster.Spec.Image.COSObject == nil { @@ -903,13 +907,13 @@ func (s *VPCClusterScope) buildCOSObjectHRef() (*string, error) { // Expected HRef format: // cos://// href := fmt.Sprintf("cos://%s/%s/%s", bucketRegion, *s.IBMVPCCluster.Spec.Image.COSBucket, *s.IBMVPCCluster.Spec.Image.COSObject) - s.V(3).Info("building image ref", "href", href) + log.V(3).Info("building image ref", "href", href) return ptr.To(href), nil } // ReconcileSubnets reconciles the VPC Subnet(s). // For Subnets, we collect all of the required subnets, for each Plane, and reconcile them individually. Requeing if one is missing or just created. Reconciliation is attempted on all subnets each loop, to prevent single subnet creation per reconciliation loop. -func (s *VPCClusterScope) ReconcileSubnets() (bool, error) { +func (s *VPCClusterScope) ReconcileSubnets(ctx context.Context) (bool, error) { var subnets []infrav1.Subnet var err error // If no ControlPlane Subnets were supplied, we default to create one in each availability zone of the region. @@ -925,7 +929,7 @@ func (s *VPCClusterScope) ReconcileSubnets() (bool, error) { // Reconcile Control Plane subnets. requeue := false for _, subnet := range subnets { - if requiresRequeue, err := s.reconcileSubnet(subnet, true); err != nil { + if requiresRequeue, err := s.reconcileSubnet(ctx, subnet, true); err != nil { return false, fmt.Errorf("error failed reconciling control plane subnet: %w", err) } else if requiresRequeue { // If the reconcile of the subnet requires further reconciliation, plan to requeue entire ReconcileSubnets call, but attempt to further reconcile additional Subnets (attempt all subnet reconciliation). @@ -949,7 +953,7 @@ func (s *VPCClusterScope) ReconcileSubnets() (bool, error) { // Reconcile Worker subnets. for _, subnet := range subnets { - if requiresRequeue, err := s.reconcileSubnet(subnet, false); err != nil { + if requiresRequeue, err := s.reconcileSubnet(ctx, subnet, false); err != nil { return false, fmt.Errorf("error failed reconciling worker subnet: %w", err) } else if requiresRequeue { // If the reconcile of the subnet requires further reconciliation, plan to requeue entire ReconcileSubnets call, but attempt to further reconcile additional Subnets (attempt all subnet reconciliation). @@ -963,7 +967,8 @@ func (s *VPCClusterScope) ReconcileSubnets() (bool, error) { // reconcileSubnet will attempt to find the existing subnet, or create it if necessary. // The logic can handle either Control Plane or Worker subnets, but must distinguish between them for Status updates. -func (s *VPCClusterScope) reconcileSubnet(subnet infrav1.Subnet, isControlPlane bool) (bool, error) { //nolint: gocyclo +func (s *VPCClusterScope) reconcileSubnet(ctx context.Context, subnet infrav1.Subnet, isControlPlane bool) (bool, error) { //nolint: gocyclo + log := ctrl.LoggerFrom(ctx) // If no ID or name was provided, that is an error to be raised. One or the other must be specified when subnets are supplied. if subnet.ID == nil && subnet.Name == nil { return false, fmt.Errorf("error subnet has no defined id or name, one is required") @@ -1040,12 +1045,12 @@ func (s *VPCClusterScope) reconcileSubnet(subnet infrav1.Subnet, isControlPlane } // If the subnet has not yet been at this point, assume it needs to be created. - s.V(3).Info("creating subnet", "subnetName", subnet.Name) - err := s.createSubnet(subnet, isControlPlane) + log.V(3).Info("creating subnet", "subnetName", subnet.Name) + err := s.createSubnet(ctx, subnet, isControlPlane) if err != nil { return false, err } - s.V(3).Info("Successfully created subnet", "subnetName", subnet.Name) + log.V(3).Info("Successfully created subnet", "subnetName", subnet.Name) // Recommend we requeue reconciliation after subnet was successfully created return true, nil @@ -1093,7 +1098,7 @@ func (s *VPCClusterScope) updateSubnetStatus(subnetDetails *vpcv1.Subnet, isCont } // createSubnet creates a new VPC subnet. -func (s *VPCClusterScope) createSubnet(subnet infrav1.Subnet, isControlPlane bool) error { +func (s *VPCClusterScope) createSubnet(ctx context.Context, subnet infrav1.Subnet, isControlPlane bool) error { // TODO(cjschaef): Move to webhook validation. if subnet.Zone == nil { return fmt.Errorf("error subnet zone must be defined for subnet %s", *subnet.Name) @@ -1120,7 +1125,7 @@ func (s *VPCClusterScope) createSubnet(subnet infrav1.Subnet, isControlPlane boo // Find or create a Public Gateway in this zone for the subnet, only one Public Gateway is required for each zone, for this cluster. // NOTE(cjschaef): We may need to add support to not attach Public Gateways to subnets. - publicGateway, err := s.findOrCreatePublicGateway(*subnet.Zone) + publicGateway, err := s.findOrCreatePublicGateway(ctx, *subnet.Zone) if err != nil { return fmt.Errorf("error failed to find or create public gateway for subnet %s: %w", *subnet.Name, err) } @@ -1175,7 +1180,8 @@ func (s *VPCClusterScope) createSubnet(subnet infrav1.Subnet, isControlPlane boo } // findOrCreatePublicGateway will attempt to find if there is an existing Public Gateway for a specific zone, for the cluster (in cluster's Resource Group and VPC), or create a new one. Only one Public Gateway is required in each zone, for any subnets in that zone. -func (s *VPCClusterScope) findOrCreatePublicGateway(zone string) (*vpcv1.PublicGateway, error) { +func (s *VPCClusterScope) findOrCreatePublicGateway(ctx context.Context, zone string) (*vpcv1.PublicGateway, error) { + log := ctrl.LoggerFrom(ctx) publicGatewayName := fmt.Sprintf("%s-%s", *s.GetServiceName(infrav1.ResourceTypePublicGateway), zone) // We will use the cluster Resource Group ID, as we expect to create all resources (Public Gateways and Subnets) in that Resource Group. resourceGroupID, err := s.GetResourceGroupID() @@ -1221,7 +1227,7 @@ func (s *VPCClusterScope) findOrCreatePublicGateway(zone string) (*vpcv1.PublicG return nil, fmt.Errorf("error failed creating public gateway for zone %s", zone) } - s.V(3).Info("created public gateway", "id", publicGatewayDetails.ID) + log.V(3).Info("created public gateway", "id", publicGatewayDetails.ID) // Add a tag to the public gateway for the cluster err = s.TagResource(s.IBMVPCCluster.Name, *publicGatewayDetails.CRN) @@ -1233,7 +1239,8 @@ func (s *VPCClusterScope) findOrCreatePublicGateway(zone string) (*vpcv1.PublicG } // ReconcileSecurityGroups will attempt to reconcile the defined SecurityGroups and their SecurityGroupRules. Our best option is to perform a first set of passes, creating all the SecurityGroups first, then reconcile the SecurityGroupRules after that, as the SecuirtyGroupRules could be dependent on an IBM Cloud Security Group that must be created first. -func (s *VPCClusterScope) ReconcileSecurityGroups() (bool, error) { +func (s *VPCClusterScope) ReconcileSecurityGroups(ctx context.Context) (bool, error) { + log := ctrl.LoggerFrom(ctx) // If no Security Groups were supplied, we have nothing to do. if len(s.IBMVPCCluster.Spec.Network.SecurityGroups) == 0 { return false, nil @@ -1241,7 +1248,7 @@ func (s *VPCClusterScope) ReconcileSecurityGroups() (bool, error) { // Reconcile each Security Group first, process rules later. for _, securityGroup := range s.IBMVPCCluster.Spec.Network.SecurityGroups { - if err := s.reconcileSecurityGroup(securityGroup); err != nil { + if err := s.reconcileSecurityGroup(ctx, securityGroup); err != nil { return false, fmt.Errorf("error failed reonciling security groups: %w", err) } } @@ -1249,10 +1256,10 @@ func (s *VPCClusterScope) ReconcileSecurityGroups() (bool, error) { // Reconcile each Security Groups's Rules. requeue := false for _, securityGroup := range s.IBMVPCCluster.Spec.Network.SecurityGroups { - if requiresRequeue, err := s.reconcileSecurityGroupRules(securityGroup); err != nil { + if requiresRequeue, err := s.reconcileSecurityGroupRules(ctx, securityGroup); err != nil { return false, fmt.Errorf("error failed reconciling security group rules: %w", err) } else if requiresRequeue { - s.V(3).Info("requeuing for security group rules") + log.V(3).Info("requeuing for security group rules") requeue = true } } @@ -1261,7 +1268,8 @@ func (s *VPCClusterScope) ReconcileSecurityGroups() (bool, error) { } // reconcileSecurityGroup will attempt to reconcile a defined SecurityGroup. By design, we confirm the IBM Cloud Security Group exists first, before attempting to reconcile the defined SecurityGroupRules. -func (s *VPCClusterScope) reconcileSecurityGroup(securityGroup infrav1.VPCSecurityGroup) error { +func (s *VPCClusterScope) reconcileSecurityGroup(ctx context.Context, securityGroup infrav1.VPCSecurityGroup) error { + log := ctrl.LoggerFrom(ctx) var securityGroupID *string // If Security Group already has an ID defined, use that for lookup. if securityGroup.ID != nil { @@ -1295,7 +1303,7 @@ func (s *VPCClusterScope) reconcileSecurityGroup(securityGroup infrav1.VPCSecuri // If we have an ID for the SecurityGroup, we can check the status. if securityGroupID != nil { - s.V(3).Info("checking security group status", "securityGroupName", securityGroup.Name, "securityGroupID", securityGroupID) + log.V(3).Info("checking security group status", "securityGroupName", securityGroup.Name, "securityGroupID", securityGroupID) securityGroupDetails, _, err := s.VPCClient.GetSecurityGroup(&vpcv1.GetSecurityGroupOptions{ ID: securityGroupID, }) @@ -1336,11 +1344,11 @@ func (s *VPCClusterScope) reconcileSecurityGroup(securityGroup infrav1.VPCSecuri } securityGroupDetails, _, err := s.VPCClient.CreateSecurityGroup(createOptions) if err != nil { - s.V(3).Error(err, "error creating security group", "securityGroupName", securityGroup.Name) + log.V(3).Error(err, "error creating security group", "securityGroupName", securityGroup.Name) return fmt.Errorf("error failed to create security group: %w", err) } if securityGroupDetails == nil { - s.V(3).Info("error failed creating security group", "securityGroupName", securityGroup.Name) + log.V(3).Info("error failed creating security group", "securityGroupName", securityGroup.Name) return fmt.Errorf("error failed creating security group") } @@ -1362,7 +1370,8 @@ func (s *VPCClusterScope) reconcileSecurityGroup(securityGroup infrav1.VPCSecuri } // reconcile SecurityGroupRules will attempt to reconcile the set of defined SecurityGroupRules for a SecurityGroup, one Rule at a time. Each defined Rule can contain multiple remotes, requiring a unique IBM Cloud Security Group Rule, based on the expected traffic direction, inbound (Source) or outbound (Destination). -func (s *VPCClusterScope) reconcileSecurityGroupRules(securityGroup infrav1.VPCSecurityGroup) (bool, error) { +func (s *VPCClusterScope) reconcileSecurityGroupRules(ctx context.Context, securityGroup infrav1.VPCSecurityGroup) (bool, error) { + log := ctrl.LoggerFrom(ctx) // If the SecurityGroup has no rules, we have nothing more to do for this Security Group. if len(securityGroup.Rules) == 0 { return false, nil @@ -1378,14 +1387,14 @@ func (s *VPCClusterScope) reconcileSecurityGroupRules(securityGroup infrav1.VPCS } if securityGroupID == nil { - s.V(3).Info("security group not found, requeue", "securityGroup", securityGroup) + log.V(3).Info("security group not found, requeue", "securityGroup", securityGroup) return true, nil } // Reconcile each SecurityGroupRule in the SecurityGroup. for _, securityGroupRule := range securityGroup.Rules { - s.V(3).Info("reconcile security group rule", "securityGroupID", securityGroupID) - if err := s.reconcileSecurityGroupRule(*securityGroupID, *securityGroupRule); err != nil { + log.V(3).Info("reconcile security group rule", "securityGroupID", securityGroupID) + if err := s.reconcileSecurityGroupRule(ctx, *securityGroupID, *securityGroupRule); err != nil { return false, fmt.Errorf("error failed to reconcile security group rule: %w", err) } } @@ -1395,7 +1404,8 @@ func (s *VPCClusterScope) reconcileSecurityGroupRules(securityGroup infrav1.VPCS } // reconcileSecurityGroupRule will attempt to reconcile a defined SecurityGroupRule, with one or more Remotes, for a SecurityGroup. If the IBM Cloud Security Group contains no Rules, simply attempt to create the defined Rule (via the Remote(s) provided). -func (s *VPCClusterScope) reconcileSecurityGroupRule(securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule) error { +func (s *VPCClusterScope) reconcileSecurityGroupRule(ctx context.Context, securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule) error { + log := ctrl.LoggerFrom(ctx) existingSecurityGroupRuleIntfs, _, err := s.VPCClient.ListSecurityGroupRules(&vpcv1.ListSecurityGroupRulesOptions{ SecurityGroupID: ptr.To(securityGroupID), }) @@ -1405,26 +1415,27 @@ func (s *VPCClusterScope) reconcileSecurityGroupRule(securityGroupID string, sec // If the Security Group has no Rules at all, we simply create all the Rules if existingSecurityGroupRuleIntfs == nil || len(existingSecurityGroupRuleIntfs.Rules) == 0 { - s.V(3).Info("Creating security group rules for security group", "securityGroupID", securityGroupID) - err := s.createSecurityGroupRuleAllRemotes(securityGroupID, securityGroupRule) + log.V(3).Info("Creating security group rules for security group", "securityGroupID", securityGroupID) + err := s.createSecurityGroupRuleAllRemotes(ctx, securityGroupID, securityGroupRule) if err != nil { return fmt.Errorf("error failed creating all security group rule remotes: %w", err) } - s.V(3).Info("Created security group rules", "securityGroupID", securityGroupID, "securityGroupRule", securityGroupRule) + log.V(3).Info("Created security group rules", "securityGroupID", securityGroupID, "securityGroupRule", securityGroupRule) // Security Group Rules do not have a Status, so assume they are ready immediately. return nil } // Validate the Security Group Rule(s) exist or were created. - if err := s.findOrCreateSecurityGroupRule(securityGroupID, securityGroupRule, existingSecurityGroupRuleIntfs); err != nil { + if err := s.findOrCreateSecurityGroupRule(ctx, securityGroupID, securityGroupRule, existingSecurityGroupRuleIntfs); err != nil { return fmt.Errorf("error failed to find or create security group rule: %w", err) } return nil } // findOrCreateSecurityGroupRule will attempt to match up the SecurityGroupRule's Remote(s) (multiple Remotes can be supplied per Rule definition), and will create any missing IBM Cloud Security Group Rules based on the SecurityGroupRule and Remote(s). Remotes are defined either by a Destination (outbound) or a Source (inbound), which defines the type of IBM Cloud Security Group Rule that should exist or be created. -func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule, existingSecurityGroupRules *vpcv1.SecurityGroupRuleCollection) error { //nolint: gocyclo +func (s *VPCClusterScope) findOrCreateSecurityGroupRule(ctx context.Context, securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule, existingSecurityGroupRules *vpcv1.SecurityGroupRuleCollection) error { //nolint: gocyclo + log := ctrl.LoggerFrom(ctx) // Use either the SecurityGroupRule.Destination or SecurityGroupRule.Source for further details based on SecurityGroupRule.Direction var securityGroupRulePrototype infrav1.VPCSecurityGroupRulePrototype switch securityGroupRule.Direction { @@ -1436,7 +1447,7 @@ func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, return fmt.Errorf("error unsupported SecurityGroupRuleDirection defined") } - s.V(3).Info("checking security group rules for security group", "securityGroupID", securityGroupID) + log.V(3).Info("checking security group rules for security group", "securityGroupID", securityGroupID) // Each defined SecurityGroupRule can have multiple Remotes specified, each signifying a separate Security Group Rule (with the same Action, Direction, etc.) for _, remote := range securityGroupRulePrototype.Remotes { @@ -1454,12 +1465,12 @@ func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, if securityGroupRule.Direction != infrav1.VPCSecurityGroupRuleDirection(*existingRule.Direction) { continue } - if found, err := s.checkSecurityGroupRuleProtocolAll(securityGroupRulePrototype, remote, existingRule); err != nil { + if found, err := s.checkSecurityGroupRuleProtocolAll(ctx, securityGroupRulePrototype, remote, existingRule); err != nil { return fmt.Errorf("error failure checking security group rule protocol all: %w", err) } else if found { // If we found the matching IBM Cloud Security Group Rule for the defined SecurityGroupRule and Remote, we can stop checking IBM Cloud Security Group Rules for this remote and move onto the next remote. // The expectation is that only one IBM Cloud Security Group Rule will match, but if at least one matches the defined SecurityGroupRule, that is sufficient. - s.V(3).Info("security group rule all protocol match found") + log.V(3).Info("security group rule all protocol match found") remoteMatch = true break } @@ -1473,11 +1484,11 @@ func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, if securityGroupRule.Direction != infrav1.VPCSecurityGroupRuleDirection(*existingRule.Direction) { continue } - if found, err := s.checkSecurityGroupRuleProtocolIcmp(securityGroupRulePrototype, remote, existingRule); err != nil { + if found, err := s.checkSecurityGroupRuleProtocolIcmp(ctx, securityGroupRulePrototype, remote, existingRule); err != nil { return fmt.Errorf("error failure checking security group rule protocol icmp: %w", err) } else if found { // If we found the matching IBM Cloud Security Group Rule for the defined SecurityGroupRule and Remote, we can stop checking IBM Cloud Security Group Rules for this remote and move onto the next remote. - s.V(3).Info("security group rule icmp match found") + log.V(3).Info("security group rule icmp match found") remoteMatch = true break } @@ -1491,23 +1502,23 @@ func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, if securityGroupRule.Direction != infrav1.VPCSecurityGroupRuleDirection(*existingRule.Direction) { continue } - if found, err := s.checkSecurityGroupRuleProtocolTcpudp(securityGroupRulePrototype, remote, existingRule); err != nil { + if found, err := s.checkSecurityGroupRuleProtocolTcpudp(ctx, securityGroupRulePrototype, remote, existingRule); err != nil { return fmt.Errorf("error failure checking security group rule protocol tcp-udp: %w", err) } else if found { // If we found the matching IBM Cloud Security Group Rule for the defined SecurityGroupRule and Remote, we can stop checking IBM Cloud Security Group Rules for this remote and move onto the next remote. - s.V(3).Info("security group rule tcp/udp match found") + log.V(3).Info("security group rule tcp/udp match found") remoteMatch = true break } default: // This is an unexpected IBM Cloud Security Group Rule Prototype, log it and move on - s.V(3).Info("unexpected security group rule prototype", "securityGroupRulePrototype", reflect.TypeOf(existingRuleIntf).String()) + log.V(3).Info("unexpected security group rule prototype", "securityGroupRulePrototype", reflect.TypeOf(existingRuleIntf).String()) } } // If we did not find a matching SecurityGroupRule for this defined Remote, create one now. if !remoteMatch { - err := s.createSecurityGroupRule(securityGroupID, securityGroupRule, remote) + err := s.createSecurityGroupRule(ctx, securityGroupID, securityGroupRule, remote) if err != nil { return fmt.Errorf("error failure creating security group rule: %w", err) } @@ -1517,19 +1528,21 @@ func (s *VPCClusterScope) findOrCreateSecurityGroupRule(securityGroupID string, } // checkSecurityGroupRuleProtocolAll analyzes an IBM Cloud Security Group Rule designated for 'all' protocols, to verify if the supplied Rule and Remote match the attributes from the existing 'ProtocolAll' Rule. -func (s *VPCClusterScope) checkSecurityGroupRuleProtocolAll(_ infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolAll) (bool, error) { - if exists, err := s.checkSecurityGroupRulePrototypeRemote(securityGroupRuleRemote, existingRule.Remote); err != nil { +func (s *VPCClusterScope) checkSecurityGroupRuleProtocolAll(ctx context.Context, _ infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolAll) (bool, error) { + log := ctrl.LoggerFrom(ctx) + if exists, err := s.checkSecurityGroupRulePrototypeRemote(ctx, securityGroupRuleRemote, existingRule.Remote); err != nil { return false, fmt.Errorf("error failed checking security group rule all remote: %w", err) } else if exists { - s.V(3).Info("security group rule all protocols match") + log.V(3).Info("security group rule all protocols match") return true, nil } return false, nil } // checkSecurityGroupRuleProtocolIcmp analyzes an IBM Cloud Security Group Rule designated for 'icmp' protocol, to verify if the supplied Rule and Remote match the attributes from the existing 'ProtocolIcmp' Rule. -func (s *VPCClusterScope) checkSecurityGroupRuleProtocolIcmp(securityGroupRulePrototype infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolIcmp) (bool, error) { - if exists, err := s.checkSecurityGroupRulePrototypeRemote(securityGroupRuleRemote, existingRule.Remote); err != nil { +func (s *VPCClusterScope) checkSecurityGroupRuleProtocolIcmp(ctx context.Context, securityGroupRulePrototype infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolIcmp) (bool, error) { + log := ctrl.LoggerFrom(ctx) + if exists, err := s.checkSecurityGroupRulePrototypeRemote(ctx, securityGroupRuleRemote, existingRule.Remote); err != nil { return false, fmt.Errorf("error failed checking security group rule icmp remote: %w", err) } else if !exists { return false, nil @@ -1538,30 +1551,31 @@ func (s *VPCClusterScope) checkSecurityGroupRuleProtocolIcmp(securityGroupRulePr if securityGroupRulePrototype.ICMPCode != nil && securityGroupRulePrototype.ICMPType != nil { // If the existingRule Code and Type are both equal to the securityGroupRulePrototype's ICMPType and ICMPCode, the existingRule matches our definition for ICMP in securityGroupRulePrototype. if *securityGroupRulePrototype.ICMPCode == *existingRule.Code && *securityGroupRulePrototype.ICMPType == *existingRule.Type { - s.V(3).Info("security group rule icmp code and type match", "icmpCode", *existingRule.Code, "icmpType", *existingRule.Type) + log.V(3).Info("security group rule icmp code and type match", "icmpCode", *existingRule.Code, "icmpType", *existingRule.Type) return true, nil } } else if existingRule.Code == nil && existingRule.Type == nil { - s.V(3).Info("security group rule unset icmp matches") + log.V(3).Info("security group rule unset icmp matches") return true, nil } return false, nil } // checkSecurityGroupRuleProtocolTcpudp analyzes an IBM Cloud Security Group Rule designated for either 'tcp' or 'udp' protocols, to verify if the supplied Rule and Remote match the attributes from the existing 'ProtocolTcpudp' Rule. -func (s *VPCClusterScope) checkSecurityGroupRuleProtocolTcpudp(securityGroupRulePrototype infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp) (bool, error) { +func (s *VPCClusterScope) checkSecurityGroupRuleProtocolTcpudp(ctx context.Context, securityGroupRulePrototype infrav1.VPCSecurityGroupRulePrototype, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp) (bool, error) { + log := ctrl.LoggerFrom(ctx) // Check the protocol next, either TCP or UDP, to verify it matches if securityGroupRulePrototype.Protocol != infrav1.VPCSecurityGroupRuleProtocol(*existingRule.Protocol) { return false, nil } - if exists, err := s.checkSecurityGroupRulePrototypeRemote(securityGroupRuleRemote, existingRule.Remote); err != nil { + if exists, err := s.checkSecurityGroupRulePrototypeRemote(ctx, securityGroupRuleRemote, existingRule.Remote); err != nil { return false, fmt.Errorf("error failed checking security group rule tcp-udp remote: %w", err) } else if exists { // If PortRange is set, verify whether the MinimumPort and MaximumPort match the existingRule's values, if they are set. if securityGroupRulePrototype.PortRange != nil { if existingRule.PortMin != nil && securityGroupRulePrototype.PortRange.MinimumPort == *existingRule.PortMin && existingRule.PortMax != nil && securityGroupRulePrototype.PortRange.MaximumPort == *existingRule.PortMax { - s.V(3).Info("security group rule port range matches", "ruleID", *existingRule.ID, "portMin", *existingRule.PortMin, "portMax", *existingRule.PortMax) + log.V(3).Info("security group rule port range matches", "ruleID", *existingRule.ID, "portMin", *existingRule.PortMin, "portMax", *existingRule.PortMax) return true, nil } } @@ -1569,7 +1583,8 @@ func (s *VPCClusterScope) checkSecurityGroupRuleProtocolTcpudp(securityGroupRule return false, nil } -func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRemote vpcv1.SecurityGroupRuleRemoteIntf) (bool, error) { //nolint: gocyclo +func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(ctx context.Context, securityGroupRuleRemote infrav1.VPCSecurityGroupRuleRemote, existingRemote vpcv1.SecurityGroupRuleRemoteIntf) (bool, error) { //nolint: gocyclo + log := ctrl.LoggerFrom(ctx) // NOTE(cjschaef): We only currently monitor Remote, not Local, as we don't support defining Local in SecurityGroup/SecurityGroupRule. switch securityGroupRuleRemote.RemoteType { case infrav1.VPCSecurityGroupRuleRemoteTypeCIDR: @@ -1584,7 +1599,7 @@ func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRul return false, fmt.Errorf("error failed getting subnet by name for security group rule") } if *subnetDetails.Ipv4CIDRBlock == *cidrRule.CIDRBlock { - s.V(3).Info("security group rule remote cidr's match", "remoteCIDR", *cidrRule.CIDRBlock) + log.V(3).Info("security group rule remote cidr's match", "remoteCIDR", *cidrRule.CIDRBlock) return true, nil } case infrav1.VPCSecurityGroupRuleRemoteTypeAddress: @@ -1593,7 +1608,7 @@ func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRul return false, nil } if *securityGroupRuleRemote.Address == *ipRule.Address { - s.V(3).Info("security group rule remote addresses match", "remoteAddress", *ipRule.Address) + log.V(3).Info("security group rule remote addresses match", "remoteAddress", *ipRule.Address) return true, nil } case infrav1.VPCSecurityGroupRuleRemoteTypeSG: @@ -1609,7 +1624,7 @@ func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRul // Option #1: If the SecurityGroupRuleRemoteSecurityGroupReference has a name assigned, we can shortcut and simply check that if sgRule.Name != nil && *sgRule.Name == *securityGroupRuleRemote.SecurityGroupName { - s.V(3).Info("security group rule remote security group name matches", "securityGroupRuleRemoteSecurityGroupName", *sgRule.Name) + log.V(3).Info("security group rule remote security group name matches", "securityGroupRuleRemoteSecurityGroupName", *sgRule.Name) return true, nil } // Try to get the Security Group Id for quick lookup (from Network Status) @@ -1618,7 +1633,7 @@ func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRul if securityGroupID := s.getSecurityGroupIDFromStatus(*securityGroupRuleRemote.SecurityGroupName); securityGroupID != nil { // Option #2: If the SecurityGroupRuleRemoteSecurityGroupReference has an ID assigned, we can shortcut and simply check that if sgRule.ID != nil && *securityGroupID == *sgRule.ID { - s.V(3).Info("security group rule remote security group id matches", "securityGroupRuleRemoteSecurityGroupID", *sgRule.ID) + log.V(3).Info("security group rule remote security group id matches", "securityGroupRuleRemoteSecurityGroupID", *sgRule.ID) return true, nil } securityGroupDetails, _, err = s.VPCClient.GetSecurityGroup(&vpcv1.GetSecurityGroupOptions{ @@ -1635,27 +1650,27 @@ func (s *VPCClusterScope) checkSecurityGroupRulePrototypeRemote(securityGroupRul // Option #3: We check the SecurityGroupRuleRemoteSecurityGroupReference's CRN, if the Name and ID were not available if *securityGroupDetails.CRN == *sgRule.CRN { - s.V(3).Info("security group rule remote security group crn matches", "securityGroupRuleRemoteSecurityGroupCRN", *securityGroupDetails.CRN) + log.V(3).Info("security group rule remote security group crn matches", "securityGroupRuleRemoteSecurityGroupCRN", *securityGroupDetails.CRN) return true, nil } case infrav1.VPCSecurityGroupRuleRemoteTypeAny: ipRule := existingRemote.(*vpcv1.SecurityGroupRuleRemote) if ipRule.Address == nil { - s.V(3).Info("security group rule remote has no address, defaults to any remote") + log.V(3).Info("security group rule remote has no address, defaults to any remote") return true, nil } if *ipRule.Address == infrav1.CIDRBlockAny { - s.V(3).Info("security group rule remote address matches %s", infrav1.CIDRBlockAny) + log.V(3).Info("security group rule remote address matches %s", infrav1.CIDRBlockAny) return true, nil } default: - s.V(3).Info("unknown security group rule remote") + log.V(3).Info("unknown security group rule remote") } return false, nil } // createSecurityGroupRuleAllRemotes will create one or more IBM Cloud Security Group Rules for a specific SecurityGroup, based on the provided SecurityGroupRule and Remotes defined in the SecurityGroupRule definition (one or more Remotes can be defined per SecurityGroupRule definition). -func (s *VPCClusterScope) createSecurityGroupRuleAllRemotes(securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule) error { +func (s *VPCClusterScope) createSecurityGroupRuleAllRemotes(ctx context.Context, securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule) error { var remotes []infrav1.VPCSecurityGroupRuleRemote switch securityGroupRule.Direction { case infrav1.VPCSecurityGroupRuleDirectionInbound: @@ -1664,7 +1679,7 @@ func (s *VPCClusterScope) createSecurityGroupRuleAllRemotes(securityGroupID stri remotes = securityGroupRule.Destination.Remotes } for _, remote := range remotes { - err := s.createSecurityGroupRule(securityGroupID, securityGroupRule, remote) + err := s.createSecurityGroupRule(ctx, securityGroupID, securityGroupRule, remote) if err != nil { return fmt.Errorf("error failed creating security group rule: %w", err) } @@ -1674,7 +1689,8 @@ func (s *VPCClusterScope) createSecurityGroupRuleAllRemotes(securityGroupID stri } // createSecurityGroupRule will create a new IBM Cloud Security Group Rule for a specific Security Group, based on the provided SecurityGroupRule and Remote definitions. -func (s *VPCClusterScope) createSecurityGroupRule(securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule, remote infrav1.VPCSecurityGroupRuleRemote) error { +func (s *VPCClusterScope) createSecurityGroupRule(ctx context.Context, securityGroupID string, securityGroupRule infrav1.VPCSecurityGroupRule, remote infrav1.VPCSecurityGroupRuleRemote) error { + log := ctrl.LoggerFrom(ctx) options := &vpcv1.CreateSecurityGroupRuleOptions{ SecurityGroupID: &securityGroupID, } @@ -1726,7 +1742,7 @@ func (s *VPCClusterScope) createSecurityGroupRule(securityGroupID string, securi return fmt.Errorf("error failed creating security group rule, unknown protocol") } - s.V(3).Info("Creating Security Group Rule for Security Group", "securityGroupID", securityGroupID, "direction", securityGroupRule.Direction, "protocol", securityGroupRulePrototype.Protocol, "prototypeRemote", prototypeRemote) + log.V(3).Info("Creating Security Group Rule for Security Group", "securityGroupID", securityGroupID, "direction", securityGroupRule.Direction, "protocol", securityGroupRulePrototype.Protocol, "prototypeRemote", prototypeRemote) securityGroupRuleIntfDetails, _, err := s.VPCClient.CreateSecurityGroupRule(options) if err != nil { return fmt.Errorf("error unexpected failure creating security group rule: %w", err) @@ -1747,7 +1763,7 @@ func (s *VPCClusterScope) createSecurityGroupRule(securityGroupID string, securi rule := securityGroupRuleIntfDetails.(*vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp) ruleID = rule.ID } - s.V(3).Info("Created Security Group Rule", "ruleID", ruleID) + log.V(3).Info("Created Security Group Rule", "ruleID", ruleID) return nil } @@ -1786,7 +1802,7 @@ func (s *VPCClusterScope) createSecurityGroupRuleRemote(remote infrav1.VPCSecuri } // ReconcileLoadBalancers reconciles Load Balancers. -func (s *VPCClusterScope) ReconcileLoadBalancers() (bool, error) { +func (s *VPCClusterScope) ReconcileLoadBalancers(ctx context.Context) (bool, error) { // TODO(cjschaef): Determine if we want to use default LB configuration or require at least one is defined in Cluster spec. // TODO(cjschaef): Remove in favor of webhook validation. Perhaps to limit the number of LB's to one public and one private maximum. if len(s.NetworkSpec().LoadBalancers) == 0 { @@ -1810,14 +1826,14 @@ func (s *VPCClusterScope) ReconcileLoadBalancers() (bool, error) { if lbStatus != nil { s.setLoadBalancerStatus(lbStatus) // If the Load Balancer status isn't ready, flag for requeue and continue to next Load Balancer. - if isReady := s.isLoadBalancerReady(lbStatus.State); !isReady { + if isReady := s.isLoadBalancerReady(ctx, lbStatus.State); !isReady { requeue = true } continue } // Otherwise, create the Load Balancer. - err = s.createLoadBalancer(loadBalancer) + err = s.createLoadBalancer(ctx, loadBalancer) if err != nil { return false, fmt.Errorf("error creating load balancer: %w", err) } @@ -1830,15 +1846,16 @@ func (s *VPCClusterScope) ReconcileLoadBalancers() (bool, error) { // isLoadBalancerReady checks the state of a Load Balancer. // If state is active, true is returned, in all other cases, it returns false. // NOTE(cjschaef): May wish to extend this function to check all Load Balancer details (pools, listeners, etc.) as part of a Load Balancer being ready. -func (s *VPCClusterScope) isLoadBalancerReady(status infrav1.VPCLoadBalancerState) bool { +func (s *VPCClusterScope) isLoadBalancerReady(ctx context.Context, status infrav1.VPCLoadBalancerState) bool { + log := ctrl.LoggerFrom(ctx) switch status { case infrav1.VPCLoadBalancerStateActive: - s.V(5).Info("load balancer is in active state") + log.V(5).Info("load balancer is in active state") return true case infrav1.VPCLoadBalancerStateCreatePending: - s.V(5).Info("load balancer is in create pending state") + log.V(5).Info("load balancer is in create pending state") default: - s.V(5).Info("load balancer is in unexpected state", "loadBalancerStatus", status) + log.V(5).Info("load balancer is in unexpected state", "loadBalancerStatus", status) } return false } @@ -1882,7 +1899,8 @@ func (s *VPCClusterScope) getLoadBalancer(lb infrav1.VPCLoadBalancerSpec) (*infr } // createLoadBalancer creates a Load Balancer. -func (s *VPCClusterScope) createLoadBalancer(loadBalancer infrav1.VPCLoadBalancerSpec) error { +func (s *VPCClusterScope) createLoadBalancer(ctx context.Context, loadBalancer infrav1.VPCLoadBalancerSpec) error { + log := ctrl.LoggerFrom(ctx) options := &vpcv1.CreateLoadBalancerOptions{} resourceGroupID, err := s.GetResourceGroupID() if err != nil { @@ -1926,7 +1944,7 @@ func (s *VPCClusterScope) createLoadBalancer(loadBalancer infrav1.VPCLoadBalance subnet := &vpcv1.SubnetIdentityByID{ ID: ptr.To(subnetID), } - s.V(3).Info("adding subnet to load balancer", "loadBalancerName", loadBalancer.Name, "subnetID", subnetID) + log.V(3).Info("adding subnet to load balancer", "loadBalancerName", loadBalancer.Name, "subnetID", subnetID) options.Subnets = append(options.Subnets, subnet) } @@ -1939,7 +1957,7 @@ func (s *VPCClusterScope) createLoadBalancer(loadBalancer infrav1.VPCLoadBalance sg := &vpcv1.SecurityGroupIdentityByID{ ID: ptr.To(securityGroupID), } - s.V(3).Info("adding security group to load balancer", "loadBalancerName", loadBalancer.Name, "securityGroupID", securityGroupID) + log.V(3).Info("adding security group to load balancer", "loadBalancerName", loadBalancer.Name, "securityGroupID", securityGroupID) options.SecurityGroups = append(options.SecurityGroups, sg) } @@ -1951,11 +1969,11 @@ func (s *VPCClusterScope) createLoadBalancer(loadBalancer infrav1.VPCLoadBalance for _, pool := range loadBalancer.BackendPools { backendPool := s.buildLoadBalancerBackendPool(pool) - s.V(3).Info("added pool to load balancer", "loadBalancerName", loadBalancer.Name, "backendPoolName", pool.Name) + log.V(3).Info("added pool to load balancer", "loadBalancerName", loadBalancer.Name, "backendPoolName", pool.Name) backendPools = append(backendPools, backendPool) } } else { - s.V(3).Info("using default backend pools for load balancer", "loadBalancerName", loadBalancer.Name) + log.V(3).Info("using default backend pools for load balancer", "loadBalancerName", loadBalancer.Name) backendPools = append(backendPools, s.getDefaultLoadBalancerBackendPools()...) } options.SetPools(backendPools) @@ -1968,17 +1986,17 @@ func (s *VPCClusterScope) createLoadBalancer(loadBalancer infrav1.VPCLoadBalance for _, additionalListener := range loadBalancer.AdditionalListeners { listener := s.buildLoadBalancerListener(additionalListener) - s.V(3).Info("addd listener to load balancer", "loadBalancerName", loadBalancer.Name, "listenerPort", listener.Port) + log.V(3).Info("addd listener to load balancer", "loadBalancerName", loadBalancer.Name, "listenerPort", listener.Port) listeners = append(listeners, listener) } } else { - s.V(3).Info("using default listeners for load balancer", "loadBalancerName", loadBalancer.Name) + log.V(3).Info("using default listeners for load balancer", "loadBalancerName", loadBalancer.Name) listeners = append(listeners, s.getDefaultLoadBalancerListeners(loadBalancer.BackendPools == nil)...) } options.SetListeners(listeners) // Create the load balancer. - s.V(5).Info("creating new load balancer", "loadBalancerOptions", options) + log.V(5).Info("creating new load balancer", "loadBalancerOptions", options) loadBalancerDetails, _, err := s.VPCClient.CreateLoadBalancer(options) if err != nil { return fmt.Errorf("error creating load balancer: %w", err) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml index cd18f466e..a177ba084 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ibmvpcclusters.yaml @@ -1498,6 +1498,74 @@ spec: zone: type: string type: object + v1beta2: + description: V1beta2 groups all the fields that will be added or modified + in IBMVPCCluster's status with the V1Beta2 version. + properties: + conditions: + description: Conditions represents the observations of a IBMVPCCluster's + current state. + items: + description: Condition contains details for one aspect of the + current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object vpc: description: |- Important: Run "make" to regenerate code after modifying this file diff --git a/controllers/ibmpowervsmachine_controller.go b/controllers/ibmpowervsmachine_controller.go index 003b39eb3..570713400 100644 --- a/controllers/ibmpowervsmachine_controller.go +++ b/controllers/ibmpowervsmachine_controller.go @@ -36,7 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" - clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1" //nolint:staticcheck //nolint:staticcheck + clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1" //nolint:staticcheck clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/conditions" diff --git a/controllers/ibmvpccluster_controller.go b/controllers/ibmvpccluster_controller.go index 56d703cb4..83b552415 100644 --- a/controllers/ibmvpccluster_controller.go +++ b/controllers/ibmvpccluster_controller.go @@ -26,8 +26,11 @@ import ( "github.com/IBM/vpc-go-sdk/vpcv1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + kerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -36,7 +39,11 @@ import ( clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1" //nolint:staticcheck "sigs.k8s.io/cluster-api/util" - v1beta1conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions" //nolint:staticcheck + v1beta1conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions" //nolint:staticcheck + v1beta2conditions "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/conditions/v1beta2" //nolint:staticcheck + v1beta1patch "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/patch" //nolint:staticcheck + "sigs.k8s.io/cluster-api/util/deprecated/v1beta1/paused" + "sigs.k8s.io/cluster-api/util/finalizers" "sigs.k8s.io/cluster-api/util/predicates" infrav1 "sigs.k8s.io/cluster-api-provider-ibmcloud/api/v1beta2" @@ -59,25 +66,29 @@ type IBMVPCClusterReconciler struct { // Reconcile implements controller runtime Reconciler interface and handles reconcileation logic for IBMVPCCluster. func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { - log := r.Log.WithValues("ibmvpccluster", req.NamespacedName) + log := ctrl.LoggerFrom(ctx) + + log.Info("Reconciling IBMVPCCluster") + defer log.Info("Finished reconciling IBMVPCCluster") // Fetch the IBMVPCCluster instance. - ibmCluster := &infrav1.IBMVPCCluster{} - err := r.Get(ctx, req.NamespacedName, ibmCluster) + ibmVPCCluster := &infrav1.IBMVPCCluster{} + err := r.Get(ctx, req.NamespacedName, ibmVPCCluster) if err != nil { if apierrors.IsNotFound(err) { + log.Info("IBMVPCCluster not found") return ctrl.Result{}, nil } return ctrl.Result{}, err } // Determine whether the Cluster is designed for extended Infrastructure support, implemented in a separate path. - if ibmCluster.Spec.Network != nil { + if ibmVPCCluster.Spec.Network != nil { return r.reconcileV2(ctx, req) } // Fetch the Cluster. - cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) + cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmVPCCluster.ObjectMeta) if err != nil { return ctrl.Result{}, err } @@ -86,40 +97,55 @@ func (r *IBMVPCClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, nil } + // Add finalizer first if not set to avoid the race condition between init and delete. + if finalizerAdded, err := finalizers.EnsureFinalizer(ctx, r.Client, ibmVPCCluster, infrav1.ClusterFinalizer); err != nil || finalizerAdded { + return ctrl.Result{}, err + } + + log = log.WithValues("Cluster", klog.KObj(cluster)) + ctx = ctrl.LoggerInto(ctx, log) + + if isPaused, requeue, err := paused.EnsurePausedCondition(ctx, r.Client, cluster, ibmVPCCluster); err != nil || isPaused || requeue { + return ctrl.Result{}, err + } + clusterScope, err := scope.NewClusterScope(scope.ClusterScopeParams{ Client: r.Client, - Logger: log, Cluster: cluster, - IBMVPCCluster: ibmCluster, + IBMVPCCluster: ibmVPCCluster, ServiceEndpoint: r.ServiceEndpoint, }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create scope: %w", err) + } - // Always close the scope when exiting this function so we can persist any IBMVPCCluster changes. + // Initialize the patch helper. + patchHelper, err := v1beta1patch.NewHelper(ibmVPCCluster, r.Client) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to initialize patch helper: %w", err) + } + + // Always attempt to Patch the IBMVPCCluster object and status after each reconciliation. defer func() { - if clusterScope != nil { - if err := clusterScope.Close(); err != nil && reterr == nil { - reterr = err - } + if err := patchIBMVPCCluster(ctx, patchHelper, ibmVPCCluster); err != nil { + reterr = kerrors.NewAggregate([]error{reterr, err}) } }() // Handle deleted clusters. - if !ibmCluster.DeletionTimestamp.IsZero() { - return r.reconcileDelete(clusterScope) + if !ibmVPCCluster.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, clusterScope) } - if err != nil { - return reconcile.Result{}, fmt.Errorf("failed to create scope: %w", err) - } - return r.reconcile(clusterScope) + return r.reconcile(ctx, clusterScope) } func (r *IBMVPCClusterReconciler) reconcileV2(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) { - log := r.Log.WithValues("ibmvpccluster", req.NamespacedName) + log := ctrl.LoggerFrom(ctx).WithValues("controller", "IBMVPCCluster") // Fetch the IBMVPCCluster instance. - ibmCluster := &infrav1.IBMVPCCluster{} - err := r.Get(ctx, req.NamespacedName, ibmCluster) + ibmVPCCluster := &infrav1.IBMVPCCluster{} + err := r.Get(ctx, req.NamespacedName, ibmVPCCluster) if err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil @@ -127,8 +153,13 @@ func (r *IBMVPCClusterReconciler) reconcileV2(ctx context.Context, req ctrl.Requ return ctrl.Result{}, err } + // Add finalizer first if not set to avoid the race condition between init and delete. + if finalizerAdded, err := finalizers.EnsureFinalizer(ctx, r.Client, ibmVPCCluster, infrav1.ClusterFinalizer); err != nil || finalizerAdded { + return ctrl.Result{}, err + } + // Fetch the Cluster. - cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmCluster.ObjectMeta) + cluster, err := util.GetOwnerCluster(ctx, r.Client, ibmVPCCluster.ObjectMeta) if err != nil { return ctrl.Result{}, err } @@ -137,35 +168,44 @@ func (r *IBMVPCClusterReconciler) reconcileV2(ctx context.Context, req ctrl.Requ return ctrl.Result{}, nil } + if isPaused, requeue, err := paused.EnsurePausedCondition(ctx, r.Client, cluster, ibmVPCCluster); err != nil || isPaused || requeue { + return ctrl.Result{}, err + } + clusterScope, err := scope.NewVPCClusterScope(scope.VPCClusterScopeParams{ Client: r.Client, Logger: log, Cluster: cluster, - IBMVPCCluster: ibmCluster, + IBMVPCCluster: ibmVPCCluster, ServiceEndpoint: r.ServiceEndpoint, }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create scope: %w", err) + } - // Always close the scope when exiting this function so we can persist any IBMVPCCluster changes. + // Initialize the patch helper. + patchHelper, err := v1beta1patch.NewHelper(ibmVPCCluster, r.Client) + if err != nil { + return ctrl.Result{}, err + } + + // Always attempt to Patch the IBMVPCCluster object and status after each reconciliation. defer func() { - if clusterScope != nil { - if err := clusterScope.Close(); err != nil && reterr == nil { - reterr = err - } + if err := patchIBMVPCCluster(ctx, patchHelper, ibmVPCCluster); err != nil { + reterr = kerrors.NewAggregate([]error{reterr, err}) } }() // Handle deleted clusters. - if !ibmCluster.DeletionTimestamp.IsZero() { + if !ibmVPCCluster.DeletionTimestamp.IsZero() { return r.reconcileDeleteV2(clusterScope) } - if err != nil { - return reconcile.Result{}, fmt.Errorf("failed to create scope: %w", err) - } - return r.reconcileCluster(clusterScope) + return r.reconcileCluster(ctx, clusterScope) } -func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (ctrl.Result, error) { +func (r *IBMVPCClusterReconciler) reconcile(ctx context.Context, clusterScope *scope.ClusterScope) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx).WithValues("controller", "IBMVPCCluster") // If the IBMVPCCluster doesn't have our finalizer, add it. if controllerutil.AddFinalizer(clusterScope.IBMVPCCluster, infrav1.ClusterFinalizer) { return ctrl.Result{}, nil @@ -180,23 +220,46 @@ func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (c if loadBalancerEndpoint == nil { return ctrl.Result{}, fmt.Errorf("no loadBalancer found with hostname - %s", clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Host) } - r.reconcileLBState(clusterScope, loadBalancerEndpoint) + r.reconcileLBState(ctx, clusterScope, loadBalancerEndpoint) } + log.Info("Reconciling VPC") vpc, err := clusterScope.CreateVPC() if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to reconcile VPC for IBMVPCCluster %s/%s: %w", clusterScope.IBMVPCCluster.Namespace, clusterScope.IBMVPCCluster.Name, err) + v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.VPCReadyCondition, infrav1.VPCReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCNotReadyV1Beta2Reason, + Message: err.Error(), + }) + return ctrl.Result{}, fmt.Errorf("failed to reconcile VPC for IBMVPCCluster %s: %w", klog.KObj(clusterScope.IBMVPCCluster), err) } if vpc != nil { clusterScope.IBMVPCCluster.Status.VPC = infrav1.VPC{ ID: *vpc.ID, Name: *vpc.Name, } + v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.VPCReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCReadyV1Beta2Reason, + }) + log.Info("Reconciliation of VPC complete") } if clusterScope.IBMVPCCluster.Status.Subnet.ID == nil { + log.Info("Reconciling VPC Subnets") subnet, err := clusterScope.CreateSubnet() if err != nil { + v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.VPCSubnetReadyCondition, infrav1.VPCSubnetReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSubnetReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCSubnetNotReadyV1Beta2Reason, + Message: err.Error(), + }) return ctrl.Result{}, fmt.Errorf("failed to reconcile Subnet for IBMVPCCluster %s/%s: %w", clusterScope.IBMVPCCluster.Namespace, clusterScope.IBMVPCCluster.Name, err) } if subnet != nil { @@ -206,10 +269,18 @@ func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (c ID: subnet.ID, Zone: subnet.Zone.Name, } + v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.VPCSubnetReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSubnetReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCSubnetReadyV1Beta2Reason, + }) + log.Info("Reconciliation of VPC Subnets complete") } } if clusterScope.IBMVPCCluster.Spec.ControlPlaneLoadBalancer != nil && clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Host == "" { + log.Info("Reconciling Load Balancers") loadBalancer, err := r.getOrCreate(clusterScope) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to reconcile Control Plane LoadBalancer for IBMVPCCluster %s/%s: %w", clusterScope.IBMVPCCluster.Namespace, clusterScope.IBMVPCCluster.Name, err) @@ -217,95 +288,152 @@ func (r *IBMVPCClusterReconciler) reconcile(clusterScope *scope.ClusterScope) (c if loadBalancer != nil { clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Host = *loadBalancer.Hostname - r.reconcileLBState(clusterScope, loadBalancer) + r.reconcileLBState(ctx, clusterScope, loadBalancer) + log.Info("Reconciliation of Load Balancers complete") } } // Requeue after 1 minute if cluster is not ready to update status of the cluster properly. if !clusterScope.IsReady() { - clusterScope.Info("Cluster is not yet ready") + log.Info("Cluster is not yet ready") return ctrl.Result{RequeueAfter: 1 * time.Minute}, nil } return ctrl.Result{}, nil } -func (r *IBMVPCClusterReconciler) reconcileCluster(clusterScope *scope.VPCClusterScope) (ctrl.Result, error) { +func (r *IBMVPCClusterReconciler) reconcileCluster(ctx context.Context, clusterScope *scope.VPCClusterScope) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) // If the IBMVPCCluster doesn't have our finalizer, add it. if controllerutil.AddFinalizer(clusterScope.IBMVPCCluster, infrav1.ClusterFinalizer) { return ctrl.Result{}, nil } // Reconcile the cluster's VPC. - clusterScope.Info("Reconciling VPC") - if requeue, err := clusterScope.ReconcileVPC(); err != nil { - clusterScope.Error(err, "failed to reconcile VPC") + log.Info("Reconciling VPC") + if requeue, err := clusterScope.ReconcileVPC(ctx); err != nil { + log.Error(err, "failed to reconcile VPC") v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.VPCReadyCondition, infrav1.VPCReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCNotReadyV1Beta2Reason, + Message: err.Error(), + }) return reconcile.Result{}, err } else if requeue { - clusterScope.Info("VPC creation is pending, requeuing") + log.Info("VPC creation is pending, requeuing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } - clusterScope.Info("Reconciliation of VPC complete") + log.Info("Reconciliation of VPC complete") v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.VPCReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCReadyV1Beta2Reason, + }) // Reconcile the cluster's VPC Custom Image. - clusterScope.Info("Reconciling VPC Custom Image") - if requeue, err := clusterScope.ReconcileVPCCustomImage(); err != nil { - clusterScope.Error(err, "failed to reconcile VPC Custom Image") + log.Info("Reconciling VPC Custom Image") + if requeue, err := clusterScope.ReconcileVPCCustomImage(ctx); err != nil { + log.Error(err, "failed to reconcile VPC Custom Image") v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.ImageReadyCondition, infrav1.ImageReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCImageReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCImageNotReadyV1Beta2Reason, + Message: err.Error(), + }) return reconcile.Result{}, err } else if requeue { - clusterScope.Info("VPC Custom Image creation is pending, requeueing") + log.Info("VPC Custom Image creation is pending, requeueing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } - clusterScope.Info("Reconciliation of VPC Custom Image complete") + log.Info("Reconciliation of VPC Custom Image complete") v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.ImageReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCImageReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCImageReadyV1Beta2Reason, + }) // Reconcile the cluster's VPC Subnets. - clusterScope.Info("Reconciling VPC Subnets") - if requeue, err := clusterScope.ReconcileSubnets(); err != nil { - clusterScope.Error(err, "failed to reconcile VPC Subnets") + log.Info("Reconciling VPC Subnets") + if requeue, err := clusterScope.ReconcileSubnets(ctx); err != nil { + log.Error(err, "failed to reconcile VPC Subnets") v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.VPCSubnetReadyCondition, infrav1.VPCSubnetReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSubnetReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCSubnetNotReadyV1Beta2Reason, + Message: err.Error(), + }) return reconcile.Result{}, err } else if requeue { - clusterScope.Info("VPC Subnets creation is pending, requeueing") + log.Info("VPC Subnets creation is pending, requeueing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } - clusterScope.Info("Reconciliation of VPC Subnets complete") + log.Info("Reconciliation of VPC Subnets complete") v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.VPCSubnetReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSubnetReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCSubnetReadyV1Beta2Reason, + }) // Reconcile the cluster's Security Groups (and Security Group Rules) - clusterScope.Info("Reconciling Security Groups") - if requeue, err := clusterScope.ReconcileSecurityGroups(); err != nil { - clusterScope.Error(err, "failed to reconcile Security Groups") + log.Info("Reconciling Security Groups") + if requeue, err := clusterScope.ReconcileSecurityGroups(ctx); err != nil { + log.Error(err, "failed to reconcile Security Groups") v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.VPCSecurityGroupReadyCondition, infrav1.VPCSecurityGroupReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSecurityGroupReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCSecurityGroupNotReadyV1Beta2Reason, + Message: err.Error(), + }) return reconcile.Result{}, err } else if requeue { - clusterScope.Info("Security Groups creation is pending, requeueing") + log.Info("Security Groups creation is pending, requeueing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } - clusterScope.Info("Reconciliation of Security Groups complete") + log.Info("Reconciliation of Security Groups complete") v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.VPCSecurityGroupReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSecurityGroupReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCSecurityGroupReadyV1Beta2Reason, + }) // Reconcile the cluster's Load Balancers - clusterScope.Info("Reconciling Load Balancers") - if requeue, err := clusterScope.ReconcileLoadBalancers(); err != nil { - clusterScope.Error(err, "failed to reconcile Load Balancers") + log.Info("Reconciling Load Balancers") + if requeue, err := clusterScope.ReconcileLoadBalancers(ctx); err != nil { + log.Error(err, "failed to reconcile Load Balancers") v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.LoadBalancerReadyCondition, infrav1.LoadBalancerReconciliationFailedReason, clusterv1beta1.ConditionSeverityError, "%s", err.Error()) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCLoadBalancerNotReadyV1Beta2Reason, + Message: err.Error(), + }) return reconcile.Result{}, err } else if requeue { - clusterScope.Info("Load Balancers creation is pending, requeueing") + log.Info("Load Balancers creation is pending, requeueing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } - clusterScope.Info("Reconciliation of Load Balancers complete") + log.Info("Reconciliation of Load Balancers complete") v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.LoadBalancerReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCLoadBalancerReadyV1Beta2Reason, + }) // Collect cluster's Load Balancer hostname for spec. hostName, err := clusterScope.GetLoadBalancerHostName() if err != nil { return reconcile.Result{}, fmt.Errorf("error retrieving load balancer hostname: %w", err) } else if hostName == nil || *hostName == "" { - clusterScope.Info("No Load Balancer hostname found, requeueing") + log.Info("No Load Balancer hostname found, requeueing") return reconcile.Result{RequeueAfter: 15 * time.Second}, nil } @@ -313,11 +441,12 @@ func (r *IBMVPCClusterReconciler) reconcileCluster(clusterScope *scope.VPCCluste clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Host = *hostName clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Port = clusterScope.GetAPIServerPort() clusterScope.IBMVPCCluster.Status.Ready = true - clusterScope.Info("cluster infrastructure is now ready for cluster", "clusterName", clusterScope.IBMVPCCluster.Name) + log.Info("cluster infrastructure is now ready for cluster", "clusterName", clusterScope.IBMVPCCluster.Name) return ctrl.Result{}, nil } -func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterScope) (ctrl.Result, error) { +func (r *IBMVPCClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) // check if still have existing VSIs. listVSIOpts := &vpcv1.ListInstancesOptions{ VPCID: &clusterScope.IBMVPCCluster.Status.VPC.ID, @@ -350,6 +479,13 @@ func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterSco if *loadBalancer.Name != clusterScope.IBMVPCCluster.Spec.ControlPlaneLoadBalancer.Name { return handleFinalizerRemoval(clusterScope) } + + log.Info("Deleting VPC load balancer") + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCLoadBalancerDeletingV1Beta2Reason, + }) deleted, err := clusterScope.DeleteLoadBalancer() if err != nil { return ctrl.Result{}, fmt.Errorf("failed to delete loadBalancer: %w", err) @@ -361,13 +497,27 @@ func (r *IBMVPCClusterReconciler) reconcileDelete(clusterScope *scope.ClusterSco } } - if err := clusterScope.DeleteSubnet(); err != nil { + log.Info("Deleting VPC subnet") + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCSubnetReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCSubnetDeletingV1Beta2Reason, + }) + if err := clusterScope.DeleteSubnet(ctx); err != nil { return ctrl.Result{}, fmt.Errorf("failed to delete subnet: %w", err) } + log.Info("Deleting VPC") + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCDeletingV1Beta2Reason, + }) if err := clusterScope.DeleteVPC(); err != nil { return ctrl.Result{}, fmt.Errorf("failed to delete VPC: %w", err) } + + log.Info("IBMVPCCluster deletion completed") return handleFinalizerRemoval(clusterScope) } @@ -385,30 +535,49 @@ func handleFinalizerRemoval(clusterScope *scope.ClusterScope) (ctrl.Result, erro return ctrl.Result{}, nil } -func (r *IBMVPCClusterReconciler) reconcileLBState(clusterScope *scope.ClusterScope, loadBalancer *vpcv1.LoadBalancer) { +func (r *IBMVPCClusterReconciler) reconcileLBState(ctx context.Context, clusterScope *scope.ClusterScope, loadBalancer *vpcv1.LoadBalancer) { + log := ctrl.LoggerFrom(ctx) if clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Port == 0 { clusterScope.IBMVPCCluster.Spec.ControlPlaneEndpoint.Port = clusterScope.APIServerPort() } clusterScope.SetLoadBalancerID(loadBalancer.ID) - clusterScope.Logger.V(3).Info("LoadBalancerID - " + clusterScope.GetLoadBalancerID()) + log.V(3).Info("LoadBalancerID - " + clusterScope.GetLoadBalancerID()) clusterScope.SetLoadBalancerAddress(loadBalancer.Hostname) clusterScope.SetLoadBalancerState(*loadBalancer.ProvisioningStatus) - clusterScope.Logger.V(3).Info("LoadBalancerState - " + string(clusterScope.GetLoadBalancerState())) + log.V(3).Info("LoadBalancerState - " + string(clusterScope.GetLoadBalancerState())) switch clusterScope.GetLoadBalancerState() { case infrav1.VPCLoadBalancerStateCreatePending: - clusterScope.Logger.V(3).Info("LoadBalancer is in create state") + log.V(3).Info("LoadBalancer is in create state") clusterScope.SetNotReady() v1beta1conditions.MarkFalse(clusterScope.IBMVPCCluster, infrav1.LoadBalancerReadyCondition, string(infrav1.VPCLoadBalancerStateCreatePending), clusterv1beta1.ConditionSeverityInfo, "%s", *loadBalancer.OperatingStatus) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionFalse, + Reason: infrav1.VPCLoadBalancerNotReadyV1Beta2Reason, + Message: "VPC load balancer is in creating state", + }) case infrav1.VPCLoadBalancerStateActive: - clusterScope.Logger.V(3).Info("LoadBalancer is in active state") + log.V(3).Info("LoadBalancer is in active state") clusterScope.SetReady() v1beta1conditions.MarkTrue(clusterScope.IBMVPCCluster, infrav1.LoadBalancerReadyCondition) + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionTrue, + Reason: infrav1.VPCLoadBalancerReadyV1Beta2Reason, + Message: "VPC load balancer is in active state", + }) default: - clusterScope.Logger.V(3).Info("LoadBalancer state is undefined", "state", clusterScope.GetLoadBalancerState(), "loadbalancer-id", clusterScope.GetLoadBalancerID()) + log.V(3).Info("LoadBalancer state is undefined", "state", clusterScope.GetLoadBalancerState(), "loadbalancerID", clusterScope.GetLoadBalancerID()) clusterScope.SetNotReady() v1beta1conditions.MarkUnknown(clusterScope.IBMVPCCluster, infrav1.LoadBalancerReadyCondition, *loadBalancer.ProvisioningStatus, "") + v1beta2conditions.Set(clusterScope.IBMVPCCluster, metav1.Condition{ + Type: infrav1.VPCLoadBalancerReadyV1Beta2Condition, + Status: metav1.ConditionUnknown, + Reason: infrav1.VPCLoadBalancerNotReadyV1Beta2Reason, + Message: fmt.Sprintf("VPC load balancer is in an unknown state: %s", *loadBalancer.ProvisioningStatus), + }) } } @@ -419,3 +588,42 @@ func (r *IBMVPCClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl WithEventFilter(predicates.ResourceIsNotExternallyManaged(r.Scheme, ctrl.LoggerFrom(ctx))). Complete(r) } + +// patchIBMVPCCluster updates the IBMVPCCluster and its status on the API server. +func patchIBMVPCCluster(ctx context.Context, patchHelper *v1beta1patch.Helper, ibmVPCCluster *infrav1.IBMVPCCluster) error { + if err := v1beta2conditions.SetSummaryCondition(ibmVPCCluster, ibmVPCCluster, infrav1.IBMVPCClusterReadyV1Beta2Condition, + v1beta2conditions.ForConditionTypes{ + infrav1.VPCReadyV1Beta2Condition, + infrav1.VPCSubnetReadyV1Beta2Condition, + infrav1.VPCLoadBalancerReadyV1Beta2Condition, + }, + v1beta2conditions.IgnoreTypesIfMissing{ + infrav1.VPCSecurityGroupReadyV1Beta2Condition, + infrav1.VPCImageReadyV1Beta2Condition, + }, + // Using a custom merge strategy to override reasons applied during merge. + v1beta2conditions.CustomMergeStrategy{ + MergeStrategy: v1beta2conditions.DefaultMergeStrategy( + // Use custom reasons. + v1beta2conditions.ComputeReasonFunc(v1beta2conditions.GetDefaultComputeMergeReasonFunc( + infrav1.IBMVPCClusterNotReadyV1Beta2Reason, + infrav1.IBMVPCClusterReadyUnknownV1Beta2Reason, + infrav1.IBMVPCClusterReadyV1Beta2Reason, + )), + ), + }, + ); err != nil { + return fmt.Errorf("failed to set %s condition: %w", infrav1.IBMVPCClusterReadyV1Beta2Condition, err) + } + + // Patch the IBMVPCCluster resource. + return patchHelper.Patch(ctx, ibmVPCCluster, v1beta1patch.WithOwnedV1Beta2Conditions{Conditions: []string{ + infrav1.IBMVPCClusterReadyV1Beta2Condition, + clusterv1beta1.PausedV1Beta2Condition, + infrav1.VPCReadyV1Beta2Condition, + infrav1.VPCSubnetReadyV1Beta2Condition, + infrav1.VPCSecurityGroupReadyV1Beta2Condition, + infrav1.VPCLoadBalancerReadyV1Beta2Condition, + infrav1.VPCImageReadyV1Beta2Condition, + }}) +} diff --git a/controllers/ibmvpccluster_controller_test.go b/controllers/ibmvpccluster_controller_test.go index 1e37734dc..7db02b889 100644 --- a/controllers/ibmvpccluster_controller_test.go +++ b/controllers/ibmvpccluster_controller_test.go @@ -48,7 +48,7 @@ func TestIBMVPCClusterReconciler_Reconcile(t *testing.T) { testCases := []struct { name string vpcCluster *infrav1.IBMVPCCluster - ownerCluster *clusterv1.Cluster + ownerCluster *clusterv1beta1.Cluster expectError bool }{ { @@ -184,7 +184,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { g := NewWithT(t) setup(t) t.Cleanup(teardown) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -198,7 +198,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { t.Cleanup(teardown) clusterScope.IBMVPCCluster.Finalizers = []string{infrav1.ClusterFinalizer} mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, errors.New("failed to list VPCs")) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -228,7 +228,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { clusterScope.IBMVPCCluster.Finalizers = []string{infrav1.ClusterFinalizer} mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) mockvpc.EXPECT().ListSubnets(subnetOptions).Return(subnets, response, errors.New("Failed to list the subnets")) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -249,7 +249,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) mockvpc.EXPECT().ListSubnets(subnetOptions).Return(subnets, response, nil) mockvpc.EXPECT().ListLoadBalancers(loadBalancerOptions).Return(loadBalancers, response, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -264,7 +264,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) mockvpc.EXPECT().ListSubnets(subnetOptions).Return(subnets, response, nil) mockvpc.EXPECT().ListLoadBalancers(loadBalancerOptions).Return(loadBalancers, response, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -278,7 +278,7 @@ func TestIBMVPCClusterReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(listVpcsOptions).Return(vpclist, response, nil) mockvpc.EXPECT().ListSubnets(subnetOptions).Return(subnets, response, nil) mockvpc.EXPECT().ListLoadBalancers(loadBalancerOptions).Return(loadBalancers, response, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -354,7 +354,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(&vpcv1.LoadBalancerCollection{}, &core.DetailedResponse{}, errors.New("Failed to list the LoadBalancers")) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -366,7 +366,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(loadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -382,7 +382,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(loadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -399,7 +399,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(loadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(true)) @@ -414,7 +414,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(loadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(false)) @@ -428,7 +428,7 @@ func TestIBMVPCClusterLBReconciler_reconcile(t *testing.T) { mockvpc.EXPECT().ListVpcs(&vpcv1.ListVpcsOptions{}).Return(vpclist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListSubnets(&vpcv1.ListSubnetsOptions{}).Return(subnets, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(&vpcv1.ListLoadBalancersOptions{}).Return(loadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcile(clusterScope) + _, err := reconciler.reconcile(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) g.Expect(clusterScope.IBMVPCCluster.Status.Ready).To(Equal(false)) @@ -485,7 +485,7 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { setup(t) t.Cleanup(teardown) mockvpc.EXPECT().ListInstances(listVSIOpts).Return(instancelist, response, errors.New("Failed to list the VSIs")) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -495,7 +495,7 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { t.Cleanup(teardown) instancelist.TotalCount = ptr.To(int64(2)) mockvpc.EXPECT().ListInstances(listVSIOpts).Return(instancelist, response, nil) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -516,7 +516,7 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { mockvpc.EXPECT().UnsetSubnetPublicGateway(unsetPGWOptions).Return(response, nil) mockvpc.EXPECT().DeletePublicGateway(deletePGWOptions).Return(response, nil) mockvpc.EXPECT().DeleteSubnet(deleteSubnetOptions).Return(response, errors.New("failed to delete subnet")) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -532,7 +532,7 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { mockvpc.EXPECT().DeletePublicGateway(deletePGWOptions).Return(response, nil) mockvpc.EXPECT().DeleteSubnet(deleteSubnetOptions).Return(response, nil) mockvpc.EXPECT().DeleteVPC(deleteVpcOptions).Return(response, errors.New("failed to delete VPC")) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -547,7 +547,7 @@ func TestIBMVPCClusterReconciler_delete(t *testing.T) { 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) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(Not(ContainElement(infrav1.ClusterFinalizer))) }) @@ -604,7 +604,7 @@ func TestIBMVPCClusterLBReconciler_delete(t *testing.T) { t.Cleanup(mockController.Finish) mockvpc.EXPECT().ListInstances(gomock.AssignableToTypeOf(&vpcv1.ListInstancesOptions{})).Return(instancelist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancersOptions{})).Return(&vpcv1.LoadBalancerCollection{}, &core.DetailedResponse{}, errors.New("failed to list LoadBalancers")) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(Not(BeNil())) }) t.Run("Should skip deleting other resources if LoadBalancer is still present", func(t *testing.T) { @@ -624,7 +624,7 @@ func TestIBMVPCClusterLBReconciler_delete(t *testing.T) { mockvpc.EXPECT().ListInstances(gomock.AssignableToTypeOf(&vpcv1.ListInstancesOptions{})).Return(instancelist, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancersOptions{})).Return(customloadBalancerCollection, &core.DetailedResponse{}, nil) mockvpc.EXPECT().ListLoadBalancers(gomock.AssignableToTypeOf(&vpcv1.ListLoadBalancersOptions{})).Return(customloadBalancerCollection, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(ContainElement(infrav1.ClusterFinalizer)) }) @@ -634,7 +634,7 @@ func TestIBMVPCClusterLBReconciler_delete(t *testing.T) { t.Cleanup(mockController.Finish) clusterScope.IBMVPCCluster.Spec.ControlPlaneLoadBalancer = nil mockvpc.EXPECT().ListInstances(gomock.AssignableToTypeOf(&vpcv1.ListInstancesOptions{})).Return(instancelist, &core.DetailedResponse{}, nil) - _, err := reconciler.reconcileDelete(clusterScope) + _, err := reconciler.reconcileDelete(ctx, clusterScope) g.Expect(err).To(BeNil()) g.Expect(clusterScope.IBMVPCCluster.Finalizers).To(Not(ContainElement(infrav1.ClusterFinalizer))) })