1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
16 use PVE
::ReplicationConfig
;
19 use PVE
::LXC
::Migrate
;
20 use PVE
::GuestHelpers
;
21 use PVE
::API2
::LXC
::Config
;
22 use PVE
::API2
::LXC
::Status
;
23 use PVE
::API2
::LXC
::Snapshot
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
28 if (!$ENV{PVE_GENERATING_DOCS
}) {
29 require PVE
::HA
::Env
::PVE2
;
30 import PVE
::HA
::Env
::PVE2
;
31 require PVE
::HA
::Config
;
32 import PVE
::HA
::Config
;
36 __PACKAGE__-
>register_method ({
37 subclass
=> "PVE::API2::LXC::Config",
38 path
=> '{vmid}/config',
41 __PACKAGE__-
>register_method ({
42 subclass
=> "PVE::API2::LXC::Status",
43 path
=> '{vmid}/status',
46 __PACKAGE__-
>register_method ({
47 subclass
=> "PVE::API2::LXC::Snapshot",
48 path
=> '{vmid}/snapshot',
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PVE::API2::Firewall::CT",
53 path
=> '{vmid}/firewall',
56 __PACKAGE__-
>register_method({
60 description
=> "LXC container index (per node).",
62 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
66 protected
=> 1, # /proc files are only readable by root
68 additionalProperties
=> 0,
70 node
=> get_standard_option
('pve-node'),
77 properties
=> $PVE::LXC
::vmstatus_return_properties
,
79 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
84 my $rpcenv = PVE
::RPCEnvironment
::get
();
85 my $authuser = $rpcenv->get_user();
87 my $vmstatus = PVE
::LXC
::vmstatus
();
90 foreach my $vmid (keys %$vmstatus) {
91 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
93 my $data = $vmstatus->{$vmid};
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::Config-
>json_config_properties({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
120 description
=> "The OS template or backup file.",
123 completion
=> \
&PVE
::LXC
::complete_os_templates
,
128 description
=> "Sets root password inside container.",
131 storage
=> get_standard_option
('pve-storage-id', {
132 description
=> "Default Storage.",
135 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
140 description
=> "Allow to overwrite existing container.",
145 description
=> "Mark this as restore task.",
149 type
=> 'string', format
=> 'pve-poolid',
150 description
=> "Add the VM to the specified pool.",
152 'ignore-unpack-errors' => {
155 description
=> "Ignore errors when extracting the template.",
157 'ssh-public-keys' => {
160 description
=> "Setup public SSH keys (one key per line, " .
164 description
=> "Override i/o bandwidth limit (in KiB/s).",
173 description
=> "Start the CT after its creation finished successfully.",
183 my $rpcenv = PVE
::RPCEnvironment
::get
();
185 my $authuser = $rpcenv->get_user();
187 my $node = extract_param
($param, 'node');
189 my $vmid = extract_param
($param, 'vmid');
191 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
193 my $bwlimit = extract_param
($param, 'bwlimit');
195 my $start_after_create = extract_param
($param, 'start');
197 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
199 my $same_container_exists = -f
$basecfg_fn;
201 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
202 my $unprivileged = extract_param
($param, 'unprivileged');
204 my $restore = extract_param
($param, 'restore');
207 # fixme: limit allowed parameters
211 my $force = extract_param
($param, 'force');
213 if (!($same_container_exists && $restore && $force)) {
214 PVE
::Cluster
::check_vmid_unused
($vmid);
216 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
217 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
220 my $password = extract_param
($param, 'password');
222 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
223 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
225 my $pool = extract_param
($param, 'pool');
227 if (defined($pool)) {
228 $rpcenv->check_pool_exist($pool);
229 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
232 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
234 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
236 } elsif ($restore && $force && $same_container_exists &&
237 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
238 # OK: user has VM.Backup permissions, and want to restore an existing VM
243 my $ostemplate = extract_param
($param, 'ostemplate');
244 my $storage = extract_param
($param, 'storage') // 'local';
246 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
248 my $storage_cfg = cfs_read_file
("storage.cfg");
253 if ($ostemplate eq '-') {
254 die "pipe requires cli environment\n"
255 if $rpcenv->{type
} ne 'cli';
256 die "pipe can only be used with restore tasks\n"
259 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
261 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
262 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
266 my $check_and_activate_storage = sub {
269 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
271 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
272 if !$scfg->{content
}->{rootdir
};
274 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
276 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
278 $used_storages{$sid} = 1;
283 my $no_disk_param = {};
285 my $storage_only_mode = 1;
286 foreach my $opt (keys %$param) {
287 my $value = $param->{$opt};
288 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
289 # allow to use simple numbers (add default storage in that case)
290 if ($value =~ m/^\d+(\.\d+)?$/) {
291 $mp_param->{$opt} = "$storage:$value";
293 $mp_param->{$opt} = $value;
295 $storage_only_mode = 0;
296 } elsif ($opt =~ m/^unused\d+$/) {
297 warn "ignoring '$opt', cannot create/restore with unused volume\n";
298 delete $param->{$opt};
300 $no_disk_param->{$opt} = $value;
304 die "mount points configured, but 'rootfs' not set - aborting\n"
305 if !$storage_only_mode && !defined($mp_param->{rootfs
});
307 # check storage access, activate storage
308 my $delayed_mp_param = {};
309 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
310 my ($ms, $mountpoint) = @_;
312 my $volid = $mountpoint->{volume
};
313 my $mp = $mountpoint->{mp
};
315 if ($mountpoint->{type
} ne 'volume') { # bind or device
316 die "Only root can pass arbitrary filesystem paths.\n"
317 if $authuser ne 'root@pam';
319 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
320 &$check_and_activate_storage($sid);
324 # check/activate default storage
325 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
327 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
329 $conf->{unprivileged
} = 1 if $unprivileged;
331 my $check_vmid_usage = sub {
333 die "can't overwrite running container\n"
334 if PVE
::LXC
::check_running
($vmid);
336 PVE
::Cluster
::check_vmid_unused
($vmid);
341 &$check_vmid_usage(); # final check after locking
344 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
346 die "container exists" if !$restore; # just to be sure
347 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
350 # try to create empty config on local node, we have an flock
351 PVE
::LXC
::Config-
>write_config($vmid, {});
354 # another node was faster, abort
355 die "Could not reserve ID $vmid, already taken\n" if $@;
358 PVE
::Cluster
::check_cfs_quorum
();
362 if ($storage_only_mode) {
364 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
365 die "rootfs configuration could not be recovered, please check and specify manually!\n"
366 if !defined($mp_param->{rootfs
});
367 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
368 my ($ms, $mountpoint) = @_;
369 my $type = $mountpoint->{type
};
370 if ($type eq 'volume') {
371 die "unable to detect disk size - please specify $ms (size)\n"
372 if !defined($mountpoint->{size
});
373 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
374 delete $mountpoint->{size
};
375 $mountpoint->{volume
} = "$storage:$disksize";
376 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
378 my $type = $mountpoint->{type
};
379 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
380 if ($ms eq 'rootfs');
381 die "restoring '$ms' to $type mount is only possible for root\n"
382 if $authuser ne 'root@pam';
384 if ($mountpoint->{backup
}) {
385 warn "WARNING - unsupported configuration!\n";
386 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
387 warn "mount point configuration will be restored after archive extraction!\n";
388 warn "contained files will be restored to wrong directory!\n";
390 delete $mp_param->{$ms}; # actually delay bind/dev mps
391 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
395 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
399 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
401 if (defined($old_conf)) {
402 # destroy old container volumes
403 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
407 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
408 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
409 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
412 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
414 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
415 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
416 $lxc_setup->post_create_hook($password, $ssh_keys);
420 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
421 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
424 $conf->{hostname
} ||= "CT$vmid";
425 $conf->{memory
} ||= 512;
426 $conf->{swap
} //= 512;
427 foreach my $mp (keys %$delayed_mp_param) {
428 $conf->{$mp} = $delayed_mp_param->{$mp};
430 PVE
::LXC
::Config-
>write_config($vmid, $conf);
433 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
434 PVE
::LXC
::destroy_config
($vmid);
437 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
439 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
440 if $start_after_create;
443 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
445 &$check_vmid_usage(); # first check before locking
447 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
448 $vmid, $authuser, $realcmd);
452 __PACKAGE__-
>register_method({
457 description
=> "Directory index",
462 additionalProperties
=> 0,
464 node
=> get_standard_option
('pve-node'),
465 vmid
=> get_standard_option
('pve-vmid'),
473 subdir
=> { type
=> 'string' },
476 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
482 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
485 { subdir
=> 'config' },
486 { subdir
=> 'status' },
487 { subdir
=> 'vncproxy' },
488 { subdir
=> 'termproxy' },
489 { subdir
=> 'vncwebsocket' },
490 { subdir
=> 'spiceproxy' },
491 { subdir
=> 'migrate' },
492 { subdir
=> 'clone' },
493 # { subdir => 'initlog' },
495 { subdir
=> 'rrddata' },
496 { subdir
=> 'firewall' },
497 { subdir
=> 'snapshot' },
498 { subdir
=> 'resize' },
505 __PACKAGE__-
>register_method({
507 path
=> '{vmid}/rrd',
509 protected
=> 1, # fixme: can we avoid that?
511 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
513 description
=> "Read VM RRD statistics (returns PNG)",
515 additionalProperties
=> 0,
517 node
=> get_standard_option
('pve-node'),
518 vmid
=> get_standard_option
('pve-vmid'),
520 description
=> "Specify the time frame you are interested in.",
522 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
525 description
=> "The list of datasources you want to display.",
526 type
=> 'string', format
=> 'pve-configid-list',
529 description
=> "The RRD consolidation function",
531 enum
=> [ 'AVERAGE', 'MAX' ],
539 filename
=> { type
=> 'string' },
545 return PVE
::Cluster
::create_rrd_graph
(
546 "pve2-vm/$param->{vmid}", $param->{timeframe
},
547 $param->{ds
}, $param->{cf
});
551 __PACKAGE__-
>register_method({
553 path
=> '{vmid}/rrddata',
555 protected
=> 1, # fixme: can we avoid that?
557 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
559 description
=> "Read VM RRD statistics",
561 additionalProperties
=> 0,
563 node
=> get_standard_option
('pve-node'),
564 vmid
=> get_standard_option
('pve-vmid'),
566 description
=> "Specify the time frame you are interested in.",
568 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
571 description
=> "The RRD consolidation function",
573 enum
=> [ 'AVERAGE', 'MAX' ],
588 return PVE
::Cluster
::create_rrd_data
(
589 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
592 __PACKAGE__-
>register_method({
593 name
=> 'destroy_vm',
598 description
=> "Destroy the container (also delete all uses files).",
600 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
603 additionalProperties
=> 0,
605 node
=> get_standard_option
('pve-node'),
606 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
615 my $rpcenv = PVE
::RPCEnvironment
::get
();
617 my $authuser = $rpcenv->get_user();
619 my $vmid = $param->{vmid
};
621 # test if container exists
622 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
624 my $storage_cfg = cfs_read_file
("storage.cfg");
626 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
628 die "unable to remove CT $vmid - used in HA resources\n"
629 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
631 # do not allow destroy if there are replication jobs
632 my $repl_conf = PVE
::ReplicationConfig-
>new();
633 $repl_conf->check_for_existing_jobs($vmid);
635 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
637 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
640 # reload config after lock
641 $conf = PVE
::LXC
::Config-
>load_config($vmid);
642 PVE
::LXC
::Config-
>check_lock($conf);
644 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
646 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
647 PVE
::AccessControl
::remove_vm_access
($vmid);
648 PVE
::Firewall
::remove_vmfw_conf
($vmid);
651 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
653 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
658 __PACKAGE__-
>register_method ({
660 path
=> '{vmid}/vncproxy',
664 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
666 description
=> "Creates a TCP VNC proxy connections.",
668 additionalProperties
=> 0,
670 node
=> get_standard_option
('pve-node'),
671 vmid
=> get_standard_option
('pve-vmid'),
675 description
=> "use websocket instead of standard VNC.",
679 description
=> "sets the width of the console in pixels.",
686 description
=> "sets the height of the console in pixels.",
694 additionalProperties
=> 0,
696 user
=> { type
=> 'string' },
697 ticket
=> { type
=> 'string' },
698 cert
=> { type
=> 'string' },
699 port
=> { type
=> 'integer' },
700 upid
=> { type
=> 'string' },
706 my $rpcenv = PVE
::RPCEnvironment
::get
();
708 my $authuser = $rpcenv->get_user();
710 my $vmid = $param->{vmid
};
711 my $node = $param->{node
};
713 my $authpath = "/vms/$vmid";
715 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
717 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
720 my ($remip, $family);
722 if ($node ne PVE
::INotify
::nodename
()) {
723 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
725 $family = PVE
::Tools
::get_host_address_family
($node);
728 my $port = PVE
::Tools
::next_vnc_port
($family);
730 # NOTE: vncterm VNC traffic is already TLS encrypted,
731 # so we select the fastest chipher here (or 'none'?)
732 my $remcmd = $remip ?
733 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
735 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
736 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
738 my $shcmd = [ '/usr/bin/dtach', '-A',
739 "/var/run/dtach/vzctlconsole$vmid",
740 '-r', 'winch', '-z', @$concmd];
745 syslog
('info', "starting lxc vnc proxy $upid\n");
749 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
750 '-timeout', $timeout, '-authpath', $authpath,
751 '-perm', 'VM.Console'];
753 if ($param->{width
}) {
754 push @$cmd, '-width', $param->{width
};
757 if ($param->{height
}) {
758 push @$cmd, '-height', $param->{height
};
761 if ($param->{websocket
}) {
762 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
763 push @$cmd, '-notls', '-listen', 'localhost';
766 push @$cmd, '-c', @$remcmd, @$shcmd;
768 run_command
($cmd, keeplocale
=> 1);
773 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
775 PVE
::Tools
::wait_for_vnc_port
($port);
786 __PACKAGE__-
>register_method ({
788 path
=> '{vmid}/termproxy',
792 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
794 description
=> "Creates a TCP proxy connection.",
796 additionalProperties
=> 0,
798 node
=> get_standard_option
('pve-node'),
799 vmid
=> get_standard_option
('pve-vmid'),
803 additionalProperties
=> 0,
805 user
=> { type
=> 'string' },
806 ticket
=> { type
=> 'string' },
807 port
=> { type
=> 'integer' },
808 upid
=> { type
=> 'string' },
814 my $rpcenv = PVE
::RPCEnvironment
::get
();
816 my $authuser = $rpcenv->get_user();
818 my $vmid = $param->{vmid
};
819 my $node = $param->{node
};
821 my $authpath = "/vms/$vmid";
823 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
825 my ($remip, $family);
827 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
828 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
830 $family = PVE
::Tools
::get_host_address_family
($node);
833 my $port = PVE
::Tools
::next_vnc_port
($family);
835 my $remcmd = $remip ?
836 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
838 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
839 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
841 my $shcmd = [ '/usr/bin/dtach', '-A',
842 "/var/run/dtach/vzctlconsole$vmid",
843 '-r', 'winch', '-z', @$concmd];
848 syslog
('info', "starting lxc termproxy $upid\n");
850 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
851 '--perm', 'VM.Console', '--'];
852 push @$cmd, @$remcmd, @$shcmd;
854 PVE
::Tools
::run_command
($cmd);
857 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
859 PVE
::Tools
::wait_for_vnc_port
($port);
869 __PACKAGE__-
>register_method({
870 name
=> 'vncwebsocket',
871 path
=> '{vmid}/vncwebsocket',
874 description
=> "You also need to pass a valid ticket (vncticket).",
875 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
877 description
=> "Opens a weksocket for VNC traffic.",
879 additionalProperties
=> 0,
881 node
=> get_standard_option
('pve-node'),
882 vmid
=> get_standard_option
('pve-vmid'),
884 description
=> "Ticket from previous call to vncproxy.",
889 description
=> "Port number returned by previous vncproxy call.",
899 port
=> { type
=> 'string' },
905 my $rpcenv = PVE
::RPCEnvironment
::get
();
907 my $authuser = $rpcenv->get_user();
909 my $authpath = "/vms/$param->{vmid}";
911 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
913 my $port = $param->{port
};
915 return { port
=> $port };
918 __PACKAGE__-
>register_method ({
919 name
=> 'spiceproxy',
920 path
=> '{vmid}/spiceproxy',
925 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
927 description
=> "Returns a SPICE configuration to connect to the CT.",
929 additionalProperties
=> 0,
931 node
=> get_standard_option
('pve-node'),
932 vmid
=> get_standard_option
('pve-vmid'),
933 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
936 returns
=> get_standard_option
('remote-viewer-config'),
940 my $vmid = $param->{vmid
};
941 my $node = $param->{node
};
942 my $proxy = $param->{proxy
};
944 my $authpath = "/vms/$vmid";
945 my $permissions = 'VM.Console';
947 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
949 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
951 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
953 my $shcmd = ['/usr/bin/dtach', '-A',
954 "/var/run/dtach/vzctlconsole$vmid",
955 '-r', 'winch', '-z', @$concmd];
957 my $title = "CT $vmid";
959 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
963 __PACKAGE__-
>register_method({
964 name
=> 'migrate_vm',
965 path
=> '{vmid}/migrate',
969 description
=> "Migrate the container to another node. Creates a new migration task.",
971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
974 additionalProperties
=> 0,
976 node
=> get_standard_option
('pve-node'),
977 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
978 target
=> get_standard_option
('pve-node', {
979 description
=> "Target node.",
980 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
984 description
=> "Use online/live migration.",
989 description
=> "Use restart migration",
994 description
=> "Timeout in seconds for shutdown for restart migration",
1000 description
=> "Force migration despite local bind / device" .
1001 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1008 description
=> "the task ID.",
1013 my $rpcenv = PVE
::RPCEnvironment
::get
();
1015 my $authuser = $rpcenv->get_user();
1017 my $target = extract_param
($param, 'target');
1019 my $localnode = PVE
::INotify
::nodename
();
1020 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1022 PVE
::Cluster
::check_cfs_quorum
();
1024 PVE
::Cluster
::check_node_exists
($target);
1026 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1028 my $vmid = extract_param
($param, 'vmid');
1031 PVE
::LXC
::Config-
>load_config($vmid);
1033 # try to detect errors early
1034 if (PVE
::LXC
::check_running
($vmid)) {
1035 die "can't migrate running container without --online or --restart\n"
1036 if !$param->{online
} && !$param->{restart
};
1039 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1044 my $service = "ct:$vmid";
1046 my $cmd = ['ha-manager', 'migrate', $service, $target];
1048 print "Requesting HA migration for CT $vmid to node $target\n";
1050 PVE
::Tools
::run_command
($cmd);
1055 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1060 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1064 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1067 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1071 __PACKAGE__-
>register_method({
1072 name
=> 'vm_feature',
1073 path
=> '{vmid}/feature',
1077 description
=> "Check if feature for virtual machine is available.",
1079 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1082 additionalProperties
=> 0,
1084 node
=> get_standard_option
('pve-node'),
1085 vmid
=> get_standard_option
('pve-vmid'),
1087 description
=> "Feature to check.",
1089 enum
=> [ 'snapshot', 'clone', 'copy' ],
1091 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1099 hasFeature
=> { type
=> 'boolean' },
1102 #items => { type => 'string' },
1109 my $node = extract_param
($param, 'node');
1111 my $vmid = extract_param
($param, 'vmid');
1113 my $snapname = extract_param
($param, 'snapname');
1115 my $feature = extract_param
($param, 'feature');
1117 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1120 my $snap = $conf->{snapshots
}->{$snapname};
1121 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1124 my $storage_cfg = PVE
::Storage
::config
();
1125 #Maybe include later
1126 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1127 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1130 hasFeature
=> $hasFeature,
1131 #nodes => [ keys %$nodelist ],
1135 __PACKAGE__-
>register_method({
1137 path
=> '{vmid}/template',
1141 description
=> "Create a Template.",
1143 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1144 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1147 additionalProperties
=> 0,
1149 node
=> get_standard_option
('pve-node'),
1150 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1153 returns
=> { type
=> 'null'},
1157 my $rpcenv = PVE
::RPCEnvironment
::get
();
1159 my $authuser = $rpcenv->get_user();
1161 my $node = extract_param
($param, 'node');
1163 my $vmid = extract_param
($param, 'vmid');
1165 my $updatefn = sub {
1167 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1168 PVE
::LXC
::Config-
>check_lock($conf);
1170 die "unable to create template, because CT contains snapshots\n"
1171 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1173 die "you can't convert a template to a template\n"
1174 if PVE
::LXC
::Config-
>is_template($conf);
1176 die "you can't convert a CT to template if the CT is running\n"
1177 if PVE
::LXC
::check_running
($vmid);
1179 my $scfg = PVE
::Storage
::config
();
1180 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1183 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1184 die "Directory storage '$sid' does not support container templates!\n"
1185 if $scfg->{ids
}->{$sid}->{path
};
1189 PVE
::LXC
::template_create
($vmid, $conf);
1191 $conf->{template
} = 1;
1193 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1194 # and remove lxc config
1195 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1198 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1201 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1206 __PACKAGE__-
>register_method({
1208 path
=> '{vmid}/clone',
1212 description
=> "Create a container clone/copy",
1214 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1215 "and 'VM.Allocate' permissions " .
1216 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1217 "'Datastore.AllocateSpace' on any used storage.",
1220 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1222 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1223 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1228 additionalProperties
=> 0,
1230 node
=> get_standard_option
('pve-node'),
1231 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1232 newid
=> get_standard_option
('pve-vmid', {
1233 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1234 description
=> 'VMID for the clone.' }),
1237 type
=> 'string', format
=> 'dns-name',
1238 description
=> "Set a hostname for the new CT.",
1243 description
=> "Description for the new CT.",
1247 type
=> 'string', format
=> 'pve-poolid',
1248 description
=> "Add the new CT to the specified pool.",
1250 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1253 storage
=> get_standard_option
('pve-storage-id', {
1254 description
=> "Target storage for full clone.",
1260 description
=> "Create a full copy of all disks. This is always done when " .
1261 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1263 target
=> get_standard_option
('pve-node', {
1264 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1275 my $rpcenv = PVE
::RPCEnvironment
::get
();
1277 my $authuser = $rpcenv->get_user();
1279 my $node = extract_param
($param, 'node');
1281 my $vmid = extract_param
($param, 'vmid');
1283 my $newid = extract_param
($param, 'newid');
1285 my $pool = extract_param
($param, 'pool');
1287 if (defined($pool)) {
1288 $rpcenv->check_pool_exist($pool);
1291 my $snapname = extract_param
($param, 'snapname');
1293 my $storage = extract_param
($param, 'storage');
1295 my $target = extract_param
($param, 'target');
1297 my $localnode = PVE
::INotify
::nodename
();
1299 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1301 PVE
::Cluster
::check_node_exists
($target) if $target;
1303 my $storecfg = PVE
::Storage
::config
();
1306 # check if storage is enabled on local node
1307 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1309 # check if storage is available on target node
1310 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1311 # clone only works if target storage is shared
1312 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1313 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1317 PVE
::Cluster
::check_cfs_quorum
();
1321 my $mountpoints = {};
1326 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1327 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1329 $running = PVE
::LXC
::check_running
($vmid) || 0;
1331 my $full = extract_param
($param, 'full');
1332 if (!defined($full)) {
1333 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1335 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1338 die "snapshot '$snapname' does not exist\n"
1339 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1342 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1344 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1345 die "unable to create CT $newid: config file already exists\n"
1349 foreach my $opt (keys %$src_conf) {
1350 next if $opt =~ m/^unused\d+$/;
1352 my $value = $src_conf->{$opt};
1354 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1355 my $mp = $opt eq 'rootfs' ?
1356 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1357 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1359 if ($mp->{type
} eq 'volume') {
1360 my $volid = $mp->{volume
};
1362 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1363 $sid = $storage if defined($storage);
1364 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1365 if (!$scfg->{shared
}) {
1367 warn "found non-shared volume: $volid\n" if $target;
1370 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1373 die "Cannot do full clones on a running container without snapshots\n"
1374 if $running && !defined($snapname);
1375 $fullclone->{$opt} = 1;
1377 # not full means clone instead of copy
1378 die "Linked clone feature for '$volid' is not available\n"
1379 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1382 $mountpoints->{$opt} = $mp;
1383 push @$vollist, $volid;
1386 # TODO: allow bind mounts?
1387 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1389 } elsif ($opt =~ m/^net(\d+)$/) {
1390 # always change MAC! address
1391 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1392 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1393 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1394 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1396 # copy everything else
1397 $newconf->{$opt} = $value;
1400 die "can't clone CT to node '$target' (CT uses local storage)\n"
1401 if $target && !$sharedvm;
1403 # Replace the 'disk' lock with a 'create' lock.
1404 $newconf->{lock} = 'create';
1406 delete $newconf->{template
};
1407 if ($param->{hostname
}) {
1408 $newconf->{hostname
} = $param->{hostname
};
1411 if ($param->{description
}) {
1412 $newconf->{description
} = $param->{description
};
1415 # create empty/temp config - this fails if CT already exists on other node
1416 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1419 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1425 my $update_conf = sub {
1426 my ($key, $value) = @_;
1427 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1428 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1429 die "Lost 'create' config lock, aborting.\n"
1430 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1431 $conf->{$key} = $value;
1432 PVE
::LXC
::Config-
>write_config($newid, $conf);
1439 my $newvollist = [];
1441 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1442 die "unexpected state change\n" if $verify_running != $running;
1448 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1450 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1452 foreach my $opt (keys %$mountpoints) {
1453 my $mp = $mountpoints->{$opt};
1454 my $volid = $mp->{volume
};
1457 if ($fullclone->{$opt}) {
1458 print "create full clone of mountpoint $opt ($volid)\n";
1459 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1460 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1462 print "create linked clone of mount point $opt ($volid)\n";
1463 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1466 push @$newvollist, $newvolid;
1467 $mp->{volume
} = $newvolid;
1469 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1472 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1473 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1476 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1477 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1478 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1480 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1481 die "Failed to move config to node '$target' - rename failed: $!\n"
1482 if !rename($conffile, $newconffile);
1487 # Unlock the source config in any case:
1488 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1492 # Now cleanup the config & disks:
1495 sleep 1; # some storages like rbd need to wait before release volume - really?
1497 foreach my $volid (@$newvollist) {
1498 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1501 die "clone failed: $err";
1507 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1508 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1512 __PACKAGE__-
>register_method({
1513 name
=> 'resize_vm',
1514 path
=> '{vmid}/resize',
1518 description
=> "Resize a container mount point.",
1520 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1523 additionalProperties
=> 0,
1525 node
=> get_standard_option
('pve-node'),
1526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1529 description
=> "The disk you want to resize.",
1530 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1534 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1535 description
=> "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
1539 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1547 description
=> "the task ID.",
1552 my $rpcenv = PVE
::RPCEnvironment
::get
();
1554 my $authuser = $rpcenv->get_user();
1556 my $node = extract_param
($param, 'node');
1558 my $vmid = extract_param
($param, 'vmid');
1560 my $digest = extract_param
($param, 'digest');
1562 my $sizestr = extract_param
($param, 'size');
1563 my $ext = ($sizestr =~ s/^\+//);
1564 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1565 die "invalid size string" if !defined($newsize);
1567 die "no options specified\n" if !scalar(keys %$param);
1569 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1571 my $storage_cfg = cfs_read_file
("storage.cfg");
1575 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1576 PVE
::LXC
::Config-
>check_lock($conf);
1578 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1580 my $running = PVE
::LXC
::check_running
($vmid);
1582 my $disk = $param->{disk
};
1583 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1584 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1586 my $volid = $mp->{volume
};
1588 my (undef, undef, $owner, undef, undef, undef, $format) =
1589 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1591 die "can't resize mount point owned by another container ($owner)"
1594 die "can't resize volume: $disk if snapshot exists\n"
1595 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1597 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1599 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1601 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1603 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1604 $newsize += $size if $ext;
1605 $newsize = int($newsize);
1607 die "unable to shrink disk size\n" if $newsize < $size;
1609 return if $size == $newsize;
1611 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1613 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1614 # we pass 0 here (parameter only makes sense for qemu)
1615 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1617 $mp->{size
} = $newsize;
1618 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1620 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1622 if ($format eq 'raw') {
1623 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1627 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1628 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1629 die "internal error: CT running but mount point not attached to a loop device"
1631 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1633 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1634 # to be visible to it in its namespace.
1635 # To not interfere with the rest of the system we unshare the current mount namespace,
1636 # mount over /tmp and then run resize2fs.
1638 # interestingly we don't need to e2fsck on mounted systems...
1639 my $quoted = PVE
::Tools
::shellquote
($path);
1640 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1642 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1644 warn "Failed to update the container's filesystem: $@\n" if $@;
1647 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1648 PVE
::Tools
::run_command
(['resize2fs', $path]);
1650 warn "Failed to update the container's filesystem: $@\n" if $@;
1655 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1658 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1661 __PACKAGE__-
>register_method({
1662 name
=> 'move_volume',
1663 path
=> '{vmid}/move_volume',
1667 description
=> "Move a rootfs-/mp-volume to a different storage",
1669 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1670 "and 'Datastore.AllocateSpace' permissions on the storage.",
1673 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1674 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1678 additionalProperties
=> 0,
1680 node
=> get_standard_option
('pve-node'),
1681 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1684 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1685 description
=> "Volume which will be moved.",
1687 storage
=> get_standard_option
('pve-storage-id', {
1688 description
=> "Target Storage.",
1689 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1693 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1699 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1711 my $rpcenv = PVE
::RPCEnvironment
::get
();
1713 my $authuser = $rpcenv->get_user();
1715 my $vmid = extract_param
($param, 'vmid');
1717 my $storage = extract_param
($param, 'storage');
1719 my $mpkey = extract_param
($param, 'volume');
1721 my $lockname = 'disk';
1723 my ($mpdata, $old_volid);
1725 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1726 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1727 PVE
::LXC
::Config-
>check_lock($conf);
1729 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1731 if ($mpkey eq 'rootfs') {
1732 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1733 } elsif ($mpkey =~ m/mp\d+/) {
1734 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1736 die "Can't parse $mpkey\n";
1738 $old_volid = $mpdata->{volume
};
1740 die "you can't move a volume with snapshots and delete the source\n"
1741 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1743 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1745 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1750 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1752 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1753 my $storage_cfg = PVE
::Storage
::config
();
1758 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1759 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1760 $mpdata->{volume
} = $new_volid;
1762 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1763 my $digest = $conf->{digest
};
1764 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1765 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1767 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1769 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1771 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1775 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1776 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1782 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1783 if defined($new_volid);
1789 if ($param->{delete}) {
1791 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1792 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1798 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1803 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1806 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };