Merge pull request #7630 from reasonerjt/restore-vol-info
Track and persist restore volume infopull/7656/head
commit
3c377bc3ec
|
@ -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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
corev1api "k8s.io/api/core/v1"
|
corev1api "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
|
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"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||||
"github.com/vmware-tanzu/velero/pkg/features"
|
"github.com/vmware-tanzu/velero/pkg/features"
|
||||||
|
@ -34,19 +39,27 @@ import (
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VolumeBackupMethod string
|
type Method string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NativeSnapshot VolumeBackupMethod = "NativeSnapshot"
|
NativeSnapshot Method = "NativeSnapshot"
|
||||||
PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup"
|
PodVolumeBackup Method = "PodVolumeBackup"
|
||||||
CSISnapshot VolumeBackupMethod = "CSISnapshot"
|
CSISnapshot Method = "CSISnapshot"
|
||||||
|
PodVolumeRestore Method = "PodVolumeRestore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FieldValueIsUnknown string = "unknown"
|
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.
|
// The PVC's name.
|
||||||
PVCName string `json:"pvcName,omitempty"`
|
PVCName string `json:"pvcName,omitempty"`
|
||||||
|
|
||||||
|
@ -57,7 +70,7 @@ type VolumeInfo struct {
|
||||||
PVName string `json:"pvName,omitempty"`
|
PVName string `json:"pvName,omitempty"`
|
||||||
|
|
||||||
// The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`.
|
// 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.
|
// Whether the volume's snapshot data is moved to specified storage.
|
||||||
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
SnapshotDataMoved bool `json:"snapshotDataMoved"`
|
||||||
|
@ -82,10 +95,34 @@ type VolumeInfo struct {
|
||||||
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
|
CSISnapshotInfo *CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
|
||||||
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
SnapshotDataMovementInfo *SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
|
||||||
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
NativeSnapshotInfo *NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
|
||||||
PVBInfo *PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
|
PVBInfo *PodVolumeInfo `json:"pvbInfo,omitempty"`
|
||||||
PVInfo *PVInfo `json:"pvInfo,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
|
// CSISnapshotInfo is used for displaying the CSI snapshot status
|
||||||
type CSISnapshotInfo struct {
|
type CSISnapshotInfo struct {
|
||||||
// It's the storage provider's snapshot ID for CSI.
|
// It's the storage provider's snapshot ID for CSI.
|
||||||
|
@ -101,7 +138,7 @@ type CSISnapshotInfo struct {
|
||||||
VSCName string `json:"vscName"`
|
VSCName string `json:"vscName"`
|
||||||
|
|
||||||
// The Async Operation's ID.
|
// The Async Operation's ID.
|
||||||
OperationID string `json:"operationID"`
|
OperationID string `json:"operationID,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
|
// 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).
|
// The name or ID of the snapshot associated object(SAO).
|
||||||
// SAO is used to support local snapshots for the snapshot data mover,
|
// SAO is used to support local snapshots for the snapshot data mover,
|
||||||
// e.g. it could be a VolumeSnapshot for CSI snapshot data movement.
|
// 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.
|
// It's the filesystem repository's snapshot ID.
|
||||||
SnapshotHandle string `json:"snapshotHandle"`
|
SnapshotHandle string `json:"snapshotHandle"`
|
||||||
|
@ -145,13 +182,26 @@ type NativeSnapshotInfo struct {
|
||||||
IOPS string `json:"iops"`
|
IOPS string `json:"iops"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status.
|
func newNativeSnapshotInfo(s *Snapshot) *NativeSnapshotInfo {
|
||||||
type PodVolumeBackupInfo struct {
|
var iops int64
|
||||||
// It's the file-system uploader's snapshot ID for PodVolumeBackup.
|
if s.Spec.VolumeIOPS != nil {
|
||||||
SnapshotHandle string `json:"snapshotHandle"`
|
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.
|
// 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`.
|
// The type of the uploader that uploads the data. The valid values are `kopia` and `restic`.
|
||||||
UploaderType string `json:"uploaderType"`
|
UploaderType string `json:"uploaderType"`
|
||||||
|
@ -167,7 +217,31 @@ type PodVolumeBackupInfo struct {
|
||||||
PodNamespace string `json:"podNamespace"`
|
PodNamespace string `json:"podNamespace"`
|
||||||
|
|
||||||
// The PVB-taken k8s node's name.
|
// 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.
|
// PVInfo is used to store some PV information modified after creation.
|
||||||
|
@ -180,12 +254,12 @@ type PVInfo struct {
|
||||||
Labels map[string]string `json:"labels"`
|
Labels map[string]string `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VolumesInformation contains the information needs by generating
|
// BackupVolumesInformation contains the information needs by generating
|
||||||
// the backup VolumeInfo array.
|
// the backup BackupVolumeInfo array.
|
||||||
type VolumesInformation struct {
|
type BackupVolumesInformation struct {
|
||||||
// A map contains the backup-included PV detail content. The key is PV name.
|
// A map contains the backup-included PV detail content. The key is PV name.
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap *pvcPvMap
|
||||||
volumeInfos []*VolumeInfo
|
volumeInfos []*BackupVolumeInfo
|
||||||
|
|
||||||
logger logrus.FieldLogger
|
logger logrus.FieldLogger
|
||||||
crClient kbclient.Client
|
crClient kbclient.Client
|
||||||
|
@ -205,30 +279,27 @@ type pvcPvInfo struct {
|
||||||
PV corev1api.PersistentVolume
|
PV corev1api.PersistentVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VolumesInformation) Init() {
|
func (v *BackupVolumesInformation) Init() {
|
||||||
v.pvMap = make(map[string]pvcPvInfo)
|
v.pvMap = &pvcPvMap{
|
||||||
v.volumeInfos = make([]*VolumeInfo, 0)
|
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 {
|
if v.pvMap == nil {
|
||||||
v.Init()
|
v.Init()
|
||||||
}
|
}
|
||||||
|
v.pvMap.insert(pv, pvcName, pvcNamespace)
|
||||||
v.pvMap[pv.Name] = pvcPvInfo{
|
|
||||||
PVCName: pvcName,
|
|
||||||
PVCNamespace: pvcNamespace,
|
|
||||||
PV: pv,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VolumesInformation) Result(
|
func (v *BackupVolumesInformation) Result(
|
||||||
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
csiVolumeSnapshots []snapshotv1api.VolumeSnapshot,
|
||||||
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
|
csiVolumeSnapshotContents []snapshotv1api.VolumeSnapshotContent,
|
||||||
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
csiVolumesnapshotClasses []snapshotv1api.VolumeSnapshotClass,
|
||||||
crClient kbclient.Client,
|
crClient kbclient.Client,
|
||||||
logger logrus.FieldLogger,
|
logger logrus.FieldLogger,
|
||||||
) []*VolumeInfo {
|
) []*BackupVolumeInfo {
|
||||||
v.logger = logger
|
v.logger = logger
|
||||||
v.crClient = crClient
|
v.crClient = crClient
|
||||||
v.volumeSnapshots = csiVolumeSnapshots
|
v.volumeSnapshots = csiVolumeSnapshots
|
||||||
|
@ -245,12 +316,12 @@ func (v *VolumesInformation) Result(
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
// generateVolumeInfoForSkippedPV generate VolumeInfos for SkippedPV.
|
||||||
func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
|
func (v *BackupVolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||||
|
|
||||||
for pvName, skippedReason := range v.SkippedPVs {
|
for pvName, skippedReason := range v.SkippedPVs {
|
||||||
if pvcPVInfo := v.retrievePvcPvInfo(pvName, "", ""); pvcPVInfo != nil {
|
if pvcPVInfo := v.pvMap.retrieve(pvName, "", ""); pvcPVInfo != nil {
|
||||||
volumeInfo := &VolumeInfo{
|
volumeInfo := &BackupVolumeInfo{
|
||||||
PVCName: pvcPVInfo.PVCName,
|
PVCName: pvcPVInfo.PVCName,
|
||||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
PVName: pvName,
|
PVName: pvName,
|
||||||
|
@ -273,35 +344,24 @@ func (v *VolumesInformation) generateVolumeInfoForSkippedPV() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
// generateVolumeInfoForVeleroNativeSnapshot generate VolumeInfos for Velero native snapshot
|
||||||
func (v *VolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
func (v *BackupVolumesInformation) generateVolumeInfoForVeleroNativeSnapshot() {
|
||||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||||
|
|
||||||
for _, nativeSnapshot := range v.NativeSnapshots {
|
for _, nativeSnapshot := range v.NativeSnapshots {
|
||||||
var iops int64
|
if pvcPVInfo := v.pvMap.retrieve(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
||||||
if nativeSnapshot.Spec.VolumeIOPS != nil {
|
volumeInfo := &BackupVolumeInfo{
|
||||||
iops = *nativeSnapshot.Spec.VolumeIOPS
|
BackupMethod: NativeSnapshot,
|
||||||
}
|
PVCName: pvcPVInfo.PVCName,
|
||||||
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
if pvcPVInfo := v.retrievePvcPvInfo(nativeSnapshot.Spec.PersistentVolumeName, "", ""); pvcPVInfo != nil {
|
PVName: pvcPVInfo.PV.Name,
|
||||||
volumeInfo := &VolumeInfo{
|
SnapshotDataMoved: false,
|
||||||
BackupMethod: NativeSnapshot,
|
Skipped: false,
|
||||||
PVCName: pvcPVInfo.PVCName,
|
NativeSnapshotInfo: newNativeSnapshotInfo(nativeSnapshot),
|
||||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
|
||||||
PVName: pvcPVInfo.PV.Name,
|
|
||||||
SnapshotDataMoved: false,
|
|
||||||
Skipped: false,
|
|
||||||
NativeSnapshotInfo: &NativeSnapshotInfo{
|
|
||||||
SnapshotHandle: nativeSnapshot.Status.ProviderSnapshotID,
|
|
||||||
VolumeType: nativeSnapshot.Spec.VolumeType,
|
|
||||||
VolumeAZ: nativeSnapshot.Spec.VolumeAZ,
|
|
||||||
IOPS: strconv.FormatInt(iops, 10),
|
|
||||||
},
|
|
||||||
PVInfo: &PVInfo{
|
PVInfo: &PVInfo{
|
||||||
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
ReclaimPolicy: string(pvcPVInfo.PV.Spec.PersistentVolumeReclaimPolicy),
|
||||||
Labels: pvcPVInfo.PV.Labels,
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
} else {
|
} else {
|
||||||
v.logger.Warnf("cannot find info for PV %s", nativeSnapshot.Spec.PersistentVolumeName)
|
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
|
// generateVolumeInfoForCSIVolumeSnapshot generate VolumeInfos for CSI VolumeSnapshot
|
||||||
func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
func (v *BackupVolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||||
|
|
||||||
for _, volumeSnapshot := range v.volumeSnapshots {
|
for _, volumeSnapshot := range v.volumeSnapshots {
|
||||||
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
var volumeSnapshotClass *snapshotv1api.VolumeSnapshotClass
|
||||||
|
@ -376,8 +436,8 @@ func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||||
if volumeSnapshotContent.Status.SnapshotHandle != nil {
|
if volumeSnapshotContent.Status.SnapshotHandle != nil {
|
||||||
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
|
snapshotHandle = *volumeSnapshotContent.Status.SnapshotHandle
|
||||||
}
|
}
|
||||||
if pvcPVInfo := v.retrievePvcPvInfo("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
if pvcPVInfo := v.pvMap.retrieve("", *volumeSnapshot.Spec.Source.PersistentVolumeClaimName, volumeSnapshot.Namespace); pvcPVInfo != nil {
|
||||||
volumeInfo := &VolumeInfo{
|
volumeInfo := &BackupVolumeInfo{
|
||||||
BackupMethod: CSISnapshot,
|
BackupMethod: CSISnapshot,
|
||||||
PVCName: pvcPVInfo.PVCName,
|
PVCName: pvcPVInfo.PVCName,
|
||||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
@ -412,49 +472,25 @@ func (v *VolumesInformation) generateVolumeInfoForCSIVolumeSnapshot() {
|
||||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateVolumeInfoFromPVB generate VolumeInfo for PVB.
|
// generateVolumeInfoFromPVB generate BackupVolumeInfo for PVB.
|
||||||
func (v *VolumesInformation) generateVolumeInfoFromPVB() {
|
func (v *BackupVolumesInformation) generateVolumeInfoFromPVB() {
|
||||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||||
|
|
||||||
for _, pvb := range v.PodVolumeBackups {
|
for _, pvb := range v.PodVolumeBackups {
|
||||||
volumeInfo := &VolumeInfo{
|
volumeInfo := &BackupVolumeInfo{
|
||||||
BackupMethod: PodVolumeBackup,
|
BackupMethod: PodVolumeBackup,
|
||||||
SnapshotDataMoved: false,
|
SnapshotDataMoved: false,
|
||||||
Skipped: false,
|
Skipped: false,
|
||||||
PVBInfo: &PodVolumeBackupInfo{
|
StartTimestamp: pvb.Status.StartTimestamp,
|
||||||
SnapshotHandle: pvb.Status.SnapshotID,
|
CompletionTimestamp: pvb.Status.CompletionTimestamp,
|
||||||
Size: pvb.Status.Progress.TotalBytes,
|
PVBInfo: newPodVolumeInfoFromPVB(pvb),
|
||||||
UploaderType: pvb.Spec.UploaderType,
|
|
||||||
VolumeName: pvb.Spec.Volume,
|
|
||||||
PodName: pvb.Spec.Pod.Name,
|
|
||||||
PodNamespace: pvb.Spec.Pod.Namespace,
|
|
||||||
NodeName: pvb.Spec.Node,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
pvcName, err := pvcByPodvolume(context.TODO(), v.crClient, pvb.Spec.Pod.Name, pvb.Spec.Pod.Namespace, pvb.Spec.Volume)
|
||||||
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)
|
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
for _, volume := range pod.Spec.Volumes {
|
|
||||||
if volume.Name == pvb.Spec.Volume && volume.PersistentVolumeClaim != nil {
|
|
||||||
pvcName = volume.PersistentVolumeClaim.ClaimName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pvcName != "" {
|
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.PVCName = pvcPVInfo.PVCName
|
||||||
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
volumeInfo.PVCNamespace = pvcPVInfo.PVCNamespace
|
||||||
volumeInfo.PVName = pvcPVInfo.PV.Name
|
volumeInfo.PVName = pvcPVInfo.PV.Name
|
||||||
|
@ -463,27 +499,25 @@ func (v *VolumesInformation) generateVolumeInfoFromPVB() {
|
||||||
Labels: pvcPVInfo.PV.Labels,
|
Labels: pvcPVInfo.PV.Labels,
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v.logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
v.logger.Debug("The PVB %s doesn't have a corresponding PVC", pvb.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
tmpVolumeInfos = append(tmpVolumeInfos, volumeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateVolumeInfoFromDataUpload generate VolumeInfo for DataUpload.
|
// generateVolumeInfoFromDataUpload generate BackupVolumeInfo for DataUpload.
|
||||||
func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
|
func (v *BackupVolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||||
if !features.IsEnabled(velerov1api.CSIFeatureFlag) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpVolumeInfos := make([]*VolumeInfo, 0)
|
tmpVolumeInfos := make([]*BackupVolumeInfo, 0)
|
||||||
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
vsClassList := new(snapshotv1api.VolumeSnapshotClassList)
|
||||||
if err := v.crClient.List(context.TODO(), vsClassList); err != nil {
|
if err := v.crClient.List(context.TODO(), vsClassList); err != nil {
|
||||||
v.logger.WithError(err).Errorf("cannot list VolumeSnapshotClass %s", err.Error())
|
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 {
|
if pvcPVInfo := v.pvMap.retrieve("", operation.Spec.ResourceIdentifier.Name, operation.Spec.ResourceIdentifier.Namespace); pvcPVInfo != nil {
|
||||||
dataMover := "velero"
|
dataMover := veleroDatamover
|
||||||
if dataUpload.Spec.DataMover != "" {
|
if dataUpload.Spec.DataMover != "" {
|
||||||
dataMover = dataUpload.Spec.DataMover
|
dataMover = dataUpload.Spec.DataMover
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeInfo := &VolumeInfo{
|
volumeInfo := &BackupVolumeInfo{
|
||||||
BackupMethod: CSISnapshot,
|
BackupMethod: CSISnapshot,
|
||||||
PVCName: pvcPVInfo.PVCName,
|
PVCName: pvcPVInfo.PVCName,
|
||||||
PVCNamespace: pvcPVInfo.PVCNamespace,
|
PVCNamespace: pvcPVInfo.PVCNamespace,
|
||||||
|
@ -546,7 +580,7 @@ func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||||
},
|
},
|
||||||
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
SnapshotDataMovementInfo: &SnapshotDataMovementInfo{
|
||||||
DataMover: dataMover,
|
DataMover: dataMover,
|
||||||
UploaderType: "kopia",
|
UploaderType: kopia,
|
||||||
OperationID: operation.Spec.OperationID,
|
OperationID: operation.Spec.OperationID,
|
||||||
},
|
},
|
||||||
PVInfo: &PVInfo{
|
PVInfo: &PVInfo{
|
||||||
|
@ -570,12 +604,21 @@ func (v *VolumesInformation) generateVolumeInfoFromDataUpload() {
|
||||||
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
v.volumeInfos = append(v.volumeInfos, tmpVolumeInfos...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrievePvcPvInfo gets the PvcPvInfo from the PVMap.
|
type pvcPvMap struct {
|
||||||
// support retrieve info by PV's name, or by PVC's name
|
data map[string]pvcPvInfo
|
||||||
// and namespace.
|
}
|
||||||
func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS 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 pvName != "" {
|
||||||
if info, ok := v.pvMap[pvName]; ok {
|
if info, ok := m.data[pvName]; ok {
|
||||||
return &info
|
return &info
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -585,7 +628,7 @@ func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *p
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, info := range v.pvMap {
|
for _, info := range m.data {
|
||||||
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
|
if pvcNS == info.PVCNamespace && pvcName == info.PVCName {
|
||||||
return &info
|
return &info
|
||||||
}
|
}
|
||||||
|
@ -593,3 +636,226 @@ func (v *VolumesInformation) retrievePvcPvInfo(pvName, pvcName, pvcNS string) *p
|
||||||
|
|
||||||
return nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -43,7 +46,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
skippedPVName string
|
skippedPVName string
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap map[string]pvcPvInfo
|
||||||
expectedVolumeInfos []*VolumeInfo
|
expectedVolumeInfos []*BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Cannot find info for PV",
|
name: "Cannot find info for PV",
|
||||||
|
@ -63,7 +66,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Normal Skipped PV info",
|
name: "Normal Skipped PV info",
|
||||||
|
@ -96,7 +99,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -116,7 +119,7 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumesInfo := VolumesInformation{}
|
volumesInfo := BackupVolumesInformation{}
|
||||||
volumesInfo.Init()
|
volumesInfo.Init()
|
||||||
|
|
||||||
if tc.skippedPVName != "" {
|
if tc.skippedPVName != "" {
|
||||||
|
@ -127,7 +130,9 @@ func TestGenerateVolumeInfoForSkippedPV(t *testing.T) {
|
||||||
|
|
||||||
if tc.pvMap != nil {
|
if tc.pvMap != nil {
|
||||||
for k, v := range tc.pvMap {
|
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)
|
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
@ -143,7 +148,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
nativeSnapshot Snapshot
|
nativeSnapshot Snapshot
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap map[string]pvcPvInfo
|
||||||
expectedVolumeInfos []*VolumeInfo
|
expectedVolumeInfos []*BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Native snapshot's IPOS pointer is nil",
|
name: "Native snapshot's IPOS pointer is nil",
|
||||||
|
@ -153,7 +158,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
VolumeIOPS: nil,
|
VolumeIOPS: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cannot find info for the PV",
|
name: "Cannot find info for the PV",
|
||||||
|
@ -163,7 +168,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
VolumeIOPS: int64Ptr(100),
|
VolumeIOPS: int64Ptr(100),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cannot find PV info in pvMap",
|
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",
|
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Normal native snapshot",
|
name: "Normal native snapshot",
|
||||||
|
@ -223,7 +228,7 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
ProviderSnapshotID: "pvc-b31e3386-4bbb-4937-95d-7934cd62-b0a1-494b-95d7-0687440e8d0c",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -248,12 +253,14 @@ func TestGenerateVolumeInfoForVeleroNativeSnapshot(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumesInfo := VolumesInformation{}
|
volumesInfo := BackupVolumesInformation{}
|
||||||
volumesInfo.Init()
|
volumesInfo.Init()
|
||||||
volumesInfo.NativeSnapshots = append(volumesInfo.NativeSnapshots, &tc.nativeSnapshot)
|
volumesInfo.NativeSnapshots = append(volumesInfo.NativeSnapshots, &tc.nativeSnapshot)
|
||||||
if tc.pvMap != nil {
|
if tc.pvMap != nil {
|
||||||
for k, v := range tc.pvMap {
|
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)
|
volumesInfo.logger = logging.DefaultLogger(logrus.DebugLevel, logging.FormatJSON)
|
||||||
|
@ -274,7 +281,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
|
volumeSnapshotClass snapshotv1api.VolumeSnapshotClass
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap map[string]pvcPvInfo
|
||||||
operation *itemoperation.BackupOperation
|
operation *itemoperation.BackupOperation
|
||||||
expectedVolumeInfos []*VolumeInfo
|
expectedVolumeInfos []*BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "VS doesn't have VolumeSnapshotClass name",
|
name: "VS doesn't have VolumeSnapshotClass name",
|
||||||
|
@ -285,7 +292,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
Spec: snapshotv1api.VolumeSnapshotSpec{},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "VS doesn't have status",
|
name: "VS doesn't have status",
|
||||||
|
@ -298,7 +305,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
VolumeSnapshotClassName: stringPtr("testClass"),
|
VolumeSnapshotClassName: stringPtr("testClass"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "VS doesn't have PVC",
|
name: "VS doesn't have PVC",
|
||||||
|
@ -314,7 +321,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cannot find VSC for VS",
|
name: "Cannot find VSC for VS",
|
||||||
|
@ -333,10 +340,10 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
BoundVolumeSnapshotContentName: stringPtr("testContent"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cannot find VolumeInfo for PVC",
|
name: "Cannot find BackupVolumeInfo for PVC",
|
||||||
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
volumeSnapshot: snapshotv1api.VolumeSnapshot{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "testVS",
|
Name: "testVS",
|
||||||
|
@ -354,7 +361,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
volumeSnapshotClass: *builder.ForVolumeSnapshotClass("testClass").Driver("pd.csi.storage.gke.io").Result(),
|
||||||
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
volumeSnapshotContent: *builder.ForVolumeSnapshotContent("testContent").Status(&snapshotv1api.VolumeSnapshotContentStatus{SnapshotHandle: stringPtr("testSnapshotHandle")}).Result(),
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Normal VolumeSnapshot case",
|
name: "Normal VolumeSnapshot case",
|
||||||
|
@ -406,7 +413,7 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -434,12 +441,14 @@ func TestGenerateVolumeInfoForCSIVolumeSnapshot(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumesInfo := VolumesInformation{}
|
volumesInfo := BackupVolumesInformation{}
|
||||||
volumesInfo.Init()
|
volumesInfo.Init()
|
||||||
|
|
||||||
if tc.pvMap != nil {
|
if tc.pvMap != nil {
|
||||||
for k, v := range tc.pvMap {
|
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
|
pvb *velerov1api.PodVolumeBackup
|
||||||
pod *corev1api.Pod
|
pod *corev1api.Pod
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap map[string]pvcPvInfo
|
||||||
expectedVolumeInfos []*VolumeInfo
|
expectedVolumeInfos []*BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "cannot find PVB's pod, should fail",
|
name: "cannot find PVB's pod, should fail",
|
||||||
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
pvb: builder.ForPodVolumeBackup("velero", "testPVB").PodName("testPod").PodNamespace("velero").Result(),
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PVB doesn't have a related PVC",
|
name: "PVB doesn't have a related PVC",
|
||||||
|
@ -491,13 +500,13 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
).Result(),
|
).Result(),
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "",
|
PVCName: "",
|
||||||
PVCNamespace: "",
|
PVCNamespace: "",
|
||||||
PVName: "",
|
PVName: "",
|
||||||
BackupMethod: PodVolumeBackup,
|
BackupMethod: PodVolumeBackup,
|
||||||
PVBInfo: &PodVolumeBackupInfo{
|
PVBInfo: &PodVolumeInfo{
|
||||||
PodName: "testPod",
|
PodName: "testPod",
|
||||||
PodNamespace: "velero",
|
PodNamespace: "velero",
|
||||||
},
|
},
|
||||||
|
@ -525,7 +534,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
).Result(),
|
).Result(),
|
||||||
expectedVolumeInfos: []*VolumeInfo{},
|
expectedVolumeInfos: []*BackupVolumeInfo{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PVB's volume has a PVC",
|
name: "PVB's volume has a PVC",
|
||||||
|
@ -563,7 +572,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
).Result(),
|
).Result(),
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -571,7 +580,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
BackupMethod: PodVolumeBackup,
|
BackupMethod: PodVolumeBackup,
|
||||||
StartTimestamp: &now,
|
StartTimestamp: &now,
|
||||||
CompletionTimestamp: &now,
|
CompletionTimestamp: &now,
|
||||||
PVBInfo: &PodVolumeBackupInfo{
|
PVBInfo: &PodVolumeInfo{
|
||||||
PodName: "testPod",
|
PodName: "testPod",
|
||||||
PodNamespace: "velero",
|
PodNamespace: "velero",
|
||||||
},
|
},
|
||||||
|
@ -586,7 +595,7 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumesInfo := VolumesInformation{}
|
volumesInfo := BackupVolumesInformation{}
|
||||||
volumesInfo.Init()
|
volumesInfo.Init()
|
||||||
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
|
volumesInfo.crClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
|
|
||||||
|
@ -594,7 +603,9 @@ func TestGenerateVolumeInfoFromPVB(t *testing.T) {
|
||||||
|
|
||||||
if tc.pvMap != nil {
|
if tc.pvMap != nil {
|
||||||
for k, v := range tc.pvMap {
|
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 {
|
if tc.pod != nil {
|
||||||
|
@ -621,7 +632,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
dataUpload *velerov2alpha1.DataUpload
|
dataUpload *velerov2alpha1.DataUpload
|
||||||
operation *itemoperation.BackupOperation
|
operation *itemoperation.BackupOperation
|
||||||
pvMap map[string]pvcPvInfo
|
pvMap map[string]pvcPvInfo
|
||||||
expectedVolumeInfos []*VolumeInfo
|
expectedVolumeInfos []*BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Operation is not for PVC",
|
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",
|
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",
|
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",
|
name: "VolumeSnapshotClass cannot be found for operation",
|
||||||
|
@ -731,7 +742,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -802,7 +813,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*VolumeInfo{
|
expectedVolumeInfos: []*BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "testPVC",
|
PVCName: "testPVC",
|
||||||
PVCNamespace: "velero",
|
PVCNamespace: "velero",
|
||||||
|
@ -833,7 +844,7 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumesInfo := VolumesInformation{}
|
volumesInfo := BackupVolumesInformation{}
|
||||||
volumesInfo.Init()
|
volumesInfo.Init()
|
||||||
|
|
||||||
if tc.operation != nil {
|
if tc.operation != nil {
|
||||||
|
@ -842,7 +853,9 @@ func TestGenerateVolumeInfoFromDataUpload(t *testing.T) {
|
||||||
|
|
||||||
if tc.pvMap != nil {
|
if tc.pvMap != nil {
|
||||||
for k, v := range tc.pvMap {
|
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 {
|
func stringPtr(str string) *string {
|
||||||
return &str
|
return &str
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ const (
|
||||||
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
|
||||||
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
|
||||||
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
|
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
|
||||||
|
DownloadTargetKindRestoreVolumeInfo DownloadTargetKind = "RestoreVolumeInfo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DownloadTarget is the specification for what kind of file to download, and the name of the
|
// 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(
|
func (kb *kubernetesBackupper) getVolumeInfos(
|
||||||
backup velerov1api.Backup,
|
backup velerov1api.Backup,
|
||||||
log logrus.FieldLogger,
|
log logrus.FieldLogger,
|
||||||
) (persistence.BackupStore, []*volume.VolumeInfo, error) {
|
) (persistence.BackupStore, []*volume.BackupVolumeInfo, error) {
|
||||||
location := &velerov1api.BackupStorageLocation{}
|
location := &velerov1api.BackupStorageLocation{}
|
||||||
if err := kb.kbClient.Get(context.Background(), kbclient.ObjectKey{
|
if err := kb.kbClient.Get(context.Background(), kbclient.ObjectKey{
|
||||||
Namespace: backup.Namespace,
|
Namespace: backup.Namespace,
|
||||||
|
@ -825,7 +825,7 @@ func (kb *kubernetesBackupper) getVolumeInfos(
|
||||||
|
|
||||||
// updateVolumeInfos update the VolumeInfos according to the AsyncOperations
|
// updateVolumeInfos update the VolumeInfos according to the AsyncOperations
|
||||||
func updateVolumeInfos(
|
func updateVolumeInfos(
|
||||||
volumeInfos []*volume.VolumeInfo,
|
volumeInfos []*volume.BackupVolumeInfo,
|
||||||
unstructuredItems []unstructured.Unstructured,
|
unstructuredItems []unstructured.Unstructured,
|
||||||
operations []*itemoperation.BackupOperation,
|
operations []*itemoperation.BackupOperation,
|
||||||
log logrus.FieldLogger,
|
log logrus.FieldLogger,
|
||||||
|
@ -874,7 +874,7 @@ func updateVolumeInfos(
|
||||||
|
|
||||||
func putVolumeInfos(
|
func putVolumeInfos(
|
||||||
backupName string,
|
backupName string,
|
||||||
volumeInfos []*volume.VolumeInfo,
|
volumeInfos []*volume.BackupVolumeInfo,
|
||||||
backupStore persistence.BackupStore,
|
backupStore persistence.BackupStore,
|
||||||
) error {
|
) error {
|
||||||
backupVolumeInfoBuf := new(bytes.Buffer)
|
backupVolumeInfoBuf := new(bytes.Buffer)
|
||||||
|
|
|
@ -4454,7 +4454,7 @@ func TestGetVolumeInfos(t *testing.T) {
|
||||||
backupStore := new(persistencemocks.BackupStore)
|
backupStore := new(persistencemocks.BackupStore)
|
||||||
h.backupper.pluginManager = func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager }
|
h.backupper.pluginManager = func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager }
|
||||||
h.backupper.backupStoreGetter = NewFakeSingleObjectBackupStoreGetter(backupStore)
|
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()
|
pluginManager.On("CleanupClients").Return()
|
||||||
|
|
||||||
backup := builder.ForBackup("velero", "backup-01").StorageLocation("default").Result()
|
backup := builder.ForBackup("velero", "backup-01").StorageLocation("default").Result()
|
||||||
|
@ -4474,8 +4474,8 @@ func TestUpdateVolumeInfos(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
operations []*itemoperation.BackupOperation
|
operations []*itemoperation.BackupOperation
|
||||||
dataUpload *velerov2alpha1.DataUpload
|
dataUpload *velerov2alpha1.DataUpload
|
||||||
volumeInfos []*volume.VolumeInfo
|
volumeInfos []*volume.BackupVolumeInfo
|
||||||
expectedVolumeInfos []*volume.VolumeInfo
|
expectedVolumeInfos []*volume.BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "CSISnapshot VolumeInfo update",
|
name: "CSISnapshot VolumeInfo update",
|
||||||
|
@ -4489,7 +4489,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
volumeInfos: []*volume.VolumeInfo{
|
volumeInfos: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
CompletionTimestamp: &metav1.Time{},
|
CompletionTimestamp: &metav1.Time{},
|
||||||
|
@ -4498,7 +4498,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*volume.VolumeInfo{
|
expectedVolumeInfos: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
CompletionTimestamp: &now,
|
CompletionTimestamp: &now,
|
||||||
|
@ -4519,7 +4519,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
|
||||||
SourceNamespace("ns-1").
|
SourceNamespace("ns-1").
|
||||||
SourcePVC("pvc-1").
|
SourcePVC("pvc-1").
|
||||||
Result(),
|
Result(),
|
||||||
volumeInfos: []*volume.VolumeInfo{
|
volumeInfos: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "pvc-1",
|
PVCName: "pvc-1",
|
||||||
PVCNamespace: "ns-1",
|
PVCNamespace: "ns-1",
|
||||||
|
@ -4529,7 +4529,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedVolumeInfos: []*volume.VolumeInfo{
|
expectedVolumeInfos: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "pvc-1",
|
PVCName: "pvc-1",
|
||||||
PVCNamespace: "ns-1",
|
PVCNamespace: "ns-1",
|
||||||
|
@ -4572,7 +4572,7 @@ func TestPutVolumeInfos(t *testing.T) {
|
||||||
|
|
||||||
backupStore.On("PutBackupVolumeInfos", mock.Anything, mock.Anything).Return(nil)
|
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 {
|
type fakeSingleObjectBackupStoreGetter struct {
|
||||||
|
|
|
@ -52,11 +52,11 @@ type Request struct {
|
||||||
itemOperationsList *[]*itemoperation.BackupOperation
|
itemOperationsList *[]*itemoperation.BackupOperation
|
||||||
ResPolicies *resourcepolicies.Policies
|
ResPolicies *resourcepolicies.Policies
|
||||||
SkippedPVTracker *skipPVTracker
|
SkippedPVTracker *skipPVTracker
|
||||||
VolumesInformation volume.VolumesInformation
|
VolumesInformation volume.BackupVolumesInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
// VolumesInformation contains the information needs by generating
|
// BackupVolumesInformation contains the information needs by generating
|
||||||
// the backup VolumeInfo array.
|
// the backup BackupVolumeInfo array.
|
||||||
|
|
||||||
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
|
||||||
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
|
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
|
||||||
|
|
|
@ -75,6 +75,12 @@ func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
|
||||||
return v
|
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.
|
// RestoreSize set the built VolumeSnapshot's status.RestoreSize.
|
||||||
func (v *VolumeSnapshotBuilder) RestoreSize(size string) *VolumeSnapshotBuilder {
|
func (v *VolumeSnapshotBuilder) RestoreSize(size string) *VolumeSnapshotBuilder {
|
||||||
resourceSize := resource.MustParse(size)
|
resourceSize := resource.MustParse(size)
|
||||||
|
|
|
@ -991,6 +991,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||||
s.config.formatFlag.Parse(),
|
s.config.formatFlag.Parse(),
|
||||||
s.config.defaultItemOperationTimeout,
|
s.config.defaultItemOperationTimeout,
|
||||||
s.config.disableInformerCache,
|
s.config.disableInformerCache,
|
||||||
|
s.crClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = r.SetupWithManager(s.mgr); err != nil {
|
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:")
|
d.Println("Backup Volumes:")
|
||||||
|
|
||||||
nativeSnapshots := []*volume.VolumeInfo{}
|
nativeSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
csiSnapshots := []*volume.VolumeInfo{}
|
csiSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
legacyInfoSource := false
|
legacyInfoSource := false
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
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)
|
d.Printf("\t<error getting backup volume info: %v>\n", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
var volumeInfos []volume.VolumeInfo
|
var volumeInfos []volume.BackupVolumeInfo
|
||||||
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
|
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
|
||||||
d.Printf("\t<error reading backup volume info: %v>\n", err)
|
d.Printf("\t<error reading backup volume info: %v>\n", err)
|
||||||
return
|
return
|
||||||
|
@ -488,9 +488,9 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
|
||||||
describePodVolumeBackups(d, details, podVolumeBackupCRs)
|
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
|
status := backup.Status
|
||||||
nativeSnapshots := []*volume.VolumeInfo{}
|
nativeSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
|
|
||||||
if status.VolumeSnapshotsAttempted == 0 {
|
if status.VolumeSnapshotsAttempted == 0 {
|
||||||
return nativeSnapshots, nil
|
return nativeSnapshots, nil
|
||||||
|
@ -507,7 +507,7 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, snap := range snapshots {
|
for _, snap := range snapshots {
|
||||||
volumeInfo := volume.VolumeInfo{
|
volumeInfo := volume.BackupVolumeInfo{
|
||||||
PVName: snap.Spec.PersistentVolumeName,
|
PVName: snap.Spec.PersistentVolumeName,
|
||||||
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
|
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
|
||||||
SnapshotHandle: snap.Status.ProviderSnapshotID,
|
SnapshotHandle: snap.Status.ProviderSnapshotID,
|
||||||
|
@ -526,9 +526,9 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
|
||||||
return nativeSnapshots, nil
|
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
|
status := backup.Status
|
||||||
csiSnapshots := []*volume.VolumeInfo{}
|
csiSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
|
|
||||||
if status.CSIVolumeSnapshotsAttempted == 0 {
|
if status.CSIVolumeSnapshotsAttempted == 0 {
|
||||||
return csiSnapshots, nil
|
return csiSnapshots, nil
|
||||||
|
@ -557,7 +557,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vsc := range vscList {
|
for _, vsc := range vscList {
|
||||||
volInfo := volume.VolumeInfo{
|
volInfo := volume.BackupVolumeInfo{
|
||||||
PreserveLocalSnapshot: true,
|
PreserveLocalSnapshot: true,
|
||||||
CSISnapshotInfo: &volume.CSISnapshotInfo{
|
CSISnapshotInfo: &volume.CSISnapshotInfo{
|
||||||
VSCName: vsc.Name,
|
VSCName: vsc.Name,
|
||||||
|
@ -598,7 +598,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
|
||||||
return csiSnapshots, nil
|
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 {
|
if len(infos) == 0 {
|
||||||
d.Printf("\tVelero-Native Snapshots: <none included>\n")
|
d.Printf("\tVelero-Native Snapshots: <none included>\n")
|
||||||
return
|
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 {
|
if details {
|
||||||
d.Printf("\t\t%s:\n", info.PVName)
|
d.Printf("\t\t%s:\n", info.PVName)
|
||||||
d.Printf("\t\t\tSnapshot ID:\t%s\n", info.NativeSnapshotInfo.SnapshotHandle)
|
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 len(infos) == 0 {
|
||||||
if legacyInfoSource {
|
if legacyInfoSource {
|
||||||
d.Printf("\tCSI Snapshots: <none included or not detectable>\n")
|
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))
|
d.Printf("\t\t%s:\n", fmt.Sprintf("%s/%s", info.PVCNamespace, info.PVCName))
|
||||||
|
|
||||||
describeLocalSnapshot(d, details, info)
|
describeLocalSnapshot(d, details, info)
|
||||||
describeDataMovement(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 {
|
if !info.PreserveLocalSnapshot {
|
||||||
return
|
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 {
|
if !info.SnapshotDataMoved {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,13 +325,13 @@ OrderedResources:
|
||||||
func TestDescribeNativeSnapshots(t *testing.T) {
|
func TestDescribeNativeSnapshots(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
inputDetails bool
|
inputDetails bool
|
||||||
expect string
|
expect string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no details",
|
name: "no details",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.NativeSnapshot,
|
BackupMethod: volume.NativeSnapshot,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
|
@ -349,7 +349,7 @@ func TestDescribeNativeSnapshots(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details",
|
name: "details",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.NativeSnapshot,
|
BackupMethod: volume.NativeSnapshot,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
|
@ -390,27 +390,27 @@ func TestDescribeNativeSnapshots(t *testing.T) {
|
||||||
func TestCSISnapshots(t *testing.T) {
|
func TestCSISnapshots(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
inputDetails bool
|
inputDetails bool
|
||||||
expect string
|
expect string
|
||||||
legacyInfoSource bool
|
legacyInfoSource bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty info, not legacy",
|
name: "empty info, not legacy",
|
||||||
volumeInfo: []*volume.VolumeInfo{},
|
volumeInfo: []*volume.BackupVolumeInfo{},
|
||||||
expect: ` CSI Snapshots: <none included>
|
expect: ` CSI Snapshots: <none included>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty info, legacy",
|
name: "empty info, legacy",
|
||||||
volumeInfo: []*volume.VolumeInfo{},
|
volumeInfo: []*volume.BackupVolumeInfo{},
|
||||||
legacyInfoSource: true,
|
legacyInfoSource: true,
|
||||||
expect: ` CSI Snapshots: <none included or not detectable>
|
expect: ` CSI Snapshots: <none included or not detectable>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no details, local snapshot",
|
name: "no details, local snapshot",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-1",
|
PVCNamespace: "pvc-ns-1",
|
||||||
|
@ -432,7 +432,7 @@ func TestCSISnapshots(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, local snapshot",
|
name: "details, local snapshot",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-2",
|
PVCNamespace: "pvc-ns-2",
|
||||||
|
@ -460,7 +460,7 @@ func TestCSISnapshots(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no details, data movement",
|
name: "no details, data movement",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-3",
|
PVCNamespace: "pvc-ns-3",
|
||||||
|
@ -481,7 +481,7 @@ func TestCSISnapshots(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, data movement",
|
name: "details, data movement",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-4",
|
PVCNamespace: "pvc-ns-4",
|
||||||
|
@ -506,7 +506,7 @@ func TestCSISnapshots(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, data movement, data mover is empty",
|
name: "details, data movement, data mover is empty",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-5",
|
PVCNamespace: "pvc-ns-5",
|
||||||
|
|
|
@ -308,8 +308,8 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
|
||||||
|
|
||||||
backupVolumes := make(map[string]interface{})
|
backupVolumes := make(map[string]interface{})
|
||||||
|
|
||||||
nativeSnapshots := []*volume.VolumeInfo{}
|
nativeSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
csiSnapshots := []*volume.VolumeInfo{}
|
csiSnapshots := []*volume.BackupVolumeInfo{}
|
||||||
legacyInfoSource := false
|
legacyInfoSource := false
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
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)
|
backupVolumes["errorGetBackupVolumeInfo"] = fmt.Sprintf("error getting backup volume info: %v", err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
var volumeInfos []volume.VolumeInfo
|
var volumeInfos []volume.BackupVolumeInfo
|
||||||
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
|
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
|
||||||
backupVolumes["errorReadBackupVolumeInfo"] = fmt.Sprintf("error reading backup volume info: %v", err)
|
backupVolumes["errorReadBackupVolumeInfo"] = fmt.Sprintf("error reading backup volume info: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -357,7 +357,7 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
|
||||||
backupStatusInfo["backupVolumes"] = backupVolumes
|
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 {
|
if len(infos) == 0 {
|
||||||
backupVolumes["nativeSnapshots"] = "<none included>"
|
backupVolumes["nativeSnapshots"] = "<none included>"
|
||||||
return
|
return
|
||||||
|
@ -370,7 +370,7 @@ func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backu
|
||||||
backupVolumes["nativeSnapshots"] = snapshotDetails
|
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 {
|
if details {
|
||||||
snapshotInfo := make(map[string]string)
|
snapshotInfo := make(map[string]string)
|
||||||
snapshotInfo["snapshotID"] = info.NativeSnapshotInfo.SnapshotHandle
|
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 len(infos) == 0 {
|
||||||
if legacyInfoSource {
|
if legacyInfoSource {
|
||||||
backupVolumes["csiSnapshots"] = "<none included or not detectable>"
|
backupVolumes["csiSnapshots"] = "<none included or not detectable>"
|
||||||
|
@ -401,7 +401,7 @@ func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVo
|
||||||
backupVolumes["csiSnapshots"] = snapshotDetails
|
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{})
|
snapshotDetail := make(map[string]interface{})
|
||||||
|
|
||||||
describeLocalSnapshotInSF(details, info, snapshotDetail)
|
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.
|
// 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 {
|
if !info.PreserveLocalSnapshot {
|
||||||
return
|
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 {
|
if !info.SnapshotDataMoved {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,13 +284,13 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) {
|
||||||
func TestDescribeNativeSnapshotsInSF(t *testing.T) {
|
func TestDescribeNativeSnapshotsInSF(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
inputDetails bool
|
inputDetails bool
|
||||||
expect map[string]interface{}
|
expect map[string]interface{}
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no details",
|
name: "no details",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.NativeSnapshot,
|
BackupMethod: volume.NativeSnapshot,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
|
@ -310,7 +310,7 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details",
|
name: "details",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.NativeSnapshot,
|
BackupMethod: volume.NativeSnapshot,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
|
@ -348,21 +348,21 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
|
||||||
func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
inputDetails bool
|
inputDetails bool
|
||||||
expect map[string]interface{}
|
expect map[string]interface{}
|
||||||
legacyInfoSource bool
|
legacyInfoSource bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty info, not legacy",
|
name: "empty info, not legacy",
|
||||||
volumeInfo: []*volume.VolumeInfo{},
|
volumeInfo: []*volume.BackupVolumeInfo{},
|
||||||
expect: map[string]interface{}{
|
expect: map[string]interface{}{
|
||||||
"csiSnapshots": "<none included>",
|
"csiSnapshots": "<none included>",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty info, legacy",
|
name: "empty info, legacy",
|
||||||
volumeInfo: []*volume.VolumeInfo{},
|
volumeInfo: []*volume.BackupVolumeInfo{},
|
||||||
legacyInfoSource: true,
|
legacyInfoSource: true,
|
||||||
expect: map[string]interface{}{
|
expect: map[string]interface{}{
|
||||||
"csiSnapshots": "<none included or not detectable>",
|
"csiSnapshots": "<none included or not detectable>",
|
||||||
|
@ -370,7 +370,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no details, local snapshot",
|
name: "no details, local snapshot",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-1",
|
PVCNamespace: "pvc-ns-1",
|
||||||
|
@ -395,7 +395,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, local snapshot",
|
name: "details, local snapshot",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-2",
|
PVCNamespace: "pvc-ns-2",
|
||||||
|
@ -427,7 +427,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no details, data movement",
|
name: "no details, data movement",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-3",
|
PVCNamespace: "pvc-ns-3",
|
||||||
|
@ -451,7 +451,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, data movement",
|
name: "details, data movement",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-4",
|
PVCNamespace: "pvc-ns-4",
|
||||||
|
@ -480,7 +480,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "details, data movement, data mover is empty",
|
name: "details, data movement, data mover is empty",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
PVCNamespace: "pvc-ns-4",
|
PVCNamespace: "pvc-ns-4",
|
||||||
|
|
|
@ -107,6 +107,7 @@ type restoreReconciler struct {
|
||||||
|
|
||||||
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
|
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
|
||||||
backupStoreGetter persistence.ObjectBackupStoreGetter
|
backupStoreGetter persistence.ObjectBackupStoreGetter
|
||||||
|
globalCrClient client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type backupInfo struct {
|
type backupInfo struct {
|
||||||
|
@ -127,6 +128,7 @@ func NewRestoreReconciler(
|
||||||
logFormat logging.Format,
|
logFormat logging.Format,
|
||||||
defaultItemOperationTimeout time.Duration,
|
defaultItemOperationTimeout time.Duration,
|
||||||
disableInformerCache bool,
|
disableInformerCache bool,
|
||||||
|
globalCrClient client.Client,
|
||||||
) *restoreReconciler {
|
) *restoreReconciler {
|
||||||
r := &restoreReconciler{
|
r := &restoreReconciler{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -145,6 +147,8 @@ func NewRestoreReconciler(
|
||||||
// replaced with fakes for testing.
|
// replaced with fakes for testing.
|
||||||
newPluginManager: newPluginManager,
|
newPluginManager: newPluginManager,
|
||||||
backupStoreGetter: backupStoreGetter,
|
backupStoreGetter: backupStoreGetter,
|
||||||
|
|
||||||
|
globalCrClient: globalCrClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the periodical backup and restore metrics computing logic from controllers to here.
|
// 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")
|
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)
|
volumeInfos, err := backupStore.GetBackupVolumeInfos(restore.Spec.BackupName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restoreLog.WithError(err).Errorf("fail to get VolumeInfos metadata file for backup %s", restore.Spec.BackupName)
|
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{
|
restoreReq := &pkgrestore.Request{
|
||||||
Log: restoreLog,
|
Log: restoreLog,
|
||||||
Restore: restore,
|
Restore: restore,
|
||||||
Backup: info.backup,
|
Backup: info.backup,
|
||||||
PodVolumeBackups: podVolumeBackups,
|
PodVolumeBackups: podVolumeBackups,
|
||||||
VolumeSnapshots: volumeSnapshots,
|
VolumeSnapshots: volumeSnapshots,
|
||||||
BackupReader: backupFile,
|
BackupReader: backupFile,
|
||||||
ResourceModifiers: resourceModifiers,
|
ResourceModifiers: resourceModifiers,
|
||||||
DisableInformerCache: r.disableInformerCache,
|
DisableInformerCache: r.disableInformerCache,
|
||||||
CSIVolumeSnapshots: csiVolumeSnapshots,
|
CSIVolumeSnapshots: csiVolumeSnapshots,
|
||||||
VolumeInfoMap: backupVolumeInfoMap,
|
BackupVolumeInfoMap: backupVolumeInfoMap,
|
||||||
|
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(restore, restoreLog, r.globalCrClient),
|
||||||
}
|
}
|
||||||
restoreWarnings, restoreErrors := r.restorer.RestoreWithResolvers(restoreReq, actionsResolver, pluginManager)
|
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")
|
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 restore.Status.Errors > 0 {
|
||||||
if inProgressOperations {
|
if inProgressOperations {
|
||||||
r.logger.Debug("Restore WaitingForPluginOperationsPartiallyFailed")
|
r.logger.Debug("Restore WaitingForPluginOperationsPartiallyFailed")
|
||||||
|
@ -776,6 +786,22 @@ func putOperationsForRestore(restore *api.Restore, operations []*itemoperation.R
|
||||||
return nil
|
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) {
|
func downloadToTempFile(backupName string, backupStore persistence.BackupStore, logger logrus.FieldLogger) (*os.File, error) {
|
||||||
readCloser, err := backupStore.GetBackupContents(backupName)
|
readCloser, err := backupStore.GetBackupContents(backupName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -91,11 +91,12 @@ func TestFetchBackupInfo(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
restorer = &fakeRestorer{kbClient: fakeClient}
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
logger = velerotest.NewLogger()
|
restorer = &fakeRestorer{kbClient: fakeClient}
|
||||||
pluginManager = &pluginmocks.Manager{}
|
logger = velerotest.NewLogger()
|
||||||
backupStore = &persistencemocks.BackupStore{}
|
pluginManager = &pluginmocks.Manager{}
|
||||||
|
backupStore = &persistencemocks.BackupStore{}
|
||||||
)
|
)
|
||||||
|
|
||||||
defer restorer.AssertExpectations(t)
|
defer restorer.AssertExpectations(t)
|
||||||
|
@ -114,6 +115,7 @@ func TestFetchBackupInfo(t *testing.T) {
|
||||||
formatFlag,
|
formatFlag,
|
||||||
60*time.Minute,
|
60*time.Minute,
|
||||||
false,
|
false,
|
||||||
|
fakeGlobalClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
if test.backupStoreError == nil {
|
if test.backupStoreError == nil {
|
||||||
|
@ -170,9 +172,10 @@ func TestProcessQueueItemSkips(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
restorer = &fakeRestorer{kbClient: fakeClient}
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
logger = velerotest.NewLogger()
|
restorer = &fakeRestorer{kbClient: fakeClient}
|
||||||
|
logger = velerotest.NewLogger()
|
||||||
)
|
)
|
||||||
|
|
||||||
if test.restore != nil {
|
if test.restore != nil {
|
||||||
|
@ -192,6 +195,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
|
||||||
formatFlag,
|
formatFlag,
|
||||||
60*time.Minute,
|
60*time.Minute,
|
||||||
false,
|
false,
|
||||||
|
fakeGlobalClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{
|
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{
|
||||||
|
@ -432,11 +436,12 @@ func TestRestoreReconcile(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
|
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
|
||||||
restorer = &fakeRestorer{kbClient: fakeClient}
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
logger = velerotest.NewLogger()
|
restorer = &fakeRestorer{kbClient: fakeClient}
|
||||||
pluginManager = &pluginmocks.Manager{}
|
logger = velerotest.NewLogger()
|
||||||
backupStore = &persistencemocks.BackupStore{}
|
pluginManager = &pluginmocks.Manager{}
|
||||||
|
backupStore = &persistencemocks.BackupStore{}
|
||||||
)
|
)
|
||||||
|
|
||||||
defer restorer.AssertExpectations(t)
|
defer restorer.AssertExpectations(t)
|
||||||
|
@ -459,6 +464,7 @@ func TestRestoreReconcile(t *testing.T) {
|
||||||
formatFlag,
|
formatFlag,
|
||||||
60*time.Minute,
|
60*time.Minute,
|
||||||
false,
|
false,
|
||||||
|
fakeGlobalClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
r.clock = clocktesting.NewFakeClock(now)
|
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("PutRestoreResults", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
|
||||||
backupStore.On("PutRestoredResourceList", 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("PutRestoreItemOperations", mock.Anything, mock.Anything).Return(nil)
|
||||||
|
backupStore.On("PutRestoreVolumeInfo", test.restore.Name, mock.Anything).Return(nil)
|
||||||
if test.emptyVolumeInfo == true {
|
if test.emptyVolumeInfo == true {
|
||||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
|
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
|
||||||
} else {
|
} else {
|
||||||
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.VolumeInfo{}, nil)
|
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.BackupVolumeInfo{}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeSnapshots := []*volume.Snapshot{
|
volumeSnapshots := []*volume.Snapshot{
|
||||||
|
@ -626,10 +633,11 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
|
||||||
formatFlag := logging.FormatText
|
formatFlag := logging.FormatText
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger = velerotest.NewLogger()
|
logger = velerotest.NewLogger()
|
||||||
pluginManager = &pluginmocks.Manager{}
|
pluginManager = &pluginmocks.Manager{}
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
backupStore = &persistencemocks.BackupStore{}
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
|
backupStore = &persistencemocks.BackupStore{}
|
||||||
)
|
)
|
||||||
|
|
||||||
r := NewRestoreReconciler(
|
r := NewRestoreReconciler(
|
||||||
|
@ -645,6 +653,7 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
|
||||||
formatFlag,
|
formatFlag,
|
||||||
60*time.Minute,
|
60*time.Minute,
|
||||||
false,
|
false,
|
||||||
|
fakeGlobalClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
restore := &velerov1api.Restore{
|
restore := &velerov1api.Restore{
|
||||||
|
@ -719,10 +728,11 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
|
||||||
formatFlag := logging.FormatText
|
formatFlag := logging.FormatText
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger = velerotest.NewLogger()
|
logger = velerotest.NewLogger()
|
||||||
pluginManager = &pluginmocks.Manager{}
|
pluginManager = &pluginmocks.Manager{}
|
||||||
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
backupStore = &persistencemocks.BackupStore{}
|
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
|
backupStore = &persistencemocks.BackupStore{}
|
||||||
)
|
)
|
||||||
|
|
||||||
r := NewRestoreReconciler(
|
r := NewRestoreReconciler(
|
||||||
|
@ -738,6 +748,7 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
|
||||||
formatFlag,
|
formatFlag,
|
||||||
60*time.Minute,
|
60*time.Minute,
|
||||||
false,
|
false,
|
||||||
|
fakeGlobalClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
restore := &velerov1api.Restore{
|
restore := &velerov1api.Restore{
|
||||||
|
|
|
@ -19,7 +19,6 @@ package controller
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,7 +38,6 @@ import (
|
||||||
"github.com/vmware-tanzu/velero/pkg/metrics"
|
"github.com/vmware-tanzu/velero/pkg/metrics"
|
||||||
"github.com/vmware-tanzu/velero/pkg/persistence"
|
"github.com/vmware-tanzu/velero/pkg/persistence"
|
||||||
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
|
||||||
"github.com/vmware-tanzu/velero/pkg/restore"
|
|
||||||
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
|
||||||
"github.com/vmware-tanzu/velero/pkg/util/results"
|
"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")
|
return ctrl.Result{}, errors.Wrap(err, "error getting restoredResourceList")
|
||||||
}
|
}
|
||||||
|
|
||||||
restoredPVCList := getRestoredPVCFromRestoredResourceList(restoredResourceList)
|
restoredPVCList := volume.RestoredPVCFromRestoredResourceList(restoredResourceList)
|
||||||
|
|
||||||
finalizerCtx := &finalizerContext{
|
finalizerCtx := &finalizerContext{
|
||||||
logger: log,
|
logger: log,
|
||||||
|
@ -238,7 +236,7 @@ type finalizerContext struct {
|
||||||
logger logrus.FieldLogger
|
logger logrus.FieldLogger
|
||||||
restore *velerov1api.Restore
|
restore *velerov1api.Restore
|
||||||
crClient client.Client
|
crClient client.Client
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
restoredPVCList map[string]struct{}
|
restoredPVCList map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +275,7 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
pvWaitGroup.Add(1)
|
pvWaitGroup.Add(1)
|
||||||
go func(volInfo volume.VolumeInfo, restoredNamespace string) {
|
go func(volInfo volume.BackupVolumeInfo, restoredNamespace string) {
|
||||||
defer pvWaitGroup.Done()
|
defer pvWaitGroup.Done()
|
||||||
|
|
||||||
semaphore <- struct{}{}
|
semaphore <- struct{}{}
|
||||||
|
@ -358,23 +356,6 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
|
||||||
return errs
|
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 {
|
func needPatch(newPV *v1.PersistentVolume, pvInfo *volume.PVInfo) bool {
|
||||||
if newPV.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimPolicy(pvInfo.ReclaimPolicy) {
|
if newPV.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimPolicy(pvInfo.ReclaimPolicy) {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -210,7 +210,7 @@ func TestUpdateResult(t *testing.T) {
|
||||||
func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
restoredPVCNames map[string]struct{}
|
restoredPVCNames map[string]struct{}
|
||||||
restore *velerov1api.Restore
|
restore *velerov1api.Restore
|
||||||
restoredPVC []*corev1api.PersistentVolumeClaim
|
restoredPVC []*corev1api.PersistentVolumeClaim
|
||||||
|
@ -220,21 +220,21 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no applicable volumeInfo",
|
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(),
|
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
|
||||||
expectedPatch: nil,
|
expectedPatch: nil,
|
||||||
expectedErrNum: 0,
|
expectedErrNum: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no restored PVC",
|
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(),
|
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
|
||||||
expectedPatch: nil,
|
expectedPatch: nil,
|
||||||
expectedErrNum: 0,
|
expectedErrNum: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no applicable pv patch",
|
name: "no applicable pv patch",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
PVName: "pv1",
|
||||||
|
@ -256,7 +256,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an applicable pv patch",
|
name: "an applicable pv patch",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
PVName: "pv1",
|
||||||
|
@ -281,7 +281,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "a mapped namespace restore",
|
name: "a mapped namespace restore",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
PVName: "pv1",
|
||||||
|
@ -306,7 +306,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two applicable pv patches",
|
name: "two applicable pv patches",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
PVName: "pv1",
|
||||||
|
@ -354,7 +354,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "an applicable pv patch with bound error",
|
name: "an applicable pv patch with bound error",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
PVName: "pv1",
|
||||||
|
@ -375,7 +375,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "two applicable pv patches with an error",
|
name: "two applicable pv patches with an error",
|
||||||
volumeInfo: []*volume.VolumeInfo{{
|
volumeInfo: []*volume.BackupVolumeInfo{{
|
||||||
BackupMethod: "PodVolumeBackup",
|
BackupMethod: "PodVolumeBackup",
|
||||||
PVCName: "pvc1",
|
PVCName: "pvc1",
|
||||||
PVName: "pv1",
|
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)),
|
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
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBackupVolumeInfos provides a mock function with given fields: name
|
// GetRestoreItemOperations provides a mock function with given fields: name
|
||||||
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
|
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
|
||||||
ret := _m.Called(name)
|
ret := _m.Called(name)
|
||||||
|
|
||||||
var r0 []*volume.VolumeInfo
|
var r0 []*volume.BackupVolumeInfo
|
||||||
if rf, ok := ret.Get(0).(func(string) []*volume.VolumeInfo); ok {
|
if rf, ok := ret.Get(0).(func(string) []*volume.BackupVolumeInfo); ok {
|
||||||
r0 = rf(name)
|
r0 = rf(name)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
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
|
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 {
|
type mockConstructorTestingTNewBackupStore interface {
|
||||||
mock.TestingT
|
mock.TestingT
|
||||||
Cleanup(func())
|
Cleanup(func())
|
||||||
|
|
|
@ -74,8 +74,8 @@ type BackupStore interface {
|
||||||
GetCSIVolumeSnapshots(name string) ([]*snapshotv1api.VolumeSnapshot, error)
|
GetCSIVolumeSnapshots(name string) ([]*snapshotv1api.VolumeSnapshot, error)
|
||||||
GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1api.VolumeSnapshotContent, error)
|
GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1api.VolumeSnapshotContent, error)
|
||||||
GetCSIVolumeSnapshotClasses(name string) ([]*snapshotv1api.VolumeSnapshotClass, error)
|
GetCSIVolumeSnapshotClasses(name string) ([]*snapshotv1api.VolumeSnapshotClass, error)
|
||||||
GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error)
|
|
||||||
PutBackupVolumeInfos(name string, volumeInfo io.Reader) error
|
PutBackupVolumeInfos(name string, volumeInfo io.Reader) error
|
||||||
|
GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error)
|
||||||
GetRestoreResults(name string) (map[string]results.Result, error)
|
GetRestoreResults(name string) (map[string]results.Result, error)
|
||||||
|
|
||||||
// BackupExists checks if the backup metadata file exists in object storage.
|
// 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
|
PutRestoredResourceList(restore string, results io.Reader) error
|
||||||
PutRestoreItemOperations(restore string, restoreItemOperations io.Reader) error
|
PutRestoreItemOperations(restore string, restoreItemOperations io.Reader) error
|
||||||
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
|
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
|
||||||
|
PutRestoreVolumeInfo(restore string, volumeInfo io.Reader) error
|
||||||
DeleteRestore(name string) error
|
DeleteRestore(name string) error
|
||||||
GetRestoredResourceList(name string) (map[string][]string, error)
|
GetRestoredResourceList(name string) (map[string][]string, error)
|
||||||
|
|
||||||
|
@ -498,8 +499,8 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod
|
||||||
return podVolumeBackups, nil
|
return podVolumeBackups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
|
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
|
||||||
volumeInfos := make([]*volume.VolumeInfo, 0)
|
volumeInfos := make([]*volume.BackupVolumeInfo, 0)
|
||||||
|
|
||||||
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
|
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
|
||||||
if err != nil {
|
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)
|
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 {
|
func (s *objectBackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error {
|
||||||
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(backup), backupItemOperations)
|
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)
|
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL)
|
||||||
case velerov1api.DownloadTargetKindBackupVolumeInfos:
|
case velerov1api.DownloadTargetKindBackupVolumeInfos:
|
||||||
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeInfoKey(target.Name), DownloadURLTTL)
|
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:
|
default:
|
||||||
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
|
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 {
|
func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string {
|
||||||
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfo.json.gz", backup))
|
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) {
|
func TestGetBackupVolumeInfos(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
volumeInfo []*volume.VolumeInfo
|
volumeInfo []*volume.BackupVolumeInfo
|
||||||
volumeInfoStr string
|
volumeInfoStr string
|
||||||
expectedErr string
|
expectedErr string
|
||||||
expectedResult []*volume.VolumeInfo
|
expectedResult []*volume.BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "No VolumeInfos, expect no error.",
|
name: "No VolumeInfos, expect no error.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid VolumeInfo, should pass.",
|
name: "Valid BackupVolumeInfo, should pass.",
|
||||||
volumeInfo: []*volume.VolumeInfo{
|
volumeInfo: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "pvcName",
|
PVCName: "pvcName",
|
||||||
PVName: "pvName",
|
PVName: "pvName",
|
||||||
|
@ -1086,7 +1086,7 @@ func TestGetBackupVolumeInfos(t *testing.T) {
|
||||||
SnapshotDataMoved: false,
|
SnapshotDataMoved: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedResult: []*volume.VolumeInfo{
|
expectedResult: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "pvcName",
|
PVCName: "pvcName",
|
||||||
PVName: "pvName",
|
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"}]`,
|
volumeInfoStr: `[{"abc": "123", "def": "456", "pvcName": "pvcName"}]`,
|
||||||
expectedResult: []*volume.VolumeInfo{
|
expectedResult: []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "pvcName",
|
PVCName: "pvcName",
|
||||||
},
|
},
|
||||||
|
@ -1223,7 +1223,7 @@ func TestPutBackupVolumeInfos(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
harness := newObjectBackupStoreTestHarness("foo", tc.prefix)
|
harness := newObjectBackupStoreTestHarness("foo", tc.prefix)
|
||||||
|
|
||||||
volumeInfos := []*volume.VolumeInfo{
|
volumeInfos := []*volume.BackupVolumeInfo{
|
||||||
{
|
{
|
||||||
PVCName: "test",
|
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
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
mock "github.com/stretchr/testify/mock"
|
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
|
// Restorer is an autogenerated mock type for the Restorer type
|
||||||
|
@ -12,13 +14,17 @@ type Restorer struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestorePodVolumes provides a mock function with given fields: _a0
|
// RestorePodVolumes provides a mock function with given fields: _a0, _a1
|
||||||
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
|
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData, _a1 *volume.RestoreVolumeInfoTracker) []error {
|
||||||
ret := _m.Called(_a0)
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RestorePodVolumes")
|
||||||
|
}
|
||||||
|
|
||||||
var r0 []error
|
var r0 []error
|
||||||
if rf, ok := ret.Get(0).(func(podvolume.RestoreData) []error); ok {
|
if rf, ok := ret.Get(0).(func(podvolume.RestoreData, *volume.RestoreVolumeInfoTracker) []error); ok {
|
||||||
r0 = rf(_a0)
|
r0 = rf(_a0, _a1)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]error)
|
r0 = ret.Get(0).([]error)
|
||||||
|
@ -27,3 +33,17 @@ func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
|
||||||
|
|
||||||
return r0
|
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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/vmware-tanzu/velero/internal/volume"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
corev1api "k8s.io/api/core/v1"
|
corev1api "k8s.io/api/core/v1"
|
||||||
|
@ -51,7 +53,7 @@ type RestoreData struct {
|
||||||
// Restorer can execute pod volume restores of volumes in a pod.
|
// Restorer can execute pod volume restores of volumes in a pod.
|
||||||
type Restorer interface {
|
type Restorer interface {
|
||||||
// RestorePodVolumes restores all annotated volumes in a pod.
|
// RestorePodVolumes restores all annotated volumes in a pod.
|
||||||
RestorePodVolumes(RestoreData) []error
|
RestorePodVolumes(RestoreData, *volume.RestoreVolumeInfoTracker) []error
|
||||||
}
|
}
|
||||||
|
|
||||||
type restorer struct {
|
type restorer struct {
|
||||||
|
@ -114,7 +116,7 @@ func newRestorer(
|
||||||
return r
|
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)
|
volumesToRestore := getVolumeBackupInfoForPod(data.PodVolumeBackups, data.Pod, data.SourceNamespace)
|
||||||
if len(volumesToRestore) == 0 {
|
if len(volumesToRestore) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -229,6 +231,7 @@ ForEachVolume:
|
||||||
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed {
|
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed {
|
||||||
errs = append(errs, errors.Errorf("pod volume restore failed: %s", res.Status.Message))
|
errs = append(errs, errors.Errorf("pod volume restore failed: %s", res.Status.Message))
|
||||||
}
|
}
|
||||||
|
tracker.TrackPodVolume(res)
|
||||||
case err := <-r.nodeAgentCheck:
|
case err := <-r.nodeAgentCheck:
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
break ForEachVolume
|
break ForEachVolume
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
appv1 "k8s.io/api/apps/v1"
|
appv1 "k8s.io/api/apps/v1"
|
||||||
|
@ -33,6 +35,7 @@ import (
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
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"
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||||
"github.com/vmware-tanzu/velero/pkg/builder"
|
"github.com/vmware-tanzu/velero/pkg/builder"
|
||||||
"github.com/vmware-tanzu/velero/pkg/repository"
|
"github.com/vmware-tanzu/velero/pkg/repository"
|
||||||
|
@ -418,7 +421,7 @@ func TestRestorePodVolumes(t *testing.T) {
|
||||||
PodVolumeBackups: test.pvbs,
|
PodVolumeBackups: test.pvbs,
|
||||||
SourceNamespace: test.sourceNamespace,
|
SourceNamespace: test.sourceNamespace,
|
||||||
BackupLocation: test.bsl,
|
BackupLocation: test.bsl,
|
||||||
})
|
}, volume.NewRestoreVolInfoTracker(restoreObj, logrus.New(), fakeCRClient))
|
||||||
|
|
||||||
if errs == nil {
|
if errs == nil {
|
||||||
assert.Nil(t, test.errs)
|
assert.Nil(t, test.errs)
|
||||||
|
|
|
@ -44,6 +44,7 @@ type pvRestorer struct {
|
||||||
volumeSnapshotterGetter VolumeSnapshotterGetter
|
volumeSnapshotterGetter VolumeSnapshotterGetter
|
||||||
kbclient client.Client
|
kbclient client.Client
|
||||||
credentialFileStore credentials.FileStore
|
credentialFileStore credentials.FileStore
|
||||||
|
volInfoTracker *volume.RestoreVolumeInfoTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||||
|
@ -97,6 +98,11 @@ func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructu
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("unexpected type %T", updated1)
|
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
|
return updated2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
@ -39,6 +41,7 @@ func defaultBackup() *builder.BackupBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
|
func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
|
||||||
|
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
obj *unstructured.Unstructured
|
obj *unstructured.Unstructured
|
||||||
|
@ -115,9 +118,10 @@ func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := &pvRestorer{
|
r := &pvRestorer{
|
||||||
logger: velerotest.NewLogger(),
|
logger: velerotest.NewLogger(),
|
||||||
restorePVs: tc.restore.Spec.RestorePVs,
|
restorePVs: tc.restore.Spec.RestorePVs,
|
||||||
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
|
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
|
||||||
|
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logrus.New(), fakeClient),
|
||||||
}
|
}
|
||||||
if tc.backup != nil {
|
if tc.backup != nil {
|
||||||
r.backup = tc.backup
|
r.backup = tc.backup
|
||||||
|
@ -180,6 +184,7 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
logger = velerotest.NewLogger()
|
||||||
volumeSnapshotter = new(providermocks.VolumeSnapshotter)
|
volumeSnapshotter = new(providermocks.VolumeSnapshotter)
|
||||||
volumeSnapshotterGetter = providerToVolumeSnapshotterMap(map[string]vsv1.VolumeSnapshotter{
|
volumeSnapshotterGetter = providerToVolumeSnapshotterMap(map[string]vsv1.VolumeSnapshotter{
|
||||||
tc.expectedProvider: volumeSnapshotter,
|
tc.expectedProvider: volumeSnapshotter,
|
||||||
|
@ -192,11 +197,12 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &pvRestorer{
|
r := &pvRestorer{
|
||||||
logger: velerotest.NewLogger(),
|
logger: logger,
|
||||||
backup: tc.backup,
|
backup: tc.backup,
|
||||||
volumeSnapshots: tc.volumeSnapshots,
|
volumeSnapshots: tc.volumeSnapshots,
|
||||||
kbclient: fakeClient,
|
kbclient: fakeClient,
|
||||||
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
||||||
|
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logger, fakeClient),
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeSnapshotter.On("Init", mock.Anything).Return(nil)
|
volumeSnapshotter.On("Init", mock.Anything).Return(nil)
|
||||||
|
|
|
@ -52,17 +52,18 @@ func resourceKey(obj runtime.Object) string {
|
||||||
type Request struct {
|
type Request struct {
|
||||||
*velerov1api.Restore
|
*velerov1api.Restore
|
||||||
|
|
||||||
Log logrus.FieldLogger
|
Log logrus.FieldLogger
|
||||||
Backup *velerov1api.Backup
|
Backup *velerov1api.Backup
|
||||||
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
PodVolumeBackups []*velerov1api.PodVolumeBackup
|
||||||
VolumeSnapshots []*volume.Snapshot
|
VolumeSnapshots []*volume.Snapshot
|
||||||
BackupReader io.Reader
|
BackupReader io.Reader
|
||||||
RestoredItems map[itemKey]restoredItemStatus
|
RestoredItems map[itemKey]restoredItemStatus
|
||||||
itemOperationsList *[]*itemoperation.RestoreOperation
|
itemOperationsList *[]*itemoperation.RestoreOperation
|
||||||
ResourceModifiers *resourcemodifiers.ResourceModifiers
|
ResourceModifiers *resourcemodifiers.ResourceModifiers
|
||||||
DisableInformerCache bool
|
DisableInformerCache bool
|
||||||
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
|
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
|
||||||
VolumeInfoMap map[string]volume.VolumeInfo
|
BackupVolumeInfoMap map[string]volume.BackupVolumeInfo
|
||||||
|
RestoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
type restoredItemStatus struct {
|
type restoredItemStatus struct {
|
||||||
|
|
|
@ -276,6 +276,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
||||||
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
volumeSnapshotterGetter: volumeSnapshotterGetter,
|
||||||
kbclient: kr.kbClient,
|
kbclient: kr.kbClient,
|
||||||
credentialFileStore: kr.credentialFileStore,
|
credentialFileStore: kr.credentialFileStore,
|
||||||
|
volInfoTracker: req.RestoreVolumeInfoTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
req.RestoredItems = make(map[itemKey]restoredItemStatus)
|
req.RestoredItems = make(map[itemKey]restoredItemStatus)
|
||||||
|
@ -323,7 +324,8 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
||||||
disableInformerCache: req.DisableInformerCache,
|
disableInformerCache: req.DisableInformerCache,
|
||||||
featureVerifier: kr.featureVerifier,
|
featureVerifier: kr.featureVerifier,
|
||||||
hookTracker: hook.NewHookTracker(),
|
hookTracker: hook.NewHookTracker(),
|
||||||
volumeInfoMap: req.VolumeInfoMap,
|
backupVolumeInfoMap: req.BackupVolumeInfoMap,
|
||||||
|
restoreVolumeInfoTracker: req.RestoreVolumeInfoTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
return restoreCtx.execute()
|
return restoreCtx.execute()
|
||||||
|
@ -376,7 +378,8 @@ type restoreContext struct {
|
||||||
disableInformerCache bool
|
disableInformerCache bool
|
||||||
featureVerifier features.Verifier
|
featureVerifier features.Verifier
|
||||||
hookTracker *hook.HookTracker
|
hookTracker *hook.HookTracker
|
||||||
volumeInfoMap map[string]volume.VolumeInfo
|
backupVolumeInfoMap map[string]volume.BackupVolumeInfo
|
||||||
|
restoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceClientKey struct {
|
type resourceClientKey struct {
|
||||||
|
@ -1224,8 +1227,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||||
return warnings, errs, itemExists
|
return warnings, errs, itemExists
|
||||||
}
|
}
|
||||||
|
|
||||||
if volumeInfo, ok := ctx.volumeInfoMap[obj.GetName()]; ok {
|
if volumeInfo, ok := ctx.backupVolumeInfoMap[obj.GetName()]; ok {
|
||||||
ctx.log.Infof("Find VolumeInfo for PV %s.", obj.GetName())
|
ctx.log.Infof("Find BackupVolumeInfo for PV %s.", obj.GetName())
|
||||||
|
|
||||||
switch volumeInfo.BackupMethod {
|
switch volumeInfo.BackupMethod {
|
||||||
case volume.NativeSnapshot:
|
case volume.NativeSnapshot:
|
||||||
|
@ -1258,7 +1261,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||||
// want to dynamically re-provision it.
|
// want to dynamically re-provision it.
|
||||||
return warnings, errs, itemExists
|
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.
|
// is not set, and it will fall into the default case.
|
||||||
default:
|
default:
|
||||||
if hasDeleteReclaimPolicy(obj.Object) {
|
if hasDeleteReclaimPolicy(obj.Object) {
|
||||||
|
@ -1277,9 +1280,9 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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.
|
// 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 {
|
switch {
|
||||||
case hasSnapshot(name, ctx.volumeSnapshots):
|
case hasSnapshot(name, ctx.volumeSnapshots):
|
||||||
|
@ -1899,7 +1902,7 @@ func restorePodVolumeBackups(ctx *restoreContext, createdObj *unstructured.Unstr
|
||||||
SourceNamespace: originalNamespace,
|
SourceNamespace: originalNamespace,
|
||||||
BackupLocation: ctx.backup.Spec.StorageLocation,
|
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")
|
ctx.log.WithError(kubeerrs.NewAggregate(errs)).Error("unable to successfully complete pod volume restores of pod's volumes")
|
||||||
|
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
|
@ -2498,7 +2501,7 @@ func (ctx *restoreContext) handlePVHasNativeSnapshot(obj *unstructured.Unstructu
|
||||||
|
|
||||||
ctx.renamedPVs[oldName] = pvName
|
ctx.renamedPVs[oldName] = pvName
|
||||||
retObj.SetName(pvName)
|
retObj.SetName(pvName)
|
||||||
|
ctx.restoreVolumeInfoTracker.RenamePVForNativeSnapshot(oldName, pvName)
|
||||||
// Add the original PV name as an annotation.
|
// Add the original PV name as an annotation.
|
||||||
annotations := retObj.GetAnnotations()
|
annotations := retObj.GetAnnotations()
|
||||||
if annotations == nil {
|
if annotations == nil {
|
||||||
|
|
|
@ -70,7 +70,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources []*test.APIResource
|
apiResources []*test.APIResource
|
||||||
tarball io.Reader
|
tarball io.Reader
|
||||||
want map[*test.APIResource][]string
|
want map[*test.APIResource][]string
|
||||||
volumeInfoMap map[string]volume.VolumeInfo
|
volumeInfoMap map[string]volume.BackupVolumeInfo
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Restore PV with native snapshot",
|
name: "Restore PV with native snapshot",
|
||||||
|
@ -83,7 +83,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
BackupMethod: volume.NativeSnapshot,
|
BackupMethod: volume.NativeSnapshot,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
|
@ -107,11 +107,11 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
BackupMethod: volume.PodVolumeBackup,
|
BackupMethod: volume.PodVolumeBackup,
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
PVBInfo: &volume.PodVolumeBackupInfo{
|
PVBInfo: &volume.PodVolumeInfo{
|
||||||
SnapshotHandle: "testSnapshotHandle",
|
SnapshotHandle: "testSnapshotHandle",
|
||||||
Size: 100,
|
Size: 100,
|
||||||
NodeName: "testNode",
|
NodeName: "testNode",
|
||||||
|
@ -133,7 +133,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
SnapshotDataMoved: false,
|
SnapshotDataMoved: false,
|
||||||
|
@ -158,7 +158,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
BackupMethod: volume.CSISnapshot,
|
BackupMethod: volume.CSISnapshot,
|
||||||
SnapshotDataMoved: true,
|
SnapshotDataMoved: true,
|
||||||
|
@ -186,7 +186,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
Skipped: true,
|
Skipped: true,
|
||||||
|
@ -207,7 +207,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
apiResources: []*test.APIResource{
|
apiResources: []*test.APIResource{
|
||||||
test.PVs(),
|
test.PVs(),
|
||||||
},
|
},
|
||||||
volumeInfoMap: map[string]volume.VolumeInfo{
|
volumeInfoMap: map[string]volume.BackupVolumeInfo{
|
||||||
"pv-1": {
|
"pv-1": {
|
||||||
PVName: "pv-1",
|
PVName: "pv-1",
|
||||||
Skipped: true,
|
Skipped: true,
|
||||||
|
@ -235,13 +235,13 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
|
||||||
h.restorer.featureVerifier = verifier
|
h.restorer.featureVerifier = verifier
|
||||||
|
|
||||||
data := &Request{
|
data := &Request{
|
||||||
Log: h.log,
|
Log: h.log,
|
||||||
Restore: tc.restore,
|
Restore: tc.restore,
|
||||||
Backup: tc.backup,
|
Backup: tc.backup,
|
||||||
PodVolumeBackups: nil,
|
PodVolumeBackups: nil,
|
||||||
VolumeSnapshots: nil,
|
VolumeSnapshots: nil,
|
||||||
BackupReader: tc.tarball,
|
BackupReader: tc.tarball,
|
||||||
VolumeInfoMap: tc.volumeInfoMap,
|
BackupVolumeInfoMap: tc.volumeInfoMap,
|
||||||
}
|
}
|
||||||
warnings, errs := h.restorer.Restore(
|
warnings, errs := h.restorer.Restore(
|
||||||
data,
|
data,
|
||||||
|
@ -3311,12 +3311,13 @@ func TestRestorePersistentVolumes(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &Request{
|
data := &Request{
|
||||||
Log: h.log,
|
Log: h.log,
|
||||||
Restore: tc.restore,
|
Restore: tc.restore,
|
||||||
Backup: tc.backup,
|
Backup: tc.backup,
|
||||||
VolumeSnapshots: tc.volumeSnapshots,
|
VolumeSnapshots: tc.volumeSnapshots,
|
||||||
BackupReader: tc.tarball,
|
BackupReader: tc.tarball,
|
||||||
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
|
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
|
||||||
|
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, h.log, test.NewFakeControllerRuntimeClient(t)),
|
||||||
}
|
}
|
||||||
warnings, errs := h.restorer.Restore(
|
warnings, errs := h.restorer.Restore(
|
||||||
data,
|
data,
|
||||||
|
@ -3441,7 +3442,7 @@ func TestRestoreWithPodVolume(t *testing.T) {
|
||||||
BackupLocation: "",
|
BackupLocation: "",
|
||||||
}
|
}
|
||||||
restorer.
|
restorer.
|
||||||
On("RestorePodVolumes", expectedArgs).
|
On("RestorePodVolumes", expectedArgs, mock.Anything).
|
||||||
Return(nil)
|
Return(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -235,7 +235,7 @@ func GetVolumeInfo(
|
||||||
bslConfig,
|
bslConfig,
|
||||||
backupName,
|
backupName,
|
||||||
subPrefix string,
|
subPrefix string,
|
||||||
) ([]*volume.VolumeInfo, error) {
|
) ([]*volume.BackupVolumeInfo, error) {
|
||||||
readCloser, err := GetVolumeInfoMetadataContent(objectStoreProvider,
|
readCloser, err := GetVolumeInfoMetadataContent(objectStoreProvider,
|
||||||
cloudCredentialsFile,
|
cloudCredentialsFile,
|
||||||
bslBucket,
|
bslBucket,
|
||||||
|
@ -254,7 +254,7 @@ func GetVolumeInfo(
|
||||||
}
|
}
|
||||||
defer gzr.Close()
|
defer gzr.Close()
|
||||||
|
|
||||||
volumeInfos := make([]*volume.VolumeInfo, 0)
|
volumeInfos := make([]*volume.BackupVolumeInfo, 0)
|
||||||
|
|
||||||
if err := json.NewDecoder(gzr).Decode(&volumeInfos); err != nil {
|
if err := json.NewDecoder(gzr).Decode(&volumeInfos); err != nil {
|
||||||
return nil, errors.Wrap(err, "error decoding object data")
|
return nil, errors.Wrap(err, "error decoding object data")
|
||||||
|
|
Loading…
Reference in New Issue