fix: Make DependencyFilter handle DryRun

DryRun skips WaitEvents. So the DependencyFilter needs to skip
checking for reconciliation if DryRun is enabled.
This commit is contained in:
Karl Isenberg 2022-03-01 15:48:02 -08:00
parent 5c6134aeac
commit 0f7f95b24a
6 changed files with 426 additions and 46 deletions

View File

@ -141,8 +141,9 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec
InvPolicy: options.InventoryPolicy,
},
filter.DependencyFilter{
TaskContext: taskContext,
Strategy: actuation.ActuationStrategyApply,
TaskContext: taskContext,
ActuationStrategy: actuation.ActuationStrategyApply,
DryRunStrategy: options.DryRunStrategy,
},
}
// Build list of prune validation filters.
@ -156,8 +157,9 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec
LocalNamespaces: localNamespaces(invInfo, object.UnstructuredSetToObjMetadataSet(objects)),
},
filter.DependencyFilter{
TaskContext: taskContext,
Strategy: actuation.ActuationStrategyDelete,
TaskContext: taskContext,
ActuationStrategy: actuation.ActuationStrategyDelete,
DryRunStrategy: options.DryRunStrategy,
},
}
// Build list of apply mutators.

View File

@ -147,8 +147,9 @@ func (d *Destroyer) Run(ctx context.Context, invInfo inventory.Info, options Des
InvPolicy: options.InventoryPolicy,
},
filter.DependencyFilter{
TaskContext: taskContext,
Strategy: actuation.ActuationStrategyDelete,
TaskContext: taskContext,
ActuationStrategy: actuation.ActuationStrategyDelete,
DryRunStrategy: options.DryRunStrategy,
},
}
taskBuilder := &solver.TaskQueueBuilder{

View File

@ -10,6 +10,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/object"
)
@ -24,8 +25,9 @@ const (
// DependencyFilter implements ValidationFilter interface to determine if an
// object can be applied or deleted based on the status of it's dependencies.
type DependencyFilter struct {
TaskContext *taskrunner.TaskContext
Strategy actuation.ActuationStrategy
TaskContext *taskrunner.TaskContext
ActuationStrategy actuation.ActuationStrategy
DryRunStrategy common.DryRunStrategy
}
const DependencyFilterName = "DependencyFilter"
@ -40,7 +42,7 @@ func (dnrf DependencyFilter) Name() string {
func (dnrf DependencyFilter) Filter(obj *unstructured.Unstructured) (bool, string, error) {
id := object.UnstructuredToObjMetadata(obj)
switch dnrf.Strategy {
switch dnrf.ActuationStrategy {
case actuation.ActuationStrategyApply:
// For apply, check dependencies (outgoing)
for _, depID := range dnrf.TaskContext.Graph().Dependencies(id) {
@ -64,7 +66,7 @@ func (dnrf DependencyFilter) Filter(obj *unstructured.Unstructured) (bool, strin
}
}
default:
panic(fmt.Sprintf("invalid filter strategy: %q", dnrf.Strategy))
panic(fmt.Sprintf("invalid filter strategy: %q", dnrf.ActuationStrategy))
}
return false, "", nil
}
@ -91,9 +93,9 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
// Dependencies must have the same actuation strategy.
// If there is a mismatch, skip both.
if status.Strategy != dnrf.Strategy {
if status.Strategy != dnrf.ActuationStrategy {
return true, fmt.Sprintf("%s skipped because %s is scheduled for %s: %q",
strings.ToLower(dnrf.Strategy.String()),
strings.ToLower(dnrf.ActuationStrategy.String()),
strings.ToLower(relationship.String()),
strings.ToLower(status.Strategy.String()),
id), nil
@ -103,7 +105,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
case actuation.ActuationPending:
// If actuation is still pending, dependency sorting is probably broken.
return false, "", fmt.Errorf("premature %s: %s %s actuation %s: %q",
strings.ToLower(dnrf.Strategy.String()),
strings.ToLower(dnrf.ActuationStrategy.String()),
strings.ToLower(relationship.String()),
strings.ToLower(status.Strategy.String()),
strings.ToLower(status.Actuation.String()),
@ -112,7 +114,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
// Skip!
return true, fmt.Sprintf("%s %s actuation %s: %q",
strings.ToLower(relationship.String()),
strings.ToLower(dnrf.Strategy.String()),
strings.ToLower(dnrf.ActuationStrategy.String()),
strings.ToLower(status.Actuation.String()),
id), nil
case actuation.ActuationSucceeded:
@ -124,11 +126,17 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
id)
}
// DryRun skips WaitTasks, so reconcile status can be ignored
if dnrf.DryRunStrategy.ClientOrServerDryRun() {
// Don't skip!
return false, "", nil
}
switch status.Reconcile {
case actuation.ReconcilePending:
// If reconcile is still pending, dependency sorting is probably broken.
return false, "", fmt.Errorf("premature %s: %s %s reconcile %s: %q",
strings.ToLower(dnrf.Strategy.String()),
strings.ToLower(dnrf.ActuationStrategy.String()),
strings.ToLower(relationship.String()),
strings.ToLower(status.Strategy.String()),
strings.ToLower(status.Reconcile.String()),
@ -137,7 +145,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
// Skip!
return true, fmt.Sprintf("%s %s reconcile %s: %q",
strings.ToLower(relationship.String()),
strings.ToLower(dnrf.Strategy.String()),
strings.ToLower(dnrf.ActuationStrategy.String()),
strings.ToLower(status.Reconcile.String()),
id), nil
case actuation.ReconcileSucceeded:

View File

@ -12,6 +12,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object"
)
@ -43,15 +44,16 @@ var idB = object.ObjMetadata{
func TestDependencyFilter(t *testing.T) {
tests := map[string]struct {
strategy actuation.ActuationStrategy
contextSetup func(*taskrunner.TaskContext)
id object.ObjMetadata
expectedFiltered bool
expectedReason string
expectedError error
dryRunStrategy common.DryRunStrategy
actuationStrategy actuation.ActuationStrategy
contextSetup func(*taskrunner.TaskContext)
id object.ObjMetadata
expectedFiltered bool
expectedReason string
expectedError error
}{
"apply A (no deps)": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.InventoryManager().AddPendingApply(idA)
@ -62,7 +64,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) when B is invalid": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idInvalid)
@ -76,7 +78,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) before B is applied": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -88,7 +90,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: fmt.Errorf("premature apply: dependency apply actuation pending: %q", idB),
},
"apply A (A -> B) before B is reconciled": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -105,7 +107,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: fmt.Errorf("premature apply: dependency apply reconcile pending: %q", idB),
},
"apply A (A -> B) after B is reconciled": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -124,7 +126,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) after B apply failed": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -143,7 +145,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) after B apply skipped": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -162,7 +164,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) after B reconcile failed": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -181,7 +183,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) after B reconcile timeout": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -201,7 +203,7 @@ func TestDependencyFilter(t *testing.T) {
},
// artificial use case: reconcile should only be skipped if apply failed or was skipped
"apply A (A -> B) after B reconcile skipped": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -220,7 +222,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"apply A (A -> B) when B delete pending": {
strategy: actuation.ActuationStrategyApply,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -234,7 +236,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (no deps)": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idB)
taskContext.InventoryManager().AddPendingDelete(idB)
@ -245,7 +247,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) when A is invalid": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idInvalid)
taskContext.Graph().AddVertex(idB)
@ -259,7 +261,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) before A is deleted": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -271,7 +273,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: fmt.Errorf("premature delete: dependent delete actuation pending: %q", idA),
},
"delete B (A -> B) before A is reconciled": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -288,7 +290,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: fmt.Errorf("premature delete: dependent delete reconcile pending: %q", idA),
},
"delete B (A -> B) after A is reconciled": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -307,7 +309,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) after A delete failed": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -326,7 +328,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) after A delete skipped": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -346,7 +348,7 @@ func TestDependencyFilter(t *testing.T) {
},
// artificial use case: delete reconcile can't fail, only timeout
"delete B (A -> B) after A reconcile failed": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -365,7 +367,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) after A reconcile timeout": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -385,7 +387,7 @@ func TestDependencyFilter(t *testing.T) {
},
// artificial use case: reconcile should only be skipped if delete failed or was skipped
"delete B (A -> B) after A reconcile skipped": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -404,7 +406,7 @@ func TestDependencyFilter(t *testing.T) {
expectedError: nil,
},
"delete B (A -> B) when A apply succeeded": {
strategy: actuation.ActuationStrategyDelete,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
@ -422,6 +424,46 @@ func TestDependencyFilter(t *testing.T) {
expectedReason: fmt.Sprintf("delete skipped because dependent is scheduled for apply: %q", idA),
expectedError: nil,
},
"DryRun: apply A (A -> B) when B apply reconcile pending": {
dryRunStrategy: common.DryRunClient,
actuationStrategy: actuation.ActuationStrategyApply,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
taskContext.Graph().AddEdge(idA, idB)
taskContext.InventoryManager().AddPendingApply(idA)
taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
Strategy: actuation.ActuationStrategyApply,
Actuation: actuation.ActuationSucceeded,
Reconcile: actuation.ReconcilePending,
})
},
id: idA,
expectedFiltered: false,
expectedReason: "",
expectedError: nil,
},
"DryRun: delete B (A -> B) when A delete reconcile pending": {
dryRunStrategy: common.DryRunClient,
actuationStrategy: actuation.ActuationStrategyDelete,
contextSetup: func(taskContext *taskrunner.TaskContext) {
taskContext.Graph().AddVertex(idA)
taskContext.Graph().AddVertex(idB)
taskContext.Graph().AddEdge(idA, idB)
taskContext.InventoryManager().AddPendingDelete(idB)
taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
Strategy: actuation.ActuationStrategyDelete,
Actuation: actuation.ActuationSucceeded,
Reconcile: actuation.ReconcilePending,
})
},
id: idB,
expectedFiltered: false,
expectedReason: "",
expectedError: nil,
},
}
for name, tc := range tests {
@ -430,8 +472,9 @@ func TestDependencyFilter(t *testing.T) {
tc.contextSetup(taskContext)
filter := DependencyFilter{
TaskContext: taskContext,
Strategy: tc.strategy,
TaskContext: taskContext,
ActuationStrategy: tc.actuationStrategy,
DryRunStrategy: tc.dryRunStrategy,
}
obj := defaultObj.DeepCopy()
obj.SetGroupVersionKind(tc.id.GroupKind.WithVersion("v1"))

301
test/e2e/dry_run_test.go Normal file
View File

@ -0,0 +1,301 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/pkg/kstatus/status"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) {
By("Apply with DryRun")
applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID)
namespace1Name := fmt.Sprintf("%s-ns1", namespaceName)
fields := struct{ Namespace string }{Namespace: namespace1Name}
namespace1Obj := templateToUnstructured(namespaceTemplate, fields)
podBObj := templateToUnstructured(podBTemplate, fields)
// Dependency order: podB -> namespace1
// Apply order: namespace1, podB
resources := []*unstructured.Unstructured{
namespace1Obj,
podBObj,
}
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute,
EmitStatusEvents: true,
DryRunStrategy: common.DryRunClient,
}))
expEvents := []testutil.ExpEvent{
{
// InitTask
EventType: event.InitType,
InitEvent: &testutil.ExpInitEvent{},
},
{
// InvAddTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "inventory-add-0",
Type: event.Started,
},
},
{
// InvAddTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "inventory-add-0",
Type: event.Finished,
},
},
{
// ApplyTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.ApplyAction,
GroupName: "apply-0",
Type: event.Started,
},
},
{
// Create namespace
EventType: event.ApplyType,
ApplyEvent: &testutil.ExpApplyEvent{
GroupName: "apply-0",
Operation: event.Created,
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
Error: nil,
},
},
{
// ApplyTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.ApplyAction,
GroupName: "apply-0",
Type: event.Finished,
},
},
{
// ApplyTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.ApplyAction,
GroupName: "apply-1",
Type: event.Started,
},
},
{
// Create pod
EventType: event.ApplyType,
ApplyEvent: &testutil.ExpApplyEvent{
GroupName: "apply-1",
Operation: event.Created,
Identifier: object.UnstructuredToObjMetadata(podBObj),
Error: nil,
},
},
{
// ApplyTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.ApplyAction,
GroupName: "apply-1",
Type: event.Finished,
},
},
// No Wait Tasks for Dry Run
{
// InvSetTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "inventory-set-0",
Type: event.Started,
},
},
{
// InvSetTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "inventory-set-0",
Type: event.Finished,
},
},
}
received := testutil.EventsToExpEvents(applierEvents)
// handle required async NotFound StatusEvent for pod
expected := testutil.ExpEvent{
EventType: event.StatusType,
StatusEvent: &testutil.ExpStatusEvent{
Identifier: object.UnstructuredToObjMetadata(podBObj),
Status: status.NotFoundStatus,
Error: nil,
},
}
received, matches := testutil.RemoveEqualEvents(received, expected)
Expect(matches).To(BeNumerically(">=", 1), "unexpected number of %q status events for namespace", status.NotFoundStatus)
// handle required async NotFound StatusEvent for namespace
expected = testutil.ExpEvent{
EventType: event.StatusType,
StatusEvent: &testutil.ExpStatusEvent{
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
Status: status.NotFoundStatus,
Error: nil,
},
}
received, matches = testutil.RemoveEqualEvents(received, expected)
Expect(matches).To(BeNumerically(">=", 1), "unexpected number of %q status events for pod", status.NotFoundStatus)
Expect(received).To(testutil.Equal(expEvents))
By("Verify pod NotFound")
assertUnstructuredDoesNotExist(ctx, c, podBObj)
By("Verify inventory NotFound")
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)
By("Apply")
runWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
ReconcileTimeout: 2 * time.Minute,
}))
By("Verify pod created")
assertUnstructuredExists(ctx, c, podBObj)
By("Verify inventory size")
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 2)
By("Destroy with DryRun")
destroyer := invConfig.DestroyerFactoryFunc()
destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
EmitStatusEvents: true,
DryRunStrategy: common.DryRunClient,
}))
expEvents = []testutil.ExpEvent{
{
// InitTask
EventType: event.InitType,
InitEvent: &testutil.ExpInitEvent{},
},
{
// PruneTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.DeleteAction,
GroupName: "prune-0",
Type: event.Started,
},
},
{
// Delete pod
EventType: event.DeleteType,
DeleteEvent: &testutil.ExpDeleteEvent{
GroupName: "prune-0",
Operation: event.Deleted,
Identifier: object.UnstructuredToObjMetadata(podBObj),
Error: nil,
},
},
{
// PruneTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.DeleteAction,
GroupName: "prune-0",
Type: event.Finished,
},
},
{
// PruneTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.DeleteAction,
GroupName: "prune-1",
Type: event.Started,
},
},
{
// Delete namespace
EventType: event.DeleteType,
DeleteEvent: &testutil.ExpDeleteEvent{
GroupName: "prune-1",
Operation: event.Deleted,
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
Error: nil,
},
},
{
// PruneTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.DeleteAction,
GroupName: "prune-1",
Type: event.Finished,
},
},
// No Wait Tasks for Dry Run
{
// DeleteInvTask start
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "delete-inventory-0",
Type: event.Started,
},
},
{
// DeleteInvTask finished
EventType: event.ActionGroupType,
ActionGroupEvent: &testutil.ExpActionGroupEvent{
Action: event.InventoryAction,
GroupName: "delete-inventory-0",
Type: event.Finished,
},
},
}
Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
By("Verify pod still exists")
assertUnstructuredExists(ctx, c, podBObj)
By("Destroy")
runWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
}))
By("Verify pod deleted")
assertUnstructuredDoesNotExist(ctx, c, podBObj)
By("Verify inventory deleted")
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)
}

View File

@ -36,6 +36,7 @@ type applierFactoryFunc func() *apply.Applier
type destroyerFactoryFunc func() *apply.Destroyer
type invSizeVerifyFunc func(ctx context.Context, c client.Client, name, namespace, id string, count 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
@ -45,6 +46,7 @@ type InventoryConfig struct {
DestroyerFactoryFunc destroyerFactoryFunc
InvSizeVerifyFunc invSizeVerifyFunc
InvCountVerifyFunc invCountVerifyFunc
InvNotExistsFunc invNotExistsFunc
}
const (
@ -61,6 +63,7 @@ var inventoryConfigs = map[string]InventoryConfig{
DestroyerFactoryFunc: newDefaultInvDestroyer,
InvSizeVerifyFunc: defaultInvSizeVerifyFunc,
InvCountVerifyFunc: defaultInvCountVerifyFunc,
InvNotExistsFunc: defaultInvNotExistsFunc,
},
CustomTypeInvConfig: {
Strategy: inventory.NameStrategy,
@ -70,6 +73,7 @@ var inventoryConfigs = map[string]InventoryConfig{
DestroyerFactoryFunc: newCustomInvDestroyer,
InvSizeVerifyFunc: customInvSizeVerifyFunc,
InvCountVerifyFunc: customInvCountVerifyFunc,
InvNotExistsFunc: customInvNotExistsFunc,
},
}
@ -163,6 +167,10 @@ var _ = Describe("Applier", func() {
applyAndDestroyTest(ctx, c, invConfig, inventoryName, namespace.GetName())
})
It("DryRun", func() {
dryRunTest(ctx, c, invConfig, inventoryName, namespace.GetName())
})
It("Deletion Prevention", func() {
deletionPreventionTest(ctx, c, invConfig, inventoryName, namespace.GetName())
})
@ -340,6 +348,15 @@ func newDefaultInvDestroyer() *apply.Destroyer {
return newDestroyerFromInvFactory(inventory.ClusterClientFactory{})
}
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, count int) {
var cmList v1.ConfigMapList
err := c.List(ctx, &cmList,
@ -376,6 +393,14 @@ func newFactory() util.Factory {
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, count int) {
var u unstructured.Unstructured
u.SetGroupVersionKind(customprovider.InventoryGVK)