Skip to content

Commit 8bc7e4f

Browse files
committed
store backups & restores in backups/, restores/ subdirs in obj storage
Signed-off-by: Steve Kriss <steve@heptio.com>
1 parent 889b220 commit 8bc7e4f

10 files changed

+475
-121
lines changed

docs/storage-layout-reorg-v0.10.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Object Storage Layout Changes in v0.10
2+
3+
## Overview
4+
5+
Ark v0.10 includes breaking changes to where data is stored in your object storage bucket. You'll need to run a [one-time migration procedure](#upgrading-to-v0.10)
6+
if you're upgrading from prior versions of Ark.
7+
8+
## Details
9+
10+
Prior to v0.10, Ark stored data in an object storage bucket using the following structure:
11+
12+
```
13+
<your-bucket>/
14+
backup-1/
15+
ark-backup.json
16+
backup-1.tar.gz
17+
backup-1-logs.gz
18+
restore-of-backup-1-logs.gz
19+
restore-of-backup-1-results.gz
20+
backup-2/
21+
ark-backup.json
22+
backup-2.tar.gz
23+
backup-2-logs.gz
24+
restore-of-backup-2-logs.gz
25+
restore-of-backup-2-results.gz
26+
...
27+
```
28+
29+
As of v0.10, we've reorganized this layout to provide a cleaner and more extensible directory structure. The new layout looks like:
30+
31+
```
32+
<your-bucket>[/<your-prefix>]/
33+
backups/
34+
backup-1/
35+
ark-backup.json
36+
backup-1.tar.gz
37+
backup-1-logs.gz
38+
backup-2/
39+
ark-backup.json
40+
backup-2.tar.gz
41+
backup-2-logs.gz
42+
...
43+
restores/
44+
restore-of-backup-1/
45+
restore-of-backup-1-logs.gz
46+
restore-of-backup-1-results.gz
47+
restore-of-backup-2/
48+
restore-of-backup-2-logs.gz
49+
restore-of-backup-2-results.gz
50+
...
51+
...
52+
```
53+
54+
## Upgrading to v0.10
55+
56+
Before upgrading to v0.10, you'll need to run a one-time upgrade script to rearrange the contents of your existing Ark bucket(s) to be compatible with
57+
the new layout.
58+
59+
Please note that the following scripts **will not** migrate existing restore logs/results into the new `restores/` subdirectory. This means that they
60+
will not be accessible using `ark restore describe` or `ark restore logs`. They *will* remain in the relevant backup's subdirectory so they are manually
61+
accessible, and will eventually be garbage-collected along with the backup. We've taken this approach in order to keep the migration scripts simple
62+
and less error-prone.
63+
64+
### rclone-Based Script
65+
66+
This script uses [rclone][1], which you can download and install following the instructions [here][2].
67+
Please read through the script carefully before starting and execute it step-by-step.
68+
69+
```bash
70+
ARK_BUCKET=<your-ark-bucket>
71+
ARK_TEMP_MIGRATION_BUCKET=<a-temp-bucket-for-migration>
72+
73+
# 1. This is an interactive step that configures rclone to be
74+
# able to access your storage provider. Follow the instructions,
75+
# and keep track of the "remote name" for the next step:
76+
rclone config
77+
78+
# 2. Store the name of the rclone remote that you just set up
79+
# in Step #1:
80+
RCLONE_REMOTE_NAME=<your-remote-name>
81+
82+
# 3. Create a temporary bucket to be used as a backup of your
83+
# current Ark bucket's contents:
84+
rclone mkdir ${RCLONE_REMOTE_NAME}:${ARK_TEMP_MIGRATION_BUCKET}
85+
86+
# 4. Do a full copy of the contents of your Ark bucket into the
87+
# temporary bucket:
88+
rclone copy ${RCLONE_REMOTE_NAME}:${ARK_BUCKET} ${RCLONE_REMOTE_NAME}:${ARK_TEMP_MIGRATION_BUCKET}
89+
90+
# 5. Verify that the temporary bucket contains an exact copy of
91+
# your Ark bucket's contents. You should see a short block
92+
# of output stating "0 differences found":
93+
rclone check ${RCLONE_REMOTE_NAME}:${ARK_BUCKET} ${RCLONE_REMOTE_NAME}:${ARK_TEMP_MIGRATION_BUCKET}
94+
95+
# 6. Delete your Ark bucket's contents (this command does not
96+
# delete the bucket itself, only the contents):
97+
rclone delete ${RCLONE_REMOTE_NAME}:${ARK_BUCKET}
98+
99+
# 7. Copy the contents of the temporary bucket into your Ark bucket,
100+
# under the 'backups/' directory/prefix:
101+
rclone copy ${RCLONE_REMOTE_NAME}:${ARK_TEMP_MIGRATION_BUCKET} ${RCLONE_REMOTE_NAME}:${ARK_BUCKET}/backups
102+
103+
# 8. Verify that the 'backups/' directory in your Ark bucket now
104+
# contains an exact copy of the temporary bucket's contents:
105+
rclone check ${RCLONE_REMOTE_NAME}:${ARK_BUCKET}/backups ${RCLONE_REMOTE_NAME}:${ARK_TEMP_MIGRATION_BUCKET}
106+
107+
# 9. Once you've confirmed that Ark v0.10 works with your revised Ark
108+
# bucket, you can delete the temporary migration bucket.
109+
```
110+
111+
[1]: https://rclone.org/
112+
[2]: https://rclone.org/downloads/

