2017-08-02 17:27:17 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2017 the Velero contributors .
2017-08-02 17:27:17 +00:00
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 controller
import (
2018-08-27 15:44:48 +00:00
"encoding/json"
2017-08-02 17:27:17 +00:00
"time"
2017-09-14 21:27:31 +00:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kuberrs "k8s.io/apimachinery/pkg/api/errors"
2018-07-03 20:30:30 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-07-17 21:24:58 +00:00
"k8s.io/apimachinery/pkg/labels"
2018-08-27 15:44:48 +00:00
"k8s.io/apimachinery/pkg/types"
2018-07-17 21:24:58 +00:00
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/cache"
2017-08-02 17:27:17 +00:00
2019-01-25 03:33:07 +00:00
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
2019-04-23 23:58:59 +00:00
"github.com/heptio/velero/pkg/label"
2019-01-25 03:33:07 +00:00
"github.com/heptio/velero/pkg/persistence"
2019-03-15 18:32:11 +00:00
"github.com/heptio/velero/pkg/plugin/clientmgmt"
2017-08-02 17:27:17 +00:00
)
type backupSyncController struct {
2018-08-21 23:52:49 +00:00
* genericController
2019-01-25 03:33:07 +00:00
backupClient velerov1client . BackupsGetter
backupLocationClient velerov1client . BackupStorageLocationsGetter
2019-07-24 19:51:20 +00:00
podVolumeBackupClient velerov1client . PodVolumeBackupsGetter
2018-08-21 23:52:49 +00:00
backupLister listers . BackupLister
backupStorageLocationLister listers . BackupStorageLocationLister
2019-07-24 19:51:20 +00:00
podVolumeBackupLister listers . PodVolumeBackupLister
2018-08-21 23:52:49 +00:00
namespace string
2018-08-24 18:07:01 +00:00
defaultBackupLocation string
2019-03-15 18:32:11 +00:00
newPluginManager func ( logrus . FieldLogger ) clientmgmt . Manager
2019-01-25 03:33:07 +00:00
newBackupStore func ( * velerov1api . BackupStorageLocation , persistence . ObjectStoreGetter , logrus . FieldLogger ) ( persistence . BackupStore , error )
2017-08-02 17:27:17 +00:00
}
2017-09-14 21:27:31 +00:00
func NewBackupSyncController (
2019-01-25 03:33:07 +00:00
backupClient velerov1client . BackupsGetter ,
backupLocationClient velerov1client . BackupStorageLocationsGetter ,
2019-07-24 19:51:20 +00:00
podVolumeBackupClient velerov1client . PodVolumeBackupsGetter ,
2018-08-21 23:52:49 +00:00
backupInformer informers . BackupInformer ,
backupStorageLocationInformer informers . BackupStorageLocationInformer ,
2019-07-24 19:51:20 +00:00
podVolumeBackupInformer informers . PodVolumeBackupInformer ,
2017-09-14 21:27:31 +00:00
syncPeriod time . Duration ,
2018-05-02 21:43:44 +00:00
namespace string ,
2018-08-24 18:07:01 +00:00
defaultBackupLocation string ,
2019-03-15 18:32:11 +00:00
newPluginManager func ( logrus . FieldLogger ) clientmgmt . Manager ,
2017-12-12 23:22:46 +00:00
logger logrus . FieldLogger ,
2017-09-14 21:27:31 +00:00
) Interface {
2017-08-02 17:27:17 +00:00
if syncPeriod < time . Minute {
2017-09-14 21:27:31 +00:00
logger . Infof ( "Provided backup sync period %v is too short. Setting to 1 minute" , syncPeriod )
2017-08-02 17:27:17 +00:00
syncPeriod = time . Minute
}
2018-08-21 23:52:49 +00:00
c := & backupSyncController {
genericController : newGenericController ( "backup-sync" , logger ) ,
2018-08-27 15:44:48 +00:00
backupClient : backupClient ,
backupLocationClient : backupLocationClient ,
2019-07-24 19:51:20 +00:00
podVolumeBackupClient : podVolumeBackupClient ,
2018-08-21 23:52:49 +00:00
namespace : namespace ,
2018-08-24 18:07:01 +00:00
defaultBackupLocation : defaultBackupLocation ,
2018-08-21 23:52:49 +00:00
backupLister : backupInformer . Lister ( ) ,
backupStorageLocationLister : backupStorageLocationInformer . Lister ( ) ,
2019-07-24 19:51:20 +00:00
podVolumeBackupLister : podVolumeBackupInformer . Lister ( ) ,
2018-08-21 23:52:49 +00:00
2018-08-25 19:53:56 +00:00
// use variables to refer to these functions so they can be
// replaced with fakes for testing.
newPluginManager : newPluginManager ,
2018-08-20 23:29:54 +00:00
newBackupStore : persistence . NewObjectBackupStore ,
2017-08-02 17:27:17 +00:00
}
2018-08-21 23:52:49 +00:00
c . resyncFunc = c . run
c . resyncPeriod = syncPeriod
c . cacheSyncWaiters = [ ] cache . InformerSynced {
backupInformer . Informer ( ) . HasSynced ,
backupStorageLocationInformer . Informer ( ) . HasSynced ,
2018-07-17 21:24:58 +00:00
}
2018-08-21 23:52:49 +00:00
return c
2017-08-02 17:27:17 +00:00
}
2019-01-25 03:33:07 +00:00
func shouldSync ( location * velerov1api . BackupStorageLocation , now time . Time , backupStore persistence . BackupStore , log logrus . FieldLogger ) ( bool , string ) {
2018-08-27 15:44:48 +00:00
log = log . WithFields ( map [ string ] interface { } {
"lastSyncedRevision" : location . Status . LastSyncedRevision ,
"lastSyncedTime" : location . Status . LastSyncedTime . Time . Format ( time . RFC1123Z ) ,
} )
revision , err := backupStore . GetRevision ( )
if err != nil {
2019-01-24 16:55:08 +00:00
log . WithError ( err ) . Debugf ( "Unable to get backup store's revision file, syncing (this is not an error if a v0.10+ backup has not yet been taken into this location)" )
2018-08-27 15:44:48 +00:00
return true , ""
}
log = log . WithField ( "revision" , revision )
if location . Status . LastSyncedTime . Add ( time . Hour ) . Before ( now ) {
2019-01-24 16:55:08 +00:00
log . Debugf ( "Backup location hasn't been synced in more than %s, syncing" , time . Hour )
2018-08-27 15:44:48 +00:00
return true , revision
}
if string ( location . Status . LastSyncedRevision ) != revision {
2019-01-24 16:55:08 +00:00
log . Debugf ( "Backup location hasn't been synced since its last modification, syncing" )
2018-08-27 15:44:48 +00:00
return true , revision
}
2019-01-24 16:55:08 +00:00
log . Debugf ( "Backup location's contents haven't changed since last sync, not syncing" )
2018-08-27 15:44:48 +00:00
return false , ""
}
2018-10-30 19:14:40 +00:00
// orderedBackupLocations returns a new slice with the default backup location first (if it exists),
// followed by the rest of the locations in no particular order.
2019-01-25 03:33:07 +00:00
func orderedBackupLocations ( locations [ ] * velerov1api . BackupStorageLocation , defaultLocationName string ) [ ] * velerov1api . BackupStorageLocation {
var result [ ] * velerov1api . BackupStorageLocation
2018-10-30 19:14:40 +00:00
for i := range locations {
if locations [ i ] . Name == defaultLocationName {
// put the default location first
result = append ( result , locations [ i ] )
// append everything before the default
result = append ( result , locations [ : i ] ... )
// append everything after the default
result = append ( result , locations [ i + 1 : ] ... )
return result
}
}
return locations
}
2017-08-02 17:27:17 +00:00
func ( c * backupSyncController ) run ( ) {
2018-11-02 21:55:47 +00:00
c . logger . Debug ( "Checking for existing backup storage locations to sync into cluster" )
2018-08-21 23:52:49 +00:00
locations , err := c . backupStorageLocationLister . BackupStorageLocations ( c . namespace ) . List ( labels . Everything ( ) )
2017-08-02 17:27:17 +00:00
if err != nil {
2018-08-21 23:52:49 +00:00
c . logger . WithError ( errors . WithStack ( err ) ) . Error ( "Error getting backup storage locations from lister" )
2017-08-02 17:27:17 +00:00
return
}
2018-08-24 18:07:01 +00:00
// sync the default location first, if it exists
locations = orderedBackupLocations ( locations , c . defaultBackupLocation )
2017-08-02 17:27:17 +00:00
2018-08-21 23:52:49 +00:00
pluginManager := c . newPluginManager ( c . logger )
2018-08-27 15:44:48 +00:00
defer pluginManager . CleanupClients ( )
2017-09-14 21:27:31 +00:00
2018-08-21 23:52:49 +00:00
for _ , location := range locations {
log := c . logger . WithField ( "backupLocation" , location . Name )
2018-07-17 21:24:58 +00:00
2018-08-20 23:29:54 +00:00
backupStore , err := c . newBackupStore ( location , pluginManager , log )
2018-08-21 23:52:49 +00:00
if err != nil {
2019-07-24 19:51:20 +00:00
log . WithError ( err ) . Error ( "Error getting backup store for this location" )
2018-08-21 23:52:49 +00:00
continue
}
2018-07-03 20:30:30 +00:00
2018-08-27 15:44:48 +00:00
ok , revision := shouldSync ( location , time . Now ( ) . UTC ( ) , backupStore , log )
if ! ok {
continue
}
2019-07-24 19:51:20 +00:00
log . Info ( "Syncing contents of backup store into cluster" )
2018-08-27 15:44:48 +00:00
res , err := backupStore . ListBackups ( )
2018-07-03 20:30:30 +00:00
if err != nil {
2018-08-20 23:29:54 +00:00
log . WithError ( err ) . Error ( "Error listing backups in backup store" )
2018-08-21 23:52:49 +00:00
continue
}
2018-08-27 15:44:48 +00:00
backupStoreBackups := sets . NewString ( res ... )
log . WithField ( "backupCount" , len ( backupStoreBackups ) ) . Info ( "Got backups from backup store" )
2018-08-21 23:52:49 +00:00
2018-08-27 15:44:48 +00:00
for backupName := range backupStoreBackups {
log = log . WithField ( "backup" , backupName )
2019-07-24 19:51:20 +00:00
log . Debug ( "Checking this backup to see if it needs to be synced into the cluster" )
2018-08-21 23:52:49 +00:00
// use the controller's namespace when getting the backup because that's where we
// are syncing backups to, regardless of the namespace of the cloud backup.
2018-10-23 22:24:08 +00:00
backup , err := c . backupClient . Backups ( c . namespace ) . Get ( backupName , metav1 . GetOptions { } )
2018-08-21 23:52:49 +00:00
if err == nil {
log . Debug ( "Backup already exists in cluster" )
continue
}
2019-07-24 19:51:20 +00:00
2018-07-03 20:30:30 +00:00
if ! kuberrs . IsNotFound ( err ) {
2018-08-21 23:52:49 +00:00
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error getting backup from client, proceeding with sync into cluster" )
2018-07-03 20:30:30 +00:00
}
2018-10-23 22:24:08 +00:00
backup , err = backupStore . GetBackupMetadata ( backupName )
2018-08-27 15:44:48 +00:00
if err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error getting backup metadata from backup store" )
continue
}
backup . Namespace = c . namespace
backup . ResourceVersion = ""
2018-08-21 23:52:49 +00:00
// update the StorageLocation field and label since the name of the location
// may be different in this cluster than in the cluster that created the
// backup.
2018-08-27 15:44:48 +00:00
backup . Spec . StorageLocation = location . Name
if backup . Labels == nil {
backup . Labels = make ( map [ string ] string )
2018-08-21 23:52:49 +00:00
}
2019-04-23 23:58:59 +00:00
backup . Labels [ velerov1api . StorageLocationLabel ] = label . GetValidName ( backup . Spec . StorageLocation )
2019-07-24 19:51:20 +00:00
// process the regular velero backup
backup , err = c . backupClient . Backups ( backup . Namespace ) . Create ( backup )
2018-08-21 23:52:49 +00:00
switch {
case err != nil && kuberrs . IsAlreadyExists ( err ) :
log . Debug ( "Backup already exists in cluster" )
2018-10-12 17:55:02 +00:00
continue
2018-08-21 23:52:49 +00:00
case err != nil && ! kuberrs . IsAlreadyExists ( err ) :
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error syncing backup into cluster" )
2018-10-12 17:55:02 +00:00
continue
2018-08-21 23:52:49 +00:00
default :
log . Debug ( "Synced backup into cluster" )
2018-07-03 20:30:30 +00:00
}
2019-07-24 19:51:20 +00:00
// process the pod volume backups from object store, if any
podVolumeBackups , err := backupStore . GetPodVolumeBackups ( backupName )
if err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error getting pod volumes for this backup from backup store" )
continue
}
for _ , podVolumeBackup := range podVolumeBackups {
log = log . WithField ( "podVolumeBackup" , podVolumeBackup . Name )
log . Debug ( "Checking this pod volume backup to see if it needs to be synced into the cluster" )
for _ , or := range podVolumeBackup . ObjectMeta . OwnerReferences {
if or . Name == backup . Name {
or . UID = backup . UID
}
}
if _ , ok := podVolumeBackup . Labels [ velerov1api . BackupUIDLabel ] ; ok {
podVolumeBackup . Labels [ velerov1api . BackupUIDLabel ] = string ( backup . UID )
}
_ , err = c . podVolumeBackupClient . PodVolumeBackups ( backup . Namespace ) . Create ( podVolumeBackup )
switch {
case err != nil && kuberrs . IsAlreadyExists ( err ) :
log . Debug ( "Pod volume backup already exists in cluster" )
continue
case err != nil && ! kuberrs . IsAlreadyExists ( err ) :
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error syncing pod volume backup into cluster" )
continue
default :
log . Debug ( "Synced pod volume backup into cluster" )
}
}
2017-08-02 17:27:17 +00:00
}
2018-07-17 21:24:58 +00:00
2018-08-27 15:44:48 +00:00
c . deleteOrphanedBackups ( location . Name , backupStoreBackups , log )
// update the location's status's last-synced fields
patch := map [ string ] interface { } {
"status" : map [ string ] interface { } {
"lastSyncedTime" : time . Now ( ) . UTC ( ) ,
"lastSyncedRevision" : revision ,
} ,
}
patchBytes , err := json . Marshal ( patch )
if err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error marshaling last-synced patch to JSON" )
continue
}
if _ , err = c . backupLocationClient . BackupStorageLocations ( c . namespace ) . Patch (
location . Name ,
types . MergePatchType ,
patchBytes ,
) ; err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error patching backup location's last-synced time and revision" )
continue
}
2018-08-21 23:52:49 +00:00
}
2018-07-17 21:24:58 +00:00
}
2019-01-25 03:33:07 +00:00
func patchStorageLocation ( backup * velerov1api . Backup , client velerov1client . BackupInterface , location string ) error {
2018-10-23 22:24:08 +00:00
patch := map [ string ] interface { } {
"spec" : map [ string ] interface { } {
"storageLocation" : location ,
} ,
}
patchBytes , err := json . Marshal ( patch )
if err != nil {
return errors . WithStack ( err )
}
if _ , err := client . Patch ( backup . Name , types . MergePatchType , patchBytes ) ; err != nil {
return errors . WithStack ( err )
}
return nil
}
2019-07-24 19:51:20 +00:00
// deleteOrphanedBackups deletes backup objects (CRDs) from Kubernetes that have the specified location
2018-08-21 23:52:49 +00:00
// and a phase of Completed, but no corresponding backup in object storage.
2019-07-24 19:51:20 +00:00
func ( c * backupSyncController ) deleteOrphanedBackups ( locationName string , backupStoreBackups sets . String , log logrus . FieldLogger ) {
2018-08-21 23:52:49 +00:00
locationSelector := labels . Set ( map [ string ] string {
2019-04-23 23:58:59 +00:00
velerov1api . StorageLocationLabel : label . GetValidName ( locationName ) ,
2018-08-21 23:52:49 +00:00
} ) . AsSelector ( )
backups , err := c . backupLister . Backups ( c . namespace ) . List ( locationSelector )
2018-07-17 21:24:58 +00:00
if err != nil {
2018-08-21 23:52:49 +00:00
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error listing backups from cluster" )
return
2018-07-17 21:24:58 +00:00
}
if len ( backups ) == 0 {
return
}
for _ , backup := range backups {
2018-08-21 23:52:49 +00:00
log = log . WithField ( "backup" , backup . Name )
2019-07-24 19:51:20 +00:00
if backup . Status . Phase != velerov1api . BackupPhaseCompleted || backupStoreBackups . Has ( backup . Name ) {
2018-08-21 23:52:49 +00:00
continue
2018-07-17 21:24:58 +00:00
}
2018-08-27 15:44:48 +00:00
if err := c . backupClient . Backups ( backup . Namespace ) . Delete ( backup . Name , nil ) ; err != nil {
2018-08-21 23:52:49 +00:00
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error deleting orphaned backup from cluster" )
} else {
log . Debug ( "Deleted orphaned backup from cluster" )
}
}
2017-08-02 17:27:17 +00:00
}