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
();
361 my ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
362 if ($authuser eq 'root@pam') {
363 @{$conf->{lxc
}} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}]; # do not remove lxc.idmap entries
365 if ($storage_only_mode) {
367 $mp_param = $orig_mp_param;
368 die "rootfs configuration could not be recovered, please check and specify manually!\n"
369 if !defined($mp_param->{rootfs
});
370 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
371 my ($ms, $mountpoint) = @_;
372 my $type = $mountpoint->{type
};
373 if ($type eq 'volume') {
374 die "unable to detect disk size - please specify $ms (size)\n"
375 if !defined($mountpoint->{size
});
376 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
377 delete $mountpoint->{size
};
378 $mountpoint->{volume
} = "$storage:$disksize";
379 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
381 my $type = $mountpoint->{type
};
382 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
383 if ($ms eq 'rootfs');
384 die "restoring '$ms' to $type mount is only possible for root\n"
385 if $authuser ne 'root@pam';
387 if ($mountpoint->{backup
}) {
388 warn "WARNING - unsupported configuration!\n";
389 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
390 warn "mount point configuration will be restored after archive extraction!\n";
391 warn "contained files will be restored to wrong directory!\n";
393 delete $mp_param->{$ms}; # actually delay bind/dev mps
394 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
398 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
402 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
404 if (defined($old_conf)) {
405 # destroy old container volumes
406 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
410 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
411 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
412 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
415 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
417 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
418 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
419 $lxc_setup->post_create_hook($password, $ssh_keys);
423 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
424 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
427 $conf->{hostname
} ||= "CT$vmid";
428 $conf->{memory
} ||= 512;
429 $conf->{swap
} //= 512;
430 foreach my $mp (keys %$delayed_mp_param) {
431 $conf->{$mp} = $delayed_mp_param->{$mp};
433 PVE
::LXC
::Config-
>write_config($vmid, $conf);
436 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
437 PVE
::LXC
::destroy_config
($vmid);
440 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
442 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
443 if $start_after_create;
446 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
448 &$check_vmid_usage(); # first check before locking
450 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
451 $vmid, $authuser, $realcmd);
455 __PACKAGE__-
>register_method({
460 description
=> "Directory index",
465 additionalProperties
=> 0,
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid'),
476 subdir
=> { type
=> 'string' },
479 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
485 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
488 { subdir
=> 'config' },
489 { subdir
=> 'status' },
490 { subdir
=> 'vncproxy' },
491 { subdir
=> 'termproxy' },
492 { subdir
=> 'vncwebsocket' },
493 { subdir
=> 'spiceproxy' },
494 { subdir
=> 'migrate' },
495 { subdir
=> 'clone' },
496 # { subdir => 'initlog' },
498 { subdir
=> 'rrddata' },
499 { subdir
=> 'firewall' },
500 { subdir
=> 'snapshot' },
501 { subdir
=> 'resize' },
508 __PACKAGE__-
>register_method({
510 path
=> '{vmid}/rrd',
512 protected
=> 1, # fixme: can we avoid that?
514 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
516 description
=> "Read VM RRD statistics (returns PNG)",
518 additionalProperties
=> 0,
520 node
=> get_standard_option
('pve-node'),
521 vmid
=> get_standard_option
('pve-vmid'),
523 description
=> "Specify the time frame you are interested in.",
525 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
528 description
=> "The list of datasources you want to display.",
529 type
=> 'string', format
=> 'pve-configid-list',
532 description
=> "The RRD consolidation function",
534 enum
=> [ 'AVERAGE', 'MAX' ],
542 filename
=> { type
=> 'string' },
548 return PVE
::Cluster
::create_rrd_graph
(
549 "pve2-vm/$param->{vmid}", $param->{timeframe
},
550 $param->{ds
}, $param->{cf
});
554 __PACKAGE__-
>register_method({
556 path
=> '{vmid}/rrddata',
558 protected
=> 1, # fixme: can we avoid that?
560 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
562 description
=> "Read VM RRD statistics",
564 additionalProperties
=> 0,
566 node
=> get_standard_option
('pve-node'),
567 vmid
=> get_standard_option
('pve-vmid'),
569 description
=> "Specify the time frame you are interested in.",
571 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
574 description
=> "The RRD consolidation function",
576 enum
=> [ 'AVERAGE', 'MAX' ],
591 return PVE
::Cluster
::create_rrd_data
(
592 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
595 __PACKAGE__-
>register_method({
596 name
=> 'destroy_vm',
601 description
=> "Destroy the container (also delete all uses files).",
603 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
606 additionalProperties
=> 0,
608 node
=> get_standard_option
('pve-node'),
609 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
618 my $rpcenv = PVE
::RPCEnvironment
::get
();
620 my $authuser = $rpcenv->get_user();
622 my $vmid = $param->{vmid
};
624 # test if container exists
625 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
627 my $storage_cfg = cfs_read_file
("storage.cfg");
629 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
631 die "unable to remove CT $vmid - used in HA resources\n"
632 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
634 # do not allow destroy if there are replication jobs
635 my $repl_conf = PVE
::ReplicationConfig-
>new();
636 $repl_conf->check_for_existing_jobs($vmid);
638 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
640 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
643 # reload config after lock
644 $conf = PVE
::LXC
::Config-
>load_config($vmid);
645 PVE
::LXC
::Config-
>check_lock($conf);
647 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
649 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
650 PVE
::AccessControl
::remove_vm_access
($vmid);
651 PVE
::Firewall
::remove_vmfw_conf
($vmid);
654 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
656 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
661 __PACKAGE__-
>register_method ({
663 path
=> '{vmid}/vncproxy',
667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
669 description
=> "Creates a TCP VNC proxy connections.",
671 additionalProperties
=> 0,
673 node
=> get_standard_option
('pve-node'),
674 vmid
=> get_standard_option
('pve-vmid'),
678 description
=> "use websocket instead of standard VNC.",
682 description
=> "sets the width of the console in pixels.",
689 description
=> "sets the height of the console in pixels.",
697 additionalProperties
=> 0,
699 user
=> { type
=> 'string' },
700 ticket
=> { type
=> 'string' },
701 cert
=> { type
=> 'string' },
702 port
=> { type
=> 'integer' },
703 upid
=> { type
=> 'string' },
709 my $rpcenv = PVE
::RPCEnvironment
::get
();
711 my $authuser = $rpcenv->get_user();
713 my $vmid = $param->{vmid
};
714 my $node = $param->{node
};
716 my $authpath = "/vms/$vmid";
718 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
720 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
723 my ($remip, $family);
725 if ($node ne PVE
::INotify
::nodename
()) {
726 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
728 $family = PVE
::Tools
::get_host_address_family
($node);
731 my $port = PVE
::Tools
::next_vnc_port
($family);
733 # NOTE: vncterm VNC traffic is already TLS encrypted,
734 # so we select the fastest chipher here (or 'none'?)
735 my $remcmd = $remip ?
736 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
738 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
739 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
741 my $shcmd = [ '/usr/bin/dtach', '-A',
742 "/var/run/dtach/vzctlconsole$vmid",
743 '-r', 'winch', '-z', @$concmd];
748 syslog
('info', "starting lxc vnc proxy $upid\n");
752 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
753 '-timeout', $timeout, '-authpath', $authpath,
754 '-perm', 'VM.Console'];
756 if ($param->{width
}) {
757 push @$cmd, '-width', $param->{width
};
760 if ($param->{height
}) {
761 push @$cmd, '-height', $param->{height
};
764 if ($param->{websocket
}) {
765 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
766 push @$cmd, '-notls', '-listen', 'localhost';
769 push @$cmd, '-c', @$remcmd, @$shcmd;
771 run_command
($cmd, keeplocale
=> 1);
776 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
778 PVE
::Tools
::wait_for_vnc_port
($port);
789 __PACKAGE__-
>register_method ({
791 path
=> '{vmid}/termproxy',
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
797 description
=> "Creates a TCP proxy connection.",
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid'),
806 additionalProperties
=> 0,
808 user
=> { type
=> 'string' },
809 ticket
=> { type
=> 'string' },
810 port
=> { type
=> 'integer' },
811 upid
=> { type
=> 'string' },
817 my $rpcenv = PVE
::RPCEnvironment
::get
();
819 my $authuser = $rpcenv->get_user();
821 my $vmid = $param->{vmid
};
822 my $node = $param->{node
};
824 my $authpath = "/vms/$vmid";
826 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
828 my ($remip, $family);
830 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
831 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
833 $family = PVE
::Tools
::get_host_address_family
($node);
836 my $port = PVE
::Tools
::next_vnc_port
($family);
838 my $remcmd = $remip ?
839 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
841 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
842 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
844 my $shcmd = [ '/usr/bin/dtach', '-A',
845 "/var/run/dtach/vzctlconsole$vmid",
846 '-r', 'winch', '-z', @$concmd];
851 syslog
('info', "starting lxc termproxy $upid\n");
853 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
854 '--perm', 'VM.Console', '--'];
855 push @$cmd, @$remcmd, @$shcmd;
857 PVE
::Tools
::run_command
($cmd);
860 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
862 PVE
::Tools
::wait_for_vnc_port
($port);
872 __PACKAGE__-
>register_method({
873 name
=> 'vncwebsocket',
874 path
=> '{vmid}/vncwebsocket',
877 description
=> "You also need to pass a valid ticket (vncticket).",
878 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
880 description
=> "Opens a weksocket for VNC traffic.",
882 additionalProperties
=> 0,
884 node
=> get_standard_option
('pve-node'),
885 vmid
=> get_standard_option
('pve-vmid'),
887 description
=> "Ticket from previous call to vncproxy.",
892 description
=> "Port number returned by previous vncproxy call.",
902 port
=> { type
=> 'string' },
908 my $rpcenv = PVE
::RPCEnvironment
::get
();
910 my $authuser = $rpcenv->get_user();
912 my $authpath = "/vms/$param->{vmid}";
914 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
916 my $port = $param->{port
};
918 return { port
=> $port };
921 __PACKAGE__-
>register_method ({
922 name
=> 'spiceproxy',
923 path
=> '{vmid}/spiceproxy',
928 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
930 description
=> "Returns a SPICE configuration to connect to the CT.",
932 additionalProperties
=> 0,
934 node
=> get_standard_option
('pve-node'),
935 vmid
=> get_standard_option
('pve-vmid'),
936 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
939 returns
=> get_standard_option
('remote-viewer-config'),
943 my $vmid = $param->{vmid
};
944 my $node = $param->{node
};
945 my $proxy = $param->{proxy
};
947 my $authpath = "/vms/$vmid";
948 my $permissions = 'VM.Console';
950 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
952 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
954 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
956 my $shcmd = ['/usr/bin/dtach', '-A',
957 "/var/run/dtach/vzctlconsole$vmid",
958 '-r', 'winch', '-z', @$concmd];
960 my $title = "CT $vmid";
962 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
966 __PACKAGE__-
>register_method({
967 name
=> 'migrate_vm',
968 path
=> '{vmid}/migrate',
972 description
=> "Migrate the container to another node. Creates a new migration task.",
974 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
977 additionalProperties
=> 0,
979 node
=> get_standard_option
('pve-node'),
980 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
981 target
=> get_standard_option
('pve-node', {
982 description
=> "Target node.",
983 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
987 description
=> "Use online/live migration.",
992 description
=> "Use restart migration",
997 description
=> "Timeout in seconds for shutdown for restart migration",
1003 description
=> "Force migration despite local bind / device" .
1004 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1011 description
=> "the task ID.",
1016 my $rpcenv = PVE
::RPCEnvironment
::get
();
1018 my $authuser = $rpcenv->get_user();
1020 my $target = extract_param
($param, 'target');
1022 my $localnode = PVE
::INotify
::nodename
();
1023 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1025 PVE
::Cluster
::check_cfs_quorum
();
1027 PVE
::Cluster
::check_node_exists
($target);
1029 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1031 my $vmid = extract_param
($param, 'vmid');
1034 PVE
::LXC
::Config-
>load_config($vmid);
1036 # try to detect errors early
1037 if (PVE
::LXC
::check_running
($vmid)) {
1038 die "can't migrate running container without --online or --restart\n"
1039 if !$param->{online
} && !$param->{restart
};
1042 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1047 my $service = "ct:$vmid";
1049 my $cmd = ['ha-manager', 'migrate', $service, $target];
1051 print "Requesting HA migration for CT $vmid to node $target\n";
1053 PVE
::Tools
::run_command
($cmd);
1058 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1063 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1067 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1070 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1074 __PACKAGE__-
>register_method({
1075 name
=> 'vm_feature',
1076 path
=> '{vmid}/feature',
1080 description
=> "Check if feature for virtual machine is available.",
1082 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1085 additionalProperties
=> 0,
1087 node
=> get_standard_option
('pve-node'),
1088 vmid
=> get_standard_option
('pve-vmid'),
1090 description
=> "Feature to check.",
1092 enum
=> [ 'snapshot', 'clone', 'copy' ],
1094 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1102 hasFeature
=> { type
=> 'boolean' },
1105 #items => { type => 'string' },
1112 my $node = extract_param
($param, 'node');
1114 my $vmid = extract_param
($param, 'vmid');
1116 my $snapname = extract_param
($param, 'snapname');
1118 my $feature = extract_param
($param, 'feature');
1120 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1123 my $snap = $conf->{snapshots
}->{$snapname};
1124 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1127 my $storage_cfg = PVE
::Storage
::config
();
1128 #Maybe include later
1129 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1130 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1133 hasFeature
=> $hasFeature,
1134 #nodes => [ keys %$nodelist ],
1138 __PACKAGE__-
>register_method({
1140 path
=> '{vmid}/template',
1144 description
=> "Create a Template.",
1146 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1147 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1150 additionalProperties
=> 0,
1152 node
=> get_standard_option
('pve-node'),
1153 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1156 returns
=> { type
=> 'null'},
1160 my $rpcenv = PVE
::RPCEnvironment
::get
();
1162 my $authuser = $rpcenv->get_user();
1164 my $node = extract_param
($param, 'node');
1166 my $vmid = extract_param
($param, 'vmid');
1168 my $updatefn = sub {
1170 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1171 PVE
::LXC
::Config-
>check_lock($conf);
1173 die "unable to create template, because CT contains snapshots\n"
1174 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1176 die "you can't convert a template to a template\n"
1177 if PVE
::LXC
::Config-
>is_template($conf);
1179 die "you can't convert a CT to template if the CT is running\n"
1180 if PVE
::LXC
::check_running
($vmid);
1182 my $scfg = PVE
::Storage
::config
();
1183 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1186 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1187 die "Directory storage '$sid' does not support container templates!\n"
1188 if $scfg->{ids
}->{$sid}->{path
};
1192 PVE
::LXC
::template_create
($vmid, $conf);
1194 $conf->{template
} = 1;
1196 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1197 # and remove lxc config
1198 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1201 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1204 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1209 __PACKAGE__-
>register_method({
1211 path
=> '{vmid}/clone',
1215 description
=> "Create a container clone/copy",
1217 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1218 "and 'VM.Allocate' permissions " .
1219 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1220 "'Datastore.AllocateSpace' on any used storage.",
1223 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1225 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1226 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1231 additionalProperties
=> 0,
1233 node
=> get_standard_option
('pve-node'),
1234 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1235 newid
=> get_standard_option
('pve-vmid', {
1236 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1237 description
=> 'VMID for the clone.' }),
1240 type
=> 'string', format
=> 'dns-name',
1241 description
=> "Set a hostname for the new CT.",
1246 description
=> "Description for the new CT.",
1250 type
=> 'string', format
=> 'pve-poolid',
1251 description
=> "Add the new CT to the specified pool.",
1253 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1256 storage
=> get_standard_option
('pve-storage-id', {
1257 description
=> "Target storage for full clone.",
1263 description
=> "Create a full copy of all disks. This is always done when " .
1264 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1266 target
=> get_standard_option
('pve-node', {
1267 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1278 my $rpcenv = PVE
::RPCEnvironment
::get
();
1280 my $authuser = $rpcenv->get_user();
1282 my $node = extract_param
($param, 'node');
1284 my $vmid = extract_param
($param, 'vmid');
1286 my $newid = extract_param
($param, 'newid');
1288 my $pool = extract_param
($param, 'pool');
1290 if (defined($pool)) {
1291 $rpcenv->check_pool_exist($pool);
1294 my $snapname = extract_param
($param, 'snapname');
1296 my $storage = extract_param
($param, 'storage');
1298 my $target = extract_param
($param, 'target');
1300 my $localnode = PVE
::INotify
::nodename
();
1302 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1304 PVE
::Cluster
::check_node_exists
($target) if $target;
1306 my $storecfg = PVE
::Storage
::config
();
1309 # check if storage is enabled on local node
1310 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1312 # check if storage is available on target node
1313 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1314 # clone only works if target storage is shared
1315 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1316 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1320 PVE
::Cluster
::check_cfs_quorum
();
1324 my $mountpoints = {};
1329 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1330 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1332 $running = PVE
::LXC
::check_running
($vmid) || 0;
1334 my $full = extract_param
($param, 'full');
1335 if (!defined($full)) {
1336 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1338 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1341 die "snapshot '$snapname' does not exist\n"
1342 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1345 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1347 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1348 die "unable to create CT $newid: config file already exists\n"
1352 foreach my $opt (keys %$src_conf) {
1353 next if $opt =~ m/^unused\d+$/;
1355 my $value = $src_conf->{$opt};
1357 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1358 my $mp = $opt eq 'rootfs' ?
1359 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1360 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1362 if ($mp->{type
} eq 'volume') {
1363 my $volid = $mp->{volume
};
1365 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1366 $sid = $storage if defined($storage);
1367 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1368 if (!$scfg->{shared
}) {
1370 warn "found non-shared volume: $volid\n" if $target;
1373 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1376 die "Cannot do full clones on a running container without snapshots\n"
1377 if $running && !defined($snapname);
1378 $fullclone->{$opt} = 1;
1380 # not full means clone instead of copy
1381 die "Linked clone feature for '$volid' is not available\n"
1382 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1385 $mountpoints->{$opt} = $mp;
1386 push @$vollist, $volid;
1389 # TODO: allow bind mounts?
1390 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1392 } elsif ($opt =~ m/^net(\d+)$/) {
1393 # always change MAC! address
1394 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1395 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1396 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1397 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1399 # copy everything else
1400 $newconf->{$opt} = $value;
1403 die "can't clone CT to node '$target' (CT uses local storage)\n"
1404 if $target && !$sharedvm;
1406 # Replace the 'disk' lock with a 'create' lock.
1407 $newconf->{lock} = 'create';
1409 delete $newconf->{template
};
1410 if ($param->{hostname
}) {
1411 $newconf->{hostname
} = $param->{hostname
};
1414 if ($param->{description
}) {
1415 $newconf->{description
} = $param->{description
};
1418 # create empty/temp config - this fails if CT already exists on other node
1419 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1422 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1428 my $update_conf = sub {
1429 my ($key, $value) = @_;
1430 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1431 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1432 die "Lost 'create' config lock, aborting.\n"
1433 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1434 $conf->{$key} = $value;
1435 PVE
::LXC
::Config-
>write_config($newid, $conf);
1442 my $newvollist = [];
1444 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1445 die "unexpected state change\n" if $verify_running != $running;
1451 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1453 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1455 foreach my $opt (keys %$mountpoints) {
1456 my $mp = $mountpoints->{$opt};
1457 my $volid = $mp->{volume
};
1460 if ($fullclone->{$opt}) {
1461 print "create full clone of mountpoint $opt ($volid)\n";
1462 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1463 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1465 print "create linked clone of mount point $opt ($volid)\n";
1466 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1469 push @$newvollist, $newvolid;
1470 $mp->{volume
} = $newvolid;
1472 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1475 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1476 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1479 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1480 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1481 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1483 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1484 die "Failed to move config to node '$target' - rename failed: $!\n"
1485 if !rename($conffile, $newconffile);
1490 # Unlock the source config in any case:
1491 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1495 # Now cleanup the config & disks:
1498 sleep 1; # some storages like rbd need to wait before release volume - really?
1500 foreach my $volid (@$newvollist) {
1501 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1504 die "clone failed: $err";
1510 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1511 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1515 __PACKAGE__-
>register_method({
1516 name
=> 'resize_vm',
1517 path
=> '{vmid}/resize',
1521 description
=> "Resize a container mount point.",
1523 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1526 additionalProperties
=> 0,
1528 node
=> get_standard_option
('pve-node'),
1529 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1532 description
=> "The disk you want to resize.",
1533 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1537 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1538 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.",
1542 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1550 description
=> "the task ID.",
1555 my $rpcenv = PVE
::RPCEnvironment
::get
();
1557 my $authuser = $rpcenv->get_user();
1559 my $node = extract_param
($param, 'node');
1561 my $vmid = extract_param
($param, 'vmid');
1563 my $digest = extract_param
($param, 'digest');
1565 my $sizestr = extract_param
($param, 'size');
1566 my $ext = ($sizestr =~ s/^\+//);
1567 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1568 die "invalid size string" if !defined($newsize);
1570 die "no options specified\n" if !scalar(keys %$param);
1572 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1574 my $storage_cfg = cfs_read_file
("storage.cfg");
1578 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1579 PVE
::LXC
::Config-
>check_lock($conf);
1581 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1583 my $running = PVE
::LXC
::check_running
($vmid);
1585 my $disk = $param->{disk
};
1586 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1587 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1589 my $volid = $mp->{volume
};
1591 my (undef, undef, $owner, undef, undef, undef, $format) =
1592 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1594 die "can't resize mount point owned by another container ($owner)"
1597 die "can't resize volume: $disk if snapshot exists\n"
1598 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1600 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1602 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1604 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1606 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1607 $newsize += $size if $ext;
1608 $newsize = int($newsize);
1610 die "unable to shrink disk size\n" if $newsize < $size;
1612 return if $size == $newsize;
1614 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1616 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1617 # we pass 0 here (parameter only makes sense for qemu)
1618 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1620 $mp->{size
} = $newsize;
1621 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1623 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1625 if ($format eq 'raw') {
1626 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1630 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1631 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1632 die "internal error: CT running but mount point not attached to a loop device"
1634 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1636 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1637 # to be visible to it in its namespace.
1638 # To not interfere with the rest of the system we unshare the current mount namespace,
1639 # mount over /tmp and then run resize2fs.
1641 # interestingly we don't need to e2fsck on mounted systems...
1642 my $quoted = PVE
::Tools
::shellquote
($path);
1643 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1645 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1647 warn "Failed to update the container's filesystem: $@\n" if $@;
1650 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1651 PVE
::Tools
::run_command
(['resize2fs', $path]);
1653 warn "Failed to update the container's filesystem: $@\n" if $@;
1658 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1661 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1664 __PACKAGE__-
>register_method({
1665 name
=> 'move_volume',
1666 path
=> '{vmid}/move_volume',
1670 description
=> "Move a rootfs-/mp-volume to a different storage",
1672 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1673 "and 'Datastore.AllocateSpace' permissions on the storage.",
1676 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1677 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1681 additionalProperties
=> 0,
1683 node
=> get_standard_option
('pve-node'),
1684 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1687 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1688 description
=> "Volume which will be moved.",
1690 storage
=> get_standard_option
('pve-storage-id', {
1691 description
=> "Target Storage.",
1692 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1696 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1702 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1714 my $rpcenv = PVE
::RPCEnvironment
::get
();
1716 my $authuser = $rpcenv->get_user();
1718 my $vmid = extract_param
($param, 'vmid');
1720 my $storage = extract_param
($param, 'storage');
1722 my $mpkey = extract_param
($param, 'volume');
1724 my $lockname = 'disk';
1726 my ($mpdata, $old_volid);
1728 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1729 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1730 PVE
::LXC
::Config-
>check_lock($conf);
1732 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1734 if ($mpkey eq 'rootfs') {
1735 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1736 } elsif ($mpkey =~ m/mp\d+/) {
1737 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1739 die "Can't parse $mpkey\n";
1741 $old_volid = $mpdata->{volume
};
1743 die "you can't move a volume with snapshots and delete the source\n"
1744 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1746 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1748 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1753 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1755 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1756 my $storage_cfg = PVE
::Storage
::config
();
1761 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1762 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1763 $mpdata->{volume
} = $new_volid;
1765 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1766 my $digest = $conf->{digest
};
1767 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1768 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1770 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1772 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1774 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1778 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1779 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1785 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1786 if defined($new_volid);
1792 if ($param->{delete}) {
1794 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1795 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1801 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1806 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1809 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };