Skip to content

Commit

Permalink
Add modifycontroller SyncPVC unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSirenko committed Nov 12, 2024
1 parent babf8dc commit e5067f5
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 54 deletions.
89 changes: 83 additions & 6 deletions pkg/modifycontroller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func TestController(t *testing.T) {
client := csi.NewMockClient(testDriverName, true, true, true, true, true, false)

initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject}
ctrlInstance := setupFakeK8sEnvironment(t, client, initialObjects)
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects)
defer ctx.Done()

_ = ctrlInstance.modifyPVC(test.pvc, test.pv)

Expand Down Expand Up @@ -115,7 +116,8 @@ func TestModifyPVC(t *testing.T) {
}

initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject}
ctrlInstance := setupFakeK8sEnvironment(t, client, initialObjects)
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects)
defer ctx.Done()

_, _, err, _ := ctrlInstance.modify(test.pvc, test.pv)

Expand All @@ -132,8 +134,84 @@ func TestModifyPVC(t *testing.T) {
}
}

// setupFakeK8sEnvironment
func setupFakeK8sEnvironment(t *testing.T, client *csi.MockClient, initialObjects []runtime.Object) *modifyController {
func TestSyncPVC(t *testing.T) {
basePVC := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/)
basePV := createTestPV(1, pvcName, pvcNamespace, "foobaz" /*pvcUID*/, &fsVolumeMode, testVac)

otherDriverPV := createTestPV(1, pvcName, pvcNamespace, "foobaz" /*pvcUID*/, &fsVolumeMode, testVac)
otherDriverPV.Spec.PersistentVolumeSource.CSI.Driver = "some-other-driver"

unboundPVC := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/)
unboundPVC.Status.Phase = v1.ClaimPending

pvcWithUncreatedPV := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/)
pvcWithUncreatedPV.Spec.VolumeName = ""

tests := []struct {
name string
pvc *v1.PersistentVolumeClaim
pv *v1.PersistentVolume
callCSIModify bool
}{
{
name: "Should execute ModifyVolume operation when PVC's VAC changes",
pvc: basePVC,
pv: basePV,
callCSIModify: true,
},
{
name: "Should NOT modify if PVC managed by another CSI Driver",
pvc: basePVC,
pv: otherDriverPV,
callCSIModify: false,
},
{
name: "Should NOT modify if PVC has empty Spec.VACName",
pvc: createTestPVC(pvcName, "" /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/),
pv: basePV,
callCSIModify: false,
},
{
name: "Should NOT modify if PVC not in bound state",
pvc: unboundPVC,
pv: basePV,
callCSIModify: false,
},
{
name: "Should NOT modify if PVC's PV not created yet",
pvc: pvcWithUncreatedPV,
pv: basePV,
callCSIModify: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := csi.NewMockClient(testDriverName, true, true, true, true, true, false)

initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject}
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects)
defer ctx.Done()

err := ctrlInstance.syncPVC(pvcNamespace + "/" + pvcName)
if err != nil {
t.Errorf("for %s, unexpected error: %v", test.name, err)
}

modifyCallCount := client.GetModifyCount()
if test.callCSIModify && modifyCallCount == 0 {
t.Fatalf("for %s: expected csi modify call, no csi modify call was made", test.name)
}

if !test.callCSIModify && modifyCallCount > 0 {
t.Fatalf("for %s: expected no csi modify call, received csi modify request", test.name)
}
})
}
}

// setupFakeK8sEnvironment creates fake K8s environment and starts Informers and ModifyController
func setupFakeK8sEnvironment(t *testing.T, client *csi.MockClient, initialObjects []runtime.Object) (*modifyController, context.Context) {
t.Helper()

featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true)
Expand Down Expand Up @@ -161,7 +239,6 @@ func setupFakeK8sEnvironment(t *testing.T, client *csi.MockClient, initialObject
informerFactory.Start(stopCh)

ctx := context.TODO()
defer ctx.Done()
go controller.Run(1, ctx)

/* Add initial objects to informer caches (TODO Q confirm this is true/needed?) */
Expand All @@ -180,5 +257,5 @@ func setupFakeK8sEnvironment(t *testing.T, client *csi.MockClient, initialObject

ctrlInstance, _ := controller.(*modifyController)

return ctrlInstance
return ctrlInstance, ctx
}
52 changes: 4 additions & 48 deletions pkg/modifycontroller/modify_volume_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
package modifycontroller

import (
"context"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/kubernetes-csi/external-resizer/pkg/csi"
"github.com/kubernetes-csi/external-resizer/pkg/features"
"github.com/kubernetes-csi/external-resizer/pkg/modifier"
v1 "k8s.io/api/core/v1"
storagev1beta1 "k8s.io/api/storage/v1beta1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/util/workqueue"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"testing"
)

var (
Expand Down Expand Up @@ -111,50 +103,14 @@ func TestModify(t *testing.T) {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
// Setup
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true)
client := csi.NewMockClient(testDriverName, true, true, true, true, true, test.withExtraMetadata)
driverName, _ := client.GetDriverName(context.TODO())

var initialObjects []runtime.Object
initialObjects = append(initialObjects, test.pvc)
initialObjects = append(initialObjects, test.pv)
// existing vac set in the pvc and pv
initialObjects = append(initialObjects, testVacObject)
initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject}
if test.vacExists {
initialObjects = append(initialObjects, targetVacObject)
}
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects)
defer ctx.Done()

kubeClient, informerFactory := fakeK8s(initialObjects)
pvInformer := informerFactory.Core().V1().PersistentVolumes()
pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
vacInformer := informerFactory.Storage().V1beta1().VolumeAttributesClasses()

csiModifier, err := modifier.NewModifierFromClient(client, 15*time.Second, kubeClient, informerFactory, test.withExtraMetadata, driverName)
if err != nil {
t.Fatalf("Test %s: Unable to create modifier: %v", test.name, err)
}
controller := NewModifyController(driverName,
csiModifier, kubeClient,
time.Second, test.withExtraMetadata, informerFactory,
workqueue.DefaultControllerRateLimiter())

ctrlInstance, _ := controller.(*modifyController)

stopCh := make(chan struct{})
informerFactory.Start(stopCh)

for _, obj := range initialObjects {
switch obj.(type) {
case *v1.PersistentVolume:
pvInformer.Informer().GetStore().Add(obj)
case *v1.PersistentVolumeClaim:
pvcInformer.Informer().GetStore().Add(obj)
case *storagev1beta1.VolumeAttributesClass:
vacInformer.Informer().GetStore().Add(obj)
default:
t.Fatalf("Test %s: Unknown initalObject type: %+v", test.name, obj)
}
}
// Action
pvc, pv, err, modifyCalled := ctrlInstance.modify(test.pvc, test.pv)
// Verify
Expand Down

0 comments on commit e5067f5

Please sign in to comment.