kubeadm/operator/controllers/runtimetask_controller_test.go

1139 lines
29 KiB
Go

/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"reflect"
"testing"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/runtime/log"
"k8s.io/kubeadm/operator/errors"
operatorv1 "k8s.io/kubeadm/operator/api/v1alpha1"
)
func TestRuntimeTaskReconciler_reconcilePhase(t *testing.T) {
tx := timePtr(metav1.Now())
type args struct {
task *operatorv1.RuntimeTask
}
type want struct {
task *operatorv1.RuntimeTask
}
tests := []struct {
name string
args args
want want
}{
{
name: "Reconcile pending state",
args: args{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Phase: string(operatorv1.RuntimeTaskPhasePending),
},
},
},
},
{
name: "Reconcile running state",
args: args{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
Phase: string(operatorv1.RuntimeTaskPhaseRunning),
},
},
},
},
{
name: "Reconcile paused state",
args: args{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
Paused: true,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
Paused: true,
Phase: string(operatorv1.RuntimeTaskPhasePaused),
},
},
},
},
{
name: "Reconcile succeeded state",
args: args{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
CompletionTime: tx,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
CompletionTime: tx,
Phase: string(operatorv1.RuntimeTaskPhaseSucceeded),
},
},
},
},
{
name: "Reconcile failed state",
args: args{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
ErrorMessage: stringPtr("error"),
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
StartTime: tx,
ErrorMessage: stringPtr("error"),
Phase: string(operatorv1.RuntimeTaskPhaseFailed),
},
},
},
},
{
name: "Reconcile deleted state",
args: args{
task: &operatorv1.RuntimeTask{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: tx,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: tx,
},
Status: operatorv1.RuntimeTaskStatus{
Phase: string(operatorv1.RuntimeTaskPhaseDeleted),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &RuntimeTaskReconciler{}
r.reconcilePhase(tt.args.task)
if !reflect.DeepEqual(tt.args.task, tt.want.task) {
t.Errorf("reconcilePhase() = %v, want %v", tt.args.task, tt.want.task)
}
})
}
}
func TestRuntimeTaskReconciler_reconcilePauseOverride(t *testing.T) {
type args struct {
operationPaused bool
task *operatorv1.RuntimeTask
}
type want struct {
task *operatorv1.RuntimeTask
events int
}
tests := []struct {
name string
args args
want want
}{
{
name: "Reconcile a Task not paused with an Operation not paused is NOP",
args: args{
operationPaused: false,
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: false,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: false,
},
},
events: 0,
},
},
{
name: "Reconcile a Task not paused with an Operation currently paused set pause",
args: args{
operationPaused: true,
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: false,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
events: 1,
},
},
{
name: "Reconcile a Task paused with an Operation currently paused is NOP",
args: args{
operationPaused: true,
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
events: 0,
},
},
{
name: "Reconcile a Task paused with an Operation currently not paused unset pause",
args: args{
operationPaused: false,
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: false,
},
},
events: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := record.NewFakeRecorder(1)
r := &RuntimeTaskReconciler{
Client: nil,
NodeName: "",
Operation: "",
recorder: rec,
Log: nil,
}
r.reconcilePauseOverride(tt.args.operationPaused, tt.args.task)
if !reflect.DeepEqual(tt.args.task, tt.want.task) {
t.Errorf("reconcilePauseOverride() = %v, want %v", tt.args.task, tt.want.task)
}
if tt.want.events != len(rec.Events) {
t.Errorf("reconcilePauseOverride() = %v events recorded, want %v events", tt.want.events, len(rec.Events))
}
})
}
}
func TestRuntimeTaskReconciler_reconcileRecovery(t *testing.T) {
type args struct {
executionMode operatorv1.OperationExecutionMode
task *operatorv1.RuntimeTask
}
type want struct {
ret bool
task *operatorv1.RuntimeTask
events int
}
tests := []struct {
name string
args args
want want
}{
{
name: "Reconcile a Task without errors is NOP",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
},
},
want: want{
ret: false,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
},
events: 0,
},
},
{
name: "Reconcile a Task without recovery strategy is NOP",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
},
},
},
want: want{
ret: false,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
},
},
events: 0,
},
},
{
name: "Reconcile a Task using RetryError strategy removes the error (and keep CurrentCommand)",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: string(operatorv1.RuntimeTaskRecoveryRetryingFailedCommandStrategy),
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
CurrentCommand: 1,
},
},
},
want: want{
ret: true,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: "", // recovery strategy back to empty
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: nil, // error removed
ErrorMessage: nil,
CurrentCommand: 1,
},
},
events: 1,
},
},
{
name: "Reconcile a Task using SkipError strategy removes the error and moves to the next command",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: string(operatorv1.RuntimeTaskRecoverySkippingFailedCommandStrategy),
Commands: []operatorv1.CommandDescriptor{
{},
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
CurrentCommand: 1, // same command
},
},
},
want: want{
ret: true,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: "", // recovery strategy back to empty
Commands: []operatorv1.CommandDescriptor{
{},
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: nil, // error removed
ErrorMessage: nil,
CurrentCommand: 2, // next command
CommandProgress: "2/2",
},
},
events: 1,
},
},
{
name: "Reconcile a Task using SkipError strategy removes the error and moves to the next command + set pause if Mode=Controlled",
args: args{
executionMode: operatorv1.OperationExecutionModeControlled,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: string(operatorv1.RuntimeTaskRecoverySkippingFailedCommandStrategy),
Commands: []operatorv1.CommandDescriptor{
{},
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
CurrentCommand: 1,
},
},
},
want: want{
ret: true,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: "", // recovery strategy back to empty
Commands: []operatorv1.CommandDescriptor{
{},
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: nil, // error removed
ErrorMessage: nil,
CurrentCommand: 2, // next command
CommandProgress: "2/2",
Paused: true, // paused
},
},
events: 1,
},
},
{
name: "Reconcile a Task using SkipError strategy removes the error and set completed if there are no more commands",
args: args{
executionMode: operatorv1.OperationExecutionModeControlled,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: string(operatorv1.RuntimeTaskRecoverySkippingFailedCommandStrategy),
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
ErrorReason: runtimeTaskStatusErrorPtr(errors.RuntimeTaskExecutionError),
ErrorMessage: stringPtr("error"),
CurrentCommand: 1,
},
},
},
want: want{
ret: true,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
RecoveryMode: "", // recovery strategy back to empty
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
CompletionTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it completes"
ErrorReason: nil, // error removed
ErrorMessage: nil,
CurrentCommand: 1, // next command
},
},
events: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := record.NewFakeRecorder(1)
r := &RuntimeTaskReconciler{
Client: nil,
NodeName: "",
Operation: "",
recorder: rec,
Log: log.Log,
}
if got := r.reconcileRecovery(tt.args.executionMode, tt.args.task, log.Log); got != tt.want.ret {
t.Errorf("reconcileRecovery() = %v, want %v", got, tt.want.ret)
}
fixupWantTask(tt.want.task, tt.args.task)
if !reflect.DeepEqual(tt.args.task, tt.want.task) {
t.Errorf("reconcileRecovery() = %v, want %v", tt.args.task, tt.want.task)
}
if tt.want.events != len(rec.Events) {
t.Errorf("reconcileRecovery() = %v events recorded, want %v events", tt.want.events, len(rec.Events))
}
})
}
}
func TestRuntimeTaskReconciler_reconcileNormal(t *testing.T) {
type args struct {
executionMode operatorv1.OperationExecutionMode
task *operatorv1.RuntimeTask
}
type want struct {
task *operatorv1.RuntimeTask
events int
}
tests := []struct {
name string
args args
want want
wantErr bool
}{
{
name: "Reconcile paused task is a NOP",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Status: operatorv1.RuntimeTaskStatus{
Paused: true,
},
},
events: 0,
},
wantErr: false,
},
{
name: "Reconcile new task sets start time",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it started"
CurrentCommand: 1,
CommandProgress: "1/1",
},
},
events: 0,
},
wantErr: false,
},
{
name: "Reconcile task already started run commands and move to the next command",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Now()),
CurrentCommand: 1,
CommandProgress: "1/2",
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it started"
CurrentCommand: 2,
CommandProgress: "2/2",
},
},
events: 1,
},
wantErr: false,
},
{
name: "Reconcile task already started run commands and move to the next command + pause if operation mode=controlled",
args: args{
executionMode: operatorv1.OperationExecutionModeControlled,
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Now()),
CurrentCommand: 1,
CommandProgress: "1/2",
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it started"
CurrentCommand: 2,
CommandProgress: "2/2",
Paused: true,
},
},
events: 1,
},
wantErr: false,
},
{
name: "Reconcile task already started run commands and completes if no more commands",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Now()),
CurrentCommand: 1,
CommandProgress: "1/1",
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Pass: &operatorv1.PassCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it started"
CurrentCommand: 1,
CommandProgress: "1/1",
CompletionTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it completed"
},
},
events: 1,
},
wantErr: false,
},
{
name: "Reconcile task handle command failures",
args: args{
executionMode: "",
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Fail: &operatorv1.FailCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Now()),
CurrentCommand: 1,
CommandProgress: "1/1",
},
},
},
want: want{
task: &operatorv1.RuntimeTask{
Spec: operatorv1.RuntimeTaskSpec{
Commands: []operatorv1.CommandDescriptor{
{
Fail: &operatorv1.FailCommandSpec{},
},
},
},
Status: operatorv1.RuntimeTaskStatus{
StartTime: timePtr(metav1.Time{}), //using zero as a marker for "whatever time it started"
CurrentCommand: 1,
CommandProgress: "1/1",
ErrorMessage: stringPtr("error"), //using error as a marker for "whatever error there is"
},
},
events: 1,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rec := record.NewFakeRecorder(1)
r := &RuntimeTaskReconciler{
Client: nil,
NodeName: "",
Operation: "",
recorder: rec,
Log: log.Log,
}
if err := r.reconcileNormal(tt.args.executionMode, tt.args.task, log.Log); (err != nil) != tt.wantErr {
t.Errorf("reconcileNormal() error = %v, wantErr %v", err, tt.wantErr)
}
fixupWantTask(tt.want.task, tt.args.task)
if !reflect.DeepEqual(tt.args.task, tt.want.task) {
t.Errorf("reconcileRecovery() = %v, want %v", tt.args.task, tt.want.task)
}
if tt.want.events != len(rec.Events) {
t.Errorf("reconcileRecovery() = %v events recorded, want %v events", tt.want.events, len(rec.Events))
}
})
}
}
func TestRuntimeTaskReconciler_Reconcile(t *testing.T) {
type fields struct {
NodeName string
Operation string
Objs []runtime.Object
}
type args struct {
req ctrl.Request
}
tests := []struct {
name string
fields fields
args args
want ctrl.Result
wantErr bool
}{
{
name: "Reconcile does nothing if task does not exist",
fields: fields{},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo"}},
},
want: ctrl.Result{},
wantErr: false,
},
{
name: "Reconcile does nothing if task doesn't target the node the controller is supervising",
fields: fields{
NodeName: "foo-node",
Objs: []runtime.Object{
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "bar-task",
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "bar-node",
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "bar-task"}},
},
want: ctrl.Result{},
wantErr: false,
},
{
name: "Reconcile does nothing if the task is already completed",
fields: fields{
NodeName: "foo-node",
Objs: []runtime.Object{
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-task",
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "foo-node",
},
Status: operatorv1.RuntimeTaskStatus{
CompletionTime: timePtr(metav1.Now()),
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo-task"}},
},
want: ctrl.Result{},
wantErr: false,
},
{
name: "Reconcile fails if failing to retrieve parent taskgroup",
fields: fields{
NodeName: "foo-node",
Objs: []runtime.Object{
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-task",
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "foo-node",
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo-task"}},
},
want: ctrl.Result{},
wantErr: true,
},
{
name: "Reconcile fails if failing to retrieve parent operation",
fields: fields{
NodeName: "foo-node",
Objs: []runtime.Object{
&operatorv1.RuntimeTaskGroup{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTaskGroup",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-taskgroup",
},
},
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-task",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: operatorv1.GroupVersion.String(),
Kind: "RuntimeTaskGroup",
Name: "foo-taskgroup",
},
},
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "foo-node",
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo-task"}},
},
want: ctrl.Result{},
wantErr: true,
},
{
name: "Reconcile does nothing if task doesn't belong to the operation the controller is supervising",
fields: fields{
NodeName: "foo-node",
Operation: "foo-operation",
Objs: []runtime.Object{
&operatorv1.Operation{
TypeMeta: metav1.TypeMeta{
Kind: "Operation",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "bar-operation",
},
},
&operatorv1.RuntimeTaskGroup{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTaskGroup",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-taskgroup",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: operatorv1.GroupVersion.String(),
Kind: "Operation",
Name: "bar-operation",
},
},
},
},
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-task",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: operatorv1.GroupVersion.String(),
Kind: "RuntimeTaskGroup",
Name: "foo-taskgroup",
},
},
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "foo-node",
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo-task"}},
},
want: ctrl.Result{},
wantErr: false,
},
{
name: "Reconcile pass",
fields: fields{
NodeName: "foo-node",
Operation: "foo-operation",
Objs: []runtime.Object{
&operatorv1.Operation{
TypeMeta: metav1.TypeMeta{
Kind: "Operation",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-operation",
},
},
&operatorv1.RuntimeTaskGroup{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTaskGroup",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-taskgroup",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: operatorv1.GroupVersion.String(),
Kind: "Operation",
Name: "foo-operation",
},
},
},
},
&operatorv1.RuntimeTask{
TypeMeta: metav1.TypeMeta{
Kind: "RuntimeTask",
APIVersion: operatorv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "foo-task",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: operatorv1.GroupVersion.String(),
Kind: "RuntimeTaskGroup",
Name: "foo-taskgroup",
},
},
},
Spec: operatorv1.RuntimeTaskSpec{
NodeName: "foo-node",
},
},
},
},
args: args{
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: "foo-task"}},
},
want: ctrl.Result{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &RuntimeTaskReconciler{
Client: fake.NewFakeClientWithScheme(setupScheme(), tt.fields.Objs...),
NodeName: tt.fields.NodeName,
Operation: tt.fields.Operation,
recorder: record.NewFakeRecorder(1),
Log: log.Log,
}
got, err := r.Reconcile(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Reconcile() got = %v, want %v", got, tt.want)
}
})
}
}
func fixupWantTask(want *operatorv1.RuntimeTask, got *operatorv1.RuntimeTask) {
// In case want.StartTime is a marker, replace it with the current CompletionTime
if want.CreationTimestamp.IsZero() {
want.CreationTimestamp = got.CreationTimestamp
}
// In case want.ErrorMessage is a marker, replace it with the current error
if want.Status.ErrorMessage != nil && *want.Status.ErrorMessage == "error" && got.Status.ErrorMessage != nil {
want.Status.ErrorMessage = got.Status.ErrorMessage
want.Status.ErrorReason = got.Status.ErrorReason
}
// In case want.StartTime is a marker, replace it with the current CompletionTime
if want.Status.StartTime != nil && want.Status.StartTime.IsZero() && got.Status.StartTime != nil {
want.Status.StartTime = got.Status.StartTime
}
// In case want.CompletionTime is a marker, replace it with the current CompletionTime
if want.Status.CompletionTime != nil && want.Status.CompletionTime.IsZero() && got.Status.CompletionTime != nil {
want.Status.CompletionTime = got.Status.CompletionTime
}
}
func runtimeTaskStatusErrorPtr(s errors.RuntimeTaskStatusError) *errors.RuntimeTaskStatusError {
return &s
}
func setupScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
if err := operatorv1.AddToScheme(scheme); err != nil {
panic(err)
}
if err := corev1.AddToScheme(scheme); err != nil {
panic(err)
}
if err := appsv1.AddToScheme(scheme); err != nil {
panic(err)
}
return scheme
}