Skip to content

Commit 71493fe

Browse files
authored
forklift: fix race between prime pvc and pod removal (#3280)
* forklift: fix race between prime pvc and pod removal Because there is time until deletionTimestamp is set on the PVC prime, and since the populator pod is deleted first we might reconcile again when the PVC is not yet marked for deletion, causing the populator pod to be recreated. This change reorders the operation, to first wait until the PVC is marked for deletion, and then delete the populator pod. Signed-off-by: Benny Zlotnik <[email protected]> * forklift: add tests Signed-off-by: Benny Zlotnik <[email protected]> --------- Signed-off-by: Benny Zlotnik <[email protected]>
1 parent e9b2077 commit 71493fe

File tree

2 files changed

+152
-27
lines changed

2 files changed

+152
-27
lines changed

pkg/controller/populators/forklift-populator.go

+33-21
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func addWatchers(mgr manager.Manager, c controller.Controller, log logr.Logger,
136136
}
137137

138138
// Check if the owner is a PVC prime
139-
if !isPVCPrimeForkliftKind(pvcPrime) && pvcPrime.DeletionTimestamp == nil {
139+
if !isPVCPrimeForkliftKind(pvcPrime) || pvcPrime.DeletionTimestamp != nil {
140140
return nil
141141
}
142142

@@ -188,11 +188,6 @@ func (r *ForkliftPopulatorReconciler) reconcile(ctx context.Context, req reconci
188188
return reconcile.Result{}, err
189189
}
190190

191-
if pvc.Status.Phase == corev1.ClaimBound {
192-
r.log.Info("PVC is bound, skipping...", "pvc", pvc.Name)
193-
return reconcile.Result{}, nil
194-
}
195-
196191
// We first perform the common reconcile steps.
197192
// We should only continue if we get a valid PVC'
198193
pvcPrime, err := r.reconcileCommon(pvc, populator, log)
@@ -212,6 +207,10 @@ func (r *ForkliftPopulatorReconciler) reconcile(ctx context.Context, req reconci
212207
res, err = r.reconcileCleanup(pvcPrime)
213208
}
214209

210+
if pvcPrime.DeletionTimestamp != nil {
211+
res, err = r.deletePopulatorPod(fmt.Sprintf("%s-%s", populatorPodPrefix, pvc.UID), pvc, pvcPrime)
212+
}
213+
215214
return res, err
216215
}
217216

@@ -365,21 +364,6 @@ func (r *ForkliftPopulatorReconciler) reconcileTargetPVC(pvc, pvcPrime *corev1.P
365364
r.recorder.Eventf(pvc, corev1.EventTypeNormal, importSucceeded, messageImportSucceeded, pvc.Name)
366365
}
367366

368-
if pod.DeletionTimestamp == nil && cc.ShouldDeletePod(pvcPrime) {
369-
if err := r.client.Delete(context.TODO(), &corev1.Pod{
370-
ObjectMeta: metav1.ObjectMeta{
371-
Name: podName,
372-
Namespace: pvc.GetNamespace(),
373-
},
374-
}); err != nil {
375-
if k8serrors.IsNotFound(err) {
376-
return reconcile.Result{}, nil
377-
}
378-
379-
return reconcile.Result{}, err
380-
}
381-
}
382-
383367
return reconcile.Result{}, nil
384368
}
385369

@@ -578,6 +562,34 @@ func (r *ForkliftPopulatorReconciler) createPopulatorPod(pvcPrime, pvc *corev1.P
578562
return nil
579563
}
580564

