Skip to content

Commit

Permalink
Add unit tests for upload populator
Browse files Browse the repository at this point in the history
Signed-off-by: Shelly Kagan <skagan@redhat.com>
  • Loading branch information
ShellyKa13 committed Apr 20, 2023
1 parent ed6bb6f commit a7e7009
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 1 deletion.
38 changes: 37 additions & 1 deletion pkg/controller/populators/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -14,6 +14,7 @@ go_library(
"//pkg/controller/common:go_default_library",
"//pkg/feature-gates:go_default_library",
"//pkg/util:go_default_library",
"//staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1:go_default_library",
"//vendor/github.com/go-logr/logr:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
Expand All @@ -31,3 +32,38 @@ go_library(
"//vendor/sigs.k8s.io/controller-runtime/pkg/source:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = [
"populators_suite_test.go",
"upload-populator_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/common:go_default_library",
"//pkg/controller/common:go_default_library",
"//pkg/feature-gates:go_default_library",
"//staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1:go_default_library",
"//tests/reporters:go_default_library",
"//vendor/github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1:go_default_library",
"//vendor/github.com/onsi/ginkgo:go_default_library",
"//vendor/github.com/onsi/ginkgo/extensions/table:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/client/fake:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/log:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/log/zap:go_default_library",
"//vendor/sigs.k8s.io/controller-runtime/pkg/reconcile:go_default_library",
],
)
22 changes: 22 additions & 0 deletions pkg/controller/populators/populators_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package populators_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"kubevirt.io/containerized-data-importer/tests/reporters"
)

var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
})

