mirror of https://github.com/fluxcd/cli-utils.git
fix: skipped deletes no longer cause waiting
- Added SkippedApplies and SkippedDeletes to the TaskContext - Modified tasks to use the new skipped tracking, replacing usage of failure tracking, where skipped is more accurate. - Renamed some TaskContext methods for consistency - Added ObjMetadataSetFromMap for use by TaskContext - Added ObjectMetadataSet.Intersection for use by InvSetTask - Cleaned up InvSetTask to be more readable with comments explaining intended behavior, including handling of skips. - Added apply and prune tests for skipped, failure, and abandoned
This commit is contained in:
parent
89b6f0b5b1
commit
eda3554fb1
|
@ -111,34 +111,31 @@ func (p *Pruner) Prune(
|
|||
klog.Errorf("error during %s, (%s): %v", pruneFilter.Name(), id, err)
|
||||
}
|
||||
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(id, err)
|
||||
taskContext.CapturePruneFailure(id)
|
||||
taskContext.AddFailedDelete(id)
|
||||
break
|
||||
}
|
||||
if filtered {
|
||||
klog.V(4).Infof("prune skipped (object: %q, filter: %q): %v", id, pruneFilter.Name(), reason)
|
||||
// pruneFailure indicates whether `taskContext.CapturePruneFailure` should be called.
|
||||
pruneFailure := true
|
||||
// If deletion was prevented, remove the inventory annotation.
|
||||
if pruneFilter.Name() == filter.PreventRemoveFilterName {
|
||||
if opts.DryRunStrategy.ClientOrServerDryRun() {
|
||||
pruneFailure = false
|
||||
} else {
|
||||
if !opts.DryRunStrategy.ClientOrServerDryRun() {
|
||||
err := p.removeInventoryAnnotation(obj)
|
||||
if err != nil {
|
||||
if klog.V(4).Enabled() {
|
||||
klog.Errorf("error removing annotation (object: %q, annotation: %q): %v", id, inventory.OwningInventoryKey, err)
|
||||
}
|
||||
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(id, err)
|
||||
taskContext.CapturePruneFailure(id)
|
||||
taskContext.AddFailedDelete(id)
|
||||
break
|
||||
} else {
|
||||
pruneFailure = false
|
||||
// Inventory annotation was successfully removed from the object.
|
||||
// Register for removal from the inventory.
|
||||
taskContext.AddAbandonedObject(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
taskContext.EventChannel() <- eventFactory.CreateSkippedEvent(obj, reason)
|
||||
if pruneFailure {
|
||||
taskContext.CapturePruneFailure(id)
|
||||
}
|
||||
taskContext.AddSkippedDelete(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +153,7 @@ func (p *Pruner) Prune(
|
|||
klog.Errorf("error deleting object (object: %q): %v", id, err)
|
||||
}
|
||||
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(id, err)
|
||||
taskContext.CapturePruneFailure(id)
|
||||
taskContext.AddFailedDelete(id)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,10 +184,13 @@ var (
|
|||
|
||||
func TestPrune(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
pruneObjs []*unstructured.Unstructured
|
||||
pruneFilters []filter.ValidationFilter
|
||||
options Options
|
||||
expectedEvents []testutil.ExpEvent
|
||||
pruneObjs []*unstructured.Unstructured
|
||||
pruneFilters []filter.ValidationFilter
|
||||
options Options
|
||||
expectedEvents []testutil.ExpEvent
|
||||
expectedSkipped object.ObjMetadataSet
|
||||
expectedFailed object.ObjMetadataSet
|
||||
expectedAbandoned object.ObjMetadataSet
|
||||
}{
|
||||
"No pruned objects; no prune/delete events": {
|
||||
pruneObjs: []*unstructured.Unstructured{},
|
||||
|
@ -311,6 +314,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(pod),
|
||||
},
|
||||
},
|
||||
"UID match for only one object one pruned, one skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb},
|
||||
|
@ -335,6 +341,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(pod),
|
||||
},
|
||||
},
|
||||
"Prevent delete annotation equals prune skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, testutil.Unstructured(t, pdbDeletePreventionManifest)},
|
||||
|
@ -354,6 +363,14 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
testutil.ToIdentifier(t, pdbDeletePreventionManifest),
|
||||
},
|
||||
expectedAbandoned: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
testutil.ToIdentifier(t, pdbDeletePreventionManifest),
|
||||
},
|
||||
},
|
||||
"Prevent delete annotation equals delete skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, testutil.Unstructured(t, pdbDeletePreventionManifest)},
|
||||
|
@ -373,6 +390,14 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
testutil.ToIdentifier(t, pdbDeletePreventionManifest),
|
||||
},
|
||||
expectedAbandoned: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
testutil.ToIdentifier(t, pdbDeletePreventionManifest),
|
||||
},
|
||||
},
|
||||
"Prevent delete annotation, one skipped, one pruned": {
|
||||
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, pod},
|
||||
|
@ -392,6 +417,12 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
},
|
||||
expectedAbandoned: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(podDeletionPrevention),
|
||||
},
|
||||
},
|
||||
"Namespace prune skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{namespace},
|
||||
|
@ -409,6 +440,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedSkipped: object.ObjMetadataSet{
|
||||
object.UnstructuredToObjMetaOrDie(namespace),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -449,6 +483,28 @@ func TestPrune(t *testing.T) {
|
|||
// Validate the expected/actual events
|
||||
err = testutil.VerifyEvents(tc.expectedEvents, actualEvents)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// validate record of failed prunes
|
||||
for _, id := range tc.expectedFailed {
|
||||
assert.Truef(t, taskContext.IsFailedDelete(id), "Prune() should mark object as failed: %s", id)
|
||||
}
|
||||
for _, id := range object.ObjMetadataSet(pruneIds).Diff(tc.expectedFailed) {
|
||||
assert.Falsef(t, taskContext.IsFailedDelete(id), "Prune() should NOT mark object as failed: %s", id)
|
||||
}
|
||||
// validate record of skipped prunes
|
||||
for _, id := range tc.expectedSkipped {
|
||||
assert.Truef(t, taskContext.IsSkippedDelete(id), "Prune() should mark object as skipped: %s", id)
|
||||
}
|
||||
for _, id := range object.ObjMetadataSet(pruneIds).Diff(tc.expectedSkipped) {
|
||||
assert.Falsef(t, taskContext.IsSkippedDelete(id), "Prune() should NOT mark object as skipped: %s", id)
|
||||
}
|
||||
// validate record of abandoned objects
|
||||
for _, id := range tc.expectedAbandoned {
|
||||
assert.Truef(t, taskContext.IsAbandonedObject(id), "Prune() should mark object as abandoned: %s", id)
|
||||
}
|
||||
for _, id := range object.ObjMetadataSet(pruneIds).Diff(tc.expectedAbandoned) {
|
||||
assert.Falsef(t, taskContext.IsAbandonedObject(id), "Prune() should NOT mark object as abandoned: %s", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -496,25 +552,22 @@ func TestPruneDeletionPrevention(t *testing.T) {
|
|||
// Run the prune and validate.
|
||||
return po.Prune([]*unstructured.Unstructured{tc.pruneObj}, []filter.ValidationFilter{filter.PreventRemoveFilter{}}, taskContext, "test-0", tc.options)
|
||||
}()
|
||||
require.NoError(t, err)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error during Prune(): %#v", err)
|
||||
}
|
||||
// verify that the object no longer has the annotation
|
||||
obj, err := po.getObject(pruneID)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %#v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
hasOwningInventoryAnnotation := false
|
||||
for annotation := range obj.GetAnnotations() {
|
||||
if annotation == inventory.OwningInventoryKey {
|
||||
hasOwningInventoryAnnotation = true
|
||||
t.Errorf("Prune() should remove the %s annotation", inventory.OwningInventoryKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasOwningInventoryAnnotation {
|
||||
t.Fatalf("Prune() should remove the %s annotation", inventory.OwningInventoryKey)
|
||||
}
|
||||
|
||||
assert.Truef(t, taskContext.IsAbandonedObject(pruneID), "Prune() should mark object as abandoned")
|
||||
assert.Truef(t, taskContext.IsSkippedDelete(pruneID), "Prune() should mark object as skipped")
|
||||
assert.Falsef(t, taskContext.IsFailedDelete(pruneID), "Prune() should NOT mark object as failed")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,15 +125,11 @@ func (t *TaskQueueBuilder) AppendInvAddTask(inv inventory.InventoryInfo, applyOb
|
|||
func (t *TaskQueueBuilder) AppendInvSetTask(inv inventory.InventoryInfo, dryRun common.DryRunStrategy) *TaskQueueBuilder {
|
||||
klog.V(2).Infoln("adding inventory set task")
|
||||
prevInvIds, _ := t.InvClient.GetClusterObjs(inv, dryRun)
|
||||
prevInventory := make(map[object.ObjMetadata]bool, len(prevInvIds))
|
||||
for _, prevInvID := range prevInvIds {
|
||||
prevInventory[prevInvID] = true
|
||||
}
|
||||
t.tasks = append(t.tasks, &task.InvSetTask{
|
||||
TaskName: fmt.Sprintf("inventory-set-%d", t.invSetCounter),
|
||||
InvClient: t.InvClient,
|
||||
InvInfo: inv,
|
||||
PrevInventory: prevInventory,
|
||||
PrevInventory: prevInvIds,
|
||||
DryRun: dryRun,
|
||||
})
|
||||
t.invSetCounter += 1
|
||||
|
|
|
@ -112,7 +112,7 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
|
|||
id,
|
||||
applyerror.NewUnknownTypeError(err),
|
||||
)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddFailedApply(id)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -128,13 +128,13 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
|
|||
klog.Errorf("error during %s, (%s): %s", filter.Name(), id, filterErr)
|
||||
}
|
||||
taskContext.EventChannel() <- a.createApplyFailedEvent(id, filterErr)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddFailedApply(id)
|
||||
break
|
||||
}
|
||||
if filtered {
|
||||
klog.V(4).Infof("apply filtered (filter: %q, resource: %q, reason: %q)", filter.Name(), id, reason)
|
||||
taskContext.EventChannel() <- a.createApplyEvent(id, event.Unchanged, obj)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddSkippedApply(id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
|
|||
klog.Errorf("error mutating: %w", err)
|
||||
}
|
||||
taskContext.EventChannel() <- a.createApplyFailedEvent(id, err)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddFailedApply(id)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -171,13 +171,13 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
|
|||
id,
|
||||
applyerror.NewApplyRunError(err),
|
||||
)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddFailedApply(id)
|
||||
} else if info.Object != nil {
|
||||
acc, err := meta.Accessor(info.Object)
|
||||
if err == nil {
|
||||
uid := acc.GetUID()
|
||||
gen := acc.GetGeneration()
|
||||
taskContext.ResourceApplied(id, uid, gen)
|
||||
taskContext.AddSuccessfulApply(id, uid, gen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ func (a *ApplyTask) sendBatchApplyEvents(
|
|||
id,
|
||||
applyerror.NewInitializeApplyOptionError(err),
|
||||
)
|
||||
taskContext.CaptureResourceFailure(id)
|
||||
taskContext.AddFailedApply(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,12 +112,17 @@ func TestApplyTask_BasicAppliedObjects(t *testing.T) {
|
|||
|
||||
// The applied resources should be stored in the TaskContext
|
||||
// for the final inventory.
|
||||
expected, err := object.UnstructuredsToObjMetas(objs)
|
||||
expectedIDs, err := object.UnstructuredsToObjMetas(objs)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := taskContext.AppliedResources()
|
||||
if !actual.Equal(expected) {
|
||||
t.Errorf("expected (%s) inventory resources, got (%s)", expected, actual)
|
||||
actual := taskContext.SuccessfulApplies()
|
||||
if !actual.Equal(expectedIDs) {
|
||||
t.Errorf("expected (%s) inventory resources, got (%s)", expectedIDs, actual)
|
||||
}
|
||||
|
||||
for _, id := range expectedIDs {
|
||||
assert.Falsef(t, taskContext.IsFailedApply(id), "ApplyTask should NOT mark object as failed: %s", id)
|
||||
assert.Falsef(t, taskContext.IsSkippedApply(id), "ApplyTask should NOT mark object as skipped: %s", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -193,7 +198,7 @@ func TestApplyTask_FetchGeneration(t *testing.T) {
|
|||
Name: info.name,
|
||||
Namespace: info.namespace,
|
||||
}
|
||||
uid, _ := taskContext.ResourceUID(id)
|
||||
uid, _ := taskContext.AppliedResourceUID(id)
|
||||
assert.Equal(t, info.uid, uid)
|
||||
|
||||
gen, _ := taskContext.AppliedGeneration(id)
|
||||
|
@ -341,8 +346,10 @@ func TestApplyTask_DryRun(t *testing.T) {
|
|||
func TestApplyTaskWithError(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
objs []*unstructured.Unstructured
|
||||
expectedObjects []object.ObjMetadata
|
||||
expectedObjects object.ObjMetadataSet
|
||||
expectedEvents []event.Event
|
||||
expectedSkipped object.ObjMetadataSet
|
||||
expectedFailed object.ObjMetadataSet
|
||||
}{
|
||||
"some resources have apply error": {
|
||||
objs: []*unstructured.Unstructured{
|
||||
|
@ -381,7 +388,7 @@ func TestApplyTaskWithError(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
},
|
||||
expectedObjects: []object.ObjMetadata{
|
||||
expectedObjects: object.ObjMetadataSet{
|
||||
{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "apiextensions.k8s.io",
|
||||
|
@ -406,6 +413,16 @@ func TestApplyTaskWithError(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
expectedFailed: object.ObjMetadataSet{
|
||||
{
|
||||
GroupKind: schema.GroupKind{
|
||||
Group: "anothercustom.io",
|
||||
Kind: "AnotherCustom",
|
||||
},
|
||||
Name: "bar-with-failure",
|
||||
Namespace: "barbar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -469,6 +486,24 @@ func TestApplyTaskWithError(t *testing.T) {
|
|||
assert.Equal(t, tc.expectedEvents[i].Type, e.Type)
|
||||
assert.Equal(t, tc.expectedEvents[i].ApplyEvent.Error.Error(), e.ApplyEvent.Error.Error())
|
||||
}
|
||||
|
||||
applyIds, err := object.UnstructuredsToObjMetas(tc.objs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// validate record of failed prunes
|
||||
for _, id := range tc.expectedFailed {
|
||||
assert.Truef(t, taskContext.IsFailedApply(id), "ApplyTask should mark object as failed: %s", id)
|
||||
}
|
||||
for _, id := range object.ObjMetadataSet(applyIds).Diff(tc.expectedFailed) {
|
||||
assert.Falsef(t, taskContext.IsFailedApply(id), "ApplyTask should NOT mark object as failed: %s", id)
|
||||
}
|
||||
// validate record of skipped prunes
|
||||
for _, id := range tc.expectedSkipped {
|
||||
assert.Truef(t, taskContext.IsSkippedApply(id), "ApplyTask should mark object as skipped: %s", id)
|
||||
}
|
||||
for _, id := range object.ObjMetadataSet(applyIds).Diff(tc.expectedSkipped) {
|
||||
assert.Falsef(t, taskContext.IsSkippedApply(id), "ApplyTask should NOT mark object as skipped: %s", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ type InvSetTask struct {
|
|||
TaskName string
|
||||
InvClient inventory.InventoryClient
|
||||
InvInfo inventory.InventoryInfo
|
||||
PrevInventory map[object.ObjMetadata]bool
|
||||
PrevInventory object.ObjMetadataSet
|
||||
DryRun common.DryRunStrategy
|
||||
}
|
||||
|
||||
|
@ -34,31 +34,81 @@ func (i *InvSetTask) Identifiers() object.ObjMetadataSet {
|
|||
return object.ObjMetadataSet{}
|
||||
}
|
||||
|
||||
// Start sets the inventory using the resources applied and the
|
||||
// prunes that failed. This task must run after all the apply
|
||||
// and prune tasks have completed.
|
||||
// Start sets (creates or replaces) the inventory.
|
||||
//
|
||||
// The guiding principal is that anything in the cluster should be in the
|
||||
// inventory, unless it was explicitly abandoned.
|
||||
//
|
||||
// This task must run after all the apply and prune tasks have completed.
|
||||
//
|
||||
// Added objects:
|
||||
// - Applied resources (successful)
|
||||
//
|
||||
// Retained objects:
|
||||
// - Applied resources (filtered/skipped)
|
||||
// - Applied resources (failed)
|
||||
// - Deleted resources (filtered/skipped) that were not abandoned
|
||||
// - Deleted resources (failed)
|
||||
// - Abandoned resources (failed)
|
||||
//
|
||||
// Removed objects:
|
||||
// - Deleted resources (successful)
|
||||
// - Abandoned resources (successful)
|
||||
func (i *InvSetTask) Start(taskContext *taskrunner.TaskContext) {
|
||||
go func() {
|
||||
klog.V(2).Infoln("starting inventory replace task")
|
||||
appliedObjs := taskContext.AppliedResources()
|
||||
klog.V(4).Infof("set inventory %d applied objects", len(appliedObjs))
|
||||
// If an object failed to apply, but it was previously stored in
|
||||
// the inventory, then keep it in the inventory so we don't lose
|
||||
// track of it for next apply/prune. An object not found in the cluster
|
||||
// is NOT stored as an apply failure (so it is properly removed from the inventory).
|
||||
applyFailures := object.ObjMetadataSet{}
|
||||
for _, failure := range taskContext.ResourceFailures() {
|
||||
if _, exists := i.PrevInventory[failure]; exists {
|
||||
applyFailures = append(applyFailures, failure)
|
||||
}
|
||||
}
|
||||
klog.V(4).Infof("keep in inventory %d applied failures", len(applyFailures))
|
||||
pruneFailures := taskContext.PruneFailures()
|
||||
klog.V(4).Infof("set inventory %d prune failures", len(pruneFailures))
|
||||
allApplyObjs := appliedObjs.Union(applyFailures)
|
||||
invObjs := allApplyObjs.Union(pruneFailures)
|
||||
klog.V(2).Infof("starting inventory set task (name: %q)", i.Name())
|
||||
invObjs := object.ObjMetadataSet{}
|
||||
|
||||
// If an object applied successfully, keep or add it to the inventory.
|
||||
appliedObjs := taskContext.SuccessfulApplies()
|
||||
klog.V(4).Infof("set inventory %d successful applies", len(appliedObjs))
|
||||
invObjs = invObjs.Union(appliedObjs)
|
||||
|
||||
// If an object failed to apply and was previously stored in the inventory,
|
||||
// then keep it in the inventory so it can be applied/pruned next time.
|
||||
// This will remove new resources that failed to apply from the inventory,
|
||||
// because even tho they were added by InvAddTask, the PrevInventory
|
||||
// represents the inventory before the pipeline has run.
|
||||
applyFailures := i.PrevInventory.Intersection(taskContext.FailedApplies())
|
||||
klog.V(4).Infof("keep in inventory %d failed applies", len(applyFailures))
|
||||
invObjs = invObjs.Union(applyFailures)
|
||||
|
||||
// If an object skipped apply and was previously stored in the inventory,
|
||||
// then keep it in the inventory so it can be applied/pruned next time.
|
||||
// It's likely that all the skipped applies are already in the inventory,
|
||||
// because the apply filters all currently depend on cluster state,
|
||||
// but we're doing the intersection anyway just to be sure.
|
||||
applySkips := i.PrevInventory.Intersection(taskContext.SkippedApplies())
|
||||
klog.V(4).Infof("keep in inventory %d skipped applies", len(applySkips))
|
||||
invObjs = invObjs.Union(applySkips)
|
||||
|
||||
// If an object failed to delete and was previously stored in the inventory,
|
||||
// then keep it in the inventory so it can be applied/pruned next time.
|
||||
// It's likely that all the delete failures are already in the inventory,
|
||||
// because the set of resources to prune comes from the inventory,
|
||||
// but we're doing the intersection anyway just to be sure.
|
||||
pruneFailures := i.PrevInventory.Intersection(taskContext.FailedDeletes())
|
||||
klog.V(4).Infof("set inventory %d failed prunes", len(pruneFailures))
|
||||
invObjs = invObjs.Union(pruneFailures)
|
||||
|
||||
// If an object skipped delete and was previously stored in the inventory,
|
||||
// then keep it in the inventory so it can be applied/pruned next time.
|
||||
// It's likely that all the skipped deletes are already in the inventory,
|
||||
// because the set of resources to prune comes from the inventory,
|
||||
// but we're doing the intersection anyway just to be sure.
|
||||
pruneSkips := i.PrevInventory.Intersection(taskContext.SkippedDeletes())
|
||||
klog.V(4).Infof("keep in inventory %d skipped prunes", len(pruneSkips))
|
||||
invObjs = invObjs.Union(pruneSkips)
|
||||
|
||||
// If an object is abandoned, then remove it from the inventory.
|
||||
abandonedObjects := taskContext.AbandonedObjects()
|
||||
klog.V(4).Infof("remove from inventory %d abandoned objects", len(abandonedObjects))
|
||||
invObjs = invObjs.Diff(abandonedObjects)
|
||||
|
||||
klog.V(4).Infof("set inventory %d total objects", len(invObjs))
|
||||
err := i.InvClient.Replace(i.InvInfo, invObjs, i.DryRun)
|
||||
|
||||
klog.V(2).Infof("completing inventory set task (name: %q)", i.Name())
|
||||
taskContext.TaskChannel() <- taskrunner.TaskResult{Err: err}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/cli-utils/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestInvSetTask(t *testing.T) {
|
||||
|
@ -20,86 +21,175 @@ func TestInvSetTask(t *testing.T) {
|
|||
id3 := object.UnstructuredToObjMetaOrDie(obj3)
|
||||
|
||||
tests := map[string]struct {
|
||||
appliedObjs object.ObjMetadataSet
|
||||
applyFailures object.ObjMetadataSet
|
||||
prevInventory object.ObjMetadataSet
|
||||
pruneFailures object.ObjMetadataSet
|
||||
expectedObjs object.ObjMetadataSet
|
||||
prevInventory object.ObjMetadataSet
|
||||
appliedObjs object.ObjMetadataSet
|
||||
failedApplies object.ObjMetadataSet
|
||||
failedDeletes object.ObjMetadataSet
|
||||
skippedApplies object.ObjMetadataSet
|
||||
skippedDeletes object.ObjMetadataSet
|
||||
abandonedObjs object.ObjMetadataSet
|
||||
expectedObjs object.ObjMetadataSet
|
||||
}{
|
||||
"no apply objs, no prune failures; no inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"one apply objs, no prune failures; one inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{id1},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id1},
|
||||
},
|
||||
"no apply objs, one prune failures; one inventory": {
|
||||
"no apply objs, one prune failure, in prev inventory; one inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
pruneFailures: object.ObjMetadataSet{id1},
|
||||
failedDeletes: object.ObjMetadataSet{id1},
|
||||
expectedObjs: object.ObjMetadataSet{id1},
|
||||
},
|
||||
"no apply objs, one prune failure, not in prev inventory; no inventory": {
|
||||
// aritifical use case: prunes come from the inventory
|
||||
prevInventory: object.ObjMetadataSet{},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{id1},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"one apply objs, one prune failures; one inventory": {
|
||||
// aritifical use case: applies and prunes are mutually exclusive
|
||||
appliedObjs: object.ObjMetadataSet{id3},
|
||||
pruneFailures: object.ObjMetadataSet{id3},
|
||||
failedDeletes: object.ObjMetadataSet{id3},
|
||||
expectedObjs: object.ObjMetadataSet{id3},
|
||||
},
|
||||
"two apply objs, two prune failures; three inventory": {
|
||||
// aritifical use case: applies and prunes are mutually exclusive
|
||||
prevInventory: object.ObjMetadataSet{id2, id3},
|
||||
appliedObjs: object.ObjMetadataSet{id1, id2},
|
||||
pruneFailures: object.ObjMetadataSet{id2, id3},
|
||||
failedDeletes: object.ObjMetadataSet{id2, id3},
|
||||
expectedObjs: object.ObjMetadataSet{id1, id2, id3},
|
||||
},
|
||||
"no apply objs, no apply failures, no prune failures; no inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
applyFailures: object.ObjMetadataSet{id3},
|
||||
failedApplies: object.ObjMetadataSet{id3},
|
||||
prevInventory: object.ObjMetadataSet{},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"one apply failure not in prev inventory; no inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
applyFailures: object.ObjMetadataSet{id3},
|
||||
failedApplies: object.ObjMetadataSet{id3},
|
||||
prevInventory: object.ObjMetadataSet{},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"one apply obj, one apply failure not in prev inventory; one inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{id2},
|
||||
applyFailures: object.ObjMetadataSet{id3},
|
||||
failedApplies: object.ObjMetadataSet{id3},
|
||||
prevInventory: object.ObjMetadataSet{},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id2},
|
||||
},
|
||||
"one apply obj, one apply failure in prev inventory; one inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{id2},
|
||||
applyFailures: object.ObjMetadataSet{id3},
|
||||
failedApplies: object.ObjMetadataSet{id3},
|
||||
prevInventory: object.ObjMetadataSet{id3},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id2, id3},
|
||||
},
|
||||
"one apply obj, two apply failures with one in prev inventory; two inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{id2},
|
||||
applyFailures: object.ObjMetadataSet{id1, id3},
|
||||
failedApplies: object.ObjMetadataSet{id1, id3},
|
||||
prevInventory: object.ObjMetadataSet{id3},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id2, id3},
|
||||
},
|
||||
"three apply failures with two in prev inventory; two inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
applyFailures: object.ObjMetadataSet{id1, id2, id3},
|
||||
failedApplies: object.ObjMetadataSet{id1, id2, id3},
|
||||
prevInventory: object.ObjMetadataSet{id2, id3},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id2, id3},
|
||||
},
|
||||
"three apply failures with three in prev inventory; three inventory": {
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
applyFailures: object.ObjMetadataSet{id1, id2, id3},
|
||||
failedApplies: object.ObjMetadataSet{id1, id2, id3},
|
||||
prevInventory: object.ObjMetadataSet{id2, id3, id1},
|
||||
pruneFailures: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id2, id1, id3},
|
||||
},
|
||||
"one skipped apply from prev inventory; one inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedApplies: object.ObjMetadataSet{id1},
|
||||
skippedDeletes: object.ObjMetadataSet{},
|
||||
abandonedObjs: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id1},
|
||||
},
|
||||
"one skipped apply, no prev inventory; no inventory": {
|
||||
prevInventory: object.ObjMetadataSet{},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedApplies: object.ObjMetadataSet{id1},
|
||||
skippedDeletes: object.ObjMetadataSet{},
|
||||
abandonedObjs: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"one apply obj, one skipped apply, two prev inventory; two inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1, id2},
|
||||
appliedObjs: object.ObjMetadataSet{id2},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedApplies: object.ObjMetadataSet{id1},
|
||||
skippedDeletes: object.ObjMetadataSet{},
|
||||
abandonedObjs: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id1, id2},
|
||||
},
|
||||
"one skipped delete from prev inventory; one inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedApplies: object.ObjMetadataSet{},
|
||||
skippedDeletes: object.ObjMetadataSet{id1},
|
||||
abandonedObjs: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id1},
|
||||
},
|
||||
"one apply obj, one skipped delete, two prev inventory; two inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1, id2},
|
||||
appliedObjs: object.ObjMetadataSet{id2},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedApplies: object.ObjMetadataSet{},
|
||||
skippedDeletes: object.ObjMetadataSet{id1},
|
||||
abandonedObjs: object.ObjMetadataSet{},
|
||||
expectedObjs: object.ObjMetadataSet{id1, id2},
|
||||
},
|
||||
"two apply obj, one abandoned, three in prev inventory; two inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id1, id2, id3},
|
||||
appliedObjs: object.ObjMetadataSet{id1, id2},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
abandonedObjs: object.ObjMetadataSet{id3},
|
||||
expectedObjs: object.ObjMetadataSet{id1, id2},
|
||||
},
|
||||
"two abandoned, two in prev inventory; no inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id2, id3},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
abandonedObjs: object.ObjMetadataSet{id2, id3},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
"same obj skipped delete and abandoned, one in prev inventory; no inventory": {
|
||||
prevInventory: object.ObjMetadataSet{id3},
|
||||
appliedObjs: object.ObjMetadataSet{},
|
||||
failedApplies: object.ObjMetadataSet{},
|
||||
failedDeletes: object.ObjMetadataSet{},
|
||||
skippedDeletes: object.ObjMetadataSet{id3},
|
||||
abandonedObjs: object.ObjMetadataSet{id3},
|
||||
expectedObjs: object.ObjMetadataSet{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
|
@ -109,24 +199,29 @@ func TestInvSetTask(t *testing.T) {
|
|||
resourceCache := cache.NewResourceCacheMap()
|
||||
context := taskrunner.NewTaskContext(eventChannel, resourceCache)
|
||||
|
||||
prevInventory := make(map[object.ObjMetadata]bool, len(tc.prevInventory))
|
||||
for _, prevInvID := range tc.prevInventory {
|
||||
prevInventory[prevInvID] = true
|
||||
}
|
||||
task := InvSetTask{
|
||||
TaskName: taskName,
|
||||
InvClient: client,
|
||||
InvInfo: nil,
|
||||
PrevInventory: prevInventory,
|
||||
PrevInventory: tc.prevInventory,
|
||||
}
|
||||
for _, applyObj := range tc.appliedObjs {
|
||||
context.ResourceApplied(applyObj, "unusued-uid", int64(0))
|
||||
context.AddSuccessfulApply(applyObj, "unusued-uid", int64(0))
|
||||
}
|
||||
for _, applyFailure := range tc.applyFailures {
|
||||
context.CaptureResourceFailure(applyFailure)
|
||||
for _, applyFailure := range tc.failedApplies {
|
||||
context.AddFailedApply(applyFailure)
|
||||
}
|
||||
for _, pruneObj := range tc.pruneFailures {
|
||||
context.CapturePruneFailure(pruneObj)
|
||||
for _, pruneObj := range tc.failedDeletes {
|
||||
context.AddFailedDelete(pruneObj)
|
||||
}
|
||||
for _, skippedApply := range tc.skippedApplies {
|
||||
context.AddSkippedApply(skippedApply)
|
||||
}
|
||||
for _, skippedDelete := range tc.skippedDeletes {
|
||||
context.AddSkippedDelete(skippedDelete)
|
||||
}
|
||||
for _, abandonedObj := range tc.abandonedObjs {
|
||||
context.AddAbandonedObject(abandonedObj)
|
||||
}
|
||||
if taskName != task.Name() {
|
||||
t.Errorf("expected task name (%s), got (%s)", taskName, task.Name())
|
||||
|
@ -137,9 +232,7 @@ func TestInvSetTask(t *testing.T) {
|
|||
t.Errorf("unexpected error running InvAddTask: %s", result.Err)
|
||||
}
|
||||
actual, _ := client.GetClusterObjs(nil, common.DryRunNone)
|
||||
if !tc.expectedObjs.Equal(actual) {
|
||||
t.Errorf("expected merged inventory (%s), got (%s)", tc.expectedObjs, actual)
|
||||
}
|
||||
testutil.AssertEqual(t, actual, tc.expectedObjs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ func TestCollector_ConditionMet(t *testing.T) {
|
|||
|
||||
if tc.appliedGen != nil {
|
||||
for id, gen := range tc.appliedGen {
|
||||
taskContext.ResourceApplied(id, types.UID("unused"), gen)
|
||||
taskContext.AddSuccessfulApply(id, types.UID("unused"), gen)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,31 +15,30 @@ import (
|
|||
// NewTaskContext returns a new TaskContext
|
||||
func NewTaskContext(eventChannel chan event.Event, resourceCache cache.ResourceCache) *TaskContext {
|
||||
return &TaskContext{
|
||||
taskChannel: make(chan TaskResult),
|
||||
eventChannel: eventChannel,
|
||||
resourceCache: resourceCache,
|
||||
appliedResources: make(map[object.ObjMetadata]applyInfo),
|
||||
failedResources: make(map[object.ObjMetadata]struct{}),
|
||||
pruneFailures: make(map[object.ObjMetadata]struct{}),
|
||||
taskChannel: make(chan TaskResult),
|
||||
eventChannel: eventChannel,
|
||||
resourceCache: resourceCache,
|
||||
successfulApplies: make(map[object.ObjMetadata]applyInfo),
|
||||
failedApplies: make(map[object.ObjMetadata]struct{}),
|
||||
failedDeletes: make(map[object.ObjMetadata]struct{}),
|
||||
skippedApplies: make(map[object.ObjMetadata]struct{}),
|
||||
skippedDeletes: make(map[object.ObjMetadata]struct{}),
|
||||
abandonedObjects: make(map[object.ObjMetadata]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// TaskContext defines a context that is passed between all
|
||||
// the tasks that is in a taskqueue.
|
||||
type TaskContext struct {
|
||||
taskChannel chan TaskResult
|
||||
|
||||
eventChannel chan event.Event
|
||||
|
||||
resourceCache cache.ResourceCache
|
||||
|
||||
appliedResources map[object.ObjMetadata]applyInfo
|
||||
|
||||
// failedResources records the IDs of resources that are failed during applying.
|
||||
failedResources map[object.ObjMetadata]struct{}
|
||||
|
||||
// pruneFailures records the IDs of resources that are failed during pruning.
|
||||
pruneFailures map[object.ObjMetadata]struct{}
|
||||
taskChannel chan TaskResult
|
||||
eventChannel chan event.Event
|
||||
resourceCache cache.ResourceCache
|
||||
successfulApplies map[object.ObjMetadata]applyInfo
|
||||
failedApplies map[object.ObjMetadata]struct{}
|
||||
failedDeletes map[object.ObjMetadata]struct{}
|
||||
skippedApplies map[object.ObjMetadata]struct{}
|
||||
skippedDeletes map[object.ObjMetadata]struct{}
|
||||
abandonedObjects map[object.ObjMetadata]struct{}
|
||||
}
|
||||
|
||||
func (tc *TaskContext) TaskChannel() chan TaskResult {
|
||||
|
@ -54,19 +53,35 @@ func (tc *TaskContext) ResourceCache() cache.ResourceCache {
|
|||
return tc.resourceCache
|
||||
}
|
||||
|
||||
// ResourceApplied updates the context with information about the
|
||||
// IsSuccessfulApply returns true if the object apply was successful
|
||||
func (tc *TaskContext) IsSuccessfulApply(id object.ObjMetadata) bool {
|
||||
_, found := tc.successfulApplies[id]
|
||||
return found
|
||||
}
|
||||
|
||||
// AddSuccessfulApply updates the context with information about the
|
||||
// resource identified by the provided id. Currently, we keep information
|
||||
// about the generation of the resource after the apply operation completed.
|
||||
func (tc *TaskContext) ResourceApplied(id object.ObjMetadata, uid types.UID, gen int64) {
|
||||
tc.appliedResources[id] = applyInfo{
|
||||
func (tc *TaskContext) AddSuccessfulApply(id object.ObjMetadata, uid types.UID, gen int64) {
|
||||
tc.successfulApplies[id] = applyInfo{
|
||||
generation: gen,
|
||||
uid: uid,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceUID looks up the UID of the given resource
|
||||
func (tc *TaskContext) ResourceUID(id object.ObjMetadata) (types.UID, bool) {
|
||||
ai, found := tc.appliedResources[id]
|
||||
// SuccessfulApplies returns all the objects (as ObjMetadata) that
|
||||
// were added as applied resources to the TaskContext.
|
||||
func (tc *TaskContext) SuccessfulApplies() object.ObjMetadataSet {
|
||||
all := make(object.ObjMetadataSet, 0, len(tc.successfulApplies))
|
||||
for r := range tc.successfulApplies {
|
||||
all = append(all, r)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
// AppliedResourceUID looks up the UID of the given resource
|
||||
func (tc *TaskContext) AppliedResourceUID(id object.ObjMetadata) (types.UID, bool) {
|
||||
ai, found := tc.successfulApplies[id]
|
||||
if klog.V(4).Enabled() {
|
||||
if found {
|
||||
klog.Infof("resource applied UID cache hit (%s): %d", id, ai.uid)
|
||||
|
@ -80,21 +95,11 @@ func (tc *TaskContext) ResourceUID(id object.ObjMetadata) (types.UID, bool) {
|
|||
return ai.uid, true
|
||||
}
|
||||
|
||||
// AppliedResources returns all the objects (as ObjMetadata) that
|
||||
// were added as applied resources to the TaskContext.
|
||||
func (tc *TaskContext) AppliedResources() object.ObjMetadataSet {
|
||||
all := make(object.ObjMetadataSet, 0, len(tc.appliedResources))
|
||||
for r := range tc.appliedResources {
|
||||
all = append(all, r)
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
// AppliedResourceUIDs returns a set with the UIDs of all the
|
||||
// successfully applied resources.
|
||||
func (tc *TaskContext) AppliedResourceUIDs() sets.String {
|
||||
uids := sets.NewString()
|
||||
for _, ai := range tc.appliedResources {
|
||||
for _, ai := range tc.successfulApplies {
|
||||
uid := string(ai.uid)
|
||||
if uid != "" {
|
||||
uids.Insert(uid)
|
||||
|
@ -106,7 +111,7 @@ func (tc *TaskContext) AppliedResourceUIDs() sets.String {
|
|||
// AppliedGeneration looks up the generation of the given resource
|
||||
// after it was applied.
|
||||
func (tc *TaskContext) AppliedGeneration(id object.ObjMetadata) (int64, bool) {
|
||||
ai, found := tc.appliedResources[id]
|
||||
ai, found := tc.successfulApplies[id]
|
||||
if klog.V(4).Enabled() {
|
||||
if found {
|
||||
klog.Infof("resource applied generation cache hit (%s): %d", id, ai.generation)
|
||||
|
@ -120,42 +125,86 @@ func (tc *TaskContext) AppliedGeneration(id object.ObjMetadata) (int64, bool) {
|
|||
return ai.generation, true
|
||||
}
|
||||
|
||||
func (tc *TaskContext) ResourceFailed(id object.ObjMetadata) bool {
|
||||
_, found := tc.failedResources[id]
|
||||
// IsFailedApply returns true if the object failed to apply
|
||||
func (tc *TaskContext) IsFailedApply(id object.ObjMetadata) bool {
|
||||
_, found := tc.failedApplies[id]
|
||||
return found
|
||||
}
|
||||
|
||||
func (tc *TaskContext) CaptureResourceFailure(id object.ObjMetadata) {
|
||||
tc.failedResources[id] = struct{}{}
|
||||
// AddFailedApply registers that the object failed to apply
|
||||
func (tc *TaskContext) AddFailedApply(id object.ObjMetadata) {
|
||||
tc.failedApplies[id] = struct{}{}
|
||||
}
|
||||
|
||||
func (tc *TaskContext) ResourceFailures() object.ObjMetadataSet {
|
||||
failures := make(object.ObjMetadataSet, 0, len(tc.failedResources))
|
||||
for f := range tc.failedResources {
|
||||
failures = append(failures, f)
|
||||
}
|
||||
return failures
|
||||
// FailedApplies returns all the objects that failed to apply
|
||||
func (tc *TaskContext) FailedApplies() object.ObjMetadataSet {
|
||||
return object.ObjMetadataSetFromMap(tc.failedApplies)
|
||||
}
|
||||
|
||||
func (tc *TaskContext) CapturePruneFailure(id object.ObjMetadata) {
|
||||
tc.pruneFailures[id] = struct{}{}
|
||||
}
|
||||
|
||||
func (tc *TaskContext) PruneFailures() object.ObjMetadataSet {
|
||||
failures := make(object.ObjMetadataSet, 0, len(tc.pruneFailures))
|
||||
for f := range tc.pruneFailures {
|
||||
failures = append(failures, f)
|
||||
}
|
||||
return failures
|
||||
}
|
||||
|
||||
// PruneFailed returns true if the passed object identifier
|
||||
// has been stored as a prune failure; false otherwise.
|
||||
func (tc *TaskContext) PruneFailed(id object.ObjMetadata) bool {
|
||||
_, found := tc.pruneFailures[id]
|
||||
// IsFailedDelete returns true if the object failed to delete
|
||||
func (tc *TaskContext) IsFailedDelete(id object.ObjMetadata) bool {
|
||||
_, found := tc.failedDeletes[id]
|
||||
return found
|
||||
}
|
||||
|
||||
// AddFailedDelete registers that the object failed to delete
|
||||
func (tc *TaskContext) AddFailedDelete(id object.ObjMetadata) {
|
||||
tc.failedDeletes[id] = struct{}{}
|
||||
}
|
||||
|
||||
// FailedDeletes returns all the objects that failed to delete
|
||||
func (tc *TaskContext) FailedDeletes() object.ObjMetadataSet {
|
||||
return object.ObjMetadataSetFromMap(tc.failedDeletes)
|
||||
}
|
||||
|
||||
// IsSkippedApply returns true if the object apply was skipped
|
||||
func (tc *TaskContext) IsSkippedApply(id object.ObjMetadata) bool {
|
||||
_, found := tc.skippedApplies[id]
|
||||
return found
|
||||
}
|
||||
|
||||
// AddSkippedApply registers that the object apply was skipped
|
||||
func (tc *TaskContext) AddSkippedApply(id object.ObjMetadata) {
|
||||
tc.skippedApplies[id] = struct{}{}
|
||||
}
|
||||
|
||||
// SkippedApplies returns all the objects where apply was skipped
|
||||
func (tc *TaskContext) SkippedApplies() object.ObjMetadataSet {
|
||||
return object.ObjMetadataSetFromMap(tc.skippedApplies)
|
||||
}
|
||||
|
||||
// IsSkippedDelete returns true if the object delete was skipped
|
||||
func (tc *TaskContext) IsSkippedDelete(id object.ObjMetadata) bool {
|
||||
_, found := tc.skippedDeletes[id]
|
||||
return found
|
||||
}
|
||||
|
||||
// AddSkippedDelete registers that the object delete was skipped
|
||||
func (tc *TaskContext) AddSkippedDelete(id object.ObjMetadata) {
|
||||
tc.skippedDeletes[id] = struct{}{}
|
||||
}
|
||||
|
||||
// SkippedDeletes returns all the objects where deletion was skipped
|
||||
func (tc *TaskContext) SkippedDeletes() object.ObjMetadataSet {
|
||||
return object.ObjMetadataSetFromMap(tc.skippedDeletes)
|
||||
}
|
||||
|
||||
// IsAbandonedObject returns true if the object is abandoned
|
||||
func (tc *TaskContext) IsAbandonedObject(id object.ObjMetadata) bool {
|
||||
_, found := tc.abandonedObjects[id]
|
||||
return found
|
||||
}
|
||||
|
||||
// AddAbandonedObject registers that the object is abandoned
|
||||
func (tc *TaskContext) AddAbandonedObject(id object.ObjMetadata) {
|
||||
tc.abandonedObjects[id] = struct{}{}
|
||||
}
|
||||
|
||||
// AbandonedObjects returns all the abandoned objects
|
||||
func (tc *TaskContext) AbandonedObjects() object.ObjMetadataSet {
|
||||
return object.ObjMetadataSetFromMap(tc.abandonedObjects)
|
||||
}
|
||||
|
||||
// applyInfo captures information about resources that have been
|
||||
// applied. This is captured in the TaskContext so other tasks
|
||||
// running later might use this information.
|
||||
|
|
|
@ -142,8 +142,12 @@ func (w *WaitTask) checkCondition(taskContext *TaskContext) bool {
|
|||
func (w *WaitTask) pending(taskContext *TaskContext) object.ObjMetadataSet {
|
||||
var ids object.ObjMetadataSet
|
||||
for _, id := range w.Ids {
|
||||
if (w.Condition == AllCurrent && taskContext.ResourceFailed(id)) ||
|
||||
(w.Condition == AllNotFound && taskContext.PruneFailed(id)) {
|
||||
if w.Condition == AllCurrent &&
|
||||
taskContext.IsFailedApply(id) || taskContext.IsSkippedApply(id) {
|
||||
continue
|
||||
}
|
||||
if w.Condition == AllNotFound &&
|
||||
taskContext.IsFailedDelete(id) || taskContext.IsSkippedDelete(id) {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
|
|
|
@ -20,6 +20,15 @@ func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
|
|||
return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB))
|
||||
}
|
||||
|
||||
// ObjMetadataSetFromMap constructs a set from a map
|
||||
func ObjMetadataSetFromMap(mapA map[ObjMetadata]struct{}) ObjMetadataSet {
|
||||
setA := make(ObjMetadataSet, 0, len(mapA))
|
||||
for f := range mapA {
|
||||
setA = append(setA, f)
|
||||
}
|
||||
return setA
|
||||
}
|
||||
|
||||
// Equal returns true if the two sets contain equivalent objects. Duplicates are
|
||||
// ignored.
|
||||
// This function satisfies the cmp.Equal interface from github.com/google/go-cmp
|
||||
|
@ -64,6 +73,28 @@ func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet {
|
|||
return setA
|
||||
}
|
||||
|
||||
// Intersection returns the set of unique objects in both set A and set B.
|
||||
func (setA ObjMetadataSet) Intersection(setB ObjMetadataSet) ObjMetadataSet {
|
||||
var maxlen int
|
||||
if len(setA) > len(setB) {
|
||||
maxlen = len(setA)
|
||||
} else {
|
||||
maxlen = len(setB)
|
||||
}
|
||||
mapI := make(map[ObjMetadata]struct{}, maxlen)
|
||||
mapB := setB.ToMap()
|
||||
for _, a := range setA {
|
||||
if _, ok := mapB[a]; ok {
|
||||
mapI[a] = struct{}{}
|
||||
}
|
||||
}
|
||||
intersection := make(ObjMetadataSet, 0, len(mapI))
|
||||
for o := range mapI {
|
||||
intersection = append(intersection, o)
|
||||
}
|
||||
return intersection
|
||||
}
|
||||
|
||||
// Union returns the set of unique objects from the merging of set A and set B.
|
||||
func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet {
|
||||
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
|
||||
|
@ -129,6 +160,15 @@ func calcHash(objs []string) (uint32, error) {
|
|||
return h.Sum32(), nil
|
||||
}
|
||||
|
||||
// ToMap returns the set as a map, with objMeta keys and empty struct values.
|
||||
func (setA ObjMetadataSet) ToMap() map[ObjMetadata]struct{} {
|
||||
m := make(map[ObjMetadata]struct{}, len(setA))
|
||||
for _, objMeta := range setA {
|
||||
m[objMeta] = struct{}{}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ToStringMap returns the set as a serializable map, with objMeta keys and
|
||||
// empty string values.
|
||||
func (setA ObjMetadataSet) ToStringMap() map[string]string {
|
||||
|
|
Loading…
Reference in New Issue