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'),
78 vmid
=> get_standard_option
('pve-vmid'),
80 description
=> "LXC Container status.",
82 enum
=> ['stopped', 'running'],
85 description
=> "Maximum memory in bytes.",
91 description
=> "Maximum SWAP memory in bytes.",
97 description
=> "Root disk size in bytes.",
103 description
=> "Container name.",
108 description
=> "Uptime.",
111 renderer
=> 'duration',
114 description
=> "Maximum usable CPUs.",
120 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
125 my $rpcenv = PVE
::RPCEnvironment
::get
();
126 my $authuser = $rpcenv->get_user();
128 my $vmstatus = PVE
::LXC
::vmstatus
();
131 foreach my $vmid (keys %$vmstatus) {
132 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
134 my $data = $vmstatus->{$vmid};
135 $data->{vmid
} = $vmid;
143 __PACKAGE__-
>register_method({
147 description
=> "Create or restore a container.",
149 user
=> 'all', # check inside
150 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
151 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
152 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
157 additionalProperties
=> 0,
158 properties
=> PVE
::LXC
::Config-
>json_config_properties({
159 node
=> get_standard_option
('pve-node'),
160 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
162 description
=> "The OS template or backup file.",
165 completion
=> \
&PVE
::LXC
::complete_os_templates
,
170 description
=> "Sets root password inside container.",
173 storage
=> get_standard_option
('pve-storage-id', {
174 description
=> "Default Storage.",
177 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
182 description
=> "Allow to overwrite existing container.",
187 description
=> "Mark this as restore task.",
191 type
=> 'string', format
=> 'pve-poolid',
192 description
=> "Add the VM to the specified pool.",
194 'ignore-unpack-errors' => {
197 description
=> "Ignore errors when extracting the template.",
199 'ssh-public-keys' => {
202 description
=> "Setup public SSH keys (one key per line, " .
206 description
=> "Override i/o bandwidth limit (in KiB/s).",
215 description
=> "Start the CT after its creation finished successfully.",
225 my $rpcenv = PVE
::RPCEnvironment
::get
();
227 my $authuser = $rpcenv->get_user();
229 my $node = extract_param
($param, 'node');
231 my $vmid = extract_param
($param, 'vmid');
233 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
235 my $bwlimit = extract_param
($param, 'bwlimit');
237 my $start_after_create = extract_param
($param, 'start');
239 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
241 my $same_container_exists = -f
$basecfg_fn;
243 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
244 my $unprivileged = extract_param
($param, 'unprivileged');
246 my $restore = extract_param
($param, 'restore');
249 # fixme: limit allowed parameters
253 my $force = extract_param
($param, 'force');
255 if (!($same_container_exists && $restore && $force)) {
256 PVE
::Cluster
::check_vmid_unused
($vmid);
258 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
259 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
262 my $password = extract_param
($param, 'password');
264 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
265 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
267 my $pool = extract_param
($param, 'pool');
269 if (defined($pool)) {
270 $rpcenv->check_pool_exist($pool);
271 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
274 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
276 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
278 } elsif ($restore && $force && $same_container_exists &&
279 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
280 # OK: user has VM.Backup permissions, and want to restore an existing VM
285 my $ostemplate = extract_param
($param, 'ostemplate');
286 my $storage = extract_param
($param, 'storage') // 'local';
288 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
290 my $storage_cfg = cfs_read_file
("storage.cfg");
295 if ($ostemplate eq '-') {
296 die "pipe requires cli environment\n"
297 if $rpcenv->{type
} ne 'cli';
298 die "pipe can only be used with restore tasks\n"
301 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
303 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
304 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
308 my $check_and_activate_storage = sub {
311 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
313 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
314 if !$scfg->{content
}->{rootdir
};
316 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
318 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
320 $used_storages{$sid} = 1;
325 my $no_disk_param = {};
327 my $storage_only_mode = 1;
328 foreach my $opt (keys %$param) {
329 my $value = $param->{$opt};
330 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
331 # allow to use simple numbers (add default storage in that case)
332 if ($value =~ m/^\d+(\.\d+)?$/) {
333 $mp_param->{$opt} = "$storage:$value";
335 $mp_param->{$opt} = $value;
337 $storage_only_mode = 0;
338 } elsif ($opt =~ m/^unused\d+$/) {
339 warn "ignoring '$opt', cannot create/restore with unused volume\n";
340 delete $param->{$opt};
342 $no_disk_param->{$opt} = $value;
346 die "mount points configured, but 'rootfs' not set - aborting\n"
347 if !$storage_only_mode && !defined($mp_param->{rootfs
});
349 # check storage access, activate storage
350 my $delayed_mp_param = {};
351 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
352 my ($ms, $mountpoint) = @_;
354 my $volid = $mountpoint->{volume
};
355 my $mp = $mountpoint->{mp
};
357 if ($mountpoint->{type
} ne 'volume') { # bind or device
358 die "Only root can pass arbitrary filesystem paths.\n"
359 if $authuser ne 'root@pam';
361 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
362 &$check_and_activate_storage($sid);
366 # check/activate default storage
367 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
369 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
371 $conf->{unprivileged
} = 1 if $unprivileged;
373 my $check_vmid_usage = sub {
375 die "can't overwrite running container\n"
376 if PVE
::LXC
::check_running
($vmid);
378 PVE
::Cluster
::check_vmid_unused
($vmid);
383 &$check_vmid_usage(); # final check after locking
386 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
388 die "container exists" if !$restore; # just to be sure
389 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
392 # try to create empty config on local node, we have an flock
393 PVE
::LXC
::Config-
>write_config($vmid, {});
396 # another node was faster, abort
397 die "Could not reserve ID $vmid, already taken\n" if $@;
400 PVE
::Cluster
::check_cfs_quorum
();
404 if ($storage_only_mode) {
406 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
407 die "rootfs configuration could not be recovered, please check and specify manually!\n"
408 if !defined($mp_param->{rootfs
});
409 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
410 my ($ms, $mountpoint) = @_;
411 my $type = $mountpoint->{type
};
412 if ($type eq 'volume') {
413 die "unable to detect disk size - please specify $ms (size)\n"
414 if !defined($mountpoint->{size
});
415 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
416 delete $mountpoint->{size
};
417 $mountpoint->{volume
} = "$storage:$disksize";
418 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
420 my $type = $mountpoint->{type
};
421 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
422 if ($ms eq 'rootfs');
423 die "restoring '$ms' to $type mount is only possible for root\n"
424 if $authuser ne 'root@pam';
426 if ($mountpoint->{backup
}) {
427 warn "WARNING - unsupported configuration!\n";
428 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
429 warn "mount point configuration will be restored after archive extraction!\n";
430 warn "contained files will be restored to wrong directory!\n";
432 delete $mp_param->{$ms}; # actually delay bind/dev mps
433 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
437 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
441 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
443 if (defined($old_conf)) {
444 # destroy old container volumes
445 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
449 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
450 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
451 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
454 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
456 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
457 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
458 $lxc_setup->post_create_hook($password, $ssh_keys);
462 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
463 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
466 $conf->{hostname
} ||= "CT$vmid";
467 $conf->{memory
} ||= 512;
468 $conf->{swap
} //= 512;
469 foreach my $mp (keys %$delayed_mp_param) {
470 $conf->{$mp} = $delayed_mp_param->{$mp};
472 PVE
::LXC
::Config-
>write_config($vmid, $conf);
475 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
476 PVE
::LXC
::destroy_config
($vmid);
479 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
481 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
482 if $start_after_create;
485 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
487 &$check_vmid_usage(); # first check before locking
489 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
490 $vmid, $authuser, $realcmd);
494 __PACKAGE__-
>register_method({
499 description
=> "Directory index",
504 additionalProperties
=> 0,
506 node
=> get_standard_option
('pve-node'),
507 vmid
=> get_standard_option
('pve-vmid'),
515 subdir
=> { type
=> 'string' },
518 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
524 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
527 { subdir
=> 'config' },
528 { subdir
=> 'status' },
529 { subdir
=> 'vncproxy' },
530 { subdir
=> 'termproxy' },
531 { subdir
=> 'vncwebsocket' },
532 { subdir
=> 'spiceproxy' },
533 { subdir
=> 'migrate' },
534 { subdir
=> 'clone' },
535 # { subdir => 'initlog' },
537 { subdir
=> 'rrddata' },
538 { subdir
=> 'firewall' },
539 { subdir
=> 'snapshot' },
540 { subdir
=> 'resize' },
547 __PACKAGE__-
>register_method({
549 path
=> '{vmid}/rrd',
551 protected
=> 1, # fixme: can we avoid that?
553 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
555 description
=> "Read VM RRD statistics (returns PNG)",
557 additionalProperties
=> 0,
559 node
=> get_standard_option
('pve-node'),
560 vmid
=> get_standard_option
('pve-vmid'),
562 description
=> "Specify the time frame you are interested in.",
564 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
567 description
=> "The list of datasources you want to display.",
568 type
=> 'string', format
=> 'pve-configid-list',
571 description
=> "The RRD consolidation function",
573 enum
=> [ 'AVERAGE', 'MAX' ],
581 filename
=> { type
=> 'string' },
587 return PVE
::Cluster
::create_rrd_graph
(
588 "pve2-vm/$param->{vmid}", $param->{timeframe
},
589 $param->{ds
}, $param->{cf
});
593 __PACKAGE__-
>register_method({
595 path
=> '{vmid}/rrddata',
597 protected
=> 1, # fixme: can we avoid that?
599 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
601 description
=> "Read VM RRD statistics",
603 additionalProperties
=> 0,
605 node
=> get_standard_option
('pve-node'),
606 vmid
=> get_standard_option
('pve-vmid'),
608 description
=> "Specify the time frame you are interested in.",
610 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
613 description
=> "The RRD consolidation function",
615 enum
=> [ 'AVERAGE', 'MAX' ],
630 return PVE
::Cluster
::create_rrd_data
(
631 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
634 __PACKAGE__-
>register_method({
635 name
=> 'destroy_vm',
640 description
=> "Destroy the container (also delete all uses files).",
642 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
645 additionalProperties
=> 0,
647 node
=> get_standard_option
('pve-node'),
648 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
657 my $rpcenv = PVE
::RPCEnvironment
::get
();
659 my $authuser = $rpcenv->get_user();
661 my $vmid = $param->{vmid
};
663 # test if container exists
664 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
666 my $storage_cfg = cfs_read_file
("storage.cfg");
668 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
670 die "unable to remove CT $vmid - used in HA resources\n"
671 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
673 # do not allow destroy if there are replication jobs
674 my $repl_conf = PVE
::ReplicationConfig-
>new();
675 $repl_conf->check_for_existing_jobs($vmid);
677 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
679 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
682 # reload config after lock
683 $conf = PVE
::LXC
::Config-
>load_config($vmid);
684 PVE
::LXC
::Config-
>check_lock($conf);
686 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
688 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
689 PVE
::AccessControl
::remove_vm_access
($vmid);
690 PVE
::Firewall
::remove_vmfw_conf
($vmid);
693 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
695 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
700 __PACKAGE__-
>register_method ({
702 path
=> '{vmid}/vncproxy',
706 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
708 description
=> "Creates a TCP VNC proxy connections.",
710 additionalProperties
=> 0,
712 node
=> get_standard_option
('pve-node'),
713 vmid
=> get_standard_option
('pve-vmid'),
717 description
=> "use websocket instead of standard VNC.",
721 description
=> "sets the width of the console in pixels.",
728 description
=> "sets the height of the console in pixels.",
736 additionalProperties
=> 0,
738 user
=> { type
=> 'string' },
739 ticket
=> { type
=> 'string' },
740 cert
=> { type
=> 'string' },
741 port
=> { type
=> 'integer' },
742 upid
=> { type
=> 'string' },
748 my $rpcenv = PVE
::RPCEnvironment
::get
();
750 my $authuser = $rpcenv->get_user();
752 my $vmid = $param->{vmid
};
753 my $node = $param->{node
};
755 my $authpath = "/vms/$vmid";
757 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
759 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
762 my ($remip, $family);
764 if ($node ne PVE
::INotify
::nodename
()) {
765 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
767 $family = PVE
::Tools
::get_host_address_family
($node);
770 my $port = PVE
::Tools
::next_vnc_port
($family);
772 # NOTE: vncterm VNC traffic is already TLS encrypted,
773 # so we select the fastest chipher here (or 'none'?)
774 my $remcmd = $remip ?
775 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
777 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
778 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
780 my $shcmd = [ '/usr/bin/dtach', '-A',
781 "/var/run/dtach/vzctlconsole$vmid",
782 '-r', 'winch', '-z', @$concmd];
787 syslog
('info', "starting lxc vnc proxy $upid\n");
791 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
792 '-timeout', $timeout, '-authpath', $authpath,
793 '-perm', 'VM.Console'];
795 if ($param->{width
}) {
796 push @$cmd, '-width', $param->{width
};
799 if ($param->{height
}) {
800 push @$cmd, '-height', $param->{height
};
803 if ($param->{websocket
}) {
804 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
805 push @$cmd, '-notls', '-listen', 'localhost';
808 push @$cmd, '-c', @$remcmd, @$shcmd;
810 run_command
($cmd, keeplocale
=> 1);
815 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
817 PVE
::Tools
::wait_for_vnc_port
($port);
828 __PACKAGE__-
>register_method ({
830 path
=> '{vmid}/termproxy',
834 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
836 description
=> "Creates a TCP proxy connection.",
838 additionalProperties
=> 0,
840 node
=> get_standard_option
('pve-node'),
841 vmid
=> get_standard_option
('pve-vmid'),
845 additionalProperties
=> 0,
847 user
=> { type
=> 'string' },
848 ticket
=> { type
=> 'string' },
849 port
=> { type
=> 'integer' },
850 upid
=> { type
=> 'string' },
856 my $rpcenv = PVE
::RPCEnvironment
::get
();
858 my $authuser = $rpcenv->get_user();
860 my $vmid = $param->{vmid
};
861 my $node = $param->{node
};
863 my $authpath = "/vms/$vmid";
865 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
867 my ($remip, $family);
869 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
870 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
872 $family = PVE
::Tools
::get_host_address_family
($node);
875 my $port = PVE
::Tools
::next_vnc_port
($family);
877 my $remcmd = $remip ?
878 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
880 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
881 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
883 my $shcmd = [ '/usr/bin/dtach', '-A',
884 "/var/run/dtach/vzctlconsole$vmid",
885 '-r', 'winch', '-z', @$concmd];
890 syslog
('info', "starting lxc termproxy $upid\n");
892 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
893 '--perm', 'VM.Console', '--'];
894 push @$cmd, @$remcmd, @$shcmd;
896 PVE
::Tools
::run_command
($cmd);
899 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
901 PVE
::Tools
::wait_for_vnc_port
($port);
911 __PACKAGE__-
>register_method({
912 name
=> 'vncwebsocket',
913 path
=> '{vmid}/vncwebsocket',
916 description
=> "You also need to pass a valid ticket (vncticket).",
917 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
919 description
=> "Opens a weksocket for VNC traffic.",
921 additionalProperties
=> 0,
923 node
=> get_standard_option
('pve-node'),
924 vmid
=> get_standard_option
('pve-vmid'),
926 description
=> "Ticket from previous call to vncproxy.",
931 description
=> "Port number returned by previous vncproxy call.",
941 port
=> { type
=> 'string' },
947 my $rpcenv = PVE
::RPCEnvironment
::get
();
949 my $authuser = $rpcenv->get_user();
951 my $authpath = "/vms/$param->{vmid}";
953 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
955 my $port = $param->{port
};
957 return { port
=> $port };
960 __PACKAGE__-
>register_method ({
961 name
=> 'spiceproxy',
962 path
=> '{vmid}/spiceproxy',
967 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
969 description
=> "Returns a SPICE configuration to connect to the CT.",
971 additionalProperties
=> 0,
973 node
=> get_standard_option
('pve-node'),
974 vmid
=> get_standard_option
('pve-vmid'),
975 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
978 returns
=> get_standard_option
('remote-viewer-config'),
982 my $vmid = $param->{vmid
};
983 my $node = $param->{node
};
984 my $proxy = $param->{proxy
};
986 my $authpath = "/vms/$vmid";
987 my $permissions = 'VM.Console';
989 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
991 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
993 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
995 my $shcmd = ['/usr/bin/dtach', '-A',
996 "/var/run/dtach/vzctlconsole$vmid",
997 '-r', 'winch', '-z', @$concmd];
999 my $title = "CT $vmid";
1001 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1005 __PACKAGE__-
>register_method({
1006 name
=> 'migrate_vm',
1007 path
=> '{vmid}/migrate',
1011 description
=> "Migrate the container to another node. Creates a new migration task.",
1013 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1016 additionalProperties
=> 0,
1018 node
=> get_standard_option
('pve-node'),
1019 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1020 target
=> get_standard_option
('pve-node', {
1021 description
=> "Target node.",
1022 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1026 description
=> "Use online/live migration.",
1031 description
=> "Use restart migration",
1036 description
=> "Timeout in seconds for shutdown for restart migration",
1042 description
=> "Force migration despite local bind / device" .
1043 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1050 description
=> "the task ID.",
1055 my $rpcenv = PVE
::RPCEnvironment
::get
();
1057 my $authuser = $rpcenv->get_user();
1059 my $target = extract_param
($param, 'target');
1061 my $localnode = PVE
::INotify
::nodename
();
1062 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1064 PVE
::Cluster
::check_cfs_quorum
();
1066 PVE
::Cluster
::check_node_exists
($target);
1068 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1070 my $vmid = extract_param
($param, 'vmid');
1073 PVE
::LXC
::Config-
>load_config($vmid);
1075 # try to detect errors early
1076 if (PVE
::LXC
::check_running
($vmid)) {
1077 die "can't migrate running container without --online or --restart\n"
1078 if !$param->{online
} && !$param->{restart
};
1081 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1086 my $service = "ct:$vmid";
1088 my $cmd = ['ha-manager', 'migrate', $service, $target];
1090 print "Requesting HA migration for CT $vmid to node $target\n";
1092 PVE
::Tools
::run_command
($cmd);
1097 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1102 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1106 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1109 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1113 __PACKAGE__-
>register_method({
1114 name
=> 'vm_feature',
1115 path
=> '{vmid}/feature',
1119 description
=> "Check if feature for virtual machine is available.",
1121 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1124 additionalProperties
=> 0,
1126 node
=> get_standard_option
('pve-node'),
1127 vmid
=> get_standard_option
('pve-vmid'),
1129 description
=> "Feature to check.",
1131 enum
=> [ 'snapshot', 'clone', 'copy' ],
1133 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1141 hasFeature
=> { type
=> 'boolean' },
1144 #items => { type => 'string' },
1151 my $node = extract_param
($param, 'node');
1153 my $vmid = extract_param
($param, 'vmid');
1155 my $snapname = extract_param
($param, 'snapname');
1157 my $feature = extract_param
($param, 'feature');
1159 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1162 my $snap = $conf->{snapshots
}->{$snapname};
1163 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1166 my $storage_cfg = PVE
::Storage
::config
();
1167 #Maybe include later
1168 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1169 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1172 hasFeature
=> $hasFeature,
1173 #nodes => [ keys %$nodelist ],
1177 __PACKAGE__-
>register_method({
1179 path
=> '{vmid}/template',
1183 description
=> "Create a Template.",
1185 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1186 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1189 additionalProperties
=> 0,
1191 node
=> get_standard_option
('pve-node'),
1192 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1195 returns
=> { type
=> 'null'},
1199 my $rpcenv = PVE
::RPCEnvironment
::get
();
1201 my $authuser = $rpcenv->get_user();
1203 my $node = extract_param
($param, 'node');
1205 my $vmid = extract_param
($param, 'vmid');
1207 my $updatefn = sub {
1209 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1210 PVE
::LXC
::Config-
>check_lock($conf);
1212 die "unable to create template, because CT contains snapshots\n"
1213 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1215 die "you can't convert a template to a template\n"
1216 if PVE
::LXC
::Config-
>is_template($conf);
1218 die "you can't convert a CT to template if the CT is running\n"
1219 if PVE
::LXC
::check_running
($vmid);
1221 my $scfg = PVE
::Storage
::config
();
1222 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1225 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1226 die "Directory storage '$sid' does not support container templates!\n"
1227 if $scfg->{ids
}->{$sid}->{path
};
1231 PVE
::LXC
::template_create
($vmid, $conf);
1233 $conf->{template
} = 1;
1235 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1236 # and remove lxc config
1237 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1240 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1243 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1248 __PACKAGE__-
>register_method({
1250 path
=> '{vmid}/clone',
1254 description
=> "Create a container clone/copy",
1256 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1257 "and 'VM.Allocate' permissions " .
1258 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1259 "'Datastore.AllocateSpace' on any used storage.",
1262 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1264 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1265 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1270 additionalProperties
=> 0,
1272 node
=> get_standard_option
('pve-node'),
1273 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1274 newid
=> get_standard_option
('pve-vmid', {
1275 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1276 description
=> 'VMID for the clone.' }),
1279 type
=> 'string', format
=> 'dns-name',
1280 description
=> "Set a hostname for the new CT.",
1285 description
=> "Description for the new CT.",
1289 type
=> 'string', format
=> 'pve-poolid',
1290 description
=> "Add the new CT to the specified pool.",
1292 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1295 storage
=> get_standard_option
('pve-storage-id', {
1296 description
=> "Target storage for full clone.",
1302 description
=> "Create a full copy of all disks. This is always done when " .
1303 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1305 target
=> get_standard_option
('pve-node', {
1306 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1317 my $rpcenv = PVE
::RPCEnvironment
::get
();
1319 my $authuser = $rpcenv->get_user();
1321 my $node = extract_param
($param, 'node');
1323 my $vmid = extract_param
($param, 'vmid');
1325 my $newid = extract_param
($param, 'newid');
1327 my $pool = extract_param
($param, 'pool');
1329 if (defined($pool)) {
1330 $rpcenv->check_pool_exist($pool);
1333 my $snapname = extract_param
($param, 'snapname');
1335 my $storage = extract_param
($param, 'storage');
1337 my $target = extract_param
($param, 'target');
1339 my $localnode = PVE
::INotify
::nodename
();
1341 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1343 PVE
::Cluster
::check_node_exists
($target) if $target;
1345 my $storecfg = PVE
::Storage
::config
();
1348 # check if storage is enabled on local node
1349 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1351 # check if storage is available on target node
1352 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1353 # clone only works if target storage is shared
1354 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1355 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1359 PVE
::Cluster
::check_cfs_quorum
();
1363 my $mountpoints = {};
1368 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1369 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1371 $running = PVE
::LXC
::check_running
($vmid) || 0;
1373 my $full = extract_param
($param, 'full');
1374 if (!defined($full)) {
1375 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1377 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1380 die "snapshot '$snapname' does not exist\n"
1381 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1384 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1386 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1387 die "unable to create CT $newid: config file already exists\n"
1391 foreach my $opt (keys %$src_conf) {
1392 next if $opt =~ m/^unused\d+$/;
1394 my $value = $src_conf->{$opt};
1396 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1397 my $mp = $opt eq 'rootfs' ?
1398 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1399 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1401 if ($mp->{type
} eq 'volume') {
1402 my $volid = $mp->{volume
};
1404 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1405 $sid = $storage if defined($storage);
1406 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1407 if (!$scfg->{shared
}) {
1409 warn "found non-shared volume: $volid\n" if $target;
1412 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1415 die "Cannot do full clones on a running container without snapshots\n"
1416 if $running && !defined($snapname);
1417 $fullclone->{$opt} = 1;
1419 # not full means clone instead of copy
1420 die "Linked clone feature for '$volid' is not available\n"
1421 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1424 $mountpoints->{$opt} = $mp;
1425 push @$vollist, $volid;
1428 # TODO: allow bind mounts?
1429 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1431 } elsif ($opt =~ m/^net(\d+)$/) {
1432 # always change MAC! address
1433 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1434 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1435 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1436 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1438 # copy everything else
1439 $newconf->{$opt} = $value;
1442 die "can't clone CT to node '$target' (CT uses local storage)\n"
1443 if $target && !$sharedvm;
1445 # Replace the 'disk' lock with a 'create' lock.
1446 $newconf->{lock} = 'create';
1448 delete $newconf->{template
};
1449 if ($param->{hostname
}) {
1450 $newconf->{hostname
} = $param->{hostname
};
1453 if ($param->{description
}) {
1454 $newconf->{description
} = $param->{description
};
1457 # create empty/temp config - this fails if CT already exists on other node
1458 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1461 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1467 my $update_conf = sub {
1468 my ($key, $value) = @_;
1469 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1470 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1471 die "Lost 'create' config lock, aborting.\n"
1472 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1473 $conf->{$key} = $value;
1474 PVE
::LXC
::Config-
>write_config($newid, $conf);
1481 my $newvollist = [];
1483 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1484 die "unexpected state change\n" if $verify_running != $running;
1490 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1492 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1494 foreach my $opt (keys %$mountpoints) {
1495 my $mp = $mountpoints->{$opt};
1496 my $volid = $mp->{volume
};
1499 if ($fullclone->{$opt}) {
1500 print "create full clone of mountpoint $opt ($volid)\n";
1501 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1502 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1504 print "create linked clone of mount point $opt ($volid)\n";
1505 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1508 push @$newvollist, $newvolid;
1509 $mp->{volume
} = $newvolid;
1511 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1514 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1515 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1518 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1519 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1520 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1522 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1523 die "Failed to move config to node '$target' - rename failed: $!\n"
1524 if !rename($conffile, $newconffile);
1529 # Unlock the source config in any case:
1530 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1534 # Now cleanup the config & disks:
1537 sleep 1; # some storages like rbd need to wait before release volume - really?
1539 foreach my $volid (@$newvollist) {
1540 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1543 die "clone failed: $err";
1549 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1550 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1554 __PACKAGE__-
>register_method({
1555 name
=> 'resize_vm',
1556 path
=> '{vmid}/resize',
1560 description
=> "Resize a container mount point.",
1562 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1565 additionalProperties
=> 0,
1567 node
=> get_standard_option
('pve-node'),
1568 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1571 description
=> "The disk you want to resize.",
1572 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1576 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1577 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.",
1581 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1589 description
=> "the task ID.",
1594 my $rpcenv = PVE
::RPCEnvironment
::get
();
1596 my $authuser = $rpcenv->get_user();
1598 my $node = extract_param
($param, 'node');
1600 my $vmid = extract_param
($param, 'vmid');
1602 my $digest = extract_param
($param, 'digest');
1604 my $sizestr = extract_param
($param, 'size');
1605 my $ext = ($sizestr =~ s/^\+//);
1606 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1607 die "invalid size string" if !defined($newsize);
1609 die "no options specified\n" if !scalar(keys %$param);
1611 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1613 my $storage_cfg = cfs_read_file
("storage.cfg");
1617 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1618 PVE
::LXC
::Config-
>check_lock($conf);
1620 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1622 my $running = PVE
::LXC
::check_running
($vmid);
1624 my $disk = $param->{disk
};
1625 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1626 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1628 my $volid = $mp->{volume
};
1630 my (undef, undef, $owner, undef, undef, undef, $format) =
1631 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1633 die "can't resize mount point owned by another container ($owner)"
1636 die "can't resize volume: $disk if snapshot exists\n"
1637 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1639 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1641 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1643 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1645 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1646 $newsize += $size if $ext;
1647 $newsize = int($newsize);
1649 die "unable to shrink disk size\n" if $newsize < $size;
1651 return if $size == $newsize;
1653 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1655 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1656 # we pass 0 here (parameter only makes sense for qemu)
1657 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1659 $mp->{size
} = $newsize;
1660 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1662 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1664 if ($format eq 'raw') {
1665 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1669 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1670 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1671 die "internal error: CT running but mount point not attached to a loop device"
1673 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1675 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1676 # to be visible to it in its namespace.
1677 # To not interfere with the rest of the system we unshare the current mount namespace,
1678 # mount over /tmp and then run resize2fs.
1680 # interestingly we don't need to e2fsck on mounted systems...
1681 my $quoted = PVE
::Tools
::shellquote
($path);
1682 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1684 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1686 warn "Failed to update the container's filesystem: $@\n" if $@;
1689 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1690 PVE
::Tools
::run_command
(['resize2fs', $path]);
1692 warn "Failed to update the container's filesystem: $@\n" if $@;
1697 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1700 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1703 __PACKAGE__-
>register_method({
1704 name
=> 'move_volume',
1705 path
=> '{vmid}/move_volume',
1709 description
=> "Move a rootfs-/mp-volume to a different storage",
1711 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1712 "and 'Datastore.AllocateSpace' permissions on the storage.",
1715 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1716 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1720 additionalProperties
=> 0,
1722 node
=> get_standard_option
('pve-node'),
1723 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1726 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1727 description
=> "Volume which will be moved.",
1729 storage
=> get_standard_option
('pve-storage-id', {
1730 description
=> "Target Storage.",
1731 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1735 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1741 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1753 my $rpcenv = PVE
::RPCEnvironment
::get
();
1755 my $authuser = $rpcenv->get_user();
1757 my $vmid = extract_param
($param, 'vmid');
1759 my $storage = extract_param
($param, 'storage');
1761 my $mpkey = extract_param
($param, 'volume');
1763 my $lockname = 'disk';
1765 my ($mpdata, $old_volid);
1767 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1768 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1769 PVE
::LXC
::Config-
>check_lock($conf);
1771 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1773 if ($mpkey eq 'rootfs') {
1774 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1775 } elsif ($mpkey =~ m/mp\d+/) {
1776 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1778 die "Can't parse $mpkey\n";
1780 $old_volid = $mpdata->{volume
};
1782 die "you can't move a volume with snapshots and delete the source\n"
1783 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1785 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1787 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1792 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1794 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1795 my $storage_cfg = PVE
::Storage
::config
();
1800 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1801 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1802 $mpdata->{volume
} = $new_volid;
1804 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1805 my $digest = $conf->{digest
};
1806 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1807 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1809 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1811 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1813 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1817 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1818 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1824 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1825 if defined($new_volid);
1831 if ($param->{delete}) {
1833 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1834 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1840 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1845 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1848 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };