Track and persist restore volume info
Signed-off-by: Daniel Jiang <jiangd@vmware.com>pull/7630/head
parent
500e5aeeca
commit
0a280e5786
|
@ -0,0 +1 @@
|
|||
Track and persist restore volume info
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)})
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue