From 95dfa44ca1a41bd5913c5b7c66adcf8291fac6d4 Mon Sep 17 00:00:00 2001 From: Aaron Lauterer Date: Tue, 9 Nov 2021 15:55:32 +0100 Subject: [PATCH] add disk rename feature MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Functionality has been added for the following storage types: * directory ones, based on the default implementation: * directory * NFS * CIFS * gluster * ZFS * (thin) LVM * Ceph A new feature `rename` has been introduced to mark which storage plugin supports the feature. Version API and AGE have been bumped. Signed-off-by: Aaron Lauterer the intention of this feature is to support the following use-cases: - reassign a volume from one owning guest to another (which usually entails a rename, since the owning vmid is encoded in the volume name) - rename a volume (e.g., to use a more meaningful name instead of the auto-assigned ...-disk-123) only the former is implemented at the caller side in qemu-server/pve-container for now, but since the lower-level feature is basically the same for both, we can take advantage of the storage plugin API bump now to get the building block for this future feature in place already. adapt ApiChangelog change to fix conflicts and added more detail above Signed-off-by: Fabian Grünbichler --- ApiChangeLog | 5 +++++ PVE/Storage.pm | 21 ++++++++++++++++++++ PVE/Storage/LVMPlugin.pm | 34 ++++++++++++++++++++++++++++++++ PVE/Storage/LvmThinPlugin.pm | 1 + PVE/Storage/Plugin.pm | 38 ++++++++++++++++++++++++++++++++++++ PVE/Storage/RBDPlugin.pm | 34 ++++++++++++++++++++++++++++++++ PVE/Storage/ZFSPoolPlugin.pm | 31 +++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+) 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; -- 2.39.2