2018-02-28 01:35:35 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2018 the Velero contributors .
2018-02-28 01:35:35 +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 restic
import (
"context"
"fmt"
"sync"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-03-18 18:37:46 +00:00
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2018-02-28 01:35:35 +00:00
"k8s.io/client-go/tools/cache"
2019-01-25 03:33:07 +00:00
velerov1api "github.com/heptio/velero/pkg/apis/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/util/boolptr"
2018-02-28 01:35:35 +00:00
)
// Backupper can execute restic backups of volumes in a pod.
type Backupper interface {
// BackupPodVolumes backs up all annotated volumes in a pod.
2019-07-24 19:51:20 +00:00
BackupPodVolumes ( backup * velerov1api . Backup , pod * corev1api . Pod , log logrus . FieldLogger ) ( [ ] * velerov1api . PodVolumeBackup , [ ] error )
2018-02-28 01:35:35 +00:00
}
type backupper struct {
ctx context . Context
2018-06-06 21:32:28 +00:00
repoManager * repositoryManager
2018-06-22 19:07:23 +00:00
repoEnsurer * repositoryEnsurer
2019-03-18 18:37:46 +00:00
pvcClient corev1client . PersistentVolumeClaimsGetter
pvClient corev1client . PersistentVolumesGetter
2018-02-28 01:35:35 +00:00
2019-01-25 03:33:07 +00:00
results map [ string ] chan * velerov1api . PodVolumeBackup
2018-02-28 01:35:35 +00:00
resultsLock sync . Mutex
}
2018-06-06 21:32:28 +00:00
func newBackupper (
ctx context . Context ,
repoManager * repositoryManager ,
2018-06-22 19:07:23 +00:00
repoEnsurer * repositoryEnsurer ,
2018-06-06 21:32:28 +00:00
podVolumeBackupInformer cache . SharedIndexInformer ,
2019-03-18 18:37:46 +00:00
pvcClient corev1client . PersistentVolumeClaimsGetter ,
pvClient corev1client . PersistentVolumesGetter ,
2018-06-22 19:07:23 +00:00
log logrus . FieldLogger ,
2018-06-06 21:32:28 +00:00
) * backupper {
2018-02-28 01:35:35 +00:00
b := & backupper {
ctx : ctx ,
2018-06-06 21:32:28 +00:00
repoManager : repoManager ,
2018-06-22 19:07:23 +00:00
repoEnsurer : repoEnsurer ,
2019-03-18 18:37:46 +00:00
pvcClient : pvcClient ,
pvClient : pvClient ,
2018-06-06 21:32:28 +00:00
2019-01-25 03:33:07 +00:00
results : make ( map [ string ] chan * velerov1api . PodVolumeBackup ) ,
2018-02-28 01:35:35 +00:00
}
podVolumeBackupInformer . AddEventHandler (
cache . ResourceEventHandlerFuncs {
UpdateFunc : func ( _ , obj interface { } ) {
2019-01-25 03:33:07 +00:00
pvb := obj . ( * velerov1api . PodVolumeBackup )
2018-02-28 01:35:35 +00:00
2019-01-25 03:33:07 +00:00
if pvb . Status . Phase == velerov1api . PodVolumeBackupPhaseCompleted || pvb . Status . Phase == velerov1api . PodVolumeBackupPhaseFailed {
2018-02-28 01:35:35 +00:00
b . resultsLock . Lock ( )
2018-06-22 19:07:23 +00:00
defer b . resultsLock . Unlock ( )
resChan , ok := b . results [ resultsKey ( pvb . Spec . Pod . Namespace , pvb . Spec . Pod . Name ) ]
if ! ok {
log . Errorf ( "No results channel found for pod %s/%s to send pod volume backup %s/%s on" , pvb . Spec . Pod . Namespace , pvb . Spec . Pod . Name , pvb . Namespace , pvb . Name )
return
}
resChan <- pvb
2018-02-28 01:35:35 +00:00
}
} ,
} ,
)
return b
}
func resultsKey ( ns , name string ) string {
return fmt . Sprintf ( "%s/%s" , ns , name )
}
2019-07-24 19:51:20 +00:00
func ( b * backupper ) BackupPodVolumes ( backup * velerov1api . Backup , pod * corev1api . Pod , log logrus . FieldLogger ) ( [ ] * velerov1api . PodVolumeBackup , [ ] error ) {
2018-02-28 01:35:35 +00:00
// get volumes to backup from pod's annotations
volumesToBackup := GetVolumesToBackup ( pod )
if len ( volumesToBackup ) == 0 {
return nil , nil
}
2018-09-25 20:20:58 +00:00
repo , err := b . repoEnsurer . EnsureRepo ( b . ctx , backup . Namespace , pod . Namespace , backup . Spec . StorageLocation )
2018-06-06 21:32:28 +00:00
if err != nil {
2018-06-15 03:24:01 +00:00
return nil , [ ] error { err }
2018-02-28 01:35:35 +00:00
}
2018-06-22 19:07:23 +00:00
// get a single non-exclusive lock since we'll wait for all individual
// backups to be complete before releasing it.
2018-09-25 20:20:58 +00:00
b . repoManager . repoLocker . Lock ( repo . Name )
defer b . repoManager . repoLocker . Unlock ( repo . Name )
2018-06-22 19:07:23 +00:00
2019-01-25 03:33:07 +00:00
resultsChan := make ( chan * velerov1api . PodVolumeBackup )
2018-02-28 01:35:35 +00:00
b . resultsLock . Lock ( )
b . results [ resultsKey ( pod . Namespace , pod . Name ) ] = resultsChan
b . resultsLock . Unlock ( )
var (
2019-07-24 19:51:20 +00:00
errs [ ] error
podVolumeBackups [ ] * velerov1api . PodVolumeBackup
podVolumes = make ( map [ string ] corev1api . Volume )
2018-02-28 01:35:35 +00:00
)
2018-06-25 22:23:33 +00:00
// put the pod's volumes in a map for efficient lookup below
for _ , podVolume := range pod . Spec . Volumes {
podVolumes [ podVolume . Name ] = podVolume
}
2019-07-24 19:51:20 +00:00
var numVolumeSnapshots int
2018-02-28 01:35:35 +00:00
for _ , volumeName := range volumesToBackup {
2019-03-18 18:37:46 +00:00
volume , ok := podVolumes [ volumeName ]
if ! ok {
2018-06-25 22:23:33 +00:00
log . Warnf ( "No volume named %s found in pod %s/%s, skipping" , volumeName , pod . Namespace , pod . Name )
continue
}
// hostPath volumes are not supported because they're not mounted into /var/lib/kubelet/pods, so our
// daemonset pod has no way to access their data.
2019-03-18 18:37:46 +00:00
isHostPath , err := isHostPathVolume ( & volume , b . pvcClient . PersistentVolumeClaims ( pod . Namespace ) , b . pvClient . PersistentVolumes ( ) )
if err != nil {
errs = append ( errs , errors . Wrap ( err , "error checking if volume is a hostPath volume" ) )
continue
}
if isHostPath {
2018-06-25 22:23:33 +00:00
log . Warnf ( "Volume %s in pod %s/%s is a hostPath volume which is not supported for restic backup, skipping" , volumeName , pod . Namespace , pod . Name )
continue
}
2018-06-15 03:24:01 +00:00
volumeBackup := newPodVolumeBackup ( backup , pod , volumeName , repo . Spec . ResticIdentifier )
2019-07-24 19:51:20 +00:00
numVolumeSnapshots ++
if volumeBackup , err = b . repoManager . veleroClient . VeleroV1 ( ) . PodVolumeBackups ( volumeBackup . Namespace ) . Create ( volumeBackup ) ; err != nil {
2018-02-28 01:35:35 +00:00
errs = append ( errs , err )
continue
}
}
ForEachVolume :
2019-07-24 19:51:20 +00:00
for i , count := 0 , numVolumeSnapshots ; i < count ; i ++ {
2018-02-28 01:35:35 +00:00
select {
case <- b . ctx . Done ( ) :
errs = append ( errs , errors . New ( "timed out waiting for all PodVolumeBackups to complete" ) )
break ForEachVolume
case res := <- resultsChan :
switch res . Status . Phase {
2019-01-25 03:33:07 +00:00
case velerov1api . PodVolumeBackupPhaseCompleted :
2019-05-30 16:48:21 +00:00
if res . Status . SnapshotID == "" { // when the volume is empty there is no restic snapshot, so best to exclude it
break
}
2019-07-24 19:51:20 +00:00
podVolumeBackups = append ( podVolumeBackups , res )
2019-01-25 03:33:07 +00:00
case velerov1api . PodVolumeBackupPhaseFailed :
2018-02-28 01:35:35 +00:00
errs = append ( errs , errors . Errorf ( "pod volume backup failed: %s" , res . Status . Message ) )
2019-07-24 19:51:20 +00:00
podVolumeBackups = append ( podVolumeBackups , res )
2018-02-28 01:35:35 +00:00
}
}
}
b . resultsLock . Lock ( )
delete ( b . results , resultsKey ( pod . Namespace , pod . Name ) )
b . resultsLock . Unlock ( )
2019-07-24 19:51:20 +00:00
return podVolumeBackups , errs
2018-02-28 01:35:35 +00:00
}
2019-03-18 18:37:46 +00:00
type pvcGetter interface {
Get ( name string , opts metav1 . GetOptions ) ( * corev1api . PersistentVolumeClaim , error )
}
type pvGetter interface {
Get ( name string , opts metav1 . GetOptions ) ( * corev1api . PersistentVolume , error )
2018-06-25 22:23:33 +00:00
}
2019-03-18 18:37:46 +00:00
// isHostPathVolume returns true if the volume is either a hostPath pod volume or a persistent
// volume claim on a hostPath persistent volume, or false otherwise.
func isHostPathVolume ( volume * corev1api . Volume , pvcGetter pvcGetter , pvGetter pvGetter ) ( bool , error ) {
if volume . HostPath != nil {
return true , nil
}
if volume . PersistentVolumeClaim == nil {
return false , nil
}
pvc , err := pvcGetter . Get ( volume . PersistentVolumeClaim . ClaimName , metav1 . GetOptions { } )
if err != nil {
return false , errors . WithStack ( err )
}
if pvc . Spec . VolumeName == "" {
return false , nil
}
pv , err := pvGetter . Get ( pvc . Spec . VolumeName , metav1 . GetOptions { } )
if err != nil {
return false , errors . WithStack ( err )
2018-06-25 22:23:33 +00:00
}
2019-03-18 18:37:46 +00:00
return pv . Spec . HostPath != nil , nil
2018-06-25 22:23:33 +00:00
}
2019-01-25 03:33:07 +00:00
func newPodVolumeBackup ( backup * velerov1api . Backup , pod * corev1api . Pod , volumeName , repoIdentifier string ) * velerov1api . PodVolumeBackup {
return & velerov1api . PodVolumeBackup {
2018-02-28 01:35:35 +00:00
ObjectMeta : metav1 . ObjectMeta {
Namespace : backup . Namespace ,
GenerateName : backup . Name + "-" ,
OwnerReferences : [ ] metav1 . OwnerReference {
{
2019-01-25 03:33:07 +00:00
APIVersion : velerov1api . SchemeGroupVersion . String ( ) ,
2018-02-28 01:35:35 +00:00
Kind : "Backup" ,
Name : backup . Name ,
UID : backup . UID ,
Controller : boolptr . True ( ) ,
} ,
} ,
Labels : map [ string ] string {
2019-04-23 23:58:59 +00:00
velerov1api . BackupNameLabel : label . GetValidName ( backup . Name ) ,
2019-01-25 03:33:07 +00:00
velerov1api . BackupUIDLabel : string ( backup . UID ) ,
2018-02-28 01:35:35 +00:00
} ,
} ,
2019-01-25 03:33:07 +00:00
Spec : velerov1api . PodVolumeBackupSpec {
2018-02-28 01:35:35 +00:00
Node : pod . Spec . NodeName ,
Pod : corev1api . ObjectReference {
Kind : "Pod" ,
Namespace : pod . Namespace ,
Name : pod . Name ,
UID : pod . UID ,
} ,
Volume : volumeName ,
Tags : map [ string ] string {
"backup" : backup . Name ,
"backup-uid" : string ( backup . UID ) ,
"pod" : pod . Name ,
"pod-uid" : string ( pod . UID ) ,
"ns" : pod . Namespace ,
"volume" : volumeName ,
} ,
2018-09-25 21:46:29 +00:00
BackupStorageLocation : backup . Spec . StorageLocation ,
RepoIdentifier : repoIdentifier ,
2018-02-28 01:35:35 +00:00
} ,
}
}
2018-06-08 03:13:24 +00:00
func errorOnly ( _ interface { } , err error ) error {
return err
}