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

Volumes: Add remote copy support for snapshots #12045

Merged
merged 12 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions client/lxd_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func (r *ProtocolLXD) UpdateServer(server api.ServerPut, ETag string) error {
}

// HasExtension returns true if the server supports a given API extension.
// Deprecated: Use CheckExtension instead.
func (r *ProtocolLXD) HasExtension(extension string) bool {
// If no cached API information, just assume we're good
// This is needed for those rare cases where we must avoid a GetServer call
Expand Down
27 changes: 25 additions & 2 deletions client/lxd_storage_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,31 @@ func (r *ProtocolLXD) MigrateStoragePoolVolume(pool string, volume api.StorageVo
return nil, fmt.Errorf("Can't ask for a rename through MigrateStoragePoolVolume")
}

var req any
var path string

srcVolParentName, srcVolSnapName, srcIsSnapshot := api.GetParentAndSnapshotName(volume.Name)
if srcIsSnapshot {
err := r.CheckExtension("storage_api_remote_volume_snapshot_copy")
if err != nil {
return nil, err
}

// Set the actual name of the snapshot without delimiter.
req = api.StorageVolumeSnapshotPost{
Name: srcVolSnapName,
Migration: volume.Migration,
Target: volume.Target,
}

path = api.NewURL().Path("storage-pools", pool, "volumes", "custom", srcVolParentName, "snapshots", srcVolSnapName).String()
} else {
req = volume
path = api.NewURL().Path("storage-pools", pool, "volumes", "custom", volume.Name).String()
}

// Send the request
path := fmt.Sprintf("/storage-pools/%s/volumes/custom/%s", url.PathEscape(pool), volume.Name)
op, _, err := r.queryOperation("POST", path, volume, "")
op, _, err := r.queryOperation("POST", path, req, "")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -533,6 +555,7 @@ func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, source InstanceServer,
return nil, fmt.Errorf("Failed to get destination connection info: %w", err)
}

// Copy the storage pool volume locally.
if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (volume.Location == r.clusterTarget || (volume.Location == "none" && r.clusterTarget == "")) {
// Project handling
if destInfo.Project != sourceInfo.Project {
Expand Down
6 changes: 5 additions & 1 deletion doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2264,4 +2264,8 @@ This adds the possibility to list a LXD deployment's network allocations.

Through the `lxc network list-allocations` command and the `--project <PROJECT> | --all-projects` flags,
you can list all the used IP addresses, hardware addresses (for instances), resource URIs and whether it uses NAT for
each `instance`, `network`, `network forward` and `network load-balancer`.
each `instance`, `network`, `network forward` and `network load-balancer`.

## `storage_api_remote_volume_snapshot_copy`

This allows copying storage volume snapshots to and from remotes.
7 changes: 7 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5929,11 +5929,18 @@ definitions:
StorageVolumeSnapshotPost:
description: StorageVolumeSnapshotPost represents the fields required to rename/move a LXD storage volume snapshot
properties:
migration:
description: Initiate volume snapshot migration
example: false
type: boolean
x-go-name: Migration
name:
description: New snapshot name
example: snap1
type: string
x-go-name: Name
target:
$ref: '#/definitions/StorageVolumePostTarget'
type: object
x-go-package: github.com/canonical/lxd/shared/api
StorageVolumeSnapshotPut:
Expand Down
6 changes: 5 additions & 1 deletion lxc/storage_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ func (c *cmdStorageVolumeCopy) Run(cmd *cobra.Command, args []string) error {
return err
}

if srcIsSnapshot && c.flagVolumeOnly {
return fmt.Errorf("Cannot set --volume-only when copying a snapshot")
}

// If the volume is in local storage, set the target to its location (or provide a helpful error
// message if the target is incorrect). If the volume is in remote storage (and the source server is clustered) we
// can use any provided target. Note that for standalone servers, this will set the target to "none".
Expand Down Expand Up @@ -638,7 +642,7 @@ func (c *cmdStorageVolumeDelete) Run(cmd *cobra.Command, args []string) error {
// Parse the input
volName, volType := c.storageVolume.parseVolume("custom", args[1])

// If a target was specified, create the volume on the given member.
// If a target was specified, delete the volume on the given member.
if c.storage.flagTarget != "" {
client = client.UseTarget(c.storage.flagTarget)
}
Expand Down
22 changes: 14 additions & 8 deletions lxd/storage/drivers/driver_zfs_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1180,15 +1180,21 @@ func (d *zfs) createVolumeFromMigrationOptimized(vol Volume, conn io.ReadWriteCl

// RefreshVolume updates an existing volume to match the state of another.
func (d *zfs) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, allowInconsistent bool, op *operations.Operation) error {
// Get target snapshots
targetSnapshots, err := vol.Snapshots(op)
if err != nil {
return fmt.Errorf("Failed to get target snapshots: %w", err)
}
var err error
var targetSnapshots []Volume
var srcSnapshotsAll []Volume

srcSnapshotsAll, err := srcVol.Snapshots(op)
if err != nil {
return fmt.Errorf("Failed to get source snapshots: %w", err)
if !srcVol.IsSnapshot() {
// Get target snapshots
targetSnapshots, err = vol.Snapshots(op)
if err != nil {
return fmt.Errorf("Failed to get target snapshots: %w", err)
}

srcSnapshotsAll, err = srcVol.Snapshots(op)
if err != nil {
return fmt.Errorf("Failed to get source snapshots: %w", err)
}
}

// If there are no target or source snapshots, perform a simple copy using zfs.
Expand Down
7 changes: 6 additions & 1 deletion lxd/storage_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,12 @@ func storagePoolVolumeTypePostMigration(state *state.State, r *http.Request, req
}

resources := map[string][]api.URL{}
resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", poolName, "volumes", "custom", volumeName)}
srcVolParentName, srcVolSnapName, srcIsSnapshot := api.GetParentAndSnapshotName(volumeName)
if srcIsSnapshot {
resources["storage_volume_snapshots"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", poolName, "volumes", "custom", srcVolParentName, "snapshots", srcVolSnapName)}
} else {
resources["storage_volumes"] = []api.URL{*api.NewURL().Path(version.APIVersion, "storage-pools", poolName, "volumes", "custom", volumeName)}
}

run := func(op *operations.Operation) error {
return ws.DoStorage(state, projectName, poolName, volumeName, op)
Expand Down
10 changes: 10 additions & 0 deletions lxd/storage_volumes_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,16 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) response.Resp
return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
}

// This is a migration request so send back requested secrets.
if req.Migration {
req := api.StorageVolumePost{
Name: req.Name,
Target: req.Target,
}

return storagePoolVolumeTypePostMigration(s, r, projectParam(r), projectName, poolName, fullSnapshotName, req)
}

// Rename the snapshot.
snapshotRename := func(op *operations.Operation) error {
pool, err := storagePools.LoadByName(s, poolName)
Expand Down
11 changes: 11 additions & 0 deletions shared/api/storage_pool_volume_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ type StorageVolumeSnapshotPost struct {
// New snapshot name
// Example: snap1
Name string `json:"name" yaml:"name"`

// Initiate volume snapshot migration
// Example: false
//
// API extension: storage_api_remote_volume_snapshot_copy
Migration bool `json:"migration" yaml:"migration"`

// Migration target (for push mode)
//
// API extension: storage_api_remote_volume_snapshot_copy
Target *StorageVolumePostTarget `json:"target" yaml:"target"`
}

// StorageVolumeSnapshot represents a LXD storage volume snapshot
Expand Down
1 change: 1 addition & 0 deletions shared/version/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ var APIExtensions = []string{
"numa_cpu_placement",
"custom_volume_iso",
"network_allocations",
"storage_api_remote_volume_snapshot_copy",
}

// APIExtensionsCount returns the number of available API extensions.
Expand Down
117 changes: 117 additions & 0 deletions test/suites/storage_snapshots.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ test_storage_volume_snapshots() {
LXD_STORAGE_DIR=$(mktemp -d -p "${TEST_DIR}" XXXXXXXXX)
chmod +x "${LXD_STORAGE_DIR}"
spawn_lxd "${LXD_STORAGE_DIR}" false
lxc remote add test "${LXD_ADDR}" --accept-certificate --password foo

# shellcheck disable=2039,3043
local storage_pool storage_volume
storage_pool="lxdtest-$(basename "${LXD_STORAGE_DIR}")-pool"
storage_pool2="${storage_pool}2"
storage_volume="${storage_pool}-vol"

lxc storage create "$storage_pool" "$lxd_backend"
Expand Down Expand Up @@ -98,8 +100,123 @@ test_storage_volume_snapshots() {
lxc storage volume delete "${storage_pool}" "vol1"
lxc storage volume delete "${storage_pool}" "vol1-snap0"

# Check snapshot copy (mode pull).
tomponline marked this conversation as resolved.
Show resolved Hide resolved
lxc launch testimage "c1"
lxc storage volume create "${storage_pool}" "vol1"
lxc storage volume attach "${storage_pool}" "vol1" "c1" /mnt
lxc exec "c1" touch /mnt/foo
lxc delete -f "c1"
lxc storage volume snapshot "${storage_pool}" "vol1" "snap0"
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2" --mode pull
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy (mode pull, remote).
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2" --mode pull
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy (mode push).
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2" --mode push
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy (mode push, remote).
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2" --mode push
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy (mode relay).
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2" --mode relay
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy (mode relay, remote).
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2" --mode relay
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy between pools.
lxc storage create "${storage_pool2}" dir
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool2}/vol2"
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool2}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool2}" "vol2"
lxc storage delete "${storage_pool2}"

# Check snapshot copy between pools (remote).
lxc storage create "${storage_pool2}" dir
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool2}/vol2"
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool2}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool2}" "vol2"
lxc storage volume copy "test:${storage_pool}/vol1/snap0" "${storage_pool2}/vol2"
lxc launch testimage "c1"
lxc storage volume attach "${storage_pool2}" "vol2" "c1" /mnt
lxc exec "c1" -- test -f /mnt/foo
lxc delete -f "c1"
lxc storage volume delete "${storage_pool2}" "vol2"
lxc storage delete "${storage_pool2}"

# Check snapshot volume only copy.
! lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2" --volume-only || false
lxc storage volume copy "${storage_pool}/vol1" "${storage_pool}/vol2" --volume-only
[ "$(lxc query "/1.0/storage-pools/${storage_pool}/volumes/custom/vol2/snapshots" | jq "length == 0")" = "true" ]
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot volume only copy (remote).
! lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2" --volume-only || false
lxc storage volume copy "${storage_pool}/vol1" "test:${storage_pool}/vol2" --volume-only
[ "$(lxc query "/1.0/storage-pools/${storage_pool}/volumes/custom/vol2/snapshots" | jq "length == 0")" = "true" ]
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot refresh.
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2"
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol2" --refresh
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot refresh (remote).
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2"
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol2" --refresh
lxc storage volume delete "${storage_pool}" "vol2"

# Check snapshot copy between projects.
lxc project create project1
lxc storage volume copy "${storage_pool}/vol1/snap0" "${storage_pool}/vol1" --target-project project1
[ "$(lxc query "/1.0/storage-pools/${storage_pool}/volumes?project=project1" | jq "length == 1")" = "true" ]
lxc storage volume delete "${storage_pool}" "vol1" --project project1

# Check snapshot copy between projects (remote).
lxc storage volume copy "${storage_pool}/vol1/snap0" "test:${storage_pool}/vol1" --target-project project1
[ "$(lxc query "/1.0/storage-pools/${storage_pool}/volumes?project=project1" | jq "length == 1")" = "true" ]
lxc storage volume delete "${storage_pool}" "vol1" --project project1

lxc storage volume delete "${storage_pool}" "vol1"
lxc project delete "project1"
lxc storage delete "${storage_pool}"
lxc remote remove "test"

# shellcheck disable=SC2031,2269
LXD_DIR="${LXD_DIR}"
Expand Down