Pod Volume Backup/Restore Refactor: Rename Init Helper (#5432)

* restore helper refactor

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>

* resolve codespell

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
pull/5449/head^2
lyndon 2022-10-17 13:42:09 +08:00 committed by GitHub
parent 7a535ea047
commit d52ec8c079
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 169 additions and 137 deletions

View File

@ -15,6 +15,6 @@ jobs:
with:
# ignore the config/.../crd.go file as it's generated binary data that is edited elswhere.
skip: .git,*.png,*.jpg,*.woff,*.ttf,*.gif,*.ico,./config/crd/v1beta1/crds/crds.go,./config/crd/v1/crds/crds.go,./go.sum,./LICENSE
ignore_words_list: iam,aks,ist,bridget,ue
ignore_words_list: iam,aks,ist,bridget,ue,shouldnot
check_filenames: true
check_hidden: true

View File

@ -112,17 +112,17 @@ GOPROXY ?= https://proxy.golang.org
# If you want to build all containers, see the 'all-containers' rule.
all:
@$(MAKE) build
@$(MAKE) build BIN=velero-restic-restore-helper
@$(MAKE) build BIN=velero-restore-helper
build-%:
@$(MAKE) --no-print-directory ARCH=$* build
@$(MAKE) --no-print-directory ARCH=$* build BIN=velero-restic-restore-helper
@$(MAKE) --no-print-directory ARCH=$* build BIN=velero-restore-helper
all-build: $(addprefix build-, $(CLI_PLATFORMS))
all-containers: container-builder-env
@$(MAKE) --no-print-directory container
@$(MAKE) --no-print-directory container BIN=velero-restic-restore-helper
@$(MAKE) --no-print-directory container BIN=velero-restore-helper
local: build-dirs
# Add DEBUG=1 to enable debug locally

View File

@ -0,0 +1 @@
Rename Velero pod volume restore init helper from "velero-restic-restore-helper" to "velero-restore-helper"

View File

@ -2,7 +2,7 @@
This document proposes a solution that allows user to specify a backup order for resources of specific resource type.
## Background
During backup process, user may need to back up resources of specific type in some specific order to ensure the resources were backup properly because these resources are related and ordering might be required to preserve the consistency for the apps to recover itself  from the backup image
During backup process, user may need to back up resources of specific type in some specific order to ensure the resources were backup properly because these resources are related and ordering might be required to preserve the consistency for the apps to recover itself <EFBFBD>from the backup image
(Ex: primary-secondary database pods in a cluster).
## Goals
@ -12,7 +12,7 @@ During backup process, user may need to back up resources of specific type in so
- Use a plugin to backup an resources and all the sub resources. For example use a plugin for StatefulSet and backup pods belong to the StatefulSet in specific order. This plugin solution is not generic and requires plugin for each resource type.
## High-Level Design
User will specify a map of resource type to list resource names (separate by semicolons). Each name will be in the format "namespaceName/resourceName" to enable ordering accross namespaces. Based on this map, the resources of each resource type will be sorted by the order specified in the list of resources. If a resource instance belong to that specific type but its name is not in the order list, then it will be put behind other resources that are in the list.
User will specify a map of resource type to list resource names (separate by semicolons). Each name will be in the format "namespaceName/resourceName" to enable ordering across namespaces. Based on this map, the resources of each resource type will be sorted by the order specified in the list of resources. If a resource instance belong to that specific type but its name is not in the order list, then it will be put behind other resources that are in the list.
### Changes to BackupSpec
Add new field to BackupSpec
@ -36,5 +36,5 @@ Example:
>velero backup create mybackup --ordered-resources "pod=ns1/pod1,ns1/pod2;persistentvolumeclaim=n2/slavepod,ns2/primarypod"
## Open Issues
- In the CLI, the design proposes to use commas to separate items of a resource type and semicolon to separate key-value pairs. This follows the convention of using commas to separate items in a list (For example: --include-namespaces ns1,ns2). However, the syntax for map in labels and annotations use commas to seperate key-value pairs. So it introduces some inconsistency.
- In the CLI, the design proposes to use commas to separate items of a resource type and semicolon to separate key-value pairs. This follows the convention of using commas to separate items in a list (For example: --include-namespaces ns1,ns2). However, the syntax for map in labels and annotations use commas to separate key-value pairs. So it introduces some inconsistency.
- For pods that managed by Deployment or DaemonSet, this design may not work because the pods' name is randomly generated and if pods are restarted, they would have different names so the Backup operation may not consider the restarted pods in the sorting algorithm. This problem will be addressed when we enhance the design to use regular expression to specify the OrderResources instead of exact match.

View File

@ -28,7 +28,7 @@ This document proposes adding _controller-tools_ to the project to automatically
_controller-tools_ works by reading the Go files that contain the API type definitions.
It uses a combination of the struct fields, types, tags and comments to build the OpenAPIv3 schema for the CRDs. The tooling makes some assumptions based on conventions followed in upstream Kubernetes and the ecosystem, which involves some changes to the Velero API type definitions, especially around optional fields.
In order for _controller-tools_ to read the Go files containing Velero API type defintiions, the CRDs need to be generated at build time, as these files are not available at runtime (i.e. the Go files are not accessible by the compiled binary).
In order for _controller-tools_ to read the Go files containing Velero API type definitions, the CRDs need to be generated at build time, as these files are not available at runtime (i.e. the Go files are not accessible by the compiled binary).
These generated CRD manifests (YAML) will then need to be available to the `pkg/install` package for it to include when installing Velero resources.
## Detailed Design

View File

@ -36,7 +36,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/restorehelper"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/util/kube"
)
@ -121,12 +121,12 @@ func (i *InitContainerRestoreHookHandler) HandleRestoreHooks(
}
initContainers := []corev1api.Container{}
// If this pod had pod volumes backed up using restic, then we want to the pod volumes restored prior to
// If this pod is backed up with data movement, then we want to the pod volumes restored prior to
// running the restore hook init containers. This allows the restore hook init containers to prepare the
// restored data to be consumed by the application container(s).
// So if there is a "restic-wait" init container already on the pod at index 0, we'll preserve that and run
// So if there is a "restore-wait" init container already on the pod at index 0, we'll preserve that and run
// it before running any other init container.
if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == podvolume.InitContainer {
if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == restorehelper.WaitInitContainer {
initContainers = append(initContainers, pod.Spec.InitContainers[0])
pod.Spec.InitContainers = pod.Spec.InitContainers[1:]
}

View File

@ -1675,7 +1675,7 @@ func TestHandleRestoreHooks(t *testing.T) {
},
},
{
name: "should preserve restic-wait init container when it is the only existing init container",
name: "should preserve restore-wait init container when it is the only existing init container",
podInput: corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
@ -1683,8 +1683,8 @@ func TestHandleRestoreHooks(t *testing.T) {
},
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
*builder.ForContainer("restic-wait", "bus-box").
Command([]string{"restic-restore"}).Result(),
*builder.ForContainer("restore-wait", "bus-box").
Command([]string{"pod-volume-restore"}).Result(),
},
},
},
@ -1696,8 +1696,8 @@ func TestHandleRestoreHooks(t *testing.T) {
},
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
*builder.ForContainer("restic-wait", "bus-box").
Command([]string{"restic-restore"}).Result(),
*builder.ForContainer("restore-wait", "bus-box").
Command([]string{"pod-volume-restore"}).Result(),
*builder.ForContainer("restore-init-container-1", "nginx").
Command([]string{"a", "b", "c"}).Result(),
*builder.ForContainer("restore-init-container-2", "nginx").
@ -1729,7 +1729,7 @@ func TestHandleRestoreHooks(t *testing.T) {
},
{
name: "should preserve restic-wait init container when it exits with other init containers",
name: "should preserve restore-wait init container when it exits with other init containers",
podInput: corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
@ -1737,8 +1737,8 @@ func TestHandleRestoreHooks(t *testing.T) {
},
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
*builder.ForContainer("restic-wait", "bus-box").
Command([]string{"restic-restore"}).Result(),
*builder.ForContainer("restore-wait", "bus-box").
Command([]string{"pod-volume-restore"}).Result(),
*builder.ForContainer("init-app-step1", "busy-box").
Command([]string{"init-step1"}).Result(),
*builder.ForContainer("init-app-step2", "busy-box").
@ -1754,8 +1754,8 @@ func TestHandleRestoreHooks(t *testing.T) {
},
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
*builder.ForContainer("restic-wait", "bus-box").
Command([]string{"restic-restore"}).Result(),
*builder.ForContainer("restore-wait", "bus-box").
Command([]string{"pod-volume-restore"}).Result(),
*builder.ForContainer("restore-init-container-1", "nginx").
Command([]string{"a", "b", "c"}).Result(),
*builder.ForContainer("restore-init-container-2", "nginx").

View File

@ -44,8 +44,8 @@ func DefaultVeleroImage() string {
return fmt.Sprintf("%s/%s:%s", imageRegistry(), "velero", ImageTag())
}
// DefaultResticRestoreHelperImage returns the default container image to use for the restic restore helper
// DefaultRestoreHelperImage returns the default container image to use for the restore helper
// for this version of Velero.
func DefaultResticRestoreHelperImage() string {
return fmt.Sprintf("%s/%s:%s", imageRegistry(), "velero-restic-restore-helper", ImageTag())
func DefaultRestoreHelperImage() string {
return fmt.Sprintf("%s/%s:%s", imageRegistry(), "velero-restore-helper", ImageTag())
}

View File

@ -135,6 +135,6 @@ func TestDefaultVeleroImage(t *testing.T) {
testDefaultImage(t, DefaultVeleroImage, "velero")
}
func TestDefaultResticRestoreHelperImage(t *testing.T) {
testDefaultImage(t, DefaultResticRestoreHelperImage, "velero-restic-restore-helper")
func TestDefaultRestoreHelperImage(t *testing.T) {
testDefaultImage(t, DefaultRestoreHelperImage, "velero-restore-helper")
}

View File

@ -45,7 +45,7 @@ func NewCommand(f client.Factory) *cobra.Command {
RegisterBackupItemAction("velero.io/service-account", newServiceAccountBackupItemAction(f)).
RegisterRestoreItemAction("velero.io/job", newJobRestoreItemAction).
RegisterRestoreItemAction("velero.io/pod", newPodRestoreItemAction).
RegisterRestoreItemAction("velero.io/restic", newResticRestoreItemAction(f)).
RegisterRestoreItemAction("velero.io/pod-volume-restore", newPodVolumeRestoreItemAction(f)).
RegisterRestoreItemAction("velero.io/init-restore-hook", newInitRestoreHookPodAction).
RegisterRestoreItemAction("velero.io/service", newServiceRestoreItemAction).
RegisterRestoreItemAction("velero.io/service-account", newServiceAccountRestoreItemAction).
@ -139,7 +139,7 @@ func newInitRestoreHookPodAction(logger logrus.FieldLogger) (interface{}, error)
return restore.NewInitRestoreHookPodAction(logger), nil
}
func newResticRestoreItemAction(f client.Factory) plugincommon.HandlerInitializer {
func newPodVolumeRestoreItemAction(f client.Factory) plugincommon.HandlerInitializer {
return func(logger logrus.FieldLogger) (interface{}, error) {
client, err := f.KubeClient()
if err != nil {
@ -151,7 +151,7 @@ func newResticRestoreItemAction(f client.Factory) plugincommon.HandlerInitialize
return nil, err
}
return restore.NewResticRestoreAction(logger, client.CoreV1().ConfigMaps(f.Namespace()), veleroClient.VeleroV1().PodVolumeBackups(f.Namespace())), nil
return restore.NewPodVolumeRestoreAction(logger, client.CoreV1().ConfigMaps(f.Namespace()), veleroClient.VeleroV1().PodVolumeBackups(f.Namespace())), nil
}
}

View File

@ -42,6 +42,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/repository"
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
"github.com/vmware-tanzu/velero/pkg/restorehelper"
"github.com/vmware-tanzu/velero/pkg/uploader"
"github.com/vmware-tanzu/velero/pkg/uploader/provider"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
@ -105,10 +106,10 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req
return ctrl.Result{}, nil
}
resticInitContainerIndex := getResticInitContainerIndex(pod)
if resticInitContainerIndex > 0 {
initContainerIndex := getInitContainerIndex(pod)
if initContainerIndex > 0 {
log.Warnf(`Init containers before the %s container may cause issues
if they interfere with volumes being restored: %s index %d`, podvolume.InitContainer, podvolume.InitContainer, resticInitContainerIndex)
if they interfere with volumes being restored: %s index %d`, restorehelper.WaitInitContainer, restorehelper.WaitInitContainer, initContainerIndex)
}
log.Info("Restore starting")
@ -162,8 +163,8 @@ func (c *PodVolumeRestoreReconciler) shouldProcess(ctx context.Context, log logr
return false, nil, err
}
if !isResticInitContainerRunning(pod) {
log.Debug("Pod is not running restic-wait init container, skip")
if !isInitContainerRunning(pod) {
log.Debug("Pod is not running restore-wait init container, skip")
return false, nil, nil
}
@ -207,18 +208,18 @@ func isPVRNew(pvr *velerov1api.PodVolumeRestore) bool {
return pvr.Status.Phase == "" || pvr.Status.Phase == velerov1api.PodVolumeRestorePhaseNew
}
func isResticInitContainerRunning(pod *corev1api.Pod) bool {
// Restic wait container can be anywhere in the list of init containers, but must be running.
i := getResticInitContainerIndex(pod)
func isInitContainerRunning(pod *corev1api.Pod) bool {
// Pod volume wait container can be anywhere in the list of init containers, but must be running.
i := getInitContainerIndex(pod)
return i >= 0 &&
len(pod.Status.InitContainerStatuses)-1 >= i &&
pod.Status.InitContainerStatuses[i].State.Running != nil
}
func getResticInitContainerIndex(pod *corev1api.Pod) int {
// Restic wait container can be anywhere in the list of init containers so locate it.
func getInitContainerIndex(pod *corev1api.Pod) int {
// Pod volume wait container can be anywhere in the list of init containers so locate it.
for i, initContainer := range pod.Spec.InitContainers {
if initContainer.Name == podvolume.InitContainer {
if initContainer.Name == restorehelper.WaitInitContainer {
return i
}
}
@ -299,7 +300,7 @@ func (c *PodVolumeRestoreReconciler) processRestore(ctx context.Context, req *ve
}
// Write a done file with name=<restore-uid> into the just-created .velero dir
// within the volume. The velero restic init container on the pod is waiting
// within the volume. The velero init container on the pod is waiting
// for this file to exist in each restored volume before completing.
if err := ioutil.WriteFile(filepath.Join(volumePath, ".velero", string(restoreUID)), nil, 0644); err != nil {
return errors.Wrap(err, "error writing done file")

View File

@ -31,7 +31,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/restorehelper"
"github.com/vmware-tanzu/velero/pkg/test"
)
@ -120,7 +120,7 @@ func TestShouldProcess(t *testing.T) {
NodeName: controllerNode,
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
},
},
@ -160,7 +160,7 @@ func TestShouldProcess(t *testing.T) {
NodeName: controllerNode,
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
},
},
@ -205,7 +205,7 @@ func TestShouldProcess(t *testing.T) {
}
}
func TestIsResticContainerRunning(t *testing.T) {
func TestIsInitContainerRunning(t *testing.T) {
tests := []struct {
name string
pod *corev1api.Pod
@ -222,7 +222,7 @@ func TestIsResticContainerRunning(t *testing.T) {
expected: false,
},
{
name: "pod with running init container that's not restic should return false",
name: "pod with running init container that's not restore init should return false",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -231,7 +231,7 @@ func TestIsResticContainerRunning(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -248,7 +248,7 @@ func TestIsResticContainerRunning(t *testing.T) {
expected: false,
},
{
name: "pod with running restic init container that's not first should still work",
name: "pod with running init container that's not first should still work",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -257,10 +257,10 @@ func TestIsResticContainerRunning(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: "non-restic-init",
Name: "non-restore-init",
},
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
},
},
@ -282,7 +282,7 @@ func TestIsResticContainerRunning(t *testing.T) {
expected: true,
},
{
name: "pod with restic init container as first initContainer that's not running should return false",
name: "pod with init container as first initContainer that's not running should return false",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -291,10 +291,10 @@ func TestIsResticContainerRunning(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -314,7 +314,7 @@ func TestIsResticContainerRunning(t *testing.T) {
expected: false,
},
{
name: "pod with running restic init container as first initContainer should return true",
name: "pod with running init container as first initContainer should return true",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -323,10 +323,10 @@ func TestIsResticContainerRunning(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -348,7 +348,7 @@ func TestIsResticContainerRunning(t *testing.T) {
expected: true,
},
{
name: "pod with restic init container with empty InitContainerStatuses should return 0",
name: "pod with init container with empty InitContainerStatuses should return 0",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -357,7 +357,7 @@ func TestIsResticContainerRunning(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
},
},
@ -371,12 +371,12 @@ func TestIsResticContainerRunning(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, isResticInitContainerRunning(test.pod))
assert.Equal(t, test.expected, isInitContainerRunning(test.pod))
})
}
}
func TestGetResticInitContainerIndex(t *testing.T) {
func TestGetInitContainerIndex(t *testing.T) {
tests := []struct {
name string
pod *corev1api.Pod
@ -393,7 +393,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
expected: -1,
},
{
name: "pod with no restic init container return -1",
name: "pod with no init container return -1",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -402,7 +402,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -410,7 +410,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
expected: -1,
},
{
name: "pod with restic container as second initContainern should return 1",
name: "pod with container as second initContainern should return 1",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -419,10 +419,10 @@ func TestGetResticInitContainerIndex(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: "non-restic-init",
Name: "non-restore-init",
},
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
},
},
@ -430,7 +430,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
expected: 1,
},
{
name: "pod with restic init container as first initContainer should return 0",
name: "pod with init container as first initContainer should return 0",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -439,10 +439,10 @@ func TestGetResticInitContainerIndex(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -450,7 +450,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
expected: 0,
},
{
name: "pod with restic init container as first initContainer should return 0",
name: "pod with init container as first initContainer should return 0",
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns-1",
@ -459,10 +459,10 @@ func TestGetResticInitContainerIndex(t *testing.T) {
Spec: corev1api.PodSpec{
InitContainers: []corev1api.Container{
{
Name: podvolume.InitContainer,
Name: restorehelper.WaitInitContainer,
},
{
Name: "non-restic-init",
Name: "non-restore-init",
},
},
},
@ -473,7 +473,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, getResticInitContainerIndex(test.pod))
assert.Equal(t, test.expected, getInitContainerIndex(test.pod))
})
}
}

View File

@ -38,17 +38,13 @@ const (
podAnnotationPrefix = "snapshot.velero.io/"
// VolumesToBackupAnnotation is the annotation on a pod whose mounted volumes
// need to be backed up using restic.
// need to be backed up using pod volume backup.
VolumesToBackupAnnotation = "backup.velero.io/backup-volumes"
// VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes
// should be excluded from restic backup.
// should be excluded from pod volume backup.
VolumesToExcludeAnnotation = "backup.velero.io/backup-volumes-excludes"
// InitContainer is the name of the init container added
// to workload pods to help with restores.
InitContainer = "restic-wait"
// DefaultVolumesToFsBackup specifies whether pod volume backup should be used, by default, to
// take backup of all pod volumes.
DefaultVolumesToFsBackup = false
@ -201,7 +197,7 @@ func volumeHasNonRestorableSource(volumeName string, podVolumes []corev1api.Volu
// getPodSnapshotAnnotations returns a map, of volume name -> snapshot id,
// of all snapshots for this pod.
// TODO(2.0) to remove
// Deprecated: we will stop using pod annotations to record restic snapshot IDs after they're taken,
// Deprecated: we will stop using pod annotations to record pod volume snapshot IDs after they're taken,
// therefore we won't need to check if these annotations exist.
func getPodSnapshotAnnotations(obj metav1.Object) map[string]string {
var res map[string]string
@ -224,7 +220,7 @@ func getPodSnapshotAnnotations(obj metav1.Object) map[string]string {
// GetVolumesToBackup returns a list of volume names to backup for
// the provided pod.
// Deprecated: Use GetPodVolumesUsingRestic instead.
// Deprecated: Use GetVolumesByPod instead.
func GetVolumesToBackup(obj metav1.Object) []string {
annotations := obj.GetAnnotations()
if annotations == nil {
@ -267,7 +263,7 @@ func GetVolumesByPod(pod *corev1api.Pod, defaultVolumesToFsBackup bool) []string
podVolumes := []string{}
for _, pv := range pod.Spec.Volumes {
// cannot backup hostpath volumes as they are not mounted into /var/lib/kubelet/pods
// and therefore not accessible to the restic daemon set.
// and therefore not accessible to the node agent daemon set.
if pv.HostPath != nil {
continue
}

View File

@ -37,38 +37,39 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/restorehelper"
"github.com/vmware-tanzu/velero/pkg/util/kube"
)
const (
defaultCPURequestLimit = "100m"
defaultMemRequestLimit = "128Mi"
defaultCommand = "/velero-restic-restore-helper"
defaultCommand = "/velero-restore-helper"
)
type ResticRestoreAction struct {
type PodVolumeRestoreAction struct {
logger logrus.FieldLogger
client corev1client.ConfigMapInterface
podVolumeBackupClient velerov1client.PodVolumeBackupInterface
}
func NewResticRestoreAction(logger logrus.FieldLogger, client corev1client.ConfigMapInterface, podVolumeBackupClient velerov1client.PodVolumeBackupInterface) *ResticRestoreAction {
return &ResticRestoreAction{
func NewPodVolumeRestoreAction(logger logrus.FieldLogger, client corev1client.ConfigMapInterface, podVolumeBackupClient velerov1client.PodVolumeBackupInterface) *PodVolumeRestoreAction {
return &PodVolumeRestoreAction{
logger: logger,
client: client,
podVolumeBackupClient: podVolumeBackupClient,
}
}
func (a *ResticRestoreAction) AppliesTo() (velero.ResourceSelector, error) {
func (a *PodVolumeRestoreAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"pods"},
}, nil
}
func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
a.logger.Info("Executing ResticRestoreAction")
defer a.logger.Info("Done executing ResticRestoreAction")
func (a *PodVolumeRestoreAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
a.logger.Info("Executing PodVolumeRestoreAction")
defer a.logger.Info("Done executing PodVolumeRestoreAction")
var pod corev1.Pod
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(input.Item.UnstructuredContent(), &pod); err != nil {
@ -98,16 +99,16 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu
}
volumeSnapshots := podvolume.GetVolumeBackupsForPod(podVolumeBackups, &pod, podFromBackup.Namespace)
if len(volumeSnapshots) == 0 {
log.Debug("No restic backups found for pod")
log.Debug("No pod volume backups found for pod")
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
}
log.Info("Restic backups for pod found")
log.Info("Pod volume backups for pod found")
// TODO we might want/need to get plugin config at the top of this method at some point; for now, wait
// until we know we're doing a restore before getting config.
log.Debugf("Getting plugin config")
config, err := getPluginConfig(common.PluginKindRestoreItemAction, "velero.io/restic", a.client)
config, err := getPluginConfig(common.PluginKindRestoreItemAction, "velero.io/pod-volume-restore", a.client)
if err != nil {
return nil, err
}
@ -146,7 +147,7 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu
log.Errorf("Using default securityContext values, couldn't parse securityContext requirements: %s.", err)
}
initContainerBuilder := newResticInitContainerBuilder(image, string(input.Restore.UID))
initContainerBuilder := newRestoreInitContainerBuilder(image, string(input.Restore.UID))
initContainerBuilder.Resources(&resourceReqs)
initContainerBuilder.SecurityContext(&securityContext)
@ -160,7 +161,7 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu
initContainerBuilder.Command(getCommand(log, config))
initContainer := *initContainerBuilder.Result()
if len(pod.Spec.InitContainers) == 0 || pod.Spec.InitContainers[0].Name != podvolume.InitContainer {
if len(pod.Spec.InitContainers) == 0 || (pod.Spec.InitContainers[0].Name != restorehelper.WaitInitContainer && pod.Spec.InitContainers[0].Name != restorehelper.WaitInitContainerLegacy) {
pod.Spec.InitContainers = append([]corev1.Container{initContainer}, pod.Spec.InitContainers...)
} else {
pod.Spec.InitContainers[0] = initContainer
@ -192,13 +193,13 @@ func getCommand(log logrus.FieldLogger, config *corev1.ConfigMap) []string {
func getImage(log logrus.FieldLogger, config *corev1.ConfigMap) string {
if config == nil {
log.Debug("No config found for plugin")
return veleroimage.DefaultResticRestoreHelperImage()
return veleroimage.DefaultRestoreHelperImage()
}
image := config.Data["image"]
if image == "" {
log.Debugf("No custom image configured")
return veleroimage.DefaultResticRestoreHelperImage()
return veleroimage.DefaultRestoreHelperImage()
}
log = log.WithField("image", image)
@ -206,7 +207,7 @@ func getImage(log logrus.FieldLogger, config *corev1.ConfigMap) string {
parts := strings.Split(image, "/")
if len(parts) == 1 {
defaultImage := veleroimage.DefaultResticRestoreHelperImage()
defaultImage := veleroimage.DefaultRestoreHelperImage()
// Image supplied without registry part
log.Infof("Plugin config contains image name without registry name. Using default init container image: %q", defaultImage)
return defaultImage
@ -264,7 +265,7 @@ func getSecurityContext(log logrus.FieldLogger, config *corev1.ConfigMap) (strin
func getPluginConfig(kind common.PluginKind, name string, client corev1client.ConfigMapInterface) (*corev1.ConfigMap, error) {
opts := metav1.ListOptions{
// velero.io/plugin-config: true
// velero.io/restic: RestoreItemAction
// velero.io/pod-volume-restore: RestoreItemAction
LabelSelector: fmt.Sprintf("velero.io/plugin-config,%s=%s", name, kind),
}
@ -288,8 +289,8 @@ func getPluginConfig(kind common.PluginKind, name string, client corev1client.Co
return &list.Items[0], nil
}
func newResticInitContainerBuilder(image, restoreUID string) *builder.ContainerBuilder {
return builder.ForContainer(podvolume.InitContainer, image).
func newRestoreInitContainerBuilder(image, restoreUID string) *builder.ContainerBuilder {
return builder.ForContainer(restorehelper.WaitInitContainer, image).
Args(restoreUID).
Env([]*corev1.EnvVar{
{

View File

@ -49,7 +49,7 @@ func TestGetImage(t *testing.T) {
}
}
defaultImage := veleroimage.DefaultResticRestoreHelperImage()
defaultImage := veleroimage.DefaultRestoreHelperImage()
tests := []struct {
name string
@ -110,8 +110,8 @@ func TestGetImage(t *testing.T) {
}
}
// TestResticRestoreActionExecute tests the restic restore item action plugin's Execute method.
func TestResticRestoreActionExecute(t *testing.T) {
// TestPodVolumeRestoreActionExecute tests the pod volume restore item action plugin's Execute method.
func TestPodVolumeRestoreActionExecute(t *testing.T) {
resourceReqs, _ := kube.ParseResourceRequirements(
defaultCPURequestLimit, defaultMemRequestLimit, // requests
defaultCPURequestLimit, defaultMemRequestLimit, // limits
@ -125,7 +125,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
veleroNs = "velero"
)
defaultResticRestoreHelperImage := veleroimage.DefaultResticRestoreHelperImage()
defaultRestoreHelperImage := veleroimage.DefaultRestoreHelperImage()
tests := []struct {
name string
@ -135,7 +135,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
want *corev1api.Pod
}{
{
name: "Restoring pod with no other initContainers adds the restic initContainer",
name: "Restoring pod with no other initContainers adds the restore initContainer",
pod: builder.ForPod("ns-1", "my-pod").ObjectMeta(
builder.WithAnnotations("snapshot.velero.io/myvol", "")).
Result(),
@ -143,14 +143,14 @@ func TestResticRestoreActionExecute(t *testing.T) {
ObjectMeta(
builder.WithAnnotations("snapshot.velero.io/myvol", "")).
InitContainers(
newResticInitContainerBuilder(defaultResticRestoreHelperImage, "").
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
Resources(&resourceReqs).
SecurityContext(&securityContext).
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
Command([]string{"/velero-restic-restore-helper"}).Result()).Result(),
Command([]string{"/velero-restore-helper"}).Result()).Result(),
},
{
name: "Restoring pod with other initContainers adds the restic initContainer as the first one",
name: "Restoring pod with other initContainers adds the restore initContainer as the first one",
pod: builder.ForPod("ns-1", "my-pod").
ObjectMeta(
builder.WithAnnotations("snapshot.velero.io/myvol", "")).
@ -160,16 +160,16 @@ func TestResticRestoreActionExecute(t *testing.T) {
ObjectMeta(
builder.WithAnnotations("snapshot.velero.io/myvol", "")).
InitContainers(
newResticInitContainerBuilder(defaultResticRestoreHelperImage, "").
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
Resources(&resourceReqs).
SecurityContext(&securityContext).
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
Command([]string{"/velero-restic-restore-helper"}).Result(),
Command([]string{"/velero-restore-helper"}).Result(),
builder.ForContainer("first-container", "").Result()).
Result(),
},
{
name: "Restoring pod with other initContainers adds the restic initContainer as the first one using PVB to identify the volumes and not annotations",
name: "Restoring pod with other initContainers adds the restore initContainer as the first one using PVB to identify the volumes and not annotations",
pod: builder.ForPod("ns-1", "my-pod").
Volumes(
builder.ForVolume("vol-1").PersistentVolumeClaimSource("pvc-1").Result(),
@ -203,16 +203,16 @@ func TestResticRestoreActionExecute(t *testing.T) {
ObjectMeta(
builder.WithAnnotations("snapshot.velero.io/not-used", "")).
InitContainers(
newResticInitContainerBuilder(defaultResticRestoreHelperImage, "").
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
Resources(&resourceReqs).
SecurityContext(&securityContext).
VolumeMounts(builder.ForVolumeMount("vol-1", "/restores/vol-1").Result(), builder.ForVolumeMount("vol-2", "/restores/vol-2").Result()).
Command([]string{"/velero-restic-restore-helper"}).Result(),
Command([]string{"/velero-restore-helper"}).Result(),
builder.ForContainer("first-container", "").Result()).
Result(),
},
{
name: "Restoring pod in another namespace adds the restic initContainer and uses the namespace of the backup pod for matching PVBs",
name: "Restoring pod in another namespace adds the restore initContainer and uses the namespace of the backup pod for matching PVBs",
pod: builder.ForPod("new-ns", "my-pod").
Volumes(
builder.ForVolume("vol-1").PersistentVolumeClaimSource("pvc-1").Result(),
@ -247,11 +247,11 @@ func TestResticRestoreActionExecute(t *testing.T) {
builder.ForVolume("vol-2").PersistentVolumeClaimSource("pvc-2").Result(),
).
InitContainers(
newResticInitContainerBuilder(defaultResticRestoreHelperImage, "").
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
Resources(&resourceReqs).
SecurityContext(&securityContext).
VolumeMounts(builder.ForVolumeMount("vol-1", "/restores/vol-1").Result(), builder.ForVolumeMount("vol-2", "/restores/vol-2").Result()).
Command([]string{"/velero-restic-restore-helper"}).Result()).
Command([]string{"/velero-restore-helper"}).Result()).
Result(),
},
}
@ -291,7 +291,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
Result(),
}
a := NewResticRestoreAction(
a := NewPodVolumeRestoreAction(
logrus.StandardLogger(),
clientset.CoreV1().ConfigMaps(veleroNs),
clientsetVelero.VeleroV1().PodVolumeBackups(veleroNs),

33
pkg/restorehelper/util.go Normal file
View File

@ -0,0 +1,33 @@
/*
Copyright 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 restorehelper
const (
// WaitInitContainer is the name of the init container added
// to workload pods to help with restores.
// If Velero needs to further process the volume data after PVC is
// provisioned, this init container is used to block Pod from running
// until the volume data is ready
WaitInitContainer = "restore-wait"
// This is the name of the init container added by pre-v1.10 for the same
// purpose with WaitInitContainer.
// For compatibility, we need to check it when restoring backups created by
// old releases. The pods backed up by old releases may contain this init container
// since the init container is not deleted after pod is restored.
WaitInitContainerLegacy = "restic-wait"
)

View File

@ -37,9 +37,9 @@ import (
const restoreProgressCheckInterval = 10 * time.Second
const backupProgressCheckInterval = 10 * time.Second
// Provider which is designed for one pod volumn to do the backup or restore
// Provider which is designed for one pod volume to do the backup or restore
type Provider interface {
// RunBackup which will do backup for one specific volumn and return snapshotID, isSnapshotEmpty, error
// RunBackup which will do backup for one specific volume and return snapshotID, isSnapshotEmpty, error
// updater is used for updating backup progress which implement by third-party
RunBackup(
ctx context.Context,
@ -47,7 +47,7 @@ type Provider interface {
tags map[string]string,
parentSnapshot string,
updater uploader.ProgressUpdater) (string, bool, error)
// RunRestore which will do restore for one specific volumn with given snapshot id and return error
// RunRestore which will do restore for one specific volume with given snapshot id and return error
// updater is used for updating backup progress which implement by third-party
RunRestore(
ctx context.Context,

View File

@ -53,7 +53,7 @@ VELERO_CLI ?=$$(pwd)/../../_output/bin/$(GOOS)/$(GOARCH)/velero
VELERO_IMAGE ?= velero/velero:main
VELERO_VERSION ?= $(VERSION)
PLUGINS ?=
RESTIC_HELPER_IMAGE ?=
RESTORE_HELPER_IMAGE ?=
#Released version only
UPGRADE_FROM_VELERO_VERSION ?= v1.7.1,v1.8.1
# UPGRADE_FROM_VELERO_CLI can has the same format(a list divided by comma) with UPGRADE_FROM_VELERO_VERSION
@ -115,7 +115,7 @@ run: ginkgo
-velero-image=$(VELERO_IMAGE) \
-plugins=$(PLUGINS) \
-velero-version=$(VELERO_VERSION) \
-restic-helper-image=$(RESTIC_HELPER_IMAGE) \
-restore-helper-image=$(RESTORE_HELPER_IMAGE) \
-upgrade-from-velero-cli=$(UPGRADE_FROM_VELERO_CLI) \
-upgrade-from-velero-version=$(UPGRADE_FROM_VELERO_VERSION) \
-migrate-from-velero-cli=$(MIGRATE_FROM_VELERO_CLI) \

View File

@ -55,7 +55,7 @@ func init() {
flag.StringVar(&VeleroCfg.Plugins, "plugins", "", "provider plugins to be tested.")
flag.StringVar(&VeleroCfg.AddBSLPlugins, "additional-bsl-plugins", "", "additional plugins to be tested.")
flag.StringVar(&VeleroCfg.VeleroVersion, "velero-version", "main", "image version for the velero server to be tested with.")
flag.StringVar(&VeleroCfg.ResticHelperImage, "restic-helper-image", "", "image for the velero restic restore helper to be tested.")
flag.StringVar(&VeleroCfg.RestoreHelperImage, "restore-helper-image", "", "image for the velero restore helper to be tested.")
flag.StringVar(&VeleroCfg.UpgradeFromVeleroCLI, "upgrade-from-velero-cli", "", "path to the pre-upgrade velero application to use.")
flag.StringVar(&VeleroCfg.UpgradeFromVeleroVersion, "upgrade-from-velero-version", "v1.7.1", "image for the pre-upgrade velero server to be tested.")
flag.StringVar(&VeleroCfg.MigrateFromVeleroCLI, "migrate-from-velero-cli", "", "path to the origin velero application to use.")

View File

@ -122,7 +122,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version)
if veleroCLI2Version.VeleroVersion != "self" {
fmt.Printf("Using default images address of Velero CLI %s\n", veleroCLI2Version.VeleroVersion)
OriginVeleroCfg.VeleroImage = ""
OriginVeleroCfg.ResticHelperImage = ""
OriginVeleroCfg.RestoreHelperImage = ""
OriginVeleroCfg.Plugins = ""
//TODO: Remove this once origin Velero version is 1.10 and upper
OriginVeleroCfg.UploaderType = ""

View File

@ -46,7 +46,7 @@ type VerleroConfig struct {
AdditionalBSLConfig string
AdditionalBSLCredentials string
RegistryCredentialFile string
ResticHelperImage string
RestoreHelperImage string
UpgradeFromVeleroVersion string
UpgradeFromVeleroCLI string
MigrateFromVeleroVersion string

View File

@ -104,13 +104,13 @@ func BackupUpgradeRestoreTest(useVolumeSnapshots bool, veleroCLI2Version VeleroC
VeleroCfg.GCFrequency = ""
By(fmt.Sprintf("Install the expected old version Velero (%s) for upgrade",
veleroCLI2Version.VeleroVersion), func() {
//Set VeleroImage and ResticHelperImage to blank
//VeleroImage and ResticHelperImage should be the default value in originalCli
//Set VeleroImage and RestoreHelperImage to blank
//VeleroImage and RestoreHelperImage should be the default value in originalCli
tmpCfgForOldVeleroInstall := VeleroCfg
tmpCfgForOldVeleroInstall.UpgradeFromVeleroVersion = veleroCLI2Version.VeleroVersion
tmpCfgForOldVeleroInstall.VeleroCLI = veleroCLI2Version.VeleroCLI
tmpCfgForOldVeleroInstall.VeleroImage = ""
tmpCfgForOldVeleroInstall.ResticHelperImage = ""
tmpCfgForOldVeleroInstall.RestoreHelperImage = ""
tmpCfgForOldVeleroInstall.Plugins = ""
tmpCfgForOldVeleroInstall.UploaderType = ""
tmpCfgForOldVeleroInstall.UseNodeAgent = false

View File

@ -46,7 +46,7 @@ import (
type installOptions struct {
*install.InstallOptions
RegistryCredentialFile string
ResticHelperImage string
RestoreHelperImage string
}
func VeleroInstall(ctx context.Context, veleroCfg *VerleroConfig, useVolumeSnapshots bool) error {
@ -100,7 +100,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VerleroConfig, useVolumeSnaps
err = installVeleroServer(ctx, veleroCfg.VeleroCLI, &installOptions{
InstallOptions: veleroInstallOptions,
RegistryCredentialFile: veleroCfg.RegistryCredentialFile,
ResticHelperImage: veleroCfg.ResticHelperImage,
RestoreHelperImage: veleroCfg.RestoreHelperImage,
})
if err != nil {
return errors.WithMessagef(err, "Failed to install Velero in the cluster")
@ -224,14 +224,14 @@ func installVeleroServer(ctx context.Context, cli string, options *installOption
args = append(args, fmt.Sprintf("--uploader-type=%v", options.UploaderType))
}
if err := createVelereResources(ctx, cli, namespace, args, options.RegistryCredentialFile, options.ResticHelperImage); err != nil {
if err := createVelereResources(ctx, cli, namespace, args, options.RegistryCredentialFile, options.RestoreHelperImage); err != nil {
return err
}
return waitVeleroReady(ctx, namespace, options.UseNodeAgent)
}
func createVelereResources(ctx context.Context, cli, namespace string, args []string, registryCredentialFile, resticHelperImage string) error {
func createVelereResources(ctx context.Context, cli, namespace string, args []string, registryCredentialFile, RestoreHelperImage string) error {
args = append(args, "--dry-run", "--output", "json", "--crds-only")
// get the CRD definitions
@ -276,7 +276,7 @@ func createVelereResources(ctx context.Context, cli, namespace string, args []st
return errors.Wrapf(err, "failed to unmarshal the resources: %s", string(stdout))
}
if err = patchResources(ctx, resources, namespace, registryCredentialFile, VeleroCfg.ResticHelperImage); err != nil {
if err = patchResources(ctx, resources, namespace, registryCredentialFile, VeleroCfg.RestoreHelperImage); err != nil {
return errors.Wrapf(err, "failed to patch resources")
}
@ -298,7 +298,7 @@ func createVelereResources(ctx context.Context, cli, namespace string, args []st
}
// patch the velero resources
func patchResources(ctx context.Context, resources *unstructured.UnstructuredList, namespace, registryCredentialFile, resticHelperImage string) error {
func patchResources(ctx context.Context, resources *unstructured.UnstructuredList, namespace, registryCredentialFile, RestoreHelperImage string) error {
// apply the image pull secret to avoid the image pull limit of Docker Hub
if len(registryCredentialFile) > 0 {
credential, err := ioutil.ReadFile(registryCredentialFile)
@ -342,7 +342,7 @@ func patchResources(ctx context.Context, resources *unstructured.UnstructuredLis
}
// customize the restic restore helper image
if len(VeleroCfg.ResticHelperImage) > 0 {
if len(VeleroCfg.RestoreHelperImage) > 0 {
restoreActionConfig := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
@ -357,7 +357,7 @@ func patchResources(ctx context.Context, resources *unstructured.UnstructuredLis
},
},
Data: map[string]string{
"image": VeleroCfg.ResticHelperImage,
"image": VeleroCfg.RestoreHelperImage,
},
}