-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add modifycontroller SyncPVC unit tests #447
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ func TestController(t *testing.T) { | |
}{ | ||
{ | ||
name: "Modify called", | ||
pvc: createTestPVC(pvcName, "target-vac" /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pvc: createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pv: basePV, | ||
vacExists: true, | ||
callCSIModify: true, | ||
|
@@ -61,57 +61,13 @@ func TestController(t *testing.T) { | |
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
// Setup | ||
client := csi.NewMockClient("foo", true, true, true, true, true, false) | ||
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) | ||
if test.vacExists { | ||
initialObjects = append(initialObjects, targetVacObject) | ||
} | ||
|
||
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, false, driverName) | ||
if err != nil { | ||
t.Fatalf("Test %s: Unable to create modifier: %v", test.name, err) | ||
} | ||
|
||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) | ||
controller := NewModifyController(driverName, | ||
csiModifier, kubeClient, | ||
time.Second, false, informerFactory, | ||
workqueue.DefaultControllerRateLimiter()) | ||
client := csi.NewMockClient(testDriverName, true, true, true, true, true, false) | ||
|
||
ctrlInstance, _ := controller.(*modifyController) | ||
|
||
stopCh := make(chan struct{}) | ||
informerFactory.Start(stopCh) | ||
|
||
ctx := context.TODO() | ||
initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject} | ||
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects) | ||
defer ctx.Done() | ||
go controller.Run(1, ctx) | ||
|
||
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) | ||
} | ||
} | ||
time.Sleep(time.Second * 2) | ||
err = ctrlInstance.modifyPVC(test.pvc, test.pv) | ||
|
||
_ = ctrlInstance.modifyPVC(test.pvc, test.pv) | ||
|
||
modifyCallCount := client.GetModifyCount() | ||
if test.callCSIModify && modifyCallCount == 0 { | ||
|
@@ -138,14 +94,14 @@ func TestModifyPVC(t *testing.T) { | |
}{ | ||
{ | ||
name: "Modify succeeded", | ||
pvc: createTestPVC(pvcName, "target-vac" /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pvc: createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pv: basePV, | ||
modifyFailure: false, | ||
expectFailure: false, | ||
}, | ||
{ | ||
name: "Modify failed", | ||
pvc: createTestPVC(pvcName, "target-vac" /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pvc: createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/), | ||
pv: basePV, | ||
modifyFailure: true, | ||
expectFailure: true, | ||
|
@@ -154,77 +110,152 @@ func TestModifyPVC(t *testing.T) { | |
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
client := csi.NewMockClient("mock", true, true, true, true, true, false) | ||
client := csi.NewMockClient(testDriverName, true, true, true, true, true, false) | ||
if test.modifyFailure { | ||
client.SetModifyFailed() | ||
} | ||
driverName, _ := client.GetDriverName(context.TODO()) | ||
|
||
initialObjects := []runtime.Object{} | ||
if test.pvc != nil { | ||
initialObjects = append(initialObjects, test.pvc) | ||
initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject} | ||
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects) | ||
defer ctx.Done() | ||
|
||
_, _, err, _ := ctrlInstance.modify(test.pvc, test.pv) | ||
|
||
if test.expectFailure && err == nil { | ||
t.Errorf("for %s expected error got nothing", test.name) | ||
} | ||
if test.pv != nil { | ||
test.pv.Spec.PersistentVolumeSource.CSI.Driver = driverName | ||
initialObjects = append(initialObjects, test.pv) | ||
|
||
if !test.expectFailure { | ||
if err != nil { | ||
t.Errorf("for %s, unexpected error: %v", test.name, err) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// existing vac set in the pvc and pv | ||
initialObjects = append(initialObjects, testVacObject) | ||
// new vac used in modify volume | ||
initialObjects = append(initialObjects, targetVacObject) | ||
func TestSyncPVC(t *testing.T) { | ||
basePVC := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/) | ||
basePV := createTestPV(1, pvcName, pvcNamespace, "foobaz" /*pvcUID*/, &fsVolumeMode, testVac) | ||
|
||
kubeClient, informerFactory := fakeK8s(initialObjects) | ||
pvInformer := informerFactory.Core().V1().PersistentVolumes() | ||
pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() | ||
vacInformer := informerFactory.Storage().V1beta1().VolumeAttributesClasses() | ||
otherDriverPV := createTestPV(1, pvcName, pvcNamespace, "foobaz" /*pvcUID*/, &fsVolumeMode, testVac) | ||
otherDriverPV.Spec.PersistentVolumeSource.CSI.Driver = "some-other-driver" | ||
|
||
csiModifier, err := modifier.NewModifierFromClient(client, 15*time.Second, kubeClient, informerFactory, false, driverName) | ||
if err != nil { | ||
t.Fatalf("Test %s: Unable to create modifier: %v", test.name, err) | ||
} | ||
unboundPVC := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/) | ||
unboundPVC.Status.Phase = v1.ClaimPending | ||
|
||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeAttributesClass, true) | ||
controller := NewModifyController(driverName, | ||
csiModifier, kubeClient, | ||
time.Second, false, informerFactory, | ||
workqueue.DefaultControllerRateLimiter()) | ||
pvcWithUncreatedPV := createTestPVC(pvcName, targetVac /*vacName*/, testVac /*curVacName*/, testVac /*targetVacName*/) | ||
pvcWithUncreatedPV.Spec.VolumeName = "" | ||
|
||
ctrlInstance, _ := controller.(*modifyController) | ||
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, | ||
}, | ||
} | ||
|
||
stopCh := make(chan struct{}) | ||
informerFactory.Start(stopCh) | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
client := csi.NewMockClient(testDriverName, true, true, true, true, true, false) | ||
|
||
ctx := context.TODO() | ||
initialObjects := []runtime.Object{test.pvc, test.pv, testVacObject, targetVacObject} | ||
ctrlInstance, ctx := setupFakeK8sEnvironment(t, client, initialObjects) | ||
defer ctx.Done() | ||
go controller.Run(1, ctx) | ||
|
||
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) | ||
} | ||
} | ||
|
||
time.Sleep(time.Second * 2) | ||
|
||
_, _, err, _ = ctrlInstance.modify(test.pvc, test.pv) | ||
err := ctrlInstance.syncPVC(pvcNamespace + "/" + pvcName) | ||
if err != nil { | ||
t.Errorf("for %s, unexpected error: %v", test.name, err) | ||
} | ||
|
||
if test.expectFailure && err == nil { | ||
t.Errorf("for %s expected error got nothing", test.name) | ||
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.expectFailure { | ||
if err != nil { | ||
t.Errorf("for %s, unexpected error: %v", test.name, err) | ||
} | ||
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) | ||
|
||
/* Create fake kubeClient, Informers, and ModifyController */ | ||
kubeClient, informerFactory := fakeK8s(initialObjects) | ||
pvInformer := informerFactory.Core().V1().PersistentVolumes() | ||
pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() | ||
vacInformer := informerFactory.Storage().V1beta1().VolumeAttributesClasses() | ||
|
||
driverName, _ := client.GetDriverName(context.TODO()) | ||
|
||
csiModifier, err := modifier.NewModifierFromClient(client, 15*time.Second, kubeClient, informerFactory, false, driverName) | ||
if err != nil { | ||
t.Fatalf("Test %s: Unable to create modifier: %v", t.Name(), err) | ||
} | ||
|
||
controller := NewModifyController(driverName, | ||
csiModifier, kubeClient, | ||
0, false, informerFactory, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that I have set resync period to 0 because shared_informer does not support resyncing:
This also let us cut out any sleeps in unit tests (instant tests!) |
||
workqueue.DefaultControllerRateLimiter()) | ||
|
||
/* Start informers and ModifyController*/ | ||
stopCh := make(chan struct{}) | ||
informerFactory.Start(stopCh) | ||
|
||
ctx := context.TODO() | ||
go controller.Run(1, ctx) | ||
|
||
/* Add initial objects to informer caches (TODO Q confirm this is true/needed?) */ | ||
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", t.Name(), obj) | ||
} | ||
} | ||
|
||
ctrlInstance, _ := controller.(*modifyController) | ||
|
||
return ctrlInstance, ctx | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a code smell.
Happy to attempt to try to refactor resizer's testutils to implement Builder pattern to prevent by allowing developers to use
MakePVC().WithXXX().WithYYY()
style in the test-cases themselves, similar to what k/k has.This work was started in #402, but was pushed back to once all-in-one sidecar project is here.