mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 19:56:06 +00:00
Merge pull request #7138 from blackpiglet/6595_volumeinfo_restore
Use VolumeInfo to help restore the PV.
This commit is contained in:
@@ -71,8 +71,8 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
|
||||
req := &Request{
|
||||
Backup: defaultBackup().Result(),
|
||||
SkippedPVTracker: NewSkipPVTracker(),
|
||||
PVMap: map[string]PvcPvInfo{},
|
||||
}
|
||||
|
||||
backupFile := bytes.NewBuffer([]byte{})
|
||||
|
||||
apiResources := []*test.APIResource{
|
||||
|
||||
@@ -699,28 +699,15 @@ func (ib *itemBackupper) addVolumeInfo(obj runtime.Unstructured, log logrus.Fiel
|
||||
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,
|
||||
}
|
||||
ib.backupRequest.VolumesInformation.InsertPVMap(*pv, pvcName, pvcNamespace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
@@ -242,36 +241,16 @@ func TestRandom(t *testing.T) {
|
||||
|
||||
func TestAddVolumeInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pv *corev1api.PersistentVolume
|
||||
expectedVolumeInfo map[string]PvcPvInfo
|
||||
name string
|
||||
pv *corev1api.PersistentVolume
|
||||
}{
|
||||
{
|
||||
name: "PV has ClaimRef",
|
||||
pv: builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
|
||||
expectedVolumeInfo: map[string]PvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
|
||||
},
|
||||
"testNS/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "testNS",
|
||||
PV: *builder.ForPersistentVolume("testPV").ClaimRef("testNS", "testPVC").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PV has no ClaimRef",
|
||||
pv: builder.ForPersistentVolume("testPV").Result(),
|
||||
expectedVolumeInfo: map[string]PvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "",
|
||||
PVCNamespace: "",
|
||||
PV: *builder.ForPersistentVolume("testPV").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -279,7 +258,7 @@ func TestAddVolumeInfo(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ib := itemBackupper{}
|
||||
ib.backupRequest = new(Request)
|
||||
ib.backupRequest.VolumeInfos.VolumeInfos = make([]volume.VolumeInfo, 0)
|
||||
ib.backupRequest.VolumesInformation.Init()
|
||||
|
||||
pvObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pv)
|
||||
require.NoError(t, err)
|
||||
@@ -287,7 +266,6 @@ func TestAddVolumeInfo(t *testing.T) {
|
||||
|
||||
err = ib.addVolumeInfo(&unstructured.Unstructured{Object: pvObj}, logger)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedVolumeInfo, ib.backupRequest.PVMap)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+18
-11
@@ -20,10 +20,9 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
@@ -54,17 +53,11 @@ type Request struct {
|
||||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
SkippedPVTracker *skipPVTracker
|
||||
// A map contains the backup-included PV detail content.
|
||||
// The key is PV name or PVC name(The format is PVC-namespace/PVC-name)
|
||||
PVMap map[string]PvcPvInfo
|
||||
VolumeInfos volume.VolumeInfos
|
||||
VolumesInformation internalVolume.VolumesInformation
|
||||
}
|
||||
|
||||
type PvcPvInfo struct {
|
||||
PVCName string
|
||||
PVCNamespace string
|
||||
PV corev1api.PersistentVolume
|
||||
}
|
||||
// VolumesInformation contains the information needs by generating
|
||||
// the backup VolumeInfo array.
|
||||
|
||||
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
||||
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
|
||||
@@ -94,3 +87,17 @@ func (r *Request) BackupResourceList() map[string][]string {
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (r *Request) FillVolumesInformation() {
|
||||
skippedPVMap := make(map[string]string)
|
||||
|
||||
for _, skippedPV := range r.SkippedPVTracker.Summary() {
|
||||
skippedPVMap[skippedPV.Name] = skippedPV.SerializeSkipReasons()
|
||||
}
|
||||
|
||||
r.VolumesInformation.SkippedPVs = skippedPVMap
|
||||
r.VolumesInformation.NativeSnapshots = r.VolumeSnapshots
|
||||
r.VolumesInformation.PodVolumeBackups = r.PodVolumeBackups
|
||||
r.VolumesInformation.BackupOperations = *r.GetItemOperationsList()
|
||||
r.VolumesInformation.BackupName = r.Backup.Name
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2019 the Velero contributors.
|
||||
Copyright the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright The Velero Contributors.
|
||||
|
||||
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 (
|
||||
|
||||
@@ -123,7 +123,7 @@ func Stream(ctx context.Context, kbClient kbclient.Client, namespace, name strin
|
||||
ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout,
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, created.Status.DownloadURL, nil)
|
||||
httpReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, created.Status.DownloadURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -41,19 +40,16 @@ import (
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcepolicies"
|
||||
"github.com/vmware-tanzu/velero/internal/storage"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/encode"
|
||||
@@ -319,8 +315,8 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
|
||||
request := &pkgbackup.Request{
|
||||
Backup: backup.DeepCopy(), // don't modify items in the cache
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
PVMap: map[string]pkgbackup.PvcPvInfo{},
|
||||
}
|
||||
request.VolumesInformation.Init()
|
||||
|
||||
// set backup major version - deprecated, use Status.FormatVersion
|
||||
request.Status.Version = pkgbackup.BackupVersion
|
||||
@@ -591,7 +587,7 @@ func (b *backupReconciler) validateAndGetSnapshotLocations(backup *velerov1api.B
|
||||
|
||||
// add credential to config for each location
|
||||
for _, location := range providerLocations {
|
||||
err = volume.UpdateVolumeSnapshotLocationWithCredentialConfig(location, b.credentialFileStore)
|
||||
err = internalVolume.UpdateVolumeSnapshotLocationWithCredentialConfig(location, b.credentialFileStore)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("error adding credentials to volume snapshot location named %s: %v", location.Name, err))
|
||||
continue
|
||||
@@ -737,9 +733,7 @@ func (b *backupReconciler) runBackup(backup *pkgbackup.Request) error {
|
||||
if logFile, err := backupLog.GetPersistFile(); err != nil {
|
||||
fatalErrs = append(fatalErrs, errors.Wrap(err, "error getting backup log file"))
|
||||
} else {
|
||||
backup.VolumeInfos.VolumeInfos = generateVolumeInfo(backup, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, b.globalCRClient, backupLog)
|
||||
|
||||
if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, results); len(errs) > 0 {
|
||||
if errs := persistBackup(backup, backupFile, logFile, backupStore, volumeSnapshots, volumeSnapshotContents, volumeSnapshotClasses, results, b.globalCRClient, backupLog); len(errs) > 0 {
|
||||
fatalErrs = append(fatalErrs, errs...)
|
||||
}
|
||||
}
|
||||
@@ -796,8 +790,10 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
backupStore persistence.BackupStore,
|
||||
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
|
||||
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
csiVolumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
results map[string]results.Result,
|
||||
crClient kbclient.Client,
|
||||
logger logrus.FieldLogger,
|
||||
) []error {
|
||||
persistErrs := []error{}
|
||||
backupJSON := new(bytes.Buffer)
|
||||
@@ -832,7 +828,7 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
if errs != nil {
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
csiSnapshotClassesJSON, errs := encode.ToJSONGzip(csiVolumesnapshotClasses, "csi volume snapshot classes list")
|
||||
csiSnapshotClassesJSON, errs := encode.ToJSONGzip(csiVolumeSnapshotClasses, "csi volume snapshot classes list")
|
||||
if errs != nil {
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
@@ -847,7 +843,15 @@ func persistBackup(backup *pkgbackup.Request,
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
|
||||
volumeInfoJSON, errs := encode.ToJSONGzip(backup.VolumeInfos, "backup volumes information")
|
||||
backup.FillVolumesInformation()
|
||||
|
||||
volumeInfoJSON, errs := encode.ToJSONGzip(backup.VolumesInformation.Result(
|
||||
csiVolumeSnapshots,
|
||||
csiVolumeSnapshotContents,
|
||||
csiVolumeSnapshotClasses,
|
||||
crClient,
|
||||
logger,
|
||||
), "backup volumes information")
|
||||
if errs != nil {
|
||||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
@@ -912,328 +916,3 @@ func oldAndNewFilterParametersUsedTogether(backupSpec velerov1api.BackupSpec) bo
|
||||
|
||||
return haveOldResourceFilterParameters && haveNewResourceFilterParameters
|
||||
}
|
||||
|
||||
func generateVolumeInfo(backup *pkgbackup.Request, csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
volumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
skippedVolumeInfos := generateVolumeInfoForSkippedPV(backup, logger)
|
||||
volumeInfos = append(volumeInfos, skippedVolumeInfos...)
|
||||
|
||||
nativeSnapshotVolumeInfos := generateVolumeInfoForVeleroNativeSnapshot(backup, logger)
|
||||
volumeInfos = append(volumeInfos, nativeSnapshotVolumeInfos...)
|
||||
|
||||
csiVolumeInfos := generateVolumeInfoForCSIVolumeSnapshot(backup, csiVolumeSnapshots, csiVolumeSnapshotContents, csiVolumesnapshotClasses, logger)
|
||||
volumeInfos = append(volumeInfos, csiVolumeInfos...)
|
||||
|
||||
pvbVolumeInfos := generateVolumeInfoFromPVB(backup, crClient, logger)
|
||||
volumeInfos = append(volumeInfos, pvbVolumeInfos...)
|
||||
|
||||
dataUploadVolumeInfos := generateVolumeInfoFromDataUpload(backup, crClient, logger)
|
||||
volumeInfos = append(volumeInfos, dataUploadVolumeInfos...)
|
||||
|
||||
return volumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||
func generateVolumeInfoForSkippedPV(backup *pkgbackup.Request, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
for _, skippedPV := range backup.SkippedPVTracker.Summary() {
|
||||
if pvcPVInfo, ok := backup.PVMap[skippedPV.Name]; ok {
|
||||
volumeInfo := volume.VolumeInfo{
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: skippedPV.Name,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: true,
|
||||
SkippedReason: skippedPV.SerializeSkipReasons(),
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
logger.Warnf("Cannot find info for PV %s", skippedPV.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
||||
func generateVolumeInfoForVeleroNativeSnapshot(backup *pkgbackup.Request, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
for _, nativeSnapshot := range backup.VolumeSnapshots {
|
||||
var iops int64
|
||||
if nativeSnapshot.Spec.VolumeIOPS != nil {
|
||||
iops = *nativeSnapshot.Spec.VolumeIOPS
|
||||
}
|
||||
|
||||
if pvcPVInfo, ok := backup.PVMap[nativeSnapshot.Spec.PersistentVolumeName]; ok {
|
||||
volumeInfo := volume.VolumeInfo{
|
||||
BackupMethod: volume.NativeSnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
NativeSnapshotInfo: volume.NativeSnapshotInfo{
|
||||
SnapshotHandle: nativeSnapshot.Status.ProviderSnapshotID,
|
||||
VolumeType: nativeSnapshot.Spec.VolumeType,
|
||||
VolumeAZ: nativeSnapshot.Spec.VolumeAZ,
|
||||
IOPS: strconv.FormatInt(iops, 10),
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
logger.Warnf("cannot find info for PV %s", nativeSnapshot.Spec.PersistentVolumeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
|
||||
func generateVolumeInfoForCSIVolumeSnapshot(backup *pkgbackup.Request, csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent, csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
for _, volumeSnapshot := range csiVolumeSnapshots {
|
||||
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||
var volumeSnapshotContent *snapshotv1api.VolumeSnapshotContent
|
||||
|
||||
// This is protective logic. The passed-in VS should be all related
|
||||
// to this backup.
|
||||
if volumeSnapshot.Labels[velerov1api.BackupNameLabel] != backup.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Spec.VolumeSnapshotClassName == nil {
|
||||
logger.Warnf("Cannot find VolumeSnapshotClass for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Status == nil || volumeSnapshot.Status.BoundVolumeSnapshotContentName == nil {
|
||||
logger.Warnf("Cannot fine VolumeSnapshotContent for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Spec.Source.PersistentVolumeClaimName == nil {
|
||||
logger.Warnf("VolumeSnapshot %s/%s doesn't have a source PVC", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for index := range csiVolumesnapshotClasses {
|
||||
if *volumeSnapshot.Spec.VolumeSnapshotClassName == csiVolumesnapshotClasses[index].Name {
|
||||
volumeSnapshotClass = &csiVolumesnapshotClasses[index]
|
||||
}
|
||||
}
|
||||
|
||||
for index := range csiVolumeSnapshotContents {
|
||||
if *volumeSnapshot.Status.BoundVolumeSnapshotContentName == csiVolumeSnapshotContents[index].Name {
|
||||
volumeSnapshotContent = &csiVolumeSnapshotContents[index]
|
||||
}
|
||||
}
|
||||
|
||||
if volumeSnapshotClass == nil || volumeSnapshotContent == nil {
|
||||
logger.Warnf("fail to get VolumeSnapshotContent or VolumeSnapshotClass for VolumeSnapshot: %s/%s",
|
||||
volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var operation itemoperation.BackupOperation
|
||||
for _, op := range *backup.GetItemOperationsList() {
|
||||
if op.Spec.ResourceIdentifier.GroupResource.String() == kuberesource.VolumeSnapshots.String() &&
|
||||
op.Spec.ResourceIdentifier.Name == volumeSnapshot.Name &&
|
||||
op.Spec.ResourceIdentifier.Namespace == volumeSnapshot.Namespace {
|
||||
operation = *op
|
||||
}
|
||||
}
|
||||
|
||||
var size int64
|
||||
if volumeSnapshot.Status.RestoreSize != nil {
|
||||
size = volumeSnapshot.Status.RestoreSize.Value()
|
||||
}
|
||||
snapshotHandle := ""
|
||||
if volumeSnapshotContent.Status.SnapshotHandle != nil {
|
||||
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
|
||||
}
|
||||
if pvcPVInfo, ok := backup.PVMap[volumeSnapshot.Namespace+"/"+*volumeSnapshot.Spec.Source.PersistentVolumeClaimName]; ok {
|
||||
volumeInfo := volume.VolumeInfo{
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
Skipped: false,
|
||||
SnapshotDataMoved: false,
|
||||
PreserveLocalSnapshot: true,
|
||||
OperationID: operation.Spec.OperationID,
|
||||
StartTimestamp: &(volumeSnapshot.CreationTimestamp),
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
VSCName: *volumeSnapshot.Status.BoundVolumeSnapshotContentName,
|
||||
Size: size,
|
||||
Driver: volumeSnapshotClass.Driver,
|
||||
SnapshotHandle: snapshotHandle,
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
logger.Warnf("cannot find info for PVC %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Spec.Source.PersistentVolumeClaimName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
|
||||
func generateVolumeInfoFromPVB(backup *pkgbackup.Request, crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
for _, pvb := range backup.PodVolumeBackups {
|
||||
volumeInfo := volume.VolumeInfo{
|
||||
BackupMethod: volume.PodVolumeBackup,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
StartTimestamp: pvb.Status.StartTimestamp,
|
||||
PVBInfo: volume.PodVolumeBackupInfo{
|
||||
SnapshotHandle: pvb.Status.SnapshotID,
|
||||
Size: pvb.Status.Progress.TotalBytes,
|
||||
UploaderType: pvb.Spec.UploaderType,
|
||||
VolumeName: pvb.Spec.Volume,
|
||||
PodName: pvb.Spec.Pod.Name,
|
||||
PodNamespace: pvb.Spec.Pod.Namespace,
|
||||
NodeName: pvb.Spec.Node,
|
||||
},
|
||||
}
|
||||
|
||||
pod := new(corev1api.Pod)
|
||||
pvcName := ""
|
||||
err := crClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: pvb.Spec.Pod.Namespace, Name: pvb.Spec.Pod.Name}, pod)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("Fail to get pod for PodVolumeBackup: ", pvb.Name)
|
||||
continue
|
||||
}
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == pvb.Spec.Volume && volume.PersistentVolumeClaim != nil {
|
||||
pvcName = volume.PersistentVolumeClaim.ClaimName
|
||||
}
|
||||
}
|
||||
|
||||
if pvcName != "" {
|
||||
if pvcPVInfo, ok := backup.PVMap[pod.Namespace+"/"+pvcName]; ok {
|
||||
volumeInfo.PVCName = pvcPVInfo.PVCName
|
||||
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||
volumeInfo.PVInfo = volume.PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
}
|
||||
} else {
|
||||
logger.Warnf("Cannot find info for PVC %s/%s", pod.Namespace, pvcName)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromDataUpload generate VolumeInfo for DataUpload.
|
||||
func generateVolumeInfoFromDataUpload(backup *pkgbackup.Request, crClient kbclient.Client, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
||||
if err := crClient.List(context.TODO(), vsClassList); err != nil {
|
||||
logger.WithError(err).Errorf("cannot list VolumeSnapshotClass %s", err.Error())
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
for _, operation := range *backup.GetItemOperationsList() {
|
||||
if operation.Spec.ResourceIdentifier.GroupResource.String() == kuberesource.PersistentVolumeClaims.String() {
|
||||
var duIdentifier velero.ResourceIdentifier
|
||||
|
||||
for _, identifier := range operation.Spec.PostOperationItems {
|
||||
if identifier.GroupResource.String() == "datauploads.velero.io" {
|
||||
duIdentifier = identifier
|
||||
}
|
||||
}
|
||||
if duIdentifier.Empty() {
|
||||
logger.Warnf("cannot find DataUpload for PVC %s/%s backup async operation",
|
||||
operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
dataUpload := new(velerov2alpha1.DataUpload)
|
||||
err := crClient.Get(
|
||||
context.TODO(),
|
||||
kbclient.ObjectKey{
|
||||
Namespace: duIdentifier.Namespace,
|
||||
Name: duIdentifier.Name},
|
||||
dataUpload,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Warnf("fail to get DataUpload for operation %s: %s", operation.Spec.OperationID, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
driverUsedByVSClass := ""
|
||||
for index := range vsClassList.Items {
|
||||
if vsClassList.Items[index].Name == dataUpload.Spec.CSISnapshot.SnapshotClass {
|
||||
driverUsedByVSClass = vsClassList.Items[index].Driver
|
||||
}
|
||||
}
|
||||
|
||||
if pvcPVInfo, ok := backup.PVMap[operation.Spec.ResourceIdentifier.Namespace+"/"+operation.Spec.ResourceIdentifier.Name]; ok {
|
||||
volumeInfo := volume.VolumeInfo{
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: true,
|
||||
Skipped: false,
|
||||
OperationID: operation.Spec.OperationID,
|
||||
StartTimestamp: operation.Status.Created,
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
Driver: driverUsedByVSClass,
|
||||
},
|
||||
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||
DataMover: dataUpload.Spec.DataMover,
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
logger.Warnf("Cannot find info for PVC %s/%s", operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tmpVolumeInfos
|
||||
}
|
||||
|
||||
@@ -34,26 +34,17 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/utils/clock"
|
||||
testclocks "k8s.io/utils/clock/testing"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/backup"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
|
||||
fakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
@@ -65,10 +56,10 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
)
|
||||
|
||||
@@ -1742,749 +1733,3 @@ func TestPatchResourceWorksWithStatus(t *testing.T) {
|
||||
|
||||
}
|
||||
}
|
||||
func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
skippedPVName string
|
||||
pvMap map[string]backup.PvcPvInfo
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Cannot find info for PV",
|
||||
skippedPVName: "testPV",
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal Skipped PV info",
|
||||
skippedPVName: "testPV",
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"velero/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
Skipped: true,
|
||||
SkippedReason: "CSI: skipped for PodVolumeBackup;",
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := new(backup.Request)
|
||||
request.SkippedPVTracker = backup.NewSkipPVTracker()
|
||||
if tc.skippedPVName != "" {
|
||||
request.SkippedPVTracker.Track(tc.skippedPVName, "CSI", "skipped for PodVolumeBackup")
|
||||
}
|
||||
if tc.pvMap != nil {
|
||||
request.PVMap = tc.pvMap
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForSkippedPV(request, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||
resourceQuantity := resource.MustParse("100Gi")
|
||||
now := metav1.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
volumeSnapshot snapshotv1api.VolumeSnapshot
|
||||
volumeSnapshotContent snapshotv1api.VolumeSnapshotContent
|
||||
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
|
||||
pvMap map[string]backup.PvcPvInfo
|
||||
operation *itemoperation.BackupOperation
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "VS doesn't have VolumeSnapshotClass name",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VS doesn't have status",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VS doesn't have PVC",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find VSC for VS",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
Source: snapshotv1api.VolumeSnapshotSource{
|
||||
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||
},
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find VolumeInfo for PVC",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
Source: snapshotv1api.VolumeSnapshotSource{
|
||||
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||
},
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
},
|
||||
},
|
||||
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal VolumeSnapshot case",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
CreationTimestamp: now,
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
Source: snapshotv1api.VolumeSnapshotSource{
|
||||
PersistentVolumeClaimName: stringPtr("testPVC"),
|
||||
},
|
||||
},
|
||||
Status: &snapshotv1api.VolumeSnapshotStatus{
|
||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||
RestoreSize: &resourceQuantity,
|
||||
},
|
||||
},
|
||||
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"velero/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
OperationID: "testID",
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "snapshot.storage.k8s.io",
|
||||
Resource: "volumesnapshots",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testVS",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
OperationID: "testID",
|
||||
StartTimestamp: &now,
|
||||
PreserveLocalSnapshot: true,
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
Size: 107374182400,
|
||||
VSCName: "testContent",
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := new(backup.Request)
|
||||
request.Backup = new(velerov1api.Backup)
|
||||
if tc.pvMap != nil {
|
||||
request.PVMap = tc.pvMap
|
||||
}
|
||||
operationList := request.GetItemOperationsList()
|
||||
if tc.operation != nil {
|
||||
*operationList = append(*operationList, tc.operation)
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForCSIVolumeSnapshot(request, []snapshotv1api.VolumeSnapshot{tc.volumeSnapshot}, []snapshotv1api.VolumeSnapshotContent{tc.volumeSnapshotContent}, []snapshotv1api.VolumeSnapshotClass{tc.volumeSnapshotClass}, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nativeSnapshot volume.Snapshot
|
||||
pvMap map[string]backup.PvcPvInfo
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Native snapshot's IPOS pointer is nil",
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: nil,
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find info for the PV",
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal native snapshot",
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
VolumeType: "ssd",
|
||||
VolumeAZ: "us-central1-a",
|
||||
},
|
||||
Status: volume.SnapshotStatus{
|
||||
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: volume.NativeSnapshot,
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
NativeSnapshotInfo: volume.NativeSnapshotInfo{
|
||||
SnapshotHandle: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||
VolumeType: "ssd",
|
||||
VolumeAZ: "us-central1-a",
|
||||
IOPS: "100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := new(backup.Request)
|
||||
request.VolumeSnapshots = append(request.VolumeSnapshots, &tc.nativeSnapshot)
|
||||
if tc.pvMap != nil {
|
||||
request.PVMap = tc.pvMap
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForVeleroNativeSnapshot(request, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pvb *velerov1api.PodVolumeBackup
|
||||
pod *corev1api.Pod
|
||||
pvMap map[string]backup.PvcPvInfo
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "cannot find PVB's pod, should fail",
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "PVB doesn't have a related PVC",
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||
Name: "test",
|
||||
VolumeMounts: []corev1api.VolumeMount{
|
||||
{
|
||||
Name: "testVolume",
|
||||
MountPath: "/data",
|
||||
},
|
||||
},
|
||||
}).Volumes(
|
||||
&corev1api.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1api.VolumeSource{
|
||||
HostPath: &corev1api.HostPathVolumeSource{},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "",
|
||||
PVCNamespace: "",
|
||||
PVName: "",
|
||||
BackupMethod: volume.PodVolumeBackup,
|
||||
PVBInfo: volume.PodVolumeBackupInfo{
|
||||
PodName: "testPod",
|
||||
PodNamespace: "velero",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Backup doesn't have information for PVC",
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||
Name: "test",
|
||||
VolumeMounts: []corev1api.VolumeMount{
|
||||
{
|
||||
Name: "testVolume",
|
||||
MountPath: "/data",
|
||||
},
|
||||
},
|
||||
}).Volumes(
|
||||
&corev1api.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1api.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "testPVC",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "PVB's volume has a PVC",
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"velero/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
pod: builder.ForPod("velero", "testPod").Containers(&corev1api.Container{
|
||||
Name: "test",
|
||||
VolumeMounts: []corev1api.VolumeMount{
|
||||
{
|
||||
Name: "testVolume",
|
||||
MountPath: "/data",
|
||||
},
|
||||
},
|
||||
}).Volumes(
|
||||
&corev1api.Volume{
|
||||
Name: "",
|
||||
VolumeSource: corev1api.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "testPVC",
|
||||
},
|
||||
},
|
||||
},
|
||||
).Result(),
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: volume.PodVolumeBackup,
|
||||
PVBInfo: volume.PodVolumeBackupInfo{
|
||||
PodName: "testPod",
|
||||
PodNamespace: "velero",
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
request := new(pkgbackup.Request)
|
||||
request.PodVolumeBackups = append(request.PodVolumeBackups, tc.pvb)
|
||||
if tc.pvMap != nil {
|
||||
request.PVMap = tc.pvMap
|
||||
}
|
||||
if tc.pod != nil {
|
||||
require.NoError(t, crClient.Create(context.TODO(), tc.pod))
|
||||
}
|
||||
|
||||
volumeInfos := generateVolumeInfoFromPVB(request, crClient, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
tests := []struct {
|
||||
name string
|
||||
volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||
dataUpload *velerov2alpha1.DataUpload
|
||||
operation *itemoperation.BackupOperation
|
||||
pvMap map[string]backup.PvcPvInfo
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Operation is not for PVC",
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "configmaps",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Operation doesn't have DataUpload PostItemOperation",
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "persistentvolumeclaims",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testPVC",
|
||||
},
|
||||
PostOperationItems: []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "configmaps",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "DataUpload cannot be found for operation",
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
OperationID: "testOperation",
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "persistentvolumeclaims",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testPVC",
|
||||
},
|
||||
PostOperationItems: []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testDU",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VolumeSnapshotClass cannot be found for operation",
|
||||
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||
VolumeSnapshot: "testVS",
|
||||
}).SnapshotID("testSnapshotHandle").Result(),
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
OperationID: "testOperation",
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "persistentvolumeclaims",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testPVC",
|
||||
},
|
||||
PostOperationItems: []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testDU",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"velero/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
OperationID: "testOperation",
|
||||
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Normal DataUpload case",
|
||||
dataUpload: builder.ForDataUpload("velero", "testDU").DataMover("velero").CSISnapshot(&velerov2alpha1.CSISnapshotSpec{
|
||||
VolumeSnapshot: "testVS",
|
||||
SnapshotClass: "testClass",
|
||||
}).SnapshotID("testSnapshotHandle").Result(),
|
||||
volumeSnapshotClass: builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
OperationID: "testOperation",
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "persistentvolumeclaims",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testPVC",
|
||||
},
|
||||
PostOperationItems: []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: "velero",
|
||||
Name: "testDU",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: itemoperation.OperationStatus{
|
||||
Created: &now,
|
||||
},
|
||||
},
|
||||
pvMap: map[string]backup.PvcPvInfo{
|
||||
"velero/testPVC": {
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PV: corev1api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testPV",
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
Spec: corev1api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: corev1api.PersistentVolumeReclaimDelete,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
OperationID: "testOperation",
|
||||
StartTimestamp: &now,
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: volume.PVInfo{
|
||||
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := new(backup.Request)
|
||||
operationList := request.GetItemOperationsList()
|
||||
if tc.operation != nil {
|
||||
*operationList = append(*operationList, tc.operation)
|
||||
}
|
||||
if tc.pvMap != nil {
|
||||
request.PVMap = tc.pvMap
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
crClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||
if tc.dataUpload != nil {
|
||||
crClient.Create(context.TODO(), tc.dataUpload)
|
||||
}
|
||||
|
||||
if tc.volumeSnapshotClass != nil {
|
||||
crClient.Create(context.TODO(), tc.volumeSnapshotClass)
|
||||
}
|
||||
|
||||
volumeInfos := generateVolumeInfoFromDataUpload(request, crClient, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func int64Ptr(val int) *int64 {
|
||||
i := int64(val)
|
||||
return &i
|
||||
}
|
||||
|
||||
func stringPtr(str string) *string {
|
||||
return &str
|
||||
}
|
||||
|
||||
@@ -22,40 +22,35 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/delete"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/podvolume"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -462,7 +457,7 @@ func (r *backupDeletionReconciler) volumeSnapshottersForVSL(
|
||||
}
|
||||
|
||||
// add credential to config
|
||||
err := volume.UpdateVolumeSnapshotLocationWithCredentialConfig(vsl, r.credentialStore)
|
||||
err := internalVolume.UpdateVolumeSnapshotLocationWithCredentialConfig(vsl, r.credentialStore)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcemodifiers"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/label"
|
||||
@@ -520,6 +521,17 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
|
||||
return errors.Wrap(err, "fail to fetch CSI VolumeSnapshots metadata")
|
||||
}
|
||||
|
||||
backupVolumeInfoMap := make(map[string]internalVolume.VolumeInfo)
|
||||
volumeInfos, err := backupStore.GetBackupVolumeInfos(restore.Spec.BackupName)
|
||||
if err != nil {
|
||||
restoreLog.WithError(err).Errorf("fail to get VolumeInfos metadata file for backup %s", restore.Spec.BackupName)
|
||||
return errors.WithStack(err)
|
||||
} else {
|
||||
for _, volumeInfo := range volumeInfos {
|
||||
backupVolumeInfoMap[volumeInfo.PVName] = *volumeInfo
|
||||
}
|
||||
}
|
||||
|
||||
restoreLog.Info("starting restore")
|
||||
|
||||
var podVolumeBackups []*api.PodVolumeBackup
|
||||
@@ -537,6 +549,7 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
|
||||
ResourceModifiers: resourceModifiers,
|
||||
DisableInformerCache: r.disableInformerCache,
|
||||
CSIVolumeSnapshots: csiVolumeSnapshots,
|
||||
VolumeInfoMap: backupVolumeInfoMap,
|
||||
}
|
||||
restoreWarnings, restoreErrors := r.restorer.RestoreWithResolvers(restoreReq, actionsResolver, pluginManager)
|
||||
|
||||
|
||||
@@ -29,15 +29,15 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcemodifiers"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||
@@ -235,6 +235,7 @@ func TestRestoreReconcile(t *testing.T) {
|
||||
putRestoreLogErr error
|
||||
expectedFinalPhase string
|
||||
addValidFinalizer bool
|
||||
emptyVolumeInfo bool
|
||||
}{
|
||||
{
|
||||
name: "restore with both namespace in both includedNamespaces and excludedNamespaces fails validation",
|
||||
@@ -415,6 +416,18 @@ func TestRestoreReconcile(t *testing.T) {
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid restore with empty VolumeInfos",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
emptyVolumeInfo: true,
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedStartTime: ×tamp,
|
||||
expectedCompletedTime: ×tamp,
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
|
||||
},
|
||||
}
|
||||
|
||||
formatFlag := logging.FormatText
|
||||
@@ -482,6 +495,11 @@ func TestRestoreReconcile(t *testing.T) {
|
||||
backupStore.On("PutRestoreResults", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
|
||||
backupStore.On("PutRestoredResourceList", test.restore.Name, mock.Anything).Return(nil)
|
||||
backupStore.On("PutRestoreItemOperations", mock.Anything, mock.Anything).Return(nil)
|
||||
if test.emptyVolumeInfo == true {
|
||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
|
||||
} else {
|
||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*internalVolume.VolumeInfo{}, nil)
|
||||
}
|
||||
|
||||
volumeSnapshots := []*volume.Snapshot{
|
||||
{
|
||||
|
||||
@@ -21,15 +21,14 @@ import (
|
||||
io "io"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
itemoperation "github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
|
||||
persistence "github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
|
||||
volume "github.com/vmware-tanzu/velero/pkg/volume"
|
||||
|
||||
volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
)
|
||||
|
||||
// BackupStore is an autogenerated mock type for the BackupStore type
|
||||
@@ -314,6 +313,29 @@ func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.R
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRestoreItemOperations provides a mock function with given fields: name
|
||||
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*internalVolume.VolumeInfo, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 []*internalVolume.VolumeInfo
|
||||
if rf, ok := ret.Get(0).(func(string) []*internalVolume.VolumeInfo); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*internalVolume.VolumeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// IsValid provides a mock function with given fields:
|
||||
func (_m *BackupStore) IsValid() error {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
@@ -73,6 +74,7 @@ type BackupStore interface {
|
||||
GetCSIVolumeSnapshots(name string) ([]*snapshotv1api.VolumeSnapshot, error)
|
||||
GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1api.VolumeSnapshotContent, error)
|
||||
GetCSIVolumeSnapshotClasses(name string) ([]*snapshotv1api.VolumeSnapshotClass, error)
|
||||
GetBackupVolumeInfos(name string) ([]*internalVolume.VolumeInfo, error)
|
||||
|
||||
// BackupExists checks if the backup metadata file exists in object storage.
|
||||
BackupExists(bucket, backupName string) (bool, error)
|
||||
@@ -493,8 +495,8 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod
|
||||
return podVolumeBackups, nil
|
||||
}
|
||||
|
||||
func (s *objectBackupStore) GetBackupVolumeInfos(name string) (*volume.VolumeInfos, error) {
|
||||
var volumeInfos *volume.VolumeInfos
|
||||
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*internalVolume.VolumeInfo, error) {
|
||||
volumeInfos := make([]*internalVolume.VolumeInfo, 0)
|
||||
|
||||
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
|
||||
if err != nil {
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
@@ -1067,27 +1068,25 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) {
|
||||
func TestGetBackupVolumeInfos(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
volumeInfo *volume.VolumeInfos
|
||||
volumeInfo []*internalVolume.VolumeInfo
|
||||
volumeInfoStr string
|
||||
expectedErr string
|
||||
expectedResult []volume.VolumeInfo
|
||||
expectedResult []*internalVolume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "No VolumeInfos, expect no error.",
|
||||
},
|
||||
{
|
||||
name: "Valid VolumeInfo, should pass.",
|
||||
volumeInfo: &volume.VolumeInfos{
|
||||
VolumeInfos: []volume.VolumeInfo{
|
||||
{
|
||||
PVCName: "pvcName",
|
||||
PVName: "pvName",
|
||||
Skipped: true,
|
||||
SnapshotDataMoved: false,
|
||||
},
|
||||
volumeInfo: []*internalVolume.VolumeInfo{
|
||||
{
|
||||
PVCName: "pvcName",
|
||||
PVName: "pvName",
|
||||
Skipped: true,
|
||||
SnapshotDataMoved: false,
|
||||
},
|
||||
},
|
||||
expectedResult: []volume.VolumeInfo{
|
||||
expectedResult: []*internalVolume.VolumeInfo{
|
||||
{
|
||||
PVCName: "pvcName",
|
||||
PVName: "pvName",
|
||||
@@ -1098,8 +1097,8 @@ func TestGetBackupVolumeInfos(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Invalid VolumeInfo string, should also pass.",
|
||||
volumeInfoStr: `{"volumeInfos": [{"abc": "123", "def": "456", "pvcName": "pvcName"}]}`,
|
||||
expectedResult: []volume.VolumeInfo{
|
||||
volumeInfoStr: `[{"abc": "123", "def": "456", "pvcName": "pvcName"}]`,
|
||||
expectedResult: []*internalVolume.VolumeInfo{
|
||||
{
|
||||
PVCName: "pvcName",
|
||||
},
|
||||
@@ -1141,7 +1140,7 @@ func TestGetBackupVolumeInfos(t *testing.T) {
|
||||
}
|
||||
|
||||
if len(tc.expectedResult) > 0 {
|
||||
require.Equal(t, tc.expectedResult, result.VolumeInfos)
|
||||
require.Equal(t, tc.expectedResult, result)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
@@ -132,7 +133,7 @@ func getSnapshotInfo(pvName string, backup *api.Backup, volumeSnapshots []*volum
|
||||
}
|
||||
|
||||
// add credential to config
|
||||
err = volume.UpdateVolumeSnapshotLocationWithCredentialConfig(snapshotLocation, credentialStore)
|
||||
err = internalVolume.UpdateVolumeSnapshotLocationWithCredentialConfig(snapshotLocation, credentialStore)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/resourcemodifiers"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
@@ -62,6 +63,7 @@ type Request struct {
|
||||
ResourceModifiers *resourcemodifiers.ResourceModifiers
|
||||
DisableInformerCache bool
|
||||
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
|
||||
VolumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}
|
||||
|
||||
type restoredItemStatus struct {
|
||||
|
||||
+201
-159
@@ -53,6 +53,7 @@ import (
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/hook"
|
||||
"github.com/vmware-tanzu/velero/internal/resourcemodifiers"
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/archive"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -326,6 +327,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
||||
disableInformerCache: req.DisableInformerCache,
|
||||
featureVerifier: kr.featureVerifier,
|
||||
hookTracker: hook.NewHookTracker(),
|
||||
volumeInfoMap: req.VolumeInfoMap,
|
||||
}
|
||||
|
||||
return restoreCtx.execute()
|
||||
@@ -379,6 +381,7 @@ type restoreContext struct {
|
||||
disableInformerCache bool
|
||||
featureVerifier features.Verifier
|
||||
hookTracker *hook.HookTracker
|
||||
volumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}
|
||||
|
||||
type resourceClientKey struct {
|
||||
@@ -1122,15 +1125,17 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
itemExists := false
|
||||
resourceID := getResourceID(groupResource, namespace, obj.GetName())
|
||||
|
||||
restoreLogger := ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
})
|
||||
|
||||
// Check if group/resource should be restored. We need to do this here since
|
||||
// this method may be getting called for an additional item which is a group/resource
|
||||
// that's excluded.
|
||||
if !ctx.resourceIncludesExcludes.ShouldInclude(groupResource.String()) {
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Info("Not restoring item because resource is excluded")
|
||||
restoreLogger.Info("Not restoring item because resource is excluded")
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
|
||||
@@ -1142,11 +1147,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
// to check the *original* namespace, not the remapped one if it's been remapped.
|
||||
if namespace != "" {
|
||||
if !ctx.namespaceIncludesExcludes.ShouldInclude(obj.GetNamespace()) && !ctx.resourceMustHave.Has(groupResource.String()) {
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Info("Not restoring item because namespace is excluded")
|
||||
restoreLogger.Info("Not restoring item because namespace is excluded")
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
|
||||
@@ -1170,11 +1171,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
}
|
||||
} else {
|
||||
if boolptr.IsSetToFalse(ctx.restore.Spec.IncludeClusterResources) {
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Info("Not restoring item because it's cluster-scoped")
|
||||
restoreLogger.Info("Not restoring item because it's cluster-scoped")
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
}
|
||||
@@ -1238,168 +1235,111 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
switch {
|
||||
case hasSnapshot(name, ctx.volumeSnapshots):
|
||||
oldName := obj.GetName()
|
||||
shouldRenamePV, err := shouldRenamePV(ctx, obj, resourceClient)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
if volumeInfo, ok := ctx.volumeInfoMap[obj.GetName()]; ok {
|
||||
ctx.log.Infof("Find VolumeInfo for PV %s.", obj.GetName())
|
||||
|
||||
// Check to see if the claimRef.namespace field needs to be remapped,
|
||||
// and do so if necessary.
|
||||
_, err = remapClaimRefNS(ctx, obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
|
||||
var shouldRestoreSnapshot bool
|
||||
if !shouldRenamePV {
|
||||
// Check if the PV exists in the cluster before attempting to create
|
||||
// a volume from the snapshot, in order to avoid orphaned volumes (GH #609)
|
||||
shouldRestoreSnapshot, err = ctx.shouldRestore(name, resourceClient)
|
||||
switch volumeInfo.BackupMethod {
|
||||
case internalVolume.NativeSnapshot:
|
||||
obj, err = ctx.handlePVHasNativeSnapshot(obj, resourceClient)
|
||||
if err != nil {
|
||||
errs.Add(namespace, errors.Wrapf(err, "error waiting on in-cluster persistentvolume %s", name))
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
} else {
|
||||
// If we're renaming the PV, we're going to give it a new random name,
|
||||
// so we can assume it doesn't already exist in the cluster and therefore
|
||||
// we should proceed with restoring from snapshot.
|
||||
shouldRestoreSnapshot = true
|
||||
}
|
||||
|
||||
if shouldRestoreSnapshot {
|
||||
// Reset the PV's binding status so that Kubernetes can properly
|
||||
// associate it with the restored PVC.
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
name = obj.GetName()
|
||||
|
||||
// Even if we're renaming the PV, obj still has the old name here, because the pvRestorer
|
||||
// uses the original name to look up metadata about the snapshot.
|
||||
ctx.log.Infof("Restoring persistent volume from snapshot.")
|
||||
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err))
|
||||
case internalVolume.PodVolumeBackup:
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it has a pod volume backup to be restored.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
case internalVolume.CSISnapshot:
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it has a CSI VolumeSnapshot or a related snapshot DataUpload.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
|
||||
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
|
||||
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
|
||||
}
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
// When the PV data is skipped from backup, it's VolumeInfo BackupMethod
|
||||
// is not set, and it will fall into the default case.
|
||||
default:
|
||||
if hasDeleteReclaimPolicy(obj.Object) {
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it doesn't have a snapshot and its reclaim policy is Delete.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
obj = updatedObj
|
||||
|
||||
// VolumeSnapshotter has modified the PV name, we should rename the PV.
|
||||
if oldName != obj.GetName() {
|
||||
shouldRenamePV = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldRenamePV {
|
||||
var pvName string
|
||||
if oldName == obj.GetName() {
|
||||
// pvRestorer hasn't modified the PV name, we need to rename the PV.
|
||||
pvName, err = ctx.pvRenamer(oldName)
|
||||
} else {
|
||||
obj, err = ctx.handleSkippedPVHasRetainPolicy(obj, resourceID, restoreLogger)
|
||||
if err != nil {
|
||||
errs.Add(namespace, errors.Wrapf(err, "error renaming PV"))
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
} else {
|
||||
// VolumeSnapshotter could have modified the PV name through
|
||||
// function `SetVolumeID`,
|
||||
pvName = obj.GetName()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: VolumeInfo is adopted and old logic is deprecated in v1.13.
|
||||
// Remove the old logic in v1.15.
|
||||
ctx.log.Infof("Cannot find VolumeInfo for PV %s.", obj.GetName())
|
||||
|
||||
switch {
|
||||
case hasSnapshot(name, ctx.volumeSnapshots):
|
||||
obj, err = ctx.handlePVHasNativeSnapshot(obj, resourceClient)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
|
||||
ctx.renamedPVs[oldName] = pvName
|
||||
obj.SetName(pvName)
|
||||
name = pvName
|
||||
name = obj.GetName()
|
||||
|
||||
// Add the original PV name as an annotation.
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
case hasPodVolumeBackup(obj, ctx):
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it has a pod volume backup to be restored.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
case hasCSIVolumeSnapshot(ctx, obj):
|
||||
fallthrough
|
||||
case hasSnapshotDataUpload(ctx, obj):
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it has a CSI VolumeSnapshot or a related snapshot DataUpload.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
|
||||
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
|
||||
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
|
||||
}
|
||||
annotations["velero.io/original-pv-name"] = oldName
|
||||
obj.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
case hasPodVolumeBackup(obj, ctx):
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Infof("Dynamically re-provisioning persistent volume because it has a pod volume backup to be restored.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
case hasCSIVolumeSnapshot(ctx, obj):
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Infof("Dynamically re-provisioning persistent volume because it has a related CSI VolumeSnapshot.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
|
||||
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
|
||||
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
|
||||
}
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
case hasSnapshotDataUpload(ctx, obj):
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Infof("Dynamically re-provisioning persistent volume because it has a related snapshot DataUpload.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
|
||||
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
|
||||
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
|
||||
}
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
case hasDeleteReclaimPolicy(obj.Object):
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Infof("Dynamically re-provisioning persistent volume because it doesn't have a snapshot and its reclaim policy is Delete.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
default:
|
||||
ctx.log.WithFields(logrus.Fields{
|
||||
"namespace": obj.GetNamespace(),
|
||||
"name": obj.GetName(),
|
||||
"groupResource": groupResource.String(),
|
||||
}).Infof("Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete.")
|
||||
|
||||
// Check to see if the claimRef.namespace field needs to be remapped, and do so if necessary.
|
||||
_, err = remapClaimRefNS(ctx, obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
// We call the pvRestorer here to clear out the PV's claimRef.UID,
|
||||
// so it can be re-claimed when its PVC is restored and gets a new UID.
|
||||
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
|
||||
if err != nil {
|
||||
errs.Add(namespace, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err))
|
||||
|
||||
case hasDeleteReclaimPolicy(obj.Object):
|
||||
restoreLogger.Infof("Dynamically re-provisioning persistent volume because it doesn't have a snapshot and its reclaim policy is Delete.")
|
||||
ctx.pvsToProvision.Insert(name)
|
||||
|
||||
// Return early because we don't want to restore the PV itself, we
|
||||
// want to dynamically re-provision it.
|
||||
return warnings, errs, itemExists
|
||||
|
||||
default:
|
||||
obj, err = ctx.handleSkippedPVHasRetainPolicy(obj, resourceID, restoreLogger)
|
||||
if err != nil {
|
||||
errs.Add(namespace, err)
|
||||
return warnings, errs, itemExists
|
||||
}
|
||||
}
|
||||
obj = updatedObj
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2491,3 +2431,105 @@ func (ctx *restoreContext) processUpdateResourcePolicy(fromCluster, fromClusterW
|
||||
}
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
func (ctx *restoreContext) handlePVHasNativeSnapshot(obj *unstructured.Unstructured, resourceClient client.Dynamic) (*unstructured.Unstructured, error) {
|
||||
retObj := obj.DeepCopy()
|
||||
oldName := obj.GetName()
|
||||
shouldRenamePV, err := shouldRenamePV(ctx, retObj, resourceClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check to see if the claimRef.namespace field needs to be remapped,
|
||||
// and do so if necessary.
|
||||
_, err = remapClaimRefNS(ctx, retObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var shouldRestoreSnapshot bool
|
||||
if !shouldRenamePV {
|
||||
// Check if the PV exists in the cluster before attempting to create
|
||||
// a volume from the snapshot, in order to avoid orphaned volumes (GH #609)
|
||||
shouldRestoreSnapshot, err = ctx.shouldRestore(oldName, resourceClient)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error waiting on in-cluster persistentvolume %s", oldName)
|
||||
}
|
||||
} else {
|
||||
// If we're renaming the PV, we're going to give it a new random name,
|
||||
// so we can assume it doesn't already exist in the cluster and therefore
|
||||
// we should proceed with restoring from snapshot.
|
||||
shouldRestoreSnapshot = true
|
||||
}
|
||||
|
||||
if shouldRestoreSnapshot {
|
||||
// Reset the PV's binding status so that Kubernetes can properly
|
||||
// associate it with the restored PVC.
|
||||
retObj = resetVolumeBindingInfo(retObj)
|
||||
|
||||
// Even if we're renaming the PV, obj still has the old name here, because the pvRestorer
|
||||
// uses the original name to look up metadata about the snapshot.
|
||||
ctx.log.Infof("Restoring persistent volume from snapshot.")
|
||||
retObj, err = ctx.pvRestorer.executePVAction(retObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing PVAction for %s: %v", getResourceID(kuberesource.PersistentVolumes, "", oldName), err)
|
||||
}
|
||||
|
||||
// VolumeSnapshotter has modified the PV name, we should rename the PV.
|
||||
if oldName != retObj.GetName() {
|
||||
shouldRenamePV = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldRenamePV {
|
||||
var pvName string
|
||||
if oldName == retObj.GetName() {
|
||||
// pvRestorer hasn't modified the PV name, we need to rename the PV.
|
||||
pvName, err = ctx.pvRenamer(oldName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error renaming PV")
|
||||
}
|
||||
} else {
|
||||
// VolumeSnapshotter could have modified the PV name through
|
||||
// function `SetVolumeID`,
|
||||
pvName = retObj.GetName()
|
||||
}
|
||||
|
||||
ctx.renamedPVs[oldName] = pvName
|
||||
retObj.SetName(pvName)
|
||||
|
||||
// Add the original PV name as an annotation.
|
||||
annotations := retObj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations["velero.io/original-pv-name"] = oldName
|
||||
retObj.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
return retObj, nil
|
||||
}
|
||||
|
||||
func (ctx *restoreContext) handleSkippedPVHasRetainPolicy(
|
||||
obj *unstructured.Unstructured,
|
||||
resourceID string,
|
||||
logger logrus.FieldLogger,
|
||||
) (*unstructured.Unstructured, error) {
|
||||
logger.Infof("Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete.")
|
||||
|
||||
// Check to see if the claimRef.namespace field needs to be remapped, and do so if necessary.
|
||||
if _, err := remapClaimRefNS(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj = resetVolumeBindingInfo(obj)
|
||||
|
||||
// We call the pvRestorer here to clear out the PV's claimRef.UID,
|
||||
// so it can be re-claimed when its PVC is restored and gets a new UID.
|
||||
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err)
|
||||
}
|
||||
|
||||
return updatedObj, nil
|
||||
}
|
||||
|
||||
@@ -42,11 +42,13 @@ import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
kubetesting "k8s.io/client-go/testing"
|
||||
|
||||
internalVolume "github.com/vmware-tanzu/velero/internal/volume"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/archive"
|
||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
"github.com/vmware-tanzu/velero/pkg/features"
|
||||
verifiermocks "github.com/vmware-tanzu/velero/pkg/features/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
@@ -61,6 +63,199 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
restore *velerov1api.Restore
|
||||
backup *velerov1api.Backup
|
||||
apiResources []*test.APIResource
|
||||
tarball io.Reader
|
||||
want map[*test.APIResource][]string
|
||||
volumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Restore PV with native snapshot",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: internalVolume.NativeSnapshot,
|
||||
PVName: "pv-1",
|
||||
NativeSnapshotInfo: internalVolume.NativeSnapshotInfo{
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {"/pv-1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Restore PV with PVB",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: internalVolume.PodVolumeBackup,
|
||||
PVName: "pv-1",
|
||||
PVBInfo: internalVolume.PodVolumeBackupInfo{
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
Size: 100,
|
||||
NodeName: "testNode",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Restore PV with CSI VolumeSnapshot",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: internalVolume.CSISnapshot,
|
||||
SnapshotDataMoved: false,
|
||||
PVName: "pv-1",
|
||||
CSISnapshotInfo: internalVolume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Restore PV with DataUpload",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: internalVolume.CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
PVName: "pv-1",
|
||||
CSISnapshotInfo: internalVolume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
SnapshotDataMovementInfo: internalVolume.SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Restore PV with ClaimPolicy as Delete",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimDelete).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
PVName: "pv-1",
|
||||
Skipped: true,
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Restore PV with ClaimPolicy as Retain",
|
||||
restore: defaultRestore().Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).Result(),
|
||||
).Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
PVName: "pv-1",
|
||||
Skipped: true,
|
||||
},
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.PVs(): {"/pv-1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
features.Enable("EnableCSI")
|
||||
finder := new(verifiermocks.PluginFinder)
|
||||
finder.On("Find", mock.Anything, mock.Anything).Return(true)
|
||||
verifier := features.NewVerifier(finder)
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
|
||||
for _, r := range tc.apiResources {
|
||||
h.DiscoveryClient.WithAPIResource(r)
|
||||
}
|
||||
require.NoError(t, h.restorer.discoveryHelper.Refresh())
|
||||
h.restorer.featureVerifier = verifier
|
||||
|
||||
data := &Request{
|
||||
Log: h.log,
|
||||
Restore: tc.restore,
|
||||
Backup: tc.backup,
|
||||
PodVolumeBackups: nil,
|
||||
VolumeSnapshots: nil,
|
||||
BackupReader: tc.tarball,
|
||||
VolumeInfoMap: tc.volumeInfoMap,
|
||||
}
|
||||
warnings, errs := h.restorer.Restore(
|
||||
data,
|
||||
nil, // restoreItemActions
|
||||
nil, // volume snapshotter getter
|
||||
)
|
||||
|
||||
assertEmptyResults(t, warnings, errs)
|
||||
assertAPIContents(t, h, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreResourceFiltering runs restores with different combinations
|
||||
// of resource filters (included/excluded resources, included/excluded
|
||||
// namespaces, label selectors, "include cluster resources" flag), and
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
)
|
||||
|
||||
@@ -35,7 +34,7 @@ func TestInternalLW(t *testing.T) {
|
||||
client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch)
|
||||
lw := InternalLW{
|
||||
Client: client,
|
||||
Namespace: cmdtest.VeleroNameSpace,
|
||||
Namespace: "velero",
|
||||
ObjectList: new(velerov1api.BackupList),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright the Velero contributors.
|
||||
|
||||
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 volume
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
// UpdateVolumeSnapshotLocationWithCredentialConfig adds the credentials file path to the config
|
||||
// if the VSL specifies a credential
|
||||
func UpdateVolumeSnapshotLocationWithCredentialConfig(location *velerov1api.VolumeSnapshotLocation, credentialStore credentials.FileStore) error {
|
||||
if location.Spec.Config == nil {
|
||||
location.Spec.Config = make(map[string]string)
|
||||
}
|
||||
// If the VSL specifies a credential, fetch its path on disk and pass to
|
||||
// plugin via the config.
|
||||
if location.Spec.Credential != nil && credentialStore != nil {
|
||||
credsFile, err := credentialStore.Path(location.Spec.Credential)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get credentials")
|
||||
}
|
||||
|
||||
location.Spec.Config["credentialsFile"] = credsFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 the Velero contributors.
|
||||
|
||||
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 volume
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type VolumeBackupMethod string
|
||||
|
||||
const (
|
||||
NativeSnapshot VolumeBackupMethod = "NativeSnapshot"
|
||||
PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup"
|
||||
CSISnapshot VolumeBackupMethod = "CSISnapshot"
|
||||
)
|
||||
|
||||
type VolumeInfoVersion struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type VolumeInfos struct {
|
||||
VolumeInfos []VolumeInfo `json:"volumeInfos"`
|
||||
}
|
||||
|
||||
type VolumeInfo struct {
|
||||
// The PVC's name.
|
||||
PVCName string `json:"pvcName,omitempty"`
|
||||
|
||||
// The PVC's namespace
|
||||
PVCNamespace string `json:"pvcNamespace,omitempty"`
|
||||
|
||||
// The PV name.
|
||||
PVName string `json:"pvName,omitempty"`
|
||||
|
||||
// The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`.
|
||||
BackupMethod VolumeBackupMethod `json:"backupMethod,omitempty"`
|
||||
|
||||
// Whether the volume's snapshot data is moved to specified storage.
|
||||
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
||||
|
||||
// Whether the local snapshot is preserved after snapshot is moved.
|
||||
// The local snapshot may be a result of CSI snapshot backup(no data movement)
|
||||
// or a CSI snapshot data movement plus preserve local snapshot.
|
||||
PreserveLocalSnapshot bool `json:"preserveLocalSnapshot"`
|
||||
|
||||
// Whether the Volume is skipped in this backup.
|
||||
Skipped bool `json:"skipped"`
|
||||
|
||||
// The reason for the volume is skipped in the backup.
|
||||
SkippedReason string `json:"skippedReason,omitempty"`
|
||||
|
||||
// Snapshot starts timestamp.
|
||||
StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"`
|
||||
|
||||
// The Async Operation's ID.
|
||||
OperationID string `json:"operationID,omitempty"`
|
||||
|
||||
CSISnapshotInfo CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
|
||||
SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
||||
NativeSnapshotInfo NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
||||
PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
|
||||
PVInfo PVInfo `json:"pvInfo,omitempty"`
|
||||
}
|
||||
|
||||
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||
type CSISnapshotInfo struct {
|
||||
// It's the storage provider's snapshot ID for CSI.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
|
||||
// The snapshot corresponding volume size.
|
||||
Size int64 `json:"size"`
|
||||
|
||||
// The name of the CSI driver.
|
||||
Driver string `json:"driver"`
|
||||
|
||||
// The name of the VolumeSnapshotContent.
|
||||
VSCName string `json:"vscName"`
|
||||
}
|
||||
|
||||
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
||||
type SnapshotDataMovementInfo struct {
|
||||
// The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
|
||||
DataMover string `json:"dataMover"`
|
||||
|
||||
// The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`.
|
||||
UploaderType string `json:"uploaderType"`
|
||||
|
||||
// The name or ID of the snapshot associated object(SAO).
|
||||
// SAO is used to support local snapshots for the snapshot data mover,
|
||||
// e.g. it could be a VolumeSnapshot for CSI snapshot data movement.
|
||||
RetainedSnapshot string `json:"retainedSnapshot"`
|
||||
|
||||
// It's the filesystem repository's snapshot ID.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
}
|
||||
|
||||
// NativeSnapshotInfo is used for displaying the Velero native snapshot status.
|
||||
// A Velero Native Snapshot is a cloud storage snapshot taken by the Velero native
|
||||
// plugins, e.g. velero-plugin-for-aws, velero-plugin-for-gcp, and
|
||||
// velero-plugin-for-microsoft-azure.
|
||||
type NativeSnapshotInfo struct {
|
||||
// It's the storage provider's snapshot ID for the Velero-native snapshot.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
|
||||
// The cloud provider snapshot volume type.
|
||||
VolumeType string `json:"volumeType"`
|
||||
|
||||
// The cloud provider snapshot volume's availability zones.
|
||||
VolumeAZ string `json:"volumeAZ"`
|
||||
|
||||
// The cloud provider snapshot volume's IOPS.
|
||||
IOPS string `json:"iops"`
|
||||
}
|
||||
|
||||
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
||||
type PodVolumeBackupInfo struct {
|
||||
// It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
||||
SnapshotHandle string `json:"snapshotHandle"`
|
||||
|
||||
// The snapshot corresponding volume size.
|
||||
Size int64 `json:"size"`
|
||||
|
||||
// The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
|
||||
UploaderType string `json:"uploaderType"`
|
||||
|
||||
// The PVC's corresponding volume name used by Pod
|
||||
// https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48
|
||||
VolumeName string `json:"volumeName"`
|
||||
|
||||
// The Pod name mounting this PVC.
|
||||
PodName string `json:"podName"`
|
||||
|
||||
// The Pod namespace
|
||||
PodNamespace string `json:"podNamespace"`
|
||||
|
||||
// The PVB-taken k8s node's name.
|
||||
NodeName string `json:"nodeName"`
|
||||
}
|
||||
|
||||
// PVInfo is used to store some PV information modified after creation.
|
||||
// Those information are lost after PV recreation.
|
||||
type PVInfo struct {
|
||||
// ReclaimPolicy of PV. It could be different from the referenced StorageClass.
|
||||
ReclaimPolicy string `json:"reclaimPolicy"`
|
||||
|
||||
// The PV's labels should be kept after recreation.
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
Reference in New Issue
Block a user