Merge pull request #5867 from ywk253100/230202_restore

Add restored resource list in the output of restore describe command
pull/5927/head
lyndon 2023-02-28 11:39:42 +08:00 committed by GitHub
commit da17641433
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 353 additions and 53 deletions

View File

@ -0,0 +1 @@
Add restored resource list in the restore describe command

View File

@ -50,6 +50,7 @@ spec:
- BackupResourceList
- RestoreLog
- RestoreResults
- RestoreResourceList
- RestoreItemOperations
- CSIBackupVolumeSnapshots
- CSIBackupVolumeSnapshotContents

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ type DownloadRequestSpec struct {
}
// DownloadTargetKind represents what type of file to download.
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;RestoreLog;RestoreResults;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;RestoreLog;RestoreResults;RestoreResourceList;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
type DownloadTargetKind string
const (
@ -36,6 +36,7 @@ const (
DownloadTargetKindBackupResourceList DownloadTargetKind = "BackupResourceList"
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
DownloadTargetKindRestoreResourceList DownloadTargetKind = "RestoreResourceList"
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"

View File

@ -158,6 +158,11 @@ func DescribeRestore(ctx context.Context, kbClient kbclient.Client, restore *vel
d.Println()
d.Printf("Preserve Service NodePorts:\t%s\n", BoolPointerString(restore.Spec.PreserveNodePorts, "false", "true", "auto"))
d.Println()
if details {
describeRestoreResourceList(ctx, kbClient, d, restore, insecureSkipTLSVerify, caCertFile)
d.Println()
}
})
}
@ -275,3 +280,34 @@ func groupRestoresByPhase(restores []velerov1api.PodVolumeRestore) map[string][]
return restoresByPhase
}
func describeRestoreResourceList(ctx context.Context, kbClient kbclient.Client, d *Describer, restore *velerov1api.Restore, insecureSkipTLSVerify bool, caCertPath string) {
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err == downloadrequest.ErrNotFound {
d.Println("Resource List:\t<restore resource list not found>")
} else {
d.Printf("Resource List:\t<error getting restore resource list: %v>\n", err)
}
return
}
var resourceList map[string][]string
if err := json.NewDecoder(buf).Decode(&resourceList); err != nil {
d.Printf("Resource List:\t<error reading restore resource list: %v>\n", err)
return
}
d.Println("Resource List:")
// Sort GVKs in output
gvks := make([]string, 0, len(resourceList))
for gvk := range resourceList {
gvks = append(gvks, gvk)
}
sort.Strings(gvks)
for _, gvk := range gvks {
d.Printf("\t%s:\n\t\t- %s\n", gvk, strings.Join(resourceList[gvk], "\n\t\t- "))
}
}

View File

