Skip to content

Commit

Permalink
add unit tests for clone controller (kubevirt#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
screeley44 authored and jeffvance committed Jul 10, 2018
1 parent bef0092 commit 59005c0
Show file tree
Hide file tree
Showing 5 changed files with 1,275 additions and 0 deletions.
346 changes: 346 additions & 0 deletions pkg/controller/clone-controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
package controller

import (
"fmt"
"reflect"
"testing"

"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
k8sfake "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
k8stesting "k8s.io/client-go/tools/cache/testing"
. "kubevirt.io/containerized-data-importer/pkg/common"
)

func TestNewCloneController(t *testing.T) {
type args struct {
client kubernetes.Interface
pvcInformer cache.SharedIndexInformer
podInformer cache.SharedIndexInformer
cloneImage string
pullPolicy string
verbose string
}
// Set up environment
myclient := k8sfake.NewSimpleClientset()
pvcSource := k8stesting.NewFakePVCControllerSource()
podSource := k8stesting.NewFakeControllerSource()

// create informers
pvcInformer := cache.NewSharedIndexInformer(pvcSource, nil, DEFAULT_RESYNC_PERIOD, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
podInformer := cache.NewSharedIndexInformer(podSource, nil, DEFAULT_RESYNC_PERIOD, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})

tests := []struct {
name string
args args
want *CloneController
}{
{
name: "expect controller to be created with expected informers",
args: args{myclient, pvcInformer, podInformer, CLONER_DEFAULT_IMAGE, "Always", "-v=5"},
want: &CloneController{myclient, nil, nil, pvcInformer, podInformer, "test/image", "Always", "-v=5"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewCloneController(tt.args.client, tt.args.pvcInformer, tt.args.podInformer, tt.args.cloneImage, tt.args.pullPolicy, tt.args.verbose)
if got == nil {
t.Errorf("NewCloneController() not created - %v", got)
}
if !reflect.DeepEqual(got.podInformer, tt.want.podInformer) {
t.Errorf("NewCloneController() podInformer = %v, want %v", got.podInformer, tt.want.podInformer)
}
if !reflect.DeepEqual(got.pvcInformer, tt.want.pvcInformer) {
t.Errorf("NewCloneController() pvcInformer = %v, want %v", got.pvcInformer, tt.want.pvcInformer)
}
// queues are generated from the controller
if got.pvcQueue == nil {
t.Errorf("NewCloneController() pvcQueue was not generated properly = %v", got.pvcQueue)
}
if got.podQueue == nil {
t.Errorf("NewCloneController() podQueue was not generated properly = %v", got.podQueue)
}
})
}
}

func TestCloneController_ProcessNextPodItem(t *testing.T) {
//create pod and pvc
pvcWithEndPointAnno := createPvc("testPvcWithEndPointAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
podWithCdiAnno := createPod(pvcWithEndPointAnno, DataVolName)

//create and run the informers
c, _, _, err := createCloneController(pvcWithEndPointAnno, podWithCdiAnno, "default")
if err != nil {
t.Errorf("CloneController.ProcessNextPodItem() failed to initialize fake controller error = %v", err)
return
}
tests := []struct {
name string
want bool
}{
{
name: "successfully process pod",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := c.ProcessNextPodItem(); got != tt.want {
t.Errorf("CloneController.ProcessNextPodItem() = %v, want %v", got, tt.want)
}
})
}
}

func TestCloneController_processPodItem(t *testing.T) {
//create pod and pvc
const stageCount = 3

//Generate Staging Specs
// dictates what NS the spec is actually created in
stageNs := make([]string, stageCount)
stageNs[0] = "default"
stageNs[1] = "default"
stageNs[2] = "test"

// create pvc specs
stagePvcs := make([]*v1.PersistentVolumeClaim, stageCount)
stagePvcs[0] = createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test", AnnCloneRequest: "default/golden-pvc"}, nil)
stagePvcs[1] = createPvc("testPvc2", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
stagePvcs[2] = createPvc("testPvc3", "default", map[string]string{AnnEndpoint: "http://test"}, nil)

// create pod specs with pvc reference
stagePods := make([]*v1.Pod, stageCount)
stagePods[0] = createPod(stagePvcs[0], ImagePathName)
stagePods[1] = createPod(stagePvcs[1], "myvolume")
stagePods[2] = createPod(stagePvcs[2], ImagePathName)

//create and run the informers passing back v1 processed pods
c, _, pods, err := createCloneControllerMultiObject(stagePvcs, stagePods, stageNs)
if err != nil {
t.Errorf("Controller.ProcessPodItem() failed to initialize fake controller error = %v", err)
return
}

type args struct {
pod *v1.Pod
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "successfully process pod",
args: args{pods[0]},
wantErr: false,
},
{
name: "expect error when processing pod with no image-path volume",
args: args{pods[1]},
wantErr: true,
},
{
name: "expect error when proessing pvc with different NS than expected pod",
args: args{pods[2]},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := c.processPodItem(tt.args.pod); (err != nil) != tt.wantErr {
t.Errorf("Controller.processPodItem() error = %v, wantErr %v, Pod Name = %v, Pod ClaimRef = %v", err, tt.wantErr, tt.args.pod.Name, tt.args.pod.Spec.Volumes)
}
})
}
}

