Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
34 changes: 34 additions & 0 deletions controllers/solrcloud_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ func (r *SolrCloudReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return reconcile.Result{Requeue: true}, nil
}

if !instance.ObjectMeta.DeletionTimestamp.IsZero() {
// Owned resources are garbage collected by Kubernetes, so only the storage finalizer needs handling.
// Skipping the full reconcile keeps a missing dependency, e.g. a deleted providedConfigMap, from blocking deletion.
return r.reconcileDeletion(ctx, instance, logger)
}

// When working with the clouds, some actions outside of kube may need to be retried after a few seconds
requeueOrNot := reconcile.Result{}

Expand Down Expand Up @@ -1206,6 +1212,34 @@ func (r *SolrCloudReconciler) storageClassAllowsExpansion(ctx context.Context, c
return allowed, className, nil
}

// reconcileDeletion handles a SolrCloud that is being deleted, by running the storage finalizer logic.
func (r *SolrCloudReconciler) reconcileDeletion(ctx context.Context, cloud *solrv1beta1.SolrCloud, logger logr.Logger) (ctrl.Result, error) {
if !util.ContainsString(cloud.ObjectMeta.Finalizers, util.SolrStorageFinalizer) {
return reconcile.Result{}, nil
}

statefulSet := &appsv1.StatefulSet{}
err := r.Get(ctx, types.NamespacedName{Name: cloud.StatefulSetName(), Namespace: cloud.Namespace}, statefulSet)
if errors.IsNotFound(err) {
// The StatefulSet may already be gone, e.g. with foreground deletion.
// Its PVCs still carry the selector labels the operator always generates.
statefulSet.Spec.Selector = &metav1.LabelSelector{MatchLabels: cloud.SharedLabels()}
Comment thread
syahra712 marked this conversation as resolved.
Resolved
} else if err != nil {
return reconcile.Result{}, err
}

// Do not reconcile the storage finalizer unless we have PVC Labels that we know the Solr data PVCs are using.
// Otherwise it will delete all PVCs possibly
if statefulSet.Spec.Selector == nil || len(statefulSet.Spec.Selector.MatchLabels) == 0 {
return reconcile.Result{}, nil
}
if err = r.reconcileStorageFinalizer(ctx, cloud, statefulSet, logger); err != nil {
Comment thread
syahra712 marked this conversation as resolved.
Resolved
logger.Error(err, "Cannot delete PVCs while garbage collecting after deletion.")
return reconcile.Result{RequeueAfter: time.Second * 15}, nil
}
return reconcile.Result{}, nil
}

// Logic derived from:
// - https://book.kubebuilder.io/reference/using-finalizers.html
// - https://github.com/pravega/zookeeper-operator/blob/v0.2.9/pkg/controller/zookeepercluster/zookeepercluster_controller.go#L629
Expand Down
52 changes: 52 additions & 0 deletions controllers/solrcloud_controller_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,58 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
})
})

FContext("Persistent Data - PVC Delete on Reclaim with a deleted provided ConfigMap", func() {
var providedConfigMap *corev1.ConfigMap

BeforeEach(func(ctx context.Context) {
providedConfigMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "provided-config-deleted-first",
Namespace: solrCloud.Namespace,
},
Data: map[string]string{
util.SolrXmlFile: "<solr><int name=\"hostPort\">${solr.port.advertise:80}</int></solr>",
},
}
Expect(k8sClient.Create(ctx, providedConfigMap)).To(Succeed())

solrCloud.Spec.CustomSolrKubeOptions.ConfigMapOptions = &solrv1beta1.ConfigMapOptions{
ProvidedConfigMap: providedConfigMap.Name,
}
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
PersistentStorage: &solrv1beta1.SolrPersistentDataStorageOptions{
VolumeReclaimPolicy: solrv1beta1.VolumeReclaimPolicyDelete,
PersistentVolumeClaimTemplate: solrv1beta1.PersistentVolumeClaimTemplate{
Spec: corev1.PersistentVolumeClaimSpec{
Resources: corev1.VolumeResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse("5Gi"),
},
},
},
},
},
}
})
FIt("removes the finalizer even though the provided ConfigMap no longer exists", func(ctx context.Context) {
By("waiting for the storage finalizer to be added")
expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(ContainElement(util.SolrStorageFinalizer), "Storage finalizer was not added")
})
expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())

By("deleting the provided ConfigMap before the SolrCloud, as Helm can do")
Expect(k8sClient.Delete(ctx, providedConfigMap)).To(Succeed())

By("deleting the SolrCloud")
Expect(k8sClient.Delete(ctx, solrCloud)).To(Succeed(), "Could not delete solrCloud")

Eventually(func() error {
return k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), &solrv1beta1.SolrCloud{})
}).Should(MatchError("solrclouds.solr.apache.org \""+solrCloud.Name+"\" not found"), "Cloud has not been deleted, thus the finalizers have not been removed from the object.")
})
})

FContext("Ephemeral Data - Default", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
Expand Down
7 changes: 7 additions & 0 deletions helm/solr-operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ annotations:
url: https://github.com/apache/solr-operator/issues/707
- name: Github PR
url: https://github.com/apache/solr-operator/pull/876
- kind: fixed
description: A SolrCloud that is being deleted no longer gets stuck when its providedConfigMap has already been removed, e.g. by Helm in the same uninstall
links:
- name: Github Issue
url: https://github.com/apache/solr-operator/issues/828
- name: Github PR
url: https://github.com/apache/solr-operator/pull/888
artifacthub.io/images: |
- name: solr-operator
image: apache/solr-operator:v0.10.0-prerelease
Expand Down
Loading