}
}
-sub volume_send {
- my ($cfg, $volid, $snap, $ip, $incremental_snap, $verbose, $limit,
- $target_path) = @_;
-
- my ($storeid, $volname) = parse_volume_id($volid, 1);
- if ($storeid) {
- my $scfg = storage_config($cfg, $storeid);
- my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
- return $plugin->volume_send($scfg, $storeid, $volname, $ip, $snap,
- $incremental_snap, $verbose, $limit, $target_path);
-
- } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
- die "send file/device '$volid' is not possible\n";
- } else {
- die "unable to parse volume ID '$volid'\n";
- }
-}
-
sub volume_snapshot_rollback {
my ($cfg, $volid, $snap) = @_;
}
}
+sub volume_snapshot_list {
+ my ($cfg, $volid, $prefix) = @_;
+
+ my ($storeid, $volname) = parse_volume_id($volid, 1);
+ if ($storeid) {
+ my $scfg = storage_config($cfg, $storeid);
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->volume_snapshot_list($scfg, $storeid, $volname, $prefix);
+ } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
+ die "send file/device '$volid' is not possible\n";
+ } else {
+ die "unable to parse volume ID '$volid'\n";
+ }
+ # return an empty array if dataset does not exist.
+ # youngest snap first
+}
+
sub get_image_dir {
my ($cfg, $storeid, $vmid) = @_;
}
sub storage_migrate {
- my ($cfg, $volid, $target_host, $target_storeid, $target_volname) = @_;
+ my ($cfg, $volid, $target_host, $target_storeid, $target_volname, $base_snapshot) = @_;
my ($storeid, $volname) = parse_volume_id($volid);
$target_volname = $volname if !$target_volname;
local $ENV{RSYNC_RSH} = $ssh;
+ my $no_incremental = sub {
+ my ($type) = @_;
+ die "incremental migration not supported on storage type $type\n"
+ if defined($base_snapshot);
+ };
+
# only implemented for file system based storage
if ($scfg->{path}) {
- if ($tcfg->{path}) {
+ $no_incremental->($scfg->{type});
+ if ($tcfg->{path}) {
my $src_plugin = PVE::Storage::Plugin->lookup($scfg->{type});
my $dst_plugin = PVE::Storage::Plugin->lookup($tcfg->{type});
my $src = $src_plugin->path($scfg, $volname, $storeid);
if $tcfg->{pool} ne $scfg->{pool};
my (undef, $volname) = parse_volname($cfg, $volid);
-
my $zfspath = "$scfg->{pool}\/$volname";
- my $snap = ['zfs', 'snapshot', "$zfspath\@__migration__"];
+ my @formats = volume_transfer_formats($cfg, $volid, $volid, '__migration__', $base_snapshot, 1);
+ die "cannot migrate from storage type '$scfg->{type}' to '$tcfg->{type}'\n" if !@formats;
+ my $format = $formats[0];
- my $send = [['zfs', 'send', '-Rpv', "$zfspath\@__migration__"], ['ssh', "root\@$target_host",
- 'zfs', 'recv', $zfspath]];
+ my $send = ['pvesm', 'export', $volid, $format, '-', '-snapshot', '__migration__', '-with-snapshots', '1'];
+ my $recv = ['ssh', "root\@$target_host", '--', 'pvesm', 'import', $volid, $format, '-', '-with-snapshots', '1'];
+ my $free = ['ssh', "root\@$target_host", '--', 'pvesm', 'free', $volid, '-snapshot', '__migration__'];
- my $destroy_target = ['ssh', "root\@$target_host", 'zfs', 'destroy', "$zfspath\@__migration__"];
- run_command($snap);
- eval{
- run_command($send);
+ if (defined($base_snapshot)) {
+ # Check if the snapshot exists on the remote side:
+ push @$send, '-base', $base_snapshot;
+ push @$recv, '-base', $base_snapshot;
+ }
+
+ volume_snapshot($cfg, $volid, '__migration__');
+ eval {
+ run_command([$send, $recv]);
};
my $err = $@;
- warn "zfs send/receive failed, cleaning up snapshot(s)..\n" if $err;
- eval { run_command(['zfs', 'destroy', "$zfspath\@__migration__"]); };
+ warn "send/receive failed, cleaning up snapshot(s)..\n" if $err;
+ eval { volume_snapshot_delete($cfg, $volid, '__migration__', 0) };
warn "could not remove source snapshot: $@\n" if $@;
- eval { run_command($destroy_target); };
- warn "could not remove target snapshot: $@\n" if $@;
+ if (defined($free)) {
+ eval { run_command($free) };
+ warn "could not remove target snapshot: $@\n" if $@;
+ }
die $err if $err;
-
} else {
die "$errstr - target type $tcfg->{type} is not valid\n";
}
} elsif ($scfg->{type} eq 'lvmthin' || $scfg->{type} eq 'lvm') {
+ $no_incremental->($scfg->{type});
if (($scfg->{type} eq $tcfg->{type}) &&
($tcfg->{type} eq 'lvmthin' || $tcfg->{type} eq 'lvm')) {
}
}
+sub volume_export {
+ my ($cfg, $fh, $volid, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
+
+ my ($storeid, $volname) = parse_volume_id($volid, 1);
+ die "cannot export volume '$volid'\n" if !$storeid;
+ my $scfg = storage_config($cfg, $storeid);
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->volume_export($scfg, $storeid, $fh, $volname, $format,
+ $snapshot, $base_snapshot, $with_snapshots);
+}
+
+sub volume_import {
+ my ($cfg, $fh, $volid, $format, $base_snapshot, $with_snapshots) = @_;
+
+ my ($storeid, $volname) = parse_volume_id($volid, 1);
+ die "cannot import into volume '$volid'\n" if !$storeid;
+ my $scfg = storage_config($cfg, $storeid);
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->volume_import($scfg, $storeid, $fh, $volname, $format,
+ $base_snapshot, $with_snapshots);
+}
+
+sub volume_export_formats {
+ my ($cfg, $volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
+
+ my ($storeid, $volname) = parse_volume_id($volid, 1);
+ return if !$storeid;
+ my $scfg = storage_config($cfg, $storeid);
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->volume_export_formats($scfg, $storeid, $volname,
+ $base_snapshot, $with_snapshots);
+}
+
+sub volume_import_formats {
+ my ($cfg, $volid, $base_snapshot, $with_snapshots) = @_;
+
+ my ($storeid, $volname) = parse_volume_id($volid, 1);
+ return if !$storeid;
+ my $scfg = storage_config($cfg, $storeid);
+ my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
+ return $plugin->volume_import_formats($scfg, $storeid, $volname,
+ $base_snapshot, $with_snapshots);
+}
+
+sub volume_transfer_formats {
+ 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 @import_formats = volume_import_formats($cfg, $dst_volid, $base_snapshot, $with_snapshots);
+ my %import_hash = map { $_ => 1 } @import_formats;
+ my @common = grep { $import_hash{$_} } @export_formats;
+ return @common;
+}
+
# bash completion helper
sub complete_storage {