Merge pull request #7117 from allenxu404/issue6567
Add hook status to backup/restore CRpull/7153/head
commit
85482aefaf
|
@ -0,0 +1 @@
|
|||
Add hooks status to backup/restore CR
|
|
@ -544,6 +544,22 @@ spec:
|
|||
description: FormatVersion is the backup format version, including
|
||||
major, minor, and patch version.
|
||||
type: string
|
||||
hookStatus:
|
||||
description: HookStatus contains information about the status of the
|
||||
hooks.
|
||||
nullable: true
|
||||
properties:
|
||||
hooksAttempted:
|
||||
description: HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks
|
||||
that failed to execute and the number of hooks that executed
|
||||
successfully.
|
||||
type: integer
|
||||
hooksFailed:
|
||||
description: HooksFailed is the total number of hooks which ended
|
||||
with an error
|
||||
type: integer
|
||||
type: object
|
||||
phase:
|
||||
description: Phase is the current state of the Backup.
|
||||
enum:
|
||||
|
|
|
@ -440,6 +440,22 @@ spec:
|
|||
description: FailureReason is an error that caused the entire restore
|
||||
to fail.
|
||||
type: string
|
||||
hookStatus:
|
||||
description: HookStatus contains information about the status of the
|
||||
hooks.
|
||||
nullable: true
|
||||
properties:
|
||||
hooksAttempted:
|
||||
description: HooksAttempted is the total number of attempted hooks
|
||||
Specifically, HooksAttempted represents the number of hooks
|
||||
that failed to execute and the number of hooks that executed
|
||||
successfully.
|
||||
type: integer
|
||||
hooksFailed:
|
||||
description: HooksFailed is the total number of hooks which ended
|
||||
with an error
|
||||
type: integer
|
||||
type: object
|
||||
phase:
|
||||
description: Phase is the current state of the Restore
|
||||
enum:
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
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 "sync"
|
||||
|
||||
const (
|
||||
HookSourceAnnotation = "annotation"
|
||||
HookSourceSpec = "spec"
|
||||
)
|
||||
|
||||
// hookTrackerKey identifies a backup/restore hook
|
||||
type hookTrackerKey struct {
|
||||
// PodNamespace indicates the namespace of pod where hooks are executed.
|
||||
// For hooks specified in the backup/restore spec, this field is the namespace of an applicable pod.
|
||||
// For hooks specified in pod annotation, this field is the namespace of pod where hooks are annotated.
|
||||
podNamespace string
|
||||
// PodName indicates the pod where hooks are executed.
|
||||
// For hooks specified in the backup/restore spec, this field is an applicable pod name.
|
||||
// For hooks specified in pod annotation, this field is the pod where hooks are annotated.
|
||||
podName string
|
||||
// HookPhase is only for backup hooks, for restore hooks, this field is empty.
|
||||
hookPhase hookPhase
|
||||
// HookName is only for hooks specified in the backup/restore spec.
|
||||
// For hooks specified in pod annotation, this field is empty or "<from-annotation>".
|
||||
hookName string
|
||||
// HookSource indicates where hooks come from.
|
||||
hookSource string
|
||||
// Container indicates the container hooks use.
|
||||
// For hooks specified in the backup/restore spec, the container might be the same under different hookName.
|
||||
container string
|
||||
}
|
||||
|
||||
// hookTrackerVal records the execution status of a specific hook.
|
||||
// hookTrackerVal is extensible to accommodate additional fields as needs develop.
|
||||
type hookTrackerVal struct {
|
||||
// HookFailed indicates if hook failed to execute.
|
||||
hookFailed bool
|
||||
// hookExecuted indicates if hook already execute.
|
||||
hookExecuted bool
|
||||
}
|
||||
|
||||
// HookTracker tracks all hooks' execution status
|
||||
type HookTracker struct {
|
||||
lock *sync.RWMutex
|
||||
tracker map[hookTrackerKey]hookTrackerVal
|
||||
}
|
||||
|
||||
// NewHookTracker creates a hookTracker.
|
||||
func NewHookTracker() *HookTracker {
|
||||
return &HookTracker{
|
||||
lock: &sync.RWMutex{},
|
||||
tracker: make(map[hookTrackerKey]hookTrackerVal),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a hook to the tracker
|
||||
func (ht *HookTracker) Add(podNamespace, podName, container, source, hookName string, hookPhase hookPhase) {
|
||||
ht.lock.Lock()
|
||||
defer ht.lock.Unlock()
|
||||
|
||||
key := hookTrackerKey{
|
||||
podNamespace: podNamespace,
|
||||
podName: podName,
|
||||
hookSource: source,
|
||||
container: container,
|
||||
hookPhase: hookPhase,
|
||||
hookName: hookName,
|
||||
}
|
||||
|
||||
if _, ok := ht.tracker[key]; !ok {
|
||||
ht.tracker[key] = hookTrackerVal{
|
||||
hookFailed: false,
|
||||
hookExecuted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record records the hook's execution status
|
||||
func (ht *HookTracker) Record(podNamespace, podName, container, source, hookName string, hookPhase hookPhase, hookFailed bool) {
|
||||
ht.lock.Lock()
|
||||
defer ht.lock.Unlock()
|
||||
|
||||
key := hookTrackerKey{
|
||||
podNamespace: podNamespace,
|
||||
podName: podName,
|
||||
hookSource: source,
|
||||
container: container,
|
||||
hookPhase: hookPhase,
|
||||
hookName: hookName,
|
||||
}
|
||||
|
||||
if _, ok := ht.tracker[key]; ok {
|
||||
ht.tracker[key] = hookTrackerVal{
|
||||
hookFailed: hookFailed,
|
||||
hookExecuted: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stat calculates the number of attempted hooks and failed hooks
|
||||
func (ht *HookTracker) Stat() (hookAttemptedCnt int, hookFailed int) {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
for _, hookInfo := range ht.tracker {
|
||||
if hookInfo.hookExecuted {
|
||||
hookAttemptedCnt++
|
||||
if hookInfo.hookFailed {
|
||||
hookFailed++
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTracker gets the tracker inside HookTracker
|
||||
func (ht *HookTracker) GetTracker() map[hookTrackerKey]hookTrackerVal {
|
||||
ht.lock.RLock()
|
||||
defer ht.lock.RUnlock()
|
||||
|
||||
return ht.tracker
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewHookTracker(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
|
||||
assert.NotNil(t, tracker)
|
||||
assert.Empty(t, tracker.tracker)
|
||||
}
|
||||
|
||||
func TestHookTracker_Add(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
|
||||
key := hookTrackerKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: PhasePre,
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
|
||||
_, ok := tracker.tracker[key]
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestHookTracker_Record(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true)
|
||||
|
||||
key := hookTrackerKey{
|
||||
podNamespace: "ns1",
|
||||
podName: "pod1",
|
||||
container: "container1",
|
||||
hookPhase: PhasePre,
|
||||
hookSource: HookSourceAnnotation,
|
||||
hookName: "h1",
|
||||
}
|
||||
|
||||
info := tracker.tracker[key]
|
||||
assert.True(t, info.hookFailed)
|
||||
}
|
||||
|
||||
func TestHookTracker_Stat(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
tracker.Add("ns2", "pod2", "container1", HookSourceAnnotation, "h2", PhasePre)
|
||||
tracker.Record("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre, true)
|
||||
|
||||
attempted, failed := tracker.Stat()
|
||||
assert.Equal(t, 1, attempted)
|
||||
assert.Equal(t, 1, failed)
|
||||
}
|
||||
|
||||
func TestHookTracker_Get(t *testing.T) {
|
||||
tracker := NewHookTracker()
|
||||
tracker.Add("ns1", "pod1", "container1", HookSourceAnnotation, "h1", PhasePre)
|
||||
|
||||
tr := tracker.GetTracker()
|
||||
assert.NotNil(t, tr)
|
||||
|
||||
t.Logf("tracker :%+v", tr)
|
||||
}
|
|
@ -82,6 +82,7 @@ type ItemHookHandler interface {
|
|||
obj runtime.Unstructured,
|
||||
resourceHooks []ResourceHook,
|
||||
phase hookPhase,
|
||||
hookTracker *HookTracker,
|
||||
) error
|
||||
}
|
||||
|
||||
|
@ -200,6 +201,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
obj runtime.Unstructured,
|
||||
resourceHooks []ResourceHook,
|
||||
phase hookPhase,
|
||||
hookTracker *HookTracker,
|
||||
) error {
|
||||
// We only support hooks on pods right now
|
||||
if groupResource != kuberesource.Pods {
|
||||
|
@ -221,15 +223,21 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
hookFromAnnotations = getPodExecHookFromAnnotations(metadata.GetAnnotations(), "", log)
|
||||
}
|
||||
if hookFromAnnotations != nil {
|
||||
hookTracker.Add(namespace, name, hookFromAnnotations.Container, HookSourceAnnotation, "", phase)
|
||||
|
||||
hookLog := log.WithFields(
|
||||
logrus.Fields{
|
||||
"hookSource": "annotation",
|
||||
"hookSource": HookSourceAnnotation,
|
||||
"hookType": "exec",
|
||||
"hookPhase": phase,
|
||||
},
|
||||
)
|
||||
|
||||
hookTracker.Record(namespace, name, hookFromAnnotations.Container, HookSourceAnnotation, "", phase, false)
|
||||
if err := h.PodCommandExecutor.ExecutePodCommand(hookLog, obj.UnstructuredContent(), namespace, name, "<from-annotation>", hookFromAnnotations); err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
hookTracker.Record(namespace, name, hookFromAnnotations.Container, HookSourceAnnotation, "", phase, true)
|
||||
|
||||
if hookFromAnnotations.OnError == velerov1api.HookErrorModeFail {
|
||||
return err
|
||||
}
|
||||
|
@ -240,6 +248,8 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
|
||||
labels := labels.Set(metadata.GetLabels())
|
||||
// Otherwise, check for hooks defined in the backup spec.
|
||||
// modeFailError records the error from the hook with "Fail" error mode
|
||||
var modeFailError error
|
||||
for _, resourceHook := range resourceHooks {
|
||||
if !resourceHook.Selector.applicableTo(groupResource, namespace, labels) {
|
||||
continue
|
||||
|
@ -251,21 +261,30 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
} else {
|
||||
hooks = resourceHook.Post
|
||||
}
|
||||
|
||||
for _, hook := range hooks {
|
||||
if groupResource == kuberesource.Pods {
|
||||
if hook.Exec != nil {
|
||||
hookLog := log.WithFields(
|
||||
logrus.Fields{
|
||||
"hookSource": "backupSpec",
|
||||
"hookType": "exec",
|
||||
"hookPhase": phase,
|
||||
},
|
||||
)
|
||||
err := h.PodCommandExecutor.ExecutePodCommand(hookLog, obj.UnstructuredContent(), namespace, name, resourceHook.Name, hook.Exec)
|
||||
if err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
if hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
||||
return err
|
||||
hookTracker.Add(namespace, name, hook.Exec.Container, HookSourceSpec, resourceHook.Name, phase)
|
||||
// The remaining hooks will only be executed if modeFailError is nil.
|
||||
// Otherwise, execution will stop and only hook collection will occur.
|
||||
if modeFailError == nil {
|
||||
hookLog := log.WithFields(
|
||||
logrus.Fields{
|
||||
"hookSource": HookSourceSpec,
|
||||
"hookType": "exec",
|
||||
"hookPhase": phase,
|
||||
},
|
||||
)
|
||||
|
||||
hookTracker.Record(namespace, name, hook.Exec.Container, HookSourceSpec, resourceHook.Name, phase, false)
|
||||
err := h.PodCommandExecutor.ExecutePodCommand(hookLog, obj.UnstructuredContent(), namespace, name, resourceHook.Name, hook.Exec)
|
||||
if err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
hookTracker.Record(namespace, name, hook.Exec.Container, HookSourceSpec, resourceHook.Name, phase, true)
|
||||
if hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
||||
modeFailError = err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +292,7 @@ func (h *DefaultItemHookHandler) HandleHooks(
|
|||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return modeFailError
|
||||
}
|
||||
|
||||
// NoOpItemHookHandler is the an itemHookHandler for the Finalize controller where hooks don't run
|
||||
|
@ -285,6 +304,7 @@ func (h *NoOpItemHookHandler) HandleHooks(
|
|||
obj runtime.Unstructured,
|
||||
resourceHooks []ResourceHook,
|
||||
phase hookPhase,
|
||||
hookTracker *HookTracker,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -514,6 +534,7 @@ func GroupRestoreExecHooks(
|
|||
resourceRestoreHooks []ResourceRestoreHook,
|
||||
pod *corev1api.Pod,
|
||||
log logrus.FieldLogger,
|
||||
hookTrack *HookTracker,
|
||||
) (map[string][]PodExecRestoreHook, error) {
|
||||
byContainer := map[string][]PodExecRestoreHook{}
|
||||
|
||||
|
@ -530,10 +551,11 @@ func GroupRestoreExecHooks(
|
|||
if hookFromAnnotation.Container == "" {
|
||||
hookFromAnnotation.Container = pod.Spec.Containers[0].Name
|
||||
}
|
||||
hookTrack.Add(metadata.GetNamespace(), metadata.GetName(), hookFromAnnotation.Container, HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
byContainer[hookFromAnnotation.Container] = []PodExecRestoreHook{
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: *hookFromAnnotation,
|
||||
},
|
||||
}
|
||||
|
@ -554,7 +576,7 @@ func GroupRestoreExecHooks(
|
|||
named := PodExecRestoreHook{
|
||||
HookName: rrh.Name,
|
||||
Hook: *rh.Exec,
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
}
|
||||
// default to false if attr WaitForReady not set
|
||||
if named.Hook.WaitForReady == nil {
|
||||
|
@ -564,6 +586,7 @@ func GroupRestoreExecHooks(
|
|||
if named.Hook.Container == "" {
|
||||
named.Hook.Container = pod.Spec.Containers[0].Name
|
||||
}
|
||||
hookTrack.Add(metadata.GetNamespace(), metadata.GetName(), named.Hook.Container, HookSourceSpec, rrh.Name, hookPhase(""))
|
||||
byContainer[named.Hook.Container] = append(byContainer[named.Hook.Container], named)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,7 @@ func TestHandleHooksSkips(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
hookTracker := NewHookTracker()
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
||||
|
@ -118,7 +119,7 @@ func TestHandleHooksSkips(t *testing.T) {
|
|||
}
|
||||
|
||||
groupResource := schema.ParseGroupResource(test.groupResource)
|
||||
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, PhasePre)
|
||||
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, PhasePre, hookTracker)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@ -485,7 +486,8 @@ func TestHandleHooks(t *testing.T) {
|
|||
}
|
||||
|
||||
groupResource := schema.ParseGroupResource(test.groupResource)
|
||||
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, test.phase)
|
||||
hookTracker := NewHookTracker()
|
||||
err := h.HandleHooks(velerotest.NewLogger(), groupResource, test.item, test.hooks, test.phase, hookTracker)
|
||||
|
||||
if test.expectedError != nil {
|
||||
assert.EqualError(t, err, test.expectedError.Error())
|
||||
|
@ -861,7 +863,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -892,7 +894,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -933,7 +935,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "hook1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -973,7 +975,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "hook1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -1021,7 +1023,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -1140,7 +1142,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "hook1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -1152,7 +1154,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
},
|
||||
{
|
||||
HookName: "hook1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/bar"},
|
||||
|
@ -1164,7 +1166,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
},
|
||||
{
|
||||
HookName: "hook2",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/aaa"},
|
||||
|
@ -1178,7 +1180,7 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
"container2": {
|
||||
{
|
||||
HookName: "hook1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container2",
|
||||
Command: []string{"/usr/bin/baz"},
|
||||
|
@ -1192,9 +1194,11 @@ func TestGroupRestoreExecHooks(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
hookTracker := NewHookTracker()
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual, err := GroupRestoreExecHooks(tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger())
|
||||
actual, err := GroupRestoreExecHooks(tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), hookTracker)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.expected, actual)
|
||||
})
|
||||
|
@ -1983,3 +1987,494 @@ func TestValidateContainer(t *testing.T) {
|
|||
// noCommand string should return expected error as result.
|
||||
assert.Equal(t, expectedError, ValidateContainer([]byte(noCommand)))
|
||||
}
|
||||
|
||||
func TestBackupHookTracker(t *testing.T) {
|
||||
type podWithHook struct {
|
||||
item runtime.Unstructured
|
||||
hooks []ResourceHook
|
||||
hookErrorsByContainer map[string]error
|
||||
expectedPodHook *velerov1api.ExecHook
|
||||
expectedPodHookError error
|
||||
expectedError error
|
||||
}
|
||||
test1 := []struct {
|
||||
name string
|
||||
phase hookPhase
|
||||
groupResource string
|
||||
pods []podWithHook
|
||||
hookTracker *HookTracker
|
||||
expectedHookAttempted int
|
||||
expectedHookFailed int
|
||||
}{
|
||||
{
|
||||
name: "a pod with spec hooks, no error",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 2,
|
||||
expectedHookFailed: 0,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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: "a pod with spec hooks and same container under different hook name, no error",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 4,
|
||||
expectedHookFailed: 0,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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: "1a",
|
||||
Command: []string{"2a"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: &velerov1api.ExecHook{
|
||||
Container: "2b",
|
||||
Command: []string{"2b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "a pod with spec hooks, on error=fail",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 3,
|
||||
expectedHookFailed: 2,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "a pod with annotation and spec hooks",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 1,
|
||||
expectedHookFailed: 0,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: &velerov1api.ExecHook{
|
||||
Container: "1b",
|
||||
Command: []string{"1b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "a pod with annotation, on error=fail",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 1,
|
||||
expectedHookFailed: 1,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two pods, one with annotation, the other with spec",
|
||||
phase: PhasePre,
|
||||
groupResource: "pods",
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedHookAttempted: 3,
|
||||
expectedHookFailed: 1,
|
||||
pods: []podWithHook{
|
||||
{
|
||||
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"),
|
||||
},
|
||||
{
|
||||
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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range test1 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
h := &DefaultItemHookHandler{
|
||||
PodCommandExecutor: podCommandExecutor,
|
||||
}
|
||||
|
||||
groupResource := schema.ParseGroupResource(test.groupResource)
|
||||
hookTracker := test.hookTracker
|
||||
|
||||
for _, pod := range test.pods {
|
||||
if pod.expectedPodHook != nil {
|
||||
podCommandExecutor.On("ExecutePodCommand", mock.Anything, pod.item.UnstructuredContent(), "ns", "name", "<from-annotation>", pod.expectedPodHook).Return(pod.expectedPodHookError)
|
||||
} else {
|
||||
hookLoop:
|
||||
for _, resourceHook := range pod.hooks {
|
||||
for _, hook := range resourceHook.Pre {
|
||||
hookError := pod.hookErrorsByContainer[hook.Exec.Container]
|
||||
podCommandExecutor.On("ExecutePodCommand", mock.Anything, pod.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 := pod.hookErrorsByContainer[hook.Exec.Container]
|
||||
podCommandExecutor.On("ExecutePodCommand", mock.Anything, pod.item.UnstructuredContent(), "ns", "name", resourceHook.Name, hook.Exec).Return(hookError)
|
||||
if hookError != nil && hook.Exec.OnError == velerov1api.HookErrorModeFail {
|
||||
break hookLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h.HandleHooks(velerotest.NewLogger(), groupResource, pod.item, pod.hooks, test.phase, hookTracker)
|
||||
|
||||
}
|
||||
actualAtemptted, actualFailed := hookTracker.Stat()
|
||||
assert.Equal(t, test.expectedHookAttempted, actualAtemptted)
|
||||
assert.Equal(t, test.expectedHookFailed, actualFailed)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRestoreHookTrackerAdd(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
resourceRestoreHooks []ResourceRestoreHook
|
||||
pod *corev1api.Pod
|
||||
hookTracker *HookTracker
|
||||
expectedCnt int
|
||||
}{
|
||||
{
|
||||
name: "neither spec hooks nor annotations hooks are set",
|
||||
resourceRestoreHooks: nil,
|
||||
pod: builder.ForPod("default", "my-pod").Result(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 0,
|
||||
},
|
||||
{
|
||||
name: "a hook specified in pod annotation",
|
||||
resourceRestoreHooks: nil,
|
||||
pod: builder.ForPod("default", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
podRestoreHookCommandAnnotationKey, "/usr/bin/foo",
|
||||
podRestoreHookContainerAnnotationKey, "container1",
|
||||
podRestoreHookOnErrorAnnotationKey, string(velerov1api.HookErrorModeContinue),
|
||||
podRestoreHookTimeoutAnnotationKey, "1s",
|
||||
podRestoreHookWaitTimeoutAnnotationKey, "1m",
|
||||
podRestoreHookWaitForReadyAnnotationKey, "true",
|
||||
)).
|
||||
Containers(&corev1api.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 1,
|
||||
},
|
||||
{
|
||||
name: "two hooks specified in restore spec",
|
||||
resourceRestoreHooks: []ResourceRestoreHook{
|
||||
{
|
||||
Name: "hook1",
|
||||
Selector: ResourceHookSelector{},
|
||||
RestoreHooks: []velerov1api.RestoreResourceHook{
|
||||
{
|
||||
Exec: &velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
ExecTimeout: metav1.Duration{Duration: time.Second},
|
||||
WaitTimeout: metav1.Duration{Duration: time.Minute},
|
||||
},
|
||||
},
|
||||
{
|
||||
Exec: &velerov1api.ExecRestoreHook{
|
||||
Container: "container2",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
ExecTimeout: metav1.Duration{Duration: time.Second},
|
||||
WaitTimeout: metav1.Duration{Duration: time.Minute},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pod: builder.ForPod("default", "my-pod").
|
||||
Containers(&corev1api.Container{
|
||||
Name: "container1",
|
||||
}, &corev1api.Container{
|
||||
Name: "container2",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 2,
|
||||
},
|
||||
{
|
||||
name: "both spec hooks and annotations hooks are set",
|
||||
resourceRestoreHooks: []ResourceRestoreHook{
|
||||
{
|
||||
Name: "hook1",
|
||||
Selector: ResourceHookSelector{},
|
||||
RestoreHooks: []velerov1api.RestoreResourceHook{
|
||||
{
|
||||
Exec: &velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo2"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
ExecTimeout: metav1.Duration{Duration: time.Second},
|
||||
WaitTimeout: metav1.Duration{Duration: time.Minute},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pod: builder.ForPod("default", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
podRestoreHookCommandAnnotationKey, "/usr/bin/foo",
|
||||
podRestoreHookContainerAnnotationKey, "container1",
|
||||
podRestoreHookOnErrorAnnotationKey, string(velerov1api.HookErrorModeContinue),
|
||||
podRestoreHookTimeoutAnnotationKey, "1s",
|
||||
podRestoreHookWaitTimeoutAnnotationKey, "1m",
|
||||
podRestoreHookWaitForReadyAnnotationKey, "true",
|
||||
)).
|
||||
Containers(&corev1api.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
Result(),
|
||||
hookTracker: NewHookTracker(),
|
||||
expectedCnt: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, _ = GroupRestoreExecHooks(tc.resourceRestoreHooks, tc.pod, velerotest.NewLogger(), tc.hookTracker)
|
||||
tracker := tc.hookTracker.GetTracker()
|
||||
assert.Equal(t, tc.expectedCnt, len(tracker))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ type WaitExecHookHandler interface {
|
|||
log logrus.FieldLogger,
|
||||
pod *v1.Pod,
|
||||
byContainer map[string][]PodExecRestoreHook,
|
||||
hookTrack *HookTracker,
|
||||
) []error
|
||||
}
|
||||
|
||||
|
@ -73,6 +74,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
|||
log logrus.FieldLogger,
|
||||
pod *v1.Pod,
|
||||
byContainer map[string][]PodExecRestoreHook,
|
||||
hookTracker *HookTracker,
|
||||
) []error {
|
||||
if pod == nil {
|
||||
return nil
|
||||
|
@ -164,6 +166,8 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
|||
err := fmt.Errorf("hook %s in container %s expired before executing", hook.HookName, hook.Hook.Container)
|
||||
hookLog.Error(err)
|
||||
errors = append(errors, err)
|
||||
hookTracker.Record(newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true)
|
||||
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
cancel()
|
||||
return
|
||||
|
@ -175,10 +179,13 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
|||
OnError: hook.Hook.OnError,
|
||||
Timeout: hook.Hook.ExecTimeout,
|
||||
}
|
||||
hookTracker.Record(newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), false)
|
||||
if err := e.PodCommandExecutor.ExecutePodCommand(hookLog, podMap, pod.Namespace, pod.Name, hook.HookName, eh); err != nil {
|
||||
hookLog.WithError(err).Error("Error executing hook")
|
||||
err = fmt.Errorf("hook %s in container %s failed to execute, err: %v", hook.HookName, hook.Hook.Container, err)
|
||||
errors = append(errors, err)
|
||||
hookTracker.Record(newPod.Namespace, newPod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true)
|
||||
|
||||
if hook.Hook.OnError == velerov1api.HookErrorModeFail {
|
||||
cancel()
|
||||
return
|
||||
|
@ -226,6 +233,7 @@ func (e *DefaultWaitExecHookHandler) HandleHooks(
|
|||
"hookPhase": "post",
|
||||
},
|
||||
)
|
||||
hookTracker.Record(pod.Namespace, pod.Name, hook.Hook.Container, hook.HookSource, hook.HookName, hookPhase(""), true)
|
||||
hookLog.Error(err)
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -167,7 +167,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -236,7 +236,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -305,7 +305,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: "annotation",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -391,7 +391,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -440,7 +440,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -471,7 +471,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -502,7 +502,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -533,7 +533,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -574,7 +574,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
|
@ -584,7 +584,7 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
"container2": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: "backupSpec",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container2",
|
||||
Command: []string{"/usr/bin/bar"},
|
||||
|
@ -744,7 +744,8 @@ func TestWaitExecHandleHooks(t *testing.T) {
|
|||
defer ctxCancel()
|
||||
}
|
||||
|
||||
errs := h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer)
|
||||
hookTracker := NewHookTracker()
|
||||
errs := h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, hookTracker)
|
||||
|
||||
// for i, ee := range test.expectedErrors {
|
||||
require.Len(t, errs, len(test.expectedErrors))
|
||||
|
@ -997,3 +998,253 @@ func TestMaxHookWait(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreHookTrackerUpdate(t *testing.T) {
|
||||
type change struct {
|
||||
// delta to wait since last change applied or pod added
|
||||
wait time.Duration
|
||||
updated *v1.Pod
|
||||
}
|
||||
type expectedExecution struct {
|
||||
hook *velerov1api.ExecHook
|
||||
name string
|
||||
error error
|
||||
pod *v1.Pod
|
||||
}
|
||||
|
||||
hookTracker1 := NewHookTracker()
|
||||
hookTracker1.Add("default", "my-pod", "container1", HookSourceAnnotation, "<from-annotation>", hookPhase(""))
|
||||
|
||||
hookTracker2 := NewHookTracker()
|
||||
hookTracker2.Add("default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
|
||||
hookTracker3 := NewHookTracker()
|
||||
hookTracker3.Add("default", "my-pod", "container1", HookSourceSpec, "my-hook-1", hookPhase(""))
|
||||
hookTracker3.Add("default", "my-pod", "container2", HookSourceSpec, "my-hook-2", hookPhase(""))
|
||||
|
||||
tests1 := []struct {
|
||||
name string
|
||||
initialPod *v1.Pod
|
||||
groupResource string
|
||||
byContainer map[string][]PodExecRestoreHook
|
||||
expectedExecutions []expectedExecution
|
||||
hookTracker *HookTracker
|
||||
expectedFailed int
|
||||
}{
|
||||
{
|
||||
name: "a hook executes successfully",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
podRestoreHookCommandAnnotationKey, "/usr/bin/foo",
|
||||
podRestoreHookContainerAnnotationKey, "container1",
|
||||
podRestoreHookOnErrorAnnotationKey, string(velerov1api.HookErrorModeContinue),
|
||||
podRestoreHookTimeoutAnnotationKey, "1s",
|
||||
podRestoreHookWaitTimeoutAnnotationKey, "1m",
|
||||
)).
|
||||
Containers(&v1.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container1",
|
||||
State: v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{},
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
groupResource: "pods",
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
HookName: "<from-annotation>",
|
||||
HookSource: HookSourceAnnotation,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
ExecTimeout: metav1.Duration{Duration: time.Second},
|
||||
WaitTimeout: metav1.Duration{Duration: time.Minute},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedExecutions: []expectedExecution{
|
||||
{
|
||||
name: "<from-annotation>",
|
||||
hook: &velerov1api.ExecHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
Timeout: metav1.Duration{Duration: time.Second},
|
||||
},
|
||||
error: nil,
|
||||
pod: builder.ForPod("default", "my-pod").
|
||||
ObjectMeta(builder.WithResourceVersion("1")).
|
||||
ObjectMeta(builder.WithAnnotations(
|
||||
podRestoreHookCommandAnnotationKey, "/usr/bin/foo",
|
||||
podRestoreHookContainerAnnotationKey, "container1",
|
||||
podRestoreHookOnErrorAnnotationKey, string(velerov1api.HookErrorModeContinue),
|
||||
podRestoreHookTimeoutAnnotationKey, "1s",
|
||||
podRestoreHookWaitTimeoutAnnotationKey, "1m",
|
||||
)).
|
||||
Containers(&v1.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container1",
|
||||
State: v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{},
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
},
|
||||
},
|
||||
hookTracker: hookTracker1,
|
||||
expectedFailed: 0,
|
||||
},
|
||||
{
|
||||
name: "a hook with OnError mode Fail failed to execute",
|
||||
groupResource: "pods",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
Containers(&v1.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container1",
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{},
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeFail,
|
||||
WaitTimeout: metav1.Duration{Duration: time.Millisecond},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hookTracker: hookTracker2,
|
||||
expectedFailed: 1,
|
||||
},
|
||||
{
|
||||
name: "a hook with OnError mode Continue failed to execute",
|
||||
groupResource: "pods",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
Containers(&v1.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container1",
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{},
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
WaitTimeout: metav1.Duration{Duration: time.Millisecond},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hookTracker: hookTracker2,
|
||||
expectedFailed: 1,
|
||||
},
|
||||
{
|
||||
name: "two hooks with OnError mode Continue failed to execute",
|
||||
groupResource: "pods",
|
||||
initialPod: builder.ForPod("default", "my-pod").
|
||||
Containers(&v1.Container{
|
||||
Name: "container1",
|
||||
}).
|
||||
Containers(&v1.Container{
|
||||
Name: "container2",
|
||||
}).
|
||||
// initially both are waiting
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container1",
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{},
|
||||
},
|
||||
}).
|
||||
ContainerStatuses(&v1.ContainerStatus{
|
||||
Name: "container2",
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{},
|
||||
},
|
||||
}).
|
||||
Result(),
|
||||
byContainer: map[string][]PodExecRestoreHook{
|
||||
"container1": {
|
||||
{
|
||||
HookName: "my-hook-1",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container1",
|
||||
Command: []string{"/usr/bin/foo"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
WaitTimeout: metav1.Duration{Duration: time.Millisecond},
|
||||
},
|
||||
},
|
||||
},
|
||||
"container2": {
|
||||
{
|
||||
HookName: "my-hook-2",
|
||||
HookSource: HookSourceSpec,
|
||||
Hook: velerov1api.ExecRestoreHook{
|
||||
Container: "container2",
|
||||
Command: []string{"/usr/bin/bar"},
|
||||
OnError: velerov1api.HookErrorModeContinue,
|
||||
WaitTimeout: metav1.Duration{Duration: time.Millisecond},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hookTracker: hookTracker3,
|
||||
expectedFailed: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests1 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
source := fcache.NewFakeControllerSource()
|
||||
go func() {
|
||||
// This is the state of the pod that will be seen by the AddFunc handler.
|
||||
source.Add(test.initialPod)
|
||||
}()
|
||||
|
||||
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
h := &DefaultWaitExecHookHandler{
|
||||
PodCommandExecutor: podCommandExecutor,
|
||||
ListWatchFactory: &fakeListWatchFactory{source},
|
||||
}
|
||||
|
||||
for _, e := range test.expectedExecutions {
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(e.pod)
|
||||
assert.Nil(t, err)
|
||||
podCommandExecutor.On("ExecutePodCommand", mock.Anything, obj, e.pod.Namespace, e.pod.Name, e.name, e.hook).Return(e.error)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
_ = h.HandleHooks(ctx, velerotest.NewLogger(), test.initialPod, test.byContainer, test.hookTracker)
|
||||
_, actualFailed := test.hookTracker.Stat()
|
||||
assert.Equal(t, test.expectedFailed, actualFailed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -441,6 +441,11 @@ type BackupStatus struct {
|
|||
// BackupItemAction operations for this backup which ended with an error.
|
||||
// +optional
|
||||
BackupItemOperationsFailed int `json:"backupItemOperationsFailed,omitempty"`
|
||||
|
||||
// HookStatus contains information about the status of the hooks.
|
||||
// +optional
|
||||
// +nullable
|
||||
HookStatus *HookStatus `json:"hookStatus,omitempty"`
|
||||
}
|
||||
|
||||
// BackupProgress stores information about the progress of a Backup's execution.
|
||||
|
@ -458,6 +463,19 @@ type BackupProgress struct {
|
|||
ItemsBackedUp int `json:"itemsBackedUp,omitempty"`
|
||||
}
|
||||
|
||||
// HookStatus stores information about the status of the hooks.
|
||||
type HookStatus struct {
|
||||
// HooksAttempted is the total number of attempted hooks
|
||||
// Specifically, HooksAttempted represents the number of hooks that failed to execute
|
||||
// and the number of hooks that executed successfully.
|
||||
// +optional
|
||||
HooksAttempted int `json:"hooksAttempted,omitempty"`
|
||||
|
||||
// HooksFailed is the total number of hooks which ended with an error
|
||||
// +optional
|
||||
HooksFailed int `json:"hooksFailed,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
|
|
|
@ -345,6 +345,11 @@ type RestoreStatus struct {
|
|||
// RestoreItemAction operations for this restore which ended with an error.
|
||||
// +optional
|
||||
RestoreItemOperationsFailed int `json:"restoreItemOperationsFailed,omitempty"`
|
||||
|
||||
// HookStatus contains information about the status of the hooks.
|
||||
// +optional
|
||||
// +nullable
|
||||
HookStatus *HookStatus `json:"hookStatus,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreProgress stores information about the restore's execution progress
|
||||
|
|
|
@ -419,6 +419,11 @@ func (in *BackupStatus) DeepCopyInto(out *BackupStatus) {
|
|||
*out = new(BackupProgress)
|
||||
**out = **in
|
||||
}
|
||||
if in.HookStatus != nil {
|
||||
in, out := &in.HookStatus, &out.HookStatus
|
||||
*out = new(HookStatus)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupStatus.
|
||||
|
@ -802,6 +807,21 @@ func (in *ExecRestoreHook) DeepCopy() *ExecRestoreHook {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HookStatus) DeepCopyInto(out *HookStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HookStatus.
|
||||
func (in *HookStatus) DeepCopy() *HookStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(HookStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InitRestoreHook) DeepCopyInto(out *InitRestoreHook) {
|
||||
*out = *in
|
||||
|
@ -1362,6 +1382,11 @@ func (in *RestoreStatus) DeepCopyInto(out *RestoreStatus) {
|
|||
*out = new(RestoreProgress)
|
||||
**out = **in
|
||||
}
|
||||
if in.HookStatus != nil {
|
||||
in, out := &in.HookStatus, &out.HookStatus
|
||||
*out = new(HookStatus)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreStatus.
|
||||
|
|
|
@ -302,6 +302,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
|||
itemHookHandler: &hook.DefaultItemHookHandler{
|
||||
PodCommandExecutor: kb.podCommandExecutor,
|
||||
},
|
||||
hookTracker: hook.NewHookTracker(),
|
||||
}
|
||||
|
||||
// helper struct to send current progress between the main
|
||||
|
@ -427,8 +428,15 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
|
|||
updated.Status.Progress.TotalItems = len(backupRequest.BackedUpItems)
|
||||
updated.Status.Progress.ItemsBackedUp = len(backupRequest.BackedUpItems)
|
||||
|
||||
// update the hooks execution status
|
||||
if updated.Status.HookStatus == nil {
|
||||
updated.Status.HookStatus = &velerov1api.HookStatus{}
|
||||
}
|
||||
updated.Status.HookStatus.HooksAttempted, updated.Status.HookStatus.HooksFailed = itemBackupper.hookTracker.Stat()
|
||||
log.Infof("hookTracker: %+v, hookAttempted: %d, hookFailed: %d", itemBackupper.hookTracker.GetTracker(), updated.Status.HookStatus.HooksAttempted, updated.Status.HookStatus.HooksFailed)
|
||||
|
||||
if err := kube.PatchResource(backupRequest.Backup, updated, kb.kbClient); err != nil {
|
||||
log.WithError(errors.WithStack((err))).Warn("Got error trying to update backup's status.progress")
|
||||
log.WithError(errors.WithStack((err))).Warn("Got error trying to update backup's status.progress and hook status")
|
||||
}
|
||||
skippedPVSummary, _ := json.Marshal(backupRequest.SkippedPVTracker.Summary())
|
||||
log.Infof("Summary for skipped PVs: %s", skippedPVSummary)
|
||||
|
@ -598,6 +606,7 @@ func (kb *kubernetesBackupper) FinalizeBackup(log logrus.FieldLogger,
|
|||
discoveryHelper: kb.discoveryHelper,
|
||||
itemHookHandler: &hook.NoOpItemHookHandler{},
|
||||
podVolumeSnapshotTracker: newPVCSnapshotTracker(),
|
||||
hookTracker: hook.NewHookTracker(),
|
||||
}
|
||||
updateFiles := make(map[string]FileForArchive)
|
||||
backedUpGroupResources := map[schema.GroupResource]bool{}
|
||||
|
|
|
@ -78,6 +78,7 @@ type itemBackupper struct {
|
|||
|
||||
itemHookHandler hook.ItemHookHandler
|
||||
snapshotLocationVolumeSnapshotters map[string]vsv1.VolumeSnapshotter
|
||||
hookTracker *hook.HookTracker
|
||||
}
|
||||
|
||||
type FileForArchive struct {
|
||||
|
@ -184,7 +185,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
|
|||
)
|
||||
|
||||
log.Debug("Executing pre hooks")
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePre); err != nil {
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePre, ib.hookTracker); err != nil {
|
||||
return false, itemFiles, err
|
||||
}
|
||||
if optedOut, podName := ib.podVolumeSnapshotTracker.OptedoutByPod(namespace, name); optedOut {
|
||||
|
@ -234,7 +235,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
|
|||
|
||||
// if there was an error running actions, execute post hooks and return
|
||||
log.Debug("Executing post hooks")
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePost); err != nil {
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePost, ib.hookTracker); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
return false, itemFiles, kubeerrs.NewAggregate(backupErrs)
|
||||
|
@ -293,7 +294,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
|
|||
}
|
||||
|
||||
log.Debug("Executing post hooks")
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePost); err != nil {
|
||||
if err := ib.itemHookHandler.HandleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hook.PhasePost, ib.hookTracker); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -392,6 +392,12 @@ func DescribeBackupStatus(ctx context.Context, kbClient kbclient.Client, d *Desc
|
|||
}
|
||||
|
||||
d.Printf("Velero-Native Snapshots: <none included>\n")
|
||||
|
||||
if status.HookStatus != nil {
|
||||
d.Println()
|
||||
d.Printf("HooksAttempted:\t%d\n", status.HookStatus.HooksAttempted)
|
||||
d.Printf("HooksFailed:\t%d\n", status.HookStatus.HooksFailed)
|
||||
}
|
||||
}
|
||||
|
||||
func describeBackupItemOperations(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, details bool, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
|
|
|
@ -303,6 +303,11 @@ func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d *
|
|||
backupStatusInfo["veleroNativeSnapshotsDetail"] = snapshotDetails
|
||||
return
|
||||
}
|
||||
|
||||
if status.HookStatus != nil {
|
||||
backupStatusInfo["hooksAttempted"] = status.HookStatus.HooksAttempted
|
||||
backupStatusInfo["hooksFailed"] = status.HookStatus.HooksFailed
|
||||
}
|
||||
}
|
||||
|
||||
func describeBackupResourceListInSF(ctx context.Context, kbClient kbclient.Client, backupStatusInfo map[string]interface{}, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) {
|
||||
|
|
|
@ -180,6 +180,12 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel
|
|||
d.Println()
|
||||
describeRestoreItemOperations(ctx, kbClient, d, restore, details, insecureSkipTLSVerify, caCertFile)
|
||||
|
||||
if restore.Status.HookStatus != nil {
|
||||
d.Println()
|
||||
d.Printf("HooksAttempted: \t%d\n", restore.Status.HookStatus.HooksAttempted)
|
||||
d.Printf("HooksFailed: \t%d\n", restore.Status.HookStatus.HooksFailed)
|
||||
}
|
||||
|
||||
if details {
|
||||
describeRestoreResourceList(ctx, kbClient, d, restore, insecureSkipTLSVerify, caCertFile)
|
||||
d.Println()
|
||||
|
|
|
@ -325,6 +325,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
|||
resourceModifiers: req.ResourceModifiers,
|
||||
disableInformerCache: req.DisableInformerCache,
|
||||
featureVerifier: kr.featureVerifier,
|
||||
hookTracker: hook.NewHookTracker(),
|
||||
}
|
||||
|
||||
return restoreCtx.execute()
|
||||
|
@ -377,6 +378,7 @@ type restoreContext struct {
|
|||
resourceModifiers *resourcemodifiers.ResourceModifiers
|
||||
disableInformerCache bool
|
||||
featureVerifier features.Verifier
|
||||
hookTracker *hook.HookTracker
|
||||
}
|
||||
|
||||
type resourceClientKey struct {
|
||||
|
@ -646,11 +648,6 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
|
|||
updated.Status.Progress.TotalItems = len(ctx.restoredItems)
|
||||
updated.Status.Progress.ItemsRestored = len(ctx.restoredItems)
|
||||
|
||||
err = kube.PatchResource(ctx.restore, updated, ctx.kbClient)
|
||||
if err != nil {
|
||||
ctx.log.WithError(errors.WithStack((err))).Warn("Updating restore status.progress")
|
||||
}
|
||||
|
||||
// Wait for all of the pod volume restore goroutines to be done, which is
|
||||
// only possible once all of their errors have been received by the loop
|
||||
// below, then close the podVolumeErrs channel so the loop terminates.
|
||||
|
@ -685,6 +682,19 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
|
|||
}
|
||||
ctx.log.Info("Done waiting for all post-restore exec hooks to complete")
|
||||
|
||||
// update hooks execution status
|
||||
if updated.Status.HookStatus == nil {
|
||||
updated.Status.HookStatus = &velerov1api.HookStatus{}
|
||||
}
|
||||
updated.Status.HookStatus.HooksAttempted, updated.Status.HookStatus.HooksFailed = ctx.hookTracker.Stat()
|
||||
ctx.log.Infof("hookTracker: %+v, hookAttempted: %d, hookFailed: %d", ctx.hookTracker.GetTracker(), updated.Status.HookStatus.HooksAttempted, updated.Status.HookStatus.HooksFailed)
|
||||
|
||||
// patch the restore status
|
||||
err = kube.PatchResource(ctx.restore, updated, ctx.kbClient)
|
||||
if err != nil {
|
||||
ctx.log.WithError(errors.WithStack((err))).Warn("Updating restore status")
|
||||
}
|
||||
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
|
@ -1971,6 +1981,7 @@ func (ctx *restoreContext) waitExec(createdObj *unstructured.Unstructured) {
|
|||
ctx.resourceRestoreHooks,
|
||||
pod,
|
||||
ctx.log,
|
||||
ctx.hookTracker,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.log.WithError(err).Errorf("error getting exec hooks for pod %s/%s", pod.Namespace, pod.Name)
|
||||
|
@ -1978,7 +1989,7 @@ func (ctx *restoreContext) waitExec(createdObj *unstructured.Unstructured) {
|
|||
return
|
||||
}
|
||||
|
||||
if errs := ctx.waitExecHookHandler.HandleHooks(ctx.hooksContext, ctx.log, pod, execHooksByContainer); len(errs) > 0 {
|
||||
if errs := ctx.waitExecHookHandler.HandleHooks(ctx.hooksContext, ctx.log, pod, execHooksByContainer, ctx.hookTracker); len(errs) > 0 {
|
||||
ctx.log.WithError(kubeerrs.NewAggregate(errs)).Error("unable to successfully execute post-restore hooks")
|
||||
ctx.hooksCancelFunc()
|
||||
|
||||
|
|
Loading…
Reference in New Issue