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
Original file line number Diff line number Diff line change
Expand Up @@ -2480,6 +2480,7 @@ spec:
- Removing
- Removed
- AgentRemoving
- Blocked
type: string
required:
- featureID
Expand Down
5 changes: 5 additions & 0 deletions controllers/clustersummary_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ func (r *ClusterSummaryReconciler) cleanupBeforeFinalizerRemoval(ctx context.Con
}
clusterSummaryScope.SetDependenciesMessage(&dependentMsg)
if !allRemoved {
// Teardown is deferred, not in progress: reflect that in featureSummaries instead of
// leaving it frozen at whatever status it last had (e.g. a stale Provisioning).
r.resetFeatureStatus(clusterSummaryScope, libsveltosv1beta1.FeatureStatusBlocked)
return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter}, nil, true
}

Expand All @@ -440,6 +443,8 @@ func (r *ClusterSummaryReconciler) cleanupBeforeFinalizerRemoval(ctx context.Con
}
clusterSummaryScope.SetDependenciesMessage(&transitionMsg)
if !allProvisioned {
// Same as the DependsOn gate above: teardown is deferred, not in progress.
r.resetFeatureStatus(clusterSummaryScope, libsveltosv1beta1.FeatureStatusBlocked)
return reconcile.Result{Requeue: true, RequeueAfter: deleteRequeueAfter}, nil, true
}

Expand Down
75 changes: 75 additions & 0 deletions controllers/clustersummary_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,81 @@ var _ = Describe("ClustersummaryController", func() {
Expect(removed).To(BeTrue())
})

It("cleanupBeforeFinalizerRemoval marks featureSummaries Blocked while a dependent ClusterSummary still exists", func() {
// Same DependsOn setup as the areDependentsRemoved test above. This time going through
// cleanupBeforeFinalizerRemoval to check the caller does not leave featureSummaries frozen
// at whatever status they had when teardown was deferred (issue #1950).
dependentProfileName := randomString()
dependentSummaryName := clusterops.GetClusterSummaryName(configv1beta1.ClusterProfileKind,
dependentProfileName, clusterName, false)
dependentSummary := &configv1beta1.ClusterSummary{
ObjectMeta: metav1.ObjectMeta{
Name: dependentSummaryName,
Namespace: namespace,
Labels: map[string]string{
clusterops.ClusterProfileLabelName: dependentProfileName,
configv1beta1.ClusterNameLabel: clusterName,
configv1beta1.ClusterTypeLabel: string(libsveltosv1beta1.ClusterTypeCapi),
},
},
Spec: configv1beta1.ClusterSummarySpec{
ClusterNamespace: cluster.Namespace,
ClusterName: cluster.Name,
ClusterType: libsveltosv1beta1.ClusterTypeCapi,
ClusterProfileSpec: configv1beta1.Spec{
DependsOn: []string{clusterProfile.Name},
},
},
}

clusterSummary.Spec.ClusterProfileSpec.PolicyRefs = []configv1beta1.PolicyRef{
{Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), Namespace: randomString(), Name: randomString()},
}
clusterSummary.Status.FeatureSummaries = []configv1beta1.FeatureSummary{
{FeatureID: libsveltosv1beta1.FeatureResources, Status: libsveltosv1beta1.FeatureStatusProvisioning},
}

initObjects := []client.Object{
dependentSummary,
clusterSummary,
clusterProfile,
}

c := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(initObjects...).WithObjects(initObjects...).Build()

addOwnerReference(context.TODO(), c, clusterSummary, clusterProfile)

deployer := fakedeployer.GetClient(context.TODO(), textlogger.NewLogger(textlogger.NewConfig()), c)
reconciler := &controllers.ClusterSummaryReconciler{
Client: c,
Scheme: scheme,
Deployer: deployer,
ClusterMap: make(map[corev1.ObjectReference]*libsveltosset.Set),
ReferenceMap: make(map[corev1.ObjectReference]*libsveltosset.Set),
PolicyMux: sync.Mutex{},
}

clusterSummaryScope, err := scope.NewClusterSummaryScope(&scope.ClusterSummaryScopeParams{
Client: c,
Logger: textlogger.NewLogger(textlogger.NewConfig()),
ClusterSummary: clusterSummary,
ControllerName: testControllerNameSummary,
})
Expect(err).To(BeNil())

// isDeleted: true skips the ResourceSummary-removal branch, which needs a real managed
// cluster connection; it plays no part in the gate this test is about.
_, err, done := controllers.CleanupBeforeFinalizerRemoval(reconciler, context.TODO(), clusterSummaryScope,
true, textlogger.NewLogger(textlogger.NewConfig()))
Expect(err).To(BeNil())
Expect(done).To(BeTrue())

Expect(clusterSummary.Status.FeatureSummaries).To(HaveLen(1))
Expect(clusterSummary.Status.FeatureSummaries[0].Status).To(Equal(libsveltosv1beta1.FeatureStatusBlocked))
Expect(clusterSummary.Status.Dependencies).ToNot(BeNil())
Expect(*clusterSummary.Status.Dependencies).To(ContainSubstring(dependentSummaryName))
})

It("processUndeployError requeues with deleteRequeueAfter and no error for WaitForProfileProcessingError", func() {
initObjects := []client.Object{
clusterProfile,
Expand Down
10 changes: 10 additions & 0 deletions controllers/clustersummary_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ func (r *ClusterSummaryReconciler) proceedDeployingFeatureInPullMode(ctx context
case libsveltosv1beta1.FeatureStatusFailedNonRetriable, libsveltosv1beta1.FeatureStatusRemoving,
libsveltosv1beta1.FeatureStatusAgentRemoving, libsveltosv1beta1.FeatureStatusRemoved:
logger.V(logs.LogDebug).Info("proceed deploying")
case libsveltosv1beta1.FeatureStatusBlocked:
// Blocked is set locally by cleanupBeforeFinalizerRemoval while a predecessor waits on a
// successor (TransitionFrom); the agent never reports it in a ConfigurationGroup, so this
// case is unreachable in practice. Listed only to satisfy exhaustive.
logger.V(logs.LogDebug).Info("proceed deploying")
}
} else {
provisioning := libsveltosv1beta1.FeatureStatusProvisioning
Expand Down Expand Up @@ -937,6 +942,11 @@ func (r *ClusterSummaryReconciler) updateFeatureStatus(clusterSummaryScope *scop
clusterSummaryScope.SetFeatureStatus(featureID, libsveltosv1beta1.FeatureStatusAgentRemoving, hash, nil)
case libsveltosv1beta1.FeatureStatusRemoving:
clusterSummaryScope.SetFeatureStatus(featureID, libsveltosv1beta1.FeatureStatusRemoving, hash, nil)
case libsveltosv1beta1.FeatureStatusBlocked:
// Not expected here: cleanupBeforeFinalizerRemoval sets Blocked directly via
// resetFeatureStatus, bypassing updateFeatureStatus. Handled the same way as the other
// non-terminal statuses above in case that ever changes.
clusterSummaryScope.SetFeatureStatus(featureID, libsveltosv1beta1.FeatureStatusBlocked, hash, nil)
case libsveltosv1beta1.FeatureStatusFailed, libsveltosv1beta1.FeatureStatusFailedNonRetriable:
failed := true
clusterSummaryScope.SetFeatureStatus(featureID, *status, hash, &failed)
Expand Down
59 changes: 59 additions & 0 deletions controllers/clustersummary_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,65 @@ var _ = Describe("TransitionFrom", func() {
Expect(msg).To(ContainSubstring(successor.Name))
})

It("cleanupBeforeFinalizerRemoval marks featureSummaries Blocked while a matching successor is not yet Provisioned", func() {
// Same setup as the areSuccessorsProvisioned test above. This time going through
// cleanupBeforeFinalizerRemoval to check the caller does not leave featureSummaries frozen
// at whatever status they had when teardown was deferred (issue #1950).
successor := &configv1beta1.ClusterProfile{
ObjectMeta: metav1.ObjectMeta{Name: clusterProfileNamePrefix + randomString()},
Spec: configv1beta1.Spec{
TransitionFrom: []string{predecessor.Name},
ClusterSelector: libsveltosv1beta1.Selector{
LabelSelector: metav1.LabelSelector{
MatchLabels: map[string]string{testDCLabelKey: testEngValue},
},
},
},
}
successorSummaryName := clusterops.GetClusterSummaryName(configv1beta1.ClusterProfileKind,
successor.Name, clusterName, false)
successorSummary := &configv1beta1.ClusterSummary{
ObjectMeta: metav1.ObjectMeta{Name: successorSummaryName, Namespace: namespace},
Spec: configv1beta1.ClusterSummarySpec{
ClusterNamespace: cluster.Namespace,
ClusterName: cluster.Name,
ClusterType: libsveltosv1beta1.ClusterTypeCapi,
ClusterProfileSpec: configv1beta1.Spec{
PolicyRefs: []configv1beta1.PolicyRef{
{Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), Namespace: namespace, Name: randomString()},
},
},
},
// No FeatureSummaries yet: not Provisioned.
}
successorSummary.Labels = map[string]string{
clusterops.ClusterProfileLabelName: successor.Name,
configv1beta1.ClusterNameLabel: clusterName,
configv1beta1.ClusterTypeLabel: string(libsveltosv1beta1.ClusterTypeCapi),
}

predecessorSummary.Spec.ClusterProfileSpec.PolicyRefs = []configv1beta1.PolicyRef{
{Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), Namespace: namespace, Name: randomString()},
}
predecessorSummary.Status.FeatureSummaries = []configv1beta1.FeatureSummary{
{FeatureID: libsveltosv1beta1.FeatureResources, Status: libsveltosv1beta1.FeatureStatusProvisioning},
}

reconciler := newReconciler(predecessorSummary, predecessor, cluster, successor, successorSummary)

// isDeleted: true skips the ResourceSummary-removal branch, which needs a real managed
// cluster connection; it plays no part in the gate this test is about.
_, err, done := controllers.CleanupBeforeFinalizerRemoval(reconciler, context.TODO(), newScope(reconciler),
true, textlogger.NewLogger(textlogger.NewConfig()))
Expect(err).To(BeNil())
Expect(done).To(BeTrue())

Expect(predecessorSummary.Status.FeatureSummaries).To(HaveLen(1))
Expect(predecessorSummary.Status.FeatureSummaries[0].Status).To(Equal(libsveltosv1beta1.FeatureStatusBlocked))
Expect(predecessorSummary.Status.Dependencies).ToNot(BeNil())
Expect(*predecessorSummary.Status.Dependencies).To(ContainSubstring(successor.Name))
})

It("areSuccessorsProvisioned proceeds once the matching successor is Provisioned", func() {
successor := &configv1beta1.ClusterProfile{
ObjectMeta: metav1.ObjectMeta{Name: clusterProfileNamePrefix + randomString()},
Expand Down
1 change: 1 addition & 0 deletions controllers/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var (
ShouldRedeploy = (*ClusterSummaryReconciler).shouldRedeploy
CanRemoveFinalizer = (*ClusterSummaryReconciler).canRemoveFinalizer
ReconcileDelete = (*ClusterSummaryReconciler).reconcileDelete
CleanupBeforeFinalizerRemoval = (*ClusterSummaryReconciler).cleanupBeforeFinalizerRemoval
AreDependenciesDeployed = (*ClusterSummaryReconciler).areDependenciesDeployed
AreDependentsRemoved = (*ClusterSummaryReconciler).areDependentsRemoved
AreSuccessorsProvisioned = (*ClusterSummaryReconciler).areSuccessorsProvisioned
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/onsi/gomega v1.43.0
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/projectsveltos/libsveltos v1.14.1-0.20260909063818-b0a031992dbc
github.com/projectsveltos/libsveltos v1.14.1-0.20260909140634-7473f8e8ef85
github.com/prometheus/client_golang v1.24.1
github.com/sigstore/cosign/v3 v3.1.3
github.com/sigstore/sigstore v1.10.9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
github.com/projectsveltos/libsveltos v1.14.1-0.20260909063818-b0a031992dbc h1:yDckm+M/oppO2YOqdooOLN5md+esuJovkrxH8KHojmE=
github.com/projectsveltos/libsveltos v1.14.1-0.20260909063818-b0a031992dbc/go.mod h1:U6iGj5KoC/PcTD2vh3XU6gy7g11suThT6sZSEpmLEkU=
github.com/projectsveltos/libsveltos v1.14.1-0.20260909140634-7473f8e8ef85 h1:U3vS4oqjCp7ifeIUFmwytKtDdM0b2QMLjbwQdns0axg=
github.com/projectsveltos/libsveltos v1.14.1-0.20260909140634-7473f8e8ef85/go.mod h1:U6iGj5KoC/PcTD2vh3XU6gy7g11suThT6sZSEpmLEkU=
github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5 h1:khnc+994UszxZYu69J+R5FKiLA/Nk1JQj0EYAkwTWz0=
github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5/go.mod h1:yVL8KQFa9tmcxgwl9nwIMtKgtmIVC1zaFRSCfOwYvPY=
github.com/projectsveltos/lua-utils/glua-runes v0.0.0-20251212200258-2b3cdcb7c0f5 h1:YbsebwRwTRhV8QacvEAdFqxcxHdeu7JTVtsBovbkgos=
Expand Down
1 change: 1 addition & 0 deletions lib/crd/clustersummaries.go
Original file line number Diff line number Diff line change
Expand Up @@ -2499,6 +2499,7 @@ spec:
- Removing
- Removed
- AgentRemoving
- Blocked
type: string
required:
- featureID
Expand Down
1 change: 1 addition & 0 deletions manifest/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8901,6 +8901,7 @@ spec:
- Removing
- Removed
- AgentRemoving
- Blocked
type: string
required:
- featureID
Expand Down