Move VolumesInformation to an independant package.
Signed-off-by: Xun Jiang <jxun@vmware.com>pull/7138/head
parent
ca97248f2a
commit
c77bec73bb
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
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 (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
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/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
type VolumeBackupMethod string
|
||||
|
||||
const (
|
||||
NativeSnapshot VolumeBackupMethod = "NativeSnapshot"
|
||||
PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup"
|
||||
CSISnapshot VolumeBackupMethod = "CSISnapshot"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// VolumesInformation contains the information needs by generating
|
||||
// the backup VolumeInfo array.
|
||||
type VolumesInformation struct {
|
||||
// A map contains the backup-included PV detail content. The key is PV name.
|
||||
pvMap map[string]pvcPvInfo
|
||||
volumeInfos []*VolumeInfo
|
||||
|
||||
logger logrus.FieldLogger
|
||||
crClient kbclient.Client
|
||||
volumeSnapshots []snapshotv1api.VolumeSnapshot
|
||||
volumeSnapshotContents []snapshotv1api.VolumeSnapshotContent
|
||||
volumeSnapshotClasses []snapshotv1api.VolumeSnapshotClass
|
||||
SkippedPVs map[string]string
|
||||
NativeSnapshots []*volume.Snapshot
|
||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||
BackupOperations []*itemoperation.BackupOperation
|
||||
BackupName string
|
||||
}
|
||||
|
||||
type pvcPvInfo struct {
|
||||
PVCName string
|
||||
PVCNamespace string
|
||||
PV corev1api.PersistentVolume
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) Init() {
|
||||
v.pvMap = make(map[string]pvcPvInfo)
|
||||
v.volumeInfos = make([]*VolumeInfo, 0)
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) InsertPVMap(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
|
||||
if v.pvMap == nil {
|
||||
v.Init()
|
||||
}
|
||||
|
||||
v.pvMap[pv.Name] = pvcPvInfo{
|
||||
PVCName: pvcName,
|
||||
PVCNamespace: pvcNamespace,
|
||||
PV: pv,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) Result(
|
||||
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
|
||||
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||
crClient kbclient.Client,
|
||||
logger logrus.FieldLogger,
|
||||
) []*VolumeInfo {
|
||||
v.logger = logger
|
||||
v.crClient = crClient
|
||||
v.volumeSnapshots = csiVolumeSnapshots
|
||||
v.volumeSnapshotContents = csiVolumeSnapshotContents
|
||||
v.volumeSnapshotClasses = csiVolumesnapshotClasses
|
||||
|
||||
v.generateVolumeInfoForSkippedPV()
|
||||
v.generateVolumeInfoForVeleroNativeSnapshot()
|
||||
v.generateVolumeInfoForCSIVolumeSnapshot()
|
||||
v.generateVolumeInfoFromPVB()
|
||||
v.generateVolumeInfoFromDataUpload()
|
||||
|
||||
return v.volumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||
func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for pvName, skippedReason := range v.SkippedPVs {
|
||||
if pvcPVInfo := v.retrievePvcPvInfo(pvName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvName,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: true,
|
||||
SkippedReason: skippedReason,
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("Cannot find info for PV %s", pvName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
||||
func (v *VolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, nativeSnapshot := range v.NativeSnapshots {
|
||||
var iops int64
|
||||
if nativeSnapshot.Spec.VolumeIOPS != nil {
|
||||
iops = *nativeSnapshot.Spec.VolumeIOPS
|
||||
}
|
||||
|
||||
if pvcPVInfo := v.retrievePvcPvInfo(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: NativeSnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
NativeSnapshotInfo: NativeSnapshotInfo{
|
||||
SnapshotHandle: nativeSnapshot.Status.ProviderSnapshotID,
|
||||
VolumeType: nativeSnapshot.Spec.VolumeType,
|
||||
VolumeAZ: nativeSnapshot.Spec.VolumeAZ,
|
||||
IOPS: strconv.FormatInt(iops, 10),
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("cannot find info for PV %s", nativeSnapshot.Spec.PersistentVolumeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
|
||||
func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, volumeSnapshot := range v.volumeSnapshots {
|
||||
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] != v.BackupName {
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Spec.VolumeSnapshotClassName == nil {
|
||||
v.logger.Warnf("Cannot find VolumeSnapshotClass for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Status == nil || volumeSnapshot.Status.BoundVolumeSnapshotContentName == nil {
|
||||
v.logger.Warnf("Cannot fine VolumeSnapshotContent for VolumeSnapshot %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if volumeSnapshot.Spec.Source.PersistentVolumeClaimName == nil {
|
||||
v.logger.Warnf("VolumeSnapshot %s/%s doesn't have a source PVC", volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for index := range v.volumeSnapshotClasses {
|
||||
if *volumeSnapshot.Spec.VolumeSnapshotClassName == v.volumeSnapshotClasses[index].Name {
|
||||
volumeSnapshotClass = &v.volumeSnapshotClasses[index]
|
||||
}
|
||||
}
|
||||
|
||||
for index := range v.volumeSnapshotContents {
|
||||
if *volumeSnapshot.Status.BoundVolumeSnapshotContentName == v.volumeSnapshotContents[index].Name {
|
||||
volumeSnapshotContent = &v.volumeSnapshotContents[index]
|
||||
}
|
||||
}
|
||||
|
||||
if volumeSnapshotClass == nil || volumeSnapshotContent == nil {
|
||||
v.logger.Warnf("fail to get VolumeSnapshotContent or VolumeSnapshotClass for VolumeSnapshot: %s/%s",
|
||||
volumeSnapshot.Namespace, volumeSnapshot.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var operation itemoperation.BackupOperation
|
||||
for _, op := range v.BackupOperations {
|
||||
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 := v.retrievePvcPvInfo("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: 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: CSISnapshotInfo{
|
||||
VSCName: *volumeSnapshot.Status.BoundVolumeSnapshotContentName,
|
||||
Size: size,
|
||||
Driver: volumeSnapshotClass.Driver,
|
||||
SnapshotHandle: snapshotHandle,
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("cannot find info for PVC %s/%s", volumeSnapshot.Namespace, volumeSnapshot.Spec.Source.PersistentVolumeClaimName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
|
||||
func (v *VolumesInformation) generateVolumeInfoFromPVB() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
|
||||
for _, pvb := range v.PodVolumeBackups {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: PodVolumeBackup,
|
||||
SnapshotDataMoved: false,
|
||||
Skipped: false,
|
||||
StartTimestamp: pvb.Status.StartTimestamp,
|
||||
PVBInfo: 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 := v.crClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: pvb.Spec.Pod.Namespace, Name: pvb.Spec.Pod.Name}, pod)
|
||||
if err != nil {
|
||||
v.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 := v.retrievePvcPvInfo("", pvcName, pod.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo.PVCName = pvcPVInfo.PVCName
|
||||
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||
volumeInfo.PVInfo = PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
}
|
||||
} else {
|
||||
v.logger.Warnf("Cannot find info for PVC %s/%s", pod.Namespace, pvcName)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
v.logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// generateVolumeInfoFromDataUpload generate VolumeInfo for DataUpload.
|
||||
func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
||||
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
||||
if err := v.crClient.List(context.TODO(), vsClassList); err != nil {
|
||||
v.logger.WithError(err).Errorf("cannot list VolumeSnapshotClass %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, operation := range v.BackupOperations {
|
||||
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() {
|
||||
v.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 := v.crClient.Get(
|
||||
context.TODO(),
|
||||
kbclient.ObjectKey{
|
||||
Namespace: duIdentifier.Namespace,
|
||||
Name: duIdentifier.Name},
|
||||
dataUpload,
|
||||
)
|
||||
if err != nil {
|
||||
v.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 := v.retrievePvcPvInfo("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
|
||||
volumeInfo := &VolumeInfo{
|
||||
BackupMethod: CSISnapshot,
|
||||
PVCName: pvcPVInfo.PVCName,
|
||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||
PVName: pvcPVInfo.PV.Name,
|
||||
SnapshotDataMoved: true,
|
||||
Skipped: false,
|
||||
OperationID: operation.Spec.OperationID,
|
||||
StartTimestamp: operation.Status.Created,
|
||||
CSISnapshotInfo: CSISnapshotInfo{
|
||||
Driver: driverUsedByVSClass,
|
||||
},
|
||||
SnapshotDataMovementInfo: SnapshotDataMovementInfo{
|
||||
DataMover: dataUpload.Spec.DataMover,
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||
Labels: pvcPVInfo.PV.Labels,
|
||||
},
|
||||
}
|
||||
|
||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||
} else {
|
||||
v.logger.Warnf("Cannot find info for PVC %s/%s", operation.Spec.ResourceIdentifier.Namespace, operation.Spec.ResourceIdentifier.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||
}
|
||||
|
||||
// retrievePvcPvInfo gets the PvcPvInfo from the PVMap.
|
||||
// support retrieve info by PV's name, or by PVC's name
|
||||
// and namespace.
|
||||
func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *pvcPvInfo {
|
||||
if pvName != "" {
|
||||
if info, ok := v.pvMap[pvName]; ok {
|
||||
return &info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if pvcNS == "" || pvcName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range v.pvMap {
|
||||
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
|
||||
return &info
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,856 @@
|
|||
/*
|
||||
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 (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"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/schema"
|
||||
|
||||
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/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
skippedPVName string
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Cannot find info for PV",
|
||||
skippedPVName: "testPV",
|
||||
pvMap: map[string]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: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal Skipped PV info",
|
||||
skippedPVName: "testPV",
|
||||
pvMap: map[string]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: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
Skipped: true,
|
||||
SkippedReason: "CSI: skipped for PodVolumeBackup",
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.skippedPVName != "" {
|
||||
volumesInfo.SkippedPVs = map[string]string{
|
||||
tc.skippedPVName: "CSI: skipped for PodVolumeBackup",
|
||||
}
|
||||
}
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoForSkippedPV()
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nativeSnapshot volume.Snapshot
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Native snapshot's IPOS pointer is nil",
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: nil,
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find info for the PV",
|
||||
nativeSnapshot: volume.Snapshot{
|
||||
Spec: volume.SnapshotSpec{
|
||||
PersistentVolumeName: "testPV",
|
||||
VolumeIOPS: int64Ptr(100),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Cannot find PV info in pvMap",
|
||||
pvMap: map[string]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "Normal native snapshot",
|
||||
pvMap: map[string]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: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: NativeSnapshot,
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
NativeSnapshotInfo: 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) {
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
volumesInfo.NativeSnapshots = append(volumesInfo.NativeSnapshots, &tc.nativeSnapshot)
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoForVeleroNativeSnapshot()
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.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]pvcPvInfo
|
||||
operation *itemoperation.BackupOperation
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "VS doesn't have VolumeSnapshotClass name",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
||||
},
|
||||
expectedVolumeInfos: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "VS doesn't have status",
|
||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testVS",
|
||||
Namespace: "velero",
|
||||
},
|
||||
Spec: snapshotv1api.VolumeSnapshotSpec{
|
||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*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: []*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: []*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: []*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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: CSISnapshot,
|
||||
OperationID: "testID",
|
||||
StartTimestamp: &now,
|
||||
PreserveLocalSnapshot: true,
|
||||
CSISnapshotInfo: CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
Size: 107374182400,
|
||||
VSCName: "testContent",
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: "Delete",
|
||||
Labels: map[string]string{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if tc.operation != nil {
|
||||
volumesInfo.BackupOperations = append(volumesInfo.BackupOperations, tc.operation)
|
||||
}
|
||||
|
||||
volumesInfo.volumeSnapshots = []snapshotv1api.VolumeSnapshot{tc.volumeSnapshot}
|
||||
volumesInfo.volumeSnapshotContents = []snapshotv1api.VolumeSnapshotContent{tc.volumeSnapshotContent}
|
||||
volumesInfo.volumeSnapshotClasses = []snapshotv1api.VolumeSnapshotClass{tc.volumeSnapshotClass}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoForCSIVolumeSnapshot()
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pvb *velerov1api.PodVolumeBackup
|
||||
pod *corev1api.Pod
|
||||
pvMap map[string]pvcPvInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "cannot find PVB's pod, should fail",
|
||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||
expectedVolumeInfos: []*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: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "",
|
||||
PVCNamespace: "",
|
||||
PVName: "",
|
||||
BackupMethod: PodVolumeBackup,
|
||||
PVBInfo: 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: []*VolumeInfo{},
|
||||
},
|
||||
{
|
||||
name: "PVB's volume has a PVC",
|
||||
pvMap: map[string]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: PodVolumeBackup,
|
||||
PVBInfo: PodVolumeBackupInfo{
|
||||
PodName: "testPod",
|
||||
PodNamespace: "velero",
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||
|
||||
volumesInfo.PodVolumeBackups = append(volumesInfo.PodVolumeBackups, tc.pvb)
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
if tc.pod != nil {
|
||||
require.NoError(t, volumesInfo.crClient.Create(context.TODO(), tc.pod))
|
||||
}
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoFromPVB()
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.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]pvcPvInfo
|
||||
expectedVolumeInfos []*VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Operation is not for PVC",
|
||||
operation: &itemoperation.BackupOperation{
|
||||
Spec: itemoperation.BackupOperationSpec{
|
||||
ResourceIdentifier: velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "",
|
||||
Resource: "configmaps",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*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: []*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: []*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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
OperationID: "testOperation",
|
||||
SnapshotDataMovementInfo: SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: 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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedVolumeInfos: []*VolumeInfo{
|
||||
{
|
||||
PVCName: "testPVC",
|
||||
PVCNamespace: "velero",
|
||||
PVName: "testPV",
|
||||
BackupMethod: CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
OperationID: "testOperation",
|
||||
StartTimestamp: &now,
|
||||
CSISnapshotInfo: CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
SnapshotDataMovementInfo: SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
UploaderType: "kopia",
|
||||
},
|
||||
PVInfo: PVInfo{
|
||||
ReclaimPolicy: string(corev1api.PersistentVolumeReclaimDelete),
|
||||
Labels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
volumesInfo := VolumesInformation{}
|
||||
volumesInfo.Init()
|
||||
|
||||
if tc.operation != nil {
|
||||
volumesInfo.BackupOperations = append(volumesInfo.BackupOperations, tc.operation)
|
||||
}
|
||||
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
volumesInfo.pvMap[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||
if tc.dataUpload != nil {
|
||||
volumesInfo.crClient.Create(context.TODO(), tc.dataUpload)
|
||||
}
|
||||
|
||||
if tc.volumeSnapshotClass != nil {
|
||||
volumesInfo.crClient.Create(context.TODO(), tc.volumeSnapshotClass)
|
||||
}
|
||||
|
||||
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumesInfo.generateVolumeInfoFromDataUpload()
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumesInfo.volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringPtr(str string) *string {
|
||||
return &str
|
||||
}
|
||||
|
||||
func int64Ptr(val int) *int64 {
|
||||
i := int64(val)
|
||||
return &i
|
||||
}
|
|
@ -72,7 +72,6 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
|
|||
Backup: defaultBackup().Result(),
|
||||
SkippedPVTracker: NewSkipPVTracker(),
|
||||
}
|
||||
req.VolumesInformation.InitPVMap()
|
||||
|
||||
backupFile := bytes.NewBuffer([]byte{})
|
||||
|
||||
|
|
|
@ -706,11 +706,8 @@ func (ib *itemBackupper) addVolumeInfo(obj runtime.Unstructured, log logrus.Fiel
|
|||
pvcNamespace = pv.Spec.ClaimRef.Namespace
|
||||
}
|
||||
|
||||
ib.backupRequest.VolumesInformation.InsertPVMap(pv.Name, PvcPvInfo{
|
||||
PVCName: pvcName,
|
||||
PVCNamespace: pvcNamespace,
|
||||
PV: *pv,
|
||||
})
|
||||
ib.backupRequest.VolumesInformation.InsertPVMap(*pv, pvcName, pvcNamespace)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -241,31 +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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PV has no ClaimRef",
|
||||
pv: builder.ForPersistentVolume("testPV").Result(),
|
||||
expectedVolumeInfo: map[string]PvcPvInfo{
|
||||
"testPV": {
|
||||
PVCName: "",
|
||||
PVCNamespace: "",
|
||||
PV: *builder.ForPersistentVolume("testPV").Result(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -273,7 +258,7 @@ func TestAddVolumeInfo(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
ib := itemBackupper{}
|
||||
ib.backupRequest = new(Request)
|
||||
ib.backupRequest.VolumesInformation.InitPVMap()
|
||||
ib.backupRequest.VolumesInformation.Init()
|
||||
|
||||
pvObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pv)
|
||||
require.NoError(t, err)
|
||||
|
@ -281,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.VolumesInformation.GetPVMap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,25 +17,15 @@ limitations under the License.
|
|||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"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"
|
||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/kuberesource"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
@ -63,381 +53,11 @@ type Request struct {
|
|||
itemOperationsList *[]*itemoperation.BackupOperation
|
||||
ResPolicies *resourcepolicies.Policies
|
||||
SkippedPVTracker *skipPVTracker
|
||||
VolumesInformation VolumesInformation
|
||||
VolumesInformation internalVolume.VolumesInformation
|
||||
}
|
||||
|
||||
// VolumesInformation contains the information needs by generating
|
||||
// the backup VolumeInfo array.
|
||||
type VolumesInformation struct {
|
||||
// A map contains the backup-included PV detail content. The key is PV name.
|
||||
pvMap map[string]PvcPvInfo
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) InitPVMap() {
|
||||
v.pvMap = make(map[string]PvcPvInfo)
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) InsertPVMap(pvName string, pv PvcPvInfo) {
|
||||
if v.pvMap == nil {
|
||||
v.InitPVMap()
|
||||
}
|
||||
v.pvMap[pvName] = pv
|
||||
}
|
||||
|
||||
func (v *VolumesInformation) GenerateVolumeInfo(backup *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(*v, backup, logger)
|
||||
volumeInfos = append(volumeInfos, skippedVolumeInfos...)
|
||||
|
||||
nativeSnapshotVolumeInfos := generateVolumeInfoForVeleroNativeSnapshot(*v, backup, logger)
|
||||
volumeInfos = append(volumeInfos, nativeSnapshotVolumeInfos...)
|
||||
|
||||
csiVolumeInfos := generateVolumeInfoForCSIVolumeSnapshot(*v, backup, csiVolumeSnapshots, csiVolumeSnapshotContents, csiVolumesnapshotClasses, logger)
|
||||
volumeInfos = append(volumeInfos, csiVolumeInfos...)
|
||||
|
||||
pvbVolumeInfos := generateVolumeInfoFromPVB(*v, backup, crClient, logger)
|
||||
volumeInfos = append(volumeInfos, pvbVolumeInfos...)
|
||||
|
||||
dataUploadVolumeInfos := generateVolumeInfoFromDataUpload(*v, backup, crClient, logger)
|
||||
volumeInfos = append(volumeInfos, dataUploadVolumeInfos...)
|
||||
|
||||
return volumeInfos
|
||||
}
|
||||
|
||||
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||
func generateVolumeInfoForSkippedPV(info VolumesInformation, backup *Request, logger logrus.FieldLogger) []volume.VolumeInfo {
|
||||
tmpVolumeInfos := make([]volume.VolumeInfo, 0)
|
||||
|
||||
for _, skippedPV := range backup.SkippedPVTracker.Summary() {
|
||||
if pvcPVInfo := info.retrievePvcPvInfo(skippedPV.Name, "", ""); pvcPVInfo != nil {
|
||||
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(info VolumesInformation, backup *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 := info.retrievePvcPvInfo(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
||||
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(info VolumesInformation, backup *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 := info.retrievePvcPvInfo("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
||||
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(info VolumesInformation, backup *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 := info.retrievePvcPvInfo("", pvcName, pod.Namespace); pvcPVInfo != nil {
|
||||
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(info VolumesInformation, backup *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 := info.retrievePvcPvInfo("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// retrievePvcPvInfo gets the PvcPvInfo from the PVMap.
|
||||
// support retrieve info by PV's name, or by PVC's name
|
||||
// and namespace.
|
||||
func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *PvcPvInfo {
|
||||
if pvName != "" {
|
||||
if info, ok := v.pvMap[pvName]; ok {
|
||||
return &info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if pvcNS == "" || pvcName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range v.pvMap {
|
||||
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
|
||||
return &info
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type PvcPvInfo struct {
|
||||
PVCName string
|
||||
PVCNamespace string
|
||||
PV corev1api.PersistentVolume
|
||||
}
|
||||
|
||||
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
||||
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
|
||||
|
@ -467,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.
|
||||
|
@ -17,26 +17,9 @@ limitations under the License.
|
|||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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/schema"
|
||||
|
||||
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/builder"
|
||||
"github.com/vmware-tanzu/velero/pkg/itemoperation"
|
||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||
velerotest "github.com/vmware-tanzu/velero/pkg/test"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
func TestRequest_BackupResourceList(t *testing.T) {
|
||||
|
@ -97,804 +80,3 @@ func TestRequest_BackupResourceListEntriesSorted(t *testing.T) {
|
|||
"v1/Pod": {"ns1/pod1", "ns2/pod2"},
|
||||
}, req.BackupResourceList())
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
skippedPVName string
|
||||
pvMap map[string]PvcPvInfo
|
||||
expectedVolumeInfos []volume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Cannot find info for PV",
|
||||
skippedPVName: "testPV",
|
||||
pvMap: map[string]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{},
|
||||
},
|
||||
{
|
||||
name: "Normal Skipped PV info",
|
||||
skippedPVName: "testPV",
|
||||
pvMap: map[string]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(Request)
|
||||
request.SkippedPVTracker = NewSkipPVTracker()
|
||||
request.VolumesInformation.InitPVMap()
|
||||
if tc.skippedPVName != "" {
|
||||
request.SkippedPVTracker.Track(tc.skippedPVName, "CSI", "skipped for PodVolumeBackup")
|
||||
}
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
request.VolumesInformation.InsertPVMap(k, v)
|
||||
}
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForSkippedPV(request.VolumesInformation, request, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nativeSnapshot volume.Snapshot
|
||||
pvMap map[string]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: "Cannot find PV info in pvMap",
|
||||
pvMap: map[string]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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{},
|
||||
},
|
||||
{
|
||||
name: "Normal native snapshot",
|
||||
pvMap: map[string]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(Request)
|
||||
request.VolumeSnapshots = append(request.VolumeSnapshots, &tc.nativeSnapshot)
|
||||
request.VolumesInformation.InitPVMap()
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
request.VolumesInformation.InsertPVMap(k, v)
|
||||
}
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForVeleroNativeSnapshot(request.VolumesInformation, 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]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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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(Request)
|
||||
request.Backup = new(velerov1api.Backup)
|
||||
request.VolumesInformation.InitPVMap()
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
request.VolumesInformation.InsertPVMap(k, v)
|
||||
}
|
||||
}
|
||||
operationList := request.GetItemOperationsList()
|
||||
if tc.operation != nil {
|
||||
*operationList = append(*operationList, tc.operation)
|
||||
}
|
||||
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||
|
||||
volumeInfos := generateVolumeInfoForCSIVolumeSnapshot(request.VolumesInformation, request, []snapshotv1api.VolumeSnapshot{tc.volumeSnapshot}, []snapshotv1api.VolumeSnapshotContent{tc.volumeSnapshotContent}, []snapshotv1api.VolumeSnapshotClass{tc.volumeSnapshotClass}, 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]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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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(Request)
|
||||
request.PodVolumeBackups = append(request.PodVolumeBackups, tc.pvb)
|
||||
request.VolumesInformation.InitPVMap()
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
request.VolumesInformation.InsertPVMap(k, v)
|
||||
}
|
||||
}
|
||||
if tc.pod != nil {
|
||||
require.NoError(t, crClient.Create(context.TODO(), tc.pod))
|
||||
}
|
||||
|
||||
volumeInfos := generateVolumeInfoFromPVB(request.VolumesInformation, 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]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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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]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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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(Request)
|
||||
request.VolumesInformation.InitPVMap()
|
||||
operationList := request.GetItemOperationsList()
|
||||
if tc.operation != nil {
|
||||
*operationList = append(*operationList, tc.operation)
|
||||
}
|
||||
if tc.pvMap != nil {
|
||||
for k, v := range tc.pvMap {
|
||||
request.VolumesInformation.InsertPVMap(k, v)
|
||||
}
|
||||
}
|
||||
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.VolumesInformation, request, crClient, logger)
|
||||
require.Equal(t, tc.expectedVolumeInfos, volumeInfos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringPtr(str string) *string {
|
||||
return &str
|
||||
}
|
||||
|
|
|
@ -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.NewRequest(http.MethodGet, created.Status.DownloadURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ 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"
|
||||
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/discovery"
|
||||
|
@ -315,7 +316,7 @@ func (b *backupReconciler) prepareBackupRequest(backup *velerov1api.Backup, logg
|
|||
Backup: backup.DeepCopy(), // don't modify items in the cache
|
||||
SkippedPVTracker: pkgbackup.NewSkipPVTracker(),
|
||||
}
|
||||
request.VolumesInformation.InitPVMap()
|
||||
request.VolumesInformation.Init()
|
||||
|
||||
// set backup major version - deprecated, use Status.FormatVersion
|
||||
request.Status.Version = pkgbackup.BackupVersion
|
||||
|
@ -586,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
|
||||
|
@ -842,8 +843,15 @@ func persistBackup(backup *pkgbackup.Request,
|
|||
persistErrs = append(persistErrs, errs...)
|
||||
}
|
||||
|
||||
volumeInfoJSON, errs := encode.ToJSONGzip(backup.VolumesInformation.GenerateVolumeInfo(backup, csiVolumeSnapshots,
|
||||
csiVolumeSnapshotContents, csiVolumeSnapshotClasses, crClient, logger), "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...)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -56,7 +57,6 @@ import (
|
|||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/logging"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/results"
|
||||
"github.com/vmware-tanzu/velero/pkg/volume"
|
||||
)
|
||||
|
||||
// nonRestorableResources is an exclusion list for the restoration process. Any resources
|
||||
|
@ -521,13 +521,13 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
|
|||
return errors.Wrap(err, "fail to fetch CSI VolumeSnapshots metadata")
|
||||
}
|
||||
|
||||
backupVolumeInfoMap := make(map[string]volume.VolumeInfo)
|
||||
backupVolumeInfoMap := make(map[string]internalVolume.VolumeInfo)
|
||||
volumeInfos, err := backupStore.GetBackupVolumeInfos(restore.Spec.BackupName)
|
||||
if err != nil || volumeInfos == nil {
|
||||
if err != nil || len(volumeInfos) == 0 {
|
||||
restoreLog.Infof("Backup %s doesn't have volumeinfos metadata file.", restore.Spec.BackupName)
|
||||
} else {
|
||||
for _, volumeInfo := range volumeInfos.VolumeInfos {
|
||||
backupVolumeInfoMap[volumeInfo.PVName] = volumeInfo
|
||||
for _, volumeInfo := range volumeInfos {
|
||||
backupVolumeInfoMap[volumeInfo.PVName] = *volumeInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -498,7 +498,7 @@ func TestRestoreReconcile(t *testing.T) {
|
|||
if test.emptyVolumeInfo == true {
|
||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
|
||||
} else {
|
||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(&volume.VolumeInfos{}, nil)
|
||||
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
|
||||
|
@ -315,15 +314,15 @@ func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.R
|
|||
}
|
||||
|
||||
// GetRestoreItemOperations provides a mock function with given fields: name
|
||||
func (_m *BackupStore) GetBackupVolumeInfos(name string) (*volume.VolumeInfos, error) {
|
||||
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*internalVolume.VolumeInfo, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 *volume.VolumeInfos
|
||||
if rf, ok := ret.Get(0).(func(string) *volume.VolumeInfos); ok {
|
||||
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).(*volume.VolumeInfos)
|
||||
r0 = ret.Get(0).([]*internalVolume.VolumeInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,7 +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) (*volume.VolumeInfos, error)
|
||||
GetBackupVolumeInfos(name string) ([]*internalVolume.VolumeInfo, error)
|
||||
|
||||
// BackupExists checks if the backup metadata file exists in object storage.
|
||||
BackupExists(bucket, backupName string) (bool, error)
|
||||
|
@ -494,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,7 +63,7 @@ type Request struct {
|
|||
ResourceModifiers *resourcemodifiers.ResourceModifiers
|
||||
DisableInformerCache bool
|
||||
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
|
||||
VolumeInfoMap map[string]volume.VolumeInfo
|
||||
VolumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}
|
||||
|
||||
type restoredItemStatus struct {
|
||||
|
|
|
@ -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"
|
||||
|
@ -389,7 +390,7 @@ type restoreContext struct {
|
|||
disableInformerCache bool
|
||||
featureVerifier features.Verifier
|
||||
hookTracker *hook.HookTracker
|
||||
volumeInfoMap map[string]volume.VolumeInfo
|
||||
volumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}
|
||||
|
||||
type resourceClientKey struct {
|
||||
|
@ -1248,11 +1249,11 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
|||
ctx.log.Infof("Find VolumeInfo for PV %s.", obj.GetName())
|
||||
|
||||
switch volumeInfo.BackupMethod {
|
||||
case volume.NativeSnapshot:
|
||||
case internalVolume.NativeSnapshot:
|
||||
pvCondition = pvHasNativeSnapshot
|
||||
case volume.PodVolumeBackup:
|
||||
case internalVolume.PodVolumeBackup:
|
||||
pvCondition = pvHasPodVolumeBackup
|
||||
case volume.CSISnapshot:
|
||||
case internalVolume.CSISnapshot:
|
||||
if volumeInfo.SnapshotDataMoved {
|
||||
pvCondition = pvHasDataUpload
|
||||
} else {
|
||||
|
|
|
@ -42,6 +42,7 @@ 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"
|
||||
|
@ -70,7 +71,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources []*test.APIResource
|
||||
tarball io.Reader
|
||||
want map[*test.APIResource][]string
|
||||
volumeInfoMap map[string]volume.VolumeInfo
|
||||
volumeInfoMap map[string]internalVolume.VolumeInfo
|
||||
}{
|
||||
{
|
||||
name: "Restore PV with native snapshot",
|
||||
|
@ -83,11 +84,11 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: volume.NativeSnapshot,
|
||||
BackupMethod: internalVolume.NativeSnapshot,
|
||||
PVName: "pv-1",
|
||||
NativeSnapshotInfo: volume.NativeSnapshotInfo{
|
||||
NativeSnapshotInfo: internalVolume.NativeSnapshotInfo{
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
},
|
||||
},
|
||||
|
@ -107,11 +108,11 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: volume.PodVolumeBackup,
|
||||
BackupMethod: internalVolume.PodVolumeBackup,
|
||||
PVName: "pv-1",
|
||||
PVBInfo: volume.PodVolumeBackupInfo{
|
||||
PVBInfo: internalVolume.PodVolumeBackupInfo{
|
||||
SnapshotHandle: "testSnapshotHandle",
|
||||
Size: 100,
|
||||
NodeName: "testNode",
|
||||
|
@ -133,12 +134,12 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
BackupMethod: internalVolume.CSISnapshot,
|
||||
SnapshotDataMoved: false,
|
||||
PVName: "pv-1",
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
CSISnapshotInfo: internalVolume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
},
|
||||
|
@ -158,15 +159,15 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
BackupMethod: volume.CSISnapshot,
|
||||
BackupMethod: internalVolume.CSISnapshot,
|
||||
SnapshotDataMoved: true,
|
||||
PVName: "pv-1",
|
||||
CSISnapshotInfo: volume.CSISnapshotInfo{
|
||||
CSISnapshotInfo: internalVolume.CSISnapshotInfo{
|
||||
Driver: "pd.csi.storage.gke.io",
|
||||
},
|
||||
SnapshotDataMovementInfo: volume.SnapshotDataMovementInfo{
|
||||
SnapshotDataMovementInfo: internalVolume.SnapshotDataMovementInfo{
|
||||
DataMover: "velero",
|
||||
},
|
||||
},
|
||||
|
@ -186,7 +187,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
PVName: "pv-1",
|
||||
Skipped: true,
|
||||
|
@ -207,7 +208,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
|||
apiResources: []*test.APIResource{
|
||||
test.PVs(),
|
||||
},
|
||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
||||
volumeInfoMap: map[string]internalVolume.VolumeInfo{
|
||||
"pv-1": {
|
||||
PVName: "pv-1",
|
||||
Skipped: true,
|
||||
|
|
|
@ -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,156 +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 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"`
|
||||
}
|
Loading…
Reference in New Issue