pkg/cmd/server/server.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import (
6262
clientset "github.com/heptio/ark/pkg/generated/clientset/versioned"
6363
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
6464
"github.com/heptio/ark/pkg/metrics"
65+
"github.com/heptio/ark/pkg/persistence"
6566
"github.com/heptio/ark/pkg/plugin"
6667
"github.com/heptio/ark/pkg/podexec"
6768
"github.com/heptio/ark/pkg/restic"
@@ -252,11 +253,14 @@ func (s *server) run() error {
252253
return err
253254
}
254255

255-
// check to ensure all Ark CRDs exist
256256
if err := s.arkResourcesExist(); err != nil {
257257
return err
258258
}
259259

260+
if err := s.validateBackupStorageLocations(); err != nil {
261+
return err
262+
}
263+
260264
originalConfig, err := s.loadConfig()
261265
if err != nil {
262266
return err
@@ -391,6 +395,39 @@ func (s *server) arkResourcesExist() error {
391395
return nil
392396
}
393397

398+
// validateBackupStorageLocations checks to ensure all backup storage locations exist
399+
// and have a compatible layout, and returns an error if not.
400+
func (s *server) validateBackupStorageLocations() error {
401+
s.logger.Info("Checking that all backup storage locations are valid")
402+
403+
locations, err := s.arkClient.ArkV1().BackupStorageLocations(s.namespace).List(metav1.ListOptions{})
404+
if err != nil {
405+
return errors.WithStack(err)
406+
}
407+
408+
var invalid []string
409+
for _, location := range locations.Items {
410+
backupStore, err := persistence.NewObjectBackupStore(&location, s.pluginManager, s.logger)
411+
if err != nil {
412+
invalid = append(invalid, errors.Wrapf(err, "error getting backup store for location %q", location.Name).Error())
413+
continue
414+
}
415+
416+
if err := backupStore.IsValid(); err != nil {
417+
invalid = append(invalid, errors.Wrapf(err,
418+
"backup store for location %q is invalid (if upgrading from a pre-v0.10 version of Ark, please refer to https://heptio.github.io/ark/v0.10.0/storage-layout-reorg-v0.10 for instructions)",
419+
location.Name,
420+
).Error())
421+
}
422+
}
423+
424+
if len(invalid) > 0 {
425+
return errors.Errorf("some backup storage locations are invalid: %s", strings.Join(invalid, "; "))
426+
}
427+
428+
return nil
429+
}
430+
394431
func (s *server) loadConfig() (*api.Config, error) {
395432
s.logger.Info("Retrieving Ark configuration")
396433
var (

pkg/controller/backup_deletion_controller.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,15 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
261261
}
262262

263263
log.Info("Removing backup from backup storage")
264-
if err := c.deleteBackupFromStorage(backup, log); err != nil {
264+
pluginManager := c.newPluginManager(log)
265+
defer pluginManager.CleanupClients()
266+
267+
backupStore, backupStoreErr := c.backupStoreForBackup(backup, pluginManager, log)
268+
if backupStoreErr != nil {
269+
errs = append(errs, backupStoreErr.Error())
270+
}
271+
272+
if err := backupStore.DeleteBackup(backup.Name); err != nil {
265273
errs = append(errs, err.Error())
266274
}
267275

@@ -276,6 +284,13 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
276284

277285
restoreLog := log.WithField("restore", kube.NamespaceAndName(restore))
278286

287+
restoreLog.Info("Deleting restore log/results from backup storage")
288+
if err := backupStore.DeleteRestore(restore.Name); err != nil {
289+
errs = append(errs, err.Error())
290+
// if we couldn't delete the restore files, don't delete the API object
291+
continue
292+
}
293+
279294
restoreLog.Info("Deleting restore referencing backup")
280295
if err := c.restoreClient.Restores(restore.Namespace).Delete(restore.Name, &metav1.DeleteOptions{}); err != nil {
281296
errs = append(errs, errors.Wrapf(err, "error deleting restore %s", kube.NamespaceAndName(restore)).Error())
@@ -313,27 +328,19 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
313328
return nil
314329
}
315330

316-
func (c *backupDeletionController) deleteBackupFromStorage(backup *v1.Backup, log logrus.FieldLogger) error {
317-
pluginManager := c.newPluginManager(log)
318-
defer pluginManager.CleanupClients()
319-
331+
func (c *backupDeletionController) backupStoreForBackup(backup *v1.Backup, pluginManager plugin.Manager, log logrus.FieldLogger) (persistence.BackupStore, error) {
320332
backupLocation, err := c.backupLocationLister.BackupStorageLocations(backup.Namespace).Get(backup.Spec.StorageLocation)
321333
if err != nil {
322-
return errors.WithStack(err)
334+
return nil, errors.WithStack(err)
323335
}
324336

325337
backupStore, err := c.newBackupStore(backupLocation, pluginManager, log)
326338
if err != nil {
327-
return err
339+
return nil, err
328340
}
329341

330-
if err := backupStore.DeleteBackup(backup.Name); err != nil {
331-
return errors.Wrap(err, "error deleting backup from backup storage")
332-
}
333-
334-
return nil
342+
return backupStore, nil
335343
}
336-
337344
func (c *backupDeletionController) deleteExistingDeletionRequests(req *v1.DeleteBackupRequest, log logrus.FieldLogger) []error {
338345
log.Info("Removing existing deletion requests for backup")
339346
selector := labels.SelectorFromSet(labels.Set(map[string]string{

pkg/controller/backup_deletion_controller_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
410410
})
411411

412412
td.backupStore.On("DeleteBackup", td.req.Spec.BackupName).Return(nil)
413+
td.backupStore.On("DeleteRestore", "restore-1").Return(nil)
414+
td.backupStore.On("DeleteRestore", "restore-2").Return(nil)
413415

414416
err := td.controller.processRequest(td.req)
415417
require.NoError(t, err)

pkg/controller/download_request_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
180180
return errors.WithStack(err)
181181
}
182182

183-
if update.Status.DownloadURL, err = backupStore.GetDownloadURL(backupName, downloadRequest.Spec.Target); err != nil {
183+
if update.Status.DownloadURL, err = backupStore.GetDownloadURL(downloadRequest.Spec.Target); err != nil {
184184
return err
185185
}
186186

pkg/controller/download_request_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ func TestProcessDownloadRequest(t *testing.T) {
274274
}
275275

276276
if tc.expectGetsURL {
277-
harness.backupStore.On("GetDownloadURL", tc.backup.Name, tc.downloadRequest.Spec.Target).Return("a-url", nil)
277+
harness.backupStore.On("GetDownloadURL", tc.downloadRequest.Spec.Target).Return("a-url", nil)
278278
}
279279

280280
// exercise method under test

pkg/persistence/mocks/backup_store.go

Lines changed: 35 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)