@ -121,7 +121,8 @@ func (r *downloadRequestReconciler) Reconcile(ctx context.Context, req ctrl.Requ
downloadRequest.Status.Expiration = &metav1.Time{Time: r.clock.Now().Add(persistence.DownloadURLTTL)}
if downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreLog ||
downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreResults {
downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreResults ||
downloadRequest.Spec.Target.Kind == velerov1api.DownloadTargetKindRestoreResourceList {
restore := &velerov1api.Restore{}
if err := r.client.Get(ctx, kbclient.ObjectKey{
Namespace: downloadRequest.Namespace,

View File

@ -466,7 +466,7 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
for i := range podVolumeBackupList.Items {
podVolumeBackups = append(podVolumeBackups, &podVolumeBackupList.Items[i])
}
restoreReq := pkgrestore.Request{
restoreReq := &pkgrestore.Request{
Log: restoreLog,
Restore: restore,
Backup: info.backup,
@ -539,6 +539,10 @@ func (r *restoreReconciler) runValidatedRestore(restore *api.Restore, info backu
r.logger.WithError(err).Error("Error uploading restore results to backup storage")
}
if err := putRestoredResourceList(restore, restoreReq.RestoredResourceList(), info.backupStore); err != nil {
r.logger.WithError(err).Error("Error uploading restored resource list to backup storage")
}
return nil
}
@ -585,6 +589,26 @@ func putResults(restore *api.Restore, results map[string]results.Result, backupS
return nil
}
func putRestoredResourceList(restore *api.Restore, list map[string][]string, backupStore persistence.BackupStore) error {
buf := new(bytes.Buffer)
gzw := gzip.NewWriter(buf)
defer gzw.Close()
if err := json.NewEncoder(gzw).Encode(list); err != nil {
return errors.Wrap(err, "error encoding restored resource list to JSON")
}
if err := gzw.Close(); err != nil {
return errors.Wrap(err, "error closing gzip writer")
}
if err := backupStore.PutRestoredResourceList(restore.Name, buf); err != nil {
return err
}
return nil
}
func downloadToTempFile(backupName string, backupStore persistence.BackupStore, logger logrus.FieldLogger) (*os.File, error) {
readCloser, err := backupStore.GetBackupContents(backupName)
if err != nil {

View File

@ -452,6 +452,7 @@ func TestRestoreReconcile(t *testing.T) {
backupStore.On("PutRestoreLog", test.backup.Name, test.restore.Name, mock.Anything).Return(test.putRestoreLogErr)
backupStore.On("PutRestoreResults", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
backupStore.On("PutRestoredResourceList", test.restore.Name, mock.Anything).Return(nil)
volumeSnapshots := []*volume.Snapshot{
{
@ -767,7 +768,7 @@ type fakeRestorer struct {
}
func (r *fakeRestorer) Restore(
info pkgrestore.Request,
info *pkgrestore.Request,
actions []riav2.RestoreItemAction,
volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter,
) (results.Result, results.Result) {
@ -778,7 +779,7 @@ func (r *fakeRestorer) Restore(
return res.Get(0).(results.Result), res.Get(1).(results.Result)
}
func (r *fakeRestorer) RestoreWithResolvers(req pkgrestore.Request,
func (r *fakeRestorer) RestoreWithResolvers(req *pkgrestore.Request,
resolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter,

View File

@ -327,3 +327,16 @@ func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.R
panic("implement me")
return nil, nil
}
func (_m *BackupStore) PutRestoredResourceList(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
}

View File

@ -78,6 +78,7 @@ type BackupStore interface {
PutRestoreLog(backup, restore string, log io.Reader) error
PutRestoreResults(backup, restore string, results io.Reader) error
PutRestoredResourceList(restore string, results io.Reader) error
PutRestoreItemOperations(backup, restore string, restoreItemOperations io.Reader) error
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
DeleteRestore(name string) error
@ -537,6 +538,10 @@ func (s *objectBackupStore) PutRestoreResults(backup string, restore string, res
return s.objectStore.PutObject(s.bucket, s.layout.getRestoreResultsKey(restore), results)
}
func (s *objectBackupStore) PutRestoredResourceList(restore string, list io.Reader) error {
return s.objectStore.PutObject(s.bucket, s.layout.getRestoreResourceListKey(restore), list)
}
func (s *objectBackupStore) PutRestoreItemOperations(backup string, restore string, restoreItemOperations io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(restore), restoreItemOperations)
}
@ -563,6 +568,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreLogKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreResults:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreResultsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreResourceList:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreResourceListKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindCSIBackupVolumeSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getCSIVolumeSnapshotKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindCSIBackupVolumeSnapshotContents:

View File

@ -105,6 +105,10 @@ func (l *ObjectStoreLayout) getRestoreResultsKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-results.gz", restore))
}
func (l *ObjectStoreLayout) getRestoreResourceListKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-resource-list.json.gz", restore))
}
func (l *ObjectStoreLayout) getRestoreItemOperationsKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-itemoperations.json.gz", restore))
}

View File

@ -622,6 +622,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindRestoreLog: "restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "restores/my-backup/restore-my-backup-itemoperations.json.gz",
velerov1api.DownloadTargetKindRestoreResourceList: "restores/my-backup/restore-my-backup-resource-list.json.gz",
},
},
{
@ -632,6 +633,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindRestoreLog: "velero-backups/restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "velero-backups/restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "velero-backups/restores/my-backup/restore-my-backup-itemoperations.json.gz",
velerov1api.DownloadTargetKindRestoreResourceList: "velero-backups/restores/my-backup/restore-my-backup-resource-list.json.gz",
},
},
{
@ -641,6 +643,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindRestoreLog: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-itemoperations.json.gz",
velerov1api.DownloadTargetKindRestoreResourceList: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-resource-list.json.gz",
},
},
}

79
pkg/restore/request.go Normal file
View File

@ -0,0 +1,79 @@
/*
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 restore
import (
"fmt"
"io"
"sort"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/runtime"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/volume"
)
const (
itemRestoreResultCreated = "created"
itemRestoreResultUpdated = "updated"
itemRestoreResultFailed = "failed"
itemRestoreResultSkipped = "skipped"
)
type itemKey struct {
resource string
namespace string
name string
}
func resourceKey(obj runtime.Object) string {
gvk := obj.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s", gvk.GroupVersion().String(), gvk.Kind)
}
type Request struct {
*velerov1api.Restore
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
RestoredItems map[itemKey]string
}
// RestoredResourceList returns the list of restored resources grouped by the API
// Version and Kind
func (r *Request) RestoredResourceList() map[string][]string {
resources := map[string][]string{}
for i, action := range r.RestoredItems {
entry := i.name
if i.namespace != "" {
entry = fmt.Sprintf("%s/%s", i.namespace, i.name)
}
entry = fmt.Sprintf("%s(%s)", entry, action)
resources[i.resource] = append(resources[i.resource], entry)
}
// sort namespace/name entries for each GVK
for _, v := range resources {
sort.Strings(v)
}
return resources
}

View File

@ -0,0 +1,67 @@
/*
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 restore
import (
"testing"
"github.com/stretchr/testify/assert"
coreV1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestResourceKey(t *testing.T) {
namespace := &coreV1.Namespace{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Namespace",
},
}
assert.Equal(t, "v1/Namespace", resourceKey(namespace))
cr := &coreV1.Namespace{
TypeMeta: metav1.TypeMeta{
APIVersion: "customized/v1",
Kind: "Cron",
},
}
assert.Equal(t, "customized/v1/Cron", resourceKey(cr))
}
func TestRestoredResourceList(t *testing.T) {
request := &Request{
RestoredItems: map[itemKey]string{
{
resource: "v1/Namespace",
namespace: "",
name: "default",
}: "created",
{
resource: "v1/ConfigMap",
namespace: "default",
name: "cm",
}: "skipped",
},
}
expected := map[string][]string{
"v1/ConfigMap": {"default/cm(skipped)"},
"v1/Namespace": {"default(created)"},
}
assert.EqualValues(t, expected, request.RestoredResourceList())
}

View File

@ -73,25 +73,15 @@ type VolumeSnapshotterGetter interface {
GetVolumeSnapshotter(name string) (vsv1.VolumeSnapshotter, error)
}
type Request struct {
*velerov1api.Restore
Log logrus.FieldLogger
Backup *velerov1api.Backup
PodVolumeBackups []*velerov1api.PodVolumeBackup
VolumeSnapshots []*volume.Snapshot
BackupReader io.Reader
}
// Restorer knows how to restore a backup.
type Restorer interface {
// Restore restores the backup data from backupReader, returning warnings and errors.
Restore(req Request,
Restore(req *Request,
actions []riav2.RestoreItemAction,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result)
RestoreWithResolvers(
req Request,
req *Request,
restoreItemActionResolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter VolumeSnapshotterGetter,
@ -160,7 +150,7 @@ func NewKubernetesRestorer(
// and using data from the provided backup/backup reader. Returns a warnings and errors RestoreResult,
// respectively, summarizing info about the restore.
func (kr *kubernetesRestorer) Restore(
req Request,
req *Request,
actions []riav2.RestoreItemAction,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result) {
@ -170,7 +160,7 @@ func (kr *kubernetesRestorer) Restore(
}
func (kr *kubernetesRestorer) RestoreWithResolvers(
req Request,
req *Request,
restoreItemActionResolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter VolumeSnapshotterGetter,
@ -280,6 +270,8 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
credentialFileStore: kr.credentialFileStore,
}
req.RestoredItems = make(map[itemKey]string)
restoreCtx := &restoreContext{
backup: req.Backup,
backupReader: req.BackupReader,
@ -305,7 +297,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
podVolumeBackups: req.PodVolumeBackups,
resourceTerminatingTimeout: kr.resourceTerminatingTimeout,
resourceClients: make(map[resourceClientKey]client.Dynamic),
restoredItems: make(map[velero.ResourceIdentifier]struct{}),
restoredItems: req.RestoredItems,
renamedPVs: make(map[string]string),
pvRenamer: kr.pvRenamer,
discoveryHelper: kr.discoveryHelper,
@ -348,7 +340,7 @@ type restoreContext struct {
podVolumeBackups []*velerov1api.PodVolumeBackup
resourceTerminatingTimeout time.Duration
resourceClients map[resourceClientKey]client.Dynamic
restoredItems map[velero.ResourceIdentifier]struct{}
restoredItems map[itemKey]string
renamedPVs map[string]string
pvRenamer func(string) (string, error)
discoveryHelper discovery.Helper
@ -630,12 +622,12 @@ func (ctx *restoreContext) processSelectedResource(
// Add the newly created namespace to the list of restored items.
if nsCreated {
itemKey := velero.ResourceIdentifier{
GroupResource: kuberesource.Namespaces,
Namespace: ns.Namespace,
Name: ns.Name,
itemKey := itemKey{
resource: resourceKey(ns),
namespace: ns.Namespace,
name: ns.Name,
}
ctx.restoredItems[itemKey] = struct{}{}
ctx.restoredItems[itemKey] = itemRestoreResultCreated
}
// Keep track of namespaces that we know exist so we don't
@ -702,6 +694,10 @@ func getNamespace(logger logrus.FieldLogger, path, remappedName string) *v1.Name
if nsBytes, err = ioutil.ReadFile(path); err != nil {
return &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: remappedName,
},
@ -712,6 +708,10 @@ func getNamespace(logger logrus.FieldLogger, path, remappedName string) *v1.Name
if err := json.Unmarshal(nsBytes, &backupNS); err != nil {
logger.Warnf("Error unmarshalling namespace from backup, creating new one.")
return &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: remappedName,
},
@ -719,6 +719,10 @@ func getNamespace(logger logrus.FieldLogger, path, remappedName string) *v1.Name
}
return &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: backupNS.Kind,
APIVersion: backupNS.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: remappedName,
Labels: backupNS.Labels,
@ -944,12 +948,12 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
} else {
// Add the newly created namespace to the list of restored items.
if nsCreated {
itemKey := velero.ResourceIdentifier{
GroupResource: kuberesource.Namespaces,
Namespace: nsToEnsure.Namespace,
Name: nsToEnsure.Name,
itemKey := itemKey{
resource: resourceKey(nsToEnsure),
namespace: nsToEnsure.Namespace,
name: nsToEnsure.Name,
}
ctx.restoredItems[itemKey] = struct{}{}
ctx.restoredItems[itemKey] = itemRestoreResultCreated
}
}
} else {
@ -980,16 +984,29 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
name := obj.GetName()
// Check if we've already restored this itemKey.
itemKey := velero.ResourceIdentifier{
GroupResource: groupResource,
Namespace: namespace,
Name: name,
itemKey := itemKey{
resource: resourceKey(obj),
namespace: namespace,
name: name,
}
if _, exists := ctx.restoredItems[itemKey]; exists {
ctx.log.Infof("Skipping %s because it's already been restored.", resourceID)
return warnings, errs
}
ctx.restoredItems[itemKey] = struct{}{}
ctx.restoredItems[itemKey] = ""
defer func() {
// the action field is set explicitly
if action := ctx.restoredItems[itemKey]; len(action) > 0 {
return
}
// no action specified, and no warnings and errors
if errs.IsEmpty() && warnings.IsEmpty() {
ctx.restoredItems[itemKey] = itemRestoreResultSkipped
return
}
// others are all failed
ctx.restoredItems[itemKey] = itemRestoreResultFailed
}()
// TODO: move to restore item action if/when we add a ShouldRestore() method
// to the interface.
@ -1243,6 +1260,9 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
ctx.log.Infof("Attempting to restore %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr := resourceClient.Create(obj)
if restoreErr == nil {
ctx.restoredItems[itemKey] = itemRestoreResultCreated
}
isAlreadyExistsError, err := isAlreadyExistsError(ctx, obj, restoreErr, resourceClient)
if err != nil {
errs.Add(namespace, err)
@ -1317,6 +1337,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
errs.Merge(&errsFromUpdate)
}
} else {
ctx.restoredItems[itemKey] = itemRestoreResultUpdated
ctx.log.Infof("ServiceAccount %s successfully updated", kube.NamespaceAndName(obj))
}
default:
@ -1334,6 +1355,9 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
} else if resourcePolicy == velerov1api.PolicyTypeUpdate {
// processing update as existingResourcePolicy
warningsFromUpdateRP, errsFromUpdateRP := ctx.processUpdateResourcePolicy(fromCluster, fromClusterWithLabels, obj, namespace, resourceClient)
if warningsFromUpdateRP.IsEmpty() && errsFromUpdateRP.IsEmpty() {
ctx.restoredItems[itemKey] = itemRestoreResultUpdated
}
warnings.Merge(&warningsFromUpdateRP)
errs.Merge(&errsFromUpdateRP)
}

View File

@ -569,7 +569,7 @@ func TestRestoreResourceFiltering(t *testing.T) {
}
require.NoError(t, h.restorer.discoveryHelper.Refresh())
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -649,7 +649,7 @@ func TestRestoreNamespaceMapping(t *testing.T) {
}
require.NoError(t, h.restorer.discoveryHelper.Refresh())
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -733,7 +733,7 @@ func TestRestoreResourcePriorities(t *testing.T) {
}
require.NoError(t, h.restorer.discoveryHelper.Refresh())
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -810,7 +810,7 @@ func TestInvalidTarballContents(t *testing.T) {
}
require.NoError(t, h.restorer.discoveryHelper.Refresh())
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -856,12 +856,13 @@ func assertWantErrsOrWarnings(t *testing.T, wantRes Result, res Result) {
// with the expected metadata/spec/status in the API.
func TestRestoreItems(t *testing.T) {
tests := []struct {
name string
restore *velerov1api.Restore
backup *velerov1api.Backup
apiResources []*test.APIResource
tarball io.Reader
want []*test.APIResource
name string
restore *velerov1api.Restore
backup *velerov1api.Backup
apiResources []*test.APIResource
tarball io.Reader
want []*test.APIResource
expectedRestoreItems map[itemKey]string
}{
{
name: "metadata uid/resourceVersion/etc. gets removed",
@ -893,6 +894,10 @@ func TestRestoreItems(t *testing.T) {
Result(),
),
},
expectedRestoreItems: map[itemKey]string{
{resource: "v1/Namespace", namespace: "", name: "ns-1"}: "created",
{resource: "v1/Pod", namespace: "ns-1", name: "pod-1"}: "created",
},
},
{
name: "status gets removed",
@ -999,6 +1004,10 @@ func TestRestoreItems(t *testing.T) {
want: []*test.APIResource{
test.ServiceAccounts(builder.ForServiceAccount("ns-1", "sa-1").Result()),
},
expectedRestoreItems: map[itemKey]string{
{resource: "v1/Namespace", namespace: "", name: "ns-1"}: "created",
{resource: "v1/ServiceAccount", namespace: "ns-1", name: "sa-1"}: "skipped",
},
},
{
name: "update secret data and labels when secret exists in cluster and is not identical to the backed up one, existing resource policy is update",
@ -1013,6 +1022,10 @@ func TestRestoreItems(t *testing.T) {
want: []*test.APIResource{
test.Secrets(builder.ForSecret("ns-1", "sa-1").ObjectMeta(builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1")).Data(map[string][]byte{"key-1": []byte("value-1")}).Result()),
},
expectedRestoreItems: map[itemKey]string{
{resource: "v1/Namespace", namespace: "", name: "ns-1"}: "created",
{resource: "v1/Secret", namespace: "ns-1", name: "sa-1"}: "updated",
},
},
{
name: "update service account labels when service account exists in cluster and is identical to the backed up one, existing resource policy is update",
@ -1163,13 +1176,14 @@ func TestRestoreItems(t *testing.T) {
h.AddItems(t, r)
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
PodVolumeBackups: nil,
VolumeSnapshots: nil,
BackupReader: tc.tarball,
RestoredItems: map[itemKey]string{},
}
warnings, errs := h.restorer.Restore(
data,
@ -1179,6 +1193,9 @@ func TestRestoreItems(t *testing.T) {
assertEmptyResults(t, warnings, errs)
assertRestoredItems(t, h, tc.want)
if len(tc.expectedRestoreItems) > 0 {
assert.EqualValues(t, tc.expectedRestoreItems, data.RestoredItems)
}
})
}
}
@ -1393,7 +1410,7 @@ func TestRestoreActionsRunForCorrectItems(t *testing.T) {
actions = append(actions, action)
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -1568,7 +1585,7 @@ func TestRestoreActionModifications(t *testing.T) {
}
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -1734,7 +1751,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
h.AddItems(t, r)
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -2728,7 +2745,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
}
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,
@ -2862,7 +2879,7 @@ func TestRestoreWithPodVolume(t *testing.T) {
Return(nil)
}
data := Request{
data := &Request{
Log: h.log,
Restore: tc.restore,
Backup: tc.backup,

View File

@ -65,3 +65,8 @@ func (r *Result) Add(ns string, e error) {
r.Namespaces[ns] = append(r.Namespaces[ns], e.Error())
}
}
// IsEmpty returns true if all collections of messages are empty
func (r *Result) IsEmpty() bool {
return len(r.Velero) == 0 && len(r.Cluster) == 0 && len(r.Namespaces) == 0
}

View File

@ -194,3 +194,19 @@ func TestAdd(t *testing.T) {
})
}
}
func TestIsEmpty(t *testing.T) {
result := &Result{
Velero: nil,
Cluster: nil,
Namespaces: nil,
}
assert.True(t, result.IsEmpty())
result = &Result{
Velero: []string{"error"},
Cluster: nil,
Namespaces: nil,
}
assert.False(t, result.IsEmpty())
}