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)
This commit is contained in:
Karl Isenberg 2022-03-07 12:00:42 -08:00
parent d84328fc3c
commit 34a8a7b334
28 changed files with 1110 additions and 535 deletions

View File

@ -63,6 +63,10 @@ test-e2e: $(MYGOBIN)/ginkgo $(MYGOBIN)/kind
kind delete cluster --name=cli-utils-e2e && kind create cluster --name=cli-utils-e2e kind delete cluster --name=cli-utils-e2e && kind create cluster --name=cli-utils-e2e
$(GOPATH)/bin/ginkgo ./test/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: vet:
go vet ./... go vet ./...

View File

@ -17,22 +17,24 @@ import (
"sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("Apply resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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{ resources := []*unstructured.Unstructured{
deployment1Obj, deployment1Obj,
} }
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))
@ -184,7 +186,7 @@ func applyAndDestroyTest(ctx context.Context, c client.Client, invConfig Invento
Expect(received).To(testutil.Equal(expEvents)) Expect(received).To(testutil.Equal(expEvents))
By("Verify deployment created") By("Verify deployment created")
assertUnstructuredExists(ctx, c, deployment1Obj) e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj)
By("Verify inventory") By("Verify inventory")
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1) 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() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, options))
expEvents = []testutil.ExpEvent{ 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("Verify deployment deleted") By("Verify deployment deleted")
assertUnstructuredDoesNotExist(ctx, c, deployment1Obj) e2eutil.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))
}
} }

View File

@ -16,23 +16,25 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("apply an invalid CRD")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
invalidCrdObj := manifestToUnstructured(invalidCrd) invalidCrdObj := e2eutil.ManifestToUnstructured(invalidCrd)
pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)
resources := []*unstructured.Unstructured{ resources := []*unstructured.Unstructured{
invalidCrdObj, invalidCrdObj,
pod1Obj, pod1Obj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{})) applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{}))
expEvents := []testutil.ExpEvent{ expEvents := []testutil.ExpEvent{
{ {
@ -167,8 +169,8 @@ func continueOnErrorTest(ctx context.Context, c client.Client, invConfig Invento
Expect(receivedEvents).To(testutil.Equal(expEvents)) Expect(receivedEvents).To(testutil.Equal(expEvents))
By("Verify pod1 created") By("Verify pod1 created")
assertUnstructuredExists(ctx, c, pod1Obj) e2eutil.AssertUnstructuredExists(ctx, c, pod1Obj)
By("Verify CRD not created") By("Verify CRD not created")
assertUnstructuredDoesNotExist(ctx, c, invalidCrdObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, invalidCrdObj)
} }

View File

@ -16,25 +16,27 @@ import (
"sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
//nolint:dupl // expEvents similar to mutation tests //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") By("apply a set of resources that includes both a crd and a cr")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
crdObj := manifestToUnstructured(crd) crdObj := e2eutil.ManifestToUnstructured(crd)
crObj := manifestToUnstructured(cr) crObj := e2eutil.ManifestToUnstructured(cr)
resources := []*unstructured.Unstructured{ resources := []*unstructured.Unstructured{
crObj, crObj,
crdObj, crdObj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -215,7 +217,7 @@ func crdTest(ctx context.Context, _ client.Client, invConfig InventoryConfig, in
By("destroy the resources, including the crd") By("destroy the resources, including the crd")
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
expEvents = []testutil.ExpEvent{ expEvents = []testutil.ExpEvent{
{ {

View File

@ -36,12 +36,6 @@ spec:
openAPIV3Schema: openAPIV3Schema:
description: Example for cli-utils e2e tests description: Example for cli-utils e2e tests
properties: properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec: spec:
properties: properties:
objects: objects:

View File

@ -14,36 +14,38 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/pkg/apply"
"sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory" "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" "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") By("Apply resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID) inventoryInfo := invconfig.CreateInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID)
resources := []*unstructured.Unstructured{ resources := []*unstructured.Unstructured{
withNamespace(manifestToUnstructured(deployment1), namespaceName), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName),
withAnnotation(withNamespace(manifestToUnstructured(pod1), namespaceName), common.OnRemoveAnnotation, common.OnRemoveKeep), e2eutil.WithAnnotation(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), common.OnRemoveAnnotation, common.OnRemoveKeep),
withAnnotation(withNamespace(manifestToUnstructured(pod2), namespaceName), common.LifecycleDeleteAnnotation, common.PreventDeletion), 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, ReconcileTimeout: 2 * time.Minute,
})) }))
By("Verify deployment created") 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())) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod1 created") 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())) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod2 created") 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())) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify the inventory size is 3") 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") By("Dry-run apply resources")
resources = []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
DryRunStrategy: common.DryRunClient, DryRunStrategy: common.DryRunClient,
})) }))
By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation") 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())) 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") 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())) 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") 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())) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify the inventory size is still 3") 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") By("Apply resources")
resources = []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
})) }))
By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation") 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())) 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") 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("")) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(""))
By("Verify pod2 still exits and does not have the config.k8s.io/owning-inventory annotation") 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("")) Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(""))
By("Verify the inventory size is 1") By("Verify the inventory size is 1")

View File

@ -15,18 +15,20 @@ import (
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/object/validation"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
//nolint:dupl // expEvents similar to other tests //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") By("apply resources in order based on depends-on annotation")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
pod1Obj := withDependsOn(withNamespace(manifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) pod1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName))
pod2Obj := withNamespace(manifestToUnstructured(pod2), namespaceName) pod2Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName)
// Dependency order: pod1 -> pod2 // Dependency order: pod1 -> pod2
// Apply order: pod2, pod1 // Apply order: pod2, pod1
@ -37,11 +39,11 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent
// Cleanup // Cleanup
defer func(ctx context.Context, c client.Client) { defer func(ctx context.Context, c client.Client) {
deleteUnstructuredIfExists(ctx, c, pod1Obj) e2eutil.DeleteUnstructuredIfExists(ctx, c, pod1Obj)
deleteUnstructuredIfExists(ctx, c, pod2Obj) e2eutil.DeleteUnstructuredIfExists(ctx, c, pod2Obj)
}(ctx, c) }(ctx, c)
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -219,14 +221,14 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify pod1 created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
By("verify pod2 created and ready") 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") podIP, found, err = object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
@ -237,7 +239,7 @@ func dependencyFilterTest(ctx context.Context, c client.Client, invConfig Invent
pod1Obj, pod1Obj,
} }
applierEvents = runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents = e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
ValidationPolicy: validation.SkipInvalid, 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)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify pod1 not deleted") 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") ts, found, err := object.NestedField(result.Object, "metadata", "deletionTimestamp")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts)
By("verify pod2 not deleted") 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") ts, found, err = object.NestedField(result.Object, "metadata", "deletionTimestamp")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts)

View File

@ -15,18 +15,20 @@ import (
"sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("apply resources in order based on depends-on annotation")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
pod1Obj := withDependsOn(withNamespace(manifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod3", namespaceName)) pod1Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod3", namespaceName))
pod2Obj := withNamespace(manifestToUnstructured(pod2), namespaceName) pod2Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespaceName)
pod3Obj := withDependsOn(withNamespace(manifestToUnstructured(pod3), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName)) pod3Obj := e2eutil.WithDependsOn(e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod3), namespaceName), fmt.Sprintf("/namespaces/%s/Pod/pod2", namespaceName))
// Dependency order: pod1 -> pod3 -> pod2 // Dependency order: pod1 -> pod3 -> pod2
// Apply order: pod2, pod3, pod1 // Apply order: pod2, pod3, pod1
@ -36,7 +38,7 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf
pod3Obj, pod3Obj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -278,21 +280,21 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify pod1 created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
By("verify pod2 created and ready") 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") podIP, found, err = object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
By("verify pod3 created and ready") 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") podIP, found, err = object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
@ -301,7 +303,7 @@ func dependsOnTest(ctx context.Context, c client.Client, invConfig InventoryConf
By("destroy resources in opposite order") By("destroy resources in opposite order")
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
expEvents = []testutil.ExpEvent{ 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("verify pod1 deleted") By("verify pod1 deleted")
assertUnstructuredDoesNotExist(ctx, c, pod1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj)
By("verify pod2 deleted") By("verify pod2 deleted")
assertUnstructuredDoesNotExist(ctx, c, pod2Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod2Obj)
By("verify pod3 deleted") By("verify pod3 deleted")
assertUnstructuredDoesNotExist(ctx, c, pod3Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj)
} }

View File

@ -18,21 +18,23 @@ import (
"sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("Apply with DryRun")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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) namespace1Name := fmt.Sprintf("%s-ns1", namespaceName)
fields := struct{ Namespace string }{Namespace: namespace1Name} fields := struct{ Namespace string }{Namespace: namespace1Name}
namespace1Obj := templateToUnstructured(namespaceTemplate, fields) namespace1Obj := e2eutil.TemplateToUnstructured(namespaceTemplate, fields)
podBObj := templateToUnstructured(podBTemplate, fields) podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields)
// Dependency order: podB -> namespace1 // Dependency order: podB -> namespace1
// Apply order: namespace1, podB // Apply order: namespace1, podB
@ -41,7 +43,7 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig,
podBObj, podBObj,
} }
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
DryRunStrategy: common.DryRunClient, DryRunStrategy: common.DryRunClient,
@ -176,18 +178,18 @@ func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig,
Expect(received).To(testutil.Equal(expEvents)) Expect(received).To(testutil.Equal(expEvents))
By("Verify pod NotFound") By("Verify pod NotFound")
assertUnstructuredDoesNotExist(ctx, c, podBObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
By("Verify inventory NotFound") By("Verify inventory NotFound")
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID) invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)
By("Apply") By("Apply")
runWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ e2eutil.RunWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
})) }))
By("Verify pod created") By("Verify pod created")
assertUnstructuredExists(ctx, c, podBObj) e2eutil.AssertUnstructuredExists(ctx, c, podBObj)
By("Verify inventory size") By("Verify inventory size")
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 2, 2) 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") By("Destroy with DryRun")
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
InventoryPolicy: inventory.PolicyAdoptIfNoInventory, InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
EmitStatusEvents: true, EmitStatusEvents: true,
DryRunStrategy: common.DryRunClient, 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("Verify pod still exists") By("Verify pod still exists")
assertUnstructuredExists(ctx, c, podBObj) e2eutil.AssertUnstructuredExists(ctx, c, podBObj)
By("Destroy") By("Destroy")
runWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{ e2eutil.RunWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
InventoryPolicy: inventory.PolicyAdoptIfNoInventory, InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
})) }))
By("Verify pod deleted") By("Verify pod deleted")
assertUnstructuredDoesNotExist(ctx, c, podBObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
By("Verify inventory deleted") By("Verify inventory deleted")
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID) invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)

View File

@ -12,69 +12,25 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
v1 "k8s.io/api/core/v1" 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/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/test/e2e/e2eutil"
"sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/test/e2e/invconfig"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/test/e2e/customprovider"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil" "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 ( const (
ConfigMapTypeInvConfig = "ConfigMap" ConfigMapTypeInvConfig = "ConfigMap"
CustomTypeInvConfig = "Custom" CustomTypeInvConfig = "Custom"
) )
var inventoryConfigs = map[string]InventoryConfig{ var inventoryConfigs = map[string]invconfig.InventoryConfig{}
ConfigMapTypeInvConfig: { var inventoryConfigTypes = []string{
Strategy: inventory.LabelStrategy, ConfigMapTypeInvConfig,
FactoryFunc: cmInventoryManifest, CustomTypeInvConfig,
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,
},
} }
// Parse optional logging flags // Parse optional logging flags
@ -101,6 +57,14 @@ var _ = Describe("Applier", func() {
cfg, err := ctrl.GetConfig() cfg, err := ctrl.GetConfig()
Expect(err).NotTo(HaveOccurred()) 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) mapper, err := apiutil.NewDynamicRESTMapper(cfg)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -112,20 +76,26 @@ var _ = Describe("Applier", func() {
ctx, cancel := context.WithTimeout(context.Background(), defaultBeforeTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultBeforeTestTimeout)
defer cancel() defer cancel()
createInventoryCRD(ctx, c) e2eutil.CreateInventoryCRD(ctx, c)
Expect(ctx.Err()).To(BeNil(), "BeforeSuite context cancelled or timed out") Expect(ctx.Err()).To(BeNil(), "BeforeSuite context cancelled or timed out")
}) })
AfterSuite(func() { AfterSuite(func() {
ctx, cancel := context.WithTimeout(context.Background(), defaultAfterTestTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultAfterTestTimeout)
defer cancel() defer cancel()
deleteInventoryCRD(ctx, c) e2eutil.DeleteInventoryCRD(ctx, c)
Expect(ctx.Err()).To(BeNil(), "AfterSuite context cancelled or timed out") Expect(ctx.Err()).To(BeNil(), "AfterSuite context cancelled or timed out")
}) })
for name := range inventoryConfigs { for i := range inventoryConfigTypes {
invConfig := inventoryConfigs[name] invType := inventoryConfigTypes[i]
Context(fmt.Sprintf("Inventory: %s", name), func() { Context(fmt.Sprintf("Inventory: %s", invType), func() {
var invConfig invconfig.InventoryConfig
BeforeEach(func() {
invConfig = inventoryConfigs[invType]
})
Context("Apply and destroy", func() { Context("Apply and destroy", func() {
var namespace *v1.Namespace var namespace *v1.Namespace
var inventoryName string var inventoryName string
@ -134,8 +104,8 @@ var _ = Describe("Applier", func() {
BeforeEach(func() { BeforeEach(func() {
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
inventoryName = randomString("test-inv-") inventoryName = e2eutil.RandomString("test-inv-")
namespace = createRandomNamespace(ctx, c) namespace = e2eutil.CreateRandomNamespace(ctx, c)
}) })
AfterEach(func() { AfterEach(func() {
@ -147,20 +117,20 @@ var _ = Describe("Applier", func() {
// clean up resources created by the tests // clean up resources created by the tests
fields := struct{ Namespace string }{Namespace: namespace.GetName()} fields := struct{ Namespace string }{Namespace: namespace.GetName()}
objs := []*unstructured.Unstructured{ objs := []*unstructured.Unstructured{
manifestToUnstructured(cr), e2eutil.ManifestToUnstructured(cr),
manifestToUnstructured(crd), e2eutil.ManifestToUnstructured(crd),
withNamespace(manifestToUnstructured(pod1), namespace.GetName()), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespace.GetName()),
withNamespace(manifestToUnstructured(pod2), namespace.GetName()), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod2), namespace.GetName()),
withNamespace(manifestToUnstructured(pod3), namespace.GetName()), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod3), namespace.GetName()),
templateToUnstructured(podATemplate, fields), e2eutil.TemplateToUnstructured(podATemplate, fields),
templateToUnstructured(podBTemplate, fields), e2eutil.TemplateToUnstructured(podBTemplate, fields),
withNamespace(manifestToUnstructured(deployment1), namespace.GetName()), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName()),
manifestToUnstructured(apiservice1), e2eutil.ManifestToUnstructured(apiservice1),
} }
for _, obj := range objs { 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() { It("Apply and destroy", func() {
@ -235,7 +205,7 @@ var _ = Describe("Applier", func() {
BeforeEach(func() { BeforeEach(func() {
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
namespace = createRandomNamespace(ctx, c) namespace = e2eutil.CreateRandomNamespace(ctx, c)
}) })
AfterEach(func() { AfterEach(func() {
@ -244,8 +214,8 @@ var _ = Describe("Applier", func() {
// new timeout for cleanup // new timeout for cleanup
ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout) ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout)
defer cancel() defer cancel()
deleteUnstructuredIfExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespace.GetName())) e2eutil.DeleteUnstructuredIfExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName()))
deleteNamespace(ctx, c, namespace) e2eutil.DeleteNamespace(ctx, c, namespace)
}) })
It("MustMatch policy", func() { It("MustMatch policy", func() {
@ -271,8 +241,8 @@ var _ = Describe("Applier", func() {
BeforeEach(func() { BeforeEach(func() {
ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout)
inventoryName = randomString("test-inv-") inventoryName = e2eutil.RandomString("test-inv-")
namespace = createRandomNamespace(ctx, c) namespace = e2eutil.CreateRandomNamespace(ctx, c)
}) })
AfterEach(func() { AfterEach(func() {
@ -281,8 +251,8 @@ var _ = Describe("Applier", func() {
// new timeout for cleanup // new timeout for cleanup
ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout) ctx, cancel = context.WithTimeout(context.Background(), defaultAfterTestTimeout)
defer cancel() defer cancel()
deleteUnstructuredIfExists(ctx, c, withNamespace(manifestToUnstructured(deployment1), namespace.GetName())) e2eutil.DeleteUnstructuredIfExists(ctx, c, e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespace.GetName()))
deleteNamespace(ctx, c, namespace) e2eutil.DeleteNamespace(ctx, c, namespace)
}) })
It("Apply with existing inventory", func() { 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
}

View File

@ -1,23 +1,22 @@
// Copyright 2020 The Kubernetes Authors. // Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package e2e package e2eutil
import ( import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"strings"
"text/template" "text/template"
"time" "time"
. "github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/cli-utils/pkg/apply/event" "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/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object/dependson" "sigs.k8s.io/cli-utils/pkg/object/dependson"
"sigs.k8s.io/cli-utils/pkg/object/mutation" "sigs.k8s.io/cli-utils/pkg/object/mutation"
"sigs.k8s.io/cli-utils/test/e2e/customprovider"
"sigs.k8s.io/controller-runtime/pkg/client" "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") err := unstructured.SetNestedField(obj.Object, int64(replicas), "spec", "replicas")
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
return obj return obj
} }
func withNamespace(obj *unstructured.Unstructured, namespace string) *unstructured.Unstructured { func WithNamespace(obj *unstructured.Unstructured, namespace string) *unstructured.Unstructured {
obj.SetNamespace(namespace) obj.SetNamespace(namespace)
return obj 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") containers, found, err := unstructured.NestedSlice(obj.Object, "spec", "containers")
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
Expect(found).To(BeTrue()) gomega.Expect(found).To(gomega.BeTrue())
containerFound := false containerFound := false
for i := range containers { for i := range containers {
@ -54,26 +54,26 @@ func podWithImage(obj *unstructured.Unstructured, containerName, image string) *
containerFound = true containerFound = true
container["image"] = image container["image"] = image
} }
Expect(containerFound).To(BeTrue()) gomega.Expect(containerFound).To(gomega.BeTrue())
err = unstructured.SetNestedSlice(obj.Object, containers, "spec", "containers") err = unstructured.SetNestedSlice(obj.Object, containers, "spec", "containers")
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
return obj 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") selectors, found, err := unstructured.NestedMap(obj.Object, "spec", "nodeSelector")
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
if !found { if !found {
selectors = make(map[string]interface{}) selectors = make(map[string]interface{})
} }
selectors[key] = value selectors[key] = value
err = unstructured.SetNestedMap(obj.Object, selectors, "spec", "nodeSelector") err = unstructured.SetNestedMap(obj.Object, selectors, "spec", "nodeSelector")
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
return obj 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() annotations := obj.GetAnnotations()
if annotations == nil { if annotations == nil {
annotations = make(map[string]string) annotations = make(map[string]string)
@ -83,7 +83,7 @@ func withAnnotation(obj *unstructured.Unstructured, key, value string) *unstruct
return obj return obj
} }
func withDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Unstructured { func WithDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Unstructured {
a := obj.GetAnnotations() a := obj.GetAnnotations()
if a == nil { if a == nil {
a = make(map[string]string, 1) a = make(map[string]string, 1)
@ -93,18 +93,18 @@ func withDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Uns
return obj 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) ref := mutation.ResourceReferenceFromUnstructured(obj)
err := c.Delete(ctx, obj, err := c.Delete(ctx, obj,
client.PropagationPolicy(metav1.DeletePropagationForeground)) client.PropagationPolicy(metav1.DeletePropagationForeground))
Expect(err).NotTo(HaveOccurred(), gomega.Expect(err).NotTo(gomega.HaveOccurred(),
"expected DELETE to not error (%s): %s", ref, err) "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) ref := mutation.ResourceReferenceFromUnstructured(obj)
resultObj := ref.ToUnstructured() resultObj := ref.ToUnstructured()
@ -118,7 +118,7 @@ func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Uns
for { for {
select { select {
case <-t.C: 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 return
case <-s.C: case <-s.C:
err := c.Get(ctx, types.NamespacedName{ err := c.Get(ctx, types.NamespacedName{
@ -126,7 +126,7 @@ func waitForDeletion(ctx context.Context, c client.Client, obj *unstructured.Uns
Name: obj.GetName(), Name: obj.GetName(),
}, resultObj) }, resultObj)
if err != nil { 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) "expected GET to error with NotFound (%s): %s", ref, err)
return 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) ref := mutation.ResourceReferenceFromUnstructured(obj)
err := c.Create(ctx, 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) "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) ref := mutation.ResourceReferenceFromUnstructured(obj)
resultObj := ref.ToUnstructured() resultObj := ref.ToUnstructured()
@ -159,7 +159,7 @@ func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Uns
for { for {
select { select {
case <-t.C: 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 return
case <-s.C: case <-s.C:
err := c.Get(ctx, types.NamespacedName{ err := c.Get(ctx, types.NamespacedName{
@ -169,7 +169,7 @@ func waitForCreation(ctx context.Context, c client.Client, obj *unstructured.Uns
if err == nil { if err == nil {
return 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) "expected GET to error with NotFound (%s): %s", ref, err)
// if NotFound, sleep and retry // if NotFound, sleep and retry
s = time.NewTimer(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) ref := mutation.ResourceReferenceFromUnstructured(obj)
resultObj := ref.ToUnstructured() resultObj := ref.ToUnstructured()
@ -185,12 +185,12 @@ func assertUnstructuredExists(ctx context.Context, c client.Client, obj *unstruc
Namespace: obj.GetNamespace(), Namespace: obj.GetNamespace(),
Name: obj.GetName(), Name: obj.GetName(),
}, resultObj) }, resultObj)
Expect(err).NotTo(HaveOccurred(), gomega.Expect(err).NotTo(gomega.HaveOccurred(),
"expected GET not to error (%s): %s", ref, err) "expected GET not to error (%s): %s", ref, err)
return resultObj 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) ref := mutation.ResourceReferenceFromUnstructured(obj)
resultObj := ref.ToUnstructured() resultObj := ref.ToUnstructured()
@ -198,13 +198,13 @@ func assertUnstructuredDoesNotExist(ctx context.Context, c client.Client, obj *u
Namespace: obj.GetNamespace(), Namespace: obj.GetNamespace(),
Name: obj.GetName(), Name: obj.GetName(),
}, resultObj) }, resultObj)
Expect(err).To(HaveOccurred(), gomega.Expect(err).To(gomega.HaveOccurred(),
"expected GET to error (%s)", ref) "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) "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) ref := mutation.ResourceReferenceFromUnstructured(obj)
resultObj := ref.ToUnstructured() resultObj := ref.ToUnstructured()
@ -212,18 +212,18 @@ func applyUnstructured(ctx context.Context, c client.Client, obj *unstructured.U
Namespace: obj.GetNamespace(), Namespace: obj.GetNamespace(),
Name: obj.GetName(), Name: obj.GetName(),
}, resultObj) }, resultObj)
Expect(err).NotTo(HaveOccurred(), gomega.Expect(err).NotTo(gomega.HaveOccurred(),
"expected GET not to error (%s)", ref) "expected GET not to error (%s)", ref)
err = c.Patch(ctx, obj, client.MergeFrom(resultObj)) 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) "expected PATCH not to error (%s): %s", ref, err)
} }
func assertUnstructuredAvailable(obj *unstructured.Unstructured) { func AssertUnstructuredAvailable(obj *unstructured.Unstructured) {
ref := mutation.ResourceReferenceFromUnstructured(obj) ref := mutation.ResourceReferenceFromUnstructured(obj)
objc, err := status.GetObjectWithConditions(obj.Object) objc, err := status.GetObjectWithConditions(obj.Object)
Expect(err).NotTo(HaveOccurred()) gomega.Expect(err).NotTo(gomega.HaveOccurred())
available := false available := false
for _, c := range objc.Status.Conditions { for _, c := range objc.Status.Conditions {
// appsv1.DeploymentAvailable && corev1.ConditionTrue // appsv1.DeploymentAvailable && corev1.ConditionTrue
@ -232,16 +232,30 @@ func assertUnstructuredAvailable(obj *unstructured.Unstructured) {
break break
} }
} }
Expect(available).To(BeTrue(), gomega.Expect(available).To(gomega.BeTrue(),
"expected Available condition to be True (%s)", ref) "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() randomSuffix := common.RandomStr()
return fmt.Sprintf("%s%s", prefix, randomSuffix) return fmt.Sprintf("%s%s", prefix, randomSuffix)
} }
func run(ch <-chan event.Event) error { func Run(ch <-chan event.Event) error {
var err error var err error
for e := range ch { for e := range ch {
if e.Type == event.ErrorType { if e.Type == event.ErrorType {
@ -251,11 +265,11 @@ func run(ch <-chan event.Event) error {
return err return err
} }
func runWithNoErr(ch <-chan event.Event) { func RunWithNoErr(ch <-chan event.Event) {
runCollectNoErr(ch) RunCollectNoErr(ch)
} }
func runCollect(ch <-chan event.Event) []event.Event { func RunCollect(ch <-chan event.Event) []event.Event {
var events []event.Event var events []event.Event
for e := range ch { for e := range ch {
events = append(events, e) events = append(events, e)
@ -263,53 +277,15 @@ func runCollect(ch <-chan event.Event) []event.Event {
return events return events
} }
func runCollectNoErr(ch <-chan event.Event) []event.Event { func RunCollectNoErr(ch <-chan event.Event) []event.Event {
events := runCollect(ch) events := RunCollect(ch)
for _, e := range events { for _, e := range events {
Expect(e.Type).NotTo(Equal(event.ErrorType)) gomega.Expect(e.Type).NotTo(gomega.Equal(event.ErrorType))
} }
return events return events
} }
func cmInventoryManifest(name, namespace, id string) *unstructured.Unstructured { func ManifestToUnstructured(manifest []byte) *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 {
u := make(map[string]interface{}) u := make(map[string]interface{})
err := yaml.Unmarshal(manifest, &u) err := yaml.Unmarshal(manifest, &u)
if err != nil { 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) t, err := template.New("manifest").Parse(tmpl)
if err != nil { if err != nil {
panic(fmt.Errorf("failed to parse manifest go-template: %w", err)) 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 { if err != nil {
panic(fmt.Errorf("failed to execute manifest go-template: %w", err)) 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
} }

View File

@ -14,11 +14,13 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
//nolint:dupl // expEvents similar to other tests //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") By("Apply zero resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
@ -27,7 +29,7 @@ func emptySetTest(ctx context.Context, c client.Client, invConfig InventoryConfi
resources := []*unstructured.Unstructured{} resources := []*unstructured.Unstructured{}
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))
@ -83,7 +85,7 @@ func emptySetTest(ctx context.Context, c client.Client, invConfig InventoryConfi
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inventoryInfo, options))
expEvents = []testutil.ExpEvent{ expEvents = []testutil.ExpEvent{
{ {

View File

@ -16,10 +16,12 @@ import (
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/object/validation"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("exit early on invalid object")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
@ -27,12 +29,12 @@ func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConf
fields := struct{ Namespace string }{Namespace: namespaceName} fields := struct{ Namespace string }{Namespace: namespaceName}
// valid pod // valid pod
pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)
// valid deployment with dependency // 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())) fmt.Sprintf("/namespaces/%s/Pod/%s", namespaceName, pod1Obj.GetName()))
// missing name // missing name
invalidPodObj := templateToUnstructured(invalidPodTemplate, fields) invalidPodObj := e2eutil.TemplateToUnstructured(invalidPodTemplate, fields)
resources := []*unstructured.Unstructured{ resources := []*unstructured.Unstructured{
pod1Obj, pod1Obj,
@ -40,7 +42,7 @@ func exitEarlyTest(ctx context.Context, c client.Client, invConfig InventoryConf
invalidPodObj, invalidPodObj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
ValidationPolicy: validation.ExitEarly, 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)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify pod1 not found") By("verify pod1 not found")
assertUnstructuredDoesNotExist(ctx, c, pod1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj)
By("verify deployment1 not found") By("verify deployment1 not found")
assertUnstructuredDoesNotExist(ctx, c, deployment1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, deployment1Obj)
} }

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -17,34 +17,36 @@ import (
"sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("Apply first set of resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
firstInvName := randomString("first-inv-") firstInvName := e2eutil.RandomString("first-inv-")
firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName)) firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName))
deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
firstResources := []*unstructured.Unstructured{ firstResources := []*unstructured.Unstructured{
deployment1Obj, deployment1Obj,
} }
runWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ e2eutil.RunWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))
By("Apply second set of resources") By("Apply second set of resources")
secondInvName := randomString("second-inv-") secondInvName := e2eutil.RandomString("second-inv-")
secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName)) secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName))
deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
secondResources := []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
InventoryPolicy: inventory.PolicyMustMatch, InventoryPolicy: inventory.PolicyMustMatch,
@ -178,7 +180,7 @@ func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfi
Expect(received).To(testutil.Equal(expEvents)) Expect(received).To(testutil.Equal(expEvents))
By("Verify resource wasn't updated") 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") replicas, found, err := object.NestedField(result.Object, "spec", "replicas")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
@ -187,22 +189,22 @@ func inventoryPolicyMustMatchTest(ctx context.Context, c client.Client, invConfi
invConfig.InvCountVerifyFunc(ctx, c, namespaceName, 2) 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") By("Create unmanaged resource")
deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
createUnstructuredAndWait(ctx, c, deployment1Obj) e2eutil.CreateUnstructuredAndWait(ctx, c, deployment1Obj)
By("Apply resources") By("Apply resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
invName := randomString("test-inv-") invName := e2eutil.RandomString("test-inv-")
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(invName, namespaceName, invName)) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(invName, namespaceName, invName))
deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
resources := []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
InventoryPolicy: inventory.PolicyAdoptIfNoInventory, InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
@ -344,7 +346,7 @@ func inventoryPolicyAdoptIfNoInventoryTest(ctx context.Context, c client.Client,
Expect(received).To(testutil.Equal(expEvents)) Expect(received).To(testutil.Equal(expEvents))
By("Verify resource was updated and added to inventory") 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") replicas, found, err := object.NestedField(result.Object, "spec", "replicas")
Expect(err).NotTo(HaveOccurred()) 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) 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") By("Apply an initial set of resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
firstInvName := randomString("first-inv-") firstInvName := e2eutil.RandomString("first-inv-")
firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName)) firstInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(firstInvName, namespaceName, firstInvName))
deployment1Obj := withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
firstResources := []*unstructured.Unstructured{ firstResources := []*unstructured.Unstructured{
deployment1Obj, deployment1Obj,
} }
runWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{ e2eutil.RunWithNoErr(applier.Run(ctx, firstInv, firstResources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))
By("Apply resources") By("Apply resources")
secondInvName := randomString("test-inv-") secondInvName := e2eutil.RandomString("test-inv-")
secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName)) secondInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(secondInvName, namespaceName, secondInvName))
deployment1Obj = withNamespace(manifestToUnstructured(deployment1), namespaceName) deployment1Obj = e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName)
secondResources := []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
InventoryPolicy: inventory.PolicyAdoptAll, InventoryPolicy: inventory.PolicyAdoptAll,
@ -526,7 +528,7 @@ func inventoryPolicyAdoptAllTest(ctx context.Context, c client.Client, invConfig
Expect(received).To(testutil.Equal(expEvents)) Expect(received).To(testutil.Equal(expEvents))
By("Verify resource was updated and added to inventory") 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") replicas, found, err := object.NestedField(result.Object, "spec", "replicas")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())

View File

@ -15,6 +15,8 @@ import (
"sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
@ -32,15 +34,15 @@ import (
// port from pod-b into an environment variable of pod-a. // port from pod-b into an environment variable of pod-a.
//nolint:dupl // expEvents similar to CRD tests //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") By("apply resources in order with substitutions based on apply-time-mutation annotation")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
fields := struct{ Namespace string }{Namespace: namespaceName} fields := struct{ Namespace string }{Namespace: namespaceName}
podAObj := templateToUnstructured(podATemplate, fields) podAObj := e2eutil.TemplateToUnstructured(podATemplate, fields)
podBObj := templateToUnstructured(podBTemplate, fields) podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields)
// Dependency order: podA -> podB // Dependency order: podA -> podB
// Apply order: podB, podA // Apply order: podB, podA
@ -49,7 +51,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi
podBObj, podBObj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -227,7 +229,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify podB is created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) 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) host := fmt.Sprintf("%s:%d", podIP, containerPort)
By("verify podA is mutated, created, and ready") 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") podIP, found, err = object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -257,7 +259,7 @@ func mutationTest(ctx context.Context, c client.Client, invConfig InventoryConfi
By("destroy resources in opposite order") By("destroy resources in opposite order")
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
expEvents = []testutil.ExpEvent{ 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("verify podB deleted") By("verify podB deleted")
assertUnstructuredDoesNotExist(ctx, c, podBObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
By("verify podA deleted") By("verify podA deleted")
assertUnstructuredDoesNotExist(ctx, c, podAObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj)
} }

View File

@ -12,10 +12,12 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/apply" "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" "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") By("Apply first set of resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
orgInventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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)) orgApplyInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, orgInventoryID))
resources := []*unstructured.Unstructured{ 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, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))
@ -38,7 +40,7 @@ func applyWithExistingInvTest(ctx context.Context, c client.Client, invConfig In
secondInventoryID := fmt.Sprintf("%s-%s-2", inventoryName, namespaceName) secondInventoryID := fmt.Sprintf("%s-%s-2", inventoryName, namespaceName)
secondApplyInv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, secondInventoryID)) 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, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
})) }))

View File

@ -14,11 +14,13 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "sigs.k8s.io/controller-runtime/pkg/client"
) )
//nolint:dupl // expEvents similar to other tests //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") By("apply resources in order based on depends-on annotation")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
@ -27,8 +29,8 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento
namespace1Name := fmt.Sprintf("%s-ns1", namespaceName) namespace1Name := fmt.Sprintf("%s-ns1", namespaceName)
fields := struct{ Namespace string }{Namespace: namespace1Name} fields := struct{ Namespace string }{Namespace: namespace1Name}
namespace1Obj := templateToUnstructured(namespaceTemplate, fields) namespace1Obj := e2eutil.TemplateToUnstructured(namespaceTemplate, fields)
podBObj := templateToUnstructured(podBTemplate, fields) podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields)
// Dependency order: podB -> namespace1 // Dependency order: podB -> namespace1
// Apply order: namespace1, podB // Apply order: namespace1, podB
@ -39,11 +41,11 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento
// Cleanup // Cleanup
defer func(ctx context.Context, c client.Client) { defer func(ctx context.Context, c client.Client) {
deleteUnstructuredIfExists(ctx, c, podBObj) e2eutil.DeleteUnstructuredIfExists(ctx, c, podBObj)
deleteUnstructuredIfExists(ctx, c, namespace1Obj) e2eutil.DeleteUnstructuredIfExists(ctx, c, namespace1Obj)
}(ctx, c) }(ctx, c)
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -221,10 +223,10 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify namespace1 created") By("verify namespace1 created")
assertUnstructuredExists(ctx, c, namespace1Obj) e2eutil.AssertUnstructuredExists(ctx, c, namespace1Obj)
By("verify podB created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
@ -235,7 +237,7 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento
podBObj, podBObj,
} }
applierEvents = runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents = e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -395,13 +397,13 @@ func namespaceFilterTest(ctx context.Context, c client.Client, invConfig Invento
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify namespace1 not deleted") 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") ts, found, err := object.NestedField(result.Object, "metadata", "deletionTimestamp")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts)
By("verify podB not deleted") 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") ts, found, err = object.NestedField(result.Object, "metadata", "deletionTimestamp")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts) Expect(found).To(BeFalse(), "deletionTimestamp found: ", ts)

View File

@ -15,23 +15,25 @@ import (
"sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("apply a single resource, which is referenced in the inventory")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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{ resource1 := []*unstructured.Unstructured{
pod1Obj, pod1Obj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resource1, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resource1, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -145,7 +147,7 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve
Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("Verify pod1 created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) 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. // Delete the previously applied resource, which is referenced in the inventory.
By("delete 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") By("Verify inventory")
// The inventory should still have the previously deleted item. // The inventory should still have the previously deleted item.
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1) invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 1, 1)
By("apply a different resource, and validate the inventory accurately reflects only this object") 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{ resource2 := []*unstructured.Unstructured{
pod2Obj, pod2Obj,
} }
applierEvents2 := runCollect(applier.Run(ctx, inv, resource2, apply.ApplierOptions{ applierEvents2 := e2eutil.RunCollect(applier.Run(ctx, inv, resource2, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -280,14 +282,14 @@ func pruneRetrieveErrorTest(ctx context.Context, c client.Client, invConfig Inve
Expect(testutil.EventsToExpEvents(applierEvents2)).To(testutil.Equal(expEvents2)) Expect(testutil.EventsToExpEvents(applierEvents2)).To(testutil.Equal(expEvents2))
By("Verify pod2 created and ready") 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") podIP, found, err = object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
By("Verify pod1 still deleted") By("Verify pod1 still deleted")
assertUnstructuredDoesNotExist(ctx, c, pod1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj)
By("Verify inventory") By("Verify inventory")
// The inventory should only have the currently applied item. // 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() destroyer := invConfig.DestroyerFactoryFunc()
options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory} options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
destroyerEvents := runCollect(destroyer.Run(ctx, inv, options)) destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
expEvents3 := []testutil.ExpEvent{ 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents3))
By("Verify pod1 is deleted") By("Verify pod1 is deleted")
assertUnstructuredDoesNotExist(ctx, c, pod1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj)
By("Verify pod2 is deleted") By("Verify pod2 is deleted")
assertUnstructuredDoesNotExist(ctx, c, pod2Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod2Obj)
} }

View File

@ -15,21 +15,23 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply/event" "sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/object" "sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil" "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") By("Apply resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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{ resources := []*unstructured.Unstructured{
podObj, podObj,
} }
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))
@ -40,19 +42,19 @@ func reconciliationFailed(ctx context.Context, invConfig InventoryConfig, invent
Expect(received).To(testutil.Equal(expEvents)) 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") By("Apply resources")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName) 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{ resources := []*unstructured.Unstructured{
podObj, podObj,
} }
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 30 * time.Second, ReconcileTimeout: 30 * time.Second,
EmitStatusEvents: false, EmitStatusEvents: false,
})) }))

View File

@ -14,20 +14,22 @@ import (
"sigs.k8s.io/cli-utils/pkg/apply" "sigs.k8s.io/cli-utils/pkg/apply"
"sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/object" "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" "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") By("Apply a Deployment and an APIService by server-side apply")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test")) inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
firstResources := []*unstructured.Unstructured{ firstResources := []*unstructured.Unstructured{
withNamespace(manifestToUnstructured(deployment1), namespaceName), e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(deployment1), namespaceName),
manifestToUnstructured(apiservice1), e2eutil.ManifestToUnstructured(apiservice1),
} }
runWithNoErr(applier.Run(ctx, inv, firstResources, apply.ApplierOptions{ e2eutil.RunWithNoErr(applier.Run(ctx, inv, firstResources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute, ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true, EmitStatusEvents: true,
ServerSideOptions: common.ServerSideOptions{ ServerSideOptions: common.ServerSideOptions{
@ -38,7 +40,7 @@ func serversideApplyTest(ctx context.Context, c client.Client, invConfig Invento
})) }))
By("Verify deployment is server-side applied") 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. // 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) _, 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")) Expect(manager).To(Equal("test"))
By("Verify APIService is server-side applied") 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. // 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) _, found, err = object.NestedField(result.Object, "metadata", "annotations", v1.LastAppliedConfigAnnotation)

View File

@ -21,10 +21,12 @@ import (
"sigs.k8s.io/cli-utils/pkg/object/mutation" "sigs.k8s.io/cli-utils/pkg/object/mutation"
"sigs.k8s.io/cli-utils/pkg/object/validation" "sigs.k8s.io/cli-utils/pkg/object/validation"
"sigs.k8s.io/cli-utils/pkg/testutil" "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" "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") By("apply valid objects and skip invalid objects")
applier := invConfig.ApplierFactoryFunc() applier := invConfig.ApplierFactoryFunc()
@ -32,19 +34,19 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo
fields := struct{ Namespace string }{Namespace: namespaceName} fields := struct{ Namespace string }{Namespace: namespaceName}
// valid pod // valid pod
pod1Obj := withNamespace(manifestToUnstructured(pod1), namespaceName) pod1Obj := e2eutil.WithNamespace(e2eutil.ManifestToUnstructured(pod1), namespaceName)
// valid deployment with dependency // 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())) fmt.Sprintf("/namespaces/%s/Pod/%s", namespaceName, pod1Obj.GetName()))
// external/missing dependency // 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)) fmt.Sprintf("/namespaces/%s/Pod/pod0", namespaceName))
// cyclic dependency (podB) // cyclic dependency (podB)
podAObj := templateToUnstructured(podATemplate, fields) podAObj := e2eutil.TemplateToUnstructured(podATemplate, fields)
// cyclic dependency (podA) & invalid source reference (dependency not in object set) // cyclic dependency (podA) & invalid source reference (dependency not in object set)
podBObj := templateToUnstructured(invalidMutationPodBTemplate, fields) podBObj := e2eutil.TemplateToUnstructured(invalidMutationPodBTemplate, fields)
// missing name // missing name
invalidPodObj := templateToUnstructured(invalidPodTemplate, fields) invalidPodObj := e2eutil.TemplateToUnstructured(invalidPodTemplate, fields)
resources := []*unstructured.Unstructured{ resources := []*unstructured.Unstructured{
pod1Obj, pod1Obj,
@ -55,7 +57,7 @@ func skipInvalidTest(ctx context.Context, c client.Client, invConfig InventoryCo
invalidPodObj, invalidPodObj,
} }
applierEvents := runCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{ applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
EmitStatusEvents: false, EmitStatusEvents: false,
ValidationPolicy: validation.SkipInvalid, 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)) Expect(testutil.EventsToExpEvents(applierEvents)).To(testutil.Equal(expEvents))
By("verify pod1 created and ready") 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") podIP, found, err := object.NestedField(result.Object, "status", "podIP")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
By("verify deployment1 created and ready") By("verify deployment1 created and ready")
result = assertUnstructuredExists(ctx, c, deployment1Obj) result = e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj)
assertUnstructuredAvailable(result) e2eutil.AssertUnstructuredAvailable(result)
By("verify pod3 not found") By("verify pod3 not found")
assertUnstructuredDoesNotExist(ctx, c, pod3Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj)
By("verify podA not found") By("verify podA not found")
assertUnstructuredDoesNotExist(ctx, c, podAObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj)
By("verify podB not found") By("verify podB not found")
assertUnstructuredDoesNotExist(ctx, c, podBObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
By("modify deployment1 depends-on annotation to be invalid") 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") By("destroy valid objects and skip invalid objects")
destroyer := invConfig.DestroyerFactoryFunc() destroyer := invConfig.DestroyerFactoryFunc()
destroyerEvents := runCollect(destroyer.Run(ctx, inv, apply.DestroyerOptions{ destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, apply.DestroyerOptions{
InventoryPolicy: inventory.PolicyAdoptIfNoInventory, InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
ValidationPolicy: validation.SkipInvalid, 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)) Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("verify pod1 deleted") By("verify pod1 deleted")
assertUnstructuredDoesNotExist(ctx, c, pod1Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod1Obj)
By("verify deployment1 not deleted") By("verify deployment1 not deleted")
assertUnstructuredExists(ctx, c, deployment1Obj) e2eutil.AssertUnstructuredExists(ctx, c, deployment1Obj)
deleteUnstructuredIfExists(ctx, c, deployment1Obj) e2eutil.DeleteUnstructuredIfExists(ctx, c, deployment1Obj)
By("verify pod3 not found") By("verify pod3 not found")
assertUnstructuredDoesNotExist(ctx, c, pod3Obj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, pod3Obj)
By("verify podA not found") By("verify podA not found")
assertUnstructuredDoesNotExist(ctx, c, podAObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj)
By("verify podB not found") By("verify podB not found")
assertUnstructuredDoesNotExist(ctx, c, podBObj) e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
} }

View File

@ -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"
`

View File

@ -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")
}

102
test/stress/stress_test.go Normal file
View File

@ -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())
})
})
})

View File

@ -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)
}