func TestCloneController_ProcessNextPvcItem(t *testing.T) {
//create pod and pvc
const stageCount = 4

//Generate Staging Specs
// dictates what NS the spec is actually created in
stageNs := make([]string, stageCount)
stageNs[0] = "default"
stageNs[1] = "default"
stageNs[2] = "default"
stageNs[3] = "default"

// create pvc specs
stagePvcs := make([]*v1.PersistentVolumeClaim, stageCount)
stagePvcs[0] = createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
stagePvcs[1] = createPvc("testPvc2", "default", map[string]string{AnnEndpoint: "http://test", AnnImportPod: "cdi-importer-pod"}, nil)
stagePvcs[2] = createPvc("testPvc3", "default", nil, nil)
stagePvcs[3] = createPvc("testPvc4", "default", map[string]string{AnnEndpoint: "http://test"}, nil)

// create pod specs with pvc reference
stagePods := make([]*v1.Pod, stageCount)
stagePods[0] = createPod(stagePvcs[0], DataVolName)
stagePods[1] = createPod(stagePvcs[1], DataVolName)
stagePods[2] = createPod(stagePvcs[2], DataVolName)
stagePods[3] = createPod(stagePvcs[3], DataVolName)

//create and run queues and informers
c, _, _, err := createCloneControllerMultiObject(stagePvcs, stagePods, stageNs)
if err != nil {
t.Errorf("Controller.ProcessNextPvcItem() failed to initialize fake controller error = %v", err)
return
}

tests := []struct {
name string
want bool
}{
{
name: "expect successful processing of queue",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := c.ProcessNextPvcItem(); got != tt.want {
t.Errorf("Controller.ProcessNextPvcItem() = %v, want %v", got, tt.want)
}
})
}
}

