Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/v1beta1/clustersummary_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ type ClusterSummaryStatus struct {
// instance itself, *excluding* errors related to the deployment of individual features.
// +optional
FailureMessage *string `json:"failureMessage,omitempty"`

// ReconciliationSuspended indicates whether the reconciliation loop for this
// ClusterSummary is currently paused due to an external action (e.g., a user annotation).
// When true, the status will not be updated unless the pause is lifted.
// +optional
ReconciliationSuspended bool `json:"reconciliationSuspended,omitempty"`

// SuspensionReason provides a brief explanation of why the reconciliation is suspended.
// +optional
SuspensionReason *string `json:"suspensionReason,omitempty"`
}

//nolint: lll // marker
Expand Down
10 changes: 10 additions & 0 deletions api/v1beta1/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,14 @@ type Status struct {
// DependenciesHash is a hash representing the set of clusters where this ClusterProfile
// must be deployed, based on the combined configuration of its dependencies.
DependenciesHash []byte `json:"dependenciesHash,omitempty"`

// ReconciliationSuspended indicates whether the reconciliation loop for this
// ClusterSummary is currently paused due to an external action (e.g., a user annotation).
// When true, the status will not be updated unless the pause is lifted.
// +optional
ReconciliationSuspended bool `json:"reconciliationSuspended,omitempty"`

// SuspensionReason provides a brief explanation of why the reconciliation is suspended.
// +optional
SuspensionReason *string `json:"suspensionReason,omitempty"`
}
10 changes: 10 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,16 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
updatedClusters:
description: |-
UpdatedClusters contains information all the cluster currently matching
Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/config.projectsveltos.io_clustersummaries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,16 @@ spec:
If not set, reconciliations will happen as usual.
format: date-time
type: string
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
type: object
type: object
served: true
Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/config.projectsveltos.io_profiles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,16 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
updatedClusters:
description: |-
UpdatedClusters contains information all the cluster currently matching
Expand Down
6 changes: 6 additions & 0 deletions controllers/clusterprofile_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,15 @@ func (r *ClusterProfileReconciler) Reconcile(ctx context.Context, req ctrl.Reque

if isProfilePaused(profileScope) {
logger.V(logs.LogInfo).Info("profile is paused. Skip reconciliation")
profileScope.GetStatus().ReconciliationSuspended = true
suspensionReason := "ClusterProfile is paused"
profileScope.GetStatus().SuspensionReason = &suspensionReason
return reconcile.Result{}, nil
}

profileScope.GetStatus().ReconciliationSuspended = false
profileScope.GetStatus().SuspensionReason = nil

// Handle deleted clusterProfile
if !clusterProfile.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, profileScope), nil
Expand Down
79 changes: 58 additions & 21 deletions controllers/clustersummary_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ const (
AgentSendUpdatesNoGateway
)

const (
clusterPausedMessage = "Cluster is paused"
)

