Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
174 changes: 174 additions & 0 deletions pkg/controller/controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package controller

import (
"context"
"fmt"
"testing"
"time"

"github.com/kubernetes-csi/external-resizer/pkg/csi"
"github.com/kubernetes-csi/external-resizer/pkg/resizer"

"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)

func TestController(t *testing.T) {
for _, test := range []struct {
Name string
PVC *v1.PersistentVolumeClaim
PV *v1.PersistentVolume

CreateObjects bool
NodeResize bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a field here for explicitly testing scenarios where we expect failure? As it is - we only test scenarios which aren't supposed to fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the failure scenarios I saw were either guarding against underlying kube problems (which I don't know how to mock) or resizer problems (which I believe should be handled in the resizer unit tests). Any advice on how of if either of those two categories should be tested would be appreciated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc - mock client supports adding reactors to certain actions on kube client. We could use that to test more custom stuff. For example, I was wondering if for test case where PV should be updated, if we can actually test if PV got updated. That would be awesome.

}{
{
Name: "Invalid key",
PVC: invalidPVC(),
},
{
Name: "PVC not found",
PVC: createPVC(1, 1),
},
{
Name: "PVC doesn't need resize",
PVC: createPVC(1, 1),
CreateObjects: true,
},
{
Name: "PV not found",
PVC: createPVC(2, 1),
CreateObjects: true,
},
{
Name: "PV doesn't need resize",
PVC: createPVC(2, 1),
PV: createPV(2),
CreateObjects: true,
},
{
Name: "Resize PVC, no FS resize",
PVC: createPVC(2, 1),
PV: createPV(1),
CreateObjects: true,
},
{
Name: "Resize PVC with FS resize",
PVC: createPVC(2, 1),
PV: createPV(1),
CreateObjects: true,
NodeResize: true,
},
} {
client := csi.NewMockClient(test.NodeResize, true, true)
driverName, _ := client.GetDriverName(context.TODO())

initialObjects := []runtime.Object{}
if test.CreateObjects {
if test.PVC != nil {
initialObjects = append(initialObjects, test.PVC)
}
if test.PV != nil {
test.PV.Spec.PersistentVolumeSource.CSI.Driver = driverName
initialObjects = append(initialObjects, test.PV)
}
}

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

for _, obj := range initialObjects {
switch obj.(type) {
case *v1.PersistentVolume:
pvInformer.Informer().GetStore().Add(obj)
case *v1.PersistentVolumeClaim:
pvcInformer.Informer().GetStore().Add(obj)
default:
t.Fatalf("Test %s: Unknown initalObject type: %+v", test.Name, obj)
}
}

csiResizer, err := resizer.NewResizerFromClient(client, 15*time.Second, kubeClient, informerFactory)
if err != nil {
t.Fatalf("Test %s: Unable to create resizer: %v", test.Name, err)
}

controller := NewResizeController(driverName, csiResizer, kubeClient, time.Second, informerFactory)
err = controller.(*resizeController).syncPVC(fmt.Sprintf("%s/%s", test.PVC.Namespace, test.PVC.Name))
if err != nil {
t.Fatalf("Test %s: Unexpected error: %v", test.Name, err)
}
}
}

func invalidPVC() *v1.PersistentVolumeClaim {
pvc := createPVC(1, 1)
pvc.ObjectMeta.Name = ""
pvc.ObjectMeta.Namespace = ""

return pvc
}

func quantityGB(i int) resource.Quantity {
q := resource.NewQuantity(int64(i*1024*1024), resource.BinarySI)
return *q
}

func createPVC(requestGB, capacityGB int) *v1.PersistentVolumeClaim {
request := quantityGB(requestGB)
capacity := quantityGB(capacityGB)

return &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "testPVC",
Namespace: "test",
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: map[v1.ResourceName]resource.Quantity{
v1.ResourceStorage: request,
},
},
VolumeName: "testPV",
},
Status: v1.PersistentVolumeClaimStatus{
Phase: v1.ClaimBound,
Capacity: map[v1.ResourceName]resource.Quantity{
v1.ResourceStorage: capacity,
},
},
}
}

func createPV(capacityGB int) *v1.PersistentVolume {
capacity := quantityGB(capacityGB)

return &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "testPV",
},
Spec: v1.PersistentVolumeSpec{
Capacity: map[v1.ResourceName]resource.Quantity{
v1.ResourceStorage: capacity,
},
PersistentVolumeSource: v1.PersistentVolumeSource{
CSI: &v1.CSIPersistentVolumeSource{
Driver: "foo",
VolumeHandle: "foo",
},
},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get this PV to have CSIPersistentVolumeSource field, so as it is supported by the resizer? As it is - mock driver's Expand call is never made because PVs don't have CSI field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

func fakeK8s(objs []runtime.Object) (kubernetes.Interface, informers.SharedInformerFactory) {
client := fake.NewSimpleClientset(objs...)
informerFactory := informers.NewSharedInformerFactory(client, 0)
return client, informerFactory
}
4 changes: 2 additions & 2 deletions pkg/resizer/csi_resizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ func NewResizer(
if err != nil {
return nil, err
}
return newResizer(csiClient, timeout, k8sClient, informerFactory)
return NewResizerFromClient(csiClient, timeout, k8sClient, informerFactory)
}

func newResizer(
func NewResizerFromClient(
csiClient csi.Client,
timeout time.Duration,
k8sClient kubernetes.Interface,
Expand Down
2 changes: 1 addition & 1 deletion pkg/resizer/csi_resizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestNewResizer(t *testing.T) {
} {
client := csi.NewMockClient(c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService)
k8sClient, informerFactory := fakeK8s()
resizer, err := newResizer(client, 0, k8sClient, informerFactory)
resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory)
if err != c.Error {
t.Errorf("Case %d: Unexpected error: wanted %v, got %v", i, c.Error, err)
}
Expand Down