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
4 changes: 4 additions & 0 deletions controllers/clustersummary_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@ func (r *ClusterSummaryReconciler) prepareForDeployment(ctx context.Context,
}
clusterSummaryScope.SetDependenciesMessage(&msg)
if !allDeployed {
// Deploy is deferred, not in progress: reflect that in featureSummaries instead of
// leaving it empty (a fresh ClusterSummary) or frozen at whatever it last had, the
// same way the mirroring dependsOn/transitionFrom gates on the delete side already do.
r.resetFeatureStatus(clusterSummaryScope, libsveltosv1beta1.FeatureStatusBlocked)
r.setNextReconcileTime(clusterSummaryScope, normalRequeueAfter)
return reconcile.Result{RequeueAfter: normalRequeueAfter}
}
Expand Down
47 changes: 47 additions & 0 deletions controllers/clustersummary_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,53 @@ var _ = Describe("ClustersummaryController", func() {
Expect(featureHelmVerified).To(BeTrue())
})

It("prepareForDeployment marks featureSummaries Blocked when a DependsOn prerequisite is missing", func() {
// Mirrors "cleanupBeforeFinalizerRemoval marks featureSummaries Blocked while a
// dependent ClusterSummary still exists" on the delete side: before this fix, a
// ClusterSummary blocked on a not-yet-existing DependsOn prerequisite got a
// status.dependencies message but no featureSummaries entry at all, so
// `kubectl get clustersummary` (no featureSummaries to populate the printer columns
// from) showed nothing indicating anything was wrong.
missingProfileName := randomString()
clusterSummary.Spec.ClusterProfileSpec.DependsOn = []string{missingProfileName}
clusterSummary.Spec.ClusterProfileSpec.PolicyRefs = []configv1beta1.PolicyRef{
{Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), Namespace: randomString(), Name: randomString()},
}

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

reconciler := &controllers.ClusterSummaryReconciler{
Client: testEnv.Client,
Scheme: scheme,
Deployer: nil,
ClusterMap: make(map[corev1.ObjectReference]*libsveltosset.Set),
ReferenceMap: make(map[corev1.ObjectReference]*libsveltosset.Set),
PolicyMux: sync.Mutex{},
NextReconcileTimes: make(map[types.NamespacedName]controllers.ReconcileCooldown),
}

controllers.PrepareForDeployment(reconciler, context.TODO(), clusterSummaryScope,
textlogger.NewLogger(textlogger.NewConfig()))

Expect(clusterSummary.Status.Dependencies).ToNot(BeNil())
Expect(*clusterSummary.Status.Dependencies).To(ContainSubstring(missingProfileName))

featureResourcesVerified := false
for i := range clusterSummary.Status.FeatureSummaries {
if clusterSummary.Status.FeatureSummaries[i].FeatureID == libsveltosv1beta1.FeatureResources {
Expect(clusterSummary.Status.FeatureSummaries[i].Status).To(Equal(libsveltosv1beta1.FeatureStatusBlocked))
featureResourcesVerified = true
}
}
Expect(featureResourcesVerified).To(BeTrue())
})

