Merge pull request #7630 from reasonerjt/restore-vol-info

Track and persist restore volume info
This commit is contained in:
Wenkai Yin(尹文开)
2024-04-12 11:24:05 +08:00
committed by GitHub
33 changed files with 1116 additions and 387 deletions
@@ -42,6 +42,7 @@ const (
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
DownloadTargetKindBackupVolumeInfos DownloadTargetKind = "BackupVolumeInfos"
DownloadTargetKindRestoreVolumeInfo DownloadTargetKind = "RestoreVolumeInfo"
)
// DownloadTarget is the specification for what kind of file to download, and the name of the
+3 -3
View File
@@ -798,7 +798,7 @@ type tarWriter interface {
func (kb *kubernetesBackupper) getVolumeInfos(
backup velerov1api.Backup,
log logrus.FieldLogger,
) (persistence.BackupStore, []*volume.VolumeInfo, error) {
) (persistence.BackupStore, []*volume.BackupVolumeInfo, error) {
location := &velerov1api.BackupStorageLocation{}
if err := kb.kbClient.Get(context.Background(), kbclient.ObjectKey{
Namespace: backup.Namespace,
@@ -825,7 +825,7 @@ func (kb *kubernetesBackupper) getVolumeInfos(
// updateVolumeInfos update the VolumeInfos according to the AsyncOperations
func updateVolumeInfos(
volumeInfos []*volume.VolumeInfo,
volumeInfos []*volume.BackupVolumeInfo,
unstructuredItems []unstructured.Unstructured,
operations []*itemoperation.BackupOperation,
log logrus.FieldLogger,
@@ -874,7 +874,7 @@ func updateVolumeInfos(
func putVolumeInfos(
backupName string,
volumeInfos []*volume.VolumeInfo,
volumeInfos []*volume.BackupVolumeInfo,
backupStore persistence.BackupStore,
) error {
backupVolumeInfoBuf := new(bytes.Buffer)
+8 -8
View File
@@ -4454,7 +4454,7 @@ func TestGetVolumeInfos(t *testing.T) {
backupStore := new(persistencemocks.BackupStore)
h.backupper.pluginManager = func(logrus.FieldLogger) clientmgmt.Manager { return pluginManager }
h.backupper.backupStoreGetter = NewFakeSingleObjectBackupStoreGetter(backupStore)
backupStore.On("GetBackupVolumeInfos", "backup-01").Return([]*volume.VolumeInfo{}, nil)
backupStore.On("GetBackupVolumeInfos", "backup-01").Return([]*volume.BackupVolumeInfo{}, nil)
pluginManager.On("CleanupClients").Return()
backup := builder.ForBackup("velero", "backup-01").StorageLocation("default").Result()
@@ -4474,8 +4474,8 @@ func TestUpdateVolumeInfos(t *testing.T) {
name string
operations []*itemoperation.BackupOperation
dataUpload *velerov2alpha1.DataUpload
volumeInfos []*volume.VolumeInfo
expectedVolumeInfos []*volume.VolumeInfo
volumeInfos []*volume.BackupVolumeInfo
expectedVolumeInfos []*volume.BackupVolumeInfo
}{
{
name: "CSISnapshot VolumeInfo update",
@@ -4489,7 +4489,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
volumeInfos: []*volume.VolumeInfo{
volumeInfos: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
CompletionTimestamp: &metav1.Time{},
@@ -4498,7 +4498,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
expectedVolumeInfos: []*volume.VolumeInfo{
expectedVolumeInfos: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
CompletionTimestamp: &now,
@@ -4519,7 +4519,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
SourceNamespace("ns-1").
SourcePVC("pvc-1").
Result(),
volumeInfos: []*volume.VolumeInfo{
volumeInfos: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
@@ -4529,7 +4529,7 @@ func TestUpdateVolumeInfos(t *testing.T) {
},
},
},
expectedVolumeInfos: []*volume.VolumeInfo{
expectedVolumeInfos: []*volume.BackupVolumeInfo{
{
PVCName: "pvc-1",
PVCNamespace: "ns-1",
@@ -4572,7 +4572,7 @@ func TestPutVolumeInfos(t *testing.T) {
backupStore.On("PutBackupVolumeInfos", mock.Anything, mock.Anything).Return(nil)
require.NoError(t, putVolumeInfos(backupName, []*volume.VolumeInfo{}, backupStore))
require.NoError(t, putVolumeInfos(backupName, []*volume.BackupVolumeInfo{}, backupStore))
}
type fakeSingleObjectBackupStoreGetter struct {
+3 -3
View File
@@ -52,11 +52,11 @@ type Request struct {
itemOperationsList *[]*itemoperation.BackupOperation
ResPolicies *resourcepolicies.Policies
SkippedPVTracker *skipPVTracker
VolumesInformation volume.VolumesInformation
VolumesInformation volume.BackupVolumesInformation
}
// VolumesInformation contains the information needs by generating
// the backup VolumeInfo array.
// BackupVolumesInformation contains the information needs by generating
// the backup BackupVolumeInfo array.
// GetItemOperationsList returns ItemOperationsList, initializing it if necessary
func (r *Request) GetItemOperationsList() *[]*itemoperation.BackupOperation {
+6
View File
@@ -75,6 +75,12 @@ func (v *VolumeSnapshotBuilder) SourcePVC(name string) *VolumeSnapshotBuilder {
return v
}
// SourceVolumeSnapshotContentName set the built VolumeSnapshot's spec.Source.VolumeSnapshotContentName
func (v *VolumeSnapshotBuilder) SourceVolumeSnapshotContentName(name string) *VolumeSnapshotBuilder {
v.object.Spec.Source.VolumeSnapshotContentName = &name
return v
}
// RestoreSize set the built VolumeSnapshot's status.RestoreSize.
func (v *VolumeSnapshotBuilder) RestoreSize(size string) *VolumeSnapshotBuilder {
resourceSize := resource.MustParse(size)
+1
View File
@@ -991,6 +991,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.config.formatFlag.Parse(),
s.config.defaultItemOperationTimeout,
s.config.disableInformerCache,
s.crClient,
)
if err = r.SetupWithManager(s.mgr); err != nil {
+15 -15
View File
@@ -439,8 +439,8 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
d.Println("Backup Volumes:")
nativeSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
@@ -463,7 +463,7 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
d.Printf("\t<error getting backup volume info: %v>\n", err)
return
} else {
var volumeInfos []volume.VolumeInfo
var volumeInfos []volume.BackupVolumeInfo
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
d.Printf("\t<error reading backup volume info: %v>\n", err)
return
@@ -488,9 +488,9 @@ func describeBackupVolumes(ctx context.Context, kbClient kbclient.Client, d *Des
describePodVolumeBackups(d, details, podVolumeBackupCRs)
}
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.VolumeInfo, error) {
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
nativeSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
if status.VolumeSnapshotsAttempted == 0 {
return nativeSnapshots, nil
@@ -507,7 +507,7 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
}
for _, snap := range snapshots {
volumeInfo := volume.VolumeInfo{
volumeInfo := volume.BackupVolumeInfo{
PVName: snap.Spec.PersistentVolumeName,
NativeSnapshotInfo: &volume.NativeSnapshotInfo{
SnapshotHandle: snap.Status.ProviderSnapshotID,
@@ -526,9 +526,9 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
return nativeSnapshots, nil
}
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.VolumeInfo, error) {
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
csiSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
if status.CSIVolumeSnapshotsAttempted == 0 {
return csiSnapshots, nil
@@ -557,7 +557,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
}
for _, vsc := range vscList {
volInfo := volume.VolumeInfo{
volInfo := volume.BackupVolumeInfo{
PreserveLocalSnapshot: true,
CSISnapshotInfo: &volume.CSISnapshotInfo{
VSCName: vsc.Name,
@@ -598,7 +598,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
return csiSnapshots, nil
}
func describeNativeSnapshots(d *Describer, details bool, infos []*volume.VolumeInfo) {
func describeNativeSnapshots(d *Describer, details bool, infos []*volume.BackupVolumeInfo) {
if len(infos) == 0 {
d.Printf("\tVelero-Native Snapshots: <none included>\n")
return
@@ -610,7 +610,7 @@ func describeNativeSnapshots(d *Describer, details bool, infos []*volume.VolumeI
}
}
func describNativeSnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describNativeSnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if details {
d.Printf("\t\t%s:\n", info.PVName)
d.Printf("\t\t\tSnapshot ID:\t%s\n", info.NativeSnapshotInfo.SnapshotHandle)
@@ -622,7 +622,7 @@ func describNativeSnapshot(d *Describer, details bool, info *volume.VolumeInfo)
}
}
func describeCSISnapshots(d *Describer, details bool, infos []*volume.VolumeInfo, legacyInfoSource bool) {
func describeCSISnapshots(d *Describer, details bool, infos []*volume.BackupVolumeInfo, legacyInfoSource bool) {
if len(infos) == 0 {
if legacyInfoSource {
d.Printf("\tCSI Snapshots: <none included or not detectable>\n")
@@ -638,14 +638,14 @@ func describeCSISnapshots(d *Describer, details bool, infos []*volume.VolumeInfo
}
}
func describeCSISnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describeCSISnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
d.Printf("\t\t%s:\n", fmt.Sprintf("%s/%s", info.PVCNamespace, info.PVCName))
describeLocalSnapshot(d, details, info)
describeDataMovement(d, details, info)
}
func describeLocalSnapshot(d *Describer, details bool, info *volume.VolumeInfo) {
func describeLocalSnapshot(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if !info.PreserveLocalSnapshot {
return
}
@@ -665,7 +665,7 @@ func describeLocalSnapshot(d *Describer, details bool, info *volume.VolumeInfo)
}
}
func describeDataMovement(d *Describer, details bool, info *volume.VolumeInfo) {
func describeDataMovement(d *Describer, details bool, info *volume.BackupVolumeInfo) {
if !info.SnapshotDataMoved {
return
}
+11 -11
View File
@@ -325,13 +325,13 @@ OrderedResources:
func TestDescribeNativeSnapshots(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect string
}{
{
name: "no details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@@ -349,7 +349,7 @@ func TestDescribeNativeSnapshots(t *testing.T) {
},
{
name: "details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@@ -390,27 +390,27 @@ func TestDescribeNativeSnapshots(t *testing.T) {
func TestCSISnapshots(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect string
legacyInfoSource bool
}{
{
name: "empty info, not legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
expect: ` CSI Snapshots: <none included>
`,
},
{
name: "empty info, legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
legacyInfoSource: true,
expect: ` CSI Snapshots: <none included or not detectable>
`,
},
{
name: "no details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-1",
@@ -432,7 +432,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-2",
@@ -460,7 +460,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "no details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-3",
@@ -481,7 +481,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",
@@ -506,7 +506,7 @@ func TestCSISnapshots(t *testing.T) {
},
{
name: "details, data movement, data mover is empty",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-5",
@@ -308,8 +308,8 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupVolumes := make(map[string]interface{})
nativeSnapshots := []*volume.VolumeInfo{}
csiSnapshots := []*volume.VolumeInfo{}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
@@ -332,7 +332,7 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupVolumes["errorGetBackupVolumeInfo"] = fmt.Sprintf("error getting backup volume info: %v", err)
return
} else {
var volumeInfos []volume.VolumeInfo
var volumeInfos []volume.BackupVolumeInfo
if err := json.NewDecoder(buf).Decode(&volumeInfos); err != nil {
backupVolumes["errorReadBackupVolumeInfo"] = fmt.Sprintf("error reading backup volume info: %v", err)
return
@@ -357,7 +357,7 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
backupStatusInfo["backupVolumes"] = backupVolumes
}
func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}) {
func describeNativeSnapshotsInSF(details bool, infos []*volume.BackupVolumeInfo, backupVolumes map[string]interface{}) {
if len(infos) == 0 {
backupVolumes["nativeSnapshots"] = "<none included>"
return
@@ -370,7 +370,7 @@ func describeNativeSnapshotsInSF(details bool, infos []*volume.VolumeInfo, backu
backupVolumes["nativeSnapshots"] = snapshotDetails
}
func describNativeSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) {
func describNativeSnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetails map[string]interface{}) {
if details {
snapshotInfo := make(map[string]string)
snapshotInfo["snapshotID"] = info.NativeSnapshotInfo.SnapshotHandle
@@ -384,7 +384,7 @@ func describNativeSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDe
}
}
func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVolumes map[string]interface{}, legacyInfoSource bool) {
func describeCSISnapshotsInSF(details bool, infos []*volume.BackupVolumeInfo, backupVolumes map[string]interface{}, legacyInfoSource bool) {
if len(infos) == 0 {
if legacyInfoSource {
backupVolumes["csiSnapshots"] = "<none included or not detectable>"
@@ -401,7 +401,7 @@ func describeCSISnapshotsInSF(details bool, infos []*volume.VolumeInfo, backupVo
backupVolumes["csiSnapshots"] = snapshotDetails
}
func describeCSISnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetails map[string]interface{}) {
func describeCSISnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetails map[string]interface{}) {
snapshotDetail := make(map[string]interface{})
describeLocalSnapshotInSF(details, info, snapshotDetail)
@@ -411,7 +411,7 @@ func describeCSISnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDeta
}
// describeLocalSnapshotInSF describes CSI volume snapshot contents in structured format.
func describeLocalSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) {
func describeLocalSnapshotInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetail map[string]interface{}) {
if !info.PreserveLocalSnapshot {
return
}
@@ -434,7 +434,7 @@ func describeLocalSnapshotInSF(details bool, info *volume.VolumeInfo, snapshotDe
}
}
func describeDataMovementInSF(details bool, info *volume.VolumeInfo, snapshotDetail map[string]interface{}) {
func describeDataMovementInSF(details bool, info *volume.BackupVolumeInfo, snapshotDetail map[string]interface{}) {
if !info.SnapshotDataMoved {
return
}
@@ -284,13 +284,13 @@ func TestDescribePodVolumeBackupsInSF(t *testing.T) {
func TestDescribeNativeSnapshotsInSF(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect map[string]interface{}
}{
{
name: "no details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@@ -310,7 +310,7 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
},
{
name: "details",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@@ -348,21 +348,21 @@ func TestDescribeNativeSnapshotsInSF(t *testing.T) {
func TestDescribeCSISnapshotsInSF(t *testing.T) {
testcases := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
inputDetails bool
expect map[string]interface{}
legacyInfoSource bool
}{
{
name: "empty info, not legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
expect: map[string]interface{}{
"csiSnapshots": "<none included>",
},
},
{
name: "empty info, legacy",
volumeInfo: []*volume.VolumeInfo{},
volumeInfo: []*volume.BackupVolumeInfo{},
legacyInfoSource: true,
expect: map[string]interface{}{
"csiSnapshots": "<none included or not detectable>",
@@ -370,7 +370,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "no details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-1",
@@ -395,7 +395,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, local snapshot",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-2",
@@ -427,7 +427,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "no details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-3",
@@ -451,7 +451,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, data movement",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",
@@ -480,7 +480,7 @@ func TestDescribeCSISnapshotsInSF(t *testing.T) {
},
{
name: "details, data movement, data mover is empty",
volumeInfo: []*volume.VolumeInfo{
volumeInfo: []*volume.BackupVolumeInfo{
{
BackupMethod: volume.CSISnapshot,
PVCNamespace: "pvc-ns-4",
+37 -11
View File
@@ -107,6 +107,7 @@ type restoreReconciler struct {
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
backupStoreGetter persistence.ObjectBackupStoreGetter
globalCrClient client.Client
}
type backupInfo struct {
@@ -127,6 +128,7 @@ func NewRestoreReconciler(
logFormat logging.Format,
defaultItemOperationTimeout time.Duration,
disableInformerCache bool,
globalCrClient client.Client,
) *restoreReconciler {
r := &restoreReconciler{
ctx: ctx,
@@ -145,6 +147,8 @@ func NewRestoreReconciler(
// replaced with fakes for testing.
newPluginManager: newPluginManager,
backupStoreGetter: backupStoreGetter,
globalCrClient: globalCrClient,
}
// Move the periodical backup and restore metrics computing logic from controllers to here.
@@ -521,7 +525,7 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
return errors.Wrap(err, "fail to fetch CSI VolumeSnapshots metadata")
}
backupVolumeInfoMap := make(map[string]volume.VolumeInfo)
backupVolumeInfoMap := make(map[string]volume.BackupVolumeInfo)
volumeInfos, err := backupStore.GetBackupVolumeInfos(restore.Spec.BackupName)
if err != nil {
restoreLog.WithError(err).Errorf("fail to get VolumeInfos metadata file for backup %s", restore.Spec.BackupName)
@@ -540,16 +544,17 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
}
restoreReq := &pkgrestore.Request{
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
DisableInformerCache: r.disableInformerCache,
CSIVolumeSnapshots: csiVolumeSnapshots,
VolumeInfoMap: backupVolumeInfoMap,
Log: restoreLog,
Restore: restore,
Backup: info.backup,
PodVolumeBackups: podVolumeBackups,
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
ResourceModifiers: resourceModifiers,
DisableInformerCache: r.disableInformerCache,
CSIVolumeSnapshots: csiVolumeSnapshots,
BackupVolumeInfoMap: backupVolumeInfoMap,
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(restore, restoreLog, r.globalCrClient),
}
restoreWarnings, restoreErrors := r.restorer.RestoreWithResolvers(restoreReq, actionsResolver, pluginManager)
@@ -640,6 +645,11 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
r.logger.WithError(err).Error("Error uploading restore item action operation resource list to backup storage")
}
restoreReq.RestoreVolumeInfoTracker.Populate(context.TODO(), restoreReq.RestoredResourceList())
if err := putRestoreVolumeInfoList(restore, restoreReq.RestoreVolumeInfoTracker.Result(), backupStore); err != nil {
r.logger.WithError(err).Error("Error uploading restored volume info to backup storage")
}
if restore.Status.Errors > 0 {
if inProgressOperations {
r.logger.Debug("Restore WaitingForPluginOperationsPartiallyFailed")
@@ -776,6 +786,22 @@ func putOperationsForRestore(restore *api.Restore, operations []*itemoperation.R
return nil
}
func putRestoreVolumeInfoList(restore *api.Restore, volInfoList []*volume.RestoreVolumeInfo, store persistence.BackupStore) error {
buf := new(bytes.Buffer)
gzw := gzip.NewWriter(buf)
defer gzw.Close()
if err := json.NewEncoder(gzw).Encode(volInfoList); err != nil {
return errors.Wrap(err, "error encoding restore volume info list to JSON")
}
if err := gzw.Close(); err != nil {
return errors.Wrap(err, "error closing gzip writer")
}
return store.PutRestoreVolumeInfo(restore.Name, buf)
}
func downloadToTempFile(backupName string, backupStore persistence.BackupStore, logger logrus.FieldLogger) (*os.File, error) {
readCloser, err := backupStore.GetBackupContents(backupName)
if err != nil {
+33 -22
View File
@@ -91,11 +91,12 @@ func TestFetchBackupInfo(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
)
defer restorer.AssertExpectations(t)
@@ -114,6 +115,7 @@ func TestFetchBackupInfo(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
if test.backupStoreError == nil {
@@ -170,9 +172,10 @@ func TestProcessQueueItemSkips(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
)
if test.restore != nil {
@@ -192,6 +195,7 @@ func TestProcessQueueItemSkips(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
_, err := r.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{
@@ -432,11 +436,12 @@ func TestRestoreReconcile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var (
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
fakeClient = velerotest.NewFakeControllerRuntimeClientBuilder(t).Build()
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
restorer = &fakeRestorer{kbClient: fakeClient}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
backupStore = &persistencemocks.BackupStore{}
)
defer restorer.AssertExpectations(t)
@@ -459,6 +464,7 @@ func TestRestoreReconcile(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
r.clock = clocktesting.NewFakeClock(now)
@@ -500,10 +506,11 @@ func TestRestoreReconcile(t *testing.T) {
backupStore.On("PutRestoreResults", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
backupStore.On("PutRestoredResourceList", test.restore.Name, mock.Anything).Return(nil)
backupStore.On("PutRestoreItemOperations", mock.Anything, mock.Anything).Return(nil)
backupStore.On("PutRestoreVolumeInfo", test.restore.Name, mock.Anything).Return(nil)
if test.emptyVolumeInfo == true {
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return(nil, nil)
} else {
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.VolumeInfo{}, nil)
backupStore.On("GetBackupVolumeInfos", test.backup.Name).Return([]*volume.BackupVolumeInfo{}, nil)
}
volumeSnapshots := []*volume.Snapshot{
@@ -626,10 +633,11 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
formatFlag := logging.FormatText
var (
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
)
r := NewRestoreReconciler(
@@ -645,6 +653,7 @@ func TestValidateAndCompleteWhenScheduleNameSpecified(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
restore := &velerov1api.Restore{
@@ -719,10 +728,11 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
formatFlag := logging.FormatText
var (
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
logger = velerotest.NewLogger()
pluginManager = &pluginmocks.Manager{}
fakeClient = velerotest.NewFakeControllerRuntimeClient(t)
fakeGlobalClient = velerotest.NewFakeControllerRuntimeClient(t)
backupStore = &persistencemocks.BackupStore{}
)
r := NewRestoreReconciler(
@@ -738,6 +748,7 @@ func TestValidateAndCompleteWithResourceModifierSpecified(t *testing.T) {
formatFlag,
60*time.Minute,
false,
fakeGlobalClient,
)
restore := &velerov1api.Restore{
+3 -22
View File
@@ -19,7 +19,6 @@ package controller
import (
"context"
"fmt"
"regexp"
"sync"
"time"
@@ -39,7 +38,6 @@ import (
"github.com/vmware-tanzu/velero/pkg/metrics"
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/restore"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/util/results"
)
@@ -150,7 +148,7 @@ func (r *restoreFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Req
return ctrl.Result{}, errors.Wrap(err, "error getting restoredResourceList")
}
restoredPVCList := getRestoredPVCFromRestoredResourceList(restoredResourceList)
restoredPVCList := volume.RestoredPVCFromRestoredResourceList(restoredResourceList)
finalizerCtx := &finalizerContext{
logger: log,
@@ -238,7 +236,7 @@ type finalizerContext struct {
logger logrus.FieldLogger
restore *velerov1api.Restore
crClient client.Client
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
restoredPVCList map[string]struct{}
}
@@ -277,7 +275,7 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
}
pvWaitGroup.Add(1)
go func(volInfo volume.VolumeInfo, restoredNamespace string) {
go func(volInfo volume.BackupVolumeInfo, restoredNamespace string) {
defer pvWaitGroup.Done()
semaphore <- struct{}{}
@@ -358,23 +356,6 @@ func (ctx *finalizerContext) patchDynamicPVWithVolumeInfo() (errs results.Result
return errs
}
func getRestoredPVCFromRestoredResourceList(restoredResourceList map[string][]string) map[string]struct{} {
pvcKey := "v1/PersistentVolumeClaim"
pvcList := make(map[string]struct{})
for _, pvc := range restoredResourceList[pvcKey] {
// the format of pvc string in restoredResourceList is like: "namespace/pvcName(status)"
// extract the substring before "(created)" if the status in rightmost Parenthesis is "created"
r := regexp.MustCompile(`\(([^)]+)\)`)
matches := r.FindAllStringSubmatch(pvc, -1)
if len(matches) > 0 && matches[len(matches)-1][1] == restore.ItemRestoreResultCreated {
pvcList[pvc[:len(pvc)-len("(created)")]] = struct{}{}
}
}
return pvcList
}
func needPatch(newPV *v1.PersistentVolume, pvInfo *volume.PVInfo) bool {
if newPV.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimPolicy(pvInfo.ReclaimPolicy) {
return true
@@ -210,7 +210,7 @@ func TestUpdateResult(t *testing.T) {
func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
tests := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
restoredPVCNames map[string]struct{}
restore *velerov1api.Restore
restoredPVC []*corev1api.PersistentVolumeClaim
@@ -220,21 +220,21 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
}{
{
name: "no applicable volumeInfo",
volumeInfo: []*volume.VolumeInfo{{BackupMethod: "VeleroNativeSnapshot", PVCName: "pvc1"}},
volumeInfo: []*volume.BackupVolumeInfo{{BackupMethod: "VeleroNativeSnapshot", PVCName: "pvc1"}},
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
expectedPatch: nil,
expectedErrNum: 0,
},
{
name: "no restored PVC",
volumeInfo: []*volume.VolumeInfo{{BackupMethod: "PodVolumeBackup", PVCName: "pvc1"}},
volumeInfo: []*volume.BackupVolumeInfo{{BackupMethod: "PodVolumeBackup", PVCName: "pvc1"}},
restore: builder.ForRestore(velerov1api.DefaultNamespace, "restore").Result(),
expectedPatch: nil,
expectedErrNum: 0,
},
{
name: "no applicable pv patch",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -256,7 +256,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "an applicable pv patch",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -281,7 +281,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "a mapped namespace restore",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -306,7 +306,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "two applicable pv patches",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -354,7 +354,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "an applicable pv patch with bound error",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -375,7 +375,7 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
},
{
name: "two applicable pv patches with an error",
volumeInfo: []*volume.VolumeInfo{{
volumeInfo: []*volume.BackupVolumeInfo{{
BackupMethod: "PodVolumeBackup",
PVCName: "pvc1",
PVName: "pv1",
@@ -454,37 +454,3 @@ func TestPatchDynamicPVWithVolumeInfo(t *testing.T) {
}
}
}
func TestGetRestoredPVCFromRestoredResourceList(t *testing.T) {
// test empty list
restoredResourceList := map[string][]string{}
actual := getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test no match
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(updated)",
},
"v1/PersistentVolume": {
"namespace1/pv(created)",
},
}
actual = getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Empty(t, actual)
// test matches
restoredResourceList = map[string][]string{
"v1/PersistentVolumeClaim": {
"namespace1/pvc1(created)",
"namespace2/pvc2(updated)",
"namespace3/pvc(3)(created)",
},
}
expected := map[string]struct{}{
"namespace1/pvc1": {},
"namespace3/pvc(3)": {},
}
actual = getRestoredPVCFromRestoredResourceList(restoredResourceList)
assert.Equal(t, expected, actual)
}
+6
View File
@@ -61,3 +61,9 @@ func NewListOptionsForBackup(name string) metav1.ListOptions {
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.BackupNameLabel, GetValidName(name)),
}
}
// NewSelectorForRestore returns a Selector based on the restore name.
// This is useful for interacting with Listers that need a Selector.
func NewSelectorForRestore(name string) labels.Selector {
return labels.SelectorFromSet(map[string]string{velerov1api.RestoreNameLabel: GetValidName(name)})
}
+18 -5
View File
@@ -314,16 +314,16 @@ func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.R
return r0, r1
}
// GetBackupVolumeInfos provides a mock function with given fields: name
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
// GetRestoreItemOperations provides a mock function with given fields: name
func (_m *BackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
ret := _m.Called(name)
var r0 []*volume.VolumeInfo
if rf, ok := ret.Get(0).(func(string) []*volume.VolumeInfo); ok {
var r0 []*volume.BackupVolumeInfo
if rf, ok := ret.Get(0).(func(string) []*volume.BackupVolumeInfo); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*volume.VolumeInfo)
r0 = ret.Get(0).([]*volume.BackupVolumeInfo)
}
}
@@ -546,6 +546,19 @@ func (_m *BackupStore) PutRestoredResourceList(restore string, results io.Reader
return r0
}
// PutRestoreVolumeInfo provides a mock function with given fields: restore, results
func (_m *BackupStore) PutRestoreVolumeInfo(restore string, results io.Reader) error {
ret := _m.Called(restore, results)
var r0 error
if rf, ok := ret.Get(0).(func(string, io.Reader) error); ok {
r0 = rf(restore, results)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewBackupStore interface {
mock.TestingT
Cleanup(func())
+10 -3
View File
@@ -74,8 +74,8 @@ type BackupStore interface {
GetCSIVolumeSnapshots(name string) ([]*snapshotv1api.VolumeSnapshot, error)
GetCSIVolumeSnapshotContents(name string) ([]*snapshotv1api.VolumeSnapshotContent, error)
GetCSIVolumeSnapshotClasses(name string) ([]*snapshotv1api.VolumeSnapshotClass, error)
GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error)
PutBackupVolumeInfos(name string, volumeInfo io.Reader) error
GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error)
GetRestoreResults(name string) (map[string]results.Result, error)
// BackupExists checks if the backup metadata file exists in object storage.
@@ -88,6 +88,7 @@ type BackupStore interface {
PutRestoredResourceList(restore string, results io.Reader) error
PutRestoreItemOperations(restore string, restoreItemOperations io.Reader) error
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
PutRestoreVolumeInfo(restore string, volumeInfo io.Reader) error
DeleteRestore(name string) error
GetRestoredResourceList(name string) (map[string][]string, error)
@@ -498,8 +499,8 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod
return podVolumeBackups, nil
}
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.VolumeInfo, error) {
volumeInfos := make([]*volume.VolumeInfo, 0)
func (s *objectBackupStore) GetBackupVolumeInfos(name string) ([]*volume.BackupVolumeInfo, error) {
volumeInfos := make([]*volume.BackupVolumeInfo, 0)
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
if err != nil {
@@ -602,6 +603,10 @@ func (s *objectBackupStore) PutRestoreItemOperations(restore string, restoreItem
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(restore), restoreItemOperations)
}
func (s *objectBackupStore) PutRestoreVolumeInfo(restore string, volumeInfo io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreVolumeInfoKey(restore), volumeInfo)
}
func (s *objectBackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(backup), backupItemOperations)
}
@@ -638,6 +643,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResultsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupVolumeInfos:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeInfoKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreVolumeInfo:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreVolumeInfoKey(target.Name), DownloadURLTTL)
default:
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
}
+4
View File
@@ -132,3 +132,7 @@ func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string {
func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfo.json.gz", backup))
}
func (l *ObjectStoreLayout) getRestoreVolumeInfoKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("%s-volumeinfo.json.gz", restore))
}
+8 -8
View File
@@ -1068,17 +1068,17 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) {
func TestGetBackupVolumeInfos(t *testing.T) {
tests := []struct {
name string
volumeInfo []*volume.VolumeInfo
volumeInfo []*volume.BackupVolumeInfo
volumeInfoStr string
expectedErr string
expectedResult []*volume.VolumeInfo
expectedResult []*volume.BackupVolumeInfo
}{
{
name: "No VolumeInfos, expect no error.",
},
{
name: "Valid VolumeInfo, should pass.",
volumeInfo: []*volume.VolumeInfo{
name: "Valid BackupVolumeInfo, should pass.",
volumeInfo: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
PVName: "pvName",
@@ -1086,7 +1086,7 @@ func TestGetBackupVolumeInfos(t *testing.T) {
SnapshotDataMoved: false,
},
},
expectedResult: []*volume.VolumeInfo{
expectedResult: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
PVName: "pvName",
@@ -1096,9 +1096,9 @@ func TestGetBackupVolumeInfos(t *testing.T) {
},
},
{
name: "Invalid VolumeInfo string, should also pass.",
name: "Invalid BackupVolumeInfo string, should also pass.",
volumeInfoStr: `[{"abc": "123", "def": "456", "pvcName": "pvcName"}]`,
expectedResult: []*volume.VolumeInfo{
expectedResult: []*volume.BackupVolumeInfo{
{
PVCName: "pvcName",
},
@@ -1223,7 +1223,7 @@ func TestPutBackupVolumeInfos(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
harness := newObjectBackupStoreTestHarness("foo", tc.prefix)
volumeInfos := []*volume.VolumeInfo{
volumeInfos := []*volume.BackupVolumeInfo{
{
PVCName: "test",
},
+27 -7
View File
@@ -1,10 +1,12 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
// Code generated by mockery v2.42.2. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
"github.com/vmware-tanzu/velero/pkg/podvolume"
podvolume "github.com/vmware-tanzu/velero/pkg/podvolume"
volume "github.com/vmware-tanzu/velero/internal/volume"
)
// Restorer is an autogenerated mock type for the Restorer type
@@ -12,13 +14,17 @@ type Restorer struct {
mock.Mock
}
// RestorePodVolumes provides a mock function with given fields: _a0
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
ret := _m.Called(_a0)
// RestorePodVolumes provides a mock function with given fields: _a0, _a1
func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData, _a1 *volume.RestoreVolumeInfoTracker) []error {
ret := _m.Called(_a0, _a1)
if len(ret) == 0 {
panic("no return value specified for RestorePodVolumes")
}
var r0 []error
if rf, ok := ret.Get(0).(func(podvolume.RestoreData) []error); ok {
r0 = rf(_a0)
if rf, ok := ret.Get(0).(func(podvolume.RestoreData, *volume.RestoreVolumeInfoTracker) []error); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]error)
@@ -27,3 +33,17 @@ func (_m *Restorer) RestorePodVolumes(_a0 podvolume.RestoreData) []error {
return r0
}
// NewRestorer creates a new instance of Restorer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewRestorer(t interface {
mock.TestingT
Cleanup(func())
}) *Restorer {
mock := &Restorer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+5 -2
View File
@@ -21,6 +21,8 @@ import (
"sync"
"time"
"github.com/vmware-tanzu/velero/internal/volume"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
@@ -51,7 +53,7 @@ type RestoreData struct {
// Restorer can execute pod volume restores of volumes in a pod.
type Restorer interface {
// RestorePodVolumes restores all annotated volumes in a pod.
RestorePodVolumes(RestoreData) []error
RestorePodVolumes(RestoreData, *volume.RestoreVolumeInfoTracker) []error
}
type restorer struct {
@@ -114,7 +116,7 @@ func newRestorer(
return r
}
func (r *restorer) RestorePodVolumes(data RestoreData) []error {
func (r *restorer) RestorePodVolumes(data RestoreData, tracker *volume.RestoreVolumeInfoTracker) []error {
volumesToRestore := getVolumeBackupInfoForPod(data.PodVolumeBackups, data.Pod, data.SourceNamespace)
if len(volumesToRestore) == 0 {
return nil
@@ -229,6 +231,7 @@ ForEachVolume:
if res.Status.Phase == velerov1api.PodVolumeRestorePhaseFailed {
errs = append(errs, errors.Errorf("pod volume restore failed: %s", res.Status.Message))
}
tracker.TrackPodVolume(res)
case err := <-r.nodeAgentCheck:
errs = append(errs, err)
break ForEachVolume
+4 -1
View File
@@ -22,6 +22,8 @@ import (
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appv1 "k8s.io/api/apps/v1"
@@ -33,6 +35,7 @@ import (
"k8s.io/client-go/tools/cache"
ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/vmware-tanzu/velero/internal/volume"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/repository"
@@ -418,7 +421,7 @@ func TestRestorePodVolumes(t *testing.T) {
PodVolumeBackups: test.pvbs,
SourceNamespace: test.sourceNamespace,
BackupLocation: test.bsl,
})
}, volume.NewRestoreVolInfoTracker(restoreObj, logrus.New(), fakeCRClient))
if errs == nil {
assert.Nil(t, test.errs)
+6
View File
@@ -44,6 +44,7 @@ type pvRestorer struct {
volumeSnapshotterGetter VolumeSnapshotterGetter
kbclient client.Client
credentialFileStore credentials.FileStore
volInfoTracker *volume.RestoreVolumeInfoTracker
}
func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
@@ -97,6 +98,11 @@ func (r *pvRestorer) executePVAction(obj *unstructured.Unstructured) (*unstructu
if !ok {
return nil, errors.Errorf("unexpected type %T", updated1)
}
var iops int64 = 0
if snapshotInfo.volumeIOPS != nil {
iops = *snapshotInfo.volumeIOPS
}
r.volInfoTracker.TrackNativeSnapshot(updated2.GetName(), snapshotInfo.providerSnapshotID, snapshotInfo.volumeType, snapshotInfo.volumeAZ, iops)
return updated2, nil
}
+10 -4
View File
@@ -20,6 +20,8 @@ import (
"context"
"testing"
"github.com/sirupsen/logrus"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -39,6 +41,7 @@ func defaultBackup() *builder.BackupBuilder {
}
func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t)
tests := []struct {
name string
obj *unstructured.Unstructured
@@ -115,9 +118,10 @@ func TestExecutePVAction_NoSnapshotRestores(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
r := &pvRestorer{
logger: velerotest.NewLogger(),
restorePVs: tc.restore.Spec.RestorePVs,
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
logger: velerotest.NewLogger(),
restorePVs: tc.restore.Spec.RestorePVs,
kbclient: velerotest.NewFakeControllerRuntimeClient(t),
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logrus.New(), fakeClient),
}
if tc.backup != nil {
r.backup = tc.backup
@@ -180,6 +184,7 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
logger = velerotest.NewLogger()
volumeSnapshotter = new(providermocks.VolumeSnapshotter)
volumeSnapshotterGetter = providerToVolumeSnapshotterMap(map[string]vsv1.VolumeSnapshotter{
tc.expectedProvider: volumeSnapshotter,
@@ -192,11 +197,12 @@ func TestExecutePVAction_SnapshotRestores(t *testing.T) {
}
r := &pvRestorer{
logger: velerotest.NewLogger(),
logger: logger,
backup: tc.backup,
volumeSnapshots: tc.volumeSnapshots,
kbclient: fakeClient,
volumeSnapshotterGetter: volumeSnapshotterGetter,
volInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, logger, fakeClient),
}
volumeSnapshotter.On("Init", mock.Anything).Return(nil)
+12 -11
View File
@@ -52,17 +52,18 @@ func resourceKey(obj runtime.Object) string {
type Request struct {
*velerov1api.Restore
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
DisableInformerCache bool
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
VolumeInfoMap map[string]volume.VolumeInfo
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]restoredItemStatus
itemOperationsList *[]*itemoperation.RestoreOperation
ResourceModifiers *resourcemodifiers.ResourceModifiers
DisableInformerCache bool
CSIVolumeSnapshots []*snapshotv1api.VolumeSnapshot
BackupVolumeInfoMap map[string]volume.BackupVolumeInfo
RestoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
}
type restoredItemStatus struct {
+12 -9
View File
@@ -276,6 +276,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
volumeSnapshotterGetter: volumeSnapshotterGetter,
kbclient: kr.kbClient,
credentialFileStore: kr.credentialFileStore,
volInfoTracker: req.RestoreVolumeInfoTracker,
}
req.RestoredItems = make(map[itemKey]restoredItemStatus)
@@ -323,7 +324,8 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
disableInformerCache: req.DisableInformerCache,
featureVerifier: kr.featureVerifier,
hookTracker: hook.NewHookTracker(),
volumeInfoMap: req.VolumeInfoMap,
backupVolumeInfoMap: req.BackupVolumeInfoMap,
restoreVolumeInfoTracker: req.RestoreVolumeInfoTracker,
}
return restoreCtx.execute()
@@ -376,7 +378,8 @@ type restoreContext struct {
disableInformerCache bool
featureVerifier features.Verifier
hookTracker *hook.HookTracker
volumeInfoMap map[string]volume.VolumeInfo
backupVolumeInfoMap map[string]volume.BackupVolumeInfo
restoreVolumeInfoTracker *volume.RestoreVolumeInfoTracker
}
type resourceClientKey struct {
@@ -1224,8 +1227,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
return warnings, errs, itemExists
}
if volumeInfo, ok := ctx.volumeInfoMap[obj.GetName()]; ok {
ctx.log.Infof("Find VolumeInfo for PV %s.", obj.GetName())
if volumeInfo, ok := ctx.backupVolumeInfoMap[obj.GetName()]; ok {
ctx.log.Infof("Find BackupVolumeInfo for PV %s.", obj.GetName())
switch volumeInfo.BackupMethod {
case volume.NativeSnapshot:
@@ -1258,7 +1261,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
// want to dynamically re-provision it.
return warnings, errs, itemExists
// When the PV data is skipped from backup, it's VolumeInfo BackupMethod
// When the PV data is skipped from backup, it's BackupVolumeInfo BackupMethod
// is not set, and it will fall into the default case.
default:
if hasDeleteReclaimPolicy(obj.Object) {
@@ -1277,9 +1280,9 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}
}
} else {
// TODO: VolumeInfo is adopted and old logic is deprecated in v1.13.
// TODO: BackupVolumeInfo is adopted and old logic is deprecated in v1.13.
// Remove the old logic in v1.15.
ctx.log.Infof("Cannot find VolumeInfo for PV %s.", obj.GetName())
ctx.log.Infof("Cannot find BackupVolumeInfo for PV %s.", obj.GetName())
switch {
case hasSnapshot(name, ctx.volumeSnapshots):
@@ -1899,7 +1902,7 @@ func restorePodVolumeBackups(ctx *restoreContext, createdObj *unstructured.Unstr
SourceNamespace: originalNamespace,
BackupLocation: ctx.backup.Spec.StorageLocation,
}
if errs := ctx.podVolumeRestorer.RestorePodVolumes(data); errs != nil {
if errs := ctx.podVolumeRestorer.RestorePodVolumes(data, ctx.restoreVolumeInfoTracker); errs != nil {
ctx.log.WithError(kubeerrs.NewAggregate(errs)).Error("unable to successfully complete pod volume restores of pod's volumes")
for _, err := range errs {
@@ -2498,7 +2501,7 @@ func (ctx *restoreContext) handlePVHasNativeSnapshot(obj *unstructured.Unstructu
ctx.renamedPVs[oldName] = pvName
retObj.SetName(pvName)
ctx.restoreVolumeInfoTracker.RenamePVForNativeSnapshot(oldName, pvName)
// Add the original PV name as an annotation.
annotations := retObj.GetAnnotations()
if annotations == nil {
+23 -22
View File
@@ -70,7 +70,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources []*test.APIResource
tarball io.Reader
want map[*test.APIResource][]string
volumeInfoMap map[string]volume.VolumeInfo
volumeInfoMap map[string]volume.BackupVolumeInfo
}{
{
name: "Restore PV with native snapshot",
@@ -83,7 +83,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.NativeSnapshot,
PVName: "pv-1",
@@ -107,11 +107,11 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.PodVolumeBackup,
PVName: "pv-1",
PVBInfo: &volume.PodVolumeBackupInfo{
PVBInfo: &volume.PodVolumeInfo{
SnapshotHandle: "testSnapshotHandle",
Size: 100,
NodeName: "testNode",
@@ -133,7 +133,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.CSISnapshot,
SnapshotDataMoved: false,
@@ -158,7 +158,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
BackupMethod: volume.CSISnapshot,
SnapshotDataMoved: true,
@@ -186,7 +186,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
PVName: "pv-1",
Skipped: true,
@@ -207,7 +207,7 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
apiResources: []*test.APIResource{
test.PVs(),
},
volumeInfoMap: map[string]volume.VolumeInfo{
volumeInfoMap: map[string]volume.BackupVolumeInfo{
"pv-1": {
PVName: "pv-1",
Skipped: true,
@@ -235,13 +235,13 @@ func TestRestorePVWithVolumeInfo(t *testing.T) {
h.restorer.featureVerifier = verifier
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
VolumeInfoMap: tc.volumeInfoMap,
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
BackupVolumeInfoMap: tc.volumeInfoMap,
}
warnings, errs := h.restorer.Restore(
data,
@@ -3311,12 +3311,13 @@ func TestRestorePersistentVolumes(t *testing.T) {
}
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
VolumeSnapshots: tc.volumeSnapshots,
BackupReader: tc.tarball,
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
VolumeSnapshots: tc.volumeSnapshots,
BackupReader: tc.tarball,
CSIVolumeSnapshots: tc.csiVolumeSnapshots,
RestoreVolumeInfoTracker: volume.NewRestoreVolInfoTracker(tc.restore, h.log, test.NewFakeControllerRuntimeClient(t)),
}
warnings, errs := h.restorer.Restore(
data,
@@ -3441,7 +3442,7 @@ func TestRestoreWithPodVolume(t *testing.T) {
BackupLocation: "",
}
restorer.
On("RestorePodVolumes", expectedArgs).
On("RestorePodVolumes", expectedArgs, mock.Anything).
Return(nil)
}