batch delete snapshot

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
Lyndon-Li
2024-01-09 13:32:11 +08:00
parent 72f2da92b7
commit 32d92ca964
12 changed files with 274 additions and 68 deletions

View File

@@ -787,6 +787,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
newPluginManager,
backupStoreGetter,
s.credentialFileStore,
s.repoEnsurer,
).SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", controller.BackupDeletion)
}

View File

@@ -68,6 +68,7 @@ type backupDeletionReconciler struct {
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
credentialStore credentials.FileStore
repoEnsurer *repository.Ensurer
}
// NewBackupDeletionReconciler creates a new backup deletion reconciler.
@@ -81,6 +82,7 @@ func NewBackupDeletionReconciler(
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
backupStoreGetter persistence.ObjectBackupStoreGetter,
credentialStore credentials.FileStore,
repoEnsurer *repository.Ensurer,
) *backupDeletionReconciler {
return &backupDeletionReconciler{
Client: client,
@@ -93,6 +95,7 @@ func NewBackupDeletionReconciler(
newPluginManager: newPluginManager,
backupStoreGetter: backupStoreGetter,
credentialStore: credentialStore,
repoEnsurer: repoEnsurer,
}
}
@@ -502,18 +505,12 @@ func (r *backupDeletionReconciler) deletePodVolumeSnapshots(ctx context.Context,
return nil
}
snapshots, err := getSnapshotsInBackup(ctx, backup, r.Client)
directSnapshots, err := getSnapshotsInBackup(ctx, backup, r.Client)
if err != nil {
return []error{err}
}
var errs []error
for _, snapshot := range snapshots {
if err := r.repoMgr.Forget(ctx, snapshot); err != nil {
errs = append(errs, err)
}
}
return errs
return r.batchDeleteSnapshots(ctx, directSnapshots, backup)
}
func (r *backupDeletionReconciler) deleteMovedSnapshots(ctx context.Context, backup *velerov1api.Backup) []error {
@@ -532,6 +529,7 @@ func (r *backupDeletionReconciler) deleteMovedSnapshots(ctx context.Context, bac
return []error{errors.Wrapf(err, "failed to retrieve config for snapshot info")}
}
var errs []error
directSnapshots := map[string][]repository.SnapshotIdentifier{}
for i := range list.Items {
cm := list.Items[i]
snapshot := repository.SnapshotIdentifier{}
@@ -544,15 +542,24 @@ func (r *backupDeletionReconciler) deleteMovedSnapshots(ctx context.Context, bac
errs = append(errs, errors.Wrapf(err, "failed to unmarshal snapshot info"))
continue
}
if err := r.repoMgr.Forget(ctx, snapshot); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to delete snapshot %s, namespace: %s", snapshot.SnapshotID, snapshot.VolumeNamespace))
if directSnapshots[snapshot.VolumeNamespace] == nil {
directSnapshots[snapshot.VolumeNamespace] = []repository.SnapshotIdentifier{}
}
directSnapshots[snapshot.VolumeNamespace] = append(directSnapshots[snapshot.VolumeNamespace], snapshot)
r.logger.Infof("Deleted snapshot %s, namespace: %s, repo type: %s", snapshot.SnapshotID, snapshot.VolumeNamespace, snapshot.RepositoryType)
if err := r.Client.Delete(ctx, &cm); err != nil {
r.logger.Warnf("Failed to delete snapshot info configmap %s/%s: %v", cm.Namespace, cm.Name, err)
}
}
return errs
if len(errs) > 0 {
return errs
}
return r.batchDeleteSnapshots(ctx, directSnapshots, backup)
}
func (r *backupDeletionReconciler) patchDeleteBackupRequest(ctx context.Context, req *velerov1api.DeleteBackupRequest, mutate func(*velerov1api.DeleteBackupRequest)) (*velerov1api.DeleteBackupRequest, error) {
@@ -592,7 +599,7 @@ func (r *backupDeletionReconciler) patchBackup(ctx context.Context, backup *vele
// getSnapshotsInBackup returns a list of all pod volume snapshot ids associated with
// a given Velero backup.
func getSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) ([]repository.SnapshotIdentifier, error) {
func getSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbClient client.Client) (map[string][]repository.SnapshotIdentifier, error) {
podVolumeBackups := &velerov1api.PodVolumeBackupList{}
options := &client.ListOptions{
LabelSelector: labels.Set(map[string]string{
@@ -607,3 +614,29 @@ func getSnapshotsInBackup(ctx context.Context, backup *velerov1api.Backup, kbCli
return podvolume.GetSnapshotIdentifier(podVolumeBackups), nil
}
func (r *backupDeletionReconciler) batchDeleteSnapshots(ctx context.Context, directSnapshots map[string][]repository.SnapshotIdentifier, backup *velerov1api.Backup) []error {
var errs []error
for volumeNamespace, snapshots := range directSnapshots {
batchForget := []string{}
for _, snapshot := range snapshots {
batchForget = append(batchForget, snapshot.SnapshotID)
}
// For volumes in one backup, the BSL and repositoryType should always be the same
repo, err := r.repoEnsurer.EnsureRepo(ctx, backup.Namespace, volumeNamespace, backup.Spec.StorageLocation, snapshots[0].RepositoryType)
if err != nil {
errs = append(errs, errors.Wrapf(err, "error to ensure repo %s-%s-%s, skip deleting PVB snapshots %v", backup.Spec.StorageLocation, volumeNamespace, snapshots[0].RepositoryType, batchForget))
continue
}
if forgetErrs := r.repoMgr.BatchForget(ctx, repo, batchForget); len(forgetErrs) > 0 {
errs = append(errs, forgetErrs...)
continue
}
r.logger.Infof("Batch deleted snapshots %v", batchForget)
}
return errs
}

View File

@@ -20,7 +20,7 @@ import (
"bytes"
"fmt"
"io"
"sort"
"reflect"
"time"
"context"
@@ -95,6 +95,7 @@ func setupBackupDeletionControllerTest(t *testing.T, req *velerov1api.DeleteBack
func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager },
NewFakeSingleObjectBackupStoreGetter(backupStore),
velerotest.NewFakeCredentialsFileStore("", nil),
nil,
),
req: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: req.Namespace, Name: req.Name}},
}
@@ -695,13 +696,13 @@ func TestGetSnapshotsInBackup(t *testing.T) {
tests := []struct {
name string
podVolumeBackups []velerov1api.PodVolumeBackup
expected []repository.SnapshotIdentifier
expected map[string][]repository.SnapshotIdentifier
longBackupNameEnabled bool
}{
{
name: "no pod volume backups",
podVolumeBackups: nil,
expected: nil,
expected: map[string][]repository.SnapshotIdentifier{},
},
{
name: "no pod volume backups with matching label",
@@ -721,7 +722,7 @@ func TestGetSnapshotsInBackup(t *testing.T) {
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: "snap-2"},
},
},
expected: nil,
expected: map[string][]repository.SnapshotIdentifier{},
},
{
name: "some pod volume backups with matching label",
@@ -762,16 +763,18 @@ func TestGetSnapshotsInBackup(t *testing.T) {
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
},
},
expected: []repository.SnapshotIdentifier{
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-3",
RepositoryType: "restic",
},
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-4",
RepositoryType: "restic",
expected: map[string][]repository.SnapshotIdentifier{
"ns-1": {
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-3",
RepositoryType: "restic",
},
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-4",
RepositoryType: "restic",
},
},
},
},
@@ -815,11 +818,13 @@ func TestGetSnapshotsInBackup(t *testing.T) {
Status: velerov1api.PodVolumeBackupStatus{SnapshotID: ""},
},
},
expected: []repository.SnapshotIdentifier{
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-3",
RepositoryType: "restic",
expected: map[string][]repository.SnapshotIdentifier{
"ns-1": {
{
VolumeNamespace: "ns-1",
SnapshotID: "snap-3",
RepositoryType: "restic",
},
},
},
},
@@ -844,21 +849,27 @@ func TestGetSnapshotsInBackup(t *testing.T) {
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
}
assert.True(t, reflect.DeepEqual(res, test.expected))
}
// for k, v := range res {
sort.Slice(test.expected, less(test.expected))
sort.Slice(res, less(res))
// }
assert.Equal(t, test.expected, res)
// // 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)
})
}
}

