Skip to content

Commit

Permalink
Merge pull request #7117 from allenxu404/issue6567
Browse files Browse the repository at this point in the history
Add hook status to backup/restore CR
  • Loading branch information
reasonerjt authored Nov 28, 2023
2 parents 6ac7ff1 + 5d1a632 commit 85482ae
Show file tree
Hide file tree
Showing 19 changed files with 1,173 additions and 52 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/7117-allenxu404
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add hooks status to backup/restore CR
16 changes: 16 additions & 0 deletions config/crd/v1/bases/velero.io_backups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 16 additions & 0 deletions config/crd/v1/bases/velero.io_restores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions config/crd/v1/crds/crds.go

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions internal/hook/hook_tracker.go
Original file line number Diff line number Diff line change
@@ -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
}
88 changes: 88 additions & 0 deletions internal/hook/hook_tracker_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 85482ae

Please sign in to comment.