mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-07 13:55:20 +00:00
Refactor backup deletion controller based on kubebuilder
Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
@@ -23,25 +23,19 @@ import (
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
snapshotterClientSet "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned"
|
||||
snapshotv1listers "github.com/kubernetes-csi/external-snapshotter/client/v4/listers/volumesnapshot/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"sigs.k8s.io/cluster-api/util/patch"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/delete"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
|
||||
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
|
||||
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
@@ -54,250 +48,202 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const resticTimeout = time.Minute
|
||||
const (
|
||||
resticTimeout = time.Minute
|
||||
deleteBackupRequestMaxAge = 24 * time.Hour
|
||||
)
|
||||
|
||||
type backupDeletionController struct {
|
||||
*genericController
|
||||
|
||||
deleteBackupRequestClient velerov1client.DeleteBackupRequestsGetter
|
||||
deleteBackupRequestLister velerov1listers.DeleteBackupRequestLister
|
||||
backupClient velerov1client.BackupsGetter
|
||||
restoreLister velerov1listers.RestoreLister
|
||||
restoreClient velerov1client.RestoresGetter
|
||||
backupTracker BackupTracker
|
||||
resticMgr restic.RepositoryManager
|
||||
podvolumeBackupLister velerov1listers.PodVolumeBackupLister
|
||||
kbClient client.Client
|
||||
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister
|
||||
csiSnapshotLister snapshotv1listers.VolumeSnapshotLister
|
||||
csiSnapshotContentLister snapshotv1listers.VolumeSnapshotContentLister
|
||||
csiSnapshotClient *snapshotterClientSet.Clientset
|
||||
processRequestFunc func(*velerov1api.DeleteBackupRequest) error
|
||||
clock clock.Clock
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
metrics *metrics.ServerMetrics
|
||||
helper discovery.Helper
|
||||
type backupDeletionReconciler struct {
|
||||
client.Client
|
||||
logger logrus.FieldLogger
|
||||
backupTracker BackupTracker
|
||||
resticMgr restic.RepositoryManager
|
||||
metrics *metrics.ServerMetrics
|
||||
clock clock.Clock
|
||||
discoveryHelper discovery.Helper
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||
}
|
||||
|
||||
// NewBackupDeletionController creates a new backup deletion controller.
|
||||
func NewBackupDeletionController(
|
||||
// NewBackupDeletionReconciler creates a new backup deletion reconciler.
|
||||
func NewBackupDeletionReconciler(
|
||||
logger logrus.FieldLogger,
|
||||
deleteBackupRequestInformer velerov1informers.DeleteBackupRequestInformer,
|
||||
deleteBackupRequestClient velerov1client.DeleteBackupRequestsGetter,
|
||||
backupClient velerov1client.BackupsGetter,
|
||||
restoreLister velerov1listers.RestoreLister,
|
||||
restoreClient velerov1client.RestoresGetter,
|
||||
client client.Client,
|
||||
backupTracker BackupTracker,
|
||||
resticMgr restic.RepositoryManager,
|
||||
podvolumeBackupLister velerov1listers.PodVolumeBackupLister,
|
||||
kbClient client.Client,
|
||||
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister,
|
||||
csiSnapshotLister snapshotv1listers.VolumeSnapshotLister,
|
||||
csiSnapshotContentLister snapshotv1listers.VolumeSnapshotContentLister,
|
||||
csiSnapshotClient *snapshotterClientSet.Clientset,
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
||||
metrics *metrics.ServerMetrics,
|
||||
helper discovery.Helper,
|
||||
) Interface {
|
||||
c := &backupDeletionController{
|
||||
genericController: newGenericController(BackupDeletion, logger),
|
||||
deleteBackupRequestClient: deleteBackupRequestClient,
|
||||
deleteBackupRequestLister: deleteBackupRequestInformer.Lister(),
|
||||
backupClient: backupClient,
|
||||
restoreLister: restoreLister,
|
||||
restoreClient: restoreClient,
|
||||
backupTracker: backupTracker,
|
||||
resticMgr: resticMgr,
|
||||
podvolumeBackupLister: podvolumeBackupLister,
|
||||
kbClient: kbClient,
|
||||
snapshotLocationLister: snapshotLocationLister,
|
||||
csiSnapshotLister: csiSnapshotLister,
|
||||
csiSnapshotContentLister: csiSnapshotContentLister,
|
||||
csiSnapshotClient: csiSnapshotClient,
|
||||
metrics: metrics,
|
||||
helper: helper,
|
||||
// use variables to refer to these functions so they can be
|
||||
// replaced with fakes for testing.
|
||||
newPluginManager func(logrus.FieldLogger) clientmgmt.Manager,
|
||||
backupStoreGetter persistence.ObjectBackupStoreGetter,
|
||||
) *backupDeletionReconciler {
|
||||
return &backupDeletionReconciler{
|
||||
Client: client,
|
||||
logger: logger,
|
||||
backupTracker: backupTracker,
|
||||
resticMgr: resticMgr,
|
||||
metrics: metrics,
|
||||
clock: clock.RealClock{},
|
||||
discoveryHelper: helper,
|
||||
newPluginManager: newPluginManager,
|
||||
backupStoreGetter: backupStoreGetter,
|
||||
|
||||
clock: &clock.RealClock{},
|
||||
}
|
||||
|
||||
c.syncHandler = c.processQueueItem
|
||||
c.processRequestFunc = c.processRequest
|
||||
|
||||
deleteBackupRequestInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: c.enqueue,
|
||||
},
|
||||
)
|
||||
|
||||
c.resyncPeriod = time.Hour
|
||||
c.resyncFunc = c.deleteExpiredRequests
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) processQueueItem(key string) error {
|
||||
log := c.logger.WithField("key", key)
|
||||
log.Debug("Running processItem")
|
||||
|
||||
ns, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error splitting queue key")
|
||||
}
|
||||
|
||||
req, err := c.deleteBackupRequestLister.DeleteBackupRequests(ns).Get(name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Debug("Unable to find DeleteBackupRequest")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting DeleteBackupRequest")
|
||||
}
|
||||
|
||||
switch req.Status.Phase {
|
||||
case velerov1api.DeleteBackupRequestPhaseProcessed:
|
||||
// Don't do anything because it's already been processed
|
||||
default:
|
||||
// Don't mutate the shared cache
|
||||
reqCopy := req.DeepCopy()
|
||||
return c.processRequestFunc(reqCopy)
|
||||
}
|
||||
|
||||
return nil
|
||||
func (r *backupDeletionReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
// Make sure the expired requests can be deleted eventually
|
||||
s := kube.NewPeriodicalEnqueueSource(r.logger, mgr.GetClient(), &velerov1api.DeleteBackupRequestList{}, time.Hour)
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&velerov1api.DeleteBackupRequest{}).
|
||||
Watches(s, nil).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupRequest) error {
|
||||
log := c.logger.WithFields(logrus.Fields{
|
||||
"namespace": req.Namespace,
|
||||
"name": req.Name,
|
||||
"backup": req.Spec.BackupName,
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=deletebackuprequests,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=deletebackuprequests/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=velero.io,resources=backups,verbs=delete
|
||||
|
||||
func (r *backupDeletionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.logger.WithFields(logrus.Fields{
|
||||
"controller": BackupDeletion,
|
||||
"deletebackuprequest": req.String(),
|
||||
})
|
||||
log.Debug("Getting deletebackuprequest")
|
||||
dbr := &velerov1api.DeleteBackupRequest{}
|
||||
if err := r.Get(ctx, req.NamespacedName, dbr); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Debug("Unable to find the deletebackuprequest")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
log.WithError(err).Error("Error getting deletebackuprequest")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
var err error
|
||||
// Since we use the reconciler along with the PeriodicalEnqueueSource, there may be reconciliation triggered by
|
||||
// stale requests.
|
||||
if dbr.Status.Phase == velerov1api.DeleteBackupRequestPhaseProcessed {
|
||||
age := r.clock.Now().Sub(dbr.CreationTimestamp.Time)
|
||||
if age >= deleteBackupRequestMaxAge { // delete the expired request
|
||||
log.Debug("The request is expired, deleting it.")
|
||||
if err := r.Delete(ctx, dbr); err != nil {
|
||||
log.WithError(err).Error("Error deleting DeleteBackupRequest")
|
||||
}
|
||||
} else {
|
||||
log.Info("The request has been processed, skip.")
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Make sure we have the backup name
|
||||
if req.Spec.BackupName == "" {
|
||||
_, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = []string{"spec.backupName is required"}
|
||||
if dbr.Spec.BackupName == "" {
|
||||
_, err := r.patchDeleteBackupRequest(ctx, dbr, func(res *velerov1api.DeleteBackupRequest) {
|
||||
res.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
res.Status.Errors = []string{"spec.backupName is required"}
|
||||
})
|
||||
return err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
log = log.WithField("backup", dbr.Spec.BackupName)
|
||||
|
||||
// Remove any existing deletion requests for this backup so we only have
|
||||
// one at a time
|
||||
if errs := c.deleteExistingDeletionRequests(req, log); errs != nil {
|
||||
return kubeerrs.NewAggregate(errs)
|
||||
if errs := r.deleteExistingDeletionRequests(ctx, dbr, log); errs != nil {
|
||||
return ctrl.Result{}, kubeerrs.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// Don't allow deleting an in-progress backup
|
||||
if c.backupTracker.Contains(req.Namespace, req.Spec.BackupName) {
|
||||
_, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
if r.backupTracker.Contains(dbr.Namespace, dbr.Spec.BackupName) {
|
||||
_, err := r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = []string{"backup is still in progress"}
|
||||
})
|
||||
|
||||
return err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Get the backup we're trying to delete
|
||||
backup, err := c.backupClient.Backups(req.Namespace).Get(context.TODO(), req.Spec.BackupName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
backup := &velerov1api.Backup{}
|
||||
if err := r.Get(ctx, types.NamespacedName{
|
||||
Namespace: dbr.Namespace,
|
||||
Name: dbr.Spec.BackupName,
|
||||
}, backup); apierrors.IsNotFound(err) {
|
||||
// Couldn't find backup - update status to Processed and record the not-found error
|
||||
req, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
_, err = r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = []string{"backup not found"}
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting backup")
|
||||
return ctrl.Result{}, err
|
||||
} else if err != nil {
|
||||
return ctrl.Result{}, errors.Wrap(err, "error getting backup")
|
||||
}
|
||||
|
||||
// Don't allow deleting backups in read-only storage locations
|
||||
location := &velerov1api.BackupStorageLocation{}
|
||||
if err := c.kbClient.Get(context.Background(), client.ObjectKey{
|
||||
if err := r.Get(context.Background(), client.ObjectKey{
|
||||
Namespace: backup.Namespace,
|
||||
Name: backup.Spec.StorageLocation,
|
||||
}, location); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
_, err := c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
_, err := r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = append(r.Status.Errors, fmt.Sprintf("backup storage location %s not found", backup.Spec.StorageLocation))
|
||||
})
|
||||
return err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return errors.Wrap(err, "error getting backup storage location")
|
||||
return ctrl.Result{}, errors.Wrap(err, "error getting backup storage location")
|
||||
}
|
||||
|
||||
if location.Spec.AccessMode == velerov1api.BackupStorageLocationAccessModeReadOnly {
|
||||
_, err := c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
_, err := r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = append(r.Status.Errors, fmt.Sprintf("cannot delete backup because backup storage location %s is currently in read-only mode", location.Name))
|
||||
})
|
||||
return err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// if the request object has no labels defined, initialise an empty map since
|
||||
// we will be updating labels
|
||||
if req.Labels == nil {
|
||||
req.Labels = map[string]string{}
|
||||
if dbr.Labels == nil {
|
||||
dbr.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
// Update status to InProgress and set backup-name label if needed
|
||||
req, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
// Update status to InProgress and set backup-name and backup-uid label if needed
|
||||
dbr, err := r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseInProgress
|
||||
|
||||
if req.Labels[velerov1api.BackupNameLabel] == "" {
|
||||
req.Labels[velerov1api.BackupNameLabel] = label.GetValidName(req.Spec.BackupName)
|
||||
if r.Labels[velerov1api.BackupNameLabel] == "" {
|
||||
r.Labels[velerov1api.BackupNameLabel] = label.GetValidName(dbr.Spec.BackupName)
|
||||
}
|
||||
|
||||
if r.Labels[velerov1api.BackupUIDLabel] == "" {
|
||||
r.Labels[velerov1api.BackupUIDLabel] = string(backup.UID)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set backup-uid label if needed
|
||||
if req.Labels[velerov1api.BackupUIDLabel] == "" {
|
||||
req, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
req.Labels[velerov1api.BackupUIDLabel] = string(backup.UID)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Set backup status to Deleting
|
||||
backup, err = c.patchBackup(backup, func(b *velerov1api.Backup) {
|
||||
backup, err = r.patchBackup(ctx, backup, func(b *velerov1api.Backup) {
|
||||
b.Status.Phase = velerov1api.BackupPhaseDeleting
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error setting backup phase to deleting")
|
||||
return err
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
backupScheduleName := backup.GetLabels()[velerov1api.ScheduleNameLabel]
|
||||
c.metrics.RegisterBackupDeletionAttempt(backupScheduleName)
|
||||
r.metrics.RegisterBackupDeletionAttempt(backupScheduleName)
|
||||
|
||||
var errs []string
|
||||
|
||||
pluginManager := c.newPluginManager(log)
|
||||
pluginManager := r.newPluginManager(log)
|
||||
defer pluginManager.CleanupClients()
|
||||
|
||||
backupStore, err := c.backupStoreGetter.Get(location, pluginManager, log)
|
||||
backupStore, err := r.backupStoreGetter.Get(location, pluginManager, log)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting the backup store")
|
||||
return ctrl.Result{}, errors.Wrap(err, "error getting the backup store")
|
||||
}
|
||||
|
||||
actions, err := pluginManager.GetDeleteItemActions()
|
||||
log.Debugf("%d actions before invoking actions", len(actions))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting delete item actions")
|
||||
return ctrl.Result{}, errors.Wrap(err, "error getting delete item actions")
|
||||
}
|
||||
// don't defer CleanupClients here, since it was already called above.
|
||||
|
||||
@@ -308,13 +254,13 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Unable to download tarball for backup %s, skipping associated DeleteItemAction plugins", backup.Name)
|
||||
} else {
|
||||
defer closeAndRemoveFile(backupFile, c.logger)
|
||||
defer closeAndRemoveFile(backupFile, r.logger)
|
||||
ctx := &delete.Context{
|
||||
Backup: backup,
|
||||
BackupReader: backupFile,
|
||||
Actions: actions,
|
||||
Log: c.logger,
|
||||
DiscoveryHelper: c.helper,
|
||||
Log: r.logger,
|
||||
DiscoveryHelper: r.discoveryHelper,
|
||||
Filesystem: filesystem.NewFileSystem(),
|
||||
}
|
||||
|
||||
@@ -322,11 +268,13 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
// but what do we do with the error returned? We can't just swallow it as that may lead to dangling resources.
|
||||
err = delete.InvokeDeleteActions(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error invoking delete item actions")
|
||||
return ctrl.Result{}, errors.Wrap(err, "error invoking delete item actions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errs []string
|
||||
|
||||
if backupStore != nil {
|
||||
log.Info("Removing PV snapshots")
|
||||
|
||||
@@ -340,7 +288,7 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
|
||||
volumeSnapshotter, ok := volumeSnapshotters[snapshot.Spec.Location]
|
||||
if !ok {
|
||||
if volumeSnapshotter, err = volumeSnapshotterForSnapshotLocation(backup.Namespace, snapshot.Spec.Location, c.snapshotLocationLister, pluginManager); err != nil {
|
||||
if volumeSnapshotter, err = volumeSnapshottersForVSL(ctx, backup.Namespace, snapshot.Spec.Location, r.Client, pluginManager); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -353,9 +301,8 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Removing restic snapshots")
|
||||
if deleteErrs := c.deleteResticSnapshots(backup); len(deleteErrs) > 0 {
|
||||
if deleteErrs := r.deleteResticSnapshots(ctx, backup); len(deleteErrs) > 0 {
|
||||
for _, err := range deleteErrs {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
@@ -369,15 +316,19 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
}
|
||||
|
||||
log.Info("Removing restores")
|
||||
if restores, err := c.restoreLister.Restores(backup.Namespace).List(labels.Everything()); err != nil {
|
||||
restoreList := &velerov1api.RestoreList{}
|
||||
selector := labels.Everything()
|
||||
if err := r.List(ctx, restoreList, &client.ListOptions{
|
||||
Namespace: backup.Namespace,
|
||||
LabelSelector: selector,
|
||||
}); err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error listing restore API objects")
|
||||
} else {
|
||||
for _, restore := range restores {
|
||||
for _, restore := range restoreList.Items {
|
||||
if restore.Spec.BackupName != backup.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
restoreLog := log.WithField("restore", kube.NamespaceAndName(restore))
|
||||
restoreLog := log.WithField("restore", kube.NamespaceAndName(&restore))
|
||||
|
||||
restoreLog.Info("Deleting restore log/results from backup storage")
|
||||
if err := backupStore.DeleteRestore(restore.Name); err != nil {
|
||||
@@ -387,202 +338,160 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
|
||||
}
|
||||
|
||||
restoreLog.Info("Deleting restore referencing backup")
|
||||
if err := c.restoreClient.Restores(restore.Namespace).Delete(context.TODO(), restore.Name, metav1.DeleteOptions{}); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error deleting restore %s", kube.NamespaceAndName(restore)).Error())
|
||||
if err := r.Delete(ctx, &restore); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error deleting restore %s", kube.NamespaceAndName(&restore)).Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
// Only try to delete the backup object from kube if everything preceding went smoothly
|
||||
err = c.backupClient.Backups(backup.Namespace).Delete(context.TODO(), backup.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
if err := r.Delete(ctx, backup); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "error deleting backup %s", kube.NamespaceAndName(backup)).Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
c.metrics.RegisterBackupDeletionSuccess(backupScheduleName)
|
||||
r.metrics.RegisterBackupDeletionSuccess(backupScheduleName)
|
||||
} else {
|
||||
c.metrics.RegisterBackupDeletionFailed(backupScheduleName)
|
||||
r.metrics.RegisterBackupDeletionFailed(backupScheduleName)
|
||||
}
|
||||
|
||||
// Update status to processed and record errors
|
||||
req, err = c.patchDeleteBackupRequest(req, func(r *velerov1api.DeleteBackupRequest) {
|
||||
if _, err := r.patchDeleteBackupRequest(ctx, dbr, func(r *velerov1api.DeleteBackupRequest) {
|
||||
r.Status.Phase = velerov1api.DeleteBackupRequestPhaseProcessed
|
||||
r.Status.Errors = errs
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Everything deleted correctly, so we can delete all DeleteBackupRequests for this backup
|
||||
if len(errs) == 0 {
|
||||
listOptions := pkgbackup.NewDeleteBackupRequestListOptions(backup.Name, string(backup.UID))
|
||||
err = c.deleteBackupRequestClient.DeleteBackupRequests(req.Namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, listOptions)
|
||||
labelSelector, err := labels.Parse(fmt.Sprintf("%s=%s,%s=%s", velerov1api.BackupNameLabel, label.GetValidName(backup.Name), velerov1api.BackupUIDLabel, backup.UID))
|
||||
if err != nil {
|
||||
// Should not be here
|
||||
r.logger.WithError(err).WithField("backup", kube.NamespaceAndName(backup)).Error("error creating label selector for the backup for deleting DeleteBackupRequests")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
alldbr := &velerov1api.DeleteBackupRequest{}
|
||||
err = r.DeleteAllOf(ctx, alldbr, client.MatchingLabelsSelector{
|
||||
Selector: labelSelector,
|
||||
}, client.InNamespace(dbr.Namespace))
|
||||
if err != nil {
|
||||
// If this errors, all we can do is log it.
|
||||
c.logger.WithField("backup", kube.NamespaceAndName(backup)).Error("error deleting all associated DeleteBackupRequests after successfully deleting the backup")
|
||||
r.logger.WithError(err).WithField("backup", kube.NamespaceAndName(backup)).Error("error deleting all associated DeleteBackupRequests after successfully deleting the backup")
|
||||
}
|
||||
}
|
||||
log.Infof("Reconciliation done")
|
||||
|
||||
return nil
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func volumeSnapshotterForSnapshotLocation(
|
||||
namespace, snapshotLocationName string,
|
||||
snapshotLocationLister velerov1listers.VolumeSnapshotLocationLister,
|
||||
func volumeSnapshottersForVSL(
|
||||
ctx context.Context,
|
||||
namespace, vslName string,
|
||||
client client.Client,
|
||||
pluginManager clientmgmt.Manager,
|
||||
) (velero.VolumeSnapshotter, error) {
|
||||
snapshotLocation, err := snapshotLocationLister.VolumeSnapshotLocations(namespace).Get(snapshotLocationName)
|
||||
vsl := &velerov1api.VolumeSnapshotLocation{}
|
||||
if err := client.Get(ctx, types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: vslName,
|
||||
}, vsl); err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting volume snapshot location %s", vslName)
|
||||
}
|
||||
volumeSnapshotter, err := pluginManager.GetVolumeSnapshotter(vsl.Spec.Provider)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting volume snapshot location %s", snapshotLocationName)
|
||||
return nil, errors.Wrapf(err, "error getting volume snapshotter for provider %s", vsl.Spec.Provider)
|
||||
}
|
||||
|
||||
volumeSnapshotter, err := pluginManager.GetVolumeSnapshotter(snapshotLocation.Spec.Provider)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting volume snapshotter for provider %s", snapshotLocation.Spec.Provider)
|
||||
}
|
||||
|
||||
if err = volumeSnapshotter.Init(snapshotLocation.Spec.Config); err != nil {
|
||||
return nil, errors.Wrapf(err, "error initializing volume snapshotter for volume snapshot location %s", snapshotLocationName)
|
||||
if err = volumeSnapshotter.Init(vsl.Spec.Config); err != nil {
|
||||
return nil, errors.Wrapf(err, "error initializing volume snapshotter for volume snapshot location %s", vslName)
|
||||
}
|
||||
|
||||
return volumeSnapshotter, nil
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) deleteExistingDeletionRequests(req *velerov1api.DeleteBackupRequest, log logrus.FieldLogger) []error {
|
||||
func (r *backupDeletionReconciler) deleteExistingDeletionRequests(ctx context.Context, req *velerov1api.DeleteBackupRequest, log logrus.FieldLogger) []error {
|
||||
log.Info("Removing existing deletion requests for backup")
|
||||
dbrList := &velerov1api.DeleteBackupRequestList{}
|
||||
selector := label.NewSelectorForBackup(req.Spec.BackupName)
|
||||
dbrs, err := c.deleteBackupRequestLister.DeleteBackupRequests(req.Namespace).List(selector)
|
||||
if err != nil {
|
||||
if err := r.List(ctx, dbrList, &client.ListOptions{
|
||||
Namespace: req.Namespace,
|
||||
LabelSelector: selector,
|
||||
}); err != nil {
|
||||
return []error{errors.Wrap(err, "error listing existing DeleteBackupRequests for backup")}
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, dbr := range dbrs {
|
||||
for _, dbr := range dbrList.Items {
|
||||
if dbr.Name == req.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.deleteBackupRequestClient.DeleteBackupRequests(req.Namespace).Delete(context.TODO(), dbr.Name, metav1.DeleteOptions{}); err != nil {
|
||||
if err := r.Delete(ctx, &dbr); err != nil {
|
||||
errs = append(errs, errors.WithStack(err))
|
||||
} else {
|
||||
log.Infof("deletion request '%s' removed.", dbr.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) deleteResticSnapshots(backup *velerov1api.Backup) []error {
|
||||
if c.resticMgr == nil {
|
||||
func (r *backupDeletionReconciler) deleteResticSnapshots(ctx context.Context, backup *velerov1api.Backup) []error {
|
||||
if r.resticMgr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshots, err := restic.GetSnapshotsInBackup(backup, c.podvolumeBackupLister)
|
||||
snapshots, err := restic.GetSnapshotsInBackup(ctx, backup, r.Client)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), resticTimeout)
|
||||
ctx2, cancelFunc := context.WithTimeout(ctx, resticTimeout)
|
||||
defer cancelFunc()
|
||||
|
||||
var errs []error
|
||||
for _, snapshot := range snapshots {
|
||||
if err := c.resticMgr.Forget(ctx, snapshot); err != nil {
|
||||
if err := r.resticMgr.Forget(ctx2, snapshot); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
const deleteBackupRequestMaxAge = 24 * time.Hour
|
||||
|
||||
func (c *backupDeletionController) deleteExpiredRequests() {
|
||||
c.logger.Info("Checking for expired DeleteBackupRequests")
|
||||
defer c.logger.Info("Done checking for expired DeleteBackupRequests")
|
||||
|
||||
// Our shared informer factory filters on a single namespace, so asking for all is ok here.
|
||||
requests, err := c.deleteBackupRequestLister.List(labels.Everything())
|
||||
func (r *backupDeletionReconciler) patchDeleteBackupRequest(ctx context.Context, req *velerov1api.DeleteBackupRequest, mutate func(*velerov1api.DeleteBackupRequest)) (*velerov1api.DeleteBackupRequest, error) {
|
||||
patchHelper, err := patch.NewHelper(req, r.Client)
|
||||
if err != nil {
|
||||
c.logger.WithError(err).Error("unable to check for expired DeleteBackupRequests")
|
||||
return
|
||||
return nil, errors.Wrap(err, "unable to get the patch helper")
|
||||
}
|
||||
|
||||
now := c.clock.Now()
|
||||
|
||||
for _, req := range requests {
|
||||
if req.Status.Phase != velerov1api.DeleteBackupRequestPhaseProcessed {
|
||||
continue
|
||||
}
|
||||
|
||||
age := now.Sub(req.CreationTimestamp.Time)
|
||||
if age >= deleteBackupRequestMaxAge {
|
||||
reqLog := c.logger.WithFields(logrus.Fields{"namespace": req.Namespace, "name": req.Name})
|
||||
reqLog.Info("Deleting expired DeleteBackupRequest")
|
||||
|
||||
err = c.deleteBackupRequestClient.DeleteBackupRequests(req.Namespace).Delete(context.TODO(), req.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
reqLog.WithError(err).Error("Error deleting DeleteBackupRequest")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) patchDeleteBackupRequest(req *velerov1api.DeleteBackupRequest, mutate func(*velerov1api.DeleteBackupRequest)) (*velerov1api.DeleteBackupRequest, error) {
|
||||
// Record original json
|
||||
oldData, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling original DeleteBackupRequest")
|
||||
}
|
||||
|
||||
// Mutate
|
||||
mutate(req)
|
||||
|
||||
// Record new json
|
||||
newData, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling updated DeleteBackupRequest")
|
||||
if err := patchHelper.Patch(ctx, req); err != nil {
|
||||
return nil, errors.Wrap(err, "error patching the deletebackuprquest")
|
||||
}
|
||||
|
||||
patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating json merge patch for DeleteBackupRequest")
|
||||
}
|
||||
|
||||
req, err = c.deleteBackupRequestClient.DeleteBackupRequests(req.Namespace).Patch(context.TODO(), req.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error patching DeleteBackupRequest")
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *backupDeletionController) patchBackup(backup *velerov1api.Backup, mutate func(*velerov1api.Backup)) (*velerov1api.Backup, error) {
|
||||
func (r *backupDeletionReconciler) patchBackup(ctx context.Context, backup *velerov1api.Backup, mutate func(*velerov1api.Backup)) (*velerov1api.Backup, error) {
|
||||
//TODO: The patchHelper can't be used here because the `backup/xxx/status` does not exist, until the bakcup resource is refactored
|
||||
|
||||
// Record original json
|
||||
oldData, err := json.Marshal(backup)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling original Backup")
|
||||
}
|
||||
|
||||
// Mutate
|
||||
mutate(backup)
|
||||
|
||||
// Record new json
|
||||
newData, err := json.Marshal(backup)
|
||||
newBackup := backup.DeepCopy()
|
||||
mutate(newBackup)
|
||||
newData, err := json.Marshal(newBackup)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshalling updated Backup")
|
||||
}
|
||||
|
||||
patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating json merge patch for Backup")
|
||||
}
|
||||
|
||||
backup, err = c.backupClient.Backups(backup.Namespace).Patch(context.TODO(), backup.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
if err := r.Client.Patch(ctx, backup, client.RawPatch(types.MergePatchType, patchBytes)); err != nil {
|
||||
return nil, errors.Wrap(err, "error patching Backup")
|
||||
}
|
||||
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user