From 5aad5ee380158f747df0bd133b5ca40faef0897e Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Thu, 10 Dec 2020 14:31:09 -0800 Subject: [PATCH] add test cases for applier --- pkg/apply/applier_test.go | 220 +++++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 5 deletions(-) diff --git a/pkg/apply/applier_test.go b/pkg/apply/applier_test.go index 226739c..27b1358 100644 --- a/pkg/apply/applier_test.go +++ b/pkg/apply/applier_test.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "net/http" "path" + "reflect" "regexp" "testing" "time" @@ -23,7 +24,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" + dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/rest/fake" + clienttesting "k8s.io/client-go/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" "k8s.io/kubectl/pkg/scheme" "sigs.k8s.io/cli-utils/pkg/apply/event" @@ -66,6 +69,34 @@ var ( } ) +var deploymentUnmatched = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "foo", + "namespace": "default", + "annotations": map[string]interface{}{ + "config.k8s.io/owning-inventory": "unmatched", + }, + }, + }, +} + +var deploymentMatched = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "foo", + "namespace": "default", + "annotations": map[string]interface{}{ + "config.k8s.io/owning-inventory": "test", + }, + }, + }, +} + // resourceStatus contains information about a specific resource, such // as the manifest yaml and the URL path for this resource in the // client. It also contains a factory function for creating a new @@ -83,6 +114,9 @@ type expectedEvent struct { statusEventType event.StatusEventType pruneEventType event.PruneEventType deleteEventType event.DeleteEventType + + applyErrorType error + pruneEventOp event.PruneEventOperation } func TestApplier(t *testing.T) { @@ -94,6 +128,7 @@ func TestApplier(t *testing.T) { prune bool statusEvents []pollevent.Event expectedEventTypes []expectedEvent + clusterObj *unstructured.Unstructured }{ "apply without status or prune": { namespace: "default", @@ -158,14 +193,14 @@ func TestApplier(t *testing.T) { { EventType: pollevent.ResourceUpdateEvent, Resource: &pollevent.ResourceStatus{ - Identifier: toIdentifier(t, resources["deployment"], "default"), + Identifier: toIdentifier(t, resources["deployment"]), Status: status.InProgressStatus, }, }, { EventType: pollevent.ResourceUpdateEvent, Resource: &pollevent.ResourceStatus{ - Identifier: toIdentifier(t, resources["deployment"], "default"), + Identifier: toIdentifier(t, resources["deployment"]), Status: status.CurrentStatus, }, }, @@ -200,6 +235,152 @@ func TestApplier(t *testing.T) { }, }, }, + "apply with inventory object and cluster object": { + namespace: "default", + resources: []resourceInfo{ + resources["deployment"], + resources["inventoryObject"], + }, + handlers: []handler{ + &nsHandler{}, + &inventoryObjectHandler{}, + &genericHandler{ + resourceInfo: resources["deployment"], + namespace: "default", + }, + }, + reconcileTimeout: time.Minute, + statusEvents: []pollevent.Event{ + { + EventType: pollevent.ResourceUpdateEvent, + Resource: &pollevent.ResourceStatus{ + Identifier: object.ObjMetadata{ + Name: "foo-dcf2c498", + Namespace: "default", + GroupKind: schema.GroupKind{ + Group: "", + Kind: "ConfigMap", + }, + }, + Status: status.CurrentStatus, + }, + }, + { + EventType: pollevent.ResourceUpdateEvent, + Resource: &pollevent.ResourceStatus{ + Identifier: toIdentifier(t, resources["deployment"]), + Status: status.InProgressStatus, + }, + }, + { + EventType: pollevent.ResourceUpdateEvent, + Resource: &pollevent.ResourceStatus{ + Identifier: toIdentifier(t, resources["deployment"]), + Status: status.CurrentStatus, + }, + }, + }, + expectedEventTypes: []expectedEvent{ + { + eventType: event.InitType, + }, + { + eventType: event.ApplyType, + applyEventType: event.ApplyEventResourceUpdate, + applyErrorType: inventory.NewInventoryOverlapError(fmt.Errorf("")), + }, + { + eventType: event.ApplyType, + applyEventType: event.ApplyEventCompleted, + }, + { + eventType: event.StatusType, + statusEventType: event.StatusEventResourceUpdate, + }, + { + eventType: event.StatusType, + statusEventType: event.StatusEventResourceUpdate, + }, + { + eventType: event.StatusType, + statusEventType: event.StatusEventResourceUpdate, + }, + { + eventType: event.StatusType, + statusEventType: event.StatusEventCompleted, + }, + }, + clusterObj: deploymentUnmatched, + }, + "prune with inventory object annotation unmatched": { + namespace: "default", + resources: []resourceInfo{ + resources["inventoryObject"], + }, + handlers: []handler{ + &nsHandler{}, + &inventoryObjectHandler{}, + &genericHandler{ + namespace: "default", + }, + }, + reconcileTimeout: 0, + expectedEventTypes: []expectedEvent{ + { + eventType: event.InitType, + }, + { + eventType: event.ApplyType, + applyEventType: event.ApplyEventCompleted, + }, + { + eventType: event.PruneType, + pruneEventType: event.PruneEventResourceUpdate, + pruneEventOp: event.PruneSkipped, + }, + { + eventType: event.PruneType, + pruneEventType: event.PruneEventCompleted, + }, + }, + clusterObj: deploymentUnmatched, + prune: true, + }, + "prune with inventory object annotation matched": { + namespace: "default", + resources: []resourceInfo{ + resources["inventoryObject"], + }, + handlers: []handler{ + &nsHandler{}, + &inventoryObjectHandler{}, + &genericHandler{ + resourceInfo: resources["deployment"], + namespace: "default", + }, + }, + reconcileTimeout: 0, + expectedEventTypes: []expectedEvent{ + { + eventType: event.InitType, + }, + { + eventType: event.ApplyType, + applyEventType: event.ApplyEventCompleted, + }, + { + eventType: event.PruneType, + pruneEventType: event.PruneEventResourceUpdate, + pruneEventOp: event.Pruned, + }, + { + eventType: event.PruneType, + pruneEventType: event.PruneEventCompleted, + }, + }, + clusterObj: deploymentMatched, + prune: true, + }, } for tn, tc := range testCases { @@ -211,6 +392,9 @@ func TestApplier(t *testing.T) { defer tf.Cleanup() tf.UnstructuredClient = newFakeRESTClient(t, tc.handlers) + if tc.clusterObj != nil { + tf.FakeDynamicClient = fakeDynamicClient(tc.clusterObj) + } cf := provider.NewProvider(tf) applier := NewApplier(cf) @@ -222,7 +406,17 @@ func TestApplier(t *testing.T) { if !assert.NoError(t, err) { return } - applier.invClient = inventory.NewFakeInventoryClient([]object.ObjMetadata{}) + + objmeta := []object.ObjMetadata{} + if tc.clusterObj != nil { + objmeta = append(objmeta, object.UnstructuredToObjMeta(tc.clusterObj)) + } + invClient := inventory.NewFakeInventoryClient(objmeta) + applier.invClient = invClient + err = applier.PruneOptions.Initialize(cf.Factory(), invClient) + if !assert.NoError(t, err) { + return + } poller := &fakePoller{ events: tc.statusEvents, @@ -255,10 +449,12 @@ func TestApplier(t *testing.T) { case event.InitType: case event.ApplyType: assert.Equal(t, expected.applyEventType.String(), e.ApplyEvent.Type.String()) + assert.Equal(t, reflect.TypeOf(expected.applyErrorType), reflect.TypeOf(e.ApplyEvent.Error)) case event.StatusType: assert.Equal(t, expected.statusEventType.String(), e.StatusEvent.Type.String()) case event.PruneType: assert.Equal(t, expected.pruneEventType.String(), e.PruneEvent.Type.String()) + assert.Equal(t, expected.pruneEventOp.String(), e.PruneEvent.Operation.String()) case event.DeleteType: assert.Equal(t, expected.deleteEventType.String(), e.DeleteEvent.Type.String()) default: @@ -523,7 +719,7 @@ func newFakeRESTClient(t *testing.T, handlers []handler) *fake.RESTClient { } } -func toIdentifier(t *testing.T, resourceInfo resourceInfo, namespace string) object.ObjMetadata { +func toIdentifier(t *testing.T, resourceInfo resourceInfo) object.ObjMetadata { obj := resourceInfo.factoryFunc() err := runtime.DecodeInto(codec, []byte(resourceInfo.manifest), obj) if err != nil { @@ -537,7 +733,7 @@ func toIdentifier(t *testing.T, resourceInfo resourceInfo, namespace string) obj return object.ObjMetadata{ GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(), Name: accessor.GetName(), - Namespace: namespace, + Namespace: "default", } } @@ -768,3 +964,17 @@ func objSetsEqual(setA []*unstructured.Unstructured, setB []*unstructured.Unstru } return true } + +// fakeDynamicClient returns a fake dynamic client. +func fakeDynamicClient(obj *unstructured.Unstructured) *dynamicfake.FakeDynamicClient { + fakeClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme()) + + fakeClient.PrependReactor("get", "deployments", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, obj, nil + }) + fakeClient.PrependReactor("delete", "deployments", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, nil + }) + + return fakeClient +}