containerized-data-importer/pkg/controller/import-controller_test.go
John Griffith c455577cca Fix lint errors on common package (#449)
This change just updates the common package to pass golint.  Of course
that has some reaching implications into other packages with renaming of
constants.

I've intentionally kept this patch set to the bare minimum for the lint
test, I do think that we should put some effort into our use of
constants (especially those in common.go) in the future.

Ideally we'd declare constants where they're used, and we wouldn't
export them if they don't need to be.  There are some in here that
are only used in one or two packages, and that can be cleaned up pretty
easily however it's likely that there might be plans to use them
elsewhere in the future.
2018-09-10 09:14:09 -04:00

361 lines
11 KiB
Go

package controller
import (
"fmt"
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
kubeinformers "k8s.io/client-go/informers"
k8sfake "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/cache"
. "kubevirt.io/containerized-data-importer/pkg/common"
)
type importFixture struct {
t *testing.T
kubeclient *k8sfake.Clientset
// Objects to put in the store.
pvcLister []*corev1.PersistentVolumeClaim
podLister []*corev1.Pod
// Actions expected to happen on the client.
kubeactions []core.Action
actions []core.Action
// Objects from here preloaded into NewSimpleFake.
kubeobjects []runtime.Object
}
func newImportFixture(t *testing.T) *importFixture {
f := &importFixture{}
f.t = t
f.kubeobjects = []runtime.Object{}
return f
}
func (f *importFixture) newController() *ImportController {
f.kubeclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
podFactory := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
pvcFactory := kubeinformers.NewSharedInformerFactory(f.kubeclient, noResyncPeriodFunc())
podInformer := podFactory.Core().V1().Pods()
pvcInformer := pvcFactory.Core().V1().PersistentVolumeClaims()
c := NewImportController(f.kubeclient,
pvcInformer,
podInformer,
"test/myimage",
"Always",
"5")
for _, pod := range f.podLister {
c.podInformer.GetIndexer().Add(pod)
}
for _, pvc := range f.pvcLister {
c.pvcInformer.GetIndexer().Add(pvc)
}
return c
}
func (f *importFixture) run(pvcName string) {
f.runController(pvcName, true, false, false)
}
func (f *importFixture) runWithExpectation(pvcName string) {
f.runController(pvcName, true, false, true)
}
func (f *importFixture) runExpectError(pvcName string) {
f.runController(pvcName, true, true, false)
}
func (f *importFixture) runController(pvcName string,
startInformers bool,
expectError bool,
withCreateExpectation bool) {
c := f.newController()
if startInformers {
stopCh := make(chan struct{})
defer close(stopCh)
go c.pvcInformer.Run(stopCh)
go c.podInformer.Run(stopCh)
cache.WaitForCacheSync(stopCh, c.pvcInformer.HasSynced)
cache.WaitForCacheSync(stopCh, c.pvcInformer.HasSynced)
}
if withCreateExpectation {
c.expectPodCreate(pvcName)
}
err := c.syncPvc(pvcName)
if !expectError && err != nil {
f.t.Errorf("error syncing pvc: %s: %v", pvcName, err)
} else if expectError && err == nil {
f.t.Error("expected error syncing pvc, got nil")
}
k8sActions := filterImportActions(f.kubeclient.Actions())
for i, action := range k8sActions {
if len(f.kubeactions) < i+1 {
f.t.Errorf("%d unexpected actions: %+v", len(k8sActions)-len(f.kubeactions), k8sActions[i:])
break
}
expectedAction := f.kubeactions[i]
checkImportAction(expectedAction, action, f.t)
}
if len(f.kubeactions) > len(k8sActions) {
f.t.Errorf("%d additional expected actions:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):])
}
}
// checkImportAction verifies that expected and actual actions are equal and both have
// same attached resources
func checkImportAction(expected, actual core.Action, t *testing.T) {
if !(expected.Matches(actual.GetVerb(), actual.GetResource().Resource) && actual.GetSubresource() == expected.GetSubresource()) {
t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expected, actual)
return
}
if reflect.TypeOf(actual) != reflect.TypeOf(expected) {
t.Errorf("Action has wrong type. Expected: %t. Got: %t", expected, actual)
return
}
switch a := actual.(type) {
case core.CreateAction:
e, _ := expected.(core.CreateAction)
expObject := e.GetObject()
object := a.GetObject()
if !reflect.DeepEqual(expObject, object) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintDiff(expObject, object))
}
case core.UpdateAction:
e, _ := expected.(core.UpdateAction)
expObject := e.GetObject()
object := a.GetObject()
if !reflect.DeepEqual(expObject, object) {
t.Errorf("Action %s %s has wrong object\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintDiff(expObject, object))
}
case core.PatchAction:
e, _ := expected.(core.PatchAction)
expPatch := e.GetPatch()
patch := a.GetPatch()
if !reflect.DeepEqual(expPatch, expPatch) {
t.Errorf("Action %s %s has wrong patch\nDiff:\n %s",
a.GetVerb(), a.GetResource().Resource, diff.ObjectGoPrintDiff(expPatch, patch))
}
}
}
// filterImportActions filters list and watch actions for testing resources.
// Since list and watch don't change resource state we can filter it to lower
// nose level in our tests.
func filterImportActions(actions []core.Action) []core.Action {
ret := []core.Action{}
for _, action := range actions {
if len(action.GetNamespace()) == 0 &&
(action.Matches("list", "persistentvolumeclaims") ||
action.Matches("watch", "persistentvolumeclaims") ||
action.Matches("list", "pods") ||
action.Matches("watch", "pods")) {
continue
}
ret = append(ret, action)
}
return ret
}
func (f *importFixture) expectCreatePodAction(d *corev1.Pod) {
f.kubeactions = append(f.kubeactions, core.NewCreateAction(schema.GroupVersionResource{Resource: "pods", Version: "v1"}, d.Namespace, d))
}
func (f *importFixture) expectUpdatePvcAction(d *corev1.PersistentVolumeClaim) {
f.kubeactions = append(f.kubeactions, core.NewUpdateAction(schema.GroupVersionResource{Resource: "persistentvolumeclaims", Version: "v1"}, d.Namespace, d))
}
// Verifies basic pod creation when new PVC is discovered
func TestCreatesImportPod(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
f.pvcLister = append(f.pvcLister, pvc)
f.kubeobjects = append(f.kubeobjects, pvc)
expPod := createPod(pvc, DataVolName)
f.expectCreatePodAction(expPod)
f.run(getPvcKey(pvc, t))
}
// Verifies pod creation does not occur when waiting for expectation.
func TestImportPodCreationExpectation(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
f.pvcLister = append(f.pvcLister, pvc)
f.kubeobjects = append(f.kubeobjects, pvc)
expPvc := pvc.DeepCopy()
expPvc.ObjectMeta.Labels = map[string]string{CDILabelKey: CDILabelValue}
f.expectUpdatePvcAction(expPvc)
f.runWithExpectation(getPvcKey(pvc, t))
}
// Verifies pod creation is observed and pvc labels are set.
func TestImportObservePod(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
pod := createPod(pvc, DataVolName)
pod.Name = "madeup-name"
pod.Status.Phase = corev1.PodPending
pod.Namespace = pvc.Namespace
f.pvcLister = append(f.pvcLister, pvc)
f.podLister = append(f.podLister, pod)
f.kubeobjects = append(f.kubeobjects, pvc)
f.kubeobjects = append(f.kubeobjects, pod)
expPvc := pvc.DeepCopy()
expPvc.ObjectMeta.Labels = map[string]string{CDILabelKey: CDILabelValue}
expPvc.ObjectMeta.Annotations = map[string]string{AnnImportPod: pod.Name, AnnPodPhase: string(corev1.PodPending), AnnEndpoint: "http://test"}
f.expectUpdatePvcAction(expPvc)
f.run(getPvcKey(pvc, t))
}
// Verifies pod status updates are reflected in PVC annotations
func TestImportPodStatusUpdating(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
pod := createPod(pvc, DataVolName)
pod.Name = "madeup-name"
pod.Status.Phase = corev1.PodRunning
pod.Namespace = pvc.Namespace
pvc.ObjectMeta.Annotations = map[string]string{AnnImportPod: pod.Name, AnnPodPhase: string(corev1.PodPending), AnnEndpoint: "http://test"}
pvc.ObjectMeta.Labels = map[string]string{CDILabelKey: CDILabelValue}
f.pvcLister = append(f.pvcLister, pvc)
f.podLister = append(f.podLister, pod)
f.kubeobjects = append(f.kubeobjects, pvc)
f.kubeobjects = append(f.kubeobjects, pod)
// expecting pvc's pod status annotation to be updated from pending => running
expPvc := pvc.DeepCopy()
expPvc.ObjectMeta.Annotations = map[string]string{AnnImportPod: pod.Name, AnnPodPhase: string(pod.Status.Phase), AnnEndpoint: "http://test"}
f.expectUpdatePvcAction(expPvc)
f.run(getPvcKey(pvc, t))
}
func TestImportFindPodInCacheUpdating(t *testing.T) {
f := newImportFixture(t)
tests := []struct {
pvc *corev1.PersistentVolumeClaim
pod *corev1.Pod
}{
{
pvc: createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil),
},
{
pvc: createPvc("testPvc2", "default", map[string]string{AnnEndpoint: "http://test"}, nil),
},
{
pvc: createPvc("testPvc3", "default", map[string]string{AnnEndpoint: "http://test"}, nil),
},
}
for idx, test := range tests {
test.pod = createPod(test.pvc, DataVolName)
test.pod.Namespace = test.pvc.Namespace
test.pod.Name = fmt.Sprintf("fakename%d", idx)
f.pvcLister = append(f.pvcLister, test.pvc)
f.podLister = append(f.podLister, test.pod)
f.kubeobjects = append(f.kubeobjects, test.pvc)
f.kubeobjects = append(f.kubeobjects, test.pod)
tests[idx] = test
}
controller := f.newController()
stopCh := make(chan struct{})
defer close(stopCh)
go controller.pvcInformer.Run(stopCh)
go controller.podInformer.Run(stopCh)
cache.WaitForCacheSync(stopCh, controller.pvcInformer.HasSynced)
cache.WaitForCacheSync(stopCh, controller.pvcInformer.HasSynced)
for _, test := range tests {
foundPod, err := controller.findImportPodFromCache(test.pvc)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if foundPod == nil {
t.Errorf("didn't find pod for pvc %v", test.pvc)
}
if !reflect.DeepEqual(foundPod, test.pod) {
t.Errorf("wrong pod found.\nfound %v\nwant %v", foundPod, test.pod)
}
}
}
// verifies no work is done on pvcs without our annotations
func TestImportIgnorePVC(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", nil, nil)
f.pvcLister = append(f.pvcLister, pvc)
f.kubeobjects = append(f.kubeobjects, pvc)
f.runWithExpectation(getPvcKey(pvc, t))
}
// verify error if ownership doesn't match
func TestImportOwnership(t *testing.T) {
f := newImportFixture(t)
pvc := createPvc("testPvc1", "default", map[string]string{AnnEndpoint: "http://test"}, nil)
pod := createPod(pvc, DataVolName)
pod.Name = "madeup-name"
pod.Status.Phase = corev1.PodPending
pod.Namespace = pvc.Namespace
pod.ObjectMeta.OwnerReferences = []metav1.OwnerReference{}
f.pvcLister = append(f.pvcLister, pvc)
f.podLister = append(f.podLister, pod)
f.kubeobjects = append(f.kubeobjects, pvc)
f.kubeobjects = append(f.kubeobjects, pod)
f.runExpectError(getPvcKey(pvc, t))
}