mirror of https://github.com/fluxcd/cli-utils.git
545 lines
14 KiB
Go
545 lines
14 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package taskrunner
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"sigs.k8s.io/cli-utils/pkg/apply/cache"
|
|
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
|
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
|
"sigs.k8s.io/cli-utils/pkg/object"
|
|
"sigs.k8s.io/cli-utils/pkg/testutil"
|
|
)
|
|
|
|
var testDeployment1YAML = `
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: a
|
|
namespace: default
|
|
uid: dep-uid-a
|
|
generation: 1
|
|
spec:
|
|
replicas: 1
|
|
`
|
|
|
|
var testDeployment2YAML = `
|
|
apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: b
|
|
namespace: default
|
|
uid: dep-uid-b
|
|
generation: 1
|
|
spec:
|
|
replicas: 2
|
|
`
|
|
|
|
var testDeployment3YAML = `
|
|
apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: c
|
|
namespace: default
|
|
uid: dep-uid-c
|
|
generation: 1
|
|
spec:
|
|
replicas: 3
|
|
`
|
|
|
|
var testDeployment4YAML = `
|
|
apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: d
|
|
namespace: default
|
|
uid: dep-uid-d
|
|
generation: 1
|
|
spec:
|
|
replicas: 4
|
|
`
|
|
|
|
func TestWaitTask_CompleteEventually(t *testing.T) {
|
|
testDeployment1ID := testutil.ToIdentifier(t, testDeployment1YAML)
|
|
testDeployment1 := testutil.Unstructured(t, testDeployment1YAML)
|
|
testDeployment2ID := testutil.ToIdentifier(t, testDeployment2YAML)
|
|
testDeployment2 := testutil.Unstructured(t, testDeployment2YAML)
|
|
testDeployment3ID := testutil.ToIdentifier(t, testDeployment3YAML)
|
|
testDeployment4ID := testutil.ToIdentifier(t, testDeployment4YAML)
|
|
ids := object.ObjMetadataSet{
|
|
testDeployment1ID,
|
|
testDeployment2ID,
|
|
testDeployment3ID,
|
|
testDeployment4ID,
|
|
}
|
|
waitTimeout := 2 * time.Second
|
|
taskName := "wait-1"
|
|
task := NewWaitTask(taskName, ids, AllCurrent,
|
|
waitTimeout, testutil.NewFakeRESTMapper())
|
|
|
|
eventChannel := make(chan event.Event)
|
|
resourceCache := cache.NewResourceCacheMap()
|
|
taskContext := NewTaskContext(eventChannel, resourceCache)
|
|
defer close(eventChannel)
|
|
|
|
// mark deployment 1 & 2 as applied
|
|
taskContext.AddSuccessfulApply(testDeployment1ID, "unused", 1)
|
|
taskContext.AddSuccessfulApply(testDeployment2ID, "unused", 1)
|
|
|
|
// mark deployment 3 as failed
|
|
taskContext.AddFailedApply(testDeployment3ID)
|
|
|
|
// mark deployment 4 as skipped
|
|
taskContext.AddSkippedApply(testDeployment4ID)
|
|
|
|
// run task async, to let the test collect events
|
|
go func() {
|
|
// start the task
|
|
task.Start(taskContext)
|
|
|
|
// mark deployment1 as Current
|
|
resourceCache.Put(testDeployment1ID, cache.ResourceStatus{
|
|
Resource: testDeployment1,
|
|
Status: status.CurrentStatus,
|
|
})
|
|
// tell the WaitTask deployment1 has new status
|
|
task.StatusUpdate(taskContext, testDeployment1ID)
|
|
|
|
// mark deployment2 as InProgress
|
|
resourceCache.Put(testDeployment2ID, cache.ResourceStatus{
|
|
Resource: testDeployment2,
|
|
Status: status.InProgressStatus,
|
|
})
|
|
// tell the WaitTask deployment2 has new status
|
|
task.StatusUpdate(taskContext, testDeployment2ID)
|
|
|
|
// mark deployment2 as Current
|
|
resourceCache.Put(testDeployment2ID, cache.ResourceStatus{
|
|
Resource: testDeployment2,
|
|
Status: status.CurrentStatus,
|
|
})
|
|
// tell the WaitTask deployment2 has new status
|
|
task.StatusUpdate(taskContext, testDeployment2ID)
|
|
}()
|
|
|
|
// wait for task result
|
|
timer := time.NewTimer(5 * time.Second)
|
|
receivedEvents := []event.Event{}
|
|
loop:
|
|
for {
|
|
select {
|
|
case e := <-taskContext.EventChannel():
|
|
receivedEvents = append(receivedEvents, e)
|
|
case res := <-taskContext.TaskChannel():
|
|
timer.Stop()
|
|
assert.NoError(t, res.Err)
|
|
break loop
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for TaskResult")
|
|
}
|
|
}
|
|
|
|
// Expect an event for every object (sorted).
|
|
expectedEvents := []event.Event{
|
|
// skipped/reconciled/pending events first, in the order provided to the WaitTask
|
|
// deployment1 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment1ID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
// deployment2 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment2ID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
// deployment3 skipped
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment3ID,
|
|
Operation: event.ReconcileSkipped,
|
|
},
|
|
},
|
|
// deployment4 skipped
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment4ID,
|
|
Operation: event.ReconcileSkipped,
|
|
},
|
|
},
|
|
// current events next, in the order of status updates
|
|
// deployment1 current
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment1ID,
|
|
Operation: event.Reconciled,
|
|
},
|
|
},
|
|
// deployment2 current
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment2ID,
|
|
Operation: event.Reconciled,
|
|
},
|
|
},
|
|
}
|
|
testutil.AssertEqual(t, receivedEvents, expectedEvents)
|
|
}
|
|
|
|
func TestWaitTask_Timeout(t *testing.T) {
|
|
testDeployment1ID := testutil.ToIdentifier(t, testDeployment1YAML)
|
|
testDeployment1 := testutil.Unstructured(t, testDeployment1YAML)
|
|
testDeployment2ID := testutil.ToIdentifier(t, testDeployment2YAML)
|
|
testDeployment3ID := testutil.ToIdentifier(t, testDeployment3YAML)
|
|
testDeployment4ID := testutil.ToIdentifier(t, testDeployment4YAML)
|
|
ids := object.ObjMetadataSet{
|
|
testDeployment1ID,
|
|
testDeployment2ID,
|
|
testDeployment3ID,
|
|
testDeployment4ID,
|
|
}
|
|
waitTimeout := 2 * time.Second
|
|
taskName := "wait-2"
|
|
task := NewWaitTask(taskName, ids, AllCurrent,
|
|
waitTimeout, testutil.NewFakeRESTMapper())
|
|
|
|
eventChannel := make(chan event.Event)
|
|
resourceCache := cache.NewResourceCacheMap()
|
|
taskContext := NewTaskContext(eventChannel, resourceCache)
|
|
defer close(eventChannel)
|
|
|
|
// mark deployment 1 & 2 as applied
|
|
taskContext.AddSuccessfulApply(testDeployment1ID, "unused", 1)
|
|
taskContext.AddSuccessfulApply(testDeployment2ID, "unused", 1)
|
|
|
|
// mark deployment 3 as failed
|
|
taskContext.AddFailedApply(testDeployment3ID)
|
|
|
|
// mark deployment 4 as skipped
|
|
taskContext.AddSkippedApply(testDeployment4ID)
|
|
|
|
// run task async, to let the test collect events
|
|
go func() {
|
|
// start the task
|
|
task.Start(taskContext)
|
|
// mark deployment1 as Current
|
|
resourceCache.Put(testDeployment1ID, cache.ResourceStatus{
|
|
Resource: testDeployment1,
|
|
Status: status.CurrentStatus,
|
|
})
|
|
// tell the WaitTask deployment1 has new status
|
|
task.StatusUpdate(taskContext, testDeployment1ID)
|
|
}()
|
|
|
|
// wait for task result
|
|
timer := time.NewTimer(5 * time.Second)
|
|
receivedEvents := []event.Event{}
|
|
loop:
|
|
for {
|
|
select {
|
|
case e := <-taskContext.EventChannel():
|
|
receivedEvents = append(receivedEvents, e)
|
|
case res := <-taskContext.TaskChannel():
|
|
timer.Stop()
|
|
assert.NoError(t, res.Err)
|
|
break loop
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for TaskResult")
|
|
}
|
|
}
|
|
|
|
// Expect an event for every object (sorted).
|
|
expectedEvents := []event.Event{
|
|
// skipped/reconciled/pending events first, in the order provided to the WaitTask
|
|
// deployment1 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment1ID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
// deployment2 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment2ID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
// deployment3 skipped
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment3ID,
|
|
Operation: event.ReconcileSkipped,
|
|
},
|
|
},
|
|
// deployment4 skipped
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment4ID,
|
|
Operation: event.ReconcileSkipped,
|
|
},
|
|
},
|
|
// current events next, in the order of status updates
|
|
// deployment1 current
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment1ID,
|
|
Operation: event.Reconciled,
|
|
},
|
|
},
|
|
// timeout events last, in the order provided to the WaitTask
|
|
// deployment2 timeout
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeployment2ID,
|
|
Operation: event.ReconcileTimeout,
|
|
},
|
|
},
|
|
}
|
|
testutil.AssertEqual(t, receivedEvents, expectedEvents)
|
|
}
|
|
|
|
func TestWaitTask_StartAndComplete(t *testing.T) {
|
|
testDeploymentID := testutil.ToIdentifier(t, testDeployment1YAML)
|
|
testDeployment := testutil.Unstructured(t, testDeployment1YAML)
|
|
ids := object.ObjMetadataSet{
|
|
testDeploymentID,
|
|
}
|
|
waitTimeout := 2 * time.Second
|
|
taskName := "wait-3"
|
|
task := NewWaitTask(taskName, ids, AllCurrent,
|
|
waitTimeout, testutil.NewFakeRESTMapper())
|
|
|
|
eventChannel := make(chan event.Event)
|
|
resourceCache := cache.NewResourceCacheMap()
|
|
taskContext := NewTaskContext(eventChannel, resourceCache)
|
|
defer close(eventChannel)
|
|
|
|
// mark the deployment as applied
|
|
appliedGeneration := int64(1)
|
|
taskContext.AddSuccessfulApply(testDeploymentID, "unused", appliedGeneration)
|
|
|
|
// mark the deployment as Current before starting
|
|
resourceCache.Put(testDeploymentID, cache.ResourceStatus{
|
|
Resource: testDeployment,
|
|
Status: status.CurrentStatus,
|
|
})
|
|
|
|
// run task async, to let the test collect events
|
|
go func() {
|
|
// start the task
|
|
task.Start(taskContext)
|
|
}()
|
|
|
|
// wait for first task result
|
|
timer := time.NewTimer(5 * time.Second)
|
|
receivedEvents := []event.Event{}
|
|
loop:
|
|
for {
|
|
select {
|
|
case e := <-taskContext.EventChannel():
|
|
receivedEvents = append(receivedEvents, e)
|
|
case res := <-taskContext.TaskChannel():
|
|
timer.Stop()
|
|
assert.NoError(t, res.Err)
|
|
break loop
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for TaskResult")
|
|
}
|
|
}
|
|
|
|
expectedEvents := []event.Event{
|
|
// deployment1 current (no pending event when Current before start)
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeploymentID,
|
|
Operation: event.Reconciled,
|
|
},
|
|
},
|
|
}
|
|
testutil.AssertEqual(t, receivedEvents, expectedEvents)
|
|
}
|
|
|
|
func TestWaitTask_Cancel(t *testing.T) {
|
|
testDeploymentID := testutil.ToIdentifier(t, testDeployment1YAML)
|
|
ids := object.ObjMetadataSet{
|
|
testDeploymentID,
|
|
}
|
|
waitTimeout := 5 * time.Second
|
|
taskName := "wait-4"
|
|
task := NewWaitTask(taskName, ids, AllCurrent,
|
|
waitTimeout, testutil.NewFakeRESTMapper())
|
|
|
|
eventChannel := make(chan event.Event)
|
|
resourceCache := cache.NewResourceCacheMap()
|
|
taskContext := NewTaskContext(eventChannel, resourceCache)
|
|
defer close(eventChannel)
|
|
|
|
// run task async, to let the test collect events
|
|
go func() {
|
|
// start the task
|
|
task.Start(taskContext)
|
|
|
|
// wait a bit
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// cancel immediately (simulate context cancel from baseRunner)
|
|
task.Cancel(taskContext)
|
|
}()
|
|
|
|
// wait for first task result
|
|
timer := time.NewTimer(10 * time.Second)
|
|
receivedEvents := []event.Event{}
|
|
loop:
|
|
for {
|
|
select {
|
|
case e := <-taskContext.EventChannel():
|
|
receivedEvents = append(receivedEvents, e)
|
|
case res := <-taskContext.TaskChannel():
|
|
timer.Stop()
|
|
assert.NoError(t, res.Err)
|
|
break loop
|
|
case <-timer.C:
|
|
t.Fatalf("timed out waiting for TaskResult")
|
|
}
|
|
}
|
|
|
|
// no timeout events sent on cancel
|
|
expectedEvents := []event.Event{
|
|
// skipped/reconciled/pending events first, in the order provided to the WaitTask
|
|
// deployment1 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeploymentID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
}
|
|
testutil.AssertEqual(t, receivedEvents, expectedEvents)
|
|
}
|
|
|
|
func TestWaitTask_SingleTaskResult(t *testing.T) {
|
|
testDeploymentID := testutil.ToIdentifier(t, testDeployment1YAML)
|
|
testDeployment := testutil.Unstructured(t, testDeployment1YAML)
|
|
ids := object.ObjMetadataSet{
|
|
testDeploymentID,
|
|
}
|
|
waitTimeout := 3 * time.Second
|
|
taskName := "wait-5"
|
|
task := NewWaitTask(taskName, ids, AllCurrent,
|
|
waitTimeout, testutil.NewFakeRESTMapper())
|
|
|
|
// buffer events, because they're sent by StatusUpdate
|
|
eventChannel := make(chan event.Event, 10)
|
|
resourceCache := cache.NewResourceCacheMap()
|
|
taskContext := NewTaskContext(eventChannel, resourceCache)
|
|
taskContext.taskChannel = make(chan TaskResult, 10)
|
|
defer close(eventChannel)
|
|
|
|
// mark the deployment as applied
|
|
appliedGeneration := int64(1)
|
|
taskContext.AddSuccessfulApply(testDeploymentID, "unused", appliedGeneration)
|
|
|
|
// run task async, to let the test collect events
|
|
go func() {
|
|
// start the task
|
|
task.Start(taskContext)
|
|
|
|
// wait a bit
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// mark the deployment as Current
|
|
resourceCache.Put(testDeploymentID, cache.ResourceStatus{
|
|
Resource: withGeneration(testDeployment, appliedGeneration),
|
|
Status: status.CurrentStatus,
|
|
})
|
|
|
|
// send multiple status updates
|
|
for i := 0; i < 10; i++ {
|
|
task.StatusUpdate(taskContext, testDeploymentID)
|
|
}
|
|
}()
|
|
|
|
// wait for timeout
|
|
timer := time.NewTimer(5 * time.Second)
|
|
receivedEvents := []event.Event{}
|
|
receivedResults := []TaskResult{}
|
|
loop:
|
|
for {
|
|
select {
|
|
case e := <-taskContext.EventChannel():
|
|
receivedEvents = append(receivedEvents, e)
|
|
case res := <-taskContext.TaskChannel():
|
|
receivedResults = append(receivedResults, res)
|
|
case <-timer.C:
|
|
break loop
|
|
}
|
|
}
|
|
|
|
expectedEvents := []event.Event{
|
|
// skipped/reconciled/pending events first, in the order provided to the WaitTask
|
|
// deployment1 pending
|
|
{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeploymentID,
|
|
Operation: event.ReconcilePending,
|
|
},
|
|
},
|
|
}
|
|
// Expect an event for every call to StatusUpdate,
|
|
// because the object is already Current.
|
|
for i := 0; i < 10; i++ {
|
|
expectedEvents = append(expectedEvents, event.Event{
|
|
Type: event.WaitType,
|
|
WaitEvent: event.WaitEvent{
|
|
GroupName: taskName,
|
|
Identifier: testDeploymentID,
|
|
Operation: event.Reconciled,
|
|
},
|
|
})
|
|
}
|
|
assert.Equal(t, expectedEvents, receivedEvents)
|
|
|
|
expectedResults := []TaskResult{
|
|
{}, // Empty result means success
|
|
}
|
|
assert.Equal(t, expectedResults, receivedResults)
|
|
}
|