2017-10-02 20:53:08 +00:00
/ *
2021-12-10 17:53:47 +00:00
Copyright the Velero contributors .
2017-10-02 20:53:08 +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 backup
import (
"archive/tar"
2023-03-21 06:39:25 +00:00
"context"
2017-10-02 20:53:08 +00:00
"encoding/json"
2019-08-05 17:15:55 +00:00
"fmt"
2022-02-21 14:04:21 +00:00
"strings"
2017-10-02 20:53:08 +00:00
"time"
2022-09-26 10:33:02 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2017-10-02 20:53:08 +00:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
2018-02-28 01:35:35 +00:00
corev1api "k8s.io/api/core/v1"
2020-06-04 23:29:55 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2017-10-02 20:53:08 +00:00
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
2018-02-28 01:35:35 +00:00
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
2021-11-11 06:54:31 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-11-15 02:35:02 +00:00
2023-03-21 06:39:25 +00:00
kbClient "sigs.k8s.io/controller-runtime/pkg/client"
2020-07-22 21:26:14 +00:00
"github.com/vmware-tanzu/velero/internal/hook"
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"
2023-01-24 17:59:53 +00:00
"github.com/vmware-tanzu/velero/pkg/archive"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/discovery"
2022-03-31 08:58:00 +00:00
"github.com/vmware-tanzu/velero/pkg/features"
2023-01-24 17:59:53 +00:00
"github.com/vmware-tanzu/velero/pkg/itemoperation"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/kuberesource"
2023-01-24 17:59:53 +00:00
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
2022-09-08 22:09:18 +00:00
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
2022-08-05 09:15:38 +00:00
"github.com/vmware-tanzu/velero/pkg/podvolume"
2020-04-24 16:46:20 +00:00
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
2023-10-17 12:57:11 +00:00
csiutil "github.com/vmware-tanzu/velero/pkg/util/csi"
2023-09-26 08:21:46 +00:00
pdvolumeutil "github.com/vmware-tanzu/velero/pkg/util/podvolume"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/volume"
2017-10-02 20:53:08 +00:00
)
2022-09-26 10:33:02 +00:00
const (
mustIncludeAdditionalItemAnnotation = "backup.velero.io/must-include-additional-items"
2023-07-20 08:13:48 +00:00
skippedNoCSIPVAnnotation = "backup.velero.io/skipped-no-csi-pv"
2022-09-26 10:33:02 +00:00
excludeFromBackupLabel = "velero.io/exclude-from-backup"
2023-07-20 08:13:48 +00:00
csiBIAPluginName = "velero.io/csi-pvc-backupper"
vsphereBIAPluginName = "velero.io/vsphere-pvc-backupper"
2022-09-26 10:33:02 +00:00
)
2020-04-13 20:53:58 +00:00
// itemBackupper can back up individual items to a tar writer.
type itemBackupper struct {
2022-10-12 11:12:40 +00:00
backupRequest * Request
tarWriter tarWriter
dynamicFactory client . DynamicFactory
2023-03-21 06:39:25 +00:00
kbClient kbClient . Client
2022-10-12 11:12:40 +00:00
discoveryHelper discovery . Helper
podVolumeBackupper podvolume . Backupper
podVolumeSnapshotTracker * pvcSnapshotTracker
volumeSnapshotterGetter VolumeSnapshotterGetter
2019-03-27 18:22:04 +00:00
2020-07-22 21:26:14 +00:00
itemHookHandler hook . ItemHookHandler
2022-09-08 22:09:18 +00:00
snapshotLocationVolumeSnapshotters map [ string ] vsv1 . VolumeSnapshotter
2017-10-02 20:53:08 +00:00
}
2023-01-24 17:59:53 +00:00
type FileForArchive struct {
FilePath string
Header * tar . Header
FileBytes [ ] byte
}
2017-10-02 20:53:08 +00:00
// backupItem backs up an individual item to tarWriter. The item may be excluded based on the
// namespaces IncludesExcludes list.
2023-03-08 14:37:07 +00:00
// If finalize is true, then it returns the bytes instead of writing them to the tarWriter
2019-10-17 18:31:04 +00:00
// In addition to the error return, backupItem also returns a bool indicating whether the item
// was actually backed up.
2023-03-08 14:37:07 +00:00
func ( ib * itemBackupper ) backupItem ( logger logrus . FieldLogger , obj runtime . Unstructured , groupResource schema . GroupResource , preferredGVR schema . GroupVersionResource , mustInclude , finalize bool ) ( bool , [ ] FileForArchive , error ) {
selectedForBackup , files , err := ib . backupItemInternal ( logger , obj , groupResource , preferredGVR , mustInclude , finalize )
// return if not selected, an error occurred, there are no files to add, or for finalize
2023-04-25 12:06:42 +00:00
if ! selectedForBackup || err != nil || len ( files ) == 0 || finalize {
2023-03-08 14:37:07 +00:00
return selectedForBackup , files , err
2023-01-24 17:59:53 +00:00
}
for _ , file := range files {
if err := ib . tarWriter . WriteHeader ( file . Header ) ; err != nil {
2023-03-08 14:37:07 +00:00
return false , [ ] FileForArchive { } , errors . WithStack ( err )
2023-01-24 17:59:53 +00:00
}
if _ , err := ib . tarWriter . Write ( file . FileBytes ) ; err != nil {
2023-03-08 14:37:07 +00:00
return false , [ ] FileForArchive { } , errors . WithStack ( err )
2023-01-24 17:59:53 +00:00
}
}
2023-03-08 14:37:07 +00:00
return true , [ ] FileForArchive { } , nil
2023-01-24 17:59:53 +00:00
}
func ( ib * itemBackupper ) backupItemInternal ( logger logrus . FieldLogger , obj runtime . Unstructured , groupResource schema . GroupResource , preferredGVR schema . GroupVersionResource , mustInclude , finalize bool ) ( bool , [ ] FileForArchive , error ) {
var itemFiles [ ] FileForArchive
2017-10-02 20:53:08 +00:00
metadata , err := meta . Accessor ( obj )
if err != nil {
2023-01-24 17:59:53 +00:00
return false , itemFiles , err
2017-10-02 20:53:08 +00:00
}
namespace := metadata . GetNamespace ( )
name := metadata . GetName ( )
2023-01-24 17:59:53 +00:00
log := logger . WithFields ( map [ string ] interface { } {
"name" : name ,
"resource" : groupResource . String ( ) ,
"namespace" : namespace ,
} )
2017-10-02 20:53:08 +00:00
2022-09-26 10:33:02 +00:00
if mustInclude {
log . Infof ( "Skipping the exclusion checks for this resource" )
} else {
if metadata . GetLabels ( ) [ excludeFromBackupLabel ] == "true" {
log . Infof ( "Excluding item because it has label %s=true" , excludeFromBackupLabel )
2023-07-20 08:13:48 +00:00
ib . trackSkippedPV ( obj , groupResource , "" , fmt . Sprintf ( "item has label %s=true" , excludeFromBackupLabel ) , log )
2023-01-24 17:59:53 +00:00
return false , itemFiles , nil
2022-09-26 10:33:02 +00:00
}
// NOTE: we have to re-check namespace & resource includes/excludes because it's possible that
// backupItem can be invoked by a custom action.
if namespace != "" && ! ib . backupRequest . NamespaceIncludesExcludes . ShouldInclude ( namespace ) {
log . Info ( "Excluding item because namespace is excluded" )
2023-01-24 17:59:53 +00:00
return false , itemFiles , nil
2022-09-26 10:33:02 +00:00
}
2023-02-02 09:31:35 +00:00
// NOTE: we specifically allow namespaces to be backed up even if it's excluded.
// This check is more permissive for cluster resources to let those passed in by
// plugins' additional items to get involved.
// Only expel cluster resource when it's specifically listed in the excluded list here.
if namespace == "" && groupResource != kuberesource . Namespaces &&
ib . backupRequest . ResourceIncludesExcludes . ShouldExclude ( groupResource . String ( ) ) {
log . Info ( "Excluding item because resource is cluster-scoped and is excluded by cluster filter." )
2023-01-24 17:59:53 +00:00
return false , itemFiles , nil
2022-09-26 10:33:02 +00:00
}
2017-10-02 20:53:08 +00:00
2023-02-02 09:31:35 +00:00
// Only check namespace-scoped resource to avoid expelling cluster resources
// are not specified in included list.
if namespace != "" && ! ib . backupRequest . ResourceIncludesExcludes . ShouldInclude ( groupResource . String ( ) ) {
2022-09-26 10:33:02 +00:00
log . Info ( "Excluding item because resource is excluded" )
2023-01-24 17:59:53 +00:00
return false , itemFiles , nil
2022-09-26 10:33:02 +00:00
}
2017-10-02 20:53:08 +00:00
}
2018-06-04 11:00:34 +00:00
if metadata . GetDeletionTimestamp ( ) != nil {
log . Info ( "Skipping item because it's being deleted." )
2023-01-24 17:59:53 +00:00
return false , itemFiles , nil
2018-06-04 11:00:34 +00:00
}
2019-08-05 17:15:55 +00:00
2017-10-02 20:53:08 +00:00
key := itemKey {
2019-08-05 17:15:55 +00:00
resource : resourceKey ( obj ) ,
2017-10-02 20:53:08 +00:00
namespace : namespace ,
name : name ,
}
2019-08-05 17:15:55 +00:00
if _ , exists := ib . backupRequest . BackedUpItems [ key ] ; exists {
2017-10-02 20:53:08 +00:00
log . Info ( "Skipping item because it's already been backed up." )
2019-10-17 18:31:04 +00:00
// returning true since this item *is* in the backup, even though we're not backing it up here
2023-01-24 17:59:53 +00:00
return true , itemFiles , nil
2017-10-02 20:53:08 +00:00
}
2019-08-05 17:15:55 +00:00
ib . backupRequest . BackedUpItems [ key ] = struct { } { }
2019-04-26 16:14:26 +00:00
log . Info ( "Backing up item" )
2017-10-02 20:53:08 +00:00
2018-06-25 18:20:32 +00:00
var (
2022-10-12 11:12:40 +00:00
backupErrs [ ] error
pod * corev1api . Pod
pvbVolumes [ ] string
2018-06-25 18:20:32 +00:00
)
2023-01-24 17:59:53 +00:00
log . Debug ( "Executing pre hooks" )
if err := ib . itemHookHandler . HandleHooks ( log , groupResource , obj , ib . backupRequest . ResourceHooks , hook . PhasePre ) ; err != nil {
return false , itemFiles , err
}
2023-07-20 08:13:48 +00:00
if optedOut , podName := ib . podVolumeSnapshotTracker . OptedoutByPod ( namespace , name ) ; optedOut {
ib . trackSkippedPV ( obj , groupResource , podVolumeApproach , fmt . Sprintf ( "opted out due to annotation in pod %s" , podName ) , log )
}
2023-01-24 17:59:53 +00:00
2023-03-08 14:37:07 +00:00
if groupResource == kuberesource . Pods {
2018-06-25 18:20:32 +00:00
// pod needs to be initialized for the unstructured converter
pod = new ( corev1api . Pod )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pod ) ; err != nil {
backupErrs = append ( backupErrs , errors . WithStack ( err ) )
// nil it on error since it's not valid
pod = nil
} else {
2022-09-23 01:13:36 +00:00
// Get the list of volumes to back up using pod volume backup from the pod's annotations. Remove from this list
2019-10-08 21:16:35 +00:00
// any volumes that use a PVC that we've already backed up (this would be in a read-write-many scenario,
// where it's been backed up from another pod), since we don't need >1 backup per PVC.
2023-09-26 08:21:46 +00:00
includedVolumes , optedOutVolumes := pdvolumeutil . GetVolumesByPod ( pod , boolptr . IsSetToTrue ( ib . backupRequest . Spec . DefaultVolumesToFsBackup ) )
2023-07-20 08:13:48 +00:00
for _ , volume := range includedVolumes {
2023-02-21 11:05:21 +00:00
// track the volumes that are PVCs using the PVC snapshot tracker, so that when we backup PVCs/PVs
// via an item action in the next step, we don't snapshot PVs that will have their data backed up
// with pod volume backup.
ib . podVolumeSnapshotTracker . Track ( pod , volume )
if found , pvcName := ib . podVolumeSnapshotTracker . TakenForPodVolume ( pod , volume ) ; found {
2019-10-08 21:16:35 +00:00
log . WithFields ( map [ string ] interface { } {
"podVolume" : volume ,
"pvcName" : pvcName ,
2022-10-12 11:12:40 +00:00
} ) . Info ( "Pod volume uses a persistent volume claim which has already been backed up from another pod, skipping." )
2019-10-08 21:16:35 +00:00
continue
}
2022-10-12 11:12:40 +00:00
pvbVolumes = append ( pvbVolumes , volume )
2019-10-08 21:16:35 +00:00
}
2023-07-20 08:13:48 +00:00
for _ , optedOutVol := range optedOutVolumes {
ib . podVolumeSnapshotTracker . Optout ( pod , optedOutVol )
}
2018-06-25 18:20:32 +00:00
}
}
2020-07-07 17:59:41 +00:00
// capture the version of the object before invoking plugin actions as the plugin may update
// the group version of the object.
2023-01-24 17:59:53 +00:00
versionPath := resourceVersion ( obj )
2020-07-07 17:59:41 +00:00
2023-03-08 14:37:07 +00:00
updatedObj , additionalItemFiles , err := ib . executeActions ( log , obj , groupResource , name , namespace , metadata , finalize )
2018-07-26 17:38:17 +00:00
if err != nil {
2018-05-22 17:03:20 +00:00
backupErrs = append ( backupErrs , err )
2018-07-26 17:38:17 +00:00
// if there was an error running actions, execute post hooks and return
log . Debug ( "Executing post hooks" )
2020-07-22 21:26:14 +00:00
if err := ib . itemHookHandler . HandleHooks ( log , groupResource , obj , ib . backupRequest . ResourceHooks , hook . PhasePost ) ; err != nil {
2018-07-26 17:38:17 +00:00
backupErrs = append ( backupErrs , err )
}
2023-01-24 17:59:53 +00:00
return false , itemFiles , kubeerrs . NewAggregate ( backupErrs )
2018-05-22 17:03:20 +00:00
}
2023-03-21 06:39:25 +00:00
2023-03-08 14:37:07 +00:00
itemFiles = append ( itemFiles , additionalItemFiles ... )
2018-07-26 17:38:17 +00:00
obj = updatedObj
2018-09-10 21:27:00 +00:00
if metadata , err = meta . Accessor ( obj ) ; err != nil {
2023-01-24 17:59:53 +00:00
return false , itemFiles , errors . WithStack ( err )
2018-09-10 21:27:00 +00:00
}
2019-06-18 18:19:00 +00:00
// update name and namespace in case they were modified in an action
name = metadata . GetName ( )
namespace = metadata . GetNamespace ( )
2018-05-22 17:03:20 +00:00
2023-03-08 14:37:07 +00:00
if groupResource == kuberesource . PersistentVolumes {
2023-11-11 03:35:23 +00:00
if err := ib . addVolumeInfo ( obj , log ) ; err != nil {
backupErrs = append ( backupErrs , err )
}
2018-09-26 22:18:45 +00:00
if err := ib . takePVSnapshot ( obj , log ) ; err != nil {
2018-06-25 18:20:32 +00:00
backupErrs = append ( backupErrs , err )
2018-05-22 17:03:20 +00:00
}
}
2023-03-08 14:37:07 +00:00
if groupResource == kuberesource . Pods && pod != nil {
2019-07-24 19:51:20 +00:00
// this function will return partial results, so process podVolumeBackups
2018-06-25 18:20:32 +00:00
// even if there are errors.
2023-07-20 08:13:48 +00:00
podVolumeBackups , podVolumePVCBackupSummary , errs := ib . backupPodVolumes ( log , pod , pvbVolumes )
2019-08-08 15:27:46 +00:00
ib . backupRequest . PodVolumeBackups = append ( ib . backupRequest . PodVolumeBackups , podVolumeBackups ... )
2018-06-25 18:20:32 +00:00
backupErrs = append ( backupErrs , errs ... )
2022-12-01 07:12:07 +00:00
2023-02-21 11:05:21 +00:00
// Mark the volumes that has been processed by pod volume backup as Taken in the tracker.
2022-12-01 07:12:07 +00:00
for _ , pvb := range podVolumeBackups {
2023-02-21 11:05:21 +00:00
ib . podVolumeSnapshotTracker . Take ( pod , pvb . Spec . Volume )
2022-12-01 07:12:07 +00:00
}
2023-07-20 08:13:48 +00:00
// Track/Untrack the volumes based on podVolumePVCBackupSummary
if podVolumePVCBackupSummary != nil {
for _ , skippedPVC := range podVolumePVCBackupSummary . Skipped {
if obj , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( skippedPVC . PVC ) ; err != nil {
backupErrs = append ( backupErrs , errors . WithStack ( err ) )
} else {
ib . trackSkippedPV ( & unstructured . Unstructured { Object : obj } , kuberesource . PersistentVolumeClaims ,
podVolumeApproach , skippedPVC . Reason , log )
}
}
for _ , pvc := range podVolumePVCBackupSummary . Backedup {
if obj , err := runtime . DefaultUnstructuredConverter . ToUnstructured ( pvc ) ; err != nil {
backupErrs = append ( backupErrs , errors . WithStack ( err ) )
} else {
ib . unTrackSkippedPV ( & unstructured . Unstructured { Object : obj } , kuberesource . PersistentVolumeClaims , log )
}
}
}
2018-02-28 01:35:35 +00:00
}
2018-05-22 17:03:20 +00:00
log . Debug ( "Executing post hooks" )
2020-07-22 21:26:14 +00:00
if err := ib . itemHookHandler . HandleHooks ( log , groupResource , obj , ib . backupRequest . ResourceHooks , hook . PhasePost ) ; err != nil {
2018-05-22 17:03:20 +00:00
backupErrs = append ( backupErrs , err )
}
if len ( backupErrs ) != 0 {
2023-01-24 17:59:53 +00:00
return false , itemFiles , kubeerrs . NewAggregate ( backupErrs )
2018-05-22 17:03:20 +00:00
}
2023-01-24 17:59:53 +00:00
itemBytes , err := json . Marshal ( obj . UnstructuredContent ( ) )
if err != nil {
return false , itemFiles , errors . WithStack ( err )
2020-05-01 19:54:57 +00:00
}
2023-01-24 17:59:53 +00:00
if versionPath == preferredGVR . Version {
// backing up preferred version backup without API Group version - for backward compatibility
log . Debugf ( "Resource %s/%s, version= %s, preferredVersion=%s" , groupResource . String ( ) , name , versionPath , preferredGVR . Version )
itemFiles = append ( itemFiles , getFileForArchive ( namespace , name , groupResource . String ( ) , "" , itemBytes ) )
versionPath = versionPath + velerov1api . PreferredVersionDir
2018-05-22 17:03:20 +00:00
}
2023-01-24 17:59:53 +00:00
itemFiles = append ( itemFiles , getFileForArchive ( namespace , name , groupResource . String ( ) , versionPath , itemBytes ) )
return true , itemFiles , nil
}
2018-05-22 17:03:20 +00:00
2023-01-24 17:59:53 +00:00
func getFileForArchive ( namespace , name , groupResource , versionPath string , itemBytes [ ] byte ) FileForArchive {
filePath := archive . GetVersionedItemFilePath ( "" , groupResource , namespace , name , versionPath )
2018-05-22 17:03:20 +00:00
hdr := & tar . Header {
Name : filePath ,
Size : int64 ( len ( itemBytes ) ) ,
Typeflag : tar . TypeReg ,
Mode : 0755 ,
ModTime : time . Now ( ) ,
}
2023-01-24 17:59:53 +00:00
return FileForArchive { FilePath : filePath , Header : hdr , FileBytes : itemBytes }
2018-05-22 17:03:20 +00:00
}
2022-10-12 11:12:40 +00:00
// backupPodVolumes triggers pod volume backups of the specified pod volumes, and returns a list of PodVolumeBackups
2018-06-25 18:20:32 +00:00
// for volumes that were successfully backed up, and a slice of any errors that were encountered.
2023-07-20 08:13:48 +00:00
func ( ib * itemBackupper ) backupPodVolumes ( log logrus . FieldLogger , pod * corev1api . Pod , volumes [ ] string ) ( [ ] * velerov1api . PodVolumeBackup , * podvolume . PVCBackupSummary , [ ] error ) {
2018-06-25 18:20:32 +00:00
if len ( volumes ) == 0 {
2023-07-20 08:13:48 +00:00
return nil , nil , nil
2018-02-28 01:35:35 +00:00
}
2022-10-12 11:12:40 +00:00
if ib . podVolumeBackupper == nil {
log . Warn ( "No pod volume backupper, not backing up pod's volumes" )
2023-07-20 08:13:48 +00:00
return nil , nil , nil
2018-02-28 01:35:35 +00:00
}
2023-03-21 06:39:25 +00:00
return ib . podVolumeBackupper . BackupPodVolumes ( ib . backupRequest . Backup , pod , volumes , ib . backupRequest . ResPolicies , log )
2018-02-28 01:35:35 +00:00
}
2020-04-13 20:53:58 +00:00
func ( ib * itemBackupper ) executeActions (
2018-07-26 17:38:17 +00:00
log logrus . FieldLogger ,
obj runtime . Unstructured ,
groupResource schema . GroupResource ,
name , namespace string ,
metadata metav1 . Object ,
2023-01-24 17:59:53 +00:00
finalize bool ,
2023-03-08 14:37:07 +00:00
) ( runtime . Unstructured , [ ] FileForArchive , error ) {
var itemFiles [ ] FileForArchive
2018-09-26 22:18:45 +00:00
for _ , action := range ib . backupRequest . ResolvedActions {
2021-12-10 17:53:47 +00:00
if ! action . ShouldUse ( groupResource , namespace , metadata , log ) {
2017-11-15 02:35:02 +00:00
continue
}
2017-10-02 20:53:08 +00:00
log . Info ( "Executing custom action" )
2023-04-02 15:27:51 +00:00
actionName := action . Name ( )
if act , err := ib . getMatchAction ( obj , groupResource , actionName ) ; err != nil {
2023-03-21 06:39:25 +00:00
return nil , itemFiles , errors . WithStack ( err )
} else if act != nil && act . Type == resourcepolicies . Skip {
2023-04-02 15:27:51 +00:00
log . Infof ( "Skip executing Backup Item Action: %s of resource %s: %s/%s for the matched resource policies" , actionName , groupResource , namespace , name )
2023-07-20 08:13:48 +00:00
ib . trackSkippedPV ( obj , groupResource , "" , "skipped due to resource policy " , log )
2023-03-21 06:39:25 +00:00
continue
}
2023-10-17 12:57:11 +00:00
// If the EnableCSI feature is not enabled, but the executing action is from CSI plugin, skip the action.
if csiutil . ShouldSkipAction ( actionName ) {
log . Infof ( "Skip action %s for resource %s:%s/%s, because the CSI feature is not enabled. Feature setting is %s." ,
actionName , groupResource . String ( ) , metadata . GetNamespace ( ) , metadata . GetName ( ) , features . Serialize ( ) )
continue
}
2023-03-08 14:37:07 +00:00
updatedItem , additionalItemIdentifiers , operationID , postOperationItems , err := action . Execute ( obj , ib . backupRequest . Backup )
2018-07-26 17:38:17 +00:00
if err != nil {
2023-03-08 14:37:07 +00:00
return nil , itemFiles , errors . Wrapf ( err , "error executing custom action (groupResource=%s, namespace=%s, name=%s)" , groupResource . String ( ) , namespace , name )
2018-07-26 17:38:17 +00:00
}
2022-09-26 10:33:02 +00:00
u := & unstructured . Unstructured { Object : updatedItem . UnstructuredContent ( ) }
2023-07-20 08:13:48 +00:00
if actionName == csiBIAPluginName && additionalItemIdentifiers == nil && u . GetAnnotations ( ) [ skippedNoCSIPVAnnotation ] == "true" {
// snapshot was skipped by CSI plugin
ib . trackSkippedPV ( obj , groupResource , csiSnapshotApproach , "skipped b/c it's not a CSI volume" , log )
delete ( u . GetAnnotations ( ) , skippedNoCSIPVAnnotation )
} else if actionName == csiBIAPluginName || actionName == vsphereBIAPluginName {
// the snapshot has been taken
ib . unTrackSkippedPV ( obj , groupResource , log )
}
2023-03-08 14:37:07 +00:00
mustInclude := u . GetAnnotations ( ) [ mustIncludeAdditionalItemAnnotation ] == "true" || finalize
2023-01-24 17:59:53 +00:00
// remove the annotation as it's for communication between BIA and velero server,
// we don't want the resource be restored with this annotation.
2023-04-25 12:06:42 +00:00
delete ( u . GetAnnotations ( ) , mustIncludeAdditionalItemAnnotation )
2023-01-24 17:59:53 +00:00
obj = u
// If async plugin started async operation, add it to the ItemOperations list
// ignore during finalize phase
if operationID != "" {
2023-03-08 14:37:07 +00:00
if finalize {
2023-04-24 07:29:20 +00:00
return nil , itemFiles , fmt . Errorf ( "backup Item Action created operation during finalize (groupResource=%s, namespace=%s, name=%s)" , groupResource . String ( ) , namespace , name )
2023-03-08 14:37:07 +00:00
}
2023-01-24 17:59:53 +00:00
resourceIdentifier := velero . ResourceIdentifier {
GroupResource : groupResource ,
Namespace : namespace ,
Name : name ,
}
now := metav1 . Now ( )
newOperation := itemoperation . BackupOperation {
Spec : itemoperation . BackupOperationSpec {
BackupName : ib . backupRequest . Backup . Name ,
BackupUID : string ( ib . backupRequest . Backup . UID ) ,
BackupItemAction : action . Name ( ) ,
ResourceIdentifier : resourceIdentifier ,
OperationID : operationID ,
} ,
Status : itemoperation . OperationStatus {
2023-03-21 22:54:03 +00:00
Phase : itemoperation . OperationPhaseNew ,
2023-01-24 17:59:53 +00:00
Created : & now ,
} ,
}
2023-03-08 14:37:07 +00:00
newOperation . Spec . PostOperationItems = postOperationItems
2023-01-24 17:59:53 +00:00
itemOperList := ib . backupRequest . GetItemOperationsList ( )
* itemOperList = append ( * itemOperList , & newOperation )
}
2017-10-02 20:53:08 +00:00
2018-07-26 17:38:17 +00:00
for _ , additionalItem := range additionalItemIdentifiers {
gvr , resource , err := ib . discoveryHelper . ResourceFor ( additionalItem . GroupResource . WithVersion ( "" ) )
if err != nil {
2023-03-08 14:37:07 +00:00
return nil , itemFiles , err
2018-07-26 17:38:17 +00:00
}
2017-10-02 20:53:08 +00:00
2018-07-26 17:38:17 +00:00
client , err := ib . dynamicFactory . ClientForGroupVersionResource ( gvr . GroupVersion ( ) , resource , additionalItem . Namespace )
if err != nil {
2023-03-08 14:37:07 +00:00
return nil , itemFiles , err
2018-07-26 17:38:17 +00:00
}
2017-10-02 20:53:08 +00:00
2020-06-04 23:29:55 +00:00
item , err := client . Get ( additionalItem . Name , metav1 . GetOptions { } )
2022-09-26 10:33:02 +00:00
2020-06-04 23:29:55 +00:00
if apierrors . IsNotFound ( err ) {
log . WithFields ( logrus . Fields {
"groupResource" : additionalItem . GroupResource ,
"namespace" : additionalItem . Namespace ,
"name" : additionalItem . Name ,
} ) . Warnf ( "Additional item was not found in Kubernetes API, can't back it up" )
continue
}
2018-07-26 17:38:17 +00:00
if err != nil {
2023-03-08 14:37:07 +00:00
return nil , itemFiles , errors . WithStack ( err )
2017-10-02 20:53:08 +00:00
}
2018-01-03 18:02:38 +00:00
2023-03-08 14:37:07 +00:00
_ , additionalItemFiles , err := ib . backupItem ( log , item , gvr . GroupResource ( ) , gvr , mustInclude , finalize )
if err != nil {
return nil , itemFiles , err
2018-07-26 17:38:17 +00:00
}
2023-03-08 14:37:07 +00:00
itemFiles = append ( itemFiles , additionalItemFiles ... )
2017-10-02 20:53:08 +00:00
}
}
2023-03-08 14:37:07 +00:00
return obj , itemFiles , nil
2017-10-02 20:53:08 +00:00
}
2017-11-15 02:35:02 +00:00
2019-03-27 18:22:04 +00:00
// volumeSnapshotter instantiates and initializes a VolumeSnapshotter given a VolumeSnapshotLocation,
2018-09-26 22:18:45 +00:00
// or returns an existing one if one's already been initialized for the location.
2022-09-08 22:09:18 +00:00
func ( ib * itemBackupper ) volumeSnapshotter ( snapshotLocation * velerov1api . VolumeSnapshotLocation ) ( vsv1 . VolumeSnapshotter , error ) {
2019-03-27 18:22:04 +00:00
if bs , ok := ib . snapshotLocationVolumeSnapshotters [ snapshotLocation . Name ] ; ok {
2018-09-26 22:18:45 +00:00
return bs , nil
}
2019-03-27 18:22:04 +00:00
bs , err := ib . volumeSnapshotterGetter . GetVolumeSnapshotter ( snapshotLocation . Spec . Provider )
2018-09-26 22:18:45 +00:00
if err != nil {
return nil , err
}
if err := bs . Init ( snapshotLocation . Spec . Config ) ; err != nil {
return nil , err
}
2019-03-27 18:22:04 +00:00
if ib . snapshotLocationVolumeSnapshotters == nil {
2022-09-08 22:09:18 +00:00
ib . snapshotLocationVolumeSnapshotters = make ( map [ string ] vsv1 . VolumeSnapshotter )
2018-09-26 22:18:45 +00:00
}
2019-03-27 18:22:04 +00:00
ib . snapshotLocationVolumeSnapshotters [ snapshotLocation . Name ] = bs
2018-09-26 22:18:45 +00:00
return bs , nil
}
2020-02-03 18:47:18 +00:00
// zoneLabelDeprecated is the label that stores availability-zone info
// on PVs this is deprecated on Kubernetes >= 1.17.0
2017-11-15 02:35:02 +00:00
// zoneLabel is the label that stores availability-zone info
// on PVs
2020-02-03 18:47:18 +00:00
const (
zoneLabelDeprecated = "failure-domain.beta.kubernetes.io/zone"
2021-11-11 06:54:31 +00:00
// this is reused for nodeAffinity requirements
zoneLabel = "topology.kubernetes.io/zone"
awsEbsCsiZoneKey = "topology.ebs.csi.aws.com/zone"
azureCsiZoneKey = "topology.disk.csi.azure.com/zone"
2021-11-18 08:39:49 +00:00
gkeCsiZoneKey = "topology.gke.io/zone"
2022-02-22 08:06:39 +00:00
gkeZoneSeparator = "__"
2023-07-05 15:23:16 +00:00
// OpenStack CSI drivers topology keys
cinderCsiZoneKey = "topology.manila.csi.openstack.org/zone"
manilaCsiZoneKey = "topology.cinder.csi.openstack.org/zone"
2020-02-03 18:47:18 +00:00
)
2017-11-15 02:35:02 +00:00
// takePVSnapshot triggers a snapshot for the volume/disk underlying a PersistentVolume if the provided
// backup has volume snapshots enabled and the PV is of a compatible type. Also records cloud
// disk type and IOPS (if applicable) to be able to restore to current state later.
2020-04-13 20:53:58 +00:00
func ( ib * itemBackupper ) takePVSnapshot ( obj runtime . Unstructured , log logrus . FieldLogger ) error {
2017-11-15 02:35:02 +00:00
log . Info ( "Executing takePVSnapshot" )
2020-04-24 16:46:20 +00:00
if boolptr . IsSetToFalse ( ib . backupRequest . Spec . SnapshotVolumes ) {
2017-11-15 02:35:02 +00:00
log . Info ( "Backup has volume snapshots disabled; skipping volume snapshot action." )
return nil
}
2018-06-25 18:20:32 +00:00
pv := new ( corev1api . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pv ) ; err != nil {
return errors . WithStack ( err )
}
2019-01-24 16:55:08 +00:00
log = log . WithField ( "persistentVolume" , pv . Name )
2022-10-12 11:12:40 +00:00
// If this PV is claimed, see if we've already taken a (pod volume backup) snapshot of the contents
2018-06-25 18:20:32 +00:00
// of this PV. If so, don't take a snapshot.
if pv . Spec . ClaimRef != nil {
2022-10-12 11:12:40 +00:00
if ib . podVolumeSnapshotTracker . Has ( pv . Spec . ClaimRef . Namespace , pv . Spec . ClaimRef . Name ) {
log . Info ( "Skipping snapshot of persistent volume because volume is being backed up with pod volume backup." )
2018-06-25 18:20:32 +00:00
return nil
}
}
2022-03-31 08:58:00 +00:00
// #4758 Do not take snapshot for CSI PV to avoid duplicated snapshotting, when CSI feature is enabled.
if features . IsEnabled ( velerov1api . CSIFeatureFlag ) && pv . Spec . CSI != nil {
log . Infof ( "Skipping snapshot of persistent volume %s, because it's handled by CSI plugin." , pv . Name )
return nil
}
2023-06-09 12:55:00 +00:00
// TODO: Snapshot data mover is only supported for CSI plugin scenario by now.
// Need to add a mechanism to choose running which plugin for resources.
// After that, this warning can be removed.
if boolptr . IsSetToTrue ( ib . backupRequest . Spec . SnapshotMoveData ) {
log . Warnf ( "VolumeSnapshotter plugin doesn't support data movement." )
2023-08-04 10:09:21 +00:00
if features . IsEnabled ( velerov1api . CSIFeatureFlag ) && pv . Spec . CSI == nil {
log . Warn ( "Cannot use CSI data mover to handle PV, because PV doesn't contain CSI in spec." ,
" Fall back to Velero native snapshot." )
}
2023-06-09 12:55:00 +00:00
}
2023-03-21 06:39:25 +00:00
if ib . backupRequest . ResPolicies != nil {
2023-03-21 08:33:15 +00:00
if action , err := ib . backupRequest . ResPolicies . GetMatchAction ( pv ) ; err != nil {
log . WithError ( err ) . Errorf ( "Error getting matched resource policies for pv %s" , pv . Name )
return nil
} else if action != nil && action . Type == resourcepolicies . Skip {
2023-03-21 06:39:25 +00:00
log . Infof ( "skip snapshot of pv %s for the matched resource policies" , pv . Name )
2023-07-20 08:13:48 +00:00
// at this point we are sure this object is PV therefore we'll call the tracker directly
ib . backupRequest . SkippedPVTracker . Track ( pv . Name , volumeSnapshotApproach , "matched action is 'skip' in chosen resource policies" )
2023-03-21 06:39:25 +00:00
return nil
}
}
2020-02-03 18:47:18 +00:00
// TODO: -- once failure-domain.beta.kubernetes.io/zone is no longer
// supported in any velero-supported version of Kubernetes, remove fallback checking of it
pvFailureDomainZone , labelFound := pv . Labels [ zoneLabel ]
if ! labelFound {
log . Infof ( "label %q is not present on PersistentVolume, checking deprecated label..." , zoneLabel )
pvFailureDomainZone , labelFound = pv . Labels [ zoneLabelDeprecated ]
if ! labelFound {
2021-11-11 06:54:31 +00:00
var k string
2020-02-03 18:47:18 +00:00
log . Infof ( "label %q is not present on PersistentVolume" , zoneLabelDeprecated )
2023-07-05 15:23:16 +00:00
k , pvFailureDomainZone = zoneFromPVNodeAffinity ( pv , awsEbsCsiZoneKey , azureCsiZoneKey , gkeCsiZoneKey , cinderCsiZoneKey , manilaCsiZoneKey , zoneLabel , zoneLabelDeprecated )
2021-11-11 06:54:31 +00:00
if pvFailureDomainZone != "" {
log . Infof ( "zone info from nodeAffinity requirements: %s, key: %s" , pvFailureDomainZone , k )
} else {
log . Infof ( "zone info not available in nodeAffinity requirements" )
}
2020-02-03 18:47:18 +00:00
}
2017-11-15 02:35:02 +00:00
}
2018-09-26 22:18:45 +00:00
var (
2018-10-12 17:55:02 +00:00
volumeID , location string
2022-09-08 22:09:18 +00:00
volumeSnapshotter vsv1 . VolumeSnapshotter
2018-09-26 22:18:45 +00:00
)
for _ , snapshotLocation := range ib . backupRequest . SnapshotLocations {
2019-01-24 16:55:08 +00:00
log := log . WithField ( "volumeSnapshotLocation" , snapshotLocation . Name )
2019-03-27 18:22:04 +00:00
bs , err := ib . volumeSnapshotter ( snapshotLocation )
2018-09-26 22:18:45 +00:00
if err != nil {
2019-03-27 18:22:04 +00:00
log . WithError ( err ) . Error ( "Error getting volume snapshotter for volume snapshot location" )
2018-09-26 22:18:45 +00:00
continue
}
if volumeID , err = bs . GetVolumeID ( obj ) ; err != nil {
log . WithError ( err ) . Errorf ( "Error attempting to get volume ID for persistent volume" )
continue
}
if volumeID == "" {
2023-03-14 09:01:21 +00:00
log . Warn ( "No volume ID returned by volume snapshotter for persistent volume" )
2018-09-26 22:18:45 +00:00
continue
}
log . Infof ( "Got volume ID for persistent volume" )
2019-03-27 18:22:04 +00:00
volumeSnapshotter = bs
2018-10-12 17:55:02 +00:00
location = snapshotLocation . Name
2018-09-26 22:18:45 +00:00
break
2017-11-15 02:35:02 +00:00
}
2018-09-26 22:18:45 +00:00
2019-03-27 18:22:04 +00:00
if volumeSnapshotter == nil {
2023-06-30 00:14:10 +00:00
// the PV may still has change to be snapshotted by CSI plugin's `PVCBackupItemAction` in PVC backup logic
log . Info ( "Persistent volume is not a supported volume type for Velero-native volumeSnapshotter snapshot, skipping." )
2023-07-20 08:13:48 +00:00
ib . backupRequest . SkippedPVTracker . Track ( pv . Name , volumeSnapshotApproach , "no applicable volumesnapshotter found" )
2017-11-15 02:35:02 +00:00
return nil
}
log = log . WithField ( "volumeID" , volumeID )
2019-12-05 20:54:19 +00:00
// create tags from the backup's labels
tags := map [ string ] string { }
for k , v := range ib . backupRequest . GetLabels ( ) {
tags [ k ] = v
2018-03-02 22:04:06 +00:00
}
2019-08-06 20:01:36 +00:00
tags [ "velero.io/backup" ] = ib . backupRequest . Name
tags [ "velero.io/pv" ] = pv . Name
2018-03-02 22:04:06 +00:00
2018-10-12 17:55:02 +00:00
log . Info ( "Getting volume information" )
2019-03-27 18:22:04 +00:00
volumeType , iops , err := volumeSnapshotter . GetVolumeInfo ( volumeID , pvFailureDomainZone )
2017-11-15 02:35:02 +00:00
if err != nil {
return errors . WithMessage ( err , "error getting volume info" )
}
2019-04-26 16:14:26 +00:00
log . Info ( "Snapshotting persistent volume" )
2019-01-24 16:55:55 +00:00
snapshot := volumeSnapshot ( ib . backupRequest . Backup , pv . Name , volumeID , volumeType , pvFailureDomainZone , location , iops )
2017-11-15 02:35:02 +00:00
2018-10-12 17:55:02 +00:00
var errs [ ] error
2023-07-20 08:13:48 +00:00
ib . backupRequest . SkippedPVTracker . Untrack ( pv . Name )
2019-03-27 18:22:04 +00:00
snapshotID , err := volumeSnapshotter . CreateSnapshot ( snapshot . Spec . ProviderVolumeID , snapshot . Spec . VolumeAZ , tags )
2018-10-12 17:55:02 +00:00
if err != nil {
errs = append ( errs , errors . Wrap ( err , "error taking snapshot of volume" ) )
snapshot . Status . Phase = volume . SnapshotPhaseFailed
} else {
snapshot . Status . Phase = volume . SnapshotPhaseCompleted
snapshot . Status . ProviderSnapshotID = snapshotID
2017-11-15 02:35:02 +00:00
}
2018-10-12 17:55:02 +00:00
ib . backupRequest . VolumeSnapshots = append ( ib . backupRequest . VolumeSnapshots , snapshot )
2017-11-15 02:35:02 +00:00
2018-10-12 17:55:02 +00:00
// nil errors are automatically removed
return kubeerrs . NewAggregate ( errs )
}
2023-03-21 08:33:15 +00:00
func ( ib * itemBackupper ) getMatchAction ( obj runtime . Unstructured , groupResource schema . GroupResource , backupItemActionName string ) ( * resourcepolicies . Action , error ) {
2023-07-20 08:13:48 +00:00
if ib . backupRequest . ResPolicies != nil && groupResource == kuberesource . PersistentVolumeClaims && ( backupItemActionName == csiBIAPluginName || backupItemActionName == vsphereBIAPluginName ) {
2023-03-21 06:39:25 +00:00
pvc := corev1api . PersistentVolumeClaim { }
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , & pvc ) ; err != nil {
return nil , errors . WithStack ( err )
}
pvName := pvc . Spec . VolumeName
if pvName == "" {
return nil , errors . Errorf ( "PVC has no volume backing this claim" )
}
pv := & corev1api . PersistentVolume { }
if err := ib . kbClient . Get ( context . Background ( ) , kbClient . ObjectKey { Name : pvName } , pv ) ; err != nil {
return nil , errors . WithStack ( err )
}
2023-03-21 08:33:15 +00:00
return ib . backupRequest . ResPolicies . GetMatchAction ( pv )
2023-03-21 06:39:25 +00:00
}
2023-10-17 12:57:11 +00:00
2023-03-21 06:39:25 +00:00
return nil , nil
}
2023-07-20 08:13:48 +00:00
// trackSkippedPV tracks the skipped PV based on the object and the given approach and reason
// this function will be called throughout the process of backup, it needs to handle any object
func ( ib * itemBackupper ) trackSkippedPV ( obj runtime . Unstructured , groupResource schema . GroupResource , approach string , reason string , log logrus . FieldLogger ) {
if name , err := getPVName ( obj , groupResource ) ; len ( name ) > 0 && err == nil {
ib . backupRequest . SkippedPVTracker . Track ( name , approach , reason )
} else if err != nil {
log . WithError ( err ) . Warnf ( "unable to get PV name, skip tracking." )
}
}
// unTrackSkippedPV removes skipped PV based on the object from the tracker
// this function will be called throughout the process of backup, it needs to handle any object
func ( ib * itemBackupper ) unTrackSkippedPV ( obj runtime . Unstructured , groupResource schema . GroupResource , log logrus . FieldLogger ) {
if name , err := getPVName ( obj , groupResource ) ; len ( name ) > 0 && err == nil {
ib . backupRequest . SkippedPVTracker . Untrack ( name )
} else if err != nil {
log . WithError ( err ) . Warnf ( "unable to get PV name, skip untracking." )
}
}
2023-11-11 03:35:23 +00:00
func ( ib * itemBackupper ) addVolumeInfo ( obj runtime . Unstructured , log logrus . FieldLogger ) error {
pv := new ( corev1api . PersistentVolume )
err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pv )
if err != nil {
log . WithError ( err ) . Warnf ( "Fail to convert PV" )
return err
}
if ib . backupRequest . PVMap == nil {
ib . backupRequest . PVMap = make ( map [ string ] PvcPvInfo )
}
pvcName := ""
pvcNamespace := ""
if pv . Spec . ClaimRef != nil {
pvcName = pv . Spec . ClaimRef . Name
pvcNamespace = pv . Spec . ClaimRef . Namespace
ib . backupRequest . PVMap [ pvcNamespace + "/" + pvcName ] = PvcPvInfo {
PVCName : pvcName ,
PVCNamespace : pvcNamespace ,
PV : * pv ,
}
}
ib . backupRequest . PVMap [ pv . Name ] = PvcPvInfo {
PVCName : pvcName ,
PVCNamespace : pvcNamespace ,
PV : * pv ,
}
return nil
}
2023-07-20 08:13:48 +00:00
// convert the input object to PV/PVC and get the PV name
func getPVName ( obj runtime . Unstructured , groupResource schema . GroupResource ) ( string , error ) {
if groupResource == kuberesource . PersistentVolumes {
pv := new ( corev1api . PersistentVolume )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pv ) ; err != nil {
return "" , fmt . Errorf ( "failed to convert object to PV: %w" , err )
}
return pv . Name , nil
}
if groupResource == kuberesource . PersistentVolumeClaims {
pvc := new ( corev1api . PersistentVolumeClaim )
if err := runtime . DefaultUnstructuredConverter . FromUnstructured ( obj . UnstructuredContent ( ) , pvc ) ; err != nil {
return "" , fmt . Errorf ( "failed to convert object to PVC: %w" , err )
}
if pvc . Spec . VolumeName == "" {
return "" , fmt . Errorf ( "PV name is not set in PVC" )
}
return pvc . Spec . VolumeName , nil
}
return "" , nil
}
2020-04-13 19:29:34 +00:00
func volumeSnapshot ( backup * velerov1api . Backup , volumeName , volumeID , volumeType , az , location string , iops * int64 ) * volume . Snapshot {
2018-10-12 17:55:02 +00:00
return & volume . Snapshot {
Spec : volume . SnapshotSpec {
2018-10-16 14:28:05 +00:00
BackupName : backup . Name ,
BackupUID : string ( backup . UID ) ,
Location : location ,
PersistentVolumeName : volumeName ,
ProviderVolumeID : volumeID ,
VolumeType : volumeType ,
VolumeAZ : az ,
VolumeIOPS : iops ,
2018-10-12 17:55:02 +00:00
} ,
Status : volume . SnapshotStatus {
Phase : volume . SnapshotPhaseNew ,
} ,
}
2017-11-15 02:35:02 +00:00
}
2019-08-05 17:15:55 +00:00
// resourceKey returns a string representing the object's GroupVersionKind (e.g.
// apps/v1/Deployment).
func resourceKey ( obj runtime . Unstructured ) string {
gvk := obj . GetObjectKind ( ) . GroupVersionKind ( )
return fmt . Sprintf ( "%s/%s" , gvk . GroupVersion ( ) . String ( ) , gvk . Kind )
}
2020-05-01 19:54:57 +00:00
// resourceVersion returns a string representing the object's API Version (e.g.
// v1 if item belongs to apps/v1
func resourceVersion ( obj runtime . Unstructured ) string {
gvk := obj . GetObjectKind ( ) . GroupVersionKind ( )
return gvk . Version
}
2021-11-11 06:54:31 +00:00
// zoneFromPVNodeAffinity iterates the node affinity requirement of a PV to
// get its availability zone, it returns the key merely for logging.
func zoneFromPVNodeAffinity ( res * corev1api . PersistentVolume , topologyKeys ... string ) ( string , string ) {
nodeAffinity := res . Spec . NodeAffinity
if nodeAffinity == nil {
return "" , ""
}
keySet := sets . NewString ( topologyKeys ... )
2022-02-21 14:04:21 +00:00
providerGke := false
zones := make ( [ ] string , 0 )
2021-11-11 06:54:31 +00:00
for _ , term := range nodeAffinity . Required . NodeSelectorTerms {
if term . MatchExpressions == nil {
continue
}
for _ , exp := range term . MatchExpressions {
if keySet . Has ( exp . Key ) && exp . Operator == "In" && len ( exp . Values ) > 0 {
2022-02-21 14:04:21 +00:00
if exp . Key == gkeCsiZoneKey {
providerGke = true
zones = append ( zones , exp . Values [ 0 ] )
} else {
return exp . Key , exp . Values [ 0 ]
}
2021-11-11 06:54:31 +00:00
}
}
}
2022-02-21 14:04:21 +00:00
if providerGke {
2022-02-22 08:06:39 +00:00
return gkeCsiZoneKey , strings . Join ( zones , gkeZoneSeparator )
2022-02-21 14:04:21 +00:00
}
2021-11-11 06:54:31 +00:00
return "" , ""
}