2018-08-20 23:29:54 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2018 the Velero contributors .
2018-08-20 23:29:54 +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 persistence
import (
2018-10-17 21:10:42 +00:00
"compress/gzip"
2018-10-12 17:55:02 +00:00
"encoding/json"
2018-08-20 23:29:54 +00:00
"io"
"io/ioutil"
"strings"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
kerrors "k8s.io/apimachinery/pkg/util/errors"
2019-09-30 21:26:56 +00:00
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/volume"
2018-08-20 23:29:54 +00:00
)
2019-07-24 19:51:20 +00:00
type BackupInfo struct {
2019-08-05 17:15:55 +00:00
Name string
Metadata ,
Contents ,
Log ,
PodVolumeBackups ,
VolumeSnapshots ,
2020-03-12 21:01:14 +00:00
BackupResourceList ,
CSIVolumeSnapshots ,
VolumeSnapshotContents io . Reader
2019-07-24 19:51:20 +00:00
}
2018-08-20 23:29:54 +00:00
// BackupStore defines operations for creating, retrieving, and deleting
2019-01-25 03:33:07 +00:00
// Velero backup and restore data in/from a persistent backup store.
2018-08-20 23:29:54 +00:00
type BackupStore interface {
2018-09-18 15:56:45 +00:00
IsValid ( ) error
2018-08-27 15:44:48 +00:00
ListBackups ( ) ( [ ] string , error )
2018-08-20 23:29:54 +00:00
2020-03-12 21:01:14 +00:00
PutBackup ( info BackupInfo ) error
2019-01-25 03:33:07 +00:00
GetBackupMetadata ( name string ) ( * velerov1api . Backup , error )
2018-10-12 17:55:02 +00:00
GetBackupVolumeSnapshots ( name string ) ( [ ] * volume . Snapshot , error )
2019-07-24 19:51:20 +00:00
GetPodVolumeBackups ( name string ) ( [ ] * velerov1api . PodVolumeBackup , error )
2018-08-20 23:29:54 +00:00
GetBackupContents ( name string ) ( io . ReadCloser , error )
2020-03-30 18:26:23 +00:00
// TODO(nrb-csi): Any Get methods relevant to the CSI VolumeSnapshots should be added with the client-side PRs.
2019-04-23 23:19:00 +00:00
// BackupExists checks if the backup metadata file exists in object storage.
BackupExists ( bucket , backupName string ) ( bool , error )
2018-08-20 23:29:54 +00:00
DeleteBackup ( name string ) error
PutRestoreLog ( backup , restore string , log io . Reader ) error
PutRestoreResults ( backup , restore string , results io . Reader ) error
2018-09-18 15:56:45 +00:00
DeleteRestore ( name string ) error
2018-08-20 23:29:54 +00:00
2019-01-25 03:33:07 +00:00
GetDownloadURL ( target velerov1api . DownloadTarget ) ( string , error )
2018-08-20 23:29:54 +00:00
}
2018-09-18 15:56:45 +00:00
// DownloadURLTTL is how long a download URL is valid for.
const DownloadURLTTL = 10 * time . Minute
2018-08-20 23:29:54 +00:00
type objectBackupStore struct {
2019-03-14 20:35:06 +00:00
objectStore velero . ObjectStore
2018-08-20 23:29:54 +00:00
bucket string
2018-09-25 19:10:03 +00:00
layout * ObjectStoreLayout
2018-08-20 23:29:54 +00:00
logger logrus . FieldLogger
}
2019-03-14 20:35:06 +00:00
// ObjectStoreGetter is a type that can get a velero.ObjectStore
2018-08-20 23:29:54 +00:00
// from a provider name.
type ObjectStoreGetter interface {
2019-03-14 20:35:06 +00:00
GetObjectStore ( provider string ) ( velero . ObjectStore , error )
2018-08-20 23:29:54 +00:00
}
2019-01-25 03:33:07 +00:00
func NewObjectBackupStore ( location * velerov1api . BackupStorageLocation , objectStoreGetter ObjectStoreGetter , logger logrus . FieldLogger ) ( BackupStore , error ) {
2018-08-20 23:29:54 +00:00
if location . Spec . ObjectStorage == nil {
return nil , errors . New ( "backup storage location does not use object storage" )
}
if location . Spec . Provider == "" {
return nil , errors . New ( "object storage provider name must not be empty" )
}
2019-07-31 15:27:12 +00:00
// trim off any leading/trailing slashes
bucket := strings . Trim ( location . Spec . ObjectStorage . Bucket , "/" )
prefix := strings . Trim ( location . Spec . ObjectStorage . Prefix , "/" )
// if there are any slashes in the middle of 'bucket', the user
// probably put <bucket>/<prefix> in the bucket field, which we
// don't support.
if strings . Contains ( bucket , "/" ) {
return nil , errors . Errorf ( "backup storage location's bucket name %q must not contain a '/' (if using a prefix, put it in the 'Prefix' field instead)" , location . Spec . ObjectStorage . Bucket )
2018-08-20 23:29:54 +00:00
}
2019-08-19 20:05:38 +00:00
// add the bucket name and prefix to the config map so that object stores
// can use them when initializing. The AWS object store uses the bucket
// name to determine the bucket's region when setting up its client.
2018-08-20 23:29:54 +00:00
if location . Spec . ObjectStorage != nil {
if location . Spec . Config == nil {
location . Spec . Config = make ( map [ string ] string )
}
2019-07-31 15:27:12 +00:00
location . Spec . Config [ "bucket" ] = bucket
2019-08-19 20:05:38 +00:00
location . Spec . Config [ "prefix" ] = prefix
2020-03-31 17:59:37 +00:00
// Only include a CACert if it's specified in order to maintain compatibility with plugins that don't expect it.
if location . Spec . ObjectStorage . CACert != nil {
location . Spec . Config [ "caCert" ] = string ( location . Spec . ObjectStorage . CACert )
}
2019-07-31 15:27:12 +00:00
}
objectStore , err := objectStoreGetter . GetObjectStore ( location . Spec . Provider )
if err != nil {
return nil , err
2018-08-20 23:29:54 +00:00
}
if err := objectStore . Init ( location . Spec . Config ) ; err != nil {
return nil , err
}
log := logger . WithFields ( logrus . Fields ( map [ string ] interface { } {
2019-07-31 15:27:12 +00:00
"bucket" : bucket ,
"prefix" : prefix ,
2018-08-20 23:29:54 +00:00
} ) )
return & objectBackupStore {
objectStore : objectStore ,
2019-07-31 15:27:12 +00:00
bucket : bucket ,
layout : NewObjectStoreLayout ( prefix ) ,
2018-08-20 23:29:54 +00:00
logger : log ,
} , nil
}
2018-09-18 15:56:45 +00:00
func ( s * objectBackupStore ) IsValid ( ) error {
dirs , err := s . objectStore . ListCommonPrefixes ( s . bucket , s . layout . rootPrefix , "/" )
if err != nil {
return errors . WithStack ( err )
}
var invalid [ ] string
for _ , dir := range dirs {
subdir := strings . TrimSuffix ( strings . TrimPrefix ( dir , s . layout . rootPrefix ) , "/" )
2018-09-25 19:10:03 +00:00
if ! s . layout . isValidSubdir ( subdir ) {
2018-09-18 15:56:45 +00:00
invalid = append ( invalid , subdir )
}
}
if len ( invalid ) > 0 {
// don't include more than 3 invalid dirs in the error message
if len ( invalid ) > 3 {
return errors . Errorf ( "Backup store contains %d invalid top-level directories: %v" , len ( invalid ) , append ( invalid [ : 3 ] , "..." ) )
}
return errors . Errorf ( "Backup store contains invalid top-level directories: %v" , invalid )
}
return nil
}
2018-08-27 15:44:48 +00:00
func ( s * objectBackupStore ) ListBackups ( ) ( [ ] string , error ) {
2018-09-25 19:10:03 +00:00
prefixes , err := s . objectStore . ListCommonPrefixes ( s . bucket , s . layout . subdirs [ "backups" ] , "/" )
2018-08-20 23:29:54 +00:00
if err != nil {
return nil , err
}
if len ( prefixes ) == 0 {
2018-08-27 15:44:48 +00:00
return [ ] string { } , nil
2018-08-20 23:29:54 +00:00
}
2018-08-27 15:44:48 +00:00
output := make ( [ ] string , 0 , len ( prefixes ) )
2018-08-20 23:29:54 +00:00
for _ , prefix := range prefixes {
2019-03-14 20:35:06 +00:00
// values returned from a call to ObjectStore's
2018-09-18 15:56:45 +00:00
// ListCommonPrefixes method return the *full* prefix, inclusive
// of s.backupsPrefix, and include the delimiter ("/") as a suffix. Trim
2018-08-20 23:29:54 +00:00
// each of those off to get the backup name.
2018-09-25 19:10:03 +00:00
backupName := strings . TrimSuffix ( strings . TrimPrefix ( prefix , s . layout . subdirs [ "backups" ] ) , "/" )
2018-08-20 23:29:54 +00:00
2018-08-27 15:44:48 +00:00
output = append ( output , backupName )
2018-08-20 23:29:54 +00:00
}
return output , nil
}
2019-07-24 19:51:20 +00:00
func ( s * objectBackupStore ) PutBackup ( info BackupInfo ) error {
if err := seekAndPutObject ( s . objectStore , s . bucket , s . layout . getBackupLogKey ( info . Name ) , info . Log ) ; err != nil {
2018-08-20 23:29:54 +00:00
// Uploading the log file is best-effort; if it fails, we log the error but it doesn't impact the
// backup's status.
2019-07-24 19:51:20 +00:00
s . logger . WithError ( err ) . WithField ( "backup" , info . Name ) . Error ( "Error uploading log file" )
2018-08-20 23:29:54 +00:00
}
2019-07-24 19:51:20 +00:00
if info . Metadata == nil {
2018-08-20 23:29:54 +00:00
// If we don't have metadata, something failed, and there's no point in continuing. An object
// storage bucket that is missing the metadata file can't be restored, nor can its logs be
// viewed.
return nil
}
2019-07-24 19:51:20 +00:00
if err := seekAndPutObject ( s . objectStore , s . bucket , s . layout . getBackupMetadataKey ( info . Name ) , info . Metadata ) ; err != nil {
2018-08-20 23:29:54 +00:00
// failure to upload metadata file is a hard-stop
return err
}
2019-07-24 19:51:20 +00:00
if err := seekAndPutObject ( s . objectStore , s . bucket , s . layout . getBackupContentsKey ( info . Name ) , info . Contents ) ; err != nil {
deleteErr := s . objectStore . DeleteObject ( s . bucket , s . layout . getBackupMetadataKey ( info . Name ) )
2018-08-20 23:29:54 +00:00
return kerrors . NewAggregate ( [ ] error { err , deleteErr } )
}
2020-04-13 17:37:39 +00:00
// Since the logic for all of these files is the exact same except for the name and the contents,
// use a map literal to iterate through them and write them to the bucket.
var backupObjs = map [ string ] io . Reader {
s . layout . getPodVolumeBackupsKey ( info . Name ) : info . PodVolumeBackups ,
s . layout . getBackupVolumeSnapshotsKey ( info . Name ) : info . VolumeSnapshots ,
s . layout . getBackupResourceListKey ( info . Name ) : info . BackupResourceList ,
s . layout . getCSIVolumeSnapshotKey ( info . Name ) : info . CSIVolumeSnapshots ,
s . layout . getVolumeSnapshotContentsKey ( info . Name ) : info . VolumeSnapshotContents ,
}
for key , reader := range backupObjs {
if err := seekAndPutObject ( s . objectStore , s . bucket , key , reader ) ; err != nil {
2020-03-30 18:26:23 +00:00
errs := [ ] error { err }
2020-03-12 21:01:14 +00:00
2020-04-16 19:17:41 +00:00
// attempt to clean up the backup contents and metadata if we fail to upload and of the extra files.
deleteErr := s . objectStore . DeleteObject ( s . bucket , s . layout . getBackupContentsKey ( info . Name ) )
2020-03-30 18:26:23 +00:00
errs = append ( errs , deleteErr )
2020-03-12 21:01:14 +00:00
2020-03-30 18:26:23 +00:00
deleteErr = s . objectStore . DeleteObject ( s . bucket , s . layout . getBackupMetadataKey ( info . Name ) )
errs = append ( errs , deleteErr )
return kerrors . NewAggregate ( errs )
}
2020-03-12 21:01:14 +00:00
}
2018-08-20 23:29:54 +00:00
return nil
}
2019-01-25 03:33:07 +00:00
func ( s * objectBackupStore ) GetBackupMetadata ( name string ) ( * velerov1api . Backup , error ) {
2019-03-27 22:49:42 +00:00
metadataKey := s . layout . getBackupMetadataKey ( name )
2019-01-25 03:33:07 +00:00
res , err := s . objectStore . GetObject ( s . bucket , metadataKey )
2018-08-20 23:29:54 +00:00
if err != nil {
return nil , err
}
defer res . Close ( )
data , err := ioutil . ReadAll ( res )
if err != nil {
return nil , errors . WithStack ( err )
}
2019-01-25 03:33:07 +00:00
decoder := scheme . Codecs . UniversalDecoder ( velerov1api . SchemeGroupVersion )
2018-08-20 23:29:54 +00:00
obj , _ , err := decoder . Decode ( data , nil , nil )
if err != nil {
return nil , errors . WithStack ( err )
}
2019-01-25 03:33:07 +00:00
backupObj , ok := obj . ( * velerov1api . Backup )
2018-08-20 23:29:54 +00:00
if ! ok {
2019-01-25 03:33:07 +00:00
return nil , errors . Errorf ( "unexpected type for %s/%s: %T" , s . bucket , metadataKey , obj )
2018-08-20 23:29:54 +00:00
}
return backupObj , nil
2018-10-12 17:55:02 +00:00
}
2019-08-05 21:42:01 +00:00
func ( s * objectBackupStore ) GetBackupVolumeSnapshots ( name string ) ( [ ] * volume . Snapshot , error ) {
// if the volumesnapshots file doesn't exist, we don't want to return an error, since
// a legacy backup or a backup with no snapshots would not have this file, so check for
// its existence before attempting to get its contents.
res , err := tryGet ( s . objectStore , s . bucket , s . layout . getBackupVolumeSnapshotsKey ( name ) )
2018-10-17 21:10:42 +00:00
if err != nil {
2019-08-05 21:42:01 +00:00
return nil , err
2018-10-17 21:10:42 +00:00
}
2019-08-05 21:42:01 +00:00
if res == nil {
return nil , nil
}
defer res . Close ( )
2018-10-17 21:10:42 +00:00
2019-08-05 21:42:01 +00:00
var volumeSnapshots [ ] * volume . Snapshot
if err := decode ( res , & volumeSnapshots ) ; err != nil {
return nil , err
2018-10-17 21:10:42 +00:00
}
2019-08-05 21:42:01 +00:00
return volumeSnapshots , nil
2018-10-17 21:10:42 +00:00
}
2019-08-05 21:42:01 +00:00
// tryGet returns the object with the given key if it exists, nil if it does not exist,
// or an error if it was unable to check existence or get the object.
func tryGet ( objectStore velero . ObjectStore , bucket , key string ) ( io . ReadCloser , error ) {
exists , err := objectStore . ObjectExists ( bucket , key )
2018-10-17 21:10:42 +00:00
if err != nil {
return nil , errors . WithStack ( err )
}
2019-08-05 21:42:01 +00:00
if ! exists {
2018-10-17 21:10:42 +00:00
return nil , nil
}
2019-08-05 21:42:01 +00:00
return objectStore . GetObject ( bucket , key )
}
2018-10-12 17:55:02 +00:00
2019-08-05 21:42:01 +00:00
// decode extracts a .json.gz file reader into the object pointed to
// by 'into'.
func decode ( jsongzReader io . Reader , into interface { } ) error {
gzr , err := gzip . NewReader ( jsongzReader )
2018-10-17 21:10:42 +00:00
if err != nil {
2019-08-05 21:42:01 +00:00
return errors . WithStack ( err )
2018-10-17 21:10:42 +00:00
}
defer gzr . Close ( )
2019-08-05 21:42:01 +00:00
if err := json . NewDecoder ( gzr ) . Decode ( into ) ; err != nil {
return errors . Wrap ( err , "error decoding object data" )
2018-10-12 17:55:02 +00:00
}
2018-08-20 23:29:54 +00:00
2019-08-05 21:42:01 +00:00
return nil
2018-08-20 23:29:54 +00:00
}
2019-07-24 19:51:20 +00:00
func ( s * objectBackupStore ) GetPodVolumeBackups ( name string ) ( [ ] * velerov1api . PodVolumeBackup , error ) {
// if the podvolumebackups file doesn't exist, we don't want to return an error, since
2019-08-05 21:42:01 +00:00
// a legacy backup or a backup with no pod volume backups would not have this file, so
// check for its existence before attempting to get its contents.
res , err := tryGet ( s . objectStore , s . bucket , s . layout . getPodVolumeBackupsKey ( name ) )
2019-07-24 19:51:20 +00:00
if err != nil {
2019-08-05 21:42:01 +00:00
return nil , err
2019-07-24 19:51:20 +00:00
}
2019-08-05 21:42:01 +00:00
if res == nil {
2019-07-24 19:51:20 +00:00
return nil , nil
}
defer res . Close ( )
var podVolumeBackups [ ] * velerov1api . PodVolumeBackup
2019-08-05 21:42:01 +00:00
if err := decode ( res , & podVolumeBackups ) ; err != nil {
return nil , err
2019-07-24 19:51:20 +00:00
}
return podVolumeBackups , nil
}
2018-08-20 23:29:54 +00:00
func ( s * objectBackupStore ) GetBackupContents ( name string ) ( io . ReadCloser , error ) {
2018-09-18 15:56:45 +00:00
return s . objectStore . GetObject ( s . bucket , s . layout . getBackupContentsKey ( name ) )
2018-08-20 23:29:54 +00:00
}
2019-04-23 23:19:00 +00:00
func ( s * objectBackupStore ) BackupExists ( bucket , backupName string ) ( bool , error ) {
return s . objectStore . ObjectExists ( bucket , s . layout . getBackupMetadataKey ( backupName ) )
}
2018-08-20 23:29:54 +00:00
func ( s * objectBackupStore ) DeleteBackup ( name string ) error {
2018-09-18 15:56:45 +00:00
objects , err := s . objectStore . ListObjects ( s . bucket , s . layout . getBackupDir ( name ) )
if err != nil {
return err
}
var errs [ ] error
for _ , key := range objects {
s . logger . WithFields ( logrus . Fields {
"key" : key ,
} ) . Debug ( "Trying to delete object" )
if err := s . objectStore . DeleteObject ( s . bucket , key ) ; err != nil {
errs = append ( errs , err )
}
}
return errors . WithStack ( kerrors . NewAggregate ( errs ) )
}
func ( s * objectBackupStore ) DeleteRestore ( name string ) error {
objects , err := s . objectStore . ListObjects ( s . bucket , s . layout . getRestoreDir ( name ) )
2018-08-20 23:29:54 +00:00
if err != nil {
return err
}
var errs [ ] error
for _ , key := range objects {
s . logger . WithFields ( logrus . Fields {
"key" : key ,
} ) . Debug ( "Trying to delete object" )
if err := s . objectStore . DeleteObject ( s . bucket , key ) ; err != nil {
errs = append ( errs , err )
}
}
return errors . WithStack ( kerrors . NewAggregate ( errs ) )
}
func ( s * objectBackupStore ) PutRestoreLog ( backup string , restore string , log io . Reader ) error {
2018-09-18 15:56:45 +00:00
return s . objectStore . PutObject ( s . bucket , s . layout . getRestoreLogKey ( restore ) , log )
2018-08-20 23:29:54 +00:00
}
func ( s * objectBackupStore ) PutRestoreResults ( backup string , restore string , results io . Reader ) error {
2018-09-18 15:56:45 +00:00
return s . objectStore . PutObject ( s . bucket , s . layout . getRestoreResultsKey ( restore ) , results )
2018-08-20 23:29:54 +00:00
}
2019-01-25 03:33:07 +00:00
func ( s * objectBackupStore ) GetDownloadURL ( target velerov1api . DownloadTarget ) ( string , error ) {
2018-08-20 23:29:54 +00:00
switch target . Kind {
2019-01-25 03:33:07 +00:00
case velerov1api . DownloadTargetKindBackupContents :
2018-09-18 15:56:45 +00:00
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getBackupContentsKey ( target . Name ) , DownloadURLTTL )
2019-01-25 03:33:07 +00:00
case velerov1api . DownloadTargetKindBackupLog :
2018-09-18 15:56:45 +00:00
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getBackupLogKey ( target . Name ) , DownloadURLTTL )
2019-01-25 03:33:07 +00:00
case velerov1api . DownloadTargetKindBackupVolumeSnapshots :
2018-10-22 16:37:30 +00:00
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getBackupVolumeSnapshotsKey ( target . Name ) , DownloadURLTTL )
2019-08-05 18:23:20 +00:00
case velerov1api . DownloadTargetKindBackupResourceList :
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getBackupResourceListKey ( target . Name ) , DownloadURLTTL )
2019-01-25 03:33:07 +00:00
case velerov1api . DownloadTargetKindRestoreLog :
2018-09-18 15:56:45 +00:00
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getRestoreLogKey ( target . Name ) , DownloadURLTTL )
2019-01-25 03:33:07 +00:00
case velerov1api . DownloadTargetKindRestoreResults :
2018-09-18 15:56:45 +00:00
return s . objectStore . CreateSignedURL ( s . bucket , s . layout . getRestoreResultsKey ( target . Name ) , DownloadURLTTL )
2018-08-20 23:29:54 +00:00
default :
return "" , errors . Errorf ( "unsupported download target kind %q" , target . Kind )
}
}
func seekToBeginning ( r io . Reader ) error {
seeker , ok := r . ( io . Seeker )
if ! ok {
return nil
}
_ , err := seeker . Seek ( 0 , 0 )
return err
}
2019-03-14 20:35:06 +00:00
func seekAndPutObject ( objectStore velero . ObjectStore , bucket , key string , file io . Reader ) error {
2018-08-20 23:29:54 +00:00
if file == nil {
return nil
}
if err := seekToBeginning ( file ) ; err != nil {
return errors . WithStack ( err )
}
return objectStore . PutObject ( bucket , key , file )
}