backup deletion controller: use backup location for object store

Signed-off-by: Steve Kriss <steve@heptio.com>
pull/799/head
Steve Kriss 2018-08-15 16:27:27 -07:00
parent c6f488f75f
commit bab08ed1a6
3 changed files with 93 additions and 33 deletions

View File

@ -682,13 +682,13 @@ func (s *server) runControllers(config *api.Config) error {
s.arkClient.ArkV1(), // deleteBackupRequestClient s.arkClient.ArkV1(), // deleteBackupRequestClient
s.arkClient.ArkV1(), // backupClient s.arkClient.ArkV1(), // backupClient
s.blockStore, s.blockStore,
s.objectStore,
config.BackupStorageProvider.Bucket,
s.sharedInformerFactory.Ark().V1().Restores(), s.sharedInformerFactory.Ark().V1().Restores(),
s.arkClient.ArkV1(), // restoreClient s.arkClient.ArkV1(), // restoreClient
backupTracker, backupTracker,
s.resticManager, s.resticManager,
s.sharedInformerFactory.Ark().V1().PodVolumeBackups(), s.sharedInformerFactory.Ark().V1().PodVolumeBackups(),
s.sharedInformerFactory.Ark().V1().BackupStorageLocations(),
s.pluginRegistry,
) )
wg.Add(1) wg.Add(1)
go func() { go func() {

View File

@ -28,6 +28,7 @@ import (
arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1" arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1"
informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1" informers "github.com/heptio/ark/pkg/generated/informers/externalversions/ark/v1"
listers "github.com/heptio/ark/pkg/generated/listers/ark/v1" listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
"github.com/heptio/ark/pkg/plugin"
"github.com/heptio/ark/pkg/restic" "github.com/heptio/ark/pkg/restic"
"github.com/heptio/ark/pkg/util/kube" "github.com/heptio/ark/pkg/util/kube"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -50,17 +51,17 @@ type backupDeletionController struct {
deleteBackupRequestLister listers.DeleteBackupRequestLister deleteBackupRequestLister listers.DeleteBackupRequestLister
backupClient arkv1client.BackupsGetter backupClient arkv1client.BackupsGetter
blockStore cloudprovider.BlockStore blockStore cloudprovider.BlockStore
objectStore cloudprovider.ObjectStore
bucket string
restoreLister listers.RestoreLister restoreLister listers.RestoreLister
restoreClient arkv1client.RestoresGetter restoreClient arkv1client.RestoresGetter
backupTracker BackupTracker backupTracker BackupTracker
resticMgr restic.RepositoryManager resticMgr restic.RepositoryManager
podvolumeBackupLister listers.PodVolumeBackupLister podvolumeBackupLister listers.PodVolumeBackupLister
backupLocationLister listers.BackupStorageLocationLister
deleteBackupDir cloudprovider.DeleteBackupDirFunc pluginRegistry plugin.Registry
processRequestFunc func(*v1.DeleteBackupRequest) error deleteBackupDir cloudprovider.DeleteBackupDirFunc
clock clock.Clock processRequestFunc func(*v1.DeleteBackupRequest) error
clock clock.Clock
newPluginManager func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager
} }
// NewBackupDeletionController creates a new backup deletion controller. // NewBackupDeletionController creates a new backup deletion controller.
@ -70,13 +71,13 @@ func NewBackupDeletionController(
deleteBackupRequestClient arkv1client.DeleteBackupRequestsGetter, deleteBackupRequestClient arkv1client.DeleteBackupRequestsGetter,
backupClient arkv1client.BackupsGetter, backupClient arkv1client.BackupsGetter,
blockStore cloudprovider.BlockStore, blockStore cloudprovider.BlockStore,
objectStore cloudprovider.ObjectStore,
bucket string,
restoreInformer informers.RestoreInformer, restoreInformer informers.RestoreInformer,
restoreClient arkv1client.RestoresGetter, restoreClient arkv1client.RestoresGetter,
backupTracker BackupTracker, backupTracker BackupTracker,
resticMgr restic.RepositoryManager, resticMgr restic.RepositoryManager,
podvolumeBackupInformer informers.PodVolumeBackupInformer, podvolumeBackupInformer informers.PodVolumeBackupInformer,
backupLocationInformer informers.BackupStorageLocationInformer,
pluginRegistry plugin.Registry,
) Interface { ) Interface {
c := &backupDeletionController{ c := &backupDeletionController{
genericController: newGenericController("backup-deletion", logger), genericController: newGenericController("backup-deletion", logger),
@ -84,16 +85,20 @@ func NewBackupDeletionController(
deleteBackupRequestLister: deleteBackupRequestInformer.Lister(), deleteBackupRequestLister: deleteBackupRequestInformer.Lister(),
backupClient: backupClient, backupClient: backupClient,
blockStore: blockStore, blockStore: blockStore,
objectStore: objectStore,
bucket: bucket,
restoreLister: restoreInformer.Lister(), restoreLister: restoreInformer.Lister(),
restoreClient: restoreClient, restoreClient: restoreClient,
backupTracker: backupTracker, backupTracker: backupTracker,
resticMgr: resticMgr, resticMgr: resticMgr,
podvolumeBackupLister: podvolumeBackupInformer.Lister(),
backupLocationLister: backupLocationInformer.Lister(),
pluginRegistry: pluginRegistry,
podvolumeBackupLister: podvolumeBackupInformer.Lister(), // use variables to refer to these functions so they can be
deleteBackupDir: cloudprovider.DeleteBackupDir, // replaced with fakes for testing.
clock: &clock.RealClock{}, deleteBackupDir: cloudprovider.DeleteBackupDir,
newPluginManager: plugin.NewManager,
clock: &clock.RealClock{},
} }
c.syncHandler = c.processQueueItem c.syncHandler = c.processQueueItem
@ -102,6 +107,7 @@ func NewBackupDeletionController(
deleteBackupRequestInformer.Informer().HasSynced, deleteBackupRequestInformer.Informer().HasSynced,
restoreInformer.Informer().HasSynced, restoreInformer.Informer().HasSynced,
podvolumeBackupInformer.Informer().HasSynced, podvolumeBackupInformer.Informer().HasSynced,
backupLocationInformer.Informer().HasSynced,
) )
c.processRequestFunc = c.processRequest c.processRequestFunc = c.processRequest
@ -240,7 +246,6 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
var errs []string var errs []string
// Try to delete snapshots
log.Info("Removing PV snapshots") log.Info("Removing PV snapshots")
for _, volumeBackup := range backup.Status.VolumeBackups { for _, volumeBackup := range backup.Status.VolumeBackups {
log.WithField("snapshotID", volumeBackup.SnapshotID).Info("Removing snapshot associated with backup") log.WithField("snapshotID", volumeBackup.SnapshotID).Info("Removing snapshot associated with backup")
@ -249,7 +254,6 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
} }
} }
// Try to delete restic snapshots
log.Info("Removing restic snapshots") log.Info("Removing restic snapshots")
if deleteErrs := c.deleteResticSnapshots(backup); len(deleteErrs) > 0 { if deleteErrs := c.deleteResticSnapshots(backup); len(deleteErrs) > 0 {
for _, err := range deleteErrs { for _, err := range deleteErrs {
@ -257,13 +261,11 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
} }
} }
// Try to delete backup from backup storage
log.Info("Removing backup from backup storage") log.Info("Removing backup from backup storage")
if err := c.deleteBackupDir(log, c.objectStore, c.bucket, backup.Name); err != nil { if err := c.deleteBackupFromStorage(backup, log); err != nil {
errs = append(errs, errors.Wrap(err, "error deleting backup from backup storage").Error()) errs = append(errs, err.Error())
} }
// Try to delete restores
log.Info("Removing restores") log.Info("Removing restores")
if restores, err := c.restoreLister.Restores(backup.Namespace).List(labels.Everything()); err != nil { if restores, err := c.restoreLister.Restores(backup.Namespace).List(labels.Everything()); err != nil {
log.WithError(errors.WithStack(err)).Error("Error listing restore API objects") log.WithError(errors.WithStack(err)).Error("Error listing restore API objects")
@ -312,6 +314,27 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
return nil return nil
} }
func (c *backupDeletionController) deleteBackupFromStorage(backup *v1.Backup, log *logrus.Entry) error {
pluginManager := c.newPluginManager(log, log.Level, c.pluginRegistry)
defer pluginManager.CleanupClients()
backupLocation, err := c.backupLocationLister.BackupStorageLocations(backup.Namespace).Get(backup.Spec.StorageLocation)
if err != nil {
return errors.WithStack(err)
}
objectStore, err := getObjectStoreForLocation(backupLocation, pluginManager)
if err != nil {
return err
}
if err := c.deleteBackupDir(log, objectStore, backupLocation.Spec.ObjectStorage.Bucket, backup.Name); err != nil {
return errors.Wrap(err, "error deleting backup from backup storage")
}
return nil
}
func (c *backupDeletionController) deleteExistingDeletionRequests(req *v1.DeleteBackupRequest, log logrus.FieldLogger) []error { func (c *backupDeletionController) deleteExistingDeletionRequests(req *v1.DeleteBackupRequest, log logrus.FieldLogger) []error {
log.Info("Removing existing deletion requests for backup") log.Info("Removing existing deletion requests for backup")
selector := labels.SelectorFromSet(labels.Set(map[string]string{ selector := labels.SelectorFromSet(labels.Set(map[string]string{

View File

@ -26,10 +26,13 @@ import (
"github.com/heptio/ark/pkg/cloudprovider" "github.com/heptio/ark/pkg/cloudprovider"
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake" "github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
informers "github.com/heptio/ark/pkg/generated/informers/externalversions" informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
"github.com/heptio/ark/pkg/plugin"
pluginmocks "github.com/heptio/ark/pkg/plugin/mocks"
arktest "github.com/heptio/ark/pkg/util/test" arktest "github.com/heptio/ark/pkg/util/test"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -49,13 +52,13 @@ func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
client.ArkV1(), // deleteBackupRequestClient client.ArkV1(), // deleteBackupRequestClient
client.ArkV1(), // backupClient client.ArkV1(), // backupClient
nil, // blockStore nil, // blockStore
nil, // backupService
"bucket",
sharedInformers.Ark().V1().Restores(), sharedInformers.Ark().V1().Restores(),
client.ArkV1(), // restoreClient client.ArkV1(), // restoreClient
NewBackupTracker(), NewBackupTracker(),
nil, // restic repository manager nil, // restic repository manager
sharedInformers.Ark().V1().PodVolumeBackups(), sharedInformers.Ark().V1().PodVolumeBackups(),
sharedInformers.Ark().V1().BackupStorageLocations(),
nil, // pluginRegistry
).(*backupDeletionController) ).(*backupDeletionController)
// Error splitting key // Error splitting key
@ -109,37 +112,51 @@ type backupDeletionControllerTestData struct {
client *fake.Clientset client *fake.Clientset
sharedInformers informers.SharedInformerFactory sharedInformers informers.SharedInformerFactory
blockStore *arktest.FakeBlockStore blockStore *arktest.FakeBlockStore
objectStore *arktest.ObjectStore
controller *backupDeletionController controller *backupDeletionController
req *v1.DeleteBackupRequest req *v1.DeleteBackupRequest
} }
func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletionControllerTestData { func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletionControllerTestData {
client := fake.NewSimpleClientset(objects...) var (
sharedInformers := informers.NewSharedInformerFactory(client, 0) client = fake.NewSimpleClientset(objects...)
blockStore := &arktest.FakeBlockStore{SnapshotsTaken: sets.NewString()} sharedInformers = informers.NewSharedInformerFactory(client, 0)
req := pkgbackup.NewDeleteBackupRequest("foo", "uid") blockStore = &arktest.FakeBlockStore{SnapshotsTaken: sets.NewString()}
pluginManager = &pluginmocks.Manager{}
objectStore = &arktest.ObjectStore{}
req = pkgbackup.NewDeleteBackupRequest("foo", "uid")
)
data := &backupDeletionControllerTestData{ data := &backupDeletionControllerTestData{
client: client, client: client,
sharedInformers: sharedInformers, sharedInformers: sharedInformers,
blockStore: blockStore, blockStore: blockStore,
objectStore: objectStore,
controller: NewBackupDeletionController( controller: NewBackupDeletionController(
arktest.NewLogger(), arktest.NewLogger(),
sharedInformers.Ark().V1().DeleteBackupRequests(), sharedInformers.Ark().V1().DeleteBackupRequests(),
client.ArkV1(), // deleteBackupRequestClient client.ArkV1(), // deleteBackupRequestClient
client.ArkV1(), // backupClient client.ArkV1(), // backupClient
blockStore, blockStore,
nil, // objectStore
"bucket",
sharedInformers.Ark().V1().Restores(), sharedInformers.Ark().V1().Restores(),
client.ArkV1(), // restoreClient client.ArkV1(), // restoreClient
NewBackupTracker(), NewBackupTracker(),
nil, // restic repository manager nil, // restic repository manager
sharedInformers.Ark().V1().PodVolumeBackups(), sharedInformers.Ark().V1().PodVolumeBackups(),
sharedInformers.Ark().V1().BackupStorageLocations(),
nil, // pluginRegistry
).(*backupDeletionController), ).(*backupDeletionController),
req: req, req: req,
} }
data.controller.newPluginManager = func(_ logrus.FieldLogger, _ logrus.Level, _ plugin.Registry) plugin.Manager {
return pluginManager
}
pluginManager.On("GetObjectStore", "objStoreProvider").Return(objectStore, nil)
pluginManager.On("CleanupClients").Return(nil)
req.Namespace = "heptio-ark" req.Namespace = "heptio-ark"
req.Name = "foo-abcde" req.Name = "foo-abcde"
@ -347,6 +364,7 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
t.Run("full delete, no errors", func(t *testing.T) { t.Run("full delete, no errors", func(t *testing.T) {
backup := arktest.NewTestBackup().WithName("foo").WithSnapshot("pv-1", "snap-1").Backup backup := arktest.NewTestBackup().WithName("foo").WithSnapshot("pv-1", "snap-1").Backup
backup.UID = "uid" backup.UID = "uid"
backup.Spec.StorageLocation = "primary"
restore1 := arktest.NewTestRestore("heptio-ark", "restore-1", v1.RestorePhaseCompleted).WithBackup("foo").Restore restore1 := arktest.NewTestRestore("heptio-ark", "restore-1", v1.RestorePhaseCompleted).WithBackup("foo").Restore
restore2 := arktest.NewTestRestore("heptio-ark", "restore-2", v1.RestorePhaseCompleted).WithBackup("foo").Restore restore2 := arktest.NewTestRestore("heptio-ark", "restore-2", v1.RestorePhaseCompleted).WithBackup("foo").Restore
@ -358,6 +376,24 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore2) td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore2)
td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore3) td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore3)
location := &v1.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
},
Spec: v1.BackupStorageLocationSpec{
Provider: "objStoreProvider",
StorageType: v1.StorageType{
ObjectStorage: &v1.ObjectStorageLocation{
Bucket: "bucket",
},
},
},
}
require.NoError(t, td.sharedInformers.Ark().V1().BackupStorageLocations().Informer().GetStore().Add(location))
td.objectStore.On("Init", mock.Anything).Return(nil)
// Clear out req labels to make sure the controller adds them // Clear out req labels to make sure the controller adds them
td.req.Labels = make(map[string]string) td.req.Labels = make(map[string]string)
@ -374,8 +410,9 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
return true, backup, nil return true, backup, nil
}) })
td.controller.deleteBackupDir = func(_ logrus.FieldLogger, _ cloudprovider.ObjectStore, bucket, backupName string) error { td.controller.deleteBackupDir = func(_ logrus.FieldLogger, objectStore cloudprovider.ObjectStore, bucket, backupName string) error {
require.Equal(t, "bucket", bucket) require.NotNil(t, objectStore)
require.Equal(t, location.Spec.ObjectStorage.Bucket, bucket)
require.Equal(t, td.req.Spec.BackupName, backupName) require.Equal(t, td.req.Spec.BackupName, backupName)
return nil return nil
} }
@ -561,13 +598,13 @@ func TestBackupDeletionControllerDeleteExpiredRequests(t *testing.T) {
client.ArkV1(), // deleteBackupRequestClient client.ArkV1(), // deleteBackupRequestClient
client.ArkV1(), // backupClient client.ArkV1(), // backupClient
nil, // blockStore nil, // blockStore
nil, // backupService
"bucket",
sharedInformers.Ark().V1().Restores(), sharedInformers.Ark().V1().Restores(),
client.ArkV1(), // restoreClient client.ArkV1(), // restoreClient
NewBackupTracker(), NewBackupTracker(),
nil, nil,
sharedInformers.Ark().V1().PodVolumeBackups(), sharedInformers.Ark().V1().PodVolumeBackups(),
sharedInformers.Ark().V1().BackupStorageLocations(),
nil, // pluginRegistry
).(*backupDeletionController) ).(*backupDeletionController)
fakeClock := &clock.FakeClock{} fakeClock := &clock.FakeClock{}