It("shouldReconcile returns true when mode is OneTime but not all helm charts are deployed", func() {
clusterSummary.Spec.ClusterProfileSpec.SyncMode = configv1beta1.SyncModeOneTime
clusterSummary.Spec.ClusterProfileSpec.HelmCharts = []configv1beta1.HelmChart{
Expand Down
146 changes: 54 additions & 92 deletions test/fv/dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"

configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1"
"github.com/projectsveltos/addon-controller/lib/clusterops"
Expand Down Expand Up @@ -52,121 +51,84 @@ var _ = Describe("Dependencies", func() {

It("ClusterProfile with dependencies is deployed after dependencies are provisioned",
Label("FV", "PULLMODE", "EXTENDED"), func() {
Byf("Create a ClusterProfile matching Cluster %s/%s",
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName())
// clusterProfileDependency is only built in memory here, not yet created: the
// point of this test is to cover the prerequisite not existing yet at all when
// the dependent ClusterProfile is created, not just not being provisioned yet.
Byf("Compute name for a ClusterProfile that will act as a dependency, without creating it yet")
clusterProfileDependency := getClusterProfile(namePrefix, map[string]string{key: value})
clusterProfileDependency.Spec.SyncMode = configv1beta1.SyncModeContinuous
Expect(k8sClient.Create(context.TODO(), clusterProfileDependency)).To(Succeed())
verifyClusterProfileMatches(clusterProfileDependency)
verifyClusterSummary(clusterops.ClusterProfileLabelName, clusterProfileDependency.Name,
&clusterProfileDependency.Spec, kindWorkloadCluster.GetNamespace(),
kindWorkloadCluster.GetName(), getClusterType())

Byf("Create a ClusterProfile matching Cluster %s/%s",
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName())
Byf("Create a ClusterProfile matching Cluster %s/%s, depending on a ClusterProfile (%s) that does not exist yet",
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), clusterProfileDependency.Name)
clusterProfile := getClusterProfile(namePrefix, map[string]string{key: value})
clusterProfile.Spec.SyncMode = configv1beta1.SyncModeContinuous
Byf("Set ClusterProfile %s as dependency for ClusterProfile %s",
clusterProfileDependency.Name, clusterProfile.Name)
clusterProfile.Spec.DependsOn = []string{clusterProfileDependency.Name}
clusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{
{
RepositoryURL: bitnamiURL,
RepositoryName: bitnamiName,
ChartName: "bitnami/flink",
ChartVersion: "1.4.0",
ReleaseName: flinkRelease,
ReleaseNamespace: flinkRelease,
HelmChartAction: configv1beta1.HelmChartActionInstall,
},
}
Expect(k8sClient.Create(context.TODO(), clusterProfile)).To(Succeed())
verifyClusterProfileMatches(clusterProfile)
verifyClusterSummary(clusterops.ClusterProfileLabelName, clusterProfile.Name,
clusterSummary := verifyClusterSummary(clusterops.ClusterProfileLabelName, clusterProfile.Name,
&clusterProfile.Spec, kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType())

Byf("Update ClusterProfile %s to deploy helm charts", clusterProfileDependency.Name)
currentClusterProfile := &configv1beta1.ClusterProfile{}

err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
Expect(k8sClient.Get(context.TODO(),
types.NamespacedName{Name: clusterProfileDependency.Name}, currentClusterProfile)).To(Succeed())
currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{
{
RepositoryURL: "https://airflow.apache.org",
RepositoryName: "apache-airflow",
ChartName: "apache-airflow/airflow",
ChartVersion: "1.15.0",
ReleaseName: airflowRelease,
ReleaseNamespace: airflowRelease,
HelmChartAction: configv1beta1.HelmChartActionInstall,
Values: `createUserJob:
useHelmHooks: false
applyCustomEnv: false
migrateDatabaseJob:
useHelmHooks: false
applyCustomEnv: false`,
},
}
return k8sClient.Update(context.TODO(), currentClusterProfile)
})
Expect(err).To(BeNil())

Expect(k8sClient.Get(context.TODO(),
types.NamespacedName{Name: clusterProfileDependency.Name}, currentClusterProfile)).To(Succeed())

clusterSummaryDependency := verifyClusterSummary(clusterops.ClusterProfileLabelName,
currentClusterProfile.Name, &currentClusterProfile.Spec,
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType())

Byf("Update ClusterProfile %s to deploy helm charts", clusterProfile.Name)
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
Expect(k8sClient.Get(context.TODO(),
types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed())
currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{
{
RepositoryURL: bitnamiURL,
RepositoryName: bitnamiName,
ChartName: "bitnami/flink",
ChartVersion: "1.4.0",
ReleaseName: flinkRelease,
ReleaseNamespace: flinkRelease,
HelmChartAction: configv1beta1.HelmChartActionInstall,
},
}

return k8sClient.Update(context.TODO(), currentClusterProfile)
})
Expect(err).To(BeNil())

Expect(k8sClient.Get(context.TODO(),
types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed())

clusterSummary := verifyClusterSummary(clusterops.ClusterProfileLabelName,
currentClusterProfile.Name, &currentClusterProfile.Spec,
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType())

By("Verifying clusterSummary is not deployed till the dependencies are provisioned")
Byf("Verifying ClusterSummary %s status reports the missing dependency and Blocked, not empty, featureSummaries",
clusterSummary.Name)
Eventually(func() bool {
currentClusterSummaryDependecy := &configv1beta1.ClusterSummary{}
err := k8sClient.Get(context.TODO(),
types.NamespacedName{Namespace: clusterSummaryDependency.Namespace, Name: clusterSummaryDependency.Name},
currentClusterSummaryDependecy)
if err != nil {
return false
}
currentClusterSummary := &configv1beta1.ClusterSummary{}
err = k8sClient.Get(context.TODO(),
err := k8sClient.Get(context.TODO(),
types.NamespacedName{Namespace: clusterSummary.Namespace, Name: clusterSummary.Name},
currentClusterSummary)
if err != nil {
return false
}

// currentClusterSummary depends on currentClusterSummaryDependecy so expects
// currentClusterSummary.Status.FeatureSummaries to be nil till all helm charts
// in currentClusterSummaryDependecy not provisioned
if currentClusterSummaryDependecy.Status.FeatureSummaries == nil {
return currentClusterSummary.Status.FeatureSummaries == nil
if currentClusterSummary.Status.Dependencies == nil ||
!strings.Contains(*currentClusterSummary.Status.Dependencies, clusterProfileDependency.Name) {

return false
}

for i := range currentClusterSummaryDependecy.Status.FeatureSummaries {
if currentClusterSummaryDependecy.Status.FeatureSummaries[i].Status != libsveltosv1beta1.FeatureStatusProvisioned {
return currentClusterSummary.Status.FeatureSummaries == nil
for i := range currentClusterSummary.Status.FeatureSummaries {
if currentClusterSummary.Status.FeatureSummaries[i].FeatureID == libsveltosv1beta1.FeatureHelm {
return currentClusterSummary.Status.FeatureSummaries[i].Status == libsveltosv1beta1.FeatureStatusBlocked
}
}
return true
return false
}, timeout, pollingInterval).Should(BeTrue())

Byf("Create the dependency ClusterProfile %s matching Cluster %s/%s",
clusterProfileDependency.Name, kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName())
clusterProfileDependency.Spec.HelmCharts = []configv1beta1.HelmChart{
{
RepositoryURL: "https://airflow.apache.org",
RepositoryName: "apache-airflow",
ChartName: "apache-airflow/airflow",
ChartVersion: "1.15.0",
ReleaseName: airflowRelease,
ReleaseNamespace: airflowRelease,
HelmChartAction: configv1beta1.HelmChartActionInstall,
Values: `createUserJob:
useHelmHooks: false
applyCustomEnv: false
migrateDatabaseJob:
useHelmHooks: false
applyCustomEnv: false`,
},
}
Expect(k8sClient.Create(context.TODO(), clusterProfileDependency)).To(Succeed())
verifyClusterProfileMatches(clusterProfileDependency)
clusterSummaryDependency := verifyClusterSummary(clusterops.ClusterProfileLabelName,
clusterProfileDependency.Name, &clusterProfileDependency.Spec,
kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType())

Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", clusterSummaryDependency.Name)
verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummaryDependency.Name, libsveltosv1beta1.FeatureHelm)

Expand Down