// ClusterSummaryReconciler reconciles a ClusterSummary object
type ClusterSummaryReconciler struct {
*rest.Config
Expand Down Expand Up @@ -270,9 +274,15 @@ func (r *ClusterSummaryReconciler) reconcileDelete(
}
if paused {
logger.V(logs.LogInfo).Info("cluster is paused. Do nothing.")
clusterSummaryScope.ClusterSummary.Status.ReconciliationSuspended = true
suspensionReason := clusterPausedMessage
clusterSummaryScope.ClusterSummary.Status.SuspensionReason = &suspensionReason
return reconcile.Result{}, nil
}

clusterSummaryScope.ClusterSummary.Status.ReconciliationSuspended = false
clusterSummaryScope.ClusterSummary.Status.SuspensionReason = nil

if !isDeleted {
// if cluster is marked for deletion do not try to remove ResourceSummaries.
// those are only deployed in the managed cluster so no need to cleanup on a deleted cluster
Expand All @@ -287,21 +297,7 @@ func (r *ClusterSummaryReconciler) reconcileDelete(
// in the management cluster and those need to be removed.
err = r.undeploy(ctx, clusterSummaryScope, logger)
if err != nil {
// In DryRun mode it is expected to always get an error back
if !clusterSummaryScope.IsDryRunSync() {
logger.V(logs.LogInfo).Error(err, "failed to undeploy")
return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter}, nil
}

var nonRetriableError *configv1beta1.NonRetriableError
if errors.As(err, &nonRetriableError) {
return reconcile.Result{Requeue: true, RequeueAfter: deleteHandOverRequeueAfter}, nil
}

var templateError *configv1beta1.TemplateInstantiationError
if errors.As(err, &templateError) {
return reconcile.Result{Requeue: true, RequeueAfter: deleteHandOverRequeueAfter}, nil
}
return r.processUndeployError(clusterSummaryScope, err, logger)
}

if !r.canRemoveFinalizer(ctx, clusterSummaryScope, logger) {
Expand All @@ -319,12 +315,9 @@ func (r *ClusterSummaryReconciler) reconcileDelete(
}

// Cluster is not present anymore or cleanup succeeded
logger.V(logs.LogInfo).Info("Removing finalizer")
if controllerutil.ContainsFinalizer(clusterSummaryScope.ClusterSummary, configv1beta1.ClusterSummaryFinalizer) {
if finalizersUpdated := controllerutil.RemoveFinalizer(clusterSummaryScope.ClusterSummary,
configv1beta1.ClusterSummaryFinalizer); !finalizersUpdated {
return reconcile.Result{}, fmt.Errorf("failed to remove finalizer")
}
err = r.removeFinalizer(clusterSummaryScope, logger)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to remove finalizer")
}

if err := r.deleteChartMap(ctx, clusterSummaryScope, logger); err != nil {
Expand Down Expand Up @@ -379,9 +372,15 @@ func (r *ClusterSummaryReconciler) reconcileNormal(ctx context.Context,
}
if paused {
logger.V(logs.LogInfo).Info("cluster is paused. Do nothing.")
clusterSummaryScope.ClusterSummary.Status.ReconciliationSuspended = true
suspensionReason := clusterPausedMessage
clusterSummaryScope.ClusterSummary.Status.SuspensionReason = &suspensionReason
return reconcile.Result{}, nil
}

clusterSummaryScope.ClusterSummary.Status.ReconciliationSuspended = false
clusterSummaryScope.ClusterSummary.Status.SuspensionReason = nil

err = r.startWatcherForTemplateResourceRefs(ctx, clusterSummaryScope.ClusterSummary)
if err != nil {
logger.V(logs.LogInfo).Error(err, "failed to start watcher on resources referenced in TemplateResourceRefs.")
Expand Down Expand Up @@ -1588,3 +1587,41 @@ func (r *ClusterSummaryReconciler) updateStatusWithMissingLicenseError(
r.updateFeatureStatus(clusterSummaryScope, libsveltosv1beta1.FeatureResources, &failed, nil,
notEligibleError, logger)
}

// removeFinalizer removes the finalizer
func (r *ClusterSummaryReconciler) removeFinalizer(clusterSummaryScope *scope.ClusterSummaryScope,
logger logr.Logger) error {

// Remove Finalizer
logger.V(logs.LogInfo).Info("Removing finalizer")
if controllerutil.ContainsFinalizer(clusterSummaryScope.ClusterSummary, configv1beta1.ClusterSummaryFinalizer) {
if finalizersUpdated := controllerutil.RemoveFinalizer(clusterSummaryScope.ClusterSummary,
configv1beta1.ClusterSummaryFinalizer); !finalizersUpdated {
return fmt.Errorf("failed to remove finalizer")
}
}

return nil
}

func (r *ClusterSummaryReconciler) processUndeployError(clusterSummaryScope *scope.ClusterSummaryScope,
undeployError error, logger logr.Logger) (reconcile.Result, error) {

// In DryRun mode it is expected to always get an error back
if !clusterSummaryScope.IsDryRunSync() {
logger.V(logs.LogInfo).Error(undeployError, "failed to undeploy")
return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter}, nil
}

var nonRetriableError *configv1beta1.NonRetriableError
if errors.As(undeployError, &nonRetriableError) {
return reconcile.Result{Requeue: true, RequeueAfter: deleteHandOverRequeueAfter}, nil
}

var templateError *configv1beta1.TemplateInstantiationError
if errors.As(undeployError, &templateError) {
return reconcile.Result{Requeue: true, RequeueAfter: deleteHandOverRequeueAfter}, nil
}

return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter}, nil
}
5 changes: 5 additions & 0 deletions controllers/profile_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,14 @@ func (r *ProfileReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_

if isProfilePaused(profileScope) {
logger.V(logs.LogInfo).Info("profile is paused. Skip reconciliation")
profileScope.GetStatus().ReconciliationSuspended = true
suspensionReason := "Profile is paused"
profileScope.GetStatus().SuspensionReason = &suspensionReason
return reconcile.Result{}, nil
}

profileScope.GetStatus().ReconciliationSuspended = false
profileScope.GetStatus().SuspensionReason = nil
// Always close the scope when exiting this function so we can persist any Profile
// changes.
defer func() {
Expand Down
30 changes: 30 additions & 0 deletions manifest/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,16 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
updatedClusters:
description: |-
UpdatedClusters contains information all the cluster currently matching
Expand Down Expand Up @@ -3354,6 +3364,16 @@ spec:
If not set, reconciliations will happen as usual.
format: date-time
type: string
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
type: object
type: object
served: true
Expand Down Expand Up @@ -4592,6 +4612,16 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
reconciliationSuspended:
description: |-
ReconciliationSuspended indicates whether the reconciliation loop for this
ClusterSummary is currently paused due to an external action (e.g., a user annotation).
When true, the status will not be updated unless the pause is lifted.
type: boolean
suspensionReason:
description: SuspensionReason provides a brief explanation of why
the reconciliation is suspended.
type: string
updatedClusters:
description: |-
UpdatedClusters contains information all the cluster currently matching
Expand Down
38 changes: 25 additions & 13 deletions test/fv/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,11 +738,6 @@ func verifyResourceReport(clusterReport *configv1beta1.ClusterReport,
}

func verifyDeployedGroupVersionKind(clusterProfileName string) {
Byf("Verifying DeployedGroupVersionKind are set for ClusterProfile %s", clusterProfileName)
// Test has been flaky. Rarely it happens that Kong service is not removed
// when clusterProfile is.
// Adding this extra code to make test fails if at this points, ClusterSummary
// has lost list of deployed GVKs (which will cause the cleanup to not happen)
listOptions := []client.ListOption{
client.MatchingLabels{
clusterops.ClusterProfileLabelName: clusterProfileName,
Expand All @@ -751,15 +746,32 @@ func verifyDeployedGroupVersionKind(clusterProfileName string) {
clusterSummaryList := &configv1beta1.ClusterSummaryList{}
Expect(k8sClient.List(context.TODO(), clusterSummaryList, listOptions...)).To(Succeed())
Expect(len(clusterSummaryList.Items)).To(Equal(1))
found := false
for i := range clusterSummaryList.Items[0].Status.DeployedGVKs {
fs := clusterSummaryList.Items[0].Status.DeployedGVKs[i]
if fs.FeatureID == libsveltosv1beta1.FeatureResources {
Expect(len(fs.DeployedGroupVersionKind)).ToNot(BeZero())
found = true

Byf("Verifying DeployedGroupVersionKind are set for ClusterSummary %s/%s",
clusterSummaryList.Items[0].Namespace, clusterSummaryList.Items[0].Name)
// Test has been flaky. Rarely it happens that Kong service is not removed
// when clusterProfile is.
// Adding this extra code to make test fails if at this points, ClusterSummary
// has lost list of deployed GVKs (which will cause the cleanup to not happen)

Eventually(func() bool {
currentClusterSummary := &configv1beta1.ClusterSummary{}
err := k8sClient.Get(context.TODO(),
types.NamespacedName{
Namespace: clusterSummaryList.Items[0].Namespace,
Name: clusterSummaryList.Items[0].Name},
currentClusterSummary)
if err != nil {
return false
}
}
Expect(found).To(BeTrue())
for i := range currentClusterSummary.Status.DeployedGVKs {
fs := currentClusterSummary.Status.DeployedGVKs[i]
if fs.FeatureID == libsveltosv1beta1.FeatureResources {
return len(fs.DeployedGroupVersionKind) != 0
}
}
return false
}, timeout, pollingInterval).Should(BeTrue())
}

func setTier(clusterProfileName string, tier int32) {
Expand Down