bump storage API: update import/export methods
Bumps APIVER to 9 and resets APIAGE to zero. The import methods (volume_import, volume_import_formats): These additionally get the '$snapshot' parameter which is already present on the export side as an informational piece to know which of the snapshots is the *current* one. This parameter is inserted *in the middle* of the current parameters, so the import & export format methods now have the same signatures. The current "disk" state will be set to this snapshot. This, too, is required for our btrfs implementation. `volume_import_formats` can obviously not make much *use* of this parameter, but it'll still be useful to know that the information is actually available in the import call, so its presence will be checked in the btrfs implementation. Currently this is intended to be used for btrfs send/recv support, which in theory could also get additional metadata similar to how we do the "tar+size" format, however, we currently only really use this within this repository in storage_migrate() which has this information readily available anyway. On the export side (volume_export, volume_export_formats): The `$with_snapshots` option is now "defined" to be an ordered array of snapshots to include, as a hint for storages which need this. (As of the next commit this is only btrfs, and only when also specifying a base snapshot, which is a case we can currently not run into except on the command line interface.) The current providers of the `with_snapshot` option will still treat it as a boolean (since eg. for ZFS you cannot really "skip" snapshots AFAIK). This is mainly intended for storages which do not have a strong association between snapshots and the originals, or an ordering (eg. btrfs and lvm-thin allow creating arbitrary snapshot trees, and with btrfs you can even create a "circular" connection between subvolumes, also we could consider reflink based copies snapshots on xfs in the future maybe?) Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
committed by
Thomas Lamprecht
parent
af50c2e671
commit
3cc29a0487
25
ApiChangeLog
Normal file
25
ApiChangeLog
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# API Versioning ChangeLog
|
||||||
|
|
||||||
|
Our API versioning contains an `APIVER` and an `APIAGE`.
|
||||||
|
The `APIAGE` is the number of versions we're backward compatible with. (iow. things got added
|
||||||
|
without breaking anything unaware of it.)
|
||||||
|
|
||||||
|
Future changes should be documented in here.
|
||||||
|
|
||||||
|
## Version 9: (AGE resets to 0):
|
||||||
|
|
||||||
|
* volume_import_formats gets a new parameter *inserted*:
|
||||||
|
|
||||||
|
Old signature:
|
||||||
|
sub($plugin, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots)
|
||||||
|
New signature:
|
||||||
|
sub($plugin, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots)
|
||||||
|
|
||||||
|
This is now the same as `volume_export_formats`.
|
||||||
|
|
||||||
|
The same goes for calls to `PVE::Storage::volume_import_formats`, which now
|
||||||
|
takes a `$snapshot` parameter in the same place.
|
||||||
|
|
||||||
|
* $with_snapshots *may* now be an array reference containing an ordered list of
|
||||||
|
snapshots, but *may* also just be a boolean, and the contained list *may* be
|
||||||
|
ignored, so it can still be treated as a boolean.
|
||||||
@ -276,12 +276,23 @@ __PACKAGE__->register_method ({
|
|||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
},
|
},
|
||||||
|
'snapshot-list' => {
|
||||||
|
description => "Ordered list of snapshots to transfer",
|
||||||
|
type => 'string',
|
||||||
|
format => 'string-list',
|
||||||
|
optional => 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
|
my $with_snapshots = $param->{'with-snapshots'};
|
||||||
|
if (defined(my $list = $param->{'snapshot-list'})) {
|
||||||
|
$with_snapshots = PVE::Tools::split_list($list);
|
||||||
|
}
|
||||||
|
|
||||||
my $filename = $param->{filename};
|
my $filename = $param->{filename};
|
||||||
|
|
||||||
my $outfh;
|
my $outfh;
|
||||||
@ -295,7 +306,7 @@ __PACKAGE__->register_method ({
|
|||||||
eval {
|
eval {
|
||||||
my $cfg = PVE::Storage::config();
|
my $cfg = PVE::Storage::config();
|
||||||
PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
|
PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
|
||||||
$param->{snapshot}, $param->{base}, $param->{'with-snapshots'});
|
$param->{snapshot}, $param->{base}, $with_snapshots);
|
||||||
};
|
};
|
||||||
my $err = $@;
|
my $err = $@;
|
||||||
if ($filename ne '-') {
|
if ($filename ne '-') {
|
||||||
@ -361,6 +372,13 @@ __PACKAGE__->register_method ({
|
|||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
},
|
},
|
||||||
|
snapshot => {
|
||||||
|
description => "The current-state snapshot if the stream contains snapshots",
|
||||||
|
type => 'string',
|
||||||
|
pattern => qr/[a-z0-9_\-]{1,40}/i,
|
||||||
|
maxLength => 40,
|
||||||
|
optional => 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'string' },
|
returns => { type => 'string' },
|
||||||
@ -436,7 +454,8 @@ __PACKAGE__->register_method ({
|
|||||||
my $volume = $param->{volume};
|
my $volume = $param->{volume};
|
||||||
my $delete = $param->{'delete-snapshot'};
|
my $delete = $param->{'delete-snapshot'};
|
||||||
my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
|
my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
|
||||||
$param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'});
|
$param->{snapshot}, $param->{base}, $param->{'with-snapshots'},
|
||||||
|
$param->{'allow-rename'});
|
||||||
PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
|
PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
|
||||||
if defined($delete);
|
if defined($delete);
|
||||||
return $imported_volid;
|
return $imported_volid;
|
||||||
|
|||||||
@ -41,11 +41,11 @@ use PVE::Storage::PBSPlugin;
|
|||||||
use PVE::Storage::BTRFSPlugin;
|
use PVE::Storage::BTRFSPlugin;
|
||||||
|
|
||||||
# Storage API version. Increment it on changes in storage API interface.
|
# Storage API version. Increment it on changes in storage API interface.
|
||||||
use constant APIVER => 8;
|
use constant APIVER => 9;
|
||||||
# Age is the number of versions we're backward compatible with.
|
# Age is the number of versions we're backward compatible with.
|
||||||
# This is like having 'current=APIVER' and age='APIAGE' in libtool,
|
# This is like having 'current=APIVER' and age='APIAGE' in libtool,
|
||||||
# see https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
|
# see https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
|
||||||
use constant APIAGE => 7;
|
use constant APIAGE => 0;
|
||||||
|
|
||||||
# load standard plugins
|
# load standard plugins
|
||||||
PVE::Storage::DirPlugin->register();
|
PVE::Storage::DirPlugin->register();
|
||||||
@ -716,7 +716,8 @@ sub storage_migrate {
|
|||||||
my $send = ['pvesm', 'export', $volid, $format, '-', '-with-snapshots', $with_snapshots];
|
my $send = ['pvesm', 'export', $volid, $format, '-', '-with-snapshots', $with_snapshots];
|
||||||
my $recv = [@$ssh, '--', 'pvesm', 'import', $target_volid, $format, $import_fn, '-with-snapshots', $with_snapshots];
|
my $recv = [@$ssh, '--', 'pvesm', 'import', $target_volid, $format, $import_fn, '-with-snapshots', $with_snapshots];
|
||||||
if (defined($snapshot)) {
|
if (defined($snapshot)) {
|
||||||
push @$send, '-snapshot', $snapshot
|
push @$send, '-snapshot', $snapshot;
|
||||||
|
push @$recv, '-snapshot', $snapshot;
|
||||||
}
|
}
|
||||||
if ($migration_snapshot) {
|
if ($migration_snapshot) {
|
||||||
push @$recv, '-delete-snapshot', $snapshot;
|
push @$recv, '-delete-snapshot', $snapshot;
|
||||||
@ -726,7 +727,7 @@ sub storage_migrate {
|
|||||||
if (defined($base_snapshot)) {
|
if (defined($base_snapshot)) {
|
||||||
# Check if the snapshot exists on the remote side:
|
# Check if the snapshot exists on the remote side:
|
||||||
push @$send, '-base', $base_snapshot;
|
push @$send, '-base', $base_snapshot;
|
||||||
push @$recv, '-base', $base_snapshot;
|
push @$recv, '-base', $base_snapshot if $target_apiver >= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $new_volid;
|
my $new_volid;
|
||||||
@ -1714,7 +1715,7 @@ sub prune_mark_backup_group {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_export {
|
sub volume_export : prototype($$$$$$$) {
|
||||||
my ($cfg, $fh, $volid, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
my ($cfg, $fh, $volid, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
|
|
||||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||||
@ -1725,18 +1726,27 @@ sub volume_export {
|
|||||||
$snapshot, $base_snapshot, $with_snapshots);
|
$snapshot, $base_snapshot, $with_snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import {
|
sub volume_import : prototype($$$$$$$$) {
|
||||||
my ($cfg, $fh, $volid, $format, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
my ($cfg, $fh, $volid, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||||
|
|
||||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||||
die "cannot import into volume '$volid'\n" if !$storeid;
|
die "cannot import into volume '$volid'\n" if !$storeid;
|
||||||
my $scfg = storage_config($cfg, $storeid);
|
my $scfg = storage_config($cfg, $storeid);
|
||||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||||
return $plugin->volume_import($scfg, $storeid, $fh, $volname, $format,
|
return $plugin->volume_import(
|
||||||
$base_snapshot, $with_snapshots, $allow_rename) // $volid;
|
$scfg,
|
||||||
|
$storeid,
|
||||||
|
$fh,
|
||||||
|
$volname,
|
||||||
|
$format,
|
||||||
|
$snapshot,
|
||||||
|
$base_snapshot,
|
||||||
|
$with_snapshots,
|
||||||
|
$allow_rename,
|
||||||
|
) // $volid;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_export_formats {
|
sub volume_export_formats : prototype($$$$$) {
|
||||||
my ($cfg, $volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
my ($cfg, $volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
|
|
||||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||||
@ -1748,21 +1758,27 @@ sub volume_export_formats {
|
|||||||
$with_snapshots);
|
$with_snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import_formats {
|
sub volume_import_formats : prototype($$$$$) {
|
||||||
my ($cfg, $volid, $base_snapshot, $with_snapshots) = @_;
|
my ($cfg, $volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
|
|
||||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||||
return if !$storeid;
|
return if !$storeid;
|
||||||
my $scfg = storage_config($cfg, $storeid);
|
my $scfg = storage_config($cfg, $storeid);
|
||||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||||
return $plugin->volume_import_formats($scfg, $storeid, $volname,
|
return $plugin->volume_import_formats(
|
||||||
$base_snapshot, $with_snapshots);
|
$scfg,
|
||||||
|
$storeid,
|
||||||
|
$volname,
|
||||||
|
$snapshot,
|
||||||
|
$base_snapshot,
|
||||||
|
$with_snapshots,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_transfer_formats {
|
sub volume_transfer_formats {
|
||||||
my ($cfg, $src_volid, $dst_volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
my ($cfg, $src_volid, $dst_volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
my @export_formats = volume_export_formats($cfg, $src_volid, $snapshot, $base_snapshot, $with_snapshots);
|
my @export_formats = volume_export_formats($cfg, $src_volid, $snapshot, $base_snapshot, $with_snapshots);
|
||||||
my @import_formats = volume_import_formats($cfg, $dst_volid, $base_snapshot, $with_snapshots);
|
my @import_formats = volume_import_formats($cfg, $dst_volid, $snapshot, $base_snapshot, $with_snapshots);
|
||||||
my %import_hash = map { $_ => 1 } @import_formats;
|
my %import_hash = map { $_ => 1 } @import_formats;
|
||||||
my @common = grep { $import_hash{$_} } @export_formats;
|
my @common = grep { $import_hash{$_} } @export_formats;
|
||||||
return @common;
|
return @common;
|
||||||
|
|||||||
@ -603,7 +603,7 @@ sub volume_has_feature {
|
|||||||
sub volume_export_formats {
|
sub volume_export_formats {
|
||||||
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
return () if defined($snapshot); # lvm-thin only
|
return () if defined($snapshot); # lvm-thin only
|
||||||
return volume_import_formats($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots);
|
return volume_import_formats($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_export {
|
sub volume_export {
|
||||||
@ -627,14 +627,14 @@ sub volume_export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import_formats {
|
sub volume_import_formats {
|
||||||
my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
|
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
return () if $with_snapshots; # not supported
|
return () if $with_snapshots; # not supported
|
||||||
return () if defined($base_snapshot); # not supported
|
return () if defined($base_snapshot); # not supported
|
||||||
return ('raw+size');
|
return ('raw+size');
|
||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import {
|
sub volume_import {
|
||||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||||
die "volume import format $format not available for $class\n"
|
die "volume import format $format not available for $class\n"
|
||||||
if $format ne 'raw+size';
|
if $format ne 'raw+size';
|
||||||
die "cannot import volumes together with their snapshots in $class\n"
|
die "cannot import volumes together with their snapshots in $class\n"
|
||||||
|
|||||||
@ -1411,7 +1411,7 @@ sub volume_export_formats {
|
|||||||
|
|
||||||
# Import data from a stream, creating a new or replacing or adding to an existing volume.
|
# Import data from a stream, creating a new or replacing or adding to an existing volume.
|
||||||
sub volume_import {
|
sub volume_import {
|
||||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||||
|
|
||||||
die "volume import format '$format' not available for $class\n"
|
die "volume import format '$format' not available for $class\n"
|
||||||
if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
|
if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
|
||||||
@ -1471,7 +1471,7 @@ sub volume_import {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import_formats {
|
sub volume_import_formats {
|
||||||
my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
|
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
if ($scfg->{path} && !defined($base_snapshot)) {
|
if ($scfg->{path} && !defined($base_snapshot)) {
|
||||||
my $format = ($class->parse_volname($volname))[6];
|
my $format = ($class->parse_volname($volname))[6];
|
||||||
if ($with_snapshots) {
|
if ($with_snapshots) {
|
||||||
|
|||||||
@ -747,7 +747,7 @@ sub volume_export_formats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import {
|
sub volume_import {
|
||||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||||
|
|
||||||
die "unsupported import stream format for $class: $format\n"
|
die "unsupported import stream format for $class: $format\n"
|
||||||
if $format ne 'zfs';
|
if $format ne 'zfs';
|
||||||
@ -784,9 +784,9 @@ sub volume_import {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub volume_import_formats {
|
sub volume_import_formats {
|
||||||
my ($class, $scfg, $storeid, $volname, $base_snapshot, $with_snapshots) = @_;
|
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||||
|
|
||||||
return $class->volume_export_formats($scfg, $storeid, $volname, undef, $base_snapshot, $with_snapshots);
|
return $class->volume_export_formats($scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|||||||
Reference in New Issue
Block a user