diff --git a/internal/controllers/clusterrequest_controller.go b/internal/controllers/clusterrequest_controller.go index 6cc22f68..0437fc97 100644 --- a/internal/controllers/clusterrequest_controller.go +++ b/internal/controllers/clusterrequest_controller.go @@ -848,37 +848,10 @@ func (t *clusterRequestReconcilerTask) checkClusterProvisionStatus( if !exists { return nil } - // Check ClusterInstance status and update the corresponding ClusterRequest status conditions + // Check ClusterInstance status and update the corresponding ClusterRequest status conditions. t.updateClusterInstanceProcessedStatus(clusterInstance) t.updateClusterProvisionStatus(clusterInstance) - // Check if the cluster provision has completed - crProvisionedCond := meta.FindStatusCondition(t.object.Status.Conditions, string(utils.CRconditionTypes.ClusterProvisioned)) - if crProvisionedCond != nil && crProvisionedCond.Status == metav1.ConditionTrue { - // Check the managed cluster for updating the ztpDone status. - managedCluster := &clusterv1.ManagedCluster{} - managedClusterExists, err := utils.DoesK8SResourceExist( - ctx, t.client, - clusterInstance.GetName(), - clusterInstance.GetName(), - managedCluster, - ) - if err != nil { - return fmt.Errorf("failed to check if managed cluster exists: %w", err) - } - - if managedClusterExists { - // If the ztp-done label exists, update the status to complete. - labels := managedCluster.GetLabels() - _, hasZtpDone := labels[ztpDoneLabel] - if hasZtpDone { - t.object.Status.ClusterDetails.ZtpStatus = utils.ClusterZtpDone - } else { - t.object.Status.ClusterDetails.ZtpStatus = utils.ClusterZtpNotDone - } - } - } - if updateErr := utils.UpdateK8sCRStatus(ctx, t.client, t.object); updateErr != nil { return fmt.Errorf("failed to update status for ClusterRequest %s: %w", t.object.Name, updateErr) } @@ -965,12 +938,38 @@ func (t *clusterRequestReconcilerTask) handleClusterPolicyConfiguration(ctx cont if err != nil { return false, err } + err = t.updateZTPStatus(ctx, allPoliciesCompliant) + if err != nil { + return false, err + } // If there are policies that are not Compliant, we need to requeue and see if they // time out or complete. return nonCompliantPolicyInEnforce, nil } +// updateZTPStatus updates status.ClusterDetails.ZtpStatus. +func (t *clusterRequestReconcilerTask) updateZTPStatus(ctx context.Context, allPoliciesCompliant bool) error { + // Check if the cluster provision has started. + crProvisionedCond := meta.FindStatusCondition(t.object.Status.Conditions, string(utils.CRconditionTypes.ClusterProvisioned)) + if crProvisionedCond != nil { + // If the provisioning has started, and the ZTP status is empty or not done. + if t.object.Status.ClusterDetails.ZtpStatus != utils.ClusterZtpDone { + t.object.Status.ClusterDetails.ZtpStatus = utils.ClusterZtpNotDone + // If the provisioning finished and all the policies are compliant, then ZTP is done. + if crProvisionedCond.Status == metav1.ConditionTrue && allPoliciesCompliant { + // Once the ZTPStatus reaches ZTP Done, it will stay that way. + t.object.Status.ClusterDetails.ZtpStatus = utils.ClusterZtpDone + } + } + } + + if err := utils.UpdateK8sCRStatus(ctx, t.client, t.object); err != nil { + return fmt.Errorf("failed to update the ZTP status for ClusterRequest %s: %w", t.object.Name, err) + } + return nil +} + // hasPolicyConfigurationTimedOut determines if the policy configuration for the // ClusterRequest has timed out. func (t *clusterRequestReconcilerTask) hasPolicyConfigurationTimedOut(ctx context.Context) bool { diff --git a/internal/controllers/clusterrequest_controller_test.go b/internal/controllers/clusterrequest_controller_test.go index 2eaa2989..a3dfe463 100644 --- a/internal/controllers/clusterrequest_controller_test.go +++ b/internal/controllers/clusterrequest_controller_test.go @@ -1161,6 +1161,140 @@ var _ = Describe("ClusterRequestReconcile", func() { }) }) }) + + Context("When evaluating ZTP Done", func() { + var ( + policy *policiesv1.Policy + managedCluster *clusterv1.ManagedCluster + ) + + BeforeEach(func() { + policy = &policiesv1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ztp-clustertemplate-a-v4-16.v1-subscriptions-policy", + Namespace: "cluster-1", + Labels: map[string]string{ + utils.ChildPolicyRootPolicyLabel: "ztp-clustertemplate-a-v4-16.v1-subscriptions-policy", + utils.ChildPolicyClusterNameLabel: "cluster-1", + utils.ChildPolicyClusterNamespaceLabel: "cluster-1", + }, + }, + Spec: policiesv1.PolicySpec{ + RemediationAction: "enforce", + }, + Status: policiesv1.PolicyStatus{ + ComplianceState: policiesv1.NonCompliant, + }, + } + Expect(c.Create(ctx, policy)).To(Succeed()) + + managedCluster = &clusterv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster-1", + }, + Spec: clusterv1.ManagedClusterSpec{ + HubAcceptsClient: true, + }, + Status: clusterv1.ManagedClusterStatus{ + Conditions: []metav1.Condition{ + { + Type: clusterv1.ManagedClusterConditionAvailable, + Status: metav1.ConditionTrue, + }, + { + Type: clusterv1.ManagedClusterConditionHubAccepted, + Status: metav1.ConditionTrue, + }, + { + Type: clusterv1.ManagedClusterConditionJoined, + Status: metav1.ConditionTrue, + }, + }, + }, + } + Expect(c.Create(ctx, managedCluster)).To(Succeed()) + + provisionedCond := metav1.Condition{ + Type: string(utils.CRconditionTypes.ClusterProvisioned), + Status: metav1.ConditionFalse, + } + cr.Status.Conditions = append(cr.Status.Conditions, provisionedCond) + cr.Status.ClusterDetails = &oranv1alpha1.ClusterDetails{} + cr.Status.ClusterDetails.Name = crName + Expect(c.Status().Update(ctx, cr)).To(Succeed()) + }) + + It("Sets the status to ZTP Not Done", func() { + // Start reconciliation + result, err := reconciler.Reconcile(ctx, req) + // Verify the reconciliation result + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(requeueWithMediumInterval())) + + reconciledCR := &oranv1alpha1.ClusterRequest{} + Expect(c.Get(ctx, req.NamespacedName, reconciledCR)).To(Succeed()) + + Expect(reconciledCR.Status.ClusterDetails.ZtpStatus).To(Equal(utils.ClusterZtpNotDone)) + conditions := reconciledCR.Status.Conditions + // Verify the ClusterRequest's status conditions + verifyStatusCondition(conditions[5], metav1.Condition{ + Type: string(utils.CRconditionTypes.ConfigurationApplied), + Status: metav1.ConditionFalse, + Reason: string(utils.CRconditionReasons.InProgress), + Message: "The configuration is still being applied", + }) + }) + + It("Sets the status to ZTP Done", func() { + // Set the policies to compliant. + policy.Status.ComplianceState = policiesv1.Compliant + Expect(c.Status().Update(ctx, policy)).To(Succeed()) + // Complete the cluster provisioning. + cr.Status.Conditions[0].Status = metav1.ConditionTrue + Expect(c.Status().Update(ctx, cr)).To(Succeed()) + // Start reconciliation. + result, err := reconciler.Reconcile(ctx, req) + // Verify the reconciliation result. + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(doNotRequeue())) + reconciledCR := &oranv1alpha1.ClusterRequest{} + Expect(c.Get(ctx, req.NamespacedName, reconciledCR)).To(Succeed()) + Expect(reconciledCR.Status.ClusterDetails.ZtpStatus).To(Equal(utils.ClusterZtpDone)) + // Verify the ClusterRequest's status conditions + conditions := reconciledCR.Status.Conditions + verifyStatusCondition(conditions[5], metav1.Condition{ + Type: string(utils.CRconditionTypes.ConfigurationApplied), + Status: metav1.ConditionTrue, + Reason: string(utils.CRconditionReasons.Completed), + Message: "The configuration is up to date", + }) + }) + + It("Keeps the ZTP status as ZTP Done if a policy becomes NonCompliant", func() { + cr.Status.ClusterDetails.ZtpStatus = utils.ClusterZtpDone + Expect(c.Status().Update(ctx, cr)).To(Succeed()) + policy.Status.ComplianceState = policiesv1.NonCompliant + Expect(c.Status().Update(ctx, policy)).To(Succeed()) + // Start reconciliation. + result, err := reconciler.Reconcile(ctx, req) + // Verify the reconciliation result. + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(requeueWithMediumInterval())) + + reconciledCR := &oranv1alpha1.ClusterRequest{} + Expect(c.Get(ctx, req.NamespacedName, reconciledCR)).To(Succeed()) + + Expect(reconciledCR.Status.ClusterDetails.ZtpStatus).To(Equal(utils.ClusterZtpDone)) + conditions := reconciledCR.Status.Conditions + // Verify the ClusterRequest's status conditions + verifyStatusCondition(conditions[5], metav1.Condition{ + Type: string(utils.CRconditionTypes.ConfigurationApplied), + Status: metav1.ConditionFalse, + Reason: string(utils.CRconditionReasons.InProgress), + Message: "The configuration is still being applied", + }) + }) + }) }) var _ = Describe("getCrClusterTemplateRef", func() { @@ -2016,8 +2150,8 @@ func verifyNodeStatus(c client.Client, ctx context.Context, nodes []*hwv1alpha1. var _ = Describe("policyManagement", func() { var ( - ctx context.Context c client.Client + ctx context.Context CRReconciler *ClusterRequestReconciler CRTask *clusterRequestReconcilerTask CTReconciler *ClusterTemplateReconciler @@ -3593,13 +3727,8 @@ defaultHugepagesSize: "1G"`, clusterRequest := &oranv1alpha1.ClusterRequest{} // Create the ClusterRequest reconciliation task. - err = CRReconciler.Client.Get( - context.TODO(), - types.NamespacedName{ - Name: "cluster-1", - Namespace: "clustertemplate-a-v4-16", - }, - clusterRequest) + namespacedName := types.NamespacedName{Name: "cluster-1", Namespace: "clustertemplate-a-v4-16"} + err = c.Get(context.TODO(), namespacedName, clusterRequest) Expect(err).ToNot(HaveOccurred()) CRTask = &clusterRequestReconcilerTask{