func TestController(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t, "Populators controllers Suite", reporters.NewReporters())
}
297 changes: 297 additions & 0 deletions pkg/controller/populators/upload-populator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
/*
Copyright 2023 The CDI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package populators

import (
"context"
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
"kubevirt.io/containerized-data-importer/pkg/common"
cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
featuregates "kubevirt.io/containerized-data-importer/pkg/feature-gates"
)

const (
testStorageClass = "test-sc"
pvcPrimeUID = "pvcPrimeUID"
)

var (
uploadPopLog = logf.Log.WithName("upload-populator-controller-test")
)

var _ = Describe("Datavolume controller reconcile loop", func() {
DescribeTable("should create PVC prime", func(contentType string) {
pvc := newUploadPopulatorPVC("test-pvc")
volumeUploadSourceCR := newUploadPopulatorCR(contentType)
scName := "test-sc"
sc := cc.CreateStorageClassWithProvisioner(scName, map[string]string{cc.AnnDefaultStorageClass: "true"}, map[string]string{}, "csi-plugin")
r := createUploadPopulatorReconciler(pvc, volumeUploadSourceCR, sc)
_, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())

expectEvent(r, createdPVCPrimeSuccessfully)

pvcPrime, err := r.getPVCPrime(pvc)
Expect(err).ToNot(HaveOccurred())
Expect(pvcPrime).ToNot(BeNil())
Expect(pvcPrime.GetAnnotations()).ToNot(BeNil())
Expect(pvcPrime.GetAnnotations()[cc.AnnImmediateBinding]).To(Equal(""))
Expect(pvcPrime.GetAnnotations()[cc.AnnUploadRequest]).To(Equal(""))
Expect(pvcPrime.GetAnnotations()[cc.AnnContentType]).To(Equal(contentType))
Expect(pvcPrime.GetAnnotations()[cc.AnnPopulatorKind]).To(Equal(cdiv1.VolumeUploadSourceRef))

_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())
updatedPVC := &corev1.PersistentVolumeClaim{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}, updatedPVC)
Expect(updatedPVC.GetAnnotations()).ToNot(BeNil())
Expect(updatedPVC.GetAnnotations()[AnnPVCPrimeName]).To(Equal(pvcPrime.Name))
},
Entry("kubevirt content type", "kubevirt"),
Entry("archive content type", "archive"),
)

It("should rebind PV to target PVC", func() {
pvc := newUploadPopulatorPVC("test-pvc")
pvc.Annotations = make(map[string]string)
pvc.Annotations[AnnPVCPrimeName] = PVCPrimeName(pvc)
uploadPV := uploadPV(pvc)

volumeUploadSourceCR := newUploadPopulatorCR("")
scName := "test-sc"
sc := cc.CreateStorageClassWithProvisioner(scName, map[string]string{cc.AnnDefaultStorageClass: "true"}, map[string]string{}, "csi-plugin")
r := createUploadPopulatorReconciler(pvc, volumeUploadSourceCR, sc, uploadPV)

_, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())
pvcPrime, err := r.getPVCPrime(pvc)
Expect(err).ToNot(HaveOccurred())
pvcPrime.Annotations[cc.AnnPodPhase] = string(corev1.PodSucceeded)
pvcPrime.Spec.VolumeName = "test-pv"
pvcPrime.UID = pvcPrimeUID
err = r.client.Update(context.TODO(), pvcPrime)
Expect(err).ToNot(HaveOccurred())

_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())

updatedPV, err := r.getPV(pvcPrime.Spec.VolumeName)
Expect(err).ToNot(HaveOccurred())

Expect(updatedPV.Spec.ClaimRef.Name).To(Equal("test-pvc"))

expectEvent(r, uploadSucceeded)
})

It("should clean PVCPrime when targetPVC bound", func() {
pvc := newUploadPopulatorPVC("test-pvc")
pvc.Spec.VolumeName = "test-pv"
pvcPrime := newUploadPopulatorPVC(PVCPrimeName(pvc))

volumeUploadSourceCR := newUploadPopulatorCR("")
scName := "test-sc"
sc := cc.CreateStorageClassWithProvisioner(scName, map[string]string{cc.AnnDefaultStorageClass: "true"}, map[string]string{}, "csi-plugin")
r := createUploadPopulatorReconciler(pvc, volumeUploadSourceCR, sc, pvcPrime)

pvcPrime = &corev1.PersistentVolumeClaim{}
pvcPrimeKey := types.NamespacedName{Namespace: pvc.Namespace, Name: PVCPrimeName(pvc)}
err := r.client.Get(context.TODO(), pvcPrimeKey, pvcPrime)
Expect(err).ToNot(HaveOccurred())

_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
pvcPrime = &corev1.PersistentVolumeClaim{}
pvcPrimeKey = types.NamespacedName{Namespace: pvc.Namespace, Name: PVCPrimeName(pvc)}
err = r.client.Get(context.TODO(), pvcPrimeKey, pvcPrime)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
})

It("should wait for selected node annotation in case of wffc", func() {
pvc := newUploadPopulatorPVC("test-pvc")
volumeUploadSourceCR := newUploadPopulatorCR("")
scName := "test-sc"
pvc.Spec.StorageClassName = &scName
sc := cc.CreateStorageClassWithProvisioner(scName, map[string]string{cc.AnnDefaultStorageClass: "true"}, map[string]string{}, "csi-plugin")
bindingMode := storagev1.VolumeBindingWaitForFirstConsumer
sc.VolumeBindingMode = &bindingMode

r := createUploadPopulatorReconciler(pvc, volumeUploadSourceCR, sc)

_, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())
// until added selected node annotation upload
// process shouldn't start
pvcPrime := &corev1.PersistentVolumeClaim{}
pvcPrimeKey := types.NamespacedName{Namespace: pvc.Namespace, Name: PVCPrimeName(pvc)}
err = r.client.Get(context.TODO(), pvcPrimeKey, pvcPrime)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())

pvc.Annotations = make(map[string]string)
pvc.Annotations[cc.AnnSelectedNode] = "node01"
err = r.client.Update(context.TODO(), pvc)
Expect(err).ToNot(HaveOccurred())

_, err = r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-pvc", Namespace: metav1.NamespaceDefault}})
Expect(err).ToNot(HaveOccurred())

expectEvent(r, createdPVCPrimeSuccessfully)

pvcPrime, err = r.getPVCPrime(pvc)
Expect(err).ToNot(HaveOccurred())
Expect(pvcPrime).ToNot(BeNil())
Expect(pvcPrime.Annotations[cc.AnnSelectedNode]).To(Equal("node01"))
})
})

func newUploadPopulatorPVC(name string) *corev1.PersistentVolumeClaim {
apiGroup := cc.AnnAPIGroup
return &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
},
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceName(corev1.ResourceStorage): resource.MustParse("1Gi"),
},
},
DataSourceRef: &corev1.TypedObjectReference{
APIGroup: &apiGroup,
Kind: cdiv1.VolumeUploadSourceRef,
Name: "upload-populator-test",
},
},
}
}

func newUploadPopulatorCR(contentType string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": cdiv1.VolumeUploadSourceRef,
"apiVersion": "cdi.kubevirt.io/v1beta1",
"metadata": map[string]interface{}{
"name": "upload-populator-test",
"namespace": metav1.NamespaceDefault,
},
"spec": map[string]interface{}{
"contentType": contentType,
},
},
}
}

func uploadPV(pvc *corev1.PersistentVolumeClaim) *corev1.PersistentVolume {
return &corev1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pv",
},
Spec: corev1.PersistentVolumeSpec{
ClaimRef: &corev1.ObjectReference{
Namespace: metav1.NamespaceDefault,
Name: PVCPrimeName(pvc),
UID: pvcPrimeUID,
},
PersistentVolumeReclaimPolicy: corev1.PersistentVolumeReclaimDelete,
},
}
}

func expectEvent(r *UploadPopulatorReconciler, expectedEvent string) {
close(r.recorder.(*record.FakeRecorder).Events)
found := false
for event := range r.recorder.(*record.FakeRecorder).Events {
By(event)
if strings.Contains(event, expectedEvent) {
found = true
}
}
Expect(found).To(BeTrue())
}

func createUploadPopulatorReconciler(objects ...runtime.Object) *UploadPopulatorReconciler {
cdiConfig := cc.MakeEmptyCDIConfigSpec(common.ConfigName)
cdiConfig.Status = cdiv1.CDIConfigStatus{
ScratchSpaceStorageClass: testStorageClass,
}
cdiConfig.Spec.FeatureGates = []string{featuregates.HonorWaitForFirstConsumer}

objs := []runtime.Object{}
objs = append(objs, objects...)
objs = append(objs, cdiConfig)

// Register operator types with the runtime scheme.
s := scheme.Scheme
cdiv1.AddToScheme(s)
snapshotv1.AddToScheme(s)
extv1.AddToScheme(s)

objs = append(objs, cc.MakeEmptyCDICR())

builder := fake.NewClientBuilder().
WithScheme(s).
WithRuntimeObjects(objs...)

for _, ia := range getIndexArgs() {
builder = builder.WithIndex(ia.obj, ia.field, ia.extractValue)
}

cl := builder.Build()

rec := record.NewFakeRecorder(10)

// Create a ReconcileMemcached object with the scheme and fake client.
r := &UploadPopulatorReconciler{
ReconcilerBase: ReconcilerBase{
client: cl,
scheme: s,
log: uploadPopLog,
recorder: rec,
featureGates: featuregates.NewFeatureGates(cl),
installerLabels: map[string]string{
common.AppKubernetesPartOfLabel: "testing",
common.AppKubernetesVersionLabel: "v0.0.0-tests",
},
sourceKind: cdiv1.VolumeUploadSourceRef,
},
}
return r
}

0 comments on commit a7e7009

Please sign in to comment.