Merge branch 'main' of https://github.com/qiuming-best/velero into uploader-restic
commit
ed71e65486
|
@ -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/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
@ -126,7 +126,7 @@ func (i *InitContainerRestoreHookHandler) HandleRestoreHooks(
|
|||
// 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
|
||||
// it before running any other init container.
|
||||
if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == restic.InitContainer {
|
||||
if len(pod.Spec.InitContainers) > 0 && pod.Spec.InitContainers[0].Name == podvolume.InitContainer {
|
||||
initContainers = append(initContainers, pod.Spec.InitContainers[0])
|
||||
pod.Spec.InitContainers = pod.Spec.InitContainers[1:]
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd"
|
||||
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
)
|
||||
|
||||
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||
|
@ -69,7 +69,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
|
||||
first := true
|
||||
for _, restore := range restores.Items {
|
||||
opts := restic.NewPodVolumeRestoreListOptions(restore.Name)
|
||||
opts := newPodVolumeRestoreListOptions(restore.Name)
|
||||
podvolumeRestoreList, err := veleroClient.VeleroV1().PodVolumeRestores(f.Namespace()).List(context.TODO(), opts)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err)
|
||||
|
@ -94,3 +94,11 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
|||
|
||||
return c
|
||||
}
|
||||
|
||||
// newPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to
|
||||
// find PodVolumeRestores for the restore identified by name.
|
||||
func newPodVolumeRestoreListOptions(name string) metav1.ListOptions {
|
||||
return metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.RestoreNameLabel, label.GetValidName(name)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,9 @@ const (
|
|||
// defaultCredentialsDirectory is the path on disk where credential
|
||||
// files will be written to
|
||||
defaultCredentialsDirectory = "/tmp/credentials"
|
||||
|
||||
// daemonSet is the name of the Velero restic daemonset.
|
||||
daemonSet = "restic"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
|
@ -529,7 +532,7 @@ var defaultRestorePriorities = []string{
|
|||
|
||||
func (s *server) initRestic() error {
|
||||
// warn if restic daemonset does not exist
|
||||
if _, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get(s.ctx, restic.DaemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) {
|
||||
if _, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get(s.ctx, daemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) {
|
||||
s.logger.Warn("Velero restic daemonset not found; restic backups/restores will not work until it's created")
|
||||
} else if err != nil {
|
||||
s.logger.WithError(errors.WithStack(err)).Warn("Error checking for existence of velero restic daemonset")
|
||||
|
|
|
@ -41,7 +41,6 @@ import (
|
|||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
|
||||
|
@ -440,7 +439,7 @@ func (r *backupDeletionReconciler) deleteResticSnapshots(ctx context.Context, ba
|
|||
return nil
|
||||
}
|
||||
|
||||
snapshots, err := restic.GetSnapshotsInBackup(ctx, backup, r.Client)
|
||||
snapshots, err := getSnapshotsInBackup(ctx, backup, r.Client)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
@ -491,3 +490,33 @@ func (r *backupDeletionReconciler) patchBackup(ctx context.Context, backup *vele
|
|||
}
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
// getSnapshotsInBackup returns a list of all restic snapshot ids associated with
|
||||
// a given Velero backup.
|
||||
func getSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) ([]repository.SnapshotIdentifier, error) {
|
||||
podVolumeBackups := &velerov1api.PodVolumeBackupList{}
|
||||
options := &client.ListOptions{
|
||||
LabelSelector: labels.Set(map[string]string{
|
||||
velerov1api.BackupNameLabel: label.GetValidName(backup.Name),
|
||||
}).AsSelector(),
|
||||
}
|
||||
|
||||
err := kbClient.List(ctx, podVolumeBackups, options)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var res []repository.SnapshotIdentifier
|
||||
for _, item := range podVolumeBackups.Items {
|
||||
if item.Status.SnapshotID == "" {
|
||||
continue
|
||||
}
|
||||
res = append(res, repository.SnapshotIdentifier{
|
||||
VolumeNamespace: item.Spec.Pod.Namespace,
|
||||
BackupStorageLocation: backup.Spec.StorageLocation,
|
||||
SnapshotID: item.Status.SnapshotID,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package controller
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
@ -32,6 +33,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -52,6 +54,7 @@ import (
|
|||
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
|
@ -692,3 +695,172 @@ func TestBackupDeletionControllerReconcile(t *testing.T) {
|
|||
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSnapshotsInBackup(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
podVolumeBackups []velerov1api.PodVolumeBackup
|
||||
expected []repository.SnapshotIdentifier
|
||||
longBackupNameEnabled bool
|
||||
}{
|
||||
{
|
||||
name: "no pod volume backups",
|
||||
podVolumeBackups: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no pod volume backups with matching label",
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "some pod volume backups with matching label",
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
|
||||
},
|
||||
},
|
||||
expected: []repository.SnapshotIdentifier{
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-3",
|
||||
},
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-4",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some pod volume backups with matching label and backup name greater than 63 chars",
|
||||
longBackupNameEnabled: true,
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "the-really-long-backup-name-that-is-much-more-than-63-cha6ca4bc"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
|
||||
},
|
||||
},
|
||||
expected: []repository.SnapshotIdentifier{
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
clientBuilder = velerotest.NewFakeControllerRuntimeClientBuilder(t)
|
||||
veleroBackup = &velerov1api.Backup{}
|
||||
)
|
||||
|
||||
veleroBackup.Name = "backup-1"
|
||||
|
||||
if test.longBackupNameEnabled {
|
||||
veleroBackup.Name = "the-really-long-backup-name-that-is-much-more-than-63-characters"
|
||||
}
|
||||
clientBuilder.WithLists(&velerov1api.PodVolumeBackupList{
|
||||
Items: test.podVolumeBackups,
|
||||
})
|
||||
|
||||
res, err := getSnapshotsInBackup(context.TODO(), veleroBackup, clientBuilder.Build())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// sort to ensure good compare of slices
|
||||
less := func(snapshots []repository.SnapshotIdentifier) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
if snapshots[i].VolumeNamespace == snapshots[j].VolumeNamespace {
|
||||
return snapshots[i].SnapshotID < snapshots[j].SnapshotID
|
||||
}
|
||||
return snapshots[i].VolumeNamespace < snapshots[j].VolumeNamespace
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sort.Slice(test.expected, less(test.expected))
|
||||
sort.Slice(res, less(res))
|
||||
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
repokey "github.com/vmware-tanzu/velero/pkg/repository/keys"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
|
@ -108,7 +109,7 @@ func (c *PodVolumeRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Req
|
|||
resticInitContainerIndex := getResticInitContainerIndex(pod)
|
||||
if resticInitContainerIndex > 0 {
|
||||
log.Warnf(`Init containers before the %s container may cause issues
|
||||
if they interfere with volumes being restored: %s index %d`, restic.InitContainer, restic.InitContainer, resticInitContainerIndex)
|
||||
if they interfere with volumes being restored: %s index %d`, podvolume.InitContainer, podvolume.InitContainer, resticInitContainerIndex)
|
||||
}
|
||||
|
||||
log.Info("Restore starting")
|
||||
|
@ -218,7 +219,7 @@ func isResticInitContainerRunning(pod *corev1api.Pod) bool {
|
|||
func getResticInitContainerIndex(pod *corev1api.Pod) int {
|
||||
// Restic wait container can be anywhere in the list of init containers so locate it.
|
||||
for i, initContainer := range pod.Spec.InitContainers {
|
||||
if initContainer.Name == restic.InitContainer {
|
||||
if initContainer.Name == podvolume.InitContainer {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
|
@ -120,7 +120,7 @@ func TestShouldProcess(t *testing.T) {
|
|||
NodeName: controllerNode,
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -160,7 +160,7 @@ func TestShouldProcess(t *testing.T) {
|
|||
NodeName: controllerNode,
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -260,7 +260,7 @@ func TestIsResticContainerRunning(t *testing.T) {
|
|||
Name: "non-restic-init",
|
||||
},
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -291,7 +291,7 @@ func TestIsResticContainerRunning(t *testing.T) {
|
|||
Spec: corev1api.PodSpec{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
{
|
||||
Name: "non-restic-init",
|
||||
|
@ -323,7 +323,7 @@ func TestIsResticContainerRunning(t *testing.T) {
|
|||
Spec: corev1api.PodSpec{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
{
|
||||
Name: "non-restic-init",
|
||||
|
@ -357,7 +357,7 @@ func TestIsResticContainerRunning(t *testing.T) {
|
|||
Spec: corev1api.PodSpec{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -422,7 +422,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
|
|||
Name: "non-restic-init",
|
||||
},
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -439,7 +439,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
|
|||
Spec: corev1api.PodSpec{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
{
|
||||
Name: "non-restic-init",
|
||||
|
@ -459,7 +459,7 @@ func TestGetResticInitContainerIndex(t *testing.T) {
|
|||
Spec: corev1api.PodSpec{
|
||||
InitContainers: []corev1api.Container{
|
||||
{
|
||||
Name: restic.InitContainer,
|
||||
Name: podvolume.InitContainer,
|
||||
},
|
||||
{
|
||||
Name: "non-restic-init",
|
||||
|
|
|
@ -42,6 +42,10 @@ const (
|
|||
// VolumesToExcludeAnnotation is the annotation on a pod whose mounted volumes
|
||||
// should be excluded from restic 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"
|
||||
)
|
||||
|
||||
// GetVolumeBackupsForPod returns a map, of volume name -> snapshot id,
|
||||
|
|
|
@ -27,10 +27,24 @@ import (
|
|||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository/provider"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// SnapshotIdentifier uniquely identifies a restic snapshot
|
||||
// taken by Velero.
|
||||
type SnapshotIdentifier struct {
|
||||
// VolumeNamespace is the namespace of the pod/volume that
|
||||
// the restic snapshot is for.
|
||||
VolumeNamespace string
|
||||
|
||||
// BackupStorageLocation is the backup's storage location
|
||||
// name.
|
||||
BackupStorageLocation string
|
||||
|
||||
// SnapshotID is the short ID of the restic snapshot.
|
||||
SnapshotID string
|
||||
}
|
||||
|
||||
// Manager manages backup repositories.
|
||||
type Manager interface {
|
||||
// InitRepo initializes a repo with the specified name and identifier.
|
||||
|
@ -50,7 +64,7 @@ type Manager interface {
|
|||
|
||||
// Forget removes a snapshot from the list of
|
||||
// available snapshots in a repo.
|
||||
Forget(context.Context, restic.SnapshotIdentifier) error
|
||||
Forget(context.Context, SnapshotIdentifier) error
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
|
@ -147,7 +161,7 @@ func (m *manager) UnlockRepo(repo *velerov1api.BackupRepository) error {
|
|||
return prd.EnsureUnlockRepo(context.Background(), param)
|
||||
}
|
||||
|
||||
func (m *manager) Forget(ctx context.Context, snapshot restic.SnapshotIdentifier) error {
|
||||
func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error {
|
||||
repo, err := m.repoEnsurer.EnsureRepo(ctx, m.namespace, snapshot.VolumeNamespace, snapshot.BackupStorageLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -20,10 +20,10 @@ import (
|
|||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
restic "github.com/vmware-tanzu/velero/pkg/restic"
|
||||
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
)
|
||||
|
||||
// RepositoryManager is an autogenerated mock type for the RepositoryManager type
|
||||
|
@ -46,11 +46,11 @@ func (_m *RepositoryManager) ConnectToRepo(repo *v1.BackupRepository) error {
|
|||
}
|
||||
|
||||
// Forget provides a mock function with given fields: _a0, _a1
|
||||
func (_m *RepositoryManager) Forget(_a0 context.Context, _a1 restic.SnapshotIdentifier) error {
|
||||
func (_m *RepositoryManager) Forget(_a0 context.Context, _a1 repository.SnapshotIdentifier) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, restic.SnapshotIdentifier) error); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, repository.SnapshotIdentifier) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package restic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -25,24 +24,14 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
repoconfig "github.com/vmware-tanzu/velero/pkg/repository/config"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
const (
|
||||
// DaemonSet is the name of the Velero restic daemonset.
|
||||
DaemonSet = "restic"
|
||||
|
||||
// InitContainer is the name of the init container added
|
||||
// to workload pods to help with restores.
|
||||
InitContainer = "restic-wait"
|
||||
|
||||
// DefaultMaintenanceFrequency is the default time interval
|
||||
// at which restic prune is run.
|
||||
|
@ -61,51 +50,6 @@ const (
|
|||
resticInsecureTLSFlag = "--insecure-tls"
|
||||
)
|
||||
|
||||
// SnapshotIdentifier uniquely identifies a restic snapshot
|
||||
// taken by Velero.
|
||||
type SnapshotIdentifier struct {
|
||||
// VolumeNamespace is the namespace of the pod/volume that
|
||||
// the restic snapshot is for.
|
||||
VolumeNamespace string
|
||||
|
||||
// BackupStorageLocation is the backup's storage location
|
||||
// name.
|
||||
BackupStorageLocation string
|
||||
|
||||
// SnapshotID is the short ID of the restic snapshot.
|
||||
SnapshotID string
|
||||
}
|
||||
|
||||
// GetSnapshotsInBackup returns a list of all restic snapshot ids associated with
|
||||
// a given Velero backup.
|
||||
func GetSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) ([]SnapshotIdentifier, error) {
|
||||
podVolumeBackups := &velerov1api.PodVolumeBackupList{}
|
||||
options := &client.ListOptions{
|
||||
LabelSelector: labels.Set(map[string]string{
|
||||
velerov1api.BackupNameLabel: label.GetValidName(backup.Name),
|
||||
}).AsSelector(),
|
||||
}
|
||||
|
||||
err := kbClient.List(ctx, podVolumeBackups, options)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var res []SnapshotIdentifier
|
||||
for _, item := range podVolumeBackups.Items {
|
||||
if item.Status.SnapshotID == "" {
|
||||
continue
|
||||
}
|
||||
res = append(res, SnapshotIdentifier{
|
||||
VolumeNamespace: item.Spec.Pod.Namespace,
|
||||
BackupStorageLocation: backup.Spec.StorageLocation,
|
||||
SnapshotID: item.Status.SnapshotID,
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TempCACertFile creates a temp file containing a CA bundle
|
||||
// and returns its path. The caller should generally call os.Remove()
|
||||
// to remove the file when done with it.
|
||||
|
@ -131,14 +75,6 @@ func TempCACertFile(caCert []byte, bsl string, fs filesystem.Interface) (string,
|
|||
return name, nil
|
||||
}
|
||||
|
||||
// NewPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to
|
||||
// find PodVolumeRestores for the restore identified by name.
|
||||
func NewPodVolumeRestoreListOptions(name string) metav1.ListOptions {
|
||||
return metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.RestoreNameLabel, label.GetValidName(name)),
|
||||
}
|
||||
}
|
||||
|
||||
// CmdEnv returns a list of environment variables (in the format var=val) that
|
||||
// should be used when running a restic command for a particular backend provider.
|
||||
// This list is the current environment, plus any provider-specific variables restic needs.
|
||||
|
|
|
@ -17,190 +17,17 @@ limitations under the License.
|
|||
package restic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
func TestGetSnapshotsInBackup(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
podVolumeBackups []velerov1api.PodVolumeBackup
|
||||
expected []SnapshotIdentifier
|
||||
longBackupNameEnabled bool
|
||||
}{
|
||||
{
|
||||
name: "no pod volume backups",
|
||||
podVolumeBackups: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no pod volume backups with matching label",
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "some pod volume backups with matching label",
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
|
||||
},
|
||||
},
|
||||
expected: []SnapshotIdentifier{
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-3",
|
||||
},
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-4",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some pod volume backups with matching label and backup name greater than 63 chars",
|
||||
longBackupNameEnabled: true,
|
||||
podVolumeBackups: []velerov1api.PodVolumeBackup{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-1"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", Labels: map[string]string{velerov1api.BackupNameLabel: "non-matching-backup-2"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-2", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "the-really-long-backup-name-that-is-much-more-than-63-cha6ca4bc"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-3"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed-pvb-2", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-1"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-4"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "incomplete-or-failed-pvb", Labels: map[string]string{velerov1api.BackupNameLabel: "backup-1"}},
|
||||
Spec: velerov1api.PodVolumeBackupSpec{
|
||||
Pod: corev1api.ObjectReference{Name: "pod-1", Namespace: "ns-2"},
|
||||
},
|
||||
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
|
||||
},
|
||||
},
|
||||
expected: []SnapshotIdentifier{
|
||||
{
|
||||
VolumeNamespace: "ns-1",
|
||||
SnapshotID: "snap-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
clientBuilder = velerotest.NewFakeControllerRuntimeClientBuilder(t)
|
||||
veleroBackup = &velerov1api.Backup{}
|
||||
)
|
||||
|
||||
veleroBackup.Name = "backup-1"
|
||||
|
||||
if test.longBackupNameEnabled {
|
||||
veleroBackup.Name = "the-really-long-backup-name-that-is-much-more-than-63-characters"
|
||||
}
|
||||
clientBuilder.WithLists(&velerov1api.PodVolumeBackupList{
|
||||
Items: test.podVolumeBackups,
|
||||
})
|
||||
|
||||
res, err := GetSnapshotsInBackup(context.TODO(), veleroBackup, clientBuilder.Build())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// sort to ensure good compare of slices
|
||||
less := func(snapshots []SnapshotIdentifier) func(i, j int) bool {
|
||||
return func(i, j int) bool {
|
||||
if snapshots[i].VolumeNamespace == snapshots[j].VolumeNamespace {
|
||||
return snapshots[i].SnapshotID < snapshots[j].SnapshotID
|
||||
}
|
||||
return snapshots[i].VolumeNamespace < snapshots[j].VolumeNamespace
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sort.Slice(test.expected, less(test.expected))
|
||||
sort.Slice(res, less(res))
|
||||
|
||||
assert.Equal(t, test.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTempCACertFile(t *testing.T) {
|
||||
var (
|
||||
fs = velerotest.NewFakeFileSystem()
|
||||
|
|
|
@ -37,7 +37,6 @@ import (
|
|||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
)
|
||||
|
||||
|
@ -161,7 +160,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 != restic.InitContainer {
|
||||
if len(pod.Spec.InitContainers) == 0 || pod.Spec.InitContainers[0].Name != podvolume.InitContainer {
|
||||
pod.Spec.InitContainers = append([]corev1.Container{initContainer}, pod.Spec.InitContainers...)
|
||||
} else {
|
||||
pod.Spec.InitContainers[0] = initContainer
|
||||
|
@ -290,7 +289,7 @@ func getPluginConfig(kind framework.PluginKind, name string, client corev1client
|
|||
}
|
||||
|
||||
func newResticInitContainerBuilder(image, restoreUID string) *builder.ContainerBuilder {
|
||||
return builder.ForContainer(restic.InitContainer, image).
|
||||
return builder.ForContainer(podvolume.InitContainer, image).
|
||||
Args(restoreUID).
|
||||
Env([]*corev1.EnvVar{
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue