From: Filip Schauer <f.schauer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v4 storage 2/6] api: content: implement moving a volume between storages
Date: Wed, 18 Sep 2024 16:49:49 +0200 [thread overview]
Message-ID: <20240918144953.130780-3-f.schauer@proxmox.com> (raw)
In-Reply-To: <20240918144953.130780-1-f.schauer@proxmox.com>
Add the ability to move an iso, snippet or vztmpl between storages and
nodes.
Use curl to call the API method:
```
curl https://$APINODE:8006/api2/json/nodes/$SOURCENODE/storage/$SOURCESTORAGE/content/$SOURCEVOLUME \
--insecure --cookie "$(<cookie)" -H "$(<csrftoken)" -X POST \
--data-raw "target-storage=$TARGETSTORAGE&target-node=$TARGETNODE"
```
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/API2/Storage/Content.pm | 149 ++++++++++++++++++++++++--------
1 file changed, 114 insertions(+), 35 deletions(-)
diff --git a/src/PVE/API2/Storage/Content.pm b/src/PVE/API2/Storage/Content.pm
index fe0ad4a..6819eca 100644
--- a/src/PVE/API2/Storage/Content.pm
+++ b/src/PVE/API2/Storage/Content.pm
@@ -2,7 +2,10 @@ package PVE::API2::Storage::Content;
use strict;
use warnings;
-use Data::Dumper;
+
+use Errno qw(ENOENT);
+use File::Basename;
+use File::Copy qw(copy move);
use PVE::SafeSyslog;
use PVE::Cluster;
@@ -483,30 +486,101 @@ __PACKAGE__->register_method ({
return $upid;
}});
+sub volume_move {
+ my ($cfg, $source_volid, $target_storeid, $delete) = @_;
+
+ my ($source_storeid, $source_volname) = PVE::Storage::parse_volume_id($source_volid, 0);
+
+ die "source and target storage cannot be the same\n" if $source_storeid eq $target_storeid;
+
+ my $source_scfg = PVE::Storage::storage_config($cfg, $source_storeid);
+ my $source_plugin = PVE::Storage::Plugin->lookup($source_scfg->{type});
+ my ($vtype) = $source_plugin->parse_volname($source_volname);
+
+ die "source storage '$source_storeid' does not support volumes of type '$vtype'\n"
+ if !$source_scfg->{content}->{$vtype};
+
+ my $target_scfg = PVE::Storage::storage_config($cfg, $target_storeid);
+ die "target storage '$target_storeid' does not support volumes of type '$vtype'\n"
+ if !$target_scfg->{content}->{$vtype};
+
+ die "use pct move-volume or qm disk move\n" if $vtype eq 'images' || $vtype eq 'rootdir';
+ die "moving volume of type '$vtype' not implemented\n"
+ if ($vtype ne 'iso' && $vtype ne 'vztmpl' && $vtype ne 'snippets');
+
+ PVE::Storage::activate_storage($cfg, $source_storeid);
+
+ die "expected storage '$source_storeid' to be path based\n" if !$source_scfg->{path};
+ my $source_path = $source_plugin->path($source_scfg, $source_volname, $source_storeid);
+ die "$source_path does not exist" if (!-e $source_path);
+ my $source_dirname = dirname($source_path);
+
+ die "expected storage '$target_storeid' to be path based\n" if !$target_scfg->{path};
+ my $target_plugin = PVE::Storage::Plugin->lookup($target_scfg->{type});
+ my $target_path = $target_plugin->path($target_scfg, $source_volname, $target_storeid);
+
+ PVE::Storage::activate_storage($cfg, $target_storeid);
+ die "$target_path already exists" if (-e $target_path);
+
+ my @created_files = ();
+
+ eval {
+ if ($delete) {
+ move($source_path, $target_path) or die "failed to move $vtype: $!";
+ } else {
+ copy($source_path, $target_path) or die "failed to copy $vtype: $!";
+ }
+ };
+ if (my $err = $@) {
+ for my $created_file (@created_files) {
+ unlink $created_file or $!{ENOENT} or warn $!;
+ }
+ die $err;
+ }
+
+ PVE::Storage::archive_remove($source_path) if $delete;
+
+ return;
+}
+
__PACKAGE__->register_method ({
- name => 'copy',
+ name => 'move',
path => '{volume}',
method => 'POST',
- description => "Copy a volume. This is experimental code - do not use.",
+ description => "Move a volume.",
+ permissions => {
+ description => "If the --delete option is used, the 'Datastore.Allocate' privilege is " .
+ "required on the source storage. " .
+ "Without --delete, 'Datastore.AllocateSpace' is required on the target storage. " .
+ "When moving a backup, 'VM.Backup' is required on the VM or container.",
+ user => 'all',
+ },
protected => 1,
proxyto => 'node',
parameters => {
- additionalProperties => 0,
+ additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
- storage => get_standard_option('pve-storage-id', { optional => 1}),
+ storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Source volume identifier",
type => 'string',
},
- target => {
- description => "Target volume identifier",
+ 'target-storage' => {
+ description => "Target storage",
type => 'string',
},
- target_node => get_standard_option('pve-node', {
+ 'target-node' => get_standard_option('pve-node', {
description => "Target node. Default is local node.",
optional => 1,
}),
+ delete => {
+ type => 'boolean',
+ description => "Delete the original volume after a successful copy. " .
+ "By default the original is kept.",
+ optional => 1,
+ default => 0,
+ },
},
},
returns => {
@@ -515,43 +589,48 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
- my $rpcenv = PVE::RPCEnvironment::get();
-
- my $user = $rpcenv->get_user();
-
- my $target_node = $param->{target_node} || PVE::INotify::nodename();
- # pvesh examples
- # cd /nodes/localhost/storage/local/content
- # pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
- # pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
-
my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
- my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
+ my $dst_storeid = $param->{'target-storage'};
+ my ($src_storeid, $volname) = PVE::Storage::parse_volume_id($src_volid);
+ my $src_node = PVE::INotify::nodename();
+ my $dst_node = $param->{'target-node'} || $src_node;
+ my $delete = $param->{delete};
- print "DEBUG: COPY $src_volid TO $dst_volid\n";
+ die "source and target cannot be the same\n"
+ if $src_node eq $dst_node && $src_storeid eq $dst_storeid;
my $cfg = PVE::Storage::config();
- # do all parameter checks first
-
- # then do all short running task (to raise errors before we go to background)
+ my ($vtype) = PVE::Storage::parse_volname($cfg, $src_volid);
+ die "use pct move-volume or qm disk move" if $vtype eq 'images' || $vtype eq 'rootdir';
- # then start the worker task
- my $worker = sub {
- my $upid = shift;
-
- print "DEBUG: starting worker $upid\n";
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $user = $rpcenv->get_user();
- my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
- #my $target_ip = PVE::Cluster::remote_node_ip($target_node);
+ PVE::Storage::check_volume_access($rpcenv, $user, $cfg, undef, $src_volid);
- # you need to get this working (fails currently, because storage_migrate() uses
- # ssh to connect to local host (which is not needed
- my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
- PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname});
+ if ($delete) {
+ $rpcenv->check($user, "/storage/$src_storeid", ["Datastore.Allocate"]);
+ } else {
+ $rpcenv->check($user, "/storage/$dst_storeid", ["Datastore.AllocateSpace"]);
+ }
- print "DEBUG: end worker $upid\n";
+ my $worker = sub {
+ if ($src_node eq $dst_node) {
+ volume_move($cfg, $src_volid, $dst_storeid, $delete);
+ } else {
+ PVE::Storage::storage_check_enabled($cfg, $dst_storeid, $dst_node);
+ my $sshinfo = PVE::SSHInfo::get_ssh_info($dst_node);
+ PVE::Storage::storage_migrate(
+ $cfg, $src_volid, $sshinfo, $dst_storeid, {'target_volname' => $volname});
+ if ($delete) {
+ my $src_path = PVE::Storage::abs_filesystem_path($cfg, $src_volid);
+ PVE::Storage::archive_remove($src_path, 1);
+ }
+ }
+ print "Moved volume '$src_volid' on node '$src_node'"
+ ." to '$dst_storeid' on node '$dst_node'\n";
};
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
--
2.39.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-09-18 14:50 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-09-18 14:49 [pve-devel] [PATCH v4 storage 0/6] support moving volumes " Filip Schauer
2024-09-18 14:49 ` [pve-devel] [PATCH v4 storage 1/6] plugin: allow volume import of iso, snippets and vztmpl Filip Schauer
2024-09-20 14:26 ` Daniel Kral
2024-09-18 14:49 ` Filip Schauer [this message]
2024-09-20 14:27 ` [pve-devel] [PATCH v4 storage 2/6] api: content: implement moving a volume between storages Daniel Kral
2024-11-26 15:25 ` Filip Schauer
2024-10-21 11:53 ` Fiona Ebner
2024-09-18 14:49 ` [pve-devel] [PATCH v4 storage 3/6] api: content: support moving backups between path based storages Filip Schauer
2024-09-20 14:27 ` Daniel Kral
2024-09-18 14:49 ` [pve-devel] [PATCH v4 storage 4/6] storage: introduce decompress_archive_into_pipe helper Filip Schauer
2024-09-20 14:27 ` Daniel Kral
2024-09-18 14:49 ` [pve-devel] [PATCH v4 storage 5/6] support moving VMA backups to PBS Filip Schauer
2024-09-18 14:49 ` [pve-devel] [PATCH v4 storage 6/6] pvesm: add a move-volume command Filip Schauer
2024-09-20 14:28 ` Daniel Kral
2024-09-20 14:25 ` [pve-devel] [PATCH v4 storage 0/6] support moving volumes between storages Daniel Kral
2024-11-26 15:29 ` Filip Schauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240918144953.130780-3-f.schauer@proxmox.com \
--to=f.schauer@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.