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 .
* /
2022-08-05 09:15:38 +00:00
package podvolume
2018-02-28 01:35:35 +00:00
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"
2022-01-06 06:40:20 +00:00
"k8s.io/apimachinery/pkg/util/sets"
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"
2023-03-21 06:39:25 +00:00
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
2019-09-30 21:26:56 +00:00
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
2022-08-05 09:15:38 +00:00
clientset "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/label"
2022-09-07 13:44:45 +00:00
"github.com/vmware-tanzu/velero/pkg/nodeagent"
2022-08-05 09:15:38 +00:00
"github.com/vmware-tanzu/velero/pkg/repository"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
2022-09-09 01:38:29 +00:00
"github.com/vmware-tanzu/velero/pkg/util/kube"
2018-02-28 01:35:35 +00:00
)
2022-10-12 11:12:40 +00:00
// Backupper can execute pod volume backups of volumes in a pod.
2018-02-28 01:35:35 +00:00
type Backupper interface {
2019-10-08 21:16:35 +00:00
// BackupPodVolumes backs up all specified volumes in a pod.
2023-03-21 06:39:25 +00:00
BackupPodVolumes ( backup * velerov1api . Backup , pod * corev1api . Pod , volumesToBackup [ ] string , resPolicies * resourcepolicies . Policies , log logrus . FieldLogger ) ( [ ] * velerov1api . PodVolumeBackup , [ ] error )
2018-02-28 01:35:35 +00:00
}
type backupper struct {
2022-08-05 09:15:38 +00:00
ctx context . Context
repoLocker * repository . RepoLocker
repoEnsurer * repository . RepositoryEnsurer
veleroClient clientset . Interface
pvcClient corev1client . PersistentVolumeClaimsGetter
pvClient corev1client . PersistentVolumesGetter
2022-09-07 13:44:45 +00:00
podClient corev1client . PodsGetter
2022-09-05 02:29:30 +00:00
uploaderType string
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 ,
2022-08-05 09:15:38 +00:00
repoLocker * repository . RepoLocker ,
repoEnsurer * repository . RepositoryEnsurer ,
2018-06-06 21:32:28 +00:00
podVolumeBackupInformer cache . SharedIndexInformer ,
2022-08-05 09:15:38 +00:00
veleroClient clientset . Interface ,
2019-03-18 18:37:46 +00:00
pvcClient corev1client . PersistentVolumeClaimsGetter ,
pvClient corev1client . PersistentVolumesGetter ,
2022-09-07 13:44:45 +00:00
podClient corev1client . PodsGetter ,
2022-09-05 02:29:30 +00:00
uploaderType string ,
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 {
2022-08-05 09:15:38 +00:00
ctx : ctx ,
repoLocker : repoLocker ,
repoEnsurer : repoEnsurer ,
veleroClient : veleroClient ,
pvcClient : pvcClient ,
pvClient : pvClient ,
2022-09-07 13:44:45 +00:00
podClient : podClient ,
2022-09-05 02:29:30 +00:00
uploaderType : uploaderType ,
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 )
}
2023-03-21 08:33:15 +00:00
func ( b * backupper ) getMatchAction ( resPolicies * resourcepolicies . Policies , pvc * corev1api . PersistentVolumeClaim , volume * corev1api . Volume ) ( * resourcepolicies . Action , error ) {
2023-03-21 06:39:25 +00:00
if pvc != nil {
pv , err := b . pvClient . PersistentVolumes ( ) . Get ( context . TODO ( ) , pvc . Spec . VolumeName , metav1 . GetOptions { } )
if err != nil {
return nil , errors . Wrapf ( err , "error getting pv for pvc %s" , pvc . Spec . VolumeName )
}
2023-03-21 08:33:15 +00:00
return resPolicies . GetMatchAction ( pv )
2023-03-21 06:39:25 +00:00
}
2023-03-21 08:33:15 +00:00
if volume != nil {
return resPolicies . GetMatchAction ( volume )
}
return nil , errors . Errorf ( "failed to check resource policies for empty volume" )
2023-03-21 06:39:25 +00:00
}
func ( b * backupper ) BackupPodVolumes ( backup * velerov1api . Backup , pod * corev1api . Pod , volumesToBackup [ ] string , resPolicies * resourcepolicies . Policies , log logrus . FieldLogger ) ( [ ] * velerov1api . PodVolumeBackup , [ ] error ) {
2018-02-28 01:35:35 +00:00
if len ( volumesToBackup ) == 0 {
return nil , nil
}
2022-09-05 02:29:30 +00:00
repositoryType := getRepositoryType ( b . uploaderType )
if repositoryType == "" {
err := errors . Errorf ( "empty repository type, uploader %s" , b . uploaderType )
return nil , [ ] error { err }
}
repo , err := b . repoEnsurer . EnsureRepo ( b . ctx , backup . Namespace , pod . Namespace , backup . Spec . StorageLocation , repositoryType )
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
}
2022-09-09 01:38:29 +00:00
err = kube . IsPodRunning ( pod )
2022-09-07 13:44:45 +00:00
if err != nil {
return nil , [ ] error { err }
}
err = nodeagent . IsRunningInNode ( b . ctx , backup . Namespace , pod . Spec . NodeName , b . podClient )
if err != nil {
return nil , [ ] error { err }
}
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.
2022-08-05 09:15:38 +00:00
b . repoLocker . Lock ( repo . Name )
defer b . 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 (
2022-01-06 06:40:20 +00:00
errs [ ] error
podVolumeBackups [ ] * velerov1api . PodVolumeBackup
podVolumes = make ( map [ string ] corev1api . Volume )
mountedPodVolumes = sets . String { }
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
}
2022-01-06 06:40:20 +00:00
for _ , container := range pod . Spec . Containers {
for _ , volumeMount := range container . VolumeMounts {
mountedPodVolumes . Insert ( volumeMount . Name )
}
}
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
}
2019-08-27 23:37:51 +00:00
var pvc * corev1api . PersistentVolumeClaim
if volume . PersistentVolumeClaim != nil {
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
pvc , err = b . pvcClient . PersistentVolumeClaims ( pod . Namespace ) . Get ( context . TODO ( ) , volume . PersistentVolumeClaim . ClaimName , metav1 . GetOptions { } )
2019-08-27 23:37:51 +00:00
if err != nil {
errs = append ( errs , errors . Wrap ( err , "error getting persistent volume claim for volume" ) )
continue
}
}
2018-06-25 22:23:33 +00:00
// 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-08-27 23:37:51 +00:00
isHostPath , err := isHostPathVolume ( & volume , pvc , b . pvClient . PersistentVolumes ( ) )
2019-03-18 18:37:46 +00:00
if err != nil {
errs = append ( errs , errors . Wrap ( err , "error checking if volume is a hostPath volume" ) )
continue
}
if isHostPath {
2022-10-12 11:12:40 +00:00
log . Warnf ( "Volume %s in pod %s/%s is a hostPath volume which is not supported for pod volume backup, skipping" , volumeName , pod . Namespace , pod . Name )
2018-06-25 22:23:33 +00:00
continue
}
2022-01-06 06:40:20 +00:00
// volumes that are not mounted by any container should not be backed up, because
// its directory is not created
if ! mountedPodVolumes . Has ( volumeName ) {
log . Warnf ( "Volume %s is declared in pod %s/%s but not mounted by any container, skipping" , volumeName , pod . Namespace , pod . Name )
continue
}
2022-08-05 09:15:38 +00:00
2023-03-21 06:39:25 +00:00
if resPolicies != nil {
2023-03-21 08:33:15 +00:00
if action , err := b . getMatchAction ( resPolicies , pvc , & volume ) ; err != nil {
2023-03-21 06:39:25 +00:00
errs = append ( errs , errors . Wrapf ( err , "error getting pv for pvc %s" , pvc . Spec . VolumeName ) )
continue
} else if action != nil && action . Type == resourcepolicies . Skip {
log . Infof ( "skip backup of volume %s for the matched resource policies" , volumeName )
continue
}
}
2022-09-05 02:29:30 +00:00
volumeBackup := newPodVolumeBackup ( backup , pod , volume , repo . Spec . ResticIdentifier , b . uploaderType , pvc )
2022-08-05 09:15:38 +00:00
if volumeBackup , err = b . veleroClient . VeleroV1 ( ) . PodVolumeBackups ( volumeBackup . Namespace ) . Create ( context . TODO ( ) , volumeBackup , metav1 . CreateOptions { } ) ; err != nil {
2018-02-28 01:35:35 +00:00
errs = append ( errs , err )
continue
}
2020-05-20 16:05:23 +00:00
numVolumeSnapshots ++
2018-02-28 01:35:35 +00:00
}
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-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 pvGetter interface {
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
Get ( ctx context . Context , 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.
2019-08-27 23:37:51 +00:00
func isHostPathVolume ( volume * corev1api . Volume , pvc * corev1api . PersistentVolumeClaim , pvGetter pvGetter ) ( bool , error ) {
2019-03-18 18:37:46 +00:00
if volume . HostPath != nil {
return true , nil
}
2019-08-27 23:37:51 +00:00
if pvc == nil || pvc . Spec . VolumeName == "" {
2019-03-18 18:37:46 +00:00
return false , nil
}
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
pv , err := pvGetter . Get ( context . TODO ( ) , pvc . Spec . VolumeName , metav1 . GetOptions { } )
2019-03-18 18:37:46 +00:00
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
}
2022-07-19 08:06:40 +00:00
func newPodVolumeBackup ( backup * velerov1api . Backup , pod * corev1api . Pod , volume corev1api . Volume , repoIdentifier , uploaderType string , pvc * corev1api . PersistentVolumeClaim ) * velerov1api . PodVolumeBackup {
2019-08-06 20:40:35 +00:00
pvb := & 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 ,
} ,
2019-08-06 20:40:35 +00:00
Volume : volume . Name ,
2018-02-28 01:35:35 +00:00
Tags : map [ string ] string {
"backup" : backup . Name ,
"backup-uid" : string ( backup . UID ) ,
"pod" : pod . Name ,
"pod-uid" : string ( pod . UID ) ,
"ns" : pod . Namespace ,
2019-08-06 20:40:35 +00:00
"volume" : volume . Name ,
2018-02-28 01:35:35 +00:00
} ,
2018-09-25 21:46:29 +00:00
BackupStorageLocation : backup . Spec . StorageLocation ,
RepoIdentifier : repoIdentifier ,
2022-07-19 08:06:40 +00:00
UploaderType : uploaderType ,
2018-02-28 01:35:35 +00:00
} ,
}
2019-08-06 20:40:35 +00:00
2019-08-27 23:37:51 +00:00
if pvc != nil {
// this annotation is used in pkg/restore to identify if a PVC
2022-10-12 11:12:40 +00:00
// has a pod volume backup.
2019-08-27 23:37:51 +00:00
pvb . Annotations = map [ string ] string {
PVCNameAnnotation : pvc . Name ,
}
// this label is used by the pod volume backup controller to tell
// if a pod volume backup is for a PVC.
pvb . Labels [ velerov1api . PVCUIDLabel ] = string ( pvc . UID )
// this tag is not used by velero, but useful for debugging.
pvb . Spec . Tags [ "pvc-uid" ] = string ( pvc . UID )
2019-08-06 20:40:35 +00:00
}
return pvb
2018-02-28 01:35:35 +00:00
}
2018-06-08 03:13:24 +00:00
func errorOnly ( _ interface { } , err error ) error {
return err
}