Track and persist restore volume info

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
pull/7630/head
Daniel Jiang 2024-04-08 01:32:37 +08:00
parent 500e5aeeca
commit 0a280e5786
33 changed files with 1116 additions and 387 deletions

View File

@ -0,0 +1 @@
Track and persist restore volume info

42
internal/volume/utils.go Normal file
View File

@ -0,0 +1,42 @@
/*
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 (
"regexp"
)
// it has to have the same value as "github.com/vmware-tanzu/velero/pkg/restore".ItemRestoreResultCreated
const itemRestoreResultCreated = "created"
// RestoredPVCFromRestoredResourceList returns a set of PVCs that were restored from the given restoredResourceList.
func RestoredPVCFromRestoredResourceList(restoredResourceList map[string][]string) map[string]struct{} {
pvcKey := "v1/PersistentVolumeClaim"
pvcList := make(map[string]struct{})
for _, pvc := range restoredResourceList[pvcKey] {
// the format of pvc string in restoredResourceList is like: "namespace/pvcName(status)"
// extract the substring before "(created)" if the status in rightmost Parenthesis is "created"
r := regexp.MustCompile(`\(([^)]+)\)`)
matches := r.FindAllStringSubmatch(pvc, -1)
if len(matches) > 0 && matches[len(matches)-1][1] == itemRestoreResultCreated {
pvcList[pvc[:len(pvc)-len("(created)")]] = struct{}{}
}
}
return pvcList
}

View File

@ -0,0 +1,57 @@
/*
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 (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetRestoredPVCFromRestoredResourceList(t *testing.T) {
// test empty list
restoredResourceList := map[string][]string{}
actual := RestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test no match
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(updated)",
},
"v1/PersistentVolume": {
"namespace1/pv(created)",
},
}
actual = RestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test matches
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(created)",
"namespace2/pvc2(updated)",
"namespace3/pvc(3)(created)",
},
}
expected := map[string]struct{}{
"namespace1/pvc1": {},
"namespace3/pvc(3)": {},
}
actual = RestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Equal(t, expected, actual)
}

View File

@ -19,13 +19,18 @@ package volume
import (
"context"
"strconv"
"strings"
"sync"
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
"github.com/pkg/errors"
"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"
"github.com/vmware-tanzu/velero/pkg/label"
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/features"
@ -34,19 +39,27 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
type VolumeBackupMethod string
type Method string
const (
NativeSnapshot VolumeBackupMethod = "NativeSnapshot"
PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup"
CSISnapshot VolumeBackupMethod = "CSISnapshot"
NativeSnapshot Method = "NativeSnapshot"
PodVolumeBackup Method = "PodVolumeBackup"
CSISnapshot Method = "CSISnapshot"
PodVolumeRestore Method = "PodVolumeRestore"
)
const (
FieldValueIsUnknown string = "unknown"
kopia string = "kopia"
veleroDatamover string = "velero"
//TODO reuse these constants from csi-plugin-for-velero after it's merged into the same repo
CSIDriverNameAnnotation = "velero.io/csi-driver-name"
VolumeSnapshotHandleAnnotation = "velero.io/csi-volumesnapshot-handle"
)
type VolumeInfo struct {
type BackupVolumeInfo struct {
// The PVC's name.
PVCName string `json:"pvcName,omitempty"`
@ -57,7 +70,7 @@ type VolumeInfo struct {
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"`
BackupMethod Method `json:"backupMethod,omitempty"`
// Whether the volume's snapshot data is moved to specified storage.
SnapshotDataMoved bool `json:"snapshotDataMoved"`
@ -82,10 +95,34 @@ type VolumeInfo struct {
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
PVBInfo *PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
PVBInfo *PodVolumeInfo `json:"pvbInfo,omitempty"`
PVInfo *PVInfo `json:"pvInfo,omitempty"`
}
type RestoreVolumeInfo struct {
// The name of the restored PVC
PVCName string `json:"pvcName,omitempty"`
// The namespace of the restored PVC
PVCNamespace string `json:"pvcNamespace,omitempty"`
// The name of the restored PV, it is possible that in one item there is only PVC or PV info.
// But if both PVC and PV exist in one item of volume info, they should matched, and if the PV is bound to a PVC,
// they should coexist in one item.
PVName string `json:"pvName,omitempty"`
// The way the volume data is restored.
RestoreMethod Method `json:"restoreMethod,omitempty"`
// Whether the volume's data are restored via data movement
SnapshotDataMoved bool `json:"snapshotDataMoved"`
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
PVRInfo *PodVolumeInfo `json:"pvrInfo,omitempty"`
}
// CSISnapshotInfo is used for displaying the CSI snapshot status
type CSISnapshotInfo struct {
// It's the storage provider's snapshot ID for CSI.
@ -101,7 +138,7 @@ type CSISnapshotInfo struct {
VSCName string `json:"vscName"`
// The Async Operation's ID.
OperationID string `json:"operationID"`
OperationID string `json:"operationID,omitempty"`
}
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
@ -115,7 +152,7 @@ type SnapshotDataMovementInfo struct {
// 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"`
RetainedSnapshot string `json:"retainedSnapshot,omitempty"`
// It's the filesystem repository's snapshot ID.
SnapshotHandle string `json:"snapshotHandle"`
@ -145,13 +182,26 @@ type NativeSnapshotInfo struct {
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"`
func newNativeSnapshotInfo(s *Snapshot) *NativeSnapshotInfo {
var iops int64
if s.Spec.VolumeIOPS != nil {
iops = *s.Spec.VolumeIOPS
}
return &NativeSnapshotInfo{
SnapshotHandle: s.Status.ProviderSnapshotID,
VolumeType: s.Spec.VolumeType,
VolumeAZ: s.Spec.VolumeAZ,
IOPS: strconv.FormatInt(iops, 10),
}
}
// PodVolumeInfo is used for displaying the PodVolumeBackup/PodVolumeRestore snapshot status.
type PodVolumeInfo struct {
// It's the file-system uploader's snapshot ID for PodVolumeBackup/PodVolumeRestore.
SnapshotHandle string `json:"snapshotHandle,omitempty"`
// The snapshot corresponding volume size.
Size int64 `json:"size"`
Size int64 `json:"size,omitempty"`
// The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
UploaderType string `json:"uploaderType"`
@ -167,7 +217,31 @@ type PodVolumeBackupInfo struct {
PodNamespace string `json:"podNamespace"`
// The PVB-taken k8s node's name.
NodeName string `json:"nodeName"`
// This field will be empty when the struct is used to represent a podvolumerestore.
NodeName string `json:"nodeName,omitempty"`
}
func newPodVolumeInfoFromPVB(pvb *velerov1api.PodVolumeBackup) *PodVolumeInfo {
return &PodVolumeInfo{
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,
}
}
func newPodVolumeInfoFromPVR(pvr *velerov1api.PodVolumeRestore) *PodVolumeInfo {
return &PodVolumeInfo{
SnapshotHandle: pvr.Spec.SnapshotID,
Size: pvr.Status.Progress.TotalBytes,
UploaderType: pvr.Spec.UploaderType,
VolumeName: pvr.Spec.Volume,
PodName: pvr.Spec.Pod.Name,
PodNamespace: pvr.Spec.Pod.Namespace,
}
}
// PVInfo is used to store some PV information modified after creation.
@ -180,12 +254,12 @@ type PVInfo struct {
Labels map[string]string `json:"labels"`
}
// VolumesInformation contains the information needs by generating
// the backup VolumeInfo array.
type VolumesInformation struct {
// BackupVolumesInformation contains the information needs by generating
// the backup BackupVolumeInfo array.
type BackupVolumesInformation struct {
// A map contains the backup-included PV detail content. The key is PV name.
pvMap map[string]pvcPvInfo
volumeInfos []*VolumeInfo
pvMap *pvcPvMap
volumeInfos []*BackupVolumeInfo
logger logrus.FieldLogger
crClient kbclient.Client
@ -205,30 +279,27 @@ type pvcPvInfo struct {
PV corev1api.PersistentVolume
}
func (v *VolumesInformation) Init() {
v.pvMap = make(map[string]pvcPvInfo)
v.volumeInfos = make([]*VolumeInfo, 0)
func (v *BackupVolumesInformation) Init() {
v.pvMap = &pvcPvMap{
data: make(map[string]pvcPvInfo),
}
v.volumeInfos = make([]*BackupVolumeInfo, 0)
}
func (v *VolumesInformation) InsertPVMap(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
func (v *BackupVolumesInformation) InsertPVMap(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
if v.pvMap == nil {
v.Init()
}
v.pvMap[pv.Name] = pvcPvInfo{
PVCName: pvcName,
PVCNamespace: pvcNamespace,
PV: pv,
}
v.pvMap.insert(pv, pvcName, pvcNamespace)
}
func (v *VolumesInformation) Result(
func (v *BackupVolumesInformation) Result(
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
crClient kbclient.Client,
logger logrus.FieldLogger,
) []*VolumeInfo {
) []*BackupVolumeInfo {
v.logger = logger
v.crClient = crClient
v.volumeSnapshots = csiVolumeSnapshots
@ -245,12 +316,12 @@ func (v *VolumesInformation) Result(
}
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
tmpVolumeInfos := make([]*VolumeInfo, 0)
func (v *BackupVolumesInformation) generateVolumeInfoForSkippedPV() {
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
for pvName, skippedReason := range v.SkippedPVs {
if pvcPVInfo := v.retrievePvcPvInfo(pvName, "", ""); pvcPVInfo != nil {
volumeInfo := &VolumeInfo{
if pvcPVInfo := v.pvMap.retrieve(pvName, "", ""); pvcPVInfo != nil {
volumeInfo := &BackupVolumeInfo{
PVCName: pvcPVInfo.PVCName,
PVCNamespace: pvcPVInfo.PVCNamespace,
PVName: pvName,
@ -273,35 +344,24 @@ func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
}
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
func (v *VolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
tmpVolumeInfos := make([]*VolumeInfo, 0)
func (v *BackupVolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
tmpVolumeInfos := make([]*BackupVolumeInfo, 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),
},
if pvcPVInfo := v.pvMap.retrieve(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
volumeInfo := &BackupVolumeInfo{
BackupMethod: NativeSnapshot,
PVCName: pvcPVInfo.PVCName,
PVCNamespace: pvcPVInfo.PVCNamespace,
PVName: pvcPVInfo.PV.Name,
SnapshotDataMoved: false,
Skipped: false,
NativeSnapshotInfo: newNativeSnapshotInfo(nativeSnapshot),
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)
@ -313,8 +373,8 @@ func (v *VolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
}
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
tmpVolumeInfos := make([]*VolumeInfo, 0)
func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
for _, volumeSnapshot := range v.volumeSnapshots {
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
@ -376,8 +436,8 @@ func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
if volumeSnapshotContent.Status.SnapshotHandle != nil {
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
}
if pvcPVInfo := v.retrievePvcPvInfo("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
volumeInfo := &VolumeInfo{
if pvcPVInfo := v.pvMap.retrieve("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
volumeInfo := &BackupVolumeInfo{
BackupMethod: CSISnapshot,
PVCName: pvcPVInfo.PVCName,
PVCNamespace: pvcPVInfo.PVCNamespace,
@ -412,49 +472,25 @@ func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
}
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
func (v *VolumesInformation) generateVolumeInfoFromPVB() {
tmpVolumeInfos := make([]*VolumeInfo, 0)
// generateVolumeInfoFromPVB generate BackupVolumeInfo for PVB.
func (v *BackupVolumesInformation) generateVolumeInfoFromPVB() {
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
for _, pvb := range v.PodVolumeBackups {
volumeInfo := &VolumeInfo{
BackupMethod: PodVolumeBackup,
SnapshotDataMoved: false,
Skipped: false,
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,
},
volumeInfo := &BackupVolumeInfo{
BackupMethod: PodVolumeBackup,
SnapshotDataMoved: false,
Skipped: false,
StartTimestamp: pvb.Status.StartTimestamp,
CompletionTimestamp: pvb.Status.CompletionTimestamp,
PVBInfo: newPodVolumeInfoFromPVB(pvb),
}
if pvb.Status.StartTimestamp != nil {
volumeInfo.StartTimestamp = pvb.Status.StartTimestamp
}
if pvb.Status.CompletionTimestamp != nil {
volumeInfo.CompletionTimestamp = pvb.Status.CompletionTimestamp
}
pod := new(corev1api.Pod)
pvcName := ""
err := v.crClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: pvb.Spec.Pod.Namespace, Name: pvb.Spec.Pod.Name}, pod)
pvcName, err := pvcByPodvolume(context.TODO(), v.crClient, pvb.Spec.Pod.Name, pvb.Spec.Pod.Namespace, pvb.Spec.Volume)
if err != nil {
v.logger.WithError(err).Warn("Fail to get pod for PodVolumeBackup: ", pvb.Name)
v.logger.WithError(err).Warn("Fail to get PVC from 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 {
if pvcPVInfo := v.pvMap.retrieve("", pvcName, pvb.Spec.Pod.Namespace); pvcPVInfo != nil {
volumeInfo.PVCName = pvcPVInfo.PVCName
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
volumeInfo.PVName = pvcPVInfo.PV.Name
@ -463,27 +499,25 @@ func (v *VolumesInformation) generateVolumeInfoFromPVB() {
Labels: pvcPVInfo.PV.Labels,
}
} else {
v.logger.Warnf("Cannot find info for PVC %s/%s", pod.Namespace, pvcName)
v.logger.Warnf("Cannot find info for PVC %s/%s", pvb.Spec.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() {
// generateVolumeInfoFromDataUpload generate BackupVolumeInfo for DataUpload.
func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
if !features.IsEnabled(velerov1api.CSIFeatureFlag) {
v.logger.Debug("Skip generating VolumeInfo when the CSI feature is disabled.")
v.logger.Debug("Skip generating BackupVolumeInfo when the CSI feature is disabled.")
return
}
tmpVolumeInfos := make([]*VolumeInfo, 0)
tmpVolumeInfos := make([]*BackupVolumeInfo, 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())
@ -525,13 +559,13 @@ func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
}
}
if pvcPVInfo := v.retrievePvcPvInfo("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
dataMover := "velero"
if pvcPVInfo := v.pvMap.retrieve("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
dataMover := veleroDatamover
if dataUpload.Spec.DataMover != "" {
dataMover = dataUpload.Spec.DataMover
}
volumeInfo := &VolumeInfo{
volumeInfo := &BackupVolumeInfo{
BackupMethod: CSISnapshot,
PVCName: pvcPVInfo.PVCName,
PVCNamespace: pvcPVInfo.PVCNamespace,
@ -546,7 +580,7 @@ func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
},
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
DataMover: dataMover,
UploaderType: "kopia",
UploaderType: kopia,
OperationID: operation.Spec.OperationID,
},
PVInfo: &PVInfo{
@ -570,12 +604,21 @@ func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
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 {
type pvcPvMap struct {
data map[string]pvcPvInfo
}
func (m *pvcPvMap) insert(pv corev1api.PersistentVolume, pvcName, pvcNamespace string) {
m.data[pv.Name] = pvcPvInfo{
PVCName: pvcName,
PVCNamespace: pvcNamespace,
PV: pv,
}
}
func (m *pvcPvMap) retrieve(pvName, pvcName, pvcNS string) *pvcPvInfo {
if pvName != "" {
if info, ok := v.pvMap[pvName]; ok {
if info, ok := m.data[pvName]; ok {
return &info
}
return nil
@ -585,7 +628,7 @@ func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *p
return nil
}
for _, info := range v.pvMap {
for _, info := range m.data {
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
return &info
}
@ -593,3 +636,226 @@ func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *p
return nil
}
func pvcByPodvolume(ctx context.Context, crClient kbclient.Client, podName, podNamespace, volumeName string) (string, error) {
pod := new(corev1api.Pod)
err := crClient.Get(ctx, kbclient.ObjectKey{Namespace: podNamespace, Name: podName}, pod)
if err != nil {
return "", errors.Wrap(err, "failed to get pod")
}
for _, volume := range pod.Spec.Volumes {
if volume.Name == volumeName && volume.PersistentVolumeClaim != nil {
return volume.PersistentVolumeClaim.ClaimName, nil
}
}
return "", nil
}
// RestoreVolumeInfoTracker is used to track the volume information during restore.
// It is used to generate the RestoreVolumeInfo array.
type RestoreVolumeInfoTracker struct {
*sync.Mutex
restore *velerov1api.Restore
log logrus.FieldLogger
client kbclient.Client
pvPvc *pvcPvMap
// map of PV name to the NativeSnapshotInfo from which the PV is restored
pvNativeSnapshotMap map[string]*NativeSnapshotInfo
// map of PV name to the CSISnapshot object from which the PV is restored
pvCSISnapshotMap map[string]snapshotv1api.VolumeSnapshot
datadownloadList *velerov2alpha1.DataDownloadList
pvrs []*velerov1api.PodVolumeRestore
}
// Populate data objects in the tracker, which will be used to generate the RestoreVolumeInfo array in Result()
// The input param resourceList should be the final result of the restore.
func (t *RestoreVolumeInfoTracker) Populate(ctx context.Context, restoredResourceList map[string][]string) {
pvcs := RestoredPVCFromRestoredResourceList(restoredResourceList)
t.Lock()
defer t.Unlock()
for item := range pvcs {
n := strings.Split(item, "/")
pvcNS, pvcName := n[0], n[1]
log := t.log.WithField("namespace", pvcNS).WithField("name", pvcName)
pvc := &corev1api.PersistentVolumeClaim{}
if err := t.client.Get(ctx, kbclient.ObjectKey{Namespace: pvcNS, Name: pvcName}, pvc); err != nil {
log.WithError(err).Error("Failed to get PVC")
continue
}
if pvc.Status.Phase != corev1api.ClaimBound || pvc.Spec.VolumeName == "" {
log.Info("PVC is not bound or has no volume name")
continue
}
pv := &corev1api.PersistentVolume{}
if err := t.client.Get(ctx, kbclient.ObjectKey{Name: pvc.Spec.VolumeName}, pv); err != nil {
log.WithError(err).Error("Failed to get PV")
} else {
t.pvPvc.insert(*pv, pvcName, pvcNS)
}
// Collect the CSI VolumeSnapshot objects referenced by the restored PVCs,
if pvc.Spec.DataSource != nil && pvc.Spec.DataSource.Kind == "VolumeSnapshot" {
vs := &snapshotv1api.VolumeSnapshot{}
if err := t.client.Get(ctx, kbclient.ObjectKey{Namespace: pvcNS, Name: pvc.Spec.DataSource.Name}, vs); err != nil {
log.WithError(err).Error("Failed to get VolumeSnapshot")
} else {
t.pvCSISnapshotMap[pv.Name] = *vs
}
}
}
if err := t.client.List(ctx, t.datadownloadList, &kbclient.ListOptions{
Namespace: t.restore.Namespace,
LabelSelector: label.NewSelectorForRestore(t.restore.Name),
}); err != nil {
t.log.WithError(err).Error("Failed to List DataDownloads")
}
}
// Result generates the RestoreVolumeInfo array, the data should come from the Tracker itself and it should not connect tokkkk API
// server again.
func (t *RestoreVolumeInfoTracker) Result() []*RestoreVolumeInfo {
volumeInfos := make([]*RestoreVolumeInfo, 0)
// Generate RestoreVolumeInfo for PVRs
for _, pvr := range t.pvrs {
volumeInfo := &RestoreVolumeInfo{
SnapshotDataMoved: false,
PVRInfo: newPodVolumeInfoFromPVR(pvr),
RestoreMethod: PodVolumeRestore,
}
pvcName, err := pvcByPodvolume(context.TODO(), t.client, pvr.Spec.Pod.Name, pvr.Spec.Pod.Namespace, pvr.Spec.Volume)
if err != nil {
t.log.WithError(err).Warn("Fail to get PVC from PodVolumeRestore: ", pvr.Name)
continue
}
if pvcName != "" {
volumeInfo.PVCName = pvcName
volumeInfo.PVCNamespace = pvr.Spec.Pod.Namespace
if pvcPVInfo := t.pvPvc.retrieve("", pvcName, pvr.Spec.Pod.Namespace); pvcPVInfo != nil {
volumeInfo.PVName = pvcPVInfo.PV.Name
}
} else {
// In this case, the volume is not bound to a PVC and
// the PVR will not be able to populate into the volume, so we'll skip it
t.log.Warnf("unable to get PVC for PodVolumeRestore %s/%s, pod: %s/%s, volume: %s",
pvr.Namespace, pvr.Name, pvr.Spec.Pod.Namespace, pvr.Spec.Pod.Name, pvr.Spec.Volume)
continue
}
volumeInfos = append(volumeInfos, volumeInfo)
}
// Generate RestoreVolumeInfo for PVs restored from NativeSnapshots
for pvName, snapshotInfo := range t.pvNativeSnapshotMap {
volumeInfo := &RestoreVolumeInfo{
PVName: pvName,
SnapshotDataMoved: false,
NativeSnapshotInfo: snapshotInfo,
RestoreMethod: NativeSnapshot,
}
if pvcPVInfo := t.pvPvc.retrieve(pvName, "", ""); pvcPVInfo != nil {
volumeInfo.PVCName = pvcPVInfo.PVCName
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
}
volumeInfos = append(volumeInfos, volumeInfo)
}
// Generate RestoreVolumeInfo for PVs restored from CSISnapshots
for pvName, csiSnapshot := range t.pvCSISnapshotMap {
volumeInfo := &RestoreVolumeInfo{
PVName: pvName,
SnapshotDataMoved: false,
RestoreMethod: CSISnapshot,
CSISnapshotInfo: &CSISnapshotInfo{
SnapshotHandle: csiSnapshot.Annotations[VolumeSnapshotHandleAnnotation],
Size: csiSnapshot.Status.RestoreSize.Value(),
Driver: csiSnapshot.Annotations[CSIDriverNameAnnotation],
VSCName: *csiSnapshot.Spec.Source.VolumeSnapshotContentName,
},
}
if pvcPVInfo := t.pvPvc.retrieve(pvName, "", ""); pvcPVInfo != nil {
volumeInfo.PVCName = pvcPVInfo.PVCName
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
}
volumeInfos = append(volumeInfos, volumeInfo)
}
for _, dd := range t.datadownloadList.Items {
var pvcName, pvcNS, pvName string
if pvcPVInfo := t.pvPvc.retrieve(dd.Spec.TargetVolume.PV, dd.Spec.TargetVolume.PVC, dd.Spec.TargetVolume.Namespace); pvcPVInfo != nil {
pvcName = pvcPVInfo.PVCName
pvcNS = pvcPVInfo.PVCNamespace
pvName = pvcPVInfo.PV.Name
} else {
pvcName = dd.Spec.TargetVolume.PVC
pvName = dd.Spec.TargetVolume.PV
pvcNS = dd.Spec.TargetVolume.Namespace
}
operationID := dd.Labels[velerov1api.AsyncOperationIDLabel]
dataMover := veleroDatamover
if dd.Spec.DataMover != "" {
dataMover = dd.Spec.DataMover
}
volumeInfo := &RestoreVolumeInfo{
PVName: pvName,
PVCNamespace: pvcNS,
PVCName: pvcName,
SnapshotDataMoved: true,
// The method will be CSI always no CSI related CRs are created during restore, because
// the datadownload was initiated in CSI plugin
// For the same reason, no CSI snapshot info will be populated into volumeInfo
RestoreMethod: CSISnapshot,
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
DataMover: dataMover,
UploaderType: kopia,
SnapshotHandle: dd.Spec.SnapshotID,
OperationID: operationID,
},
}
volumeInfos = append(volumeInfos, volumeInfo)
}
return volumeInfos
}
func NewRestoreVolInfoTracker(restore *velerov1api.Restore, logger logrus.FieldLogger, client kbclient.Client) *RestoreVolumeInfoTracker {
return &RestoreVolumeInfoTracker{
Mutex: &sync.Mutex{},
client: client,
log: logger,
restore: restore,
pvPvc: &pvcPvMap{
data: make(map[string]pvcPvInfo),
},
pvNativeSnapshotMap: make(map[string]*NativeSnapshotInfo),
pvCSISnapshotMap: make(map[string]snapshotv1api.VolumeSnapshot),
datadownloadList: &velerov2alpha1.DataDownloadList{},
}
}
func (t *RestoreVolumeInfoTracker) TrackNativeSnapshot(pvName string, snapshotHandle, volumeType, volumeAZ string, iops int64) {
t.Lock()
defer t.Unlock()
t.pvNativeSnapshotMap[pvName] = &NativeSnapshotInfo{
SnapshotHandle: snapshotHandle,
VolumeType: volumeType,
VolumeAZ: volumeAZ,
IOPS: strconv.FormatInt(iops, 10),
}
}
func (t *RestoreVolumeInfoTracker) RenamePVForNativeSnapshot(oldName, newName string) {
t.Lock()
defer t.Unlock()
if snapshotInfo, ok := t.pvNativeSnapshotMap[oldName]; ok {
t.pvNativeSnapshotMap[newName] = snapshotInfo
delete(t.pvNativeSnapshotMap, oldName)
}
}
func (t *RestoreVolumeInfoTracker) TrackPodVolume(pvr *velerov1api.PodVolumeRestore) {
t.Lock()
defer t.Unlock()
t.pvrs = append(t.pvrs, pvr)
}

View File

@ -18,8 +18,11 @@ package volume
import (
"context"
"sync"
"testing"
"github.com/stretchr/testify/assert"
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
@ -43,7 +46,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
name string
skippedPVName string
pvMap map[string]pvcPvInfo
expectedVolumeInfos []*VolumeInfo
expectedVolumeInfos []*BackupVolumeInfo
}{
{
name: "Cannot find info for PV",
@ -63,7 +66,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Normal Skipped PV info",
@ -96,7 +99,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -116,7 +119,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
volumesInfo := VolumesInformation{}
volumesInfo := BackupVolumesInformation{}
volumesInfo.Init()
if tc.skippedPVName != "" {
@ -127,7 +130,9 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
if tc.pvMap != nil {
for k, v := range tc.pvMap {
volumesInfo.pvMap[k] = v
if k == v.PV.Name {
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
}
}
}
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
@ -143,7 +148,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
name string
nativeSnapshot Snapshot
pvMap map[string]pvcPvInfo
expectedVolumeInfos []*VolumeInfo
expectedVolumeInfos []*BackupVolumeInfo
}{
{
name: "Native snapshot's IPOS pointer is nil",
@ -153,7 +158,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
VolumeIOPS: nil,
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Cannot find info for the PV",
@ -163,7 +168,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
VolumeIOPS: int64Ptr(100),
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Cannot find PV info in pvMap",
@ -193,7 +198,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Normal native snapshot",
@ -223,7 +228,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
},
},
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -248,12 +253,14 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
volumesInfo := VolumesInformation{}
volumesInfo := BackupVolumesInformation{}
volumesInfo.Init()
volumesInfo.NativeSnapshots = append(volumesInfo.NativeSnapshots, &tc.nativeSnapshot)
if tc.pvMap != nil {
for k, v := range tc.pvMap {
volumesInfo.pvMap[k] = v
if k == v.PV.Name {
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
}
}
}
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
@ -274,7 +281,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
pvMap map[string]pvcPvInfo
operation *itemoperation.BackupOperation
expectedVolumeInfos []*VolumeInfo
expectedVolumeInfos []*BackupVolumeInfo
}{
{
name: "VS doesn't have VolumeSnapshotClass name",
@ -285,7 +292,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
},
Spec: snapshotv1api.VolumeSnapshotSpec{},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "VS doesn't have status",
@ -298,7 +305,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
VolumeSnapshotClassName: stringPtr("testClass"),
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "VS doesn't have PVC",
@ -314,7 +321,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
BoundVolumeSnapshotContentName: stringPtr("testContent"),
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Cannot find VSC for VS",
@ -333,10 +340,10 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
BoundVolumeSnapshotContentName: stringPtr("testContent"),
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Cannot find VolumeInfo for PVC",
name: "Cannot find BackupVolumeInfo for PVC",
volumeSnapshot: snapshotv1api.VolumeSnapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "testVS",
@ -354,7 +361,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
},
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Normal VolumeSnapshot case",
@ -406,7 +413,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -434,12 +441,14 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
volumesInfo := VolumesInformation{}
volumesInfo := BackupVolumesInformation{}
volumesInfo.Init()
if tc.pvMap != nil {
for k, v := range tc.pvMap {
volumesInfo.pvMap[k] = v
if k == v.PV.Name {
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
}
}
}
@ -465,12 +474,12 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
pvb *velerov1api.PodVolumeBackup
pod *corev1api.Pod
pvMap map[string]pvcPvInfo
expectedVolumeInfos []*VolumeInfo
expectedVolumeInfos []*BackupVolumeInfo
}{
{
name: "cannot find PVB's pod, should fail",
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "PVB doesn't have a related PVC",
@ -491,13 +500,13 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
},
},
).Result(),
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "",
PVCNamespace: "",
PVName: "",
BackupMethod: PodVolumeBackup,
PVBInfo: &PodVolumeBackupInfo{
PVBInfo: &PodVolumeInfo{
PodName: "testPod",
PodNamespace: "velero",
},
@ -525,7 +534,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
},
},
).Result(),
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "PVB's volume has a PVC",
@ -563,7 +572,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
},
},
).Result(),
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -571,7 +580,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
BackupMethod: PodVolumeBackup,
StartTimestamp: &now,
CompletionTimestamp: &now,
PVBInfo: &PodVolumeBackupInfo{
PVBInfo: &PodVolumeInfo{
PodName: "testPod",
PodNamespace: "velero",
},
@ -586,7 +595,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
volumesInfo := VolumesInformation{}
volumesInfo := BackupVolumesInformation{}
volumesInfo.Init()
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
@ -594,7 +603,9 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
if tc.pvMap != nil {
for k, v := range tc.pvMap {
volumesInfo.pvMap[k] = v
if k == v.PV.Name {
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
}
}
}
if tc.pod != nil {
@ -621,7 +632,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
dataUpload *velerov2alpha1.DataUpload
operation *itemoperation.BackupOperation
pvMap map[string]pvcPvInfo
expectedVolumeInfos []*VolumeInfo
expectedVolumeInfos []*BackupVolumeInfo
}{
{
name: "Operation is not for PVC",
@ -635,7 +646,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "Operation doesn't have DataUpload PostItemOperation",
@ -659,7 +670,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "DataUpload cannot be found for operation",
@ -686,7 +697,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{},
expectedVolumeInfos: []*BackupVolumeInfo{},
},
{
name: "VolumeSnapshotClass cannot be found for operation",
@ -731,7 +742,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -802,7 +813,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
},
},
},
expectedVolumeInfos: []*VolumeInfo{
expectedVolumeInfos: []*BackupVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "velero",
@ -833,7 +844,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
volumesInfo := VolumesInformation{}
volumesInfo := BackupVolumesInformation{}
volumesInfo.Init()
if tc.operation != nil {
@ -842,7 +853,9 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
if tc.pvMap != nil {
for k, v := range tc.pvMap {
volumesInfo.pvMap[k] = v
if k == v.PV.Name {
volumesInfo.pvMap.insert(v.PV, v.PVCName, v.PVCNamespace)
}
}
}
@ -868,6 +881,291 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
}
}
func TestRestoreVolumeInfoTrackNativeSnapshot(t *testing.T) {
fakeCilent := velerotest.NewFakeControllerRuntimeClient(t)
restore := builder.ForRestore("velero", "testRestore").Result()
tracker := NewRestoreVolInfoTracker(restore, logrus.New(), fakeCilent)
tracker.TrackNativeSnapshot("testPV", "snap-001", "ebs", "us-west-1", 10000)
assert.Equal(t, *tracker.pvNativeSnapshotMap["testPV"], NativeSnapshotInfo{
SnapshotHandle: "snap-001",
VolumeType: "ebs",
VolumeAZ: "us-west-1",
IOPS: "10000",
})
tracker.TrackNativeSnapshot("testPV", "snap-002", "ebs", "us-west-2", 15000)
assert.Equal(t, *tracker.pvNativeSnapshotMap["testPV"], NativeSnapshotInfo{
SnapshotHandle: "snap-002",
VolumeType: "ebs",
VolumeAZ: "us-west-2",
IOPS: "15000",
})
tracker.RenamePVForNativeSnapshot("testPV", "newPV")
_, ok := tracker.pvNativeSnapshotMap["testPV"]
assert.False(t, ok)
assert.Equal(t, *tracker.pvNativeSnapshotMap["newPV"], NativeSnapshotInfo{
SnapshotHandle: "snap-002",
VolumeType: "ebs",
VolumeAZ: "us-west-2",
IOPS: "15000",
})
}
func TestRestoreVolumeInfoResult(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t,
builder.ForPod("testNS", "testPod").
Volumes(builder.ForVolume("data-volume-1").PersistentVolumeClaimSource("testPVC2").Result()).
Result())
testRestore := builder.ForRestore("velero", "testRestore").Result()
tests := []struct {
name string
tracker *RestoreVolumeInfoTracker
expectResultValues []RestoreVolumeInfo
}{
{
name: "empty",
tracker: &RestoreVolumeInfoTracker{
Mutex: &sync.Mutex{},
client: fakeClient,
log: logrus.New(),
restore: testRestore,
pvPvc: &pvcPvMap{
data: make(map[string]pvcPvInfo),
},
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
pvCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
datadownloadList: &velerov2alpha1.DataDownloadList{},
pvrs: []*velerov1api.PodVolumeRestore{},
},
expectResultValues: []RestoreVolumeInfo{},
},
{
name: "native snapshot and podvolumes",
tracker: &RestoreVolumeInfoTracker{
Mutex: &sync.Mutex{},
client: fakeClient,
log: logrus.New(),
restore: testRestore,
pvPvc: &pvcPvMap{
data: map[string]pvcPvInfo{
"testPV": {
PVCName: "testPVC",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV").Result(),
},
"testPV2": {
PVCName: "testPVC2",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV2").Result(),
},
},
},
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{
"testPV": {
SnapshotHandle: "snap-001",
VolumeType: "ebs",
VolumeAZ: "us-west-1",
IOPS: "10000",
},
},
pvCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
datadownloadList: &velerov2alpha1.DataDownloadList{},
pvrs: []*velerov1api.PodVolumeRestore{
builder.ForPodVolumeRestore("velero", "testRestore-1234").
PodNamespace("testNS").
PodName("testPod").
Volume("data-volume-1").
UploaderType("kopia").
SnapshotID("pvr-snap-001").Result(),
},
},
expectResultValues: []RestoreVolumeInfo{
{
PVCName: "testPVC2",
PVCNamespace: "testNS",
PVName: "testPV2",
RestoreMethod: PodVolumeRestore,
SnapshotDataMoved: false,
PVRInfo: &PodVolumeInfo{
SnapshotHandle: "pvr-snap-001",
PodName: "testPod",
PodNamespace: "testNS",
UploaderType: "kopia",
VolumeName: "data-volume-1",
},
},
{
PVCName: "testPVC",
PVCNamespace: "testNS",
PVName: "testPV",
RestoreMethod: NativeSnapshot,
SnapshotDataMoved: false,
NativeSnapshotInfo: &NativeSnapshotInfo{
SnapshotHandle: "snap-001",
VolumeType: "ebs",
VolumeAZ: "us-west-1",
IOPS: "10000",
},
},
},
},
{
name: "CSI snapshot without datamovement and podvolumes",
tracker: &RestoreVolumeInfoTracker{
Mutex: &sync.Mutex{},
client: fakeClient,
log: logrus.New(),
restore: testRestore,
pvPvc: &pvcPvMap{
data: map[string]pvcPvInfo{
"testPV": {
PVCName: "testPVC",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV").Result(),
},
"testPV2": {
PVCName: "testPVC2",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV2").Result(),
},
},
},
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
pvCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{
"testPV": *builder.ForVolumeSnapshot("sourceNS", "testCSISnapshot").
ObjectMeta(
builder.WithAnnotations(VolumeSnapshotHandleAnnotation, "csi-snap-001",
CSIDriverNameAnnotation, "test-csi-driver"),
).SourceVolumeSnapshotContentName("test-vsc-001").
Status().RestoreSize("1Gi").Result(),
},
datadownloadList: &velerov2alpha1.DataDownloadList{},
pvrs: []*velerov1api.PodVolumeRestore{
builder.ForPodVolumeRestore("velero", "testRestore-1234").
PodNamespace("testNS").
PodName("testPod").
Volume("data-volume-1").
UploaderType("kopia").
SnapshotID("pvr-snap-001").Result(),
},
},
expectResultValues: []RestoreVolumeInfo{
{
PVCName: "testPVC2",
PVCNamespace: "testNS",
PVName: "testPV2",
RestoreMethod: PodVolumeRestore,
SnapshotDataMoved: false,
PVRInfo: &PodVolumeInfo{
SnapshotHandle: "pvr-snap-001",
PodName: "testPod",
PodNamespace: "testNS",
UploaderType: "kopia",
VolumeName: "data-volume-1",
},
},
{
PVCName: "testPVC",
PVCNamespace: "testNS",
PVName: "testPV",
RestoreMethod: CSISnapshot,
SnapshotDataMoved: false,
CSISnapshotInfo: &CSISnapshotInfo{
SnapshotHandle: "csi-snap-001",
VSCName: "test-vsc-001",
Size: 1073741824,
Driver: "test-csi-driver",
},
},
},
},
{
name: "CSI snapshot with datamovement",
tracker: &RestoreVolumeInfoTracker{
Mutex: &sync.Mutex{},
client: fakeClient,
log: logrus.New(),
restore: testRestore,
pvPvc: &pvcPvMap{
data: map[string]pvcPvInfo{
"testPV": {
PVCName: "testPVC",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV").Result(),
},
"testPV2": {
PVCName: "testPVC2",
PVCNamespace: "testNS",
PV: *builder.ForPersistentVolume("testPV2").Result(),
},
},
},
pvNativeSnapshotMap: map[string]*NativeSnapshotInfo{},
pvCSISnapshotMap: map[string]snapshotv1api.VolumeSnapshot{},
datadownloadList: &velerov2alpha1.DataDownloadList{
Items: []velerov2alpha1.DataDownload{
*builder.ForDataDownload("velero", "testDataDownload-1").
ObjectMeta(builder.WithLabels(velerov1api.AsyncOperationIDLabel, "dd-operation-001")).
SnapshotID("dd-snap-001").
TargetVolume(velerov2alpha1.TargetVolumeSpec{
PVC: "testPVC",
Namespace: "testNS",
}).
Result(),
*builder.ForDataDownload("velero", "testDataDownload-2").
ObjectMeta(builder.WithLabels(velerov1api.AsyncOperationIDLabel, "dd-operation-002")).
SnapshotID("dd-snap-002").
TargetVolume(velerov2alpha1.TargetVolumeSpec{
PVC: "testPVC2",
Namespace: "testNS",
}).
Result(),
},
},
pvrs: []*velerov1api.PodVolumeRestore{},
},
expectResultValues: []RestoreVolumeInfo{
{
PVCName: "testPVC",
PVCNamespace: "testNS",
PVName: "testPV",
RestoreMethod: CSISnapshot,
SnapshotDataMoved: true,
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
DataMover: "velero",
UploaderType: kopia,
SnapshotHandle: "dd-snap-001",
OperationID: "dd-operation-001",
},
},
{
PVCName: "testPVC2",
PVCNamespace: "testNS",
PVName: "testPV2",
RestoreMethod: CSISnapshot,
SnapshotDataMoved: true,
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
DataMover: "velero",
UploaderType: kopia,
SnapshotHandle: "dd-snap-002",
OperationID: "dd-operation-002",
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := tc.tracker.Result()
valuesList := []RestoreVolumeInfo{}
for _, item := range result {
valuesList = append(valuesList, *item)
}
assert.Equal(t, tc.expectResultValues, valuesList)
})
}
}
func stringPtr(str string) *string {
return &str
}

View File

@ -42,6 +42,7 @@ const (
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
DownloadTargetKindRestoreVolumeInfo DownloadTargetKind = "RestoreVolumeInfo"
)
// DownloadTarget is the specification for what kind of file to download, and the name of the

View File

@ -798,7 +798,7 @@ type tarWriter interface {
func (kb *kubernetesBackupper) getVolumeInfos(
backup velerov1api.Backup,
log logrus.FieldLogger,
) (persistence.BackupStore, []*volume.VolumeInfo, error) {
) (persistence.BackupStore, []*volume.BackupVolumeInfo, error) {
location := &velerov1api.BackupStorageLocation{}
if err := kb.kbClient.Get(context.Background(), kbclient.ObjectKey{
Namespace: backup.Namespace,
@ -825,7 +825,7 @@ func (kb *kubernetesBackupper) getVolumeInfos(
// updateVolumeInfos update the VolumeInfos according to the AsyncOperations
func updateVolumeInfos(
volumeInfos []*volume.VolumeInfo,
volumeInfos []*volume.BackupVolumeInfo,
unstructuredItems []unstructured.Unstructured,
operations []*itemoperation.BackupOperation,
log logrus.FieldLogger,
@ -874,7 +874,7 @@ func updateVolumeInfos(
func putVolumeInfos(
backupName string,
volumeInfos []*volume.VolumeInfo,
volumeInfos []*volume.BackupVolumeInfo,
backupStore persistence.BackupStore,
) error {
backupVolumeInfoBuf := new(bytes.Buffer)

View File

@ -4454,7 +4454,7 @@ func TestGetVolumeInfos(t *testing.T) {
backupStore := new(persistencemocks.BackupStore)
h.backupper.pluginManager = func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager }
h.backupper.backupStoreGetter = NewFakeSingleObjectBackupStoreGetter(backupStore)
backupStore.On("GetBackupVolumeInfos", "backup-01").Return([]*volume.VolumeInfo{}, nil)
backupStore.On("GetBackupVolumeInfos", "backup-01").Return([]*volume.BackupVolumeInfo{}, nil)
pluginManager.On("CleanupClients").Return()
backup := builder.ForBackup("velero", "backup-01").StorageLocation("default").Result()
@ -4474,8 +4474,8 @@ func TestUpdateVolumeInfos(t *testing.T) {
name string
operations []*itemoperation.BackupOperation
dataUpload *velerov2alpha1.DataUpload
volumeInfos []*volume.VolumeInfo
expectedVolumeInfos []*volume.VolumeInfo
volumeInfos []*volume.BackupVolumeInfo
expectedVolumeInfos []*volume.BackupVolumeInfo
}{
{
name: "CSISnapshot VolumeInfo update",
@ -4489,7 +4489,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
volumeInfos: []*volume.VolumeInfo{
volumeInfos: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
CompletionTimestamp: &metav1.Time{},
@ -4498,7 +4498,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
expectedVolumeInfos: []*volume.VolumeInfo{
expectedVolumeInfos: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
CompletionTimestamp: &now,
@ -4519,7 +4519,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
SourceNamespace("ns-1").
SourcePVC("pvc-1").
Result(),
volumeInfos: []*volume.VolumeInfo{
volumeInfos: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
@ -4529,7 +4529,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
expectedVolumeInfos: []*volume.VolumeInfo{
expectedVolumeInfos: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
@ -4572,7 +4572,7 @@ func TestPutVolumeInfos(t *testing.T) {
backupStore.On("PutBackupVolumeInfos", mock.Anything, mock.Anything).Return(nil)
require.NoError(t, putVolumeInfos(backupName, []*volume.VolumeInfo{}, backupStore))
require.NoError(t, putVolumeInfos(backupName, []*volume.BackupVolumeInfo{}, backupStore))
}
type fakeSingleObjectBackupStoreGetter struct {

View File

@ -52,11 +52,11 @@ type Request struct {
itemOperationsList *[]*itemoperation.BackupOperation
ResPolicies *resourcepolicies.Policies
SkippedPVTracker *skipPVTracker
VolumesInformation volume.VolumesInformation
VolumesInformation volume.BackupVolumesInformation
}
// VolumesInformation contains the information needs by generating
// the backup VolumeInfo array.
// BackupVolumesInformation contains the information needs by generating
// the backup BackupVolumeInfo array.
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {

View File

@ -75,6 +75,12 @@ func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
return v
}
// SourceVolumeSnapshotContentName set the built VolumeSnapshot's spec.Source.VolumeSnapshotContentName
func (v *VolumeSnapshotBuilder) SourceVolumeSnapshotContentName(name string) *VolumeSnapshotBuilder {
v.object.Spec.Source.VolumeSnapshotContentName = &name
return v
}
// RestoreSize set the built VolumeSnapshot's status.RestoreSize.
func (v *VolumeSnapshotBuilder) RestoreSize(size string) *VolumeSnapshotBuilder {
resourceSize := resource.MustParse(size)

View File

@ -991,6 +991,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.config.formatFlag.Parse(),
s.config.defaultItemOperationTimeout,
s.config.disableInformerCache,
s.crClient,
)
if err = r.SetupWithManager(s.mgr); err != nil {

View File

@ -439,8 +439,8 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
d.Println("Backup Volumes:")
nativeSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
@ -463,7 +463,7 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
d.Printf("\t<error getting backup volume info: %v>\n", err)
return
} else {
var volumeInfos []volume.VolumeInfo
var volumeInfos []volume.BackupVolumeInfo
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
d.Printf("\t<error reading backup volume info: %v>\n", err)
return
@ -488,9 +488,9 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
describePodVolumeBackups(d, details, podVolumeBackupCRs)
}
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.VolumeInfo, error) {
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
nativeSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
if status.VolumeSnapshotsAttempted == 0 {
return nativeSnapshots, nil
@ -507,7 +507,7 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
}
for _, snap := range snapshots {
volumeInfo := volume.VolumeInfo{
volumeInfo := volume.BackupVolumeInfo{
PVName: snap.Spec.PersistentVolumeName,
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
SnapshotHandle: snap.Status.ProviderSnapshotID,
@ -526,9 +526,9 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
return nativeSnapshots, nil
}
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.VolumeInfo, error) {
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
csiSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
if status.CSIVolumeSnapshotsAttempted == 0 {
return csiSnapshots, nil
@ -557,7 +557,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
}
for _, vsc := range vscList {
volInfo := volume.VolumeInfo{
volInfo := volume.BackupVolumeInfo{
PreserveLocalSnapshot: true,
CSISnapshotInfo: &volume.CSISnapshotInfo{
VSCName: vsc.Name,
@ -598,7 +598,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
return csiSnapshots, nil
}
func describeNativeSnapshots(d *Describer, details bool, infos []*volume.VolumeInfo) {
func describeNativeSnapshots(d *Describer, details bool, infos []*volume.BackupVolumeInfo) {
if len(infos) == 0 {
d.Printf("\tVelero-Native Snapshots: <none included>\n")
return
@ -610,7 +610,7 @@ func describeNativeSnapshots(d *Describer, details bool, infos []*volume.VolumeI
}
}
func describNativeSnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describNativeSnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if details {
d.Printf("\t\t%s:\n", info.PVName)
d.Printf("\t\t\tSnapshot ID:\t%s\n", info.NativeSnapshotInfo.SnapshotHandle)
@ -622,7 +622,7 @@ func describNativeSnapshot(d *Describer, details bool, info *volume.VolumeInfo)
}
}
func describeCSISnapshots(d *Describer, details bool, infos []*volume.VolumeInfo, legacyInfoSource bool) {
func describeCSISnapshots(d *Describer, details bool, infos []*volume.BackupVolumeInfo, legacyInfoSource bool) {
if len(infos) == 0 {
if legacyInfoSource {
d.Printf("\tCSI Snapshots: <none included or not detectable>\n")
@ -638,14 +638,14 @@ func describeCSISnapshots(d *Describer, details bool, infos []*volume.VolumeInfo
}
}
func describeCSISnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describeCSISnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
d.Printf("\t\t%s:\n", fmt.Sprintf("%s/%s", info.PVCNamespace, info.PVCName))
describeLocalSnapshot(d, details, info)
describeDataMovement(d, details, info)
}
func describeLocalSnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describeLocalSnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if !info.PreserveLocalSnapshot {
return
}
@ -665,7 +665,7 @@ func describeLocalSnapshot(d *Describer, details bool, info *volume.VolumeInfo)
}
}
func describeDataMovement(d *Describer, details bool, info *volume.VolumeInfo) {
func describeDataMovement(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if !info.SnapshotDataMoved {
return
}

View File

@ -325,13 +325,13 @@ OrderedResources:
func TestDescribeNativeSnapshots(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect string
}{
{
name: "no details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@ -349,7 +349,7 @@ func TestDescribeNativeSnapshots(t *testing.T) {
},
{
name: "details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@ -390,27 +390,27 @@ func TestDescribeNativeSnapshots(t *testing.T) {
func TestCSISnapshots(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect string
legacyInfoSource bool
}{
{
name: "empty info, not legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
expect: ` CSI Snapshots: <none included>
`,
},
{
name: "empty info, legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
legacyInfoSource: true,
expect: ` CSI Snapshots: <none included or not detectable>
`,
},
{
name: "no details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-1",
@ -432,7 +432,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-2",
@ -460,7 +460,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "no details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-3",
@ -481,7 +481,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",
@ -506,7 +506,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, data movement, data mover is empty",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-5",

View File

@ -308,8 +308,8 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupVolumes := make(map[string]interface{})
nativeSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
@ -332,7 +332,7 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupVolumes["errorGetBackupVolumeInfo"] = fmt.Sprintf("error getting backup volume info: %v", err)
return
} else {
var volumeInfos []volume.VolumeInfo
var volumeInfos []volume.BackupVolumeInfo
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
backupVolumes["errorReadBackupVolumeInfo"] = fmt.Sprintf("error reading backup volume info: %v", err)
return
@ -357,7 +357,7 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupStatusInfo["backupVolumes"] = backupVolumes
}
func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}) {
func describeNativeSnapshotsInSF(details bool, infos []*volume.BackupVolumeInfo, backupVolumes map[string]interface{}) {
if len(infos) == 0 {
backupVolumes["nativeSnapshots"] = "<none included>"
return
@ -370,7 +370,7 @@ func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backu
backupVolumes["nativeSnapshots"] = snapshotDetails
}
func describNativeSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) {
func describNativeSnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetails map[string]interface{}) {
if details {
snapshotInfo := make(map[string]string)
snapshotInfo["snapshotID"] = info.NativeSnapshotInfo.SnapshotHandle
@ -384,7 +384,7 @@ func describNativeSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDe
}
}
func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}, legacyInfoSource bool) {
func describeCSISnapshotsInSF(details bool, infos []*volume.BackupVolumeInfo, backupVolumes map[string]interface{}, legacyInfoSource bool) {
if len(infos) == 0 {
if legacyInfoSource {
backupVolumes["csiSnapshots"] = "<none included or not detectable>"
@ -401,7 +401,7 @@ func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVo
backupVolumes["csiSnapshots"] = snapshotDetails
}
func describeCSISnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) {
func describeCSISnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetails map[string]interface{}) {
snapshotDetail := make(map[string]interface{})
describeLocalSnapshotInSF(details, info, snapshotDetail)
@ -411,7 +411,7 @@ func describeCSISnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDeta
}
// describeLocalSnapshotInSF describes CSI volume snapshot contents in structured format.
func describeLocalSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) {
func describeLocalSnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetail map[string]interface{}) {
if !info.PreserveLocalSnapshot {
return
}
@ -434,7 +434,7 @@ func describeLocalSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDe
}
}
func describeDataMovementInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) {
func describeDataMovementInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetail map[string]interface{}) {
if !info.SnapshotDataMoved {
return
}

View File

@ -284,13 +284,13 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) {
func TestDescribeNativeSnapshotsInSF(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect map[string]interface{}
}{
{
name: "no details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@ -310,7 +310,7 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
},
{
name: "details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@ -348,21 +348,21 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
func TestDescribeCSISnapshotsInSF(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect map[string]interface{}
legacyInfoSource bool
}{
{
name: "empty info, not legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
expect: map[string]interface{}{
"csiSnapshots": "<none included>",
},
},
{
name: "empty info, legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
legacyInfoSource: true,
expect: map[string]interface{}{
"csiSnapshots": "<none included or not detectable>",
@ -370,7 +370,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "no details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-1",
@ -395,7 +395,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-2",
@ -427,7 +427,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "no details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-3",
@ -451,7 +451,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",
@ -480,7 +480,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, data movement, data mover is empty",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",

View File

@ -107,6 +107,7 @@ type restoreReconciler struct {
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
globalCrClient client.Client
}
type backupInfo struct {
@ -127,6 +128,7 @@ func NewRestoreReconciler(
logFormat logging.Format,
defaultItemOperationTimeout time.Duration,
disableInformerCache bool,
globalCrClient client.Client,
) *restoreReconciler {
r := &restoreReconciler{
ctx: ctx,
@ -145,6 +147,8 @@ func NewRestoreReconciler(
// replaced with fakes for testing.
newPluginManager: newPluginManager,
backupStoreGetter: backupStoreGetter,
globalCrClient: globalCrClient,
}
// Move the periodical backup and restore metrics computing logic from controllers to here.
@ -521,7 +525,7 @@ 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]volume.BackupVolumeInfo)
volumeInfos, err := backupStore.GetBackupVolumeInfos(restore.Spec.BackupName)
if err != nil {
restoreLog.WithError(err).Errorf("fail to get VolumeInfos metadata file for backup %s", restore.Spec.BackupName)
@ -540,16 +544,17 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
}
restoreReq := &pkgrestore.Request{
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
DisableInformerCache: r.disableInformerCache,
CSIVolumeSnapshots: csiVolumeSnapshots,
VolumeInfoMap: backupVolumeInfoMap,
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
DisableInformerCache: r.disableInformerCache,
CSIVolumeSnapshots: csiVolumeSnapshots,
BackupVolumeInfoMap: backupVolumeInfoMap,
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(restore, restoreLog, r.globalCrClient),
}
restoreWarnings, restoreErrors := r.restorer.RestoreWithResolvers(restoreReq, actionsResolver, pluginManager)
@ -640,6 +645,11 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
r.logger.WithError(err).Error("Error uploading restore item action operation resource list to backup storage")
}
restoreReq.RestoreVolumeInfoTracker.Populate(context.TODO(), restoreReq.RestoredResourceList())
if err := putRestoreVolumeInfoList(restore, restoreReq.RestoreVolumeInfoTracker.Result(), backupStore); err != nil {
r.logger.WithError(err).Error("Error uploading restored volume info to backup storage")
}
if restore.Status.Errors > 0 {
if inProgressOperations {
r.logger.Debug("Restore WaitingForPluginOperationsPartiallyFailed")
@ -776,6 +786,22 @@ func putOperationsForRestore(restore *api.Restore, operations []*itemoperation.R
return nil
}
func putRestoreVolumeInfoList(restore *api.Restore, volInfoList []*volume.RestoreVolumeInfo, store persistence.BackupStore) error {
buf := new(bytes.Buffer)
gzw := gzip.NewWriter(buf)
defer gzw.Close()
if err := json.NewEncoder(gzw).Encode(volInfoList); err != nil {
return errors.Wrap(err, "error encoding restore volume info list to JSON")
}
if err := gzw.Close(); err != nil {
return errors.Wrap(err, "error closing gzip writer")
}
return store.PutRestoreVolumeInfo(restore.Name, buf)
}
func downloadToTempFile(backupName string, backupStore persistence.BackupStore, logger logrus.FieldLogger) (*os.File, error) {
readCloser, err := backupStore.GetBackupContents(backupName)
if err != nil {

View File

@ -91,11 +91,12 @@ func TestFetchBackupInfo(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
)
defer restorer.AssertExpectations(t)
@ -114,6 +115,7 @@ func TestFetchBackupInfo(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
if test.backupStoreError == nil {
@ -170,9 +172,10 @@ func TestProcessQueueItemSkips(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
)
if test.restore != nil {
@ -192,6 +195,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{
@ -432,11 +436,12 @@ func TestRestoreReconcile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
)
defer restorer.AssertExpectations(t)
@ -459,6 +464,7 @@ func TestRestoreReconcile(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
r.clock = clocktesting.NewFakeClock(now)
@ -500,10 +506,11 @@ func TestRestoreReconcile(t *testing.T) {
backupStore.On("PutRestoreResults", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
backupStore.On("PutRestoredResourceList", test.restore.Name, mock.Anything).Return(nil)
backupStore.On("PutRestoreItemOperations", mock.Anything, mock.Anything).Return(nil)
backupStore.On("PutRestoreVolumeInfo", test.restore.Name, mock.Anything).Return(nil)
if test.emptyVolumeInfo == true {
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
} else {
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.VolumeInfo{}, nil)
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.BackupVolumeInfo{}, nil)
}
volumeSnapshots := []*volume.Snapshot{
@ -626,10 +633,11 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
formatFlag := logging.FormatText
var (
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
)
r := NewRestoreReconciler(
@ -645,6 +653,7 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
restore := &velerov1api.Restore{
@ -719,10 +728,11 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
formatFlag := logging.FormatText
var (
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
)
r := NewRestoreReconciler(
@ -738,6 +748,7 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
restore := &velerov1api.Restore{

View File

@ -19,7 +19,6 @@ package controller
import (
"context"
"fmt"
"regexp"
"sync"
"time"
@ -39,7 +38,6 @@ import (
"github.com/vmware-tanzu/velero/pkg/metrics"
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/restore"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/results"
)
@ -150,7 +148,7 @@ func (r *restoreFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Req
return ctrl.Result{}, errors.Wrap(err, "error getting restoredResourceList")
}
restoredPVCList := getRestoredPVCFromRestoredResourceList(restoredResourceList)
restoredPVCList := volume.RestoredPVCFromRestoredResourceList(restoredResourceList)
finalizerCtx := &finalizerContext{
logger: log,
@ -238,7 +236,7 @@ type finalizerContext struct {
logger logrus.FieldLogger
restore *velerov1api.Restore
crClient client.Client
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
restoredPVCList map[string]struct{}
}
@ -277,7 +275,7 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
}
pvWaitGroup.Add(1)
go func(volInfo volume.VolumeInfo, restoredNamespace string) {
go func(volInfo volume.BackupVolumeInfo, restoredNamespace string) {
defer pvWaitGroup.Done()
semaphore <- struct{}{}
@ -358,23 +356,6 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
return errs
}
func getRestoredPVCFromRestoredResourceList(restoredResourceList map[string][]string) map[string]struct{} {
pvcKey := "v1/PersistentVolumeClaim"
pvcList := make(map[string]struct{})
for _, pvc := range restoredResourceList[pvcKey] {
// the format of pvc string in restoredResourceList is like: "namespace/pvcName(status)"
// extract the substring before "(created)" if the status in rightmost Parenthesis is "created"
r := regexp.MustCompile(`\(([^)]+)\)`)
matches := r.FindAllStringSubmatch(pvc, -1)
if len(matches) > 0 && matches[len(matches)-1][1] == restore.ItemRestoreResultCreated {
pvcList[pvc[:len(pvc)-len("(created)")]] = struct{}{}
}
}
return pvcList
}
func needPatch(newPV *v1.PersistentVolume, pvInfo *volume.PVInfo) bool {
if newPV.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimPolicy(pvInfo.ReclaimPolicy) {
return true

View File

@ -210,7 +210,7 @@ func TestUpdateResult(t *testing.T) {
func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
tests := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
restoredPVCNames map[string]struct{}
restore *velerov1api.Restore
restoredPVC []*corev1api.PersistentVolumeClaim
@ -220,21 +220,21 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
}{
{
name: "no applicable volumeInfo",
volumeInfo: []*volume.VolumeInfo{{BackupMethod: "VeleroNativeSnapshot", PVCName: "pvc1"}},
volumeInfo: []*volume.BackupVolumeInfo{{BackupMethod: "VeleroNativeSnapshot", PVCName: "pvc1"}},
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
expectedPatch: nil,
expectedErrNum: 0,
},
{
name: "no restored PVC",
volumeInfo: []*volume.VolumeInfo{{BackupMethod: "PodVolumeBackup", PVCName: "pvc1"}},
volumeInfo: []*volume.BackupVolumeInfo{{BackupMethod: "PodVolumeBackup", PVCName: "pvc1"}},
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
expectedPatch: nil,
expectedErrNum: 0,
},
{
name: "no applicable pv patch",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -256,7 +256,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "an applicable pv patch",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -281,7 +281,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "a mapped namespace restore",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -306,7 +306,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "two applicable pv patches",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -354,7 +354,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "an applicable pv patch with bound error",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -375,7 +375,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "two applicable pv patches with an error",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@ -454,37 +454,3 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
}
}
}
func TestGetRestoredPVCFromRestoredResourceList(t *testing.T) {
// test empty list
restoredResourceList := map[string][]string{}
actual := getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test no match
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(updated)",
},
"v1/PersistentVolume": {
"namespace1/pv(created)",
},
}
actual = getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test matches
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(created)",
"namespace2/pvc2(updated)",
"namespace3/pvc(3)(created)",
},
}
expected := map[string]struct{}{
"namespace1/pvc1": {},
"namespace3/pvc(3)": {},
}
actual = getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Equal(t, expected, actual)
}

View File

@ -61,3 +61,9 @@ func NewListOptionsForBackup(name string) metav1.ListOptions {
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.BackupNameLabel, GetValidName(name)),
}
}
// NewSelectorForRestore returns a Selector based on the restore name.
// This is useful for interacting with Listers that need a Selector.
func NewSelectorForRestore(name string) labels.Selector {
return labels.SelectorFromSet(map[string]string{velerov1api.RestoreNameLabel: GetValidName(name)})
}

View File

@ -314,16 +314,16 @@ func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.R
return r0, r1
}
// GetBackupVolumeInfos provides a mock function with given fields: name
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
// GetRestoreItemOperations provides a mock function with given fields: name
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
ret := _m.Called(name)
var r0 []*volume.VolumeInfo
if rf, ok := ret.Get(0).(func(string) []*volume.VolumeInfo); ok {
var r0 []*volume.BackupVolumeInfo
if rf, ok := ret.Get(0).(func(string) []*volume.BackupVolumeInfo); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*volume.VolumeInfo)
r0 = ret.Get(0).([]*volume.BackupVolumeInfo)
}
}
@ -546,6 +546,19 @@ func (_m *BackupStore) PutRestoredResourceList(restore string, results io.Reader
return r0
}
// PutRestoreVolumeInfo provides a mock function with given fields: restore, results
func (_m *BackupStore) PutRestoreVolumeInfo(restore string, results io.Reader) error {
ret := _m.Called(restore, results)
var r0 error
if rf, ok := ret.Get(0).(func(string, io.Reader) error); ok {
r0 = rf(restore, results)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewBackupStore interface {
mock.TestingT
Cleanup(func())

View File

@ -74,8 +74,8 @@ 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.VolumeInfo, error)
PutBackupVolumeInfos(name string, volumeInfo io.Reader) error
GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error)
GetRestoreResults(name string) (map[string]results.Result, error)
// BackupExists checks if the backup metadata file exists in object storage.
@ -88,6 +88,7 @@ type BackupStore interface {
PutRestoredResourceList(restore string, results io.Reader) error
PutRestoreItemOperations(restore string, restoreItemOperations io.Reader) error
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
PutRestoreVolumeInfo(restore string, volumeInfo io.Reader) error
DeleteRestore(name string) error
GetRestoredResourceList(name string) (map[string][]string, error)
@ -498,8 +499,8 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod
return podVolumeBackups, nil
}
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
volumeInfos := make([]*volume.VolumeInfo, 0)
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
volumeInfos := make([]*volume.BackupVolumeInfo, 0)
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
if err != nil {
@ -602,6 +603,10 @@ func (s *objectBackupStore) PutRestoreItemOperations(restore string, restoreItem
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(restore), restoreItemOperations)
}
func (s *objectBackupStore) PutRestoreVolumeInfo(restore string, volumeInfo io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreVolumeInfoKey(restore), volumeInfo)
}
func (s *objectBackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(backup), backupItemOperations)
}
@ -638,6 +643,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupVolumeInfos:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeInfoKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreVolumeInfo:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreVolumeInfoKey(target.Name), DownloadURLTTL)
default:
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
}

View File

@ -132,3 +132,7 @@ func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string {
func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfo.json.gz", backup))
}
func (l *ObjectStoreLayout) getRestoreVolumeInfoKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("%s-volumeinfo.json.gz", restore))
}

View File

@ -1068,17 +1068,17 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) {
func TestGetBackupVolumeInfos(t *testing.T) {
tests := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
volumeInfoStr string
expectedErr string
expectedResult []*volume.VolumeInfo
expectedResult []*volume.BackupVolumeInfo
}{
{
name: "No VolumeInfos, expect no error.",
},
{
name: "Valid VolumeInfo, should pass.",
volumeInfo: []*volume.VolumeInfo{
name: "Valid BackupVolumeInfo, should pass.",
volumeInfo: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
PVName: "pvName",
@ -1086,7 +1086,7 @@ func TestGetBackupVolumeInfos(t *testing.T) {
SnapshotDataMoved: false,
},
},
expectedResult: []*volume.VolumeInfo{
expectedResult: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
PVName: "pvName",
@ -1096,9 +1096,9 @@ func TestGetBackupVolumeInfos(t *testing.T) {
},
},
{
name: "Invalid VolumeInfo string, should also pass.",
name: "Invalid BackupVolumeInfo string, should also pass.",
volumeInfoStr: `[{"abc": "123", "def": "456", "pvcName": "pvcName"}]`,
expectedResult: []*volume.VolumeInfo{
expectedResult: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
},
@ -1223,7 +1223,7 @@ func TestPutBackupVolumeInfos(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
harness := newObjectBackupStoreTestHarness("foo", tc.prefix)
volumeInfos := []*volume.VolumeInfo{
volumeInfos := []*volume.BackupVolumeInfo{
{
PVCName: "test",
},

View File

@ -1,10 +1,12 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v2.42.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
"github.com/vmware-tanzu/velero/pkg/podvolume"
podvolume "github.com/vmware-tanzu/velero/pkg/podvolume"
volume "github.com/vmware-tanzu/velero/internal/volume"
)
// Restorer is an autogenerated mock type for the Restorer type
@ -12,13 +14,17 @@ type Restorer struct {
mock.Mock
}
// RestorePodVolumes provides a mock function with given fields: _a0
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
ret := _m.Called(_a0)
// RestorePodVolumes provides a mock function with given fields: _a0, _a1
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData, _a1 *volume.RestoreVolumeInfoTracker) []error {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for RestorePodVolumes")
}
var r0 []error
if rf, ok := ret.Get(0).(func(podvolume.RestoreData) []error); ok {
r0 = rf(_a0)
if rf, ok := ret.Get(0).(func(podvolume.RestoreData, *volume.RestoreVolumeInfoTracker) []error); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]error)
@ -27,3 +33,17 @@ func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
return r0
}
// NewRestorer creates a new instance of Restorer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRestorer(t interface {
mock.TestingT
Cleanup(func())
}) *Restorer {
mock := &Restorer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -21,6 +21,8 @@ import (
"sync"
"time"
"github.com/vmware-tanzu/velero/internal/volume"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
@ -51,7 +53,7 @@ type RestoreData struct {
// Restorer can execute pod volume restores of volumes in a pod.
type Restorer interface {
// RestorePodVolumes restores all annotated volumes in a pod.
RestorePodVolumes(RestoreData) []error
RestorePodVolumes(RestoreData, *volume.RestoreVolumeInfoTracker) []error
}
type restorer struct {
@ -114,7 +116,7 @@ func newRestorer(
return r
}
func (r *restorer) RestorePodVolumes(data RestoreData) []error {
func (r *restorer) RestorePodVolumes(data RestoreData, tracker *volume.RestoreVolumeInfoTracker) []error {
volumesToRestore := getVolumeBackupInfoForPod(data.PodVolumeBackups, data.Pod, data.SourceNamespace)
if len(volumesToRestore) == 0 {
return nil
@ -229,6 +231,7 @@ ForEachVolume:
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed {
errs = append(errs, errors.Errorf("pod volume restore failed: %s", res.Status.Message))
}
tracker.TrackPodVolume(res)
case err := <-r.nodeAgentCheck:
errs = append(errs, err)
break ForEachVolume

View File

@ -22,6 +22,8 @@ import (
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appv1 "k8s.io/api/apps/v1"
@ -33,6 +35,7 @@ import (
"k8s.io/client-go/tools/cache"
ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
"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/repository"
@ -418,7 +421,7 @@ func TestRestorePodVolumes(t *testing.T) {
PodVolumeBackups: test.pvbs,
SourceNamespace: test.sourceNamespace,
BackupLocation: test.bsl,
})
}, volume.NewRestoreVolInfoTracker(restoreObj, logrus.New(), fakeCRClient))
if errs == nil {
assert.Nil(t, test.errs)

View File

@ -44,6 +44,7 @@ type pvRestorer struct {
volumeSnapshotterGetter VolumeSnapshotterGetter
kbclient client.Client
credentialFileStore credentials.FileStore
volInfoTracker *volume.RestoreVolumeInfoTracker
}
func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
@ -97,6 +98,11 @@ func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructu
if !ok {
return nil, errors.Errorf("unexpected type %T", updated1)
}
var iops int64 = 0
if snapshotInfo.volumeIOPS != nil {
iops = *snapshotInfo.volumeIOPS
}
r.volInfoTracker.TrackNativeSnapshot(updated2.GetName(), snapshotInfo.providerSnapshotID, snapshotInfo.volumeType, snapshotInfo.volumeAZ, iops)
return updated2, nil
}

View File

@ -20,6 +20,8 @@ import (
"context"
"testing"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@ -39,6 +41,7 @@ func defaultBackup() *builder.BackupBuilder {
}
func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
tests := []struct {
name string
obj *unstructured.Unstructured
@ -115,9 +118,10 @@ func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
r := &pvRestorer{
logger: velerotest.NewLogger(),
restorePVs: tc.restore.Spec.RestorePVs,
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
logger: velerotest.NewLogger(),
restorePVs: tc.restore.Spec.RestorePVs,
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logrus.New(), fakeClient),
}
if tc.backup != nil {
r.backup = tc.backup
@ -180,6 +184,7 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
logger = velerotest.NewLogger()
volumeSnapshotter = new(providermocks.VolumeSnapshotter)
volumeSnapshotterGetter = providerToVolumeSnapshotterMap(map[string]vsv1.VolumeSnapshotter{
tc.expectedProvider: volumeSnapshotter,
@ -192,11 +197,12 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
}
r := &pvRestorer{
logger: velerotest.NewLogger(),
logger: logger,
backup: tc.backup,
volumeSnapshots: tc.volumeSnapshots,
kbclient: fakeClient,
volumeSnapshotterGetter: volumeSnapshotterGetter,
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logger, fakeClient),
}
volumeSnapshotter.On("Init", mock.Anything).Return(nil)

View File

@ -52,17 +52,18 @@ func resourceKey(obj runtime.Object) string {
type Request struct {
*velerov1api.Restore
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
DisableInformerCache bool
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
VolumeInfoMap map[string]volume.VolumeInfo
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
DisableInformerCache bool
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
BackupVolumeInfoMap map[string]volume.BackupVolumeInfo
RestoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
}
type restoredItemStatus struct {

View File

@ -276,6 +276,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
volumeSnapshotterGetter: volumeSnapshotterGetter,
kbclient: kr.kbClient,
credentialFileStore: kr.credentialFileStore,
volInfoTracker: req.RestoreVolumeInfoTracker,
}
req.RestoredItems = make(map[itemKey]restoredItemStatus)
@ -323,7 +324,8 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
disableInformerCache: req.DisableInformerCache,
featureVerifier: kr.featureVerifier,
hookTracker: hook.NewHookTracker(),
volumeInfoMap: req.VolumeInfoMap,
backupVolumeInfoMap: req.BackupVolumeInfoMap,
restoreVolumeInfoTracker: req.RestoreVolumeInfoTracker,
}
return restoreCtx.execute()
@ -376,7 +378,8 @@ type restoreContext struct {
disableInformerCache bool
featureVerifier features.Verifier
hookTracker *hook.HookTracker
volumeInfoMap map[string]volume.VolumeInfo
backupVolumeInfoMap map[string]volume.BackupVolumeInfo
restoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
}
type resourceClientKey struct {
@ -1224,8 +1227,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
return warnings, errs, itemExists
}
if volumeInfo, ok := ctx.volumeInfoMap[obj.GetName()]; ok {
ctx.log.Infof("Find VolumeInfo for PV %s.", obj.GetName())
if volumeInfo, ok := ctx.backupVolumeInfoMap[obj.GetName()]; ok {
ctx.log.Infof("Find BackupVolumeInfo for PV %s.", obj.GetName())
switch volumeInfo.BackupMethod {
case volume.NativeSnapshot:
@ -1258,7 +1261,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
// want to dynamically re-provision it.
return warnings, errs, itemExists
// When the PV data is skipped from backup, it's VolumeInfo BackupMethod
// When the PV data is skipped from backup, it's BackupVolumeInfo BackupMethod
// is not set, and it will fall into the default case.
default:
if hasDeleteReclaimPolicy(obj.Object) {
@ -1277,9 +1280,9 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}
}
} else {
// TODO: VolumeInfo is adopted and old logic is deprecated in v1.13.
// TODO: BackupVolumeInfo is adopted and old logic is deprecated in v1.13.
// Remove the old logic in v1.15.
ctx.log.Infof("Cannot find VolumeInfo for PV %s.", obj.GetName())
ctx.log.Infof("Cannot find BackupVolumeInfo for PV %s.", obj.GetName())
switch {
case hasSnapshot(name, ctx.volumeSnapshots):
@ -1899,7 +1902,7 @@ func restorePodVolumeBackups(ctx *restoreContext, createdObj *unstructured.Unstr
SourceNamespace: originalNamespace,
BackupLocation: ctx.backup.Spec.StorageLocation,
}
if errs := ctx.podVolumeRestorer.RestorePodVolumes(data); errs != nil {
if errs := ctx.podVolumeRestorer.RestorePodVolumes(data, ctx.restoreVolumeInfoTracker); errs != nil {
ctx.log.WithError(kubeerrs.NewAggregate(errs)).Error("unable to successfully complete pod volume restores of pod's volumes")
for _, err := range errs {
@ -2498,7 +2501,7 @@ func (ctx *restoreContext) handlePVHasNativeSnapshot(obj *unstructured.Unstructu
ctx.renamedPVs[oldName] = pvName
retObj.SetName(pvName)
ctx.restoreVolumeInfoTracker.RenamePVForNativeSnapshot(oldName, pvName)
// Add the original PV name as an annotation.
annotations := retObj.GetAnnotations()
if annotations == nil {

View File

@ -70,7 +70,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]volume.BackupVolumeInfo
}{
{
name: "Restore PV with native snapshot",
@ -83,7 +83,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@ -107,11 +107,11 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.PodVolumeBackup,
PVName: "pv-1",
PVBInfo: &volume.PodVolumeBackupInfo{
PVBInfo: &volume.PodVolumeInfo{
SnapshotHandle: "testSnapshotHandle",
Size: 100,
NodeName: "testNode",
@ -133,7 +133,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.CSISnapshot,
SnapshotDataMoved: false,
@ -158,7 +158,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.CSISnapshot,
SnapshotDataMoved: true,
@ -186,7 +186,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
PVName: "pv-1",
Skipped: true,
@ -207,7 +207,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
PVName: "pv-1",
Skipped: true,
@ -235,13 +235,13 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
h.restorer.featureVerifier = verifier
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
VolumeInfoMap: tc.volumeInfoMap,
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
BackupVolumeInfoMap: tc.volumeInfoMap,
}
warnings, errs := h.restorer.Restore(
data,
@ -3311,12 +3311,13 @@ func TestRestorePersistentVolumes(t *testing.T) {
}
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
VolumeSnapshots: tc.volumeSnapshots,
BackupReader: tc.tarball,
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
VolumeSnapshots: tc.volumeSnapshots,
BackupReader: tc.tarball,
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, h.log, test.NewFakeControllerRuntimeClient(t)),
}
warnings, errs := h.restorer.Restore(
data,
@ -3441,7 +3442,7 @@ func TestRestoreWithPodVolume(t *testing.T) {
BackupLocation: "",
}
restorer.
On("RestorePodVolumes", expectedArgs).
On("RestorePodVolumes", expectedArgs, mock.Anything).
Return(nil)
}

View File

@ -235,7 +235,7 @@ func GetVolumeInfo(
bslConfig,
backupName,
subPrefix string,
) ([]*volume.VolumeInfo, error) {
) ([]*volume.BackupVolumeInfo, error) {
readCloser, err := GetVolumeInfoMetadataContent(objectStoreProvider,
cloudCredentialsFile,
bslBucket,
@ -254,7 +254,7 @@ func GetVolumeInfo(
}
defer gzr.Close()
volumeInfos := make([]*volume.VolumeInfo, 0)
volumeInfos := make([]*volume.BackupVolumeInfo, 0)
if err := json.NewDecoder(gzr).Decode(&volumeInfos); err != nil {
return nil, errors.Wrap(err, "error decoding object data")