View File

@@ -344,7 +344,7 @@ func (r *DataUploadReconciler) runCancelableDataUpload(ctx context.Context, fsBa
velerov1api.AsyncOperationIDLabel: du.Labels[velerov1api.AsyncOperationIDLabel],
}
if err := fsBackup.StartBackup(path, fmt.Sprintf("%s/%s", du.Spec.SourceNamespace, du.Spec.SourcePVC), "", false, tags, du.Spec.DataMoverConfig); err != nil {
if err := fsBackup.StartBackup(path, datamover.GetRealSource(du.Spec.SourceNamespace, du.Spec.SourcePVC), "", false, tags, du.Spec.DataMoverConfig); err != nil {
return r.errorOut(ctx, du, err, "error starting data path backup", log)
}

View File

@@ -57,6 +57,8 @@ func genConfigmap(bak *velerov1.Backup, du velerov2alpha1.DataUpload) *corev1api
BackupStorageLocation: bak.Spec.StorageLocation,
SnapshotID: du.Status.SnapshotID,
RepositoryType: GetUploaderType(du.Spec.DataMover),
UploaderType: GetUploaderType(du.Spec.DataMover),
Source: GetRealSource(du.Spec.SourceNamespace, du.Spec.SourcePVC),
}
b, err := json.Marshal(snapshot)
if err != nil {

View File

@@ -16,6 +16,8 @@ limitations under the License.
package datamover
import "fmt"
func GetUploaderType(dataMover string) string {
if dataMover == "" || dataMover == "velero" {
return "kopia"
@@ -27,3 +29,7 @@ func GetUploaderType(dataMover string) string {
func IsBuiltInUploader(dataMover string) bool {
return dataMover == "" || dataMover == "velero"
}
func GetRealSource(sourceNamespace string, pvcName string) string {
return fmt.Sprintf("%s/%s", sourceNamespace, pvcName)
}

View File

@@ -122,19 +122,30 @@ func getVolumeBackupInfoForPod(podVolumeBackups []*velerov1api.PodVolumeBackup,
}
// GetSnapshotIdentifier returns the snapshots represented by SnapshotIdentifier for the given PVBs
func GetSnapshotIdentifier(podVolumeBackups *velerov1api.PodVolumeBackupList) []repository.SnapshotIdentifier {
var res []repository.SnapshotIdentifier
func GetSnapshotIdentifier(podVolumeBackups *velerov1api.PodVolumeBackupList) map[string][]repository.SnapshotIdentifier {
res := map[string][]repository.SnapshotIdentifier{}
for _, item := range podVolumeBackups.Items {
if item.Status.SnapshotID == "" {
continue
}
res = append(res, repository.SnapshotIdentifier{
if res[item.Spec.Pod.Namespace] == nil {
res[item.Spec.Pod.Namespace] = []repository.SnapshotIdentifier{}
}
snapshots := res[item.Spec.Pod.Namespace]
snapshots = append(snapshots, repository.SnapshotIdentifier{
VolumeNamespace: item.Spec.Pod.Namespace,
BackupStorageLocation: item.Spec.BackupStorageLocation,
SnapshotID: item.Status.SnapshotID,
RepositoryType: getRepositoryType(item.Spec.UploaderType),
UploaderType: item.Spec.UploaderType,
Source: item.Status.Path,
RepoIdentifier: item.Spec.RepoIdentifier,
})
res[item.Spec.Pod.Namespace] = snapshots
}
return res

View File

@@ -48,6 +48,16 @@ type SnapshotIdentifier struct {
// RepositoryType is the type of the repository where the
// snapshot is stored
RepositoryType string `json:"repositoryType"`
// Source is the source of the data saved in the repo by the snapshot
Source string `json:"source"`
// UploaderType is the type of uploader which saved the snapshot data
UploaderType string `json:"uploaderType"`
// RepoIdentifier is the identifier of the repository where the
// snapshot is stored
RepoIdentifier string `json:"repoIdentifier"`
}
// Manager manages backup repositories.
@@ -71,7 +81,12 @@ type Manager interface {
// Forget removes a snapshot from the list of
// available snapshots in a repo.
Forget(context.Context, SnapshotIdentifier) error
Forget(context.Context, *velerov1api.BackupRepository, string) error
// BatchForget removes a list of snapshots from the list of
// available snapshots in a repo.
BatchForget(context.Context, *velerov1api.BackupRepository, []string) []error
// DefaultMaintenanceFrequency returns the default maintenance frequency from the specific repo
DefaultMaintenanceFrequency(repo *velerov1api.BackupRepository) (time.Duration, error)
}
@@ -195,12 +210,7 @@ func (m *manager) UnlockRepo(repo *velerov1api.BackupRepository) error {
return prd.EnsureUnlockRepo(context.Background(), param)
}
func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error {
repo, err := m.repoEnsurer.EnsureRepo(ctx, m.namespace, snapshot.VolumeNamespace, snapshot.BackupStorageLocation, snapshot.RepositoryType)
if err != nil {
return err
}
func (m *manager) Forget(ctx context.Context, repo *velerov1api.BackupRepository, snapshot string) error {
m.repoLocker.LockExclusive(repo.Name)
defer m.repoLocker.UnlockExclusive(repo.Name)
@@ -217,7 +227,27 @@ func (m *manager) Forget(ctx context.Context, snapshot SnapshotIdentifier) error
return errors.WithStack(err)
}
return prd.Forget(context.Background(), snapshot.SnapshotID, param)
return prd.Forget(context.Background(), snapshot, param)
}
func (m *manager) BatchForget(ctx context.Context, repo *velerov1api.BackupRepository, snapshots []string) []error {
m.repoLocker.LockExclusive(repo.Name)
defer m.repoLocker.UnlockExclusive(repo.Name)
prd, err := m.getRepositoryProvider(repo)
if err != nil {
return []error{errors.WithStack(err)}
}
param, err := m.assembleRepoParam(repo)
if err != nil {
return []error{errors.WithStack(err)}
}
if err := prd.BoostRepoConnect(context.Background(), param); err != nil {
return []error{errors.WithStack(err)}
}
return prd.BatchForget(context.Background(), snapshots, param)
}
func (m *manager) DefaultMaintenanceFrequency(repo *velerov1api.BackupRepository) (time.Duration, error) {

View File

@@ -1,4 +1,4 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
// Code generated by mockery v2.39.1. DO NOT EDIT.
package mocks
@@ -6,7 +6,6 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
repository "github.com/vmware-tanzu/velero/pkg/repository"
time "time"
@@ -18,10 +17,34 @@ type Manager struct {
mock.Mock
}
// BatchForget provides a mock function with given fields: _a0, _a1, _a2
func (_m *Manager) BatchForget(_a0 context.Context, _a1 *v1.BackupRepository, _a2 []string) []error {
ret := _m.Called(_a0, _a1, _a2)
if len(ret) == 0 {
panic("no return value specified for BatchForget")
}
var r0 []error
if rf, ok := ret.Get(0).(func(context.Context, *v1.BackupRepository, []string) []error); ok {
r0 = rf(_a0, _a1, _a2)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]error)
}
}
return r0
}
// ConnectToRepo provides a mock function with given fields: repo
func (_m *Manager) ConnectToRepo(repo *v1.BackupRepository) error {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for ConnectToRepo")
}
var r0 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok {
r0 = rf(repo)
@@ -36,14 +59,21 @@ func (_m *Manager) ConnectToRepo(repo *v1.BackupRepository) error {
func (_m *Manager) DefaultMaintenanceFrequency(repo *v1.BackupRepository) (time.Duration, error) {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for DefaultMaintenanceFrequency")
}
var r0 time.Duration
var r1 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) (time.Duration, error)); ok {
return rf(repo)
}
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) time.Duration); ok {
r0 = rf(repo)
} else {
r0 = ret.Get(0).(time.Duration)
}
var r1 error
if rf, ok := ret.Get(1).(func(*v1.BackupRepository) error); ok {
r1 = rf(repo)
} else {
@@ -53,13 +83,17 @@ func (_m *Manager) DefaultMaintenanceFrequency(repo *v1.BackupRepository) (time.
return r0, r1
}
// Forget provides a mock function with given fields: _a0, _a1
func (_m *Manager) Forget(_a0 context.Context, _a1 repository.SnapshotIdentifier) error {
ret := _m.Called(_a0, _a1)
// Forget provides a mock function with given fields: _a0, _a1, _a2
func (_m *Manager) Forget(_a0 context.Context, _a1 *v1.BackupRepository, _a2 string) error {
ret := _m.Called(_a0, _a1, _a2)
if len(ret) == 0 {
panic("no return value specified for Forget")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, repository.SnapshotIdentifier) error); ok {
r0 = rf(_a0, _a1)
if rf, ok := ret.Get(0).(func(context.Context, *v1.BackupRepository, string) error); ok {
r0 = rf(_a0, _a1, _a2)
} else {
r0 = ret.Error(0)
}
@@ -71,6 +105,10 @@ func (_m *Manager) Forget(_a0 context.Context, _a1 repository.SnapshotIdentifier
func (_m *Manager) InitRepo(repo *v1.BackupRepository) error {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for InitRepo")
}
var r0 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok {
r0 = rf(repo)
@@ -85,6 +123,10 @@ func (_m *Manager) InitRepo(repo *v1.BackupRepository) error {
func (_m *Manager) PrepareRepo(repo *v1.BackupRepository) error {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for PrepareRepo")
}
var r0 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok {
r0 = rf(repo)
@@ -99,6 +141,10 @@ func (_m *Manager) PrepareRepo(repo *v1.BackupRepository) error {
func (_m *Manager) PruneRepo(repo *v1.BackupRepository) error {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for PruneRepo")
}
var r0 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok {
r0 = rf(repo)
@@ -113,6 +159,10 @@ func (_m *Manager) PruneRepo(repo *v1.BackupRepository) error {
func (_m *Manager) UnlockRepo(repo *v1.BackupRepository) error {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for UnlockRepo")
}
var r0 error
if rf, ok := ret.Get(0).(func(*v1.BackupRepository) error); ok {
r0 = rf(repo)
@@ -123,13 +173,12 @@ func (_m *Manager) UnlockRepo(repo *v1.BackupRepository) error {
return r0
}
type mockConstructorTestingTNewManager interface {
// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewManager(t interface {
mock.TestingT
Cleanup(func())
}
// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewManager(t mockConstructorTestingTNewManager) *Manager {
}) *Manager {
mock := &Manager{}
mock.Mock.Test(t)

View File

@@ -57,6 +57,9 @@ type Provider interface {
// Forget is to delete a snapshot from the repository
Forget(ctx context.Context, snapshotID string, param RepoParam) error
// BatchForget is to delete a list of snapshots from the repository
BatchForget(ctx context.Context, snapshotIDs []string, param RepoParam) []error
// DefaultMaintenanceFrequency returns the default frequency to run maintenance
DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration
}

View File

@@ -78,6 +78,16 @@ func (r *resticRepositoryProvider) Forget(ctx context.Context, snapshotID string
return r.svc.Forget(param.BackupLocation, param.BackupRepo, snapshotID)
}
func (r *resticRepositoryProvider) BatchForget(ctx context.Context, snapshotIDs []string, param RepoParam) []error {
errs := []error{}
for _, snapshot := range snapshotIDs {
err := r.Forget(ctx, snapshot, param)
errs = append(errs, err)
}
return errs
}
func (r *resticRepositoryProvider) DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration {
return r.svc.DefaultMaintenanceFrequency()
}

View File

@@ -314,6 +314,56 @@ func (urp *unifiedRepoProvider) Forget(ctx context.Context, snapshotID string, p
return nil
}
func (urp *unifiedRepoProvider) BatchForget(ctx context.Context, snapshotIDs []string, param RepoParam) []error {
log := urp.log.WithFields(logrus.Fields{
"BSL name": param.BackupLocation.Name,
"repo name": param.BackupRepo.Name,
"repo UID": param.BackupRepo.UID,
"snapshotIDs": snapshotIDs,
})
log.Debug("Start to batch forget snapshot")
repoOption, err := udmrepo.NewRepoOptions(
udmrepo.WithPassword(urp, param),
udmrepo.WithConfigFile(urp.workPath, string(param.BackupRepo.UID)),
udmrepo.WithDescription(repoOpDescForget),
)
if err != nil {
return []error{errors.Wrap(err, "error to get repo options")}
}
bkRepo, err := urp.repoService.Open(ctx, *repoOption)
if err != nil {
return []error{errors.Wrap(err, "error to open backup repo")}
}
defer func() {
c := bkRepo.Close(ctx)
if c != nil {
log.WithError(c).Error("Failed to close repo")
}
}()
errs := []error{}
for _, snapshotID := range snapshotIDs {
err = bkRepo.DeleteManifest(ctx, udmrepo.ID(snapshotID))
if err != nil {
errs = append(errs, errors.Wrap(err, "error to delete manifest"))
}
}
err = bkRepo.Flush(ctx)
if err != nil {
return []error{errors.Wrap(err, "error to flush repo")}
}
log.Debug("Forget snapshot complete")
return nil
}
func (urp *unifiedRepoProvider) DefaultMaintenanceFrequency(ctx context.Context, param RepoParam) time.Duration {
return urp.repoService.DefaultMaintenanceFrequency()
}