From 34a8a7b334e2019b8e3ffe138d9521503d01de91 Mon Sep 17 00:00:00 2001 From: Karl Isenberg Date: Mon, 7 Mar 2022 12:00:42 -0800 Subject: [PATCH] chore: add stress test - Stress test tests 1,000 Namespaces, CofnigMaps, & CronTabs (CR) - Stress test is a new test suite with its own make entrypoint - Refactor shared test code so the e2e and stress tests can both use it - Update test client QPS to 20 (from 5) --- Makefile | 4 + test/e2e/apply_and_destroy_test.go | 27 +- test/e2e/continue_on_error_test.go | 14 +- test/e2e/crd_test.go | 12 +- test/e2e/customprovider/provider.go | 6 - test/e2e/deletion_prevention_test.go | 40 +-- test/e2e/dependency_filter_test.go | 24 +- test/e2e/depends_on_test.go | 26 +- test/e2e/dry_run_test.go | 26 +- test/e2e/e2e_test.go | 292 +++--------------- .../e2e/{common_test.go => e2eutil/common.go} | 250 +++++++++------ test/e2e/empty_set_test.go | 8 +- test/e2e/exit_early_test.go | 16 +- test/e2e/invconfig/configmap.go | 80 +++++ test/e2e/invconfig/custom.go | 131 ++++++++ test/e2e/invconfig/invconfig.go | 129 ++++++++ test/e2e/inventory_policy_test.go | 54 ++-- test/e2e/mutation_test.go | 20 +- test/e2e/name_inv_strategy_test.go | 10 +- test/e2e/namespace_filter_test.go | 24 +- test/e2e/prune_retrieve_error_test.go | 28 +- test/e2e/reconcile_failed_timeout_test.go | 18 +- test/e2e/serverside_apply_test.go | 14 +- test/e2e/skip_invalid_test.go | 46 +-- test/stress/artifacts_test.go | 67 ++++ test/stress/e2e_suite_test.go | 16 + test/stress/stress_test.go | 102 ++++++ test/stress/thousand_namespaces_test.go | 161 ++++++++++ 28 files changed, 1110 insertions(+), 535 deletions(-) rename test/e2e/{common_test.go => e2eutil/common.go} (50%) create mode 100644 test/e2e/invconfig/configmap.go create mode 100644 test/e2e/invconfig/custom.go create mode 100644 test/e2e/invconfig/invconfig.go create mode 100644 test/stress/artifacts_test.go create mode 100644 test/stress/e2e_suite_test.go create mode 100644 test/stress/stress_test.go create mode 100644 test/stress/thousand_namespaces_test.go diff --git a/Makefile b/Makefile index c1f9c287..ada4e794 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,10 @@ test-e2e: $(MYGOBIN)/ginkgo $(MYGOBIN)/kind kind delete cluster --name=cli-utils-e2e && kind create cluster --name=cli-utils-e2e $(GOPATH)/bin/ginkgo ./test/e2e/... +test-stress: $(MYGOBIN)/ginkgo $(MYGOBIN)/kind + kind delete cluster --name=cli-utils-e2e && kind create cluster --name=cli-utils-e2e + $(GOPATH)/bin/ginkgo -v ./test/stress/... -- -v 5 + vet: go vet ./... diff --git a/test/e2e/apply_and_destroy_test.go b/test/e2e/apply_and_destroy_test.go index c45928e8..72552027 100644 --- a/test/e2e/apply_and_destroy_test.go +++ b/test/e2e/apply_and_destroy_test.go @@ -17,22 +17,24 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply resources") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) - deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) resources := []*unstructured.Unstructured{ deployment1Obj, } - applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, })) @@ -184,7 +186,7 @@ func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig Invento Expect(received).To(testutil.Equal(expEvents)) By("Verify deployment created") - assertUnstructuredExists(ctx, c, deployment1Obj) + e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) By("Verify inventory") invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1) @@ -193,7 +195,7 @@ func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig Invento destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, options)) expEvents = []testutil.ExpEvent{ { @@ -288,16 +290,5 @@ func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig Invento Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents)) By("Verify deployment deleted") - assertUnstructuredDoesNotExist(ctx, c, deployment1Obj) -} - -func createInventoryInfo(invConfig InventoryConfig, inventoryName, namespaceName, inventoryID string) inventory.Info { - switch invConfig.Strategy { - case inventory.NameStrategy: - return invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, randomString("inventory-"))) - case inventory.LabelStrategy: - return invConfig.InvWrapperFunc(invConfig.FactoryFunc(randomString("inventory-"), namespaceName, inventoryID)) - default: - panic(fmt.Errorf("unknown inventory strategy %q", invConfig.Strategy)) - } + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, deployment1Obj) } diff --git a/test/e2e/continue_on_error_test.go b/test/e2e/continue_on_error_test.go index 18395c78..13398269 100644 --- a/test/e2e/continue_on_error_test.go +++ b/test/e2e/continue_on_error_test.go @@ -16,23 +16,25 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func continueOnErrorTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func continueOnErrorTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply an invalid CRD") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) - invalidCrdObj := manifestToUnstructured(invalidCrd) - pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) + invalidCrdObj := e2eutil.ManifestToUnstructured(invalidCrd) + pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName) resources := []*unstructured.Unstructured{ invalidCrdObj, pod1Obj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{})) + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{})) expEvents := []testutil.ExpEvent{ { @@ -167,8 +169,8 @@ func continueOnErrorTest(ctx context.Context, c client.Client, invConfig Invento Expect(receivedEvents).To(testutil.Equal(expEvents)) By("Verify pod1 created") - assertUnstructuredExists(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) By("Verify CRD not created") - assertUnstructuredDoesNotExist(ctx, c, invalidCrdObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, invalidCrdObj) } diff --git a/test/e2e/crd_test.go b/test/e2e/crd_test.go index 11842fcf..625a4077 100644 --- a/test/e2e/crd_test.go +++ b/test/e2e/crd_test.go @@ -16,25 +16,27 @@ import ( "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) //nolint:dupl // expEvents similar to mutation tests -func crdTest(ctx context.Context, _ client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func crdTest(ctx context.Context, _ client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply a set of resources that includes both a crd and a cr") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) - crdObj := manifestToUnstructured(crd) - crObj := manifestToUnstructured(cr) + crdObj := e2eutil.ManifestToUnstructured(crd) + crObj := e2eutil.ManifestToUnstructured(cr) resources := []*unstructured.Unstructured{ crObj, crdObj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: false, })) @@ -215,7 +217,7 @@ func crdTest(ctx context.Context, _ client.Client, invConfig InventoryConfig, in By("destroy the resources, including the crd") destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options)) expEvents = []testutil.ExpEvent{ { diff --git a/test/e2e/customprovider/provider.go b/test/e2e/customprovider/provider.go index 5cab2c09..6200d1a3 100644 --- a/test/e2e/customprovider/provider.go +++ b/test/e2e/customprovider/provider.go @@ -36,12 +36,6 @@ spec: openAPIV3Schema: description: Example for cli-utils e2e tests properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object spec: properties: objects: diff --git a/test/e2e/deletion_prevention_test.go b/test/e2e/deletion_prevention_test.go index 1bd9ed09..4a73386f 100644 --- a/test/e2e/deletion_prevention_test.go +++ b/test/e2e/deletion_prevention_test.go @@ -14,36 +14,38 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func deletionPreventionTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func deletionPreventionTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply resources") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) resources := []*unstructured.Unstructured{ - withNamespace(manifestToUnstructured(deployment1), namespaceName), - withAnnotation(withNamespace(manifestToUnstructured(pod1), namespaceName), common.OnRemoveAnnotation, common.OnRemoveKeep), - withAnnotation(withNamespace(manifestToUnstructured(pod2), namespaceName), common.LifecycleDeleteAnnotation, common.PreventDeletion), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), + e2eutil.WithAnnotation(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), common.OnRemoveAnnotation, common.OnRemoveKeep), + e2eutil.WithAnnotation(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName), common.LifecycleDeleteAnnotation, common.PreventDeletion), } - runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, })) By("Verify deployment created") - obj := assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespaceName)) + obj := e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify pod1 created") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod1), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify pod2 created") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod2), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify the inventory size is 3") @@ -51,24 +53,24 @@ func deletionPreventionTest(ctx context.Context, c client.Client, invConfig Inve By("Dry-run apply resources") resources = []*unstructured.Unstructured{ - withNamespace(manifestToUnstructured(deployment1), namespaceName), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), } - runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, DryRunStrategy: common.DryRunClient, })) By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify pod1 still exits and does not have the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod1), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify pod2 still exits and does not have the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod2), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify the inventory size is still 3") @@ -76,23 +78,23 @@ func deletionPreventionTest(ctx context.Context, c client.Client, invConfig Inve By("Apply resources") resources = []*unstructured.Unstructured{ - withNamespace(manifestToUnstructured(deployment1), namespaceName), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), } - runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, })) By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID())) By("Verify pod1 still exits and does not have the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod1), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal("")) By("Verify pod2 still exits and does not have the config.k8s.io/owning-inventory annotation") - obj = assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(pod2), namespaceName)) + obj = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName)) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal("")) By("Verify the inventory size is 1") diff --git a/test/e2e/dependency_filter_test.go b/test/e2e/dependency_filter_test.go index ec8258ef..dbbd2613 100644 --- a/test/e2e/dependency_filter_test.go +++ b/test/e2e/dependency_filter_test.go @@ -15,18 +15,20 @@ import ( "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) //nolint:dupl // expEvents similar to other tests -func dependencyFilterTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func dependencyFilterTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply resources in order based on depends-on annotation") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) - pod1Obj := withDependsOn(withNamespace(manifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) - pod2Obj := withNamespace(manifestToUnstructured(pod2), namespaceName) + pod1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) + pod2Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName) // Dependency order: pod1 -> pod2 // Apply order: pod2, pod1 @@ -37,11 +39,11 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent // Cleanup defer func(ctx context.Context, c client.Client) { - deleteUnstructuredIfExists(ctx, c, pod1Obj) - deleteUnstructuredIfExists(ctx, c, pod2Obj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, pod1Obj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, pod2Obj) }(ctx, c) - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -219,14 +221,14 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify pod1 created and ready") - result := assertUnstructuredExists(ctx, c, pod1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness By("verify pod2 created and ready") - result = assertUnstructuredExists(ctx, c, pod2Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod2Obj) podIP, found, err = object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) @@ -237,7 +239,7 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent pod1Obj, } - applierEvents = runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents = e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, ValidationPolicy: validation.SkipInvalid, })) @@ -398,13 +400,13 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify pod1 not deleted") - result = assertUnstructuredExists(ctx, c, pod1Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) ts, found, err := object.NestedField(result.Object, "metadata", "deletionTimestamp") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) By("verify pod2 not deleted") - result = assertUnstructuredExists(ctx, c, pod2Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod2Obj) ts, found, err = object.NestedField(result.Object, "metadata", "deletionTimestamp") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) diff --git a/test/e2e/depends_on_test.go b/test/e2e/depends_on_test.go index d492a60e..749ea78d 100644 --- a/test/e2e/depends_on_test.go +++ b/test/e2e/depends_on_test.go @@ -15,18 +15,20 @@ import ( "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func dependsOnTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply resources in order based on depends-on annotation") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) - pod1Obj := withDependsOn(withNamespace(manifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod3", namespaceName)) - pod2Obj := withNamespace(manifestToUnstructured(pod2), namespaceName) - pod3Obj := withDependsOn(withNamespace(manifestToUnstructured(pod3), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) + pod1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod3", namespaceName)) + pod2Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName) + pod3Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod3), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) // Dependency order: pod1 -> pod3 -> pod2 // Apply order: pod2, pod3, pod1 @@ -36,7 +38,7 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf pod3Obj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -278,21 +280,21 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify pod1 created and ready") - result := assertUnstructuredExists(ctx, c, pod1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness By("verify pod2 created and ready") - result = assertUnstructuredExists(ctx, c, pod2Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod2Obj) podIP, found, err = object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness By("verify pod3 created and ready") - result = assertUnstructuredExists(ctx, c, pod3Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod3Obj) podIP, found, err = object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) @@ -301,7 +303,7 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf By("destroy resources in opposite order") destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options)) expEvents = []testutil.ExpEvent{ { @@ -523,11 +525,11 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents)) By("verify pod1 deleted") - assertUnstructuredDoesNotExist(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj) By("verify pod2 deleted") - assertUnstructuredDoesNotExist(ctx, c, pod2Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod2Obj) By("verify pod3 deleted") - assertUnstructuredDoesNotExist(ctx, c, pod3Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj) } diff --git a/test/e2e/dry_run_test.go b/test/e2e/dry_run_test.go index d44faf74..8bd524f8 100644 --- a/test/e2e/dry_run_test.go +++ b/test/e2e/dry_run_test.go @@ -18,21 +18,23 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func dryRunTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply with DryRun") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) namespace1Name := fmt.Sprintf("%s-ns1", namespaceName) fields := struct{ Namespace string }{Namespace: namespace1Name} - namespace1Obj := templateToUnstructured(namespaceTemplate, fields) - podBObj := templateToUnstructured(podBTemplate, fields) + namespace1Obj := e2eutil.TemplateToUnstructured(namespaceTemplate, fields) + podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields) // Dependency order: podB -> namespace1 // Apply order: namespace1, podB @@ -41,7 +43,7 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, podBObj, } - applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, DryRunStrategy: common.DryRunClient, @@ -176,18 +178,18 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, Expect(received).To(testutil.Equal(expEvents)) By("Verify pod NotFound") - assertUnstructuredDoesNotExist(ctx, c, podBObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj) By("Verify inventory NotFound") invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID) By("Apply") - runWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + e2eutil.RunWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, })) By("Verify pod created") - assertUnstructuredExists(ctx, c, podBObj) + e2eutil.AssertUnstructuredExists(ctx, c, podBObj) By("Verify inventory size") invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 2, 2) @@ -195,7 +197,7 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, By("Destroy with DryRun") destroyer := invConfig.DestroyerFactoryFunc() - destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ InventoryPolicy: inventory.PolicyAdoptIfNoInventory, EmitStatusEvents: true, DryRunStrategy: common.DryRunClient, @@ -286,15 +288,15 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents)) By("Verify pod still exists") - assertUnstructuredExists(ctx, c, podBObj) + e2eutil.AssertUnstructuredExists(ctx, c, podBObj) By("Destroy") - runWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ + e2eutil.RunWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ InventoryPolicy: inventory.PolicyAdoptIfNoInventory, })) By("Verify pod deleted") - assertUnstructuredDoesNotExist(ctx, c, podBObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj) By("Verify inventory deleted") invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 15318259..df273640 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -12,69 +12,25 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/format" v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" - "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/klog/v2" - "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/scheme" - "sigs.k8s.io/cli-utils/pkg/apply" - "sigs.k8s.io/cli-utils/pkg/common" - "sigs.k8s.io/cli-utils/pkg/inventory" - "sigs.k8s.io/cli-utils/test/e2e/customprovider" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) -type inventoryFactoryFunc func(name, namespace, id string) *unstructured.Unstructured -type invWrapperFunc func(*unstructured.Unstructured) inventory.Info -type applierFactoryFunc func() *apply.Applier -type destroyerFactoryFunc func() *apply.Destroyer -type invSizeVerifyFunc func(ctx context.Context, c client.Client, name, namespace, id string, specCount, statusCount int) -type invCountVerifyFunc func(ctx context.Context, c client.Client, namespace string, count int) -type invNotExistsFunc func(ctx context.Context, c client.Client, name, namespace, id string) - -type InventoryConfig struct { - Strategy inventory.Strategy - FactoryFunc inventoryFactoryFunc - InvWrapperFunc invWrapperFunc - ApplierFactoryFunc applierFactoryFunc - DestroyerFactoryFunc destroyerFactoryFunc - InvSizeVerifyFunc invSizeVerifyFunc - InvCountVerifyFunc invCountVerifyFunc - InvNotExistsFunc invNotExistsFunc -} - const ( ConfigMapTypeInvConfig = "ConfigMap" CustomTypeInvConfig = "Custom" ) -var inventoryConfigs = map[string]InventoryConfig{ - ConfigMapTypeInvConfig: { - Strategy: inventory.LabelStrategy, - FactoryFunc: cmInventoryManifest, - InvWrapperFunc: inventory.WrapInventoryInfoObj, - ApplierFactoryFunc: newDefaultInvApplier, - DestroyerFactoryFunc: newDefaultInvDestroyer, - InvSizeVerifyFunc: defaultInvSizeVerifyFunc, - InvCountVerifyFunc: defaultInvCountVerifyFunc, - InvNotExistsFunc: defaultInvNotExistsFunc, - }, - CustomTypeInvConfig: { - Strategy: inventory.NameStrategy, - FactoryFunc: customInventoryManifest, - InvWrapperFunc: customprovider.WrapInventoryInfoObj, - ApplierFactoryFunc: newCustomInvApplier, - DestroyerFactoryFunc: newCustomInvDestroyer, - InvSizeVerifyFunc: customInvSizeVerifyFunc, - InvCountVerifyFunc: customInvCountVerifyFunc, - InvNotExistsFunc: customInvNotExistsFunc, - }, +var inventoryConfigs = map[string]invconfig.InventoryConfig{} +var inventoryConfigTypes = []string{ + ConfigMapTypeInvConfig, + CustomTypeInvConfig, } // Parse optional logging flags @@ -101,6 +57,14 @@ var _ = Describe("Applier", func() { cfg, err := ctrl.GetConfig() Expect(err).NotTo(HaveOccurred()) + // increase QPS from 5 to 20 + cfg.QPS = 20 + // increase Burst QPS from 10 to 40 + cfg.Burst = 40 + + inventoryConfigs[ConfigMapTypeInvConfig] = invconfig.NewConfigMapTypeInvConfig(cfg) + inventoryConfigs[CustomTypeInvConfig] = invconfig.NewCustomTypeInvConfig(cfg) + mapper, err := apiutil.NewDynamicRESTMapper(cfg) Expect(err).NotTo(HaveOccurred()) @@ -112,20 +76,26 @@ var _ = Describe("Applier", func() { ctx, cancel := context.WithTimeout(context.Background(), defaultBeforeTestTimeout) defer cancel() - createInventoryCRD(ctx, c) + e2eutil.CreateInventoryCRD(ctx, c) Expect(ctx.Err()).To(BeNil(), "BeforeSuite context cancelled or timed out") }) AfterSuite(func() { ctx, cancel := context.WithTimeout(context.Background(), defaultAfterTestTimeout) defer cancel() - deleteInventoryCRD(ctx, c) + e2eutil.DeleteInventoryCRD(ctx, c) Expect(ctx.Err()).To(BeNil(), "AfterSuite context cancelled or timed out") }) - for name := range inventoryConfigs { - invConfig := inventoryConfigs[name] - Context(fmt.Sprintf("Inventory: %s", name), func() { + for i := range inventoryConfigTypes { + invType := inventoryConfigTypes[i] + Context(fmt.Sprintf("Inventory: %s", invType), func() { + var invConfig invconfig.InventoryConfig + + BeforeEach(func() { + invConfig = inventoryConfigs[invType] + }) + Context("Apply and destroy", func() { var namespace *v1.Namespace var inventoryName string @@ -134,8 +104,8 @@ var _ = Describe("Applier", func() { BeforeEach(func() { ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) - inventoryName = randomString("test-inv-") - namespace = createRandomNamespace(ctx, c) + inventoryName = e2eutil.RandomString("test-inv-") + namespace = e2eutil.CreateRandomNamespace(ctx, c) }) AfterEach(func() { @@ -147,20 +117,20 @@ var _ = Describe("Applier", func() { // clean up resources created by the tests fields := struct{ Namespace string }{Namespace: namespace.GetName()} objs := []*unstructured.Unstructured{ - manifestToUnstructured(cr), - manifestToUnstructured(crd), - withNamespace(manifestToUnstructured(pod1), namespace.GetName()), - withNamespace(manifestToUnstructured(pod2), namespace.GetName()), - withNamespace(manifestToUnstructured(pod3), namespace.GetName()), - templateToUnstructured(podATemplate, fields), - templateToUnstructured(podBTemplate, fields), - withNamespace(manifestToUnstructured(deployment1), namespace.GetName()), - manifestToUnstructured(apiservice1), + e2eutil.ManifestToUnstructured(cr), + e2eutil.ManifestToUnstructured(crd), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespace.GetName()), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespace.GetName()), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod3), namespace.GetName()), + e2eutil.TemplateToUnstructured(podATemplate, fields), + e2eutil.TemplateToUnstructured(podBTemplate, fields), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName()), + e2eutil.ManifestToUnstructured(apiservice1), } for _, obj := range objs { - deleteUnstructuredIfExists(ctx, c, obj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, obj) } - deleteNamespace(ctx, c, namespace) + e2eutil.DeleteNamespace(ctx, c, namespace) }) It("Apply and destroy", func() { @@ -235,7 +205,7 @@ var _ = Describe("Applier", func() { BeforeEach(func() { ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) - namespace = createRandomNamespace(ctx, c) + namespace = e2eutil.CreateRandomNamespace(ctx, c) }) AfterEach(func() { @@ -244,8 +214,8 @@ var _ = Describe("Applier", func() { // new timeout for cleanup ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout) defer cancel() - deleteUnstructuredIfExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespace.GetName())) - deleteNamespace(ctx, c, namespace) + e2eutil.DeleteUnstructuredIfExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName())) + e2eutil.DeleteNamespace(ctx, c, namespace) }) It("MustMatch policy", func() { @@ -271,8 +241,8 @@ var _ = Describe("Applier", func() { BeforeEach(func() { ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) - inventoryName = randomString("test-inv-") - namespace = createRandomNamespace(ctx, c) + inventoryName = e2eutil.RandomString("test-inv-") + namespace = e2eutil.CreateRandomNamespace(ctx, c) }) AfterEach(func() { @@ -281,8 +251,8 @@ var _ = Describe("Applier", func() { // new timeout for cleanup ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout) defer cancel() - deleteUnstructuredIfExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespace.GetName())) - deleteNamespace(ctx, c, namespace) + e2eutil.DeleteUnstructuredIfExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName())) + e2eutil.DeleteNamespace(ctx, c, namespace) }) It("Apply with existing inventory", func() { @@ -290,175 +260,3 @@ var _ = Describe("Applier", func() { }) }) }) - -func createInventoryCRD(ctx context.Context, c client.Client) { - invCRD := manifestToUnstructured(customprovider.InventoryCRD) - var u unstructured.Unstructured - u.SetGroupVersionKind(invCRD.GroupVersionKind()) - err := c.Get(ctx, types.NamespacedName{ - Name: invCRD.GetName(), - }, &u) - if apierrors.IsNotFound(err) { - err = c.Create(ctx, invCRD) - } - Expect(err).NotTo(HaveOccurred()) -} - -func createRandomNamespace(ctx context.Context, c client.Client) *v1.Namespace { - namespaceName := randomString("e2e-test-") - namespace := &v1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1.SchemeGroupVersion.String(), - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: namespaceName, - }, - } - - err := c.Create(ctx, namespace) - Expect(err).ToNot(HaveOccurred()) - return namespace -} - -func deleteInventoryCRD(ctx context.Context, c client.Client) { - invCRD := manifestToUnstructured(customprovider.InventoryCRD) - deleteUnstructuredIfExists(ctx, c, invCRD) -} - -func deleteUnstructuredIfExists(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { - err := c.Delete(ctx, obj) - if err != nil { - Expect(err).To(Or( - BeAssignableToTypeOf(&meta.NoKindMatchError{}), - BeAssignableToTypeOf(&apierrors.StatusError{}), - )) - if se, ok := err.(*apierrors.StatusError); ok { - Expect(se.ErrStatus.Reason).To(Equal(metav1.StatusReasonNotFound)) - } - } -} - -func deleteNamespace(ctx context.Context, c client.Client, namespace *v1.Namespace) { - err := c.Delete(ctx, namespace) - Expect(err).ToNot(HaveOccurred()) -} - -func newDefaultInvApplier() *apply.Applier { - return newApplierFromInvFactory(inventory.ClusterClientFactory{StatusPolicy: inventory.StatusPolicyAll}) -} - -func newDefaultInvDestroyer() *apply.Destroyer { - return newDestroyerFromInvFactory(inventory.ClusterClientFactory{StatusPolicy: inventory.StatusPolicyAll}) -} - -func defaultInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) { - var cmList v1.ConfigMapList - err := c.List(ctx, &cmList, - client.MatchingLabels(map[string]string{common.InventoryLabel: id}), - client.InNamespace(namespace)) - Expect(err).ToNot(HaveOccurred()) - Expect(cmList.Items).To(HaveLen(0), "expected inventory list to be empty") -} - -func defaultInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, id string, specCount, _ int) { - var cmList v1.ConfigMapList - err := c.List(ctx, &cmList, - client.MatchingLabels(map[string]string{common.InventoryLabel: id}), - client.InNamespace(namespace)) - Expect(err).WithOffset(1).ToNot(HaveOccurred(), "listing ConfigMap inventory from cluster") - - Expect(len(cmList.Items)).WithOffset(1).To(Equal(1), "number of inventory objects by label") - - data := cmList.Items[0].Data - Expect(len(data)).WithOffset(1).To(Equal(specCount), "inventory spec.data length") - - // Don't validate status size. - // ConfigMap provider uses inventory.StatusPolicyNone. -} - -func defaultInvCountVerifyFunc(ctx context.Context, c client.Client, namespace string, count int) { - var cmList v1.ConfigMapList - err := c.List(ctx, &cmList, client.InNamespace(namespace), client.HasLabels{common.InventoryLabel}) - Expect(err).NotTo(HaveOccurred()) - Expect(len(cmList.Items)).To(Equal(count)) -} - -func newCustomInvApplier() *apply.Applier { - return newApplierFromInvFactory(customprovider.CustomClientFactory{}) -} - -func newCustomInvDestroyer() *apply.Destroyer { - return newDestroyerFromInvFactory(customprovider.CustomClientFactory{}) -} - -func newFactory() util.Factory { - kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() - matchVersionKubeConfigFlags := util.NewMatchVersionFlags(kubeConfigFlags) - return util.NewFactory(matchVersionKubeConfigFlags) -} - -func customInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) { - var u unstructured.Unstructured - u.SetGroupVersionKind(customprovider.InventoryGVK) - u.SetName(name) - u.SetNamespace(namespace) - assertUnstructuredDoesNotExist(ctx, c, &u) -} - -func customInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, _ string, specCount, statusCount int) { - var u unstructured.Unstructured - u.SetGroupVersionKind(customprovider.InventoryGVK) - err := c.Get(ctx, types.NamespacedName{ - Name: name, - Namespace: namespace, - }, &u) - Expect(err).WithOffset(1).ToNot(HaveOccurred(), "getting custom inventory from cluster") - - s, found, err := unstructured.NestedSlice(u.Object, "spec", "objects") - Expect(err).WithOffset(1).ToNot(HaveOccurred(), "reading inventory spec.objects") - if found { - Expect(len(s)).WithOffset(1).To(Equal(specCount), "inventory status.objects length") - } else { - Expect(specCount).WithOffset(1).To(Equal(0), "inventory spec.objects not found") - } - - s, found, err = unstructured.NestedSlice(u.Object, "status", "objects") - Expect(err).WithOffset(1).ToNot(HaveOccurred(), "reading inventory status.objects") - if found { - Expect(len(s)).WithOffset(1).To(Equal(statusCount), "inventory status.objects length") - } else { - Expect(statusCount).WithOffset(1).To(Equal(0), "inventory status.objects not found") - } -} - -func customInvCountVerifyFunc(ctx context.Context, c client.Client, namespace string, count int) { - var u unstructured.UnstructuredList - u.SetGroupVersionKind(customprovider.InventoryGVK) - err := c.List(ctx, &u, client.InNamespace(namespace)) - Expect(err).NotTo(HaveOccurred()) - Expect(len(u.Items)).To(Equal(count)) -} - -func newApplierFromInvFactory(invFactory inventory.ClientFactory) *apply.Applier { - f := newFactory() - invClient, err := invFactory.NewClient(f) - Expect(err).NotTo(HaveOccurred()) - - a, err := apply.NewApplierBuilder(). - WithFactory(f). - WithInventoryClient(invClient). - Build() - Expect(err).NotTo(HaveOccurred()) - return a -} - -func newDestroyerFromInvFactory(invFactory inventory.ClientFactory) *apply.Destroyer { - f := newFactory() - invClient, err := invFactory.NewClient(f) - Expect(err).NotTo(HaveOccurred()) - - d, err := apply.NewDestroyer(f, invClient) - Expect(err).NotTo(HaveOccurred()) - return d -} diff --git a/test/e2e/common_test.go b/test/e2e/e2eutil/common.go similarity index 50% rename from test/e2e/common_test.go rename to test/e2e/e2eutil/common.go index e0652b65..b64b2ef5 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/e2eutil/common.go @@ -1,23 +1,22 @@ // Copyright 2020 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package e2e +package e2eutil import ( "bytes" "context" "fmt" - "strings" "text/template" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/cli-utils/pkg/apply/event" @@ -25,24 +24,25 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object/dependson" "sigs.k8s.io/cli-utils/pkg/object/mutation" + "sigs.k8s.io/cli-utils/test/e2e/customprovider" "sigs.k8s.io/controller-runtime/pkg/client" ) -func withReplicas(obj *unstructured.Unstructured, replicas int) *unstructured.Unstructured { +func WithReplicas(obj *unstructured.Unstructured, replicas int) *unstructured.Unstructured { err := unstructured.SetNestedField(obj.Object, int64(replicas), "spec", "replicas") - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) return obj } -func withNamespace(obj *unstructured.Unstructured, namespace string) *unstructured.Unstructured { +func WithNamespace(obj *unstructured.Unstructured, namespace string) *unstructured.Unstructured { obj.SetNamespace(namespace) return obj } -func podWithImage(obj *unstructured.Unstructured, containerName, image string) *unstructured.Unstructured { +func PodWithImage(obj *unstructured.Unstructured, containerName, image string) *unstructured.Unstructured { containers, found, err := unstructured.NestedSlice(obj.Object, "spec", "containers") - Expect(err).NotTo(HaveOccurred()) - Expect(found).To(BeTrue()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(found).To(gomega.BeTrue()) containerFound := false for i := range containers { @@ -54,26 +54,26 @@ func podWithImage(obj *unstructured.Unstructured, containerName, image string) * containerFound = true container["image"] = image } - Expect(containerFound).To(BeTrue()) + gomega.Expect(containerFound).To(gomega.BeTrue()) err = unstructured.SetNestedSlice(obj.Object, containers, "spec", "containers") - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) return obj } -func withNodeSelector(obj *unstructured.Unstructured, key, value string) *unstructured.Unstructured { +func WithNodeSelector(obj *unstructured.Unstructured, key, value string) *unstructured.Unstructured { selectors, found, err := unstructured.NestedMap(obj.Object, "spec", "nodeSelector") - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) if !found { selectors = make(map[string]interface{}) } selectors[key] = value err = unstructured.SetNestedMap(obj.Object, selectors, "spec", "nodeSelector") - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) return obj } -func withAnnotation(obj *unstructured.Unstructured, key, value string) *unstructured.Unstructured { +func WithAnnotation(obj *unstructured.Unstructured, key, value string) *unstructured.Unstructured { annotations := obj.GetAnnotations() if annotations == nil { annotations = make(map[string]string) @@ -83,7 +83,7 @@ func withAnnotation(obj *unstructured.Unstructured, key, value string) *unstruct return obj } -func withDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Unstructured { +func WithDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Unstructured { a := obj.GetAnnotations() if a == nil { a = make(map[string]string, 1) @@ -93,18 +93,18 @@ func withDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Uns return obj } -func deleteUnstructuredAndWait(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func DeleteUnstructuredAndWait(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) err := c.Delete(ctx, obj, client.PropagationPolicy(metav1.DeletePropagationForeground)) - Expect(err).NotTo(HaveOccurred(), + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "expected DELETE to not error (%s): %s", ref, err) - waitForDeletion(ctx, c, obj) + WaitForDeletion(ctx, c, obj) } -func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func WaitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) resultObj := ref.ToUnstructured() @@ -118,7 +118,7 @@ func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Uns for { select { case <-t.C: - Fail("timed out waiting for resource to be fully deleted") + ginkgo.Fail("timed out waiting for resource to be fully deleted") return case <-s.C: err := c.Get(ctx, types.NamespacedName{ @@ -126,7 +126,7 @@ func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Uns Name: obj.GetName(), }, resultObj) if err != nil { - Expect(apierrors.ReasonForError(err)).To(Equal(metav1.StatusReasonNotFound), + gomega.Expect(apierrors.ReasonForError(err)).To(gomega.Equal(metav1.StatusReasonNotFound), "expected GET to error with NotFound (%s): %s", ref, err) return } @@ -135,17 +135,17 @@ func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Uns } } -func createUnstructuredAndWait(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func CreateUnstructuredAndWait(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) err := c.Create(ctx, obj) - Expect(err).NotTo(HaveOccurred(), + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "expected CREATE to not error (%s): %s", ref, err) - waitForCreation(ctx, c, obj) + WaitForCreation(ctx, c, obj) } -func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func WaitForCreation(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) resultObj := ref.ToUnstructured() @@ -159,7 +159,7 @@ func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Uns for { select { case <-t.C: - Fail("timed out waiting for resource to be fully created") + ginkgo.Fail("timed out waiting for resource to be fully created") return case <-s.C: err := c.Get(ctx, types.NamespacedName{ @@ -169,7 +169,7 @@ func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Uns if err == nil { return } - Expect(apierrors.ReasonForError(err)).To(Equal(metav1.StatusReasonNotFound), + gomega.Expect(apierrors.ReasonForError(err)).To(gomega.Equal(metav1.StatusReasonNotFound), "expected GET to error with NotFound (%s): %s", ref, err) // if NotFound, sleep and retry s = time.NewTimer(retry) @@ -177,7 +177,7 @@ func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Uns } } -func assertUnstructuredExists(ctx context.Context, c client.Client, obj *unstructured.Unstructured) *unstructured.Unstructured { +func AssertUnstructuredExists(ctx context.Context, c client.Client, obj *unstructured.Unstructured) *unstructured.Unstructured { ref := mutation.ResourceReferenceFromUnstructured(obj) resultObj := ref.ToUnstructured() @@ -185,12 +185,12 @@ func assertUnstructuredExists(ctx context.Context, c client.Client, obj *unstruc Namespace: obj.GetNamespace(), Name: obj.GetName(), }, resultObj) - Expect(err).NotTo(HaveOccurred(), + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "expected GET not to error (%s): %s", ref, err) return resultObj } -func assertUnstructuredDoesNotExist(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func AssertUnstructuredDoesNotExist(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) resultObj := ref.ToUnstructured() @@ -198,13 +198,13 @@ func assertUnstructuredDoesNotExist(ctx context.Context, c client.Client, obj *u Namespace: obj.GetNamespace(), Name: obj.GetName(), }, resultObj) - Expect(err).To(HaveOccurred(), + gomega.Expect(err).To(gomega.HaveOccurred(), "expected GET to error (%s)", ref) - Expect(apierrors.ReasonForError(err)).To(Equal(metav1.StatusReasonNotFound), + gomega.Expect(apierrors.ReasonForError(err)).To(gomega.Equal(metav1.StatusReasonNotFound), "expected GET to error with NotFound (%s): %s", ref, err) } -func applyUnstructured(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { +func ApplyUnstructured(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) resultObj := ref.ToUnstructured() @@ -212,18 +212,18 @@ func applyUnstructured(ctx context.Context, c client.Client, obj *unstructured.U Namespace: obj.GetNamespace(), Name: obj.GetName(), }, resultObj) - Expect(err).NotTo(HaveOccurred(), + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "expected GET not to error (%s)", ref) err = c.Patch(ctx, obj, client.MergeFrom(resultObj)) - Expect(err).NotTo(HaveOccurred(), + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "expected PATCH not to error (%s): %s", ref, err) } -func assertUnstructuredAvailable(obj *unstructured.Unstructured) { +func AssertUnstructuredAvailable(obj *unstructured.Unstructured) { ref := mutation.ResourceReferenceFromUnstructured(obj) objc, err := status.GetObjectWithConditions(obj.Object) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) available := false for _, c := range objc.Status.Conditions { // appsv1.DeploymentAvailable && corev1.ConditionTrue @@ -232,16 +232,30 @@ func assertUnstructuredAvailable(obj *unstructured.Unstructured) { break } } - Expect(available).To(BeTrue(), + gomega.Expect(available).To(gomega.BeTrue(), "expected Available condition to be True (%s)", ref) } -func randomString(prefix string) string { +func AssertUnstructuredCount(ctx context.Context, c client.Client, obj *unstructured.Unstructured, count int) { + var u unstructured.UnstructuredList + u.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) + err := c.List(ctx, &u, + client.InNamespace(obj.GetNamespace()), + client.MatchingLabels(obj.GetLabels())) + if err != nil && count == 0 { + expectNotFoundError(err) + return + } + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(len(u.Items)).To(gomega.Equal(count), "unexpected number of %s", obj.GetKind()) +} + +func RandomString(prefix string) string { randomSuffix := common.RandomStr() return fmt.Sprintf("%s%s", prefix, randomSuffix) } -func run(ch <-chan event.Event) error { +func Run(ch <-chan event.Event) error { var err error for e := range ch { if e.Type == event.ErrorType { @@ -251,11 +265,11 @@ func run(ch <-chan event.Event) error { return err } -func runWithNoErr(ch <-chan event.Event) { - runCollectNoErr(ch) +func RunWithNoErr(ch <-chan event.Event) { + RunCollectNoErr(ch) } -func runCollect(ch <-chan event.Event) []event.Event { +func RunCollect(ch <-chan event.Event) []event.Event { var events []event.Event for e := range ch { events = append(events, e) @@ -263,53 +277,15 @@ func runCollect(ch <-chan event.Event) []event.Event { return events } -func runCollectNoErr(ch <-chan event.Event) []event.Event { - events := runCollect(ch) +func RunCollectNoErr(ch <-chan event.Event) []event.Event { + events := RunCollect(ch) for _, e := range events { - Expect(e.Type).NotTo(Equal(event.ErrorType)) + gomega.Expect(e.Type).NotTo(gomega.Equal(event.ErrorType)) } return events } -func cmInventoryManifest(name, namespace, id string) *unstructured.Unstructured { - cm := &v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1.SchemeGroupVersion.String(), - Kind: "ConfigMap", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{ - common.InventoryLabel: id, - }, - }, - } - u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm) - if err != nil { - panic(err) - } - return &unstructured.Unstructured{ - Object: u, - } -} - -func customInventoryManifest(name, namespace, id string) *unstructured.Unstructured { - u := manifestToUnstructured([]byte(strings.TrimSpace(` -apiVersion: cli-utils.example.io/v1alpha1 -kind: Inventory -metadata: - name: PLACEHOLDER -`))) - u.SetName(name) - u.SetNamespace(namespace) - u.SetLabels(map[string]string{ - common.InventoryLabel: id, - }) - return u -} - -func manifestToUnstructured(manifest []byte) *unstructured.Unstructured { +func ManifestToUnstructured(manifest []byte) *unstructured.Unstructured { u := make(map[string]interface{}) err := yaml.Unmarshal(manifest, &u) if err != nil { @@ -320,7 +296,7 @@ func manifestToUnstructured(manifest []byte) *unstructured.Unstructured { } } -func templateToUnstructured(tmpl string, data interface{}) *unstructured.Unstructured { +func TemplateToUnstructured(tmpl string, data interface{}) *unstructured.Unstructured { t, err := template.New("manifest").Parse(tmpl) if err != nil { panic(fmt.Errorf("failed to parse manifest go-template: %w", err)) @@ -330,5 +306,99 @@ func templateToUnstructured(tmpl string, data interface{}) *unstructured.Unstruc if err != nil { panic(fmt.Errorf("failed to execute manifest go-template: %w", err)) } - return manifestToUnstructured(buffer.Bytes()) + return ManifestToUnstructured(buffer.Bytes()) +} + +func CreateInventoryCRD(ctx context.Context, c client.Client) { + invCRD := ManifestToUnstructured(customprovider.InventoryCRD) + var u unstructured.Unstructured + u.SetGroupVersionKind(invCRD.GroupVersionKind()) + err := c.Get(ctx, types.NamespacedName{ + Name: invCRD.GetName(), + }, &u) + if apierrors.IsNotFound(err) { + err = c.Create(ctx, invCRD) + } + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +} + +func CreateRandomNamespace(ctx context.Context, c client.Client) *v1.Namespace { + namespaceName := RandomString("e2e-test-") + namespace := &v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + + err := c.Create(ctx, namespace) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + return namespace +} + +func DeleteInventoryCRD(ctx context.Context, c client.Client) { + invCRD := ManifestToUnstructured(customprovider.InventoryCRD) + DeleteUnstructuredIfExists(ctx, c, invCRD) +} + +func DeleteUnstructuredIfExists(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { + err := c.Delete(ctx, obj) + if err != nil { + expectNotFoundError(err) + } +} + +func DeleteAllUnstructuredIfExists(ctx context.Context, c client.Client, obj *unstructured.Unstructured) { + err := c.DeleteAllOf(ctx, obj, + client.InNamespace(obj.GetNamespace()), + client.MatchingLabels(obj.GetLabels())) + if err != nil { + expectNotFoundError(err) + } +} + +func DeleteNamespace(ctx context.Context, c client.Client, namespace *v1.Namespace) { + err := c.Delete(ctx, namespace) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) +} + +func UnstructuredExistsAndIsNotTerminating(ctx context.Context, c client.Client, obj *unstructured.Unstructured) bool { + serverObj := obj.DeepCopy() + err := c.Get(ctx, types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + }, serverObj) + if err != nil { + expectNotFoundError(err) + return false + } + return !UnstructuredIsTerminating(serverObj) +} + +func expectNotFoundError(err error) { + gomega.Expect(err).To(gomega.Or( + gomega.BeAssignableToTypeOf(&meta.NoKindMatchError{}), + gomega.BeAssignableToTypeOf(&apierrors.StatusError{}), + )) + if se, ok := err.(*apierrors.StatusError); ok { + gomega.Expect(se.ErrStatus.Reason).To(gomega.Or( + gomega.Equal(metav1.StatusReasonNotFound), + // custom resources dissalow deletion if the CRD is terminating + gomega.Equal(metav1.StatusReasonMethodNotAllowed), + )) + } +} + +func UnstructuredIsTerminating(obj *unstructured.Unstructured) bool { + objc, err := status.GetObjectWithConditions(obj.Object) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + for _, c := range objc.Status.Conditions { + if c.Type == "Terminating" && c.Status == "True" { + return true + } + } + return false } diff --git a/test/e2e/empty_set_test.go b/test/e2e/empty_set_test.go index 287bcfd3..437702ca 100644 --- a/test/e2e/empty_set_test.go +++ b/test/e2e/empty_set_test.go @@ -14,11 +14,13 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) //nolint:dupl // expEvents similar to other tests -func emptySetTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func emptySetTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply zero resources") applier := invConfig.ApplierFactoryFunc() @@ -27,7 +29,7 @@ func emptySetTest(ctx context.Context, c client.Client, invConfig InventoryConfi resources := []*unstructured.Unstructured{} - applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ EmitStatusEvents: true, })) @@ -83,7 +85,7 @@ func emptySetTest(ctx context.Context, c client.Client, invConfig InventoryConfi destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, options)) expEvents = []testutil.ExpEvent{ { diff --git a/test/e2e/exit_early_test.go b/test/e2e/exit_early_test.go index 2219d296..bbf2a86e 100644 --- a/test/e2e/exit_early_test.go +++ b/test/e2e/exit_early_test.go @@ -16,10 +16,12 @@ import ( "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func exitEarlyTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("exit early on invalid object") applier := invConfig.ApplierFactoryFunc() @@ -27,12 +29,12 @@ func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConf fields := struct{ Namespace string }{Namespace: namespaceName} // valid pod - pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) + pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName) // valid deployment with dependency - deployment1Obj := withDependsOn(withNamespace(manifestToUnstructured(deployment1), namespaceName), + deployment1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/%s", namespaceName, pod1Obj.GetName())) // missing name - invalidPodObj := templateToUnstructured(invalidPodTemplate, fields) + invalidPodObj := e2eutil.TemplateToUnstructured(invalidPodTemplate, fields) resources := []*unstructured.Unstructured{ pod1Obj, @@ -40,7 +42,7 @@ func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConf invalidPodObj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, ValidationPolicy: validation.ExitEarly, })) @@ -60,8 +62,8 @@ func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConf Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify pod1 not found") - assertUnstructuredDoesNotExist(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj) By("verify deployment1 not found") - assertUnstructuredDoesNotExist(ctx, c, deployment1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, deployment1Obj) } diff --git a/test/e2e/invconfig/configmap.go b/test/e2e/invconfig/configmap.go new file mode 100644 index 00000000..fce095e1 --- /dev/null +++ b/test/e2e/invconfig/configmap.go @@ -0,0 +1,80 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package invconfig + +import ( + "context" + + "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/cli-utils/pkg/apply" + "sigs.k8s.io/cli-utils/pkg/common" + "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewConfigMapTypeInvConfig(cfg *rest.Config) InventoryConfig { + return InventoryConfig{ + ClientConfig: cfg, + Strategy: inventory.LabelStrategy, + FactoryFunc: cmInventoryManifest, + InvWrapperFunc: inventory.WrapInventoryInfoObj, + ApplierFactoryFunc: newDefaultInvApplierFactory(cfg), + DestroyerFactoryFunc: newDefaultInvDestroyerFactory(cfg), + InvSizeVerifyFunc: defaultInvSizeVerifyFunc, + InvCountVerifyFunc: defaultInvCountVerifyFunc, + InvNotExistsFunc: defaultInvNotExistsFunc, + } +} + +func newDefaultInvApplierFactory(cfg *rest.Config) applierFactoryFunc { + cfgPtrCopy := cfg + return func() *apply.Applier { + return newApplier(inventory.ClusterClientFactory{ + StatusPolicy: inventory.StatusPolicyAll, + }, cfgPtrCopy) + } +} + +func newDefaultInvDestroyerFactory(cfg *rest.Config) destroyerFactoryFunc { + cfgPtrCopy := cfg + return func() *apply.Destroyer { + return newDestroyer(inventory.ClusterClientFactory{ + StatusPolicy: inventory.StatusPolicyAll, + }, cfgPtrCopy) + } +} + +func defaultInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) { + var cmList v1.ConfigMapList + err := c.List(ctx, &cmList, + client.MatchingLabels(map[string]string{common.InventoryLabel: id}), + client.InNamespace(namespace)) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + gomega.Expect(cmList.Items).To(gomega.HaveLen(0), "expected inventory list to be empty") +} + +func defaultInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, id string, specCount, _ int) { + var cmList v1.ConfigMapList + err := c.List(ctx, &cmList, + client.MatchingLabels(map[string]string{common.InventoryLabel: id}), + client.InNamespace(namespace)) + gomega.Expect(err).WithOffset(1).ToNot(gomega.HaveOccurred(), "listing ConfigMap inventory from cluster") + + gomega.Expect(len(cmList.Items)).WithOffset(1).To(gomega.Equal(1), "number of inventory objects by label") + + data := cmList.Items[0].Data + gomega.Expect(len(data)).WithOffset(1).To(gomega.Equal(specCount), "inventory spec.data length") + + // Don't validate status size. + // ConfigMap provider uses inventory.StatusPolicyNone. +} + +func defaultInvCountVerifyFunc(ctx context.Context, c client.Client, namespace string, count int) { + var cmList v1.ConfigMapList + err := c.List(ctx, &cmList, client.InNamespace(namespace), client.HasLabels{common.InventoryLabel}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(len(cmList.Items)).To(gomega.Equal(count)) +} diff --git a/test/e2e/invconfig/custom.go b/test/e2e/invconfig/custom.go new file mode 100644 index 00000000..c0deb0dd --- /dev/null +++ b/test/e2e/invconfig/custom.go @@ -0,0 +1,131 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package invconfig + +import ( + "context" + "strings" + + "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "sigs.k8s.io/cli-utils/pkg/apply" + "sigs.k8s.io/cli-utils/pkg/common" + "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/test/e2e/customprovider" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewCustomTypeInvConfig(cfg *rest.Config) InventoryConfig { + return InventoryConfig{ + ClientConfig: cfg, + Strategy: inventory.NameStrategy, + FactoryFunc: customInventoryManifest, + InvWrapperFunc: customprovider.WrapInventoryInfoObj, + ApplierFactoryFunc: newCustomInvApplierFactory(cfg), + DestroyerFactoryFunc: newCustomInvDestroyerFactory(cfg), + InvSizeVerifyFunc: customInvSizeVerifyFunc, + InvCountVerifyFunc: customInvCountVerifyFunc, + InvNotExistsFunc: customInvNotExistsFunc, + } +} + +func newCustomInvApplierFactory(cfg *rest.Config) applierFactoryFunc { + cfgPtrCopy := cfg + return func() *apply.Applier { + return newApplier(customprovider.CustomClientFactory{}, cfgPtrCopy) + } +} + +func newCustomInvDestroyerFactory(cfg *rest.Config) destroyerFactoryFunc { + cfgPtrCopy := cfg + return func() *apply.Destroyer { + return newDestroyer(customprovider.CustomClientFactory{}, cfgPtrCopy) + } +} + +func customInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) { + var u unstructured.Unstructured + u.SetGroupVersionKind(customprovider.InventoryGVK) + u.SetName(name) + u.SetNamespace(namespace) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, &u) +} + +func customInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, _ string, specCount, statusCount int) { + var u unstructured.Unstructured + u.SetGroupVersionKind(customprovider.InventoryGVK) + err := c.Get(ctx, types.NamespacedName{ + Name: name, + Namespace: namespace, + }, &u) + gomega.Expect(err).WithOffset(1).ToNot(gomega.HaveOccurred(), "getting custom inventory from cluster") + + s, found, err := unstructured.NestedSlice(u.Object, "spec", "objects") + gomega.Expect(err).WithOffset(1).ToNot(gomega.HaveOccurred(), "reading inventory spec.objects") + if found { + gomega.Expect(len(s)).WithOffset(1).To(gomega.Equal(specCount), "inventory status.objects length") + } else { + gomega.Expect(specCount).WithOffset(1).To(gomega.Equal(0), "inventory spec.objects not found") + } + + s, found, err = unstructured.NestedSlice(u.Object, "status", "objects") + gomega.Expect(err).WithOffset(1).ToNot(gomega.HaveOccurred(), "reading inventory status.objects") + if found { + gomega.Expect(len(s)).WithOffset(1).To(gomega.Equal(statusCount), "inventory status.objects length") + } else { + gomega.Expect(statusCount).WithOffset(1).To(gomega.Equal(0), "inventory status.objects not found") + } +} + +func customInvCountVerifyFunc(ctx context.Context, c client.Client, namespace string, count int) { + var u unstructured.UnstructuredList + u.SetGroupVersionKind(customprovider.InventoryGVK) + err := c.List(ctx, &u, client.InNamespace(namespace)) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(len(u.Items)).To(gomega.Equal(count)) +} + +func cmInventoryManifest(name, namespace, id string) *unstructured.Unstructured { + cm := &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + common.InventoryLabel: id, + }, + }, + } + u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cm) + if err != nil { + panic(err) + } + return &unstructured.Unstructured{ + Object: u, + } +} + +func customInventoryManifest(name, namespace, id string) *unstructured.Unstructured { + u := e2eutil.ManifestToUnstructured([]byte(strings.TrimSpace(` +apiVersion: cli-utils.example.io/v1alpha1 +kind: Inventory +metadata: + name: PLACEHOLDER +`))) + u.SetName(name) + u.SetNamespace(namespace) + u.SetLabels(map[string]string{ + common.InventoryLabel: id, + }) + return u +} diff --git a/test/e2e/invconfig/invconfig.go b/test/e2e/invconfig/invconfig.go new file mode 100644 index 00000000..c9ebc48f --- /dev/null +++ b/test/e2e/invconfig/invconfig.go @@ -0,0 +1,129 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package invconfig + +import ( + "context" + "fmt" + + "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/rest" + "k8s.io/kubectl/pkg/cmd/util" + "sigs.k8s.io/cli-utils/pkg/apply" + "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type inventoryFactoryFunc func(name, namespace, id string) *unstructured.Unstructured +type invWrapperFunc func(*unstructured.Unstructured) inventory.Info +type applierFactoryFunc func() *apply.Applier +type destroyerFactoryFunc func() *apply.Destroyer +type invSizeVerifyFunc func(ctx context.Context, c client.Client, name, namespace, id string, specCount, statusCount int) +type invCountVerifyFunc func(ctx context.Context, c client.Client, namespace string, count int) +type invNotExistsFunc func(ctx context.Context, c client.Client, name, namespace, id string) + +type InventoryConfig struct { + ClientConfig *rest.Config + Strategy inventory.Strategy + FactoryFunc inventoryFactoryFunc + InvWrapperFunc invWrapperFunc + ApplierFactoryFunc applierFactoryFunc + DestroyerFactoryFunc destroyerFactoryFunc + InvSizeVerifyFunc invSizeVerifyFunc + InvCountVerifyFunc invCountVerifyFunc + InvNotExistsFunc invNotExistsFunc +} + +func CreateInventoryInfo(invConfig InventoryConfig, inventoryName, namespaceName, inventoryID string) inventory.Info { + switch invConfig.Strategy { + case inventory.NameStrategy: + return invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, e2eutil.RandomString("inventory-"))) + case inventory.LabelStrategy: + return invConfig.InvWrapperFunc(invConfig.FactoryFunc(e2eutil.RandomString("inventory-"), namespaceName, inventoryID)) + default: + panic(fmt.Errorf("unknown inventory strategy %q", invConfig.Strategy)) + } +} + +func newFactory(cfg *rest.Config) util.Factory { + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + cfgPtrCopy := cfg + kubeConfigFlags.WrapConfigFn = func(c *rest.Config) *rest.Config { + // update rest.Config to pick up QPS & timeout changes + deepCopyRESTConfig(cfgPtrCopy, c) + return c + } + matchVersionKubeConfigFlags := util.NewMatchVersionFlags(kubeConfigFlags) + return util.NewFactory(matchVersionKubeConfigFlags) +} + +func newApplier(invFactory inventory.ClientFactory, cfg *rest.Config) *apply.Applier { + f := newFactory(cfg) + invClient, err := invFactory.NewClient(f) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + a, err := apply.NewApplierBuilder(). + WithFactory(f). + WithInventoryClient(invClient). + Build() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + return a +} + +func newDestroyer(invFactory inventory.ClientFactory, cfg *rest.Config) *apply.Destroyer { + f := newFactory(cfg) + invClient, err := invFactory.NewClient(f) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + d, err := apply.NewDestroyer(f, invClient) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + return d +} + +func deepCopyRESTConfig(from, to *rest.Config) { + to.Host = from.Host + to.APIPath = from.APIPath + to.ContentConfig = from.ContentConfig + to.Username = from.Username + to.Password = from.Password + to.BearerToken = from.BearerToken + to.BearerTokenFile = from.BearerTokenFile + to.Impersonate = rest.ImpersonationConfig{ + UserName: from.Impersonate.UserName, + UID: from.Impersonate.UID, + Groups: from.Impersonate.Groups, + Extra: from.Impersonate.Extra, + } + to.AuthProvider = from.AuthProvider + to.AuthConfigPersister = from.AuthConfigPersister + to.ExecProvider = from.ExecProvider + if from.ExecProvider != nil && from.ExecProvider.Config != nil { + to.ExecProvider.Config = from.ExecProvider.Config.DeepCopyObject() + } + to.TLSClientConfig = rest.TLSClientConfig{ + Insecure: from.TLSClientConfig.Insecure, + ServerName: from.TLSClientConfig.ServerName, + CertFile: from.TLSClientConfig.CertFile, + KeyFile: from.TLSClientConfig.KeyFile, + CAFile: from.TLSClientConfig.CAFile, + CertData: from.TLSClientConfig.CertData, + KeyData: from.TLSClientConfig.KeyData, + CAData: from.TLSClientConfig.CAData, + NextProtos: from.TLSClientConfig.NextProtos, + } + to.UserAgent = from.UserAgent + to.DisableCompression = from.DisableCompression + to.Transport = from.Transport + to.WrapTransport = from.WrapTransport + to.QPS = from.QPS + to.Burst = from.Burst + to.RateLimiter = from.RateLimiter + to.WarningHandler = from.WarningHandler + to.Timeout = from.Timeout + to.Dial = from.Dial + to.Proxy = from.Proxy +} diff --git a/test/e2e/inventory_policy_test.go b/test/e2e/inventory_policy_test.go index 61110256..281c82e0 100644 --- a/test/e2e/inventory_policy_test.go +++ b/test/e2e/inventory_policy_test.go @@ -17,34 +17,36 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfig InventoryConfig, namespaceName string) { +func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, namespaceName string) { By("Apply first set of resources") applier := invConfig.ApplierFactoryFunc() - firstInvName := randomString("first-inv-") + firstInvName := e2eutil.RandomString("first-inv-") firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName)) - deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) firstResources := []*unstructured.Unstructured{ deployment1Obj, } - runWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ + e2eutil.RunWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, })) By("Apply second set of resources") - secondInvName := randomString("second-inv-") + secondInvName := e2eutil.RandomString("second-inv-") secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName)) - deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) secondResources := []*unstructured.Unstructured{ - withReplicas(deployment1Obj, 6), + e2eutil.WithReplicas(deployment1Obj, 6), } - applierEvents := runCollect(applier.Run(ctx, secondInv, secondResources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, secondInv, secondResources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, InventoryPolicy: inventory.PolicyMustMatch, @@ -178,7 +180,7 @@ func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfi Expect(received).To(testutil.Equal(expEvents)) By("Verify resource wasn't updated") - result := assertUnstructuredExists(ctx, c, deployment1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) replicas, found, err := object.NestedField(result.Object, "spec", "replicas") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) @@ -187,22 +189,22 @@ func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfi invConfig.InvCountVerifyFunc(ctx, c, namespaceName, 2) } -func inventoryPolicyAdoptIfNoInventoryTest(ctx context.Context, c client.Client, invConfig InventoryConfig, namespaceName string) { +func inventoryPolicyAdoptIfNoInventoryTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, namespaceName string) { By("Create unmanaged resource") - deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) - createUnstructuredAndWait(ctx, c, deployment1Obj) + deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) + e2eutil.CreateUnstructuredAndWait(ctx, c, deployment1Obj) By("Apply resources") applier := invConfig.ApplierFactoryFunc() - invName := randomString("test-inv-") + invName := e2eutil.RandomString("test-inv-") inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(invName, namespaceName, invName)) - deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) resources := []*unstructured.Unstructured{ - withReplicas(deployment1Obj, 6), + e2eutil.WithReplicas(deployment1Obj, 6), } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, InventoryPolicy: inventory.PolicyAdoptIfNoInventory, @@ -344,7 +346,7 @@ func inventoryPolicyAdoptIfNoInventoryTest(ctx context.Context, c client.Client, Expect(received).To(testutil.Equal(expEvents)) By("Verify resource was updated and added to inventory") - result := assertUnstructuredExists(ctx, c, deployment1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) replicas, found, err := object.NestedField(result.Object, "spec", "replicas") Expect(err).NotTo(HaveOccurred()) @@ -360,31 +362,31 @@ func inventoryPolicyAdoptIfNoInventoryTest(ctx context.Context, c client.Client, invConfig.InvSizeVerifyFunc(ctx, c, invName, namespaceName, invName, 1, 1) } -func inventoryPolicyAdoptAllTest(ctx context.Context, c client.Client, invConfig InventoryConfig, namespaceName string) { +func inventoryPolicyAdoptAllTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, namespaceName string) { By("Apply an initial set of resources") applier := invConfig.ApplierFactoryFunc() - firstInvName := randomString("first-inv-") + firstInvName := e2eutil.RandomString("first-inv-") firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName)) - deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) firstResources := []*unstructured.Unstructured{ deployment1Obj, } - runWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ + e2eutil.RunWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, })) By("Apply resources") - secondInvName := randomString("test-inv-") + secondInvName := e2eutil.RandomString("test-inv-") secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName)) - deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) + deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName) secondResources := []*unstructured.Unstructured{ - withReplicas(deployment1Obj, 6), + e2eutil.WithReplicas(deployment1Obj, 6), } - applierEvents := runCollect(applier.Run(ctx, secondInv, secondResources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, secondInv, secondResources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, InventoryPolicy: inventory.PolicyAdoptAll, @@ -526,7 +528,7 @@ func inventoryPolicyAdoptAllTest(ctx context.Context, c client.Client, invConfig Expect(received).To(testutil.Equal(expEvents)) By("Verify resource was updated and added to inventory") - result := assertUnstructuredExists(ctx, c, deployment1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) replicas, found, err := object.NestedField(result.Object, "spec", "replicas") Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/mutation_test.go b/test/e2e/mutation_test.go index fe95f97e..ce4434f0 100644 --- a/test/e2e/mutation_test.go +++ b/test/e2e/mutation_test.go @@ -15,6 +15,8 @@ import ( "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -32,15 +34,15 @@ import ( // port from pod-b into an environment variable of pod-a. //nolint:dupl // expEvents similar to CRD tests -func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func mutationTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply resources in order with substitutions based on apply-time-mutation annotation") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) fields := struct{ Namespace string }{Namespace: namespaceName} - podAObj := templateToUnstructured(podATemplate, fields) - podBObj := templateToUnstructured(podBTemplate, fields) + podAObj := e2eutil.TemplateToUnstructured(podATemplate, fields) + podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields) // Dependency order: podA -> podB // Apply order: podB, podA @@ -49,7 +51,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi podBObj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -227,7 +229,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify podB is created and ready") - result := assertUnstructuredExists(ctx, c, podBObj) + result := e2eutil.AssertUnstructuredExists(ctx, c, podBObj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) @@ -242,7 +244,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi host := fmt.Sprintf("%s:%d", podIP, containerPort) By("verify podA is mutated, created, and ready") - result = assertUnstructuredExists(ctx, c, podAObj) + result = e2eutil.AssertUnstructuredExists(ctx, c, podAObj) podIP, found, err = object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) @@ -257,7 +259,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi By("destroy resources in opposite order") destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options)) expEvents = []testutil.ExpEvent{ { @@ -416,8 +418,8 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents)) By("verify podB deleted") - assertUnstructuredDoesNotExist(ctx, c, podBObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj) By("verify podA deleted") - assertUnstructuredDoesNotExist(ctx, c, podAObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj) } diff --git a/test/e2e/name_inv_strategy_test.go b/test/e2e/name_inv_strategy_test.go index a9daf7a3..34aec31c 100644 --- a/test/e2e/name_inv_strategy_test.go +++ b/test/e2e/name_inv_strategy_test.go @@ -12,10 +12,12 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/cli-utils/pkg/apply" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func applyWithExistingInvTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func applyWithExistingInvTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply first set of resources") applier := invConfig.ApplierFactoryFunc() orgInventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) @@ -23,10 +25,10 @@ func applyWithExistingInvTest(ctx context.Context, c client.Client, invConfig In orgApplyInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, orgInventoryID)) resources := []*unstructured.Unstructured{ - withNamespace(manifestToUnstructured(deployment1), namespaceName), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), } - runWithNoErr(applier.Run(ctx, orgApplyInv, resources, apply.ApplierOptions{ + e2eutil.RunWithNoErr(applier.Run(ctx, orgApplyInv, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, })) @@ -38,7 +40,7 @@ func applyWithExistingInvTest(ctx context.Context, c client.Client, invConfig In secondInventoryID := fmt.Sprintf("%s-%s-2", inventoryName, namespaceName) secondApplyInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, secondInventoryID)) - err := run(applier.Run(ctx, secondApplyInv, resources, apply.ApplierOptions{ + err := e2eutil.Run(applier.Run(ctx, secondApplyInv, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, })) diff --git a/test/e2e/namespace_filter_test.go b/test/e2e/namespace_filter_test.go index 598f302a..084a3024 100644 --- a/test/e2e/namespace_filter_test.go +++ b/test/e2e/namespace_filter_test.go @@ -14,11 +14,13 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) //nolint:dupl // expEvents similar to other tests -func namespaceFilterTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func namespaceFilterTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply resources in order based on depends-on annotation") applier := invConfig.ApplierFactoryFunc() @@ -27,8 +29,8 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento namespace1Name := fmt.Sprintf("%s-ns1", namespaceName) fields := struct{ Namespace string }{Namespace: namespace1Name} - namespace1Obj := templateToUnstructured(namespaceTemplate, fields) - podBObj := templateToUnstructured(podBTemplate, fields) + namespace1Obj := e2eutil.TemplateToUnstructured(namespaceTemplate, fields) + podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields) // Dependency order: podB -> namespace1 // Apply order: namespace1, podB @@ -39,11 +41,11 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento // Cleanup defer func(ctx context.Context, c client.Client) { - deleteUnstructuredIfExists(ctx, c, podBObj) - deleteUnstructuredIfExists(ctx, c, namespace1Obj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, podBObj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, namespace1Obj) }(ctx, c) - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -221,10 +223,10 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify namespace1 created") - assertUnstructuredExists(ctx, c, namespace1Obj) + e2eutil.AssertUnstructuredExists(ctx, c, namespace1Obj) By("verify podB created and ready") - result := assertUnstructuredExists(ctx, c, podBObj) + result := e2eutil.AssertUnstructuredExists(ctx, c, podBObj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) @@ -235,7 +237,7 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento podBObj, } - applierEvents = runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents = e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -395,13 +397,13 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify namespace1 not deleted") - result = assertUnstructuredExists(ctx, c, namespace1Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, namespace1Obj) ts, found, err := object.NestedField(result.Object, "metadata", "deletionTimestamp") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) By("verify podB not deleted") - result = assertUnstructuredExists(ctx, c, podBObj) + result = e2eutil.AssertUnstructuredExists(ctx, c, podBObj) ts, found, err = object.NestedField(result.Object, "metadata", "deletionTimestamp") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) diff --git a/test/e2e/prune_retrieve_error_test.go b/test/e2e/prune_retrieve_error_test.go index 998d7569..e31695d9 100644 --- a/test/e2e/prune_retrieve_error_test.go +++ b/test/e2e/prune_retrieve_error_test.go @@ -15,23 +15,25 @@ import ( "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply a single resource, which is referenced in the inventory") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inv := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inv := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) - pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) + pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName) resource1 := []*unstructured.Unstructured{ pod1Obj, } - applierEvents := runCollect(applier.Run(ctx, inv, resource1, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resource1, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -145,7 +147,7 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("Verify pod1 created and ready") - result := assertUnstructuredExists(ctx, c, pod1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) @@ -153,19 +155,19 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve // Delete the previously applied resource, which is referenced in the inventory. By("delete resource, which is referenced in the inventory") - deleteUnstructuredAndWait(ctx, c, pod1Obj) + e2eutil.DeleteUnstructuredAndWait(ctx, c, pod1Obj) By("Verify inventory") // The inventory should still have the previously deleted item. invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1) By("apply a different resource, and validate the inventory accurately reflects only this object") - pod2Obj := withNamespace(manifestToUnstructured(pod2), namespaceName) + pod2Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName) resource2 := []*unstructured.Unstructured{ pod2Obj, } - applierEvents2 := runCollect(applier.Run(ctx, inv, resource2, apply.ApplierOptions{ + applierEvents2 := e2eutil.RunCollect(applier.Run(ctx, inv, resource2, apply.ApplierOptions{ EmitStatusEvents: false, })) @@ -280,14 +282,14 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve Expect(testutil.EventsToExpEvents(applierEvents2)).To(testutil.Equal(expEvents2)) By("Verify pod2 created and ready") - result = assertUnstructuredExists(ctx, c, pod2Obj) + result = e2eutil.AssertUnstructuredExists(ctx, c, pod2Obj) podIP, found, err = object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness By("Verify pod1 still deleted") - assertUnstructuredDoesNotExist(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj) By("Verify inventory") // The inventory should only have the currently applied item. @@ -297,7 +299,7 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve destroyer := invConfig.DestroyerFactoryFunc() options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} - destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options)) expEvents3 := []testutil.ExpEvent{ { @@ -391,8 +393,8 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents3)) By("Verify pod1 is deleted") - assertUnstructuredDoesNotExist(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj) By("Verify pod2 is deleted") - assertUnstructuredDoesNotExist(ctx, c, pod2Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod2Obj) } diff --git a/test/e2e/reconcile_failed_timeout_test.go b/test/e2e/reconcile_failed_timeout_test.go index d8d0655e..9c3d8cd9 100644 --- a/test/e2e/reconcile_failed_timeout_test.go +++ b/test/e2e/reconcile_failed_timeout_test.go @@ -15,21 +15,23 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" ) -func reconciliationFailed(ctx context.Context, invConfig InventoryConfig, inventoryName, namespaceName string) { +func reconciliationFailed(ctx context.Context, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply resources") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) - podObj := withNodeSelector(withNamespace(manifestToUnstructured(pod1), namespaceName), "foo", "bar") + podObj := e2eutil.WithNodeSelector(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), "foo", "bar") resources := []*unstructured.Unstructured{ podObj, } - applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: false, })) @@ -40,19 +42,19 @@ func reconciliationFailed(ctx context.Context, invConfig InventoryConfig, invent Expect(received).To(testutil.Equal(expEvents)) } -func reconciliationTimeout(ctx context.Context, invConfig InventoryConfig, inventoryName, namespaceName string) { +func reconciliationTimeout(ctx context.Context, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply resources") applier := invConfig.ApplierFactoryFunc() inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) - inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) - podObj := podWithImage(withNamespace(manifestToUnstructured(pod1), namespaceName), "kubernetes-pause", "does-not-exist") + podObj := e2eutil.PodWithImage(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), "kubernetes-pause", "does-not-exist") resources := []*unstructured.Unstructured{ podObj, } - applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ ReconcileTimeout: 30 * time.Second, EmitStatusEvents: false, })) diff --git a/test/e2e/serverside_apply_test.go b/test/e2e/serverside_apply_test.go index 05a0476e..713d2c85 100644 --- a/test/e2e/serverside_apply_test.go +++ b/test/e2e/serverside_apply_test.go @@ -14,20 +14,22 @@ import ( "sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func serversideApplyTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func serversideApplyTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("Apply a Deployment and an APIService by server-side apply") applier := invConfig.ApplierFactoryFunc() inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) firstResources := []*unstructured.Unstructured{ - withNamespace(manifestToUnstructured(deployment1), namespaceName), - manifestToUnstructured(apiservice1), + e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), + e2eutil.ManifestToUnstructured(apiservice1), } - runWithNoErr(applier.Run(ctx, inv, firstResources, apply.ApplierOptions{ + e2eutil.RunWithNoErr(applier.Run(ctx, inv, firstResources, apply.ApplierOptions{ ReconcileTimeout: 2 * time.Minute, EmitStatusEvents: true, ServerSideOptions: common.ServerSideOptions{ @@ -38,7 +40,7 @@ func serversideApplyTest(ctx context.Context, c client.Client, invConfig Invento })) By("Verify deployment is server-side applied") - result := assertUnstructuredExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespaceName)) + result := e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)) // LastAppliedConfigAnnotation annotation is only set for client-side apply and we've server-side applied here. _, found, err := object.NestedField(result.Object, "metadata", "annotations", v1.LastAppliedConfigAnnotation) @@ -51,7 +53,7 @@ func serversideApplyTest(ctx context.Context, c client.Client, invConfig Invento Expect(manager).To(Equal("test")) By("Verify APIService is server-side applied") - result = assertUnstructuredExists(ctx, c, manifestToUnstructured(apiservice1)) + result = e2eutil.AssertUnstructuredExists(ctx, c, e2eutil.ManifestToUnstructured(apiservice1)) // LastAppliedConfigAnnotation annotation is only set for client-side apply and we've server-side applied here. _, found, err = object.NestedField(result.Object, "metadata", "annotations", v1.LastAppliedConfigAnnotation) diff --git a/test/e2e/skip_invalid_test.go b/test/e2e/skip_invalid_test.go index 08000f97..9027a5fb 100644 --- a/test/e2e/skip_invalid_test.go +++ b/test/e2e/skip_invalid_test.go @@ -21,10 +21,12 @@ import ( "sigs.k8s.io/cli-utils/pkg/object/mutation" "sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/testutil" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" "sigs.k8s.io/controller-runtime/pkg/client" ) -func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) { +func skipInvalidTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { By("apply valid objects and skip invalid objects") applier := invConfig.ApplierFactoryFunc() @@ -32,19 +34,19 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo fields := struct{ Namespace string }{Namespace: namespaceName} // valid pod - pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) + pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName) // valid deployment with dependency - deployment1Obj := withDependsOn(withNamespace(manifestToUnstructured(deployment1), namespaceName), + deployment1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/%s", namespaceName, pod1Obj.GetName())) // external/missing dependency - pod3Obj := withDependsOn(withNamespace(manifestToUnstructured(pod3), namespaceName), + pod3Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod3), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod0", namespaceName)) // cyclic dependency (podB) - podAObj := templateToUnstructured(podATemplate, fields) + podAObj := e2eutil.TemplateToUnstructured(podATemplate, fields) // cyclic dependency (podA) & invalid source reference (dependency not in object set) - podBObj := templateToUnstructured(invalidMutationPodBTemplate, fields) + podBObj := e2eutil.TemplateToUnstructured(invalidMutationPodBTemplate, fields) // missing name - invalidPodObj := templateToUnstructured(invalidPodTemplate, fields) + invalidPodObj := e2eutil.TemplateToUnstructured(invalidPodTemplate, fields) resources := []*unstructured.Unstructured{ pod1Obj, @@ -55,7 +57,7 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo invalidPodObj, } - applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ EmitStatusEvents: false, ValidationPolicy: validation.SkipInvalid, })) @@ -320,31 +322,31 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) By("verify pod1 created and ready") - result := assertUnstructuredExists(ctx, c, pod1Obj) + result := e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj) podIP, found, err := object.NestedField(result.Object, "status", "podIP") Expect(err).NotTo(HaveOccurred()) Expect(found).To(BeTrue()) Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness By("verify deployment1 created and ready") - result = assertUnstructuredExists(ctx, c, deployment1Obj) - assertUnstructuredAvailable(result) + result = e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) + e2eutil.AssertUnstructuredAvailable(result) By("verify pod3 not found") - assertUnstructuredDoesNotExist(ctx, c, pod3Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj) By("verify podA not found") - assertUnstructuredDoesNotExist(ctx, c, podAObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj) By("verify podB not found") - assertUnstructuredDoesNotExist(ctx, c, podBObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj) By("modify deployment1 depends-on annotation to be invalid") - applyUnstructured(ctx, c, withDependsOn(deployment1Obj, "invalid")) + e2eutil.ApplyUnstructured(ctx, c, e2eutil.WithDependsOn(deployment1Obj, "invalid")) By("destroy valid objects and skip invalid objects") destroyer := invConfig.DestroyerFactoryFunc() - destroyerEvents := runCollect(destroyer.Run(ctx, inv, apply.DestroyerOptions{ + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, apply.DestroyerOptions{ InventoryPolicy: inventory.PolicyAdoptIfNoInventory, ValidationPolicy: validation.SkipInvalid, })) @@ -453,18 +455,18 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents)) By("verify pod1 deleted") - assertUnstructuredDoesNotExist(ctx, c, pod1Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj) By("verify deployment1 not deleted") - assertUnstructuredExists(ctx, c, deployment1Obj) - deleteUnstructuredIfExists(ctx, c, deployment1Obj) + e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj) + e2eutil.DeleteUnstructuredIfExists(ctx, c, deployment1Obj) By("verify pod3 not found") - assertUnstructuredDoesNotExist(ctx, c, pod3Obj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj) By("verify podA not found") - assertUnstructuredDoesNotExist(ctx, c, podAObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj) By("verify podB not found") - assertUnstructuredDoesNotExist(ctx, c, podBObj) + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj) } diff --git a/test/stress/artifacts_test.go b/test/stress/artifacts_test.go new file mode 100644 index 00000000..75ad71bf --- /dev/null +++ b/test/stress/artifacts_test.go @@ -0,0 +1,67 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package stress + +var namespaceYaml = ` +apiVersion: v1 +kind: Namespace +metadata: + name: "" +` + +var configMapYaml = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: "" + namespace: "" +data: {} +` + +var cronTabCRDYaml = ` +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: crontabs.stable.example.com +spec: + group: stable.example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + properties: + cronSpec: + type: string + image: + type: string + scope: Namespaced + names: + plural: crontabs + singular: crontab + kind: CronTab + shortNames: + - ct +` + +var cronTabYaml = ` +apiVersion: stable.example.com/v1 +kind: CronTab +metadata: + name: "" + namespace: "" +spec: + cronSpec: "* * * * */5" +` diff --git a/test/stress/e2e_suite_test.go b/test/stress/e2e_suite_test.go new file mode 100644 index 00000000..fae015cb --- /dev/null +++ b/test/stress/e2e_suite_test.go @@ -0,0 +1,16 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package stress + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestE2e(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Stress Test Suite") +} diff --git a/test/stress/stress_test.go b/test/stress/stress_test.go new file mode 100644 index 00000000..79946ffa --- /dev/null +++ b/test/stress/stress_test.go @@ -0,0 +1,102 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package stress + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" + v1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +// Parse optional logging flags +// Ex: ginkgo ./test/e2e/... -- -v=5 +// Allow init for e2e test (not imported by external code) +// nolint:gochecknoinits +func init() { + klog.InitFlags(nil) + klog.SetOutput(GinkgoWriter) +} + +var defaultTestTimeout = 1 * time.Hour +var defaultBeforeTestTimeout = 30 * time.Second +var defaultAfterTestTimeout = 30 * time.Second + +var _ = Describe("Applier", func() { + + var c client.Client + var invConfig invconfig.InventoryConfig + + BeforeSuite(func() { + // increase from 4000 to handle long event lists + format.MaxLength = 10000 + + cfg, err := ctrl.GetConfig() + Expect(err).NotTo(HaveOccurred()) + + // increase QPS from 5 to 20 + cfg.QPS = 20 + // increase Burst QPS from 10 to 40 + cfg.Burst = 40 + + invConfig = invconfig.NewCustomTypeInvConfig(cfg) + + mapper, err := apiutil.NewDynamicRESTMapper(cfg) + Expect(err).NotTo(HaveOccurred()) + + c, err = client.New(cfg, client.Options{ + Scheme: scheme.Scheme, + Mapper: mapper, + }) + Expect(err).NotTo(HaveOccurred()) + + ctx, cancel := context.WithTimeout(context.Background(), defaultBeforeTestTimeout) + defer cancel() + e2eutil.CreateInventoryCRD(ctx, c) + Expect(ctx.Err()).To(BeNil(), "BeforeSuite context cancelled or timed out") + }) + + AfterSuite(func() { + ctx, cancel := context.WithTimeout(context.Background(), defaultAfterTestTimeout) + defer cancel() + e2eutil.DeleteInventoryCRD(ctx, c) + Expect(ctx.Err()).To(BeNil(), "AfterSuite context cancelled or timed out") + }) + + Context("StressTest", func() { + var namespace *v1.Namespace + var inventoryName string + var ctx context.Context + var cancel context.CancelFunc + + BeforeEach(func() { + ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) + inventoryName = e2eutil.RandomString("test-inv-") + namespace = e2eutil.CreateRandomNamespace(ctx, c) + }) + + AfterEach(func() { + Expect(ctx.Err()).To(BeNil(), "test context cancelled or timed out") + cancel() + ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout) + defer cancel() + // clean up resources created by the tests + e2eutil.DeleteNamespace(ctx, c, namespace) + }) + + It("Thousand Namespaces", func() { + thousandNamespacesTest(ctx, c, invConfig, inventoryName, namespace.GetName()) + }) + }) +}) diff --git a/test/stress/thousand_namespaces_test.go b/test/stress/thousand_namespaces_test.go new file mode 100644 index 00000000..a2ca1c95 --- /dev/null +++ b/test/stress/thousand_namespaces_test.go @@ -0,0 +1,161 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package stress + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/klog/v2" + "sigs.k8s.io/cli-utils/pkg/apply" + "sigs.k8s.io/cli-utils/pkg/apply/event" + "sigs.k8s.io/cli-utils/pkg/common" + "sigs.k8s.io/cli-utils/pkg/inventory" + "sigs.k8s.io/cli-utils/test/e2e/e2eutil" + "sigs.k8s.io/cli-utils/test/e2e/invconfig" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func thousandNamespacesTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) { + By("Apply LOTS of resources") + applier := invConfig.ApplierFactoryFunc() + inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) + + inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) + + crdObj := e2eutil.ManifestToUnstructured([]byte(cronTabCRDYaml)) + + resources := []*unstructured.Unstructured{crdObj} + + labelKey := "created-for" + labelValue := "stress-test" + + namespaceObjTemplate := e2eutil.ManifestToUnstructured([]byte(namespaceYaml)) + namespaceObjTemplate.SetLabels(map[string]string{labelKey: labelValue}) + + configMapObjTemplate := e2eutil.ManifestToUnstructured([]byte(configMapYaml)) + configMapObjTemplate.SetLabels(map[string]string{labelKey: labelValue}) + + cronTabObjTemplate := e2eutil.ManifestToUnstructured([]byte(cronTabYaml)) + cronTabObjTemplate.SetLabels(map[string]string{labelKey: labelValue}) + + for i := 1; i <= 1000; i++ { + ns := fmt.Sprintf("%s-%d", namespaceName, i) + namespaceObj := namespaceObjTemplate.DeepCopy() + namespaceObj.SetName(ns) + resources = append(resources, namespaceObj) + + configMapObj := configMapObjTemplate.DeepCopy() + configMapObj.SetName(fmt.Sprintf("configmap-%d", i)) + configMapObj.SetNamespace(ns) + resources = append(resources, configMapObj) + + cronTabObj := cronTabObjTemplate.DeepCopy() + cronTabObj.SetName(fmt.Sprintf("crontab-%d", i)) + cronTabObj.SetNamespace(ns) + resources = append(resources, cronTabObj) + } + + defer func() { + // Can't delete custom resources if the CRD is still terminating + if e2eutil.UnstructuredExistsAndIsNotTerminating(ctx, c, crdObj) { + By("Cleanup CronTabs") + e2eutil.DeleteAllUnstructuredIfExists(ctx, c, cronTabObjTemplate) + By("Cleanup CRD") + e2eutil.DeleteUnstructuredIfExists(ctx, c, crdObj) + } + + By("Cleanup ConfigMaps") + e2eutil.DeleteAllUnstructuredIfExists(ctx, c, configMapObjTemplate) + By("Cleanup Namespaces") + e2eutil.DeleteAllUnstructuredIfExists(ctx, c, namespaceObjTemplate) + }() + + start := time.Now() + + applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ + // SSA reduces GET+PATCH to just PATCH, which is faster + ServerSideOptions: common.ServerSideOptions{ + ServerSideApply: true, + ForceConflicts: true, + FieldManager: "cli-utils.kubernetes.io", + }, + ReconcileTimeout: 30 * time.Minute, + EmitStatusEvents: false, + })) + + duration := time.Since(start) + klog.Infof("Applier.Run execution time: %v", duration) + + for _, e := range applierEvents { + Expect(e.ErrorEvent.Err).To(BeNil()) + } + for _, e := range applierEvents { + Expect(e.ApplyEvent.Error).To(BeNil(), "ApplyEvent: %v", e.ApplyEvent) + } + for _, e := range applierEvents { + if e.Type == event.WaitType { + Expect(e.WaitEvent.Operation).To(BeElementOf(event.ReconcilePending, event.Reconciled), "WaitEvent: %v", e.WaitEvent) + } + } + + By("Verify inventory created") + invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, len(resources), len(resources)) + + By("Verify CRD created") + e2eutil.AssertUnstructuredExists(ctx, c, crdObj) + + By("Verify 1000 Namespaces created") + e2eutil.AssertUnstructuredCount(ctx, c, namespaceObjTemplate, 1000) + + By("Verify 1000 ConfigMaps created") + e2eutil.AssertUnstructuredCount(ctx, c, configMapObjTemplate, 1000) + + By("Verify 1000 CronTabs created") + e2eutil.AssertUnstructuredCount(ctx, c, cronTabObjTemplate, 1000) + + By("Destroy LOTS of resources") + destroyer := invConfig.DestroyerFactoryFunc() + + start = time.Now() + + destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ + InventoryPolicy: inventory.PolicyAdoptIfNoInventory, + DeleteTimeout: 30 * time.Minute, + })) + + duration = time.Since(start) + klog.Infof("Destroyer.Run execution time: %v", duration) + + for _, e := range destroyerEvents { + Expect(e.ErrorEvent.Err).To(BeNil()) + } + for _, e := range destroyerEvents { + Expect(e.PruneEvent.Error).To(BeNil(), "PruneEvent: %v", e.PruneEvent) + } + for _, e := range destroyerEvents { + if e.Type == event.WaitType { + Expect(e.WaitEvent.Operation).To(BeElementOf(event.ReconcilePending, event.Reconciled), "WaitEvent: %v", e.WaitEvent) + } + } + + By("Verify inventory deleted") + invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID) + + By("Verify 1000 CronTabs deleted") + e2eutil.AssertUnstructuredCount(ctx, c, cronTabObjTemplate, 0) + + By("Verify 1000 ConfigMaps deleted") + e2eutil.AssertUnstructuredCount(ctx, c, configMapObjTemplate, 0) + + By("Verify 1000 Namespaces deleted") + e2eutil.AssertUnstructuredCount(ctx, c, namespaceObjTemplate, 0) + + By("Verify CRD deleted") + e2eutil.AssertUnstructuredDoesNotExist(ctx, c, crdObj) +}