Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage: Add support for storage bucket backup (from Incus) #13924

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
662ef24
api: Add storage_bucket_backup extension
maveonair Jan 8, 2024
032510c
shared/api: Add storage bucket backup
maveonair Jan 8, 2024
166b1cb
lxd/db: Add storage bucket backup functions
maveonair Jan 29, 2024
74f7afe
lxd/db/operation: Add storage volume backup types
maveonair Jan 29, 2024
896e5b4
lxd/lifecycle: Add storage bucket backup events
maveonair Jan 29, 2024
7257282
lxd/project: Add StorageBucket function
maveonair Jan 29, 2024
1e8c539
lxd/storage/s3: Add transfer manager
maveonair Jan 29, 2024
e7d81f5
lxd: Add storage bucket backup
maveonair Jan 29, 2024
373592f
lxd/auth/drivers: Add can_manage_backups to type storage_bucket
boltmark Oct 15, 2024
04e3815
lxd/auth: Run make update-auth
boltmark Oct 15, 2024
923ce48
lxd/metadata: Run make update-metadata
boltmark Oct 15, 2024
52bc707
doc: Run make update-metadata
boltmark Oct 15, 2024
2865d36
client: Add storage bucket backup
maveonair Jan 29, 2024
6382d13
lxc: Add storage bucket import/export
maveonair Feb 19, 2024
e88f2c5
doc/rest-api: Refresh swagger YAML
maveonair Jan 29, 2024
88b8d05
lxc: Use errors.New instead of fmt.Errorf
boltmark Aug 30, 2024
f33b137
.github/workflows: Update max lxc binary size to 16MiB
boltmark Feb 5, 2025
e9159f9
test: Move s3cmdrun to test/includes
boltmark Feb 7, 2025
8a916f7
test: Add testing for storage bucket backups
boltmark Feb 7, 2025
92a5ee2
.github/workflows: Add storage_bucket_export to LXD_REQUIRED_TESTS
boltmark Feb 7, 2025
0b223d7
i18n: Update translation templates
boltmark Feb 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- cron: '0 0 * * *' # Test TICS daily

env:
LXD_REQUIRED_TESTS: "storage_buckets,network_ovn"
LXD_REQUIRED_TESTS: "storage_buckets,storage_bucket_export,network_ovn"
LXD_SKIP_TESTS: "clustering_upgrade clustering_upgrade_large"
GOCOVERDIR: ${{ ( github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' ) && '/home/runner/work/lxd/lxd/coverage' || '' }}

Expand Down Expand Up @@ -131,7 +131,7 @@ jobs:
strip -s /tmp/bin/*

# bin/max (sizes are in MiB)
SIZES="lxc 15
SIZES="lxc 16
lxd-agent 13"
MIB="$((1024 * 1024))"

Expand Down
16 changes: 16 additions & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ type InstanceServer interface {
UpdateStoragePoolBucketKey(poolName string, bucketName string, keyName string, key api.StorageBucketKeyPut, ETag string) (err error)
DeleteStoragePoolBucketKey(poolName string, bucketName string, keyName string) (err error)

// Storage bucket backup functions ("storage_bucket_backup" API extension)
CreateStoragePoolBucketBackup(poolName string, bucketName string, backup api.StorageBucketBackupsPost) (op Operation, err error)
DeleteStoragePoolBucketBackup(pool string, bucketName string, name string) (op Operation, err error)
GetStoragePoolBucketBackupFile(pool string, bucketName string, name string, req *BackupFileRequest) (resp *BackupFileResponse, err error)
CreateStoragePoolBucketFromBackup(pool string, args StoragePoolBucketBackupArgs) (op Operation, err error)

// List all volumes functions ("storage_volumes_all" API extension)
GetVolumesWithFilter(filters []string) (volumes []api.StorageVolume, err error)
GetVolumesWithFilterAllProjects(filters []string) (volumes []api.StorageVolume, err error)
Expand Down Expand Up @@ -754,3 +760,13 @@ type GetPermissionsArgs struct {
// level permissions will not be returned.
ProjectName string
}

// The StoragePoolBucketBackupArgs struct is used when creating a storage volume from a backup.
// API extension: storage_bucket_backup.
type StoragePoolBucketBackupArgs struct {
// The backup file
BackupFile io.Reader

// Name to import backup as
Name string
}
157 changes: 157 additions & 0 deletions client/lxd_storage_buckets.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package lxd

import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"

"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/cancel"
"github.com/canonical/lxd/shared/ioprogress"
"github.com/canonical/lxd/shared/units"
)

// GetStoragePoolBucketNames returns a list of storage bucket names.
Expand Down Expand Up @@ -233,3 +242,151 @@ func (r *ProtocolLXD) DeleteStoragePoolBucketKey(poolName string, bucketName str

return nil
}

// CreateStoragePoolBucketBackup creates a new storage bucket backup.
func (r *ProtocolLXD) CreateStoragePoolBucketBackup(poolName string, bucketName string, backup api.StorageBucketBackupsPost) (Operation, error) {
err := r.CheckExtension("storage_bucket_backup")
if err != nil {
return nil, err
}

op, _, err := r.queryOperation("POST", "/storage-pools/"+url.PathEscape(poolName)+"/buckets/"+url.PathEscape(bucketName)+"/backups", backup, "", true)
if err != nil {
return nil, err
}

return op, nil
}

// DeleteStoragePoolBucketBackup deletes an existing storage bucket backup.
func (r *ProtocolLXD) DeleteStoragePoolBucketBackup(pool string, bucketName string, name string) (Operation, error) {
err := r.CheckExtension("storage_bucket_backup")
if err != nil {
return nil, err
}

op, _, err := r.queryOperation("DELETE", "/storage-pools/"+url.PathEscape(pool)+"/buckets/"+url.PathEscape(bucketName)+"/backups/"+url.PathEscape(name), nil, "", true)
if err != nil {
return nil, err
}

return op, nil
}

// GetStoragePoolBucketBackupFile returns the storage bucket file.
func (r *ProtocolLXD) GetStoragePoolBucketBackupFile(pool string, bucketName string, name string, req *BackupFileRequest) (*BackupFileResponse, error) {
err := r.CheckExtension("storage_bucket_backup")
if err != nil {
return nil, err
}

// Build the URL
uri := r.httpBaseURL.String() + "/1.0/storage-pools/" + url.PathEscape(pool) + "/buckets/" + url.PathEscape(bucketName) + "/backups/" + url.PathEscape(name) + "/export"

if r.project != "" {
uri += "?project=" + url.QueryEscape(r.project)
}

// Prepare the download request
request, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}

if r.httpUserAgent != "" {
request.Header.Set("User-Agent", r.httpUserAgent)
}

// Start the request
response, doneCh, err := cancel.CancelableDownload(req.Canceler, r.DoHTTP, request)
if err != nil {
return nil, err
}

defer func() { _ = response.Body.Close() }()
defer close(doneCh)

if response.StatusCode != http.StatusOK {
_, _, err := lxdParseResponse(response)
if err != nil {
return nil, err
}
}

// Handle the data
body := response.Body
if req.ProgressHandler != nil {
body = &ioprogress.ProgressReader{
ReadCloser: response.Body,
Tracker: &ioprogress.ProgressTracker{
Length: response.ContentLength,
Handler: func(percent int64, speed int64) {
req.ProgressHandler(ioprogress.ProgressData{Text: strconv.FormatInt(percent, 10) + "% (" + units.GetByteSizeString(speed, 2) + "/s)"})
},
},
}
}

size, err := io.Copy(req.BackupFile, body)
if err != nil {
return nil, err
}

resp := BackupFileResponse{}
resp.Size = size

return &resp, nil
}

// CreateStoragePoolBucketFromBackup creates a storage pool bucket using a backup.
func (r *ProtocolLXD) CreateStoragePoolBucketFromBackup(pool string, args StoragePoolBucketBackupArgs) (Operation, error) {
if !r.HasExtension("storage_bucket_backup") {
return nil, fmt.Errorf(`The server is missing the required "custom_volume_backup" API extension`)
}

path := "/storage-pools/" + url.PathEscape(pool) + "/buckets"

// Prepare the HTTP request.
reqURL, err := r.setQueryAttributes(r.httpBaseURL.String() + "/1.0" + path)
if err != nil {
return nil, err
}

req, err := http.NewRequest("POST", reqURL, args.BackupFile)
if err != nil {
return nil, err
}

req.Header.Set("Content-Type", "application/octet-stream")

if args.Name != "" {
req.Header.Set("X-LXD-name", args.Name)
}

// Send the request.
resp, err := r.DoHTTP(req)
if err != nil {
return nil, err
}

defer func() { _ = resp.Body.Close() }()

// Handle errors.
response, _, err := lxdParseResponse(resp)
if err != nil {
return nil, err
}

respOperation, err := response.MetadataAsOperation()
if err != nil {
return nil, err
}

op := operation{
Operation: *respOperation,
r: r,
chActive: make(chan bool),
}

return &op, nil
}
15 changes: 15 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2599,3 +2599,18 @@ The following pool level configuration keys have been added:
1. {config:option}`storage-pure-pool-conf:pure.api.token`
1. {config:option}`storage-pure-pool-conf:pure.mode`
1. {config:option}`storage-pure-pool-conf:pure.target`

## `storage_bucket_backup`

Add storage bucket backup support.

This includes the following new endpoints (see [RESTful API](rest-api.md) for details):

* `GET /1.0/storage-pools/<pool>/buckets/<bucket>/backups`
* `POST /1.0/storage-pools/<pool>/buckets/<bucket>/backups`

* `GET /1.0/storage-pools/<pool>/buckets/<bucket>/backups/<name>`
* `POST /1.0/storage-pools/<pool>/buckets/<bucket>/backups/<name>`
* `DELETE /1.0/storage-pools/<pool>/buckets/<bucket>/backups/<name>`

* `GET /1.0/storage-pools/<pool>/buckets/<bucket>/backups/<name>/export`
3 changes: 3 additions & 0 deletions doc/metadata.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6817,6 +6817,9 @@ using the `zfs` command in the container.
`can_view`
: Grants permission to view the storage bucket.

`can_manage_backups`
: Grants permission to create and delete backups of the storage bucket.


<!-- entity group storage_bucket end -->
<!-- entity group storage_pool start -->
Expand Down
Loading
Loading