Merge pull request #754 from skriss/namespace-restore-label

update how we label restored objects
pull/783/head
Nolan Brubaker 2018-08-09 15:17:50 -04:00 committed by GitHub
commit 65cd5c602f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 67 deletions

View File

@ -29,7 +29,7 @@ Scheduled backups are saved with the name `<SCHEDULE NAME>-<TIMESTAMP>`, where `
The **restore** operation allows you to restore all of the objects and persistent volumes from a previously created backup. You can also restore only a filtered subset of objects and persistent volumes. Ark supports multiple namespace remapping--for example, in a single restore, objects in namespace "abc" can be recreated under namespace "def", and the objects in namespace "123" under "456". The **restore** operation allows you to restore all of the objects and persistent volumes from a previously created backup. You can also restore only a filtered subset of objects and persistent volumes. Ark supports multiple namespace remapping--for example, in a single restore, objects in namespace "abc" can be recreated under namespace "def", and the objects in namespace "123" under "456".
The default name of a restore is `<BACKUP NAME>-<TIMESTAMP>`, where `<TIMESTAMP>` is formatted as *YYYYMMDDhhmmss*. You can also specify a custom name. A restored object also includes a label with key `ark-restore` and value `<RESTORE NAME>`. The default name of a restore is `<BACKUP NAME>-<TIMESTAMP>`, where `<TIMESTAMP>` is formatted as *YYYYMMDDhhmmss*. You can also specify a custom name. A restored object also includes a label with key `ark.heptio.com/restore-name` and value `<RESTORE NAME>`.
You can also run the Ark server in restore-only mode, which disables backup, schedule, and garbage collection functionality during disaster recovery. You can also run the Ark server in restore-only mode, which disables backup, schedule, and garbage collection functionality during disaster recovery.

View File

@ -28,6 +28,9 @@ const (
// RestoreLabelKey is the label key that's applied to all resources that // RestoreLabelKey is the label key that's applied to all resources that
// are created during a restore. This is applied for ease of identification // are created during a restore. This is applied for ease of identification
// of restored resources. The value will be the restore's name. // of restored resources. The value will be the restore's name.
//
// This label is DEPRECATED as of v0.10 and will be removed entirely as of
// v1.0 and replaced with RestoreNameLabel ("ark.heptio.com/restore-name").
RestoreLabelKey = "ark-restore" RestoreLabelKey = "ark-restore"
// ClusterScopedDir is the name of the directory containing cluster-scoped // ClusterScopedDir is the name of the directory containing cluster-scoped

View File

@ -760,8 +760,10 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a
obj.SetNamespace(namespace) obj.SetNamespace(namespace)
} }
// add an ark-restore label to each resource for easy ID // label the resource with the restore's name and the restored backup's name
addLabel(obj, api.RestoreLabelKey, ctx.restore.Name) // for easy identification of all cluster resources created by this restore
// and which backup they came from
addRestoreLabels(obj, ctx.restore.Name, ctx.restore.Spec.BackupName)
ctx.infof("Restoring %s: %v", obj.GroupVersionKind().Kind, name) ctx.infof("Restoring %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr := resourceClient.Create(obj) createdObj, restoreErr := resourceClient.Create(obj)
@ -780,10 +782,10 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a
continue continue
} }
// We know the cluster won't have the restore name label, so // We know the object from the cluster won't have the backup/restore name labels, so
// copy it over from the backup // copy them from the object we attempted to restore.
restoreName := obj.GetLabels()[api.RestoreLabelKey] labels := obj.GetLabels()
addLabel(fromCluster, api.RestoreLabelKey, restoreName) addRestoreLabels(fromCluster, labels[api.RestoreNameLabel], labels[api.BackupNameLabel])
if !equality.Semantic.DeepEqual(fromCluster, obj) { if !equality.Semantic.DeepEqual(fromCluster, obj) {
switch groupResource { switch groupResource {
@ -997,15 +999,22 @@ func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstr
return obj, nil return obj, nil
} }
// addLabel applies the specified key/value to an object as a label. // addRestoreLabels labels the provided object with the restore name and
func addLabel(obj *unstructured.Unstructured, key string, val string) { // the restored backup's name.
func addRestoreLabels(obj metav1.Object, restoreName, backupName string) {
labels := obj.GetLabels() labels := obj.GetLabels()
if labels == nil { if labels == nil {
labels = make(map[string]string) labels = make(map[string]string)
} }
labels[key] = val labels[api.BackupNameLabel] = backupName
labels[api.RestoreNameLabel] = restoreName
// TODO(1.0): remove the below line, and remove the `RestoreLabelKey`
// constant from the API pkg, since it's been replaced with the
// namespaced label above.
labels[api.RestoreLabelKey] = restoreName
obj.SetLabels(labels) obj.SetLabels(labels)
} }

View File

@ -307,11 +307,12 @@ func TestNamespaceRemapping(t *testing.T) {
WithFile("bak/resources/configmaps/namespaces/ns-1/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()). WithFile("bak/resources/configmaps/namespaces/ns-1/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()).
WithFile("bak/resources/namespaces/cluster/ns-1.json", newTestNamespace("ns-1").ToJSON()) WithFile("bak/resources/namespaces/cluster/ns-1.json", newTestNamespace("ns-1").ToJSON())
expectedNS = "ns-2" expectedNS = "ns-2"
expectedObjs = toUnstructured(newTestConfigMap().WithNamespace("ns-2").WithArkLabel("").ConfigMap) expectedObjs = toUnstructured(newTestConfigMap().WithNamespace("ns-2").ConfigMap)
) )
resourceClient := &arktest.FakeDynamicClient{} resourceClient := &arktest.FakeDynamicClient{}
for i := range expectedObjs { for i := range expectedObjs {
addRestoreLabels(&expectedObjs[i], "", "")
resourceClient.On("Create", &expectedObjs[i]).Return(&expectedObjs[i], nil) resourceClient.On("Create", &expectedObjs[i]).Return(&expectedObjs[i], nil)
} }
@ -381,8 +382,8 @@ func TestRestoreResourceForNamespace(t *testing.T) {
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()). WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()).
WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()), WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()),
expectedObjs: toUnstructured( expectedObjs: toUnstructured(
newNamedTestConfigMap("cm-1").WithArkLabel("my-restore").ConfigMap, newNamedTestConfigMap("cm-1").ConfigMap,
newNamedTestConfigMap("cm-2").WithArkLabel("my-restore").ConfigMap, newNamedTestConfigMap("cm-2").ConfigMap,
), ),
}, },
{ {
@ -415,7 +416,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
"ns-1": {"error decoding \"configmaps/cm-1-invalid.json\": invalid character 'h' in literal true (expecting 'r')"}, "ns-1": {"error decoding \"configmaps/cm-1-invalid.json\": invalid character 'h' in literal true (expecting 'r')"},
}, },
}, },
expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").ConfigMap),
}, },
{ {
name: "matching label selector correctly includes", name: "matching label selector correctly includes",
@ -423,7 +424,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
resourcePath: "configmaps", resourcePath: "configmaps",
labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "bar"})), labelSelector: labels.SelectorFromSet(labels.Set(map[string]string{"foo": "bar"})),
fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"foo": "bar"}).ConfigMap),
}, },
{ {
name: "non-matching label selector correctly excludes", name: "non-matching label selector correctly excludes",
@ -440,7 +441,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
fileSystem: arktest.NewFakeFileSystem(). fileSystem: arktest.NewFakeFileSystem().
WithFile("configmaps/cm-1.json", newTestConfigMap().WithControllerOwner().ToJSON()). WithFile("configmaps/cm-1.json", newTestConfigMap().WithControllerOwner().ToJSON()).
WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()), WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()),
expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newNamedTestConfigMap("cm-2").ConfigMap),
}, },
{ {
name: "namespace is remapped", name: "namespace is remapped",
@ -448,7 +449,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
resourcePath: "configmaps", resourcePath: "configmaps",
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().WithNamespace("ns-1").ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithNamespace("ns-2").WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().WithNamespace("ns-2").ConfigMap),
}, },
{ {
name: "custom restorer is correctly used", name: "custom restorer is correctly used",
@ -464,7 +465,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
selector: labels.Everything(), selector: labels.Everything(),
}, },
}, },
expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"fake-restorer": "foo"}).WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().WithLabels(map[string]string{"fake-restorer": "foo"}).ConfigMap),
}, },
{ {
name: "custom restorer for different group/resource is not used", name: "custom restorer for different group/resource is not used",
@ -480,7 +481,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
selector: labels.Everything(), selector: labels.Everything(),
}, },
}, },
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
}, },
{ {
name: "cluster-scoped resources are skipped when IncludeClusterResources=false", name: "cluster-scoped resources are skipped when IncludeClusterResources=false",
@ -497,7 +498,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: falsePtr, includeClusterResources: falsePtr,
fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
}, },
{ {
name: "cluster-scoped resources are not skipped when IncludeClusterResources=true", name: "cluster-scoped resources are not skipped when IncludeClusterResources=true",
@ -506,7 +507,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: truePtr, includeClusterResources: truePtr,
fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume), expectedObjs: toUnstructured(newTestPV().PersistentVolume),
}, },
{ {
name: "namespaced resources are not skipped when IncludeClusterResources=true", name: "namespaced resources are not skipped when IncludeClusterResources=true",
@ -515,7 +516,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: truePtr, includeClusterResources: truePtr,
fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
}, },
{ {
name: "cluster-scoped resources are not skipped when IncludeClusterResources=nil", name: "cluster-scoped resources are not skipped when IncludeClusterResources=nil",
@ -524,7 +525,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: nil, includeClusterResources: nil,
fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("persistentvolumes/pv-1.json", newTestPV().ToJSON()),
expectedObjs: toUnstructured(newTestPV().WithArkLabel("my-restore").PersistentVolume), expectedObjs: toUnstructured(newTestPV().PersistentVolume),
}, },
{ {
name: "namespaced resources are not skipped when IncludeClusterResources=nil", name: "namespaced resources are not skipped when IncludeClusterResources=nil",
@ -533,7 +534,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: nil, includeClusterResources: nil,
fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("configmaps/cm-1.json", newTestConfigMap().ToJSON()),
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap), expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
}, },
{ {
name: "serviceaccounts are restored", name: "serviceaccounts are restored",
@ -542,7 +543,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(), labelSelector: labels.NewSelector(),
includeClusterResources: nil, includeClusterResources: nil,
fileSystem: arktest.NewFakeFileSystem().WithFile("serviceaccounts/sa-1.json", newTestServiceAccount().ToJSON()), fileSystem: arktest.NewFakeFileSystem().WithFile("serviceaccounts/sa-1.json", newTestServiceAccount().ToJSON()),
expectedObjs: toUnstructured(newTestServiceAccount().WithArkLabel("my-restore").ServiceAccount), expectedObjs: toUnstructured(newTestServiceAccount().ServiceAccount),
}, },
{ {
name: "non-mirror pods are restored", name: "non-mirror pods are restored",
@ -566,7 +567,6 @@ func TestRestoreResourceForNamespace(t *testing.T) {
WithKind("Pod"). WithKind("Pod").
WithNamespace("ns-1"). WithNamespace("ns-1").
WithName("pod1"). WithName("pod1").
WithArkLabel("my-restore").
Unstructured), Unstructured),
}, },
}, },
@ -594,6 +594,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
resourceClient := &arktest.FakeDynamicClient{} resourceClient := &arktest.FakeDynamicClient{}
for i := range test.expectedObjs { for i := range test.expectedObjs {
addRestoreLabels(&test.expectedObjs[i], "my-restore", "my-backup")
resourceClient.On("Create", &test.expectedObjs[i]).Return(&test.expectedObjs[i], nil) resourceClient.On("Create", &test.expectedObjs[i]).Return(&test.expectedObjs[i], nil)
} }
@ -625,6 +626,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
}, },
Spec: api.RestoreSpec{ Spec: api.RestoreSpec{
IncludeClusterResources: test.includeClusterResources, IncludeClusterResources: test.includeClusterResources,
BackupName: "my-backup",
}, },
}, },
backup: &api.Backup{}, backup: &api.Backup{},
@ -679,8 +681,7 @@ func TestRestoringExistingServiceAccount(t *testing.T) {
m[k] = v m[k] = v
} }
fromBackupWithLabel := &unstructured.Unstructured{Object: m} fromBackupWithLabel := &unstructured.Unstructured{Object: m}
l := map[string]string{api.RestoreLabelKey: "my-restore"} addRestoreLabels(fromBackupWithLabel, "my-restore", "my-backup")
fromBackupWithLabel.SetLabels(l)
// resetMetadataAndStatus will strip the creationTimestamp before calling Create // resetMetadataAndStatus will strip the creationTimestamp before calling Create
fromBackupWithLabel.SetCreationTimestamp(metav1.Time{Time: time.Time{}}) fromBackupWithLabel.SetCreationTimestamp(metav1.Time{Time: time.Time{}})
@ -711,6 +712,7 @@ func TestRestoringExistingServiceAccount(t *testing.T) {
}, },
Spec: api.RestoreSpec{ Spec: api.RestoreSpec{
IncludeClusterResources: nil, IncludeClusterResources: nil,
BackupName: "my-backup",
}, },
}, },
backup: &api.Backup{}, backup: &api.Backup{},
@ -922,7 +924,7 @@ status:
} }
resetMetadataAndStatus(unstructuredPV) resetMetadataAndStatus(unstructuredPV)
addLabel(unstructuredPV, api.RestoreLabelKey, ctx.restore.Name) addRestoreLabels(unstructuredPV, ctx.restore.Name, ctx.restore.Spec.BackupName)
unstructuredPV.Object["foo"] = "bar" unstructuredPV.Object["foo"] = "bar"
if test.expectPVCreation { if test.expectPVCreation {
@ -964,7 +966,7 @@ status:
unstructuredPVC := &unstructured.Unstructured{Object: unstructuredPVCMap} unstructuredPVC := &unstructured.Unstructured{Object: unstructuredPVCMap}
resetMetadataAndStatus(unstructuredPVC) resetMetadataAndStatus(unstructuredPVC)
addLabel(unstructuredPVC, api.RestoreLabelKey, ctx.restore.Name) addRestoreLabels(unstructuredPVC, ctx.restore.Name, ctx.restore.Spec.BackupName)
createdPVC := unstructuredPVC.DeepCopy() createdPVC := unstructuredPVC.DeepCopy()
// just to ensure we have the data flowing correctly // just to ensure we have the data flowing correctly
@ -1431,17 +1433,6 @@ func (obj *testUnstructured) WithName(name string) *testUnstructured {
return obj.WithMetadataField("name", name) return obj.WithMetadataField("name", name)
} }
func (obj *testUnstructured) WithArkLabel(restoreName string) *testUnstructured {
ls := obj.GetLabels()
if ls == nil {
ls = make(map[string]string)
}
ls[api.RestoreLabelKey] = restoreName
obj.SetLabels(ls)
return obj
}
func (obj *testUnstructured) ToJSON() []byte { func (obj *testUnstructured) ToJSON() []byte {
bytes, err := json.Marshal(obj.Object) bytes, err := json.Marshal(obj.Object)
if err != nil { if err != nil {
@ -1523,14 +1514,6 @@ func newTestServiceAccount() *testServiceAccount {
} }
} }
func (sa *testServiceAccount) WithArkLabel(restoreName string) *testServiceAccount {
if sa.Labels == nil {
sa.Labels = make(map[string]string)
}
sa.Labels[api.RestoreLabelKey] = restoreName
return sa
}
func (sa *testServiceAccount) WithImagePullSecret(name string) *testServiceAccount { func (sa *testServiceAccount) WithImagePullSecret(name string) *testServiceAccount {
secret := v1.LocalObjectReference{Name: name} secret := v1.LocalObjectReference{Name: name}
sa.ImagePullSecrets = append(sa.ImagePullSecrets, secret) sa.ImagePullSecrets = append(sa.ImagePullSecrets, secret)
@ -1567,14 +1550,6 @@ func newTestPV() *testPersistentVolume {
} }
} }
func (pv *testPersistentVolume) WithArkLabel(restoreName string) *testPersistentVolume {
if pv.Labels == nil {
pv.Labels = make(map[string]string)
}
pv.Labels[api.RestoreLabelKey] = restoreName
return pv
}
func (pv *testPersistentVolume) ToJSON() []byte { func (pv *testPersistentVolume) ToJSON() []byte {
bytes, _ := json.Marshal(pv.PersistentVolume) bytes, _ := json.Marshal(pv.PersistentVolume)
return bytes return bytes
@ -1629,16 +1604,6 @@ func newNamedTestConfigMap(name string) *testConfigMap {
} }
} }
func (cm *testConfigMap) WithArkLabel(restoreName string) *testConfigMap {
if cm.Labels == nil {
cm.Labels = make(map[string]string)
}
cm.Labels[api.RestoreLabelKey] = restoreName
return cm
}
func (cm *testConfigMap) WithNamespace(name string) *testConfigMap { func (cm *testConfigMap) WithNamespace(name string) *testConfigMap {
cm.Namespace = name cm.Namespace = name
return cm return cm