func TestCloneController_processPvcItem(t *testing.T) {
const (
stageCount = 4
srcNs = "test"
srcPvc = "testPvcSource"
)
srcCloneRequestValue := srcNs + "/" + srcPvc

//Generate Staging Specs
// create pvc specs
stagePvcs := make([]*v1.PersistentVolumeClaim, stageCount)
stagePvcs[0] = createPvc(srcPvc, srcNs, map[string]string{}, nil)
stagePvcs[1] = createPvc("testPvc1", "default", map[string]string{AnnCloneRequest: srcCloneRequestValue}, nil)
stagePvcs[2] = createPvc("testPvc2", "default", map[string]string{AnnEndpoint: "http://test", AnnCloningPods: "cdi-cloner-pod", AnnCloneRequest: "default/testPvcSource"}, nil)
stagePvcs[3] = createPvc("testPvc3", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
//create and run queues and informers
c, _, _, err := createCloneControllerMultiObject(stagePvcs, nil, nil)
if err != nil {
t.Errorf("Controller.ProcessPvcItem() failed to initialize fake controller error = %v", err)
return
}
type args struct {
pvc *v1.PersistentVolumeClaim
}
tests := []struct {
name string
args args
podSourceName string
podTargetName string
wantErr bool
wantFind bool
}{
{
name: "process pvc and expect clone pods to be created",
args: args{stagePvcs[1]},
podSourceName: fmt.Sprintf("%s-", CLONER_SOURCE_PODNAME),
podTargetName: fmt.Sprintf("%s-", CLONER_TARGET_PODNAME),
wantErr: false,
wantFind: true,
},
{
name: "process pvc and do not create cloner pods because AnnCloningPods annotation exists",
args: args{stagePvcs[2]},
podSourceName: fmt.Sprintf("%s-", CLONER_SOURCE_PODNAME),
podTargetName: fmt.Sprintf("%s-", CLONER_TARGET_PODNAME),
wantErr: false,
wantFind: false,
},
{
name: "should not process pvc as it does not contain correct cloner request annotation",
args: args{stagePvcs[3]},
podSourceName: fmt.Sprintf("%s-", CLONER_SOURCE_PODNAME),
podTargetName: fmt.Sprintf("%s-", CLONER_TARGET_PODNAME),
wantErr: true,
wantFind: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := c.processPvcItem(tt.args.pvc)
if err == nil && tt.wantErr {
t.Errorf("Controller.processPvcItem() expected error but did not get one %v", tt.wantErr)
} else if err != nil && tt.wantErr {
// good case
} else {
// retrieve the pods from respective namespaces
gotSource, err := c.clientset.CoreV1().Pods(srcNs).List(metav1.ListOptions{})
if err != nil {
// could not get pods
t.Errorf("Controller.processPvcItem() could not retrieve pods from API")
return
}
gotTarget, err := c.clientset.CoreV1().Pods(tt.args.pvc.Namespace).List(metav1.ListOptions{})
if err != nil {
// could not get pods
t.Errorf("Controller.processPvcItem() could not retrieve pods from API")
return
}
// expect 2 pods to be created - 1 in test namespace and 1 in default namespace
if len(gotSource.Items) != 1 && len(gotTarget.Items) != 1 && tt.wantFind {
t.Errorf("Controller.processPvcItem() did not find both the source and target pods, pod counts = %v - %v in namespaces %s - %s", len(gotSource.Items), len(gotTarget.Items), srcNs, tt.args.pvc.Namespace)
return
}
if gotSource.Items[0].GenerateName != tt.podSourceName && tt.wantFind {
t.Errorf("Controller.processPvcItem() could not retrieve Source Pod %v", gotSource.Items[0].GenerateName)
}
if gotTarget.Items[0].GenerateName != tt.podTargetName && tt.wantFind {
t.Errorf("Controller.processPvcItem() could not retrieve Target Pod %v", gotTarget.Items[0].GenerateName)
}
}
})
}
}

func TestCloneController_forgetKey(t *testing.T) {
//create staging pvc and pod
pvcWithEndPointAnno := createPvc("testPvcWithEndPointAnno", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
podWithCdiAnno := createPod(pvcWithEndPointAnno, DataVolName)

//run the informers
c, _, _, err := createCloneController(pvcWithEndPointAnno, podWithCdiAnno, "default")
if err != nil {
t.Errorf("Controller.forgetKey() failed to initialize fake controller error = %v", err)
return
}
key, shutdown := c.pvcQueue.Get()
if shutdown {
t.Errorf("Controller.forgetKey() failed to retrieve key from pvcQueue")
return
}
defer c.pvcQueue.Done(key)

type args struct {
key interface{}
msg string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "successfully forget key",
args: args{key, "test of forgetKey func"},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := c.forgetKey(tt.args.key, tt.args.msg); got != tt.want {
t.Errorf("Controller.forgetKey() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 59005c0

Please sign in to comment.