mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-13 03:24:39 +00:00
Merge branch 'main' into report-incremental-fallback
This commit is contained in:
@@ -136,6 +136,7 @@ func (p *pvcBackupItemAction) getVolumeHelperWithCache(backup *velerov1api.Backu
|
||||
p.crClient,
|
||||
p.log,
|
||||
p.pvcPodCache,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create VolumeHelper")
|
||||
|
||||
@@ -485,6 +485,8 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
|
||||
return err
|
||||
}
|
||||
|
||||
pvcMustInclusionTracker := NewPVCMustInclusionTracker(backupRequest.MustIncludeAdditionalItemPVCs)
|
||||
|
||||
volumeHelperImpl, err := volumehelper.NewVolumeHelperImplWithNamespaces(
|
||||
backupRequest.ResPolicies,
|
||||
backupRequest.Spec.SnapshotVolumes,
|
||||
@@ -493,6 +495,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(
|
||||
boolptr.IsSetToTrue(backupRequest.Spec.DefaultVolumesToFsBackup),
|
||||
!backupRequest.ResourceIncludesExcludes.ShouldInclude(kuberesource.PersistentVolumeClaims.String()),
|
||||
namespaces,
|
||||
pvcMustInclusionTracker,
|
||||
)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to build PVC-to-Pod cache for volume policy lookups")
|
||||
|
||||
@@ -245,6 +245,7 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti
|
||||
// where it's been backed up from another pod), since we don't need >1 backup per PVC.
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
shouldDoFSBackup, err := ib.volumeHelperImpl.ShouldPerformFSBackup(volume, *pod)
|
||||
|
||||
if err != nil {
|
||||
backupErrs = append(backupErrs, errors.WithStack(err))
|
||||
}
|
||||
@@ -480,6 +481,26 @@ func (ib *itemBackupper) executeActions(
|
||||
delete(u.GetAnnotations(), velerov1api.MustIncludeAdditionalItemAnnotation)
|
||||
obj = u
|
||||
|
||||
// If the BIA specifies that additional items must be included, we track any PVCs returned as additional items.
|
||||
// This tracking is necessary because the FSB (File System Backup) evaluation for a Pod
|
||||
// happens before its PVCs are processed. By tracking these explicitly included PVCs here,
|
||||
// the FSB logic can correctly determine that the PVC will be backed up and therefore
|
||||
// a PodVolumeBackup should be created.
|
||||
// We track this unconditionally when mustInclude is true, because fine-grained backup filters
|
||||
// might exclude a PVC even if it's globally included, but mustInclude overrides those filters.
|
||||
if mustInclude && ib.backupRequest.MustIncludeAdditionalItemPVCs != nil {
|
||||
for _, additionalItem := range additionalItemIdentifiers {
|
||||
if additionalItem.GroupResource == kuberesource.PersistentVolumeClaims {
|
||||
key := itemKey{
|
||||
resource: additionalItem.GroupResource.String(),
|
||||
namespace: additionalItem.Namespace,
|
||||
name: additionalItem.Name,
|
||||
}
|
||||
ib.backupRequest.MustIncludeAdditionalItemPVCs.AddItem(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If async plugin started async operation, add it to the ItemOperations list
|
||||
// ignore during finalize phase
|
||||
if operationID != "" {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
vhutil "github.com/vmware-tanzu/velero/pkg/util/volumehelper"
|
||||
)
|
||||
|
||||
// pvcMustInclusionTracker provides read-only checks for whether a PVC is included
|
||||
// in the backup as BIA's additionalItems through annotation
|
||||
// backup.velero.io/must-include-additional-items.
|
||||
type pvcMustInclusionTracker struct {
|
||||
mustInclude *backedUpItemsMap
|
||||
}
|
||||
|
||||
func NewPVCMustInclusionTracker(mustInclude *backedUpItemsMap) vhutil.PVCMustInclusionTracker {
|
||||
return &pvcMustInclusionTracker{
|
||||
mustInclude: mustInclude,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pvcMustInclusionTracker) IsPVCIncluded(namespace, pvcName string) bool {
|
||||
pvcKey := itemKey{
|
||||
resource: kuberesource.PersistentVolumeClaims.String(),
|
||||
namespace: namespace,
|
||||
name: pvcName,
|
||||
}
|
||||
|
||||
// 1. If the PVC was explicitly forced into the backup by a BIA, it will be backed up.
|
||||
if p.mustInclude != nil && p.mustInclude.Has(pvcKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 backup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
)
|
||||
|
||||
func TestPVCMustInclusionTracker_IsPVCIncluded(t *testing.T) {
|
||||
mustIncludeMap := NewBackedUpItemsMap()
|
||||
|
||||
tracker := NewPVCMustInclusionTracker(mustIncludeMap)
|
||||
|
||||
pvcKey1 := itemKey{
|
||||
resource: kuberesource.PersistentVolumeClaims.String(),
|
||||
namespace: "ns-1",
|
||||
name: "pvc-1",
|
||||
}
|
||||
|
||||
// Initially neither PVC is included
|
||||
assert.False(t, tracker.IsPVCIncluded("ns-1", "pvc-1"))
|
||||
|
||||
// Add pvc-1 to mustInclude map
|
||||
mustIncludeMap.AddItem(pvcKey1)
|
||||
assert.True(t, tracker.IsPVCIncluded("ns-1", "pvc-1"))
|
||||
|
||||
// Check a PVC not in any map
|
||||
assert.False(t, tracker.IsPVCIncluded("ns-1", "pvc-2"))
|
||||
}
|
||||
+10
-5
@@ -83,11 +83,16 @@ type Request struct {
|
||||
VolumeSnapshots SynchronizedVSList
|
||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
BackedUpItems *backedUpItemsMap
|
||||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
SkippedPVTracker *skipPVTracker
|
||||
VolumesInformation volume.BackupVolumesInformation
|
||||
WorkerPool *ItemBlockWorkerPool
|
||||
// MustIncludeAdditionalItemPVCs keeps track of PVCs that are returned as additionalItems
|
||||
// by a BackupItemAction plugin with the must-include annotation. This is specifically
|
||||
// used to ensure PodVolumeBackups (FSB) are created for these PVCs even when PVCs are
|
||||
// excluded by global or fine-grained backup resource filters.
|
||||
MustIncludeAdditionalItemPVCs *backedUpItemsMap
|
||||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
SkippedPVTracker *skipPVTracker
|
||||
VolumesInformation volume.BackupVolumesInformation
|
||||
WorkerPool *ItemBlockWorkerPool
|
||||
|
||||
// ClusterScopedFilterMap holds resolved global filters for cluster-scoped resources.
|
||||
// Key is the resolved group-resource string.
|
||||
|
||||
@@ -395,10 +395,11 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
|
||||
|
||||
func (b *backupReconciler) prepareBackupRequest(ctx context.Context, backup *velerov1api.Backup, logger logrus.FieldLogger) *pkgbackup.Request {
|
||||
request := &pkgbackup.Request{
|
||||
Backup: backup.DeepCopy(), // don't modify items in the cache
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
|
||||
WorkerPool: pkgbackup.StartItemBlockWorkerPool(ctx, b.itemBlockWorkerCount, logger),
|
||||
Backup: backup.DeepCopy(), // don't modify items in the cache
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
|
||||
MustIncludeAdditionalItemPVCs: pkgbackup.NewBackedUpItemsMap(),
|
||||
WorkerPool: pkgbackup.StartItemBlockWorkerPool(ctx, b.itemBlockWorkerCount, logger),
|
||||
}
|
||||
request.VolumesInformation.Init()
|
||||
|
||||
|
||||
@@ -158,10 +158,11 @@ func (r *backupFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
}
|
||||
|
||||
backupRequest := &pkgbackup.Request{
|
||||
Backup: backup,
|
||||
StorageLocation: location,
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
|
||||
Backup: backup,
|
||||
StorageLocation: location,
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
BackedUpItems: pkgbackup.NewBackedUpItemsMap(),
|
||||
MustIncludeAdditionalItemPVCs: pkgbackup.NewBackedUpItemsMap(),
|
||||
}
|
||||
var outBackupFile *os.File
|
||||
if len(operations) > 0 {
|
||||
|
||||
@@ -496,6 +496,7 @@ func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1api.
|
||||
backupPodName := ownerObject.Name
|
||||
backupPVCName := ownerObject.Name
|
||||
backupVSName := ownerObject.Name
|
||||
backupVSCName := ownerObject.Name
|
||||
|
||||
kube.DeletePodIfAny(ctx, e.kubeClient.CoreV1(), backupPodName, ownerObject.Namespace, e.log)
|
||||
kube.DeletePVAndPVCIfAny(ctx, e.kubeClient.CoreV1(), backupPVCName, ownerObject.Namespace, cleanUpTimeout, e.log)
|
||||
@@ -507,6 +508,13 @@ func (e *csiSnapshotExposer) CleanUp(ctx context.Context, ownerObject corev1api.
|
||||
|
||||
csi.DeleteVolumeSnapshotIfAny(ctx, e.csiSnapshotClient, backupVSName, ownerObject.Namespace, e.log)
|
||||
csi.DeleteVolumeSnapshotIfAny(ctx, e.csiSnapshotClient, vsName, sourceNamespace, e.log)
|
||||
|
||||
// The backup VSC is created by Velero as an internal handle to the source
|
||||
// snapshot. Deleting the backup VS above only cascades to it when its
|
||||
// deletion policy is Delete, so remove it explicitly to avoid leaking the
|
||||
// object under a Retain policy. Deleting a Retain VSC drops only the API
|
||||
// object and leaves the underlying snapshot intact.
|
||||
csi.DeleteVolumeSnapshotContentIfAny(ctx, e.csiSnapshotClient, backupVSCName, e.log)
|
||||
}
|
||||
|
||||
func getVolumeModeByAccessMode(accessMode string, dataMover string) (corev1api.PersistentVolumeMode, error) {
|
||||
@@ -571,7 +579,21 @@ func (e *csiSnapshotExposer) createBackupVSC(ctx context.Context, ownerObject co
|
||||
Source: snapshotv1api.VolumeSnapshotContentSource{
|
||||
SnapshotHandle: snapshotVSC.Status.SnapshotHandle,
|
||||
},
|
||||
DeletionPolicy: snapshotv1api.VolumeSnapshotContentDelete,
|
||||
// The backup VSC is statically provisioned against the same
|
||||
// snapshot handle as the source VSC, so both objects refer to one
|
||||
// physical snapshot. Inherit the source's deletion policy instead
|
||||
// of forcing Delete, otherwise a user who configured Retain on the
|
||||
// VolumeSnapshotClass still loses the snapshot when the backup VSC
|
||||
// is cleaned up.
|
||||
//
|
||||
// For Case 2 storages per the design (design/block-data-mover/block-data-mover.md,
|
||||
// e.g. Ceph RBD), inheriting Retain is not just an option but a requirement for
|
||||
// incrementals to work at all: rbd snap diff needs the base and target snapshots
|
||||
// in the same clone chain, so Delete destroys the base as soon as this backup
|
||||
// completes. The next incremental's delta query then fails and degrades to an
|
||||
// allocated-blocks backup (see the CBT tier ladder) or, without that fix, a full
|
||||
// whole-device transfer.
|
||||
DeletionPolicy: snapshotVSC.Spec.DeletionPolicy,
|
||||
Driver: snapshotVSC.Spec.Driver,
|
||||
VolumeSnapshotClassName: snapshotVSC.Spec.VolumeSnapshotClassName,
|
||||
},
|
||||
|
||||
@@ -2537,3 +2537,62 @@ func TestCleanUp_SecretsAndConfigMaps(t *testing.T) {
|
||||
_, err = fakeKubeClient.CoreV1().Secrets("velero").Get(t.Context(), "other-secret", metav1.GetOptions{})
|
||||
assert.NoError(t, err, "unrelated secret should not be deleted")
|
||||
}
|
||||
|
||||
func TestCreateBackupVSCDeletionPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sourcePolicy snapshotv1api.DeletionPolicy
|
||||
expectedPolicy snapshotv1api.DeletionPolicy
|
||||
}{
|
||||
{
|
||||
name: "Delete policy is inherited",
|
||||
sourcePolicy: snapshotv1api.VolumeSnapshotContentDelete,
|
||||
expectedPolicy: snapshotv1api.VolumeSnapshotContentDelete,
|
||||
},
|
||||
{
|
||||
// The backup VSC points at the same snapshot handle as the source
|
||||
// VSC, so forcing Delete here would destroy a snapshot the user
|
||||
// asked to keep.
|
||||
name: "Retain policy is inherited",
|
||||
sourcePolicy: snapshotv1api.VolumeSnapshotContentRetain,
|
||||
expectedPolicy: snapshotv1api.VolumeSnapshotContentRetain,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
handle := "fake-snapshot-handle"
|
||||
className := "fake-snapshot-class"
|
||||
|
||||
sourceVSC := &snapshotv1api.VolumeSnapshotContent{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "source-vsc"},
|
||||
Spec: snapshotv1api.VolumeSnapshotContentSpec{
|
||||
DeletionPolicy: test.sourcePolicy,
|
||||
Driver: "fake-driver",
|
||||
VolumeSnapshotClassName: &className,
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotContentStatus{
|
||||
SnapshotHandle: &handle,
|
||||
},
|
||||
}
|
||||
|
||||
exposer := csiSnapshotExposer{
|
||||
csiSnapshotClient: snapshotFake.NewSimpleClientset().SnapshotV1(),
|
||||
log: velerotest.NewLogger(),
|
||||
}
|
||||
|
||||
ownerObject := corev1api.ObjectReference{
|
||||
Name: "fake-du",
|
||||
Namespace: "velero",
|
||||
}
|
||||
vs := &snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "fake-du", Namespace: "velero"},
|
||||
}
|
||||
|
||||
backupVSC, err := exposer.createBackupVSC(t.Context(), ownerObject, sourceVSC, vs)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedPolicy, backupVSC.Spec.DeletionPolicy)
|
||||
assert.Equal(t, handle, *backupVSC.Spec.Source.SnapshotHandle)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ func ShouldPerformSnapshotWithVolumeHelper(
|
||||
crClient,
|
||||
boolptr.IsSetToTrue(backup.Spec.DefaultVolumesToFsBackup),
|
||||
true,
|
||||
nil,
|
||||
)
|
||||
|
||||
return volumeHelperImpl.ShouldPerformSnapshot(unstructured, groupResource)
|
||||
@@ -111,6 +112,7 @@ func NewVolumeHelperWithNamespaces(
|
||||
defaultVolumesToFSBackup bool,
|
||||
backupExcludePVC bool,
|
||||
namespaces []string,
|
||||
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
|
||||
) (vhutil.VolumeHelper, error) {
|
||||
return volumehelper.NewVolumeHelperImplWithNamespaces(
|
||||
volumePolicy,
|
||||
@@ -120,6 +122,7 @@ func NewVolumeHelperWithNamespaces(
|
||||
defaultVolumesToFSBackup,
|
||||
backupExcludePVC,
|
||||
namespaces,
|
||||
pvcMustInclusionTracker,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,11 +134,13 @@ func NewVolumeHelperWithCache(
|
||||
client crclient.Client,
|
||||
logger logrus.FieldLogger,
|
||||
pvcPodCache *podvolumeutil.PVCPodCache,
|
||||
pvcMustInclusionTracker vhutil.PVCMustInclusionTracker,
|
||||
) (vhutil.VolumeHelper, error) {
|
||||
return volumehelper.NewVolumeHelperImplWithCache(
|
||||
backup,
|
||||
client,
|
||||
logger,
|
||||
pvcPodCache,
|
||||
pvcMustInclusionTracker,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -300,6 +300,7 @@ func TestShouldPerformSnapshotWithNonNilVolumeHelper(t *testing.T) {
|
||||
false, // defaultVolumesToFSBackup
|
||||
true, // backupExcludePVC
|
||||
[]string{"default"},
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, vh)
|
||||
|
||||
@@ -712,6 +712,7 @@ func (kr *kopiaRepository) GetSnapshot(ctx context.Context, id udmrepo.ID) (udmr
|
||||
}
|
||||
|
||||
return udmrepo.Snapshot{
|
||||
ID: udmrepo.ID(snap.ID),
|
||||
Source: snap.Source.Path,
|
||||
Description: snap.Description,
|
||||
StartTime: snap.StartTime.ToTime(),
|
||||
@@ -751,6 +752,7 @@ func (kr *kopiaRepository) ListSnapshot(ctx context.Context, source string) ([]u
|
||||
snapshots := []udmrepo.Snapshot{}
|
||||
for _, snap := range mani {
|
||||
snapshots = append(snapshots, udmrepo.Snapshot{
|
||||
ID: udmrepo.ID(snap.ID),
|
||||
Source: snap.Source.Path,
|
||||
Description: snap.Description,
|
||||
StartTime: snap.StartTime.ToTime(),
|
||||
|
||||
@@ -1609,6 +1609,7 @@ func TestGetSnapshot(t *testing.T) {
|
||||
snapshotID: udmrepo.ID("fake-id"),
|
||||
setRepoMock: true,
|
||||
expectedSnap: udmrepo.Snapshot{
|
||||
ID: "fake-id",
|
||||
Source: "fake-source",
|
||||
Description: "fake-desc",
|
||||
StartTime: mockMani.StartTime.ToTime(),
|
||||
@@ -1805,6 +1806,7 @@ func TestListSnapshot(t *testing.T) {
|
||||
setRepoMock: true,
|
||||
expectedSnaps: []udmrepo.Snapshot{
|
||||
{
|
||||
ID: "fake-id",
|
||||
Source: "fake-source",
|
||||
Description: "fake-desc",
|
||||
StartTime: mockMani.StartTime.ToTime(),
|
||||
|
||||
@@ -98,6 +98,7 @@ type Metadata struct {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
ID ID
|
||||
Source string
|
||||
Description string
|
||||
StartTime time.Time
|
||||
|
||||
@@ -169,50 +169,52 @@ func getParentBackupInfo(ctx context.Context, rep udmrepo.BackupRepo, forceFull
|
||||
}
|
||||
|
||||
var previous *udmrepo.Snapshot
|
||||
if parentSnapshot != "" {
|
||||
log.Infof("Loading provided parent snapshot %s", parentSnapshot)
|
||||
|
||||
snap, err := rep.GetSnapshot(ctx, udmrepo.ID(parentSnapshot))
|
||||
if err != nil {
|
||||
return parentBackupInfo{}, errors.Wrapf(err, "error loading previous snapshot")
|
||||
if !forceFull {
|
||||
if parentSnapshot != "" {
|
||||
snap, err := rep.GetSnapshot(ctx, udmrepo.ID(parentSnapshot))
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Failed to load previous snapshot, fallback to full backup")
|
||||
} else {
|
||||
previous = &snap
|
||||
log.Infof("Using provided parent snapshot %s", parentSnapshot)
|
||||
}
|
||||
} else {
|
||||
log.Infof("Searching for parent snapshot")
|
||||
|
||||
snap, err := findPreviousSnapshot(ctx, rep, realSource, snapshotTags, nil, log)
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Failed to search previous snapshot, fallback to full backup")
|
||||
} else {
|
||||
previous = &snap
|
||||
log.Infof("Using previous snapshot %s", snap.ID)
|
||||
}
|
||||
}
|
||||
|
||||
previous = &snap
|
||||
|
||||
} else {
|
||||
log.Infof("Searching for parent snapshot")
|
||||
log.Info("Forcing full snapshot")
|
||||
}
|
||||
|
||||
snap, err := findPreviousSnapshot(ctx, rep, realSource, snapshotTags, nil, log)
|
||||
if err != nil {
|
||||
return parentBackupInfo{}, errors.Wrapf(err, "error searching previous snapshot")
|
||||
parentInfo := parentBackupInfo{}
|
||||
if previous != nil {
|
||||
if previous.Tags == nil {
|
||||
log.Warnf("No tag from parent snapshot %s, fallback to full backup", previous.ID)
|
||||
} else if previous.Tags[uploader.CBTChangeIDTag] == "" {
|
||||
log.Warnf("No ChangeID tag from parent snapshot %s, fallback to full backup", previous.ID)
|
||||
} else if previous.Tags[uploader.CBTVolumeIDTag] == "" {
|
||||
log.Warnf("No VolumeID tag from parent snapshot %s, fallback to full backup", previous.ID)
|
||||
} else if previous.Tags[uploader.CBTVolumeIDTag] != volumeID {
|
||||
log.Warnf("VolumeID %s from parent snapshot %s is not expected as %s, fallback to full backup", previous.Tags[uploader.CBTVolumeIDTag], previous.ID, volumeID)
|
||||
} else if obj, err := loadObjectFromSnapshot(ctx, rep, previous); err != nil {
|
||||
log.WithError(err).Warnf("Failed to load object from parent snapshot %s, fallback to full backup", previous.ID)
|
||||
} else {
|
||||
parentInfo.parentObject = obj
|
||||
parentInfo.changeID = previous.Tags[uploader.CBTChangeIDTag]
|
||||
parentInfo.volumeID = previous.Tags[uploader.CBTVolumeIDTag]
|
||||
|
||||
log.Infof("Using parent snapshot %s, start time %v, end time %v, description %s", previous.ID, previous.StartTime, previous.EndTime, previous.Description)
|
||||
}
|
||||
|
||||
previous = &snap
|
||||
}
|
||||
|
||||
if previous.Tags == nil {
|
||||
return parentBackupInfo{}, errors.Errorf("no tag from parent snapshot %s", previous.ID)
|
||||
}
|
||||
|
||||
if previous.Tags[uploader.CBTChangeIDTag] == "" {
|
||||
return parentBackupInfo{}, errors.Errorf("no ChangeID tag from parent snapshot %s", previous.ID)
|
||||
}
|
||||
|
||||
if previous.Tags[uploader.CBTVolumeIDTag] == "" {
|
||||
return parentBackupInfo{}, errors.Errorf("no VolumeID tag from parent snapshot %s", previous.ID)
|
||||
}
|
||||
|
||||
if previous.Tags[uploader.CBTVolumeIDTag] != volumeID {
|
||||
return parentBackupInfo{}, errors.Errorf("VolumeID %s from parent snapshot %s is not expected as %s", previous.Tags[uploader.CBTVolumeIDTag], previous.ID, volumeID)
|
||||
}
|
||||
|
||||
obj, err := loadObjectFromSnapshot(ctx, rep, previous)
|
||||
if err != nil {
|
||||
return parentBackupInfo{}, errors.Errorf("error loading object from parent snapshot %s", previous.ID)
|
||||
}
|
||||
|
||||
log.Infof("Using parent snapshot %s, start time %v, end time %v, description %s", previous.ID, previous.StartTime, previous.EndTime, previous.Description)
|
||||
|
||||
return parentBackupInfo{
|
||||
parentObject: obj,
|
||||
changeID: previous.Tags[uploader.CBTChangeIDTag],
|
||||
|
||||
@@ -351,6 +351,7 @@ func TestSnapshotSource(t *testing.T) {
|
||||
func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
|
||||
const volumeID = "vol-123"
|
||||
const realSource = "/test/source"
|
||||
const parentSnapID = "snap-parent-42"
|
||||
const rootObj = "root-obj-42"
|
||||
|
||||
snapshotTags := map[string]string{
|
||||
@@ -364,6 +365,7 @@ func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
|
||||
repo := udmrepomocks.NewBackupRepo(t)
|
||||
repo.On("ListSnapshot", mock.Anything, realSource).
|
||||
Return([]udmrepo.Snapshot{{
|
||||
ID: parentSnapID,
|
||||
RootObject: udmrepo.ObjectMetadata{ID: rootObj},
|
||||
Tags: map[string]string{
|
||||
uploader.CBTChangeIDTag: "cid-abc",
|
||||
@@ -389,7 +391,7 @@ func TestGetParentBackupInfoLogsDiscoveredParentID(t *testing.T) {
|
||||
for _, entry := range hook.AllEntries() {
|
||||
if strings.HasPrefix(entry.Message, "Using parent snapshot ") {
|
||||
found = true
|
||||
assert.Contains(t, entry.Message, rootObj,
|
||||
assert.Contains(t, entry.Message, parentSnapID,
|
||||
"parent-selection message must name the discovered snapshot, got %q", entry.Message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,17 +167,17 @@ func (blkup *blockUploader) Restore(snapshot udmrepo.Snapshot, dest destInfo, bi
|
||||
|
||||
meta, err := blkup.repoWriter.ReadMetadata(blkup.ctx, snapshot.RootObject.ID)
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.Description)
|
||||
return 0, 0, errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.ID)
|
||||
}
|
||||
|
||||
if len(meta.SubObjects) != 1 {
|
||||
return 0, 0, errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.Description)
|
||||
return 0, 0, errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.ID)
|
||||
}
|
||||
|
||||
sourceSize, err := getSourceSize(snapshot)
|
||||
if err != nil {
|
||||
sourceSize = meta.SubObjects[0].Size
|
||||
blkup.log.Warnf("Failed to get source size from snapshot %s, use backup size %v", snapshot.Description, sourceSize)
|
||||
blkup.log.Warnf("Failed to get source size from snapshot %s, use backup size %v", snapshot.ID, sourceSize)
|
||||
}
|
||||
|
||||
if sourceSize > meta.SubObjects[0].Size {
|
||||
@@ -667,11 +667,11 @@ func loadObjectFromSnapshot(ctx context.Context, rep udmrepo.BackupRepo, snapsho
|
||||
|
||||
meta, err := rep.ReadMetadata(ctx, snapshot.RootObject.ID)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.Description)
|
||||
return "", errors.Wrap(err, "error reading snapshot metadata")
|
||||
}
|
||||
|
||||
if len(meta.SubObjects) != 1 {
|
||||
return "", errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(meta.SubObjects), snapshot.Description)
|
||||
return "", errors.Errorf("unexpected number of bdev object (%d)", len(meta.SubObjects))
|
||||
}
|
||||
|
||||
return meta.SubObjects[0].ID, nil
|
||||
|
||||
@@ -64,6 +64,11 @@ func GetCBTInfo(ctx context.Context, kubeClient kubernetes.Interface, log logrus
|
||||
|
||||
if vsc.Status != nil && vsc.Status.SnapshotHandle != nil {
|
||||
cbtInfo.ChangeID = *vsc.Status.SnapshotHandle
|
||||
} else if vsc.Spec.Source.SnapshotHandle != nil {
|
||||
// The backup VSC is statically provisioned from the source VSC's
|
||||
// snapshot handle; its status is populated asynchronously and may
|
||||
// not be set yet, but the handle is already in the spec.
|
||||
cbtInfo.ChangeID = *vsc.Spec.Source.SnapshotHandle
|
||||
}
|
||||
|
||||
if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeHandle != "" {
|
||||
|
||||
@@ -22,6 +22,13 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// PVCMustInclusionTracker provides read-only checks for whether a PVC is included
|
||||
// in the backup as BIA's additionalItems through annotation
|
||||
// backup.velero.io/must-include-additional-items.
|
||||
type PVCMustInclusionTracker interface {
|
||||
IsPVCIncluded(namespace, pvcName string) bool
|
||||
}
|
||||
|
||||
type VolumeHelper interface {
|
||||
ShouldPerformSnapshot(obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error)
|
||||
ShouldPerformFSBackup(volume corev1api.Volume, pod corev1api.Pod) (bool, error)
|
||||
|
||||
Reference in New Issue
Block a user