565+
func (r *ForkliftPopulatorReconciler) deletePopulatorPod(podName string, pvc *corev1.PersistentVolumeClaim, pvcPrime *corev1.PersistentVolumeClaim) (reconcile.Result, error) {
566+
if !cc.ShouldDeletePod(pvcPrime) {
567+
r.log.V(3).Info("Skipping populator pod deletion", "pod", podName)
568+
return reconcile.Result{}, nil
569+
}
570+
571+
pod := &corev1.Pod{}
572+
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: pvc.GetNamespace()}, pod); err != nil {
573+
if k8serrors.IsNotFound(err) {
574+
return reconcile.Result{}, nil
575+
}
576+
}
577+
578+
if pod.DeletionTimestamp == nil {
579+
r.log.V(3).Info("Deleting populator pod", "pod", pod.Name)
580+
if err := r.client.Delete(context.TODO(), &corev1.Pod{
581+
ObjectMeta: metav1.ObjectMeta{
582+
Name: podName,
583+
Namespace: pvc.GetNamespace(),
584+
},
585+
}); err != nil {
586+
return reconcile.Result{}, err
587+
}
588+
}
589+
590+
return reconcile.Result{}, nil
591+
}
592+
581593
func makePopulatePodSpec(pvcPrimeName, secretName string) corev1.PodSpec {
582594
return corev1.PodSpec{
583595
Containers: []corev1.Container{

pkg/controller/populators/forklift-populator_test.go

+119-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/url"
99
"strconv"
1010
"strings"
11+
"time"
1112

1213
. "github.com/onsi/ginkgo/v2"
1314
. "github.com/onsi/gomega"
@@ -16,6 +17,7 @@ import (
1617

1718
corev1 "k8s.io/api/core/v1"
1819
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
20+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1921
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2123
"k8s.io/apimachinery/pkg/runtime"
@@ -42,20 +44,25 @@ var (
4244
var _ = Describe("Forklift populator tests", func() {
4345
var (
4446
reconciler *ForkliftPopulatorReconciler
47+
recorder *record.FakeRecorder
4548
)
4649

50+
BeforeEach(func() {
51+
recorder = nil
52+
})
53+
54+
AfterEach(func() {
55+
if recorder != nil {
56+
close(recorder.Events)
57+
}
58+
})
59+
4760
const (
4861
samplePopulatorName = "forklift-populator-test"
4962
targetPvcName = "test-forklift-populator-pvc"
5063
scName = "testsc"
5164
)
5265

53-
AfterEach(func() {
54-
if reconciler != nil && reconciler.recorder != nil {
55-
close(reconciler.recorder.(*record.FakeRecorder).Events)
56-
}
57-
})
58-
5966
sc := CreateStorageClassWithProvisioner(scName, map[string]string{
6067
AnnDefaultStorageClass: "true",
6168
}, map[string]string{}, "csi-plugin")
@@ -66,6 +73,7 @@ var _ = Describe("Forklift populator tests", func() {
6673
Name: PVCPrimeName(pvc),
6774
Namespace: pvc.Namespace,
6875
Annotations: annotations,
76+
Finalizers: []string{"test/finalizer"},
6977
},
7078
Spec: corev1.PersistentVolumeClaimSpec{
7179
AccessModes: pvc.Spec.AccessModes,
@@ -402,6 +410,111 @@ var _ = Describe("Forklift populator tests", func() {
402410
Expect(err).ToNot(HaveOccurred())
403411
Expect(targetPvc.Annotations[AnnPopulatorProgress]).To(BeEquivalentTo("13.45%"))
404412
})
413+
414+
It("should remove the populator pod after pvcPrime is marked for deletion", func() {
415+
targetPvc := CreatePvcInStorageClass(targetPvcName, metav1.NamespaceDefault, &sc.Name, nil, nil, corev1.ClaimPending)
416+
targetPvc.Spec.DataSourceRef = dataSourceRef
417+
pvcPrime := getPVCPrime(targetPvc, make(map[string]string))
418+
pvcPrime.DeletionTimestamp = &metav1.Time{Time: time.Now()}
419+
420+
populatorPod := getPopulatorPod(targetPvc, pvcPrime)
421+
422+
By("Reconcile")
423+
reconciler = createForkliftPopulatorReconciler(targetPvc, pvcPrime, sc, populatorPod)
424+
425+
result, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: targetPvcName, Namespace: metav1.NamespaceDefault}})
426+
Expect(err).To(Not(HaveOccurred()))
427+
Expect(result).To(Not(BeNil()))
428+
429+
By("Checking if the populator pod is deleted")
430+
pod := &corev1.Pod{}
431+
err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: populatorPod.Name, Namespace: populatorPod.Namespace}, pod)
432+
Expect(err).To(HaveOccurred())
433+
Expect(k8serrors.IsNotFound(err)).To(BeTrue())
434+
})
435+
436+
It("should create the populator pod with expected specifications", func() {
437+
targetPvc := CreatePvcInStorageClass(targetPvcName, metav1.NamespaceDefault, &sc.Name, nil, nil, corev1.ClaimPending)
438+
targetPvc.Spec.DataSourceRef = dataSourceRef
439+
pvcPrime := getPVCPrime(targetPvc, make(map[string]string))
440+
441+
By("Reconcile")
442+
reconciler = createForkliftPopulatorReconciler(targetPvc, pvcPrime, sc, ovirtCr)
443+
444+
// Call createPopulatorPod directly
445+
err := reconciler.createPopulatorPod(pvcPrime, targetPvc)
446+
Expect(err).To(Not(HaveOccurred()))
447+
448+
By("Checking if the populator pod is created with the expected specifications")
449+
pod := &corev1.Pod{}
450+
podName := fmt.Sprintf("%s-%s", populatorPodPrefix, targetPvc.UID)
451+
err = reconciler.client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: targetPvc.Namespace}, pod)
452+
Expect(err).To(Not(HaveOccurred()))
453+
Expect(pod.Name).To(Equal(podName))
454+
Expect(pod.Namespace).To(Equal(targetPvc.Namespace))
455+
Expect(pod.Spec.Containers).To(HaveLen(1))
456+
container := pod.Spec.Containers[0]
457+
Expect(container.Name).To(Equal("populate"))
458+
Expect(container.Image).To(Equal(reconciler.ovirtPopulatorImage))
459+
Expect(container.Command).To(Equal([]string{"ovirt-populator"}))
460+
Expect(container.Args).To(ContainElements(
461+
fmt.Sprintf("--owner-uid=%s", string(targetPvc.UID)),
462+
fmt.Sprintf("--pvc-size=%d", targetPvc.Spec.Resources.Requests.Storage().Value()),
463+
"--volume-path=/mnt/disk.img",
464+
"--secret-name=ovirt-engine-secret",
465+
"--disk-id=12345678-1234-1234-1234-123456789012",
466+
"--engine-url=https://ovirt-engine.example.com",
467+
))
468+
})
469+
470+
It("should correctly identify a PVC as Forklift kind", func() {
471+
validPVC := &corev1.PersistentVolumeClaim{
472+
ObjectMeta: metav1.ObjectMeta{
473+
Name: "valid-pvc",
474+
Namespace: metav1.NamespaceDefault,
475+
},
476+
Spec: corev1.PersistentVolumeClaimSpec{
477+
DataSourceRef: &corev1.TypedObjectReference{
478+
APIGroup: &apiGroup,
479+
Kind: "OvirtVolumePopulator",
480+
Name: "sample-populator",
481+
},
482+
},
483+
}
484+
485+
invalidPVC := &corev1.PersistentVolumeClaim{
486+
ObjectMeta: metav1.ObjectMeta{
487+
Name: "invalid-pvc",
488+
Namespace: metav1.NamespaceDefault,
489+
},
490+
Spec: corev1.PersistentVolumeClaimSpec{
491+
DataSourceRef: &corev1.TypedObjectReference{
492+
APIGroup: &apiGroup,
493+
Kind: "UnknownPopulator",
494+
Name: "sample-populator",
495+
},
496+
},
497+
}
498+
499+
noDataSourceRefPVC := &corev1.PersistentVolumeClaim{
500+
ObjectMeta: metav1.ObjectMeta{
501+
Name: "no-datasource-pvc",
502+
Namespace: metav1.NamespaceDefault,
503+
},
504+
}
505+
506+
By("Validating PVC with correct DataSourceRef")
507+
isValid := isPVCForkliftKind(validPVC)
508+
Expect(isValid).To(BeTrue())
509+
510+
By("Validating PVC with incorrect DataSourceRef kind")
511+
isInvalid := isPVCForkliftKind(invalidPVC)
512+
Expect(isInvalid).To(BeFalse())
513+
514+
By("Validating PVC with no DataSourceRef")
515+
isNoDataSourceRef := isPVCForkliftKind(noDataSourceRefPVC)
516+
Expect(isNoDataSourceRef).To(BeFalse())
517+
})
405518
})
406519
})
407520

0 commit comments

Comments
 (0)