Merge pull request #5867 from ywk253100/230202_restore
Add restored resource list in the output of restore describe commandpull/5927/head
commit
da17641433
|
@ -0,0 +1 @@
|
|||
Add restored resource list in the restore describe command
|
|
@ -50,6 +50,7 @@ spec:
|
|||
- BackupResourceList
|
||||
- RestoreLog
|
||||
- RestoreResults
|
||||
- RestoreResourceList
|
||||
- RestoreItemOperations
|
||||
- CSIBackupVolumeSnapshots
|
||||
- CSIBackupVolumeSnapshotContents
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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"
|
||||
|
|
|
@ -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- "))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue