diff --git a/ApiChangeLog b/ApiChangeLog index 7d2bc8a..98b5893 100644 --- a/ApiChangeLog +++ b/ApiChangeLog @@ -8,6 +8,11 @@ Future changes should be documented in here. ## Version 10: +* a new `rename_volume` method has been added + + Storage plugins with rename support need to enable + the `rename` feature flag; e.g. in the `volume_has_feature` method. + * Replace `volume_snapshot_list` with `volume_snapshot_info`: `volume_snapshot_list` was used exclusively by replication and currently, replication is only diff --git a/PVE/Storage.pm b/PVE/Storage.pm index fdc21d9..d64019f 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -349,6 +349,7 @@ sub volume_snapshot_needs_fsfreeze { # snapshot - taking a snapshot is possible # sparseinit - volume is sparsely initialized # template - conversion to base image is possible +# rename - renaming volumes is possible # $snap - check if the feature is supported for a given snapshot # $running - if the guest owning the volume is running # $opts - hash with further options: @@ -1860,6 +1861,26 @@ sub complete_volume { return $res; } +sub rename_volume { + my ($cfg, $source_volid, $target_vmid, $target_volname) = @_; + + die "no source volid provided\n" if !$source_volid; + die "no target VMID or target volname provided\n" if !$target_vmid && !$target_volname; + + my ($storeid, $source_volname) = parse_volume_id($source_volid); + + activate_storage($cfg, $storeid); + + my $scfg = storage_config($cfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + + $target_vmid = ($plugin->parse_volname($source_volname))[3] if !$target_vmid; + + return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { + return $plugin->rename_volume($scfg, $storeid, $source_volname, $target_vmid, $target_volname); + }); +} + # Various io-heavy operations require io/bandwidth limits which can be # configured on multiple levels: The global defaults in datacenter.cfg, and # per-storage overrides. When we want to do a restore from storage A to storage diff --git a/PVE/Storage/LVMPlugin.pm b/PVE/Storage/LVMPlugin.pm index 139d391..40c1613 100644 --- a/PVE/Storage/LVMPlugin.pm +++ b/PVE/Storage/LVMPlugin.pm @@ -339,6 +339,15 @@ sub lvcreate { run_command($cmd, errmsg => "lvcreate '$vg/$name' error"); } +sub lvrename { + my ($vg, $oldname, $newname) = @_; + + run_command( + ['/sbin/lvrename', $vg, $oldname, $newname], + errmsg => "lvrename '${vg}/${oldname}' to '${newname}' error", + ); +} + sub alloc_image { my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; @@ -584,6 +593,7 @@ sub volume_has_feature { my $features = { copy => { base => 1, current => 1}, + rename => {current => 1}, }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = @@ -692,4 +702,28 @@ sub volume_import_write { input => '<&'.fileno($input_fh)); } +sub rename_volume { + my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; + + my ( + undef, + $source_image, + $source_vmid, + $base_name, + $base_vmid, + undef, + $format + ) = $class->parse_volname($source_volname); + $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) + if !$target_volname; + + my $vg = $scfg->{vgname}; + my $lvs = lvm_list_volumes($vg); + die "target volume '${target_volname}' already exists\n" + if ($lvs->{$vg}->{$target_volname}); + + lvrename($vg, $source_volname, $target_volname); + return "${storeid}:${target_volname}"; +} + 1; diff --git a/PVE/Storage/LvmThinPlugin.pm b/PVE/Storage/LvmThinPlugin.pm index 4ba6f90..c24af22 100644 --- a/PVE/Storage/LvmThinPlugin.pm +++ b/PVE/Storage/LvmThinPlugin.pm @@ -355,6 +355,7 @@ sub volume_has_feature { template => { current => 1}, copy => { base => 1, current => 1, snap => 1}, sparseinit => { base => 1, current => 1}, + rename => {current => 1}, }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm index 7d2487f..12f1b4b 100644 --- a/PVE/Storage/Plugin.pm +++ b/PVE/Storage/Plugin.pm @@ -1049,6 +1049,7 @@ sub volume_has_feature { snap => {qcow2 => 1} }, sparseinit => { base => {qcow2 => 1, raw => 1, vmdk => 1}, current => {qcow2 => 1, raw => 1, vmdk => 1} }, + rename => { current => {qcow2 => 1, raw => 1, vmdk => 1} }, }; # clone_image creates a qcow2 volume @@ -1056,6 +1057,8 @@ sub volume_has_feature { defined($opts->{valid_target_formats}) && !(grep { $_ eq 'qcow2' } @{$opts->{valid_target_formats}}); + return 0 if $feature eq 'rename' && $class->can('api') && $class->api() < 10; + my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) = $class->parse_volname($volname); @@ -1578,4 +1581,39 @@ sub volume_import_formats { return (); } +sub rename_volume { + my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; + die "not implemented in storage plugin '$class'\n" if $class->can('api') && $class->api() < 10; + die "no path found\n" if !$scfg->{path}; + + my ( + undef, + $source_image, + $source_vmid, + $base_name, + $base_vmid, + undef, + $format + ) = $class->parse_volname($source_volname); + + $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1) + if !$target_volname; + + my $basedir = $class->get_subdir($scfg, 'images'); + + mkpath "${basedir}/${target_vmid}"; + + my $old_path = "${basedir}/${source_vmid}/${source_image}"; + my $new_path = "${basedir}/${target_vmid}/${target_volname}"; + + die "target volume '${target_volname}' already exists\n" if -e $new_path; + + my $base = $base_name ? "${base_vmid}/${base_name}/" : ''; + + rename($old_path, $new_path) || + die "rename '$old_path' to '$new_path' failed - $!\n"; + + return "${storeid}:${base}${target_vmid}/${target_volname}"; +} + 1; diff --git a/PVE/Storage/RBDPlugin.pm b/PVE/Storage/RBDPlugin.pm index 613d32b..2607d25 100644 --- a/PVE/Storage/RBDPlugin.pm +++ b/PVE/Storage/RBDPlugin.pm @@ -742,6 +742,7 @@ sub volume_has_feature { template => { current => 1}, copy => { base => 1, current => 1, snap => 1}, sparseinit => { base => 1, current => 1}, + rename => {current => 1}, }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname); @@ -757,4 +758,37 @@ sub volume_has_feature { return undef; } +sub rename_volume { + my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; + + my ( + undef, + $source_image, + $source_vmid, + $base_name, + $base_vmid, + undef, + $format + ) = $class->parse_volname($source_volname); + $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) + if !$target_volname; + + eval { + my $cmd = $rbd_cmd->($scfg, $storeid, 'info', $target_volname); + run_rbd_command($cmd, errmsg => "exist check", quiet => 1); + }; + die "target volume '${target_volname}' already exists\n" if !$@; + + my $cmd = $rbd_cmd->($scfg, $storeid, 'rename', $source_image, $target_volname); + + run_rbd_command( + $cmd, + errmsg => "could not rename image '${source_image}' to '${target_volname}'", + ); + + $base_name = $base_name ? "${base_name}/" : ''; + + return "${storeid}:${base_name}${target_volname}"; +} + 1; diff --git a/PVE/Storage/ZFSPoolPlugin.pm b/PVE/Storage/ZFSPoolPlugin.pm index 278438b..5f6befd 100644 --- a/PVE/Storage/ZFSPoolPlugin.pm +++ b/PVE/Storage/ZFSPoolPlugin.pm @@ -696,6 +696,7 @@ sub volume_has_feature { copy => { base => 1, current => 1}, sparseinit => { base => 1, current => 1}, replicate => { base => 1, current => 1}, + rename => {current => 1}, }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = @@ -798,4 +799,34 @@ sub volume_import_formats { return $class->volume_export_formats($scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots); } +sub rename_volume { + my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; + + my ( + undef, + $source_image, + $source_vmid, + $base_name, + $base_vmid, + undef, + $format + ) = $class->parse_volname($source_volname); + $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) + if !$target_volname; + + my $pool = $scfg->{pool}; + my $source_zfspath = "${pool}/${source_image}"; + my $target_zfspath = "${pool}/${target_volname}"; + + my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $target_zfspath], + noerr => 1, quiet => 1); + die "target volume '${target_volname}' already exists\n" if $exists; + + $class->zfs_request($scfg, 5, 'rename', ${source_zfspath}, ${target_zfspath}); + + $base_name = $base_name ? "${base_name}/" : ''; + + return "${storeid}:${base_name}${target_volname}"; +} + 1;