1378 lines
40 KiB
Go
1378 lines
40 KiB
Go
/*
|
|
Copyright 2020 the Velero contributors.
|
|
|
|
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 hook
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
corev1api "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
|
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
|
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
|
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
|
)
|
|
|
|
type mockItemHookHandler struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (h *mockItemHookHandler) HandleHooks(log logrus.FieldLogger, groupResource schema.GroupResource, obj runtime.Unstructured, resourceHooks []ResourceHook, phase hookPhase) error {
|
|
args := h.Called(log, groupResource, obj, resourceHooks, phase)
|
|
return args.Error(0)
|
|
}
|
|
|
|
func TestHandleHooksSkips(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
groupResource string
|
|
item runtime.Unstructured
|
|
hooks []ResourceHook
|
|
}{
|
|
{
|
|
name: "not a pod",
|
|
groupResource: "widget.group",
|
|
},
|
|
{
|
|
name: "pod without annotation / no spec hooks",
|
|
item: velerotest.UnstructuredOrDie(
|
|
`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "foo"
|
|
}
|
|
}
|
|
`,
|
|
),
|
|
},
|
|
{
|
|
name: "spec hooks not applicable",
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(
|
|
`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "foo",
|
|
"labels": {
|
|
"color": "blue"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
),
|
|
hooks: []ResourceHook{
|
|
{
|
|
Name: "ns exclude",
|
|
Selector: ResourceHookSelector{Namespaces: collections.NewIncludesExcludes().Excludes("ns")},
|
|
},
|
|
{
|
|
Name: "resource exclude",
|
|
Selector: ResourceHookSelector{Resources: collections.NewIncludesExcludes().Includes("widgets.group")},
|
|
},
|
|
{
|
|
Name: "label selector mismatch",
|
|
Selector: ResourceHookSelector{LabelSelector: parseLabelSelectorOrDie("color=green")},
|
|
},
|
|
{
|
|
Name: "missing exec hook",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
|
defer podCommandExecutor.AssertExpectations(t)
|
|
|
|
h := &DefaultItemHookHandler{
|
|
PodCommandExecutor: podCommandExecutor,
|
|
}
|
|
|
|
groupResource := schema.ParseGroupResource(test.groupResource)
|
|
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, PhasePre)
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleHooks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
phase hookPhase
|
|
groupResource string
|
|
item runtime.Unstructured
|
|
hooks []ResourceHook
|
|
hookErrorsByContainer map[string]error
|
|
expectedError error
|
|
expectedPodHook *velerov1api.ExecHook
|
|
expectedPodHookError error
|
|
}{
|
|
{
|
|
name: "pod, no annotation, spec (multiple pre hooks) = run spec",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []ResourceHook{
|
|
{
|
|
Name: "hook1",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"pre-1a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"pre-1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "hook2",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "2a",
|
|
Command: []string{"2a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "2b",
|
|
Command: []string{"2b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, no annotation, spec (multiple post hooks) = run spec",
|
|
phase: PhasePost,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []ResourceHook{
|
|
{
|
|
Name: "hook1",
|
|
Post: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"pre-1a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"pre-1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "hook2",
|
|
Post: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "2a",
|
|
Command: []string{"2a"},
|
|
},
|
|
},
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "2b",
|
|
Command: []string{"2b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (legacy), no spec = run annotation",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (pre), no spec = run annotation",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"pre.hook.backup.velero.io/container": "c",
|
|
"pre.hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation (post), no spec = run annotation",
|
|
phase: PhasePost,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"post.hook.backup.velero.io/container": "c",
|
|
"post.hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation & spec = run annotation",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
},
|
|
hooks: []ResourceHook{
|
|
{
|
|
Name: "hook1",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"1a"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "pod, annotation, onError=fail = return error",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls",
|
|
"hook.backup.velero.io/on-error": "Fail"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
OnError: velerov1api.HookErrorModeFail,
|
|
},
|
|
expectedPodHookError: errors.New("pod hook error"),
|
|
expectedError: errors.New("pod hook error"),
|
|
},
|
|
{
|
|
name: "pod, annotation, onError=continue = return nil",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name",
|
|
"annotations": {
|
|
"hook.backup.velero.io/container": "c",
|
|
"hook.backup.velero.io/command": "/bin/ls",
|
|
"hook.backup.velero.io/on-error": "Continue"
|
|
}
|
|
}
|
|
}`),
|
|
expectedPodHook: &velerov1api.ExecHook{
|
|
Container: "c",
|
|
Command: []string{"/bin/ls"},
|
|
OnError: velerov1api.HookErrorModeContinue,
|
|
},
|
|
expectedPodHookError: errors.New("pod hook error"),
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "pod, spec, onError=fail = don't run other hooks",
|
|
phase: PhasePre,
|
|
groupResource: "pods",
|
|
item: velerotest.UnstructuredOrDie(`
|
|
{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": {
|
|
"namespace": "ns",
|
|
"name": "name"
|
|
}
|
|
}`),
|
|
hooks: []ResourceHook{
|
|
{
|
|
Name: "hook1",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1a",
|
|
Command: []string{"1a"},
|
|
OnError: velerov1api.HookErrorModeContinue,
|
|
},
|
|
},
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "1b",
|
|
Command: []string{"1b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "hook2",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "2",
|
|
Command: []string{"2"},
|
|
OnError: velerov1api.HookErrorModeFail,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "hook3",
|
|
Pre: []velerov1api.BackupResourceHook{
|
|
{
|
|
Exec: &velerov1api.ExecHook{
|
|
Container: "3",
|
|
Command: []string{"3"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
hookErrorsByContainer: map[string]error{
|
|
"1a": errors.New("1a error, but continue"),
|
|
"2": errors.New("2 error, fail"),
|
|
},
|
|
expectedError: errors.New("2 error, fail"),
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
|
defer podCommandExecutor.AssertExpectations(t)
|
|
|
|
h := &DefaultItemHookHandler{
|
|
PodCommandExecutor: podCommandExecutor,
|
|
}
|
|
|
|
if test.expectedPodHook != nil {
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", "<from-annotation>", test.expectedPodHook).Return(test.expectedPodHookError)
|
|
} else {
|
|
hookLoop:
|
|
for _, resourceHook := range test.hooks {
|
|
for _, hook := range resourceHook.Pre {
|
|
hookError := test.hookErrorsByContainer[hook.Exec.Container]
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", resourceHook.Name, hook.Exec).Return(hookError)
|
|
if hookError != nil && hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
|
break hookLoop
|
|
}
|
|
}
|
|
for _, hook := range resourceHook.Post {
|
|
hookError := test.hookErrorsByContainer[hook.Exec.Container]
|
|
podCommandExecutor.On("ExecutePodCommand", mock.Anything, test.item.UnstructuredContent(), "ns", "name", resourceHook.Name, hook.Exec).Return(hookError)
|
|
if hookError != nil && hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
|
break hookLoop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
groupResource := schema.ParseGroupResource(test.groupResource)
|
|
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, test.phase)
|
|
|
|
if test.expectedError != nil {
|
|
assert.EqualError(t, err, test.expectedError.Error())
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPodExecHookFromAnnotations(t *testing.T) {
|
|
phases := []hookPhase{"", PhasePre, PhasePost}
|
|
for _, phase := range phases {
|
|
tests := []struct {
|
|
name string
|
|
annotations map[string]string
|
|
expectedHook *velerov1api.ExecHook
|
|
}{
|
|
{
|
|
name: "missing command annotation",
|
|
expectedHook: nil,
|
|
},
|
|
{
|
|
name: "malformed command json array",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "[blarg",
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"[blarg"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid command json array",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): `["a","b","c"]`,
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"a", "b", "c"},
|
|
},
|
|
},
|
|
{
|
|
name: "command as a string",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "hook mode set to continue",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookOnErrorAnnotationKey): string(velerov1api.HookErrorModeContinue),
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
OnError: velerov1api.HookErrorModeContinue,
|
|
},
|
|
},
|
|
{
|
|
name: "hook mode set to fail",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookOnErrorAnnotationKey): string(velerov1api.HookErrorModeFail),
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
OnError: velerov1api.HookErrorModeFail,
|
|
},
|
|
},
|
|
{
|
|
name: "use the specified timeout",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookTimeoutAnnotationKey): "5m3s",
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
Timeout: metav1.Duration{Duration: 5*time.Minute + 3*time.Second},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid timeout is logged",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
phasedKey(phase, podBackupHookTimeoutAnnotationKey): "invalid",
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
{
|
|
name: "use the specified container",
|
|
annotations: map[string]string{
|
|
phasedKey(phase, podBackupHookContainerAnnotationKey): "some-container",
|
|
phasedKey(phase, podBackupHookCommandAnnotationKey): "/usr/bin/foo",
|
|
},
|
|
expectedHook: &velerov1api.ExecHook{
|
|
Container: "some-container",
|
|
Command: []string{"/usr/bin/foo"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%s (phase=%q)", test.name, phase), func(t *testing.T) {
|
|
l := velerotest.NewLogger()
|
|
hook := getPodExecHookFromAnnotations(test.annotations, phase, l)
|
|
assert.Equal(t, test.expectedHook, hook)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceHookApplicableTo(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
includedNamespaces []string
|
|
excludedNamespaces []string
|
|
includedResources []string
|
|
excludedResources []string
|
|
labelSelector string
|
|
namespace string
|
|
resource schema.GroupResource
|
|
labels labels.Set
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "allow anything",
|
|
namespace: "foo",
|
|
resource: schema.GroupResource{Group: "foo", Resource: "bar"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "namespace in included list",
|
|
includedNamespaces: []string{"a", "b"},
|
|
excludedNamespaces: []string{"c", "d"},
|
|
namespace: "b",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "namespace not in included list",
|
|
includedNamespaces: []string{"a", "b"},
|
|
namespace: "c",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "namespace excluded",
|
|
excludedNamespaces: []string{"a", "b"},
|
|
namespace: "a",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource in included list",
|
|
includedResources: []string{"foo.a", "bar.b"},
|
|
excludedResources: []string{"baz.c"},
|
|
resource: schema.GroupResource{Group: "a", Resource: "foo"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "resource not in included list",
|
|
includedResources: []string{"foo.a", "bar.b"},
|
|
resource: schema.GroupResource{Group: "c", Resource: "baz"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "resource excluded",
|
|
excludedResources: []string{"foo.a", "bar.b"},
|
|
resource: schema.GroupResource{Group: "b", Resource: "bar"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "label selector matches",
|
|
labelSelector: "a=b",
|
|
labels: labels.Set{"a": "b"},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "label selector doesn't match",
|
|
labelSelector: "a=b",
|
|
labels: labels.Set{"a": "c"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
h := ResourceHook{
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes(test.includedNamespaces...).Excludes(test.excludedNamespaces...),
|
|
Resources: collections.NewIncludesExcludes().Includes(test.includedResources...).Excludes(test.excludedResources...),
|
|
},
|
|
}
|
|
if test.labelSelector != "" {
|
|
selector, err := labels.Parse(test.labelSelector)
|
|
require.NoError(t, err)
|
|
h.Selector.LabelSelector = selector
|
|
}
|
|
|
|
result := h.Selector.applicableTo(test.resource, test.namespace, test.labels)
|
|
assert.Equal(t, test.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func parseLabelSelectorOrDie(s string) labels.Selector {
|
|
ret, err := labels.Parse(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func TestGetInitRestoreHookFromAnnotations(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
inputAnnotations map[string]string
|
|
expected velerov1api.InitRestoreHook
|
|
expectNil bool
|
|
}{
|
|
{
|
|
name: "should return nil when container image is empty",
|
|
expectNil: true,
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "/usr/bin/data-populator",
|
|
},
|
|
},
|
|
{
|
|
name: "should return nil when container image is missing",
|
|
expectNil: true,
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "/usr/bin/data-populator",
|
|
},
|
|
},
|
|
{
|
|
name: "should generate container name when container name is empty",
|
|
expectNil: false,
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "busy-box",
|
|
podRestoreHookInitContainerNameAnnotationKey: "",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "/usr/bin/data-populator /user-data full",
|
|
},
|
|
expected: velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"/usr/bin/data-populator /user-data full"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should generate container name when container name is missing",
|
|
expectNil: false,
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "busy-box",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "/usr/bin/data-populator /user-data full",
|
|
},
|
|
expected: velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"/usr/bin/data-populator /user-data full"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should return expected init container when all annotations are specified",
|
|
expectNil: false,
|
|
expected: velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"/usr/bin/data-populator /user-data full"}).Result(),
|
|
},
|
|
},
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "busy-box",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "/usr/bin/data-populator /user-data full",
|
|
},
|
|
},
|
|
{
|
|
name: "should return expected init container when all annotations are specified with command as a JSON array",
|
|
expectNil: false,
|
|
expected: velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "busy-box",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a","b","c"]`,
|
|
},
|
|
},
|
|
{
|
|
name: "should return expected init container when all annotations are specified with command as malformed a JSON array",
|
|
expectNil: false,
|
|
expected: velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"[foobarbaz"}).Result(),
|
|
},
|
|
},
|
|
inputAnnotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "busy-box",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init",
|
|
podRestoreHookInitContainerCommandAnnotationKey: "[foobarbaz",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual := getInitRestoreHookFromAnnotation("test/pod1", tc.inputAnnotations, velerotest.NewLogger())
|
|
if tc.expectNil {
|
|
assert.Nil(t, actual)
|
|
return
|
|
}
|
|
assert.NotEmpty(t, actual.InitContainers[0].Name)
|
|
assert.Equal(t, len(tc.expected.InitContainers), len(actual.InitContainers))
|
|
assert.Equal(t, tc.expected.InitContainers[0].Image, actual.InitContainers[0].Image)
|
|
assert.Equal(t, tc.expected.InitContainers[0].Command, actual.InitContainers[0].Command)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRestoreHooksFromSpec(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
hookSpec *velerov1api.RestoreHooks
|
|
expected []ResourceRestoreHook
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "should return empty hooks and no error when hookSpec is nil",
|
|
hookSpec: nil,
|
|
expected: []ResourceRestoreHook{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "should return empty hooks and no error when hookSpec resources is nil",
|
|
hookSpec: &velerov1api.RestoreHooks{
|
|
Resources: nil,
|
|
},
|
|
expected: []ResourceRestoreHook{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "should return empty hooks and no error when hookSpec resources is empty",
|
|
hookSpec: &velerov1api.RestoreHooks{
|
|
Resources: []velerov1api.RestoreResourceHookSpec{},
|
|
},
|
|
expected: []ResourceRestoreHook{},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "should return hooks specified in the hookSpec initContainer hooks only",
|
|
hookSpec: &velerov1api.RestoreHooks{
|
|
Resources: []velerov1api.RestoreResourceHookSpec{
|
|
{
|
|
Name: "h1",
|
|
IncludedNamespaces: []string{"ns1", "ns2", "ns3"},
|
|
ExcludedNamespaces: []string{"ns4", "ns5", "ns6"},
|
|
IncludedResources: []string{kuberesource.Pods.Resource},
|
|
PostHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"foobarbaz"}).Result(),
|
|
*builder.ForContainer("restore-init2", "busy-box").
|
|
Command([]string{"foobarbaz"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: []ResourceRestoreHook{
|
|
{
|
|
Name: "h1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes([]string{"ns1", "ns2", "ns3"}...).Excludes([]string{"ns4", "ns5", "ns6"}...),
|
|
Resources: collections.NewIncludesExcludes().Includes([]string{kuberesource.Pods.Resource}...),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init1", "busy-box").
|
|
Command([]string{"foobarbaz"}).Result(),
|
|
*builder.ForContainer("restore-init2", "busy-box").
|
|
Command([]string{"foobarbaz"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual, err := GetRestoreHooksFromSpec(tc.hookSpec)
|
|
|
|
assert.Equal(t, tc.expected, actual)
|
|
assert.Equal(t, tc.expectedError, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleRestoreHooks(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
podInput corev1api.Pod
|
|
restoreHooks []ResourceRestoreHook
|
|
expectedPod *corev1api.Pod
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "should handle hook from annotation no hooks in spec on pod with no init containers",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should handle hook from annotation no hooks in spec on pod with init containers",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should handle hook from annotation ignoring hooks in spec",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
podRestoreHookInitContainerImageAnnotationKey: "nginx",
|
|
podRestoreHookInitContainerNameAnnotationKey: "restore-init-container",
|
|
podRestoreHookInitContainerCommandAnnotationKey: `["a", "b", "c"]`,
|
|
},
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "ignore-hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("should-not exist", "does-not-matter").
|
|
Command([]string{""}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should handle hook from spec on pod with no init containers",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should handle hook from spec when no restore hook annotation and existing init containers",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
*builder.ForContainer("init-app-step3", "busy-box").
|
|
Command([]string{"init-step3"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "shoud not apply any restore hook init containers when resource hook selector mismatch",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Excludes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "should preserve restic-wait init container when it is the only existing init container",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restic-wait", "bus-box").
|
|
Command([]string{"restic-restore"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restic-wait", "bus-box").
|
|
Command([]string{"restic-restore"}).Result(),
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "should preserve restic-wait init container when it exits with other init containers",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restic-wait", "bus-box").
|
|
Command([]string{"restic-restore"}).Result(),
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
Spec: corev1api.PodSpec{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restic-wait", "bus-box").
|
|
Command([]string{"restic-restore"}).Result(),
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("init-app-step1", "busy-box").
|
|
Command([]string{"init-step1"}).Result(),
|
|
*builder.ForContainer("init-app-step2", "busy-box").
|
|
Command([]string{"init-step2"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{
|
|
{
|
|
Name: "hook1",
|
|
Selector: ResourceHookSelector{
|
|
Namespaces: collections.NewIncludesExcludes().Includes("default"),
|
|
Resources: collections.NewIncludesExcludes().Includes(kuberesource.Pods.Resource),
|
|
},
|
|
RestoreHooks: []velerov1api.RestoreResourceHook{
|
|
{
|
|
Init: &velerov1api.InitRestoreHook{
|
|
InitContainers: []corev1api.Container{
|
|
*builder.ForContainer("restore-init-container-1", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
*builder.ForContainer("restore-init-container-2", "nginx").
|
|
Command([]string{"a", "b", "c"}).Result(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "shoud not apply any restore hook init containers when resource hook is nil",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
restoreHooks: nil,
|
|
},
|
|
{
|
|
name: "shoud not apply any restore hook init containers when resource hook is empty",
|
|
podInput: corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
expectedError: nil,
|
|
expectedPod: &corev1api.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "app1",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
restoreHooks: []ResourceRestoreHook{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
handler := InitContainerRestoreHookHandler{}
|
|
podMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.podInput)
|
|
assert.NoError(t, err)
|
|
actual, err := handler.HandleRestoreHooks(velerotest.NewLogger(), kuberesource.Pods, &unstructured.Unstructured{Object: podMap}, tc.restoreHooks)
|
|
assert.Equal(t, tc.expectedError, err)
|
|
actualPod := new(corev1api.Pod)
|
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(actual.UnstructuredContent(), actualPod)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedPod, actualPod)
|
|
})
|
|
}
|
|
}
|