Skip to content

Commit

Permalink
start using a namespaced label on restored objects, deprecate old label
Browse files Browse the repository at this point in the history
Signed-off-by: Steve Kriss <steve@heptio.com>
  • Loading branch information
skriss committed Aug 8, 2018
1 parent 27003af commit 0c3ac67
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 65 deletions.
2 changes: 1 addition & 1 deletion docs/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.

Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/ark/v1/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
// RestoreLabelKey is the label key that's applied to all resources that
// are created during a restore. This is applied for ease of identification
// 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"

// ClusterScopedDir is the name of the directory containing cluster-scoped
Expand Down
20 changes: 13 additions & 7 deletions pkg/restore/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,8 +760,9 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a
obj.SetNamespace(namespace)
}

// add an ark-restore label to each resource for easy ID
addLabel(obj, api.RestoreLabelKey, ctx.restore.Name)
// label the resource with the restore's name for easy identification
// of all cluster resources created by this restore
addRestoreLabel(obj, ctx.restore.Name)

ctx.infof("Restoring %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr := resourceClient.Create(obj)
Expand All @@ -782,8 +783,8 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a

// We know the cluster won't have the restore name label, so
// copy it over from the backup
restoreName := obj.GetLabels()[api.RestoreLabelKey]
addLabel(fromCluster, api.RestoreLabelKey, restoreName)
restoreName := obj.GetLabels()[api.RestoreNameLabel]
addRestoreLabel(fromCluster, restoreName)

if !equality.Semantic.DeepEqual(fromCluster, obj) {
switch groupResource {
Expand Down Expand Up @@ -997,15 +998,20 @@ func resetMetadataAndStatus(obj *unstructured.Unstructured) (*unstructured.Unstr
return obj, nil
}

// addLabel applies the specified key/value to an object as a label.
func addLabel(obj *unstructured.Unstructured, key string, val string) {
// addRestoreLabel applies the specified key/value to an object as a label.
func addRestoreLabel(obj metav1.Object, restoreName string) {
labels := obj.GetLabels()

if labels == nil {
labels = make(map[string]string)
}

labels[key] = val
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)
}
Expand Down
77 changes: 20 additions & 57 deletions pkg/restore/restore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/namespaces/cluster/ns-1.json", newTestNamespace("ns-1").ToJSON())
expectedNS = "ns-2"
expectedObjs = toUnstructured(newTestConfigMap().WithNamespace("ns-2").WithArkLabel("").ConfigMap)
expectedObjs = toUnstructured(newTestConfigMap().WithNamespace("ns-2").ConfigMap)
)

resourceClient := &arktest.FakeDynamicClient{}
for i := range expectedObjs {
addRestoreLabel(&expectedObjs[i], "")
resourceClient.On("Create", &expectedObjs[i]).Return(&expectedObjs[i], nil)
}

Expand Down Expand Up @@ -381,8 +382,8 @@ func TestRestoreResourceForNamespace(t *testing.T) {
WithFile("configmaps/cm-1.json", newNamedTestConfigMap("cm-1").ToJSON()).
WithFile("configmaps/cm-2.json", newNamedTestConfigMap("cm-2").ToJSON()),
expectedObjs: toUnstructured(
newNamedTestConfigMap("cm-1").WithArkLabel("my-restore").ConfigMap,
newNamedTestConfigMap("cm-2").WithArkLabel("my-restore").ConfigMap,
newNamedTestConfigMap("cm-1").ConfigMap,
newNamedTestConfigMap("cm-2").ConfigMap,
),
},
{
Expand Down Expand Up @@ -415,15 +416,15 @@ func TestRestoreResourceForNamespace(t *testing.T) {
"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",
namespace: "ns-1",
resourcePath: "configmaps",
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()),
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",
Expand All @@ -440,15 +441,15 @@ func TestRestoreResourceForNamespace(t *testing.T) {
fileSystem: arktest.NewFakeFileSystem().
WithFile("configmaps/cm-1.json", newTestConfigMap().WithControllerOwner().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",
namespace: "ns-2",
resourcePath: "configmaps",
labelSelector: labels.NewSelector(),
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",
Expand All @@ -464,7 +465,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
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",
Expand All @@ -480,7 +481,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
selector: labels.Everything(),
},
},
expectedObjs: toUnstructured(newTestConfigMap().WithArkLabel("my-restore").ConfigMap),
expectedObjs: toUnstructured(newTestConfigMap().ConfigMap),
},
{
name: "cluster-scoped resources are skipped when IncludeClusterResources=false",
Expand All @@ -497,7 +498,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: falsePtr,
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",
Expand All @@ -506,7 +507,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: truePtr,
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",
Expand All @@ -515,7 +516,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: truePtr,
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",
Expand All @@ -524,7 +525,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: nil,
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",
Expand All @@ -533,7 +534,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: nil,
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",
Expand All @@ -542,7 +543,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
labelSelector: labels.NewSelector(),
includeClusterResources: nil,
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",
Expand All @@ -566,7 +567,6 @@ func TestRestoreResourceForNamespace(t *testing.T) {
WithKind("Pod").
WithNamespace("ns-1").
WithName("pod1").
WithArkLabel("my-restore").
Unstructured),
},
},
Expand Down Expand Up @@ -594,6 +594,7 @@ func TestRestoreResourceForNamespace(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
resourceClient := &arktest.FakeDynamicClient{}
for i := range test.expectedObjs {
addRestoreLabel(&test.expectedObjs[i], "my-restore")
resourceClient.On("Create", &test.expectedObjs[i]).Return(&test.expectedObjs[i], nil)
}

Expand Down Expand Up @@ -679,8 +680,7 @@ func TestRestoringExistingServiceAccount(t *testing.T) {
m[k] = v
}
fromBackupWithLabel := &unstructured.Unstructured{Object: m}
l := map[string]string{api.RestoreLabelKey: "my-restore"}
fromBackupWithLabel.SetLabels(l)
addRestoreLabel(fromBackupWithLabel, "my-restore")
// resetMetadataAndStatus will strip the creationTimestamp before calling Create
fromBackupWithLabel.SetCreationTimestamp(metav1.Time{Time: time.Time{}})

Expand Down Expand Up @@ -922,7 +922,7 @@ status:
}

resetMetadataAndStatus(unstructuredPV)
addLabel(unstructuredPV, api.RestoreLabelKey, ctx.restore.Name)
addRestoreLabel(unstructuredPV, ctx.restore.Name)
unstructuredPV.Object["foo"] = "bar"

if test.expectPVCreation {
Expand Down Expand Up @@ -964,7 +964,7 @@ status:
unstructuredPVC := &unstructured.Unstructured{Object: unstructuredPVCMap}

resetMetadataAndStatus(unstructuredPVC)
addLabel(unstructuredPVC, api.RestoreLabelKey, ctx.restore.Name)
addRestoreLabel(unstructuredPVC, ctx.restore.Name)

createdPVC := unstructuredPVC.DeepCopy()
// just to ensure we have the data flowing correctly
Expand Down Expand Up @@ -1422,17 +1422,6 @@ func (obj *testUnstructured) WithName(name string) *testUnstructured {
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 {
bytes, err := json.Marshal(obj.Object)
if err != nil {
Expand Down Expand Up @@ -1514,14 +1503,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 {
secret := v1.LocalObjectReference{Name: name}
sa.ImagePullSecrets = append(sa.ImagePullSecrets, secret)
Expand Down Expand Up @@ -1558,14 +1539,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 {
bytes, _ := json.Marshal(pv.PersistentVolume)
return bytes
Expand Down Expand Up @@ -1620,16 +1593,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 {
cm.Namespace = name
return cm
Expand Down

0 comments on commit 0c3ac67

Please sign in to comment.