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 $is_root = $authuser eq 'root@pam';
285 my $no_disk_param = {};
287 my $storage_only_mode = 1;
288 foreach my $opt (keys %$param) {
289 my $value = $param->{$opt};
290 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
291 # allow to use simple numbers (add default storage in that case)
292 if ($value =~ m/^\d+(\.\d+)?$/) {
293 $mp_param->{$opt} = "$storage:$value";
295 $mp_param->{$opt} = $value;
297 $storage_only_mode = 0;
298 } elsif ($opt =~ m/^unused\d+$/) {
299 warn "ignoring '$opt', cannot create/restore with unused volume\n";
300 delete $param->{$opt};
302 $no_disk_param->{$opt} = $value;
306 die "mount points configured, but 'rootfs' not set - aborting\n"
307 if !$storage_only_mode && !defined($mp_param->{rootfs
});
309 # check storage access, activate storage
310 my $delayed_mp_param = {};
311 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
312 my ($ms, $mountpoint) = @_;
314 my $volid = $mountpoint->{volume
};
315 my $mp = $mountpoint->{mp
};
317 if ($mountpoint->{type
} ne 'volume') { # bind or device
318 die "Only root can pass arbitrary filesystem paths.\n"
321 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
322 &$check_and_activate_storage($sid);
326 # check/activate default storage
327 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
329 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
331 $conf->{unprivileged
} = 1 if $unprivileged;
333 my $check_vmid_usage = sub {
335 die "can't overwrite running container\n"
336 if PVE
::LXC
::check_running
($vmid);
338 PVE
::Cluster
::check_vmid_unused
($vmid);
343 &$check_vmid_usage(); # final check after locking
346 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
348 die "container exists" if !$restore; # just to be sure
349 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
352 # try to create empty config on local node, we have an flock
353 PVE
::LXC
::Config-
>write_config($vmid, {});
356 # another node was faster, abort
357 die "Could not reserve ID $vmid, already taken\n" if $@;
360 PVE
::Cluster
::check_cfs_quorum
();
363 my ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
365 $conf->{lxc
} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}]; # do not remove lxc.idmap entries
367 if ($storage_only_mode) {
369 $mp_param = $orig_mp_param;
370 die "rootfs configuration could not be recovered, please check and specify manually!\n"
371 if !defined($mp_param->{rootfs
});
372 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
373 my ($ms, $mountpoint) = @_;
374 my $type = $mountpoint->{type
};
375 if ($type eq 'volume') {
376 die "unable to detect disk size - please specify $ms (size)\n"
377 if !defined($mountpoint->{size
});
378 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
379 delete $mountpoint->{size
};
380 $mountpoint->{volume
} = "$storage:$disksize";
381 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
383 my $type = $mountpoint->{type
};
384 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
385 if ($ms eq 'rootfs');
386 die "restoring '$ms' to $type mount is only possible for root\n"
389 if ($mountpoint->{backup
}) {
390 warn "WARNING - unsupported configuration!\n";
391 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
392 warn "mount point configuration will be restored after archive extraction!\n";
393 warn "contained files will be restored to wrong directory!\n";
395 delete $mp_param->{$ms}; # actually delay bind/dev mps
396 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
400 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
404 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
406 if (defined($old_conf)) {
407 # destroy old container volumes
408 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
412 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
413 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
414 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
417 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root);
419 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
420 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
421 $lxc_setup->post_create_hook($password, $ssh_keys);
425 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
426 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
429 $conf->{hostname
} ||= "CT$vmid";
430 $conf->{memory
} ||= 512;
431 $conf->{swap
} //= 512;
432 foreach my $mp (keys %$delayed_mp_param) {
433 $conf->{$mp} = $delayed_mp_param->{$mp};
435 PVE
::LXC
::Config-
>write_config($vmid, $conf);
438 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
439 PVE
::LXC
::destroy_config
($vmid);
442 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
444 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
445 if $start_after_create;
448 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
450 &$check_vmid_usage(); # first check before locking
452 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
453 $vmid, $authuser, $realcmd);
457 __PACKAGE__-
>register_method({
462 description
=> "Directory index",
467 additionalProperties
=> 0,
469 node
=> get_standard_option
('pve-node'),
470 vmid
=> get_standard_option
('pve-vmid'),
478 subdir
=> { type
=> 'string' },
481 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
487 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
490 { subdir
=> 'config' },
491 { subdir
=> 'status' },
492 { subdir
=> 'vncproxy' },
493 { subdir
=> 'termproxy' },
494 { subdir
=> 'vncwebsocket' },
495 { subdir
=> 'spiceproxy' },
496 { subdir
=> 'migrate' },
497 { subdir
=> 'clone' },
498 # { subdir => 'initlog' },
500 { subdir
=> 'rrddata' },
501 { subdir
=> 'firewall' },
502 { subdir
=> 'snapshot' },
503 { subdir
=> 'resize' },
510 __PACKAGE__-
>register_method({
512 path
=> '{vmid}/rrd',
514 protected
=> 1, # fixme: can we avoid that?
516 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
518 description
=> "Read VM RRD statistics (returns PNG)",
520 additionalProperties
=> 0,
522 node
=> get_standard_option
('pve-node'),
523 vmid
=> get_standard_option
('pve-vmid'),
525 description
=> "Specify the time frame you are interested in.",
527 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
530 description
=> "The list of datasources you want to display.",
531 type
=> 'string', format
=> 'pve-configid-list',
534 description
=> "The RRD consolidation function",
536 enum
=> [ 'AVERAGE', 'MAX' ],
544 filename
=> { type
=> 'string' },
550 return PVE
::Cluster
::create_rrd_graph
(
551 "pve2-vm/$param->{vmid}", $param->{timeframe
},
552 $param->{ds
}, $param->{cf
});
556 __PACKAGE__-
>register_method({
558 path
=> '{vmid}/rrddata',
560 protected
=> 1, # fixme: can we avoid that?
562 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
564 description
=> "Read VM RRD statistics",
566 additionalProperties
=> 0,
568 node
=> get_standard_option
('pve-node'),
569 vmid
=> get_standard_option
('pve-vmid'),
571 description
=> "Specify the time frame you are interested in.",
573 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
576 description
=> "The RRD consolidation function",
578 enum
=> [ 'AVERAGE', 'MAX' ],
593 return PVE
::Cluster
::create_rrd_data
(
594 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
597 __PACKAGE__-
>register_method({
598 name
=> 'destroy_vm',
603 description
=> "Destroy the container (also delete all uses files).",
605 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
611 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
620 my $rpcenv = PVE
::RPCEnvironment
::get
();
622 my $authuser = $rpcenv->get_user();
624 my $vmid = $param->{vmid
};
626 # test if container exists
627 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
629 my $storage_cfg = cfs_read_file
("storage.cfg");
631 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
633 die "unable to remove CT $vmid - used in HA resources\n"
634 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
636 # do not allow destroy if there are replication jobs
637 my $repl_conf = PVE
::ReplicationConfig-
>new();
638 $repl_conf->check_for_existing_jobs($vmid);
640 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
642 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
645 # reload config after lock
646 $conf = PVE
::LXC
::Config-
>load_config($vmid);
647 PVE
::LXC
::Config-
>check_lock($conf);
649 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
651 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
652 PVE
::AccessControl
::remove_vm_access
($vmid);
653 PVE
::Firewall
::remove_vmfw_conf
($vmid);
656 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
658 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
663 __PACKAGE__-
>register_method ({
665 path
=> '{vmid}/vncproxy',
669 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
671 description
=> "Creates a TCP VNC proxy connections.",
673 additionalProperties
=> 0,
675 node
=> get_standard_option
('pve-node'),
676 vmid
=> get_standard_option
('pve-vmid'),
680 description
=> "use websocket instead of standard VNC.",
684 description
=> "sets the width of the console in pixels.",
691 description
=> "sets the height of the console in pixels.",
699 additionalProperties
=> 0,
701 user
=> { type
=> 'string' },
702 ticket
=> { type
=> 'string' },
703 cert
=> { type
=> 'string' },
704 port
=> { type
=> 'integer' },
705 upid
=> { type
=> 'string' },
711 my $rpcenv = PVE
::RPCEnvironment
::get
();
713 my $authuser = $rpcenv->get_user();
715 my $vmid = $param->{vmid
};
716 my $node = $param->{node
};
718 my $authpath = "/vms/$vmid";
720 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
722 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
725 my ($remip, $family);
727 if ($node ne PVE
::INotify
::nodename
()) {
728 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
730 $family = PVE
::Tools
::get_host_address_family
($node);
733 my $port = PVE
::Tools
::next_vnc_port
($family);
735 # NOTE: vncterm VNC traffic is already TLS encrypted,
736 # so we select the fastest chipher here (or 'none'?)
737 my $remcmd = $remip ?
738 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
740 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
741 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
743 my $shcmd = [ '/usr/bin/dtach', '-A',
744 "/var/run/dtach/vzctlconsole$vmid",
745 '-r', 'winch', '-z', @$concmd];
750 syslog
('info', "starting lxc vnc proxy $upid\n");
754 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
755 '-timeout', $timeout, '-authpath', $authpath,
756 '-perm', 'VM.Console'];
758 if ($param->{width
}) {
759 push @$cmd, '-width', $param->{width
};
762 if ($param->{height
}) {
763 push @$cmd, '-height', $param->{height
};
766 if ($param->{websocket
}) {
767 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
768 push @$cmd, '-notls', '-listen', 'localhost';
771 push @$cmd, '-c', @$remcmd, @$shcmd;
773 run_command
($cmd, keeplocale
=> 1);
778 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
780 PVE
::Tools
::wait_for_vnc_port
($port);
791 __PACKAGE__-
>register_method ({
793 path
=> '{vmid}/termproxy',
797 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
799 description
=> "Creates a TCP proxy connection.",
801 additionalProperties
=> 0,
803 node
=> get_standard_option
('pve-node'),
804 vmid
=> get_standard_option
('pve-vmid'),
808 additionalProperties
=> 0,
810 user
=> { type
=> 'string' },
811 ticket
=> { type
=> 'string' },
812 port
=> { type
=> 'integer' },
813 upid
=> { type
=> 'string' },
819 my $rpcenv = PVE
::RPCEnvironment
::get
();
821 my $authuser = $rpcenv->get_user();
823 my $vmid = $param->{vmid
};
824 my $node = $param->{node
};
826 my $authpath = "/vms/$vmid";
828 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
830 my ($remip, $family);
832 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
833 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
835 $family = PVE
::Tools
::get_host_address_family
($node);
838 my $port = PVE
::Tools
::next_vnc_port
($family);
840 my $remcmd = $remip ?
841 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
843 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
844 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
846 my $shcmd = [ '/usr/bin/dtach', '-A',
847 "/var/run/dtach/vzctlconsole$vmid",
848 '-r', 'winch', '-z', @$concmd];
853 syslog
('info', "starting lxc termproxy $upid\n");
855 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
856 '--perm', 'VM.Console', '--'];
857 push @$cmd, @$remcmd, @$shcmd;
859 PVE
::Tools
::run_command
($cmd);
862 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
864 PVE
::Tools
::wait_for_vnc_port
($port);
874 __PACKAGE__-
>register_method({
875 name
=> 'vncwebsocket',
876 path
=> '{vmid}/vncwebsocket',
879 description
=> "You also need to pass a valid ticket (vncticket).",
880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
882 description
=> "Opens a weksocket for VNC traffic.",
884 additionalProperties
=> 0,
886 node
=> get_standard_option
('pve-node'),
887 vmid
=> get_standard_option
('pve-vmid'),
889 description
=> "Ticket from previous call to vncproxy.",
894 description
=> "Port number returned by previous vncproxy call.",
904 port
=> { type
=> 'string' },
910 my $rpcenv = PVE
::RPCEnvironment
::get
();
912 my $authuser = $rpcenv->get_user();
914 my $authpath = "/vms/$param->{vmid}";
916 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
918 my $port = $param->{port
};
920 return { port
=> $port };
923 __PACKAGE__-
>register_method ({
924 name
=> 'spiceproxy',
925 path
=> '{vmid}/spiceproxy',
930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
932 description
=> "Returns a SPICE configuration to connect to the CT.",
934 additionalProperties
=> 0,
936 node
=> get_standard_option
('pve-node'),
937 vmid
=> get_standard_option
('pve-vmid'),
938 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
941 returns
=> get_standard_option
('remote-viewer-config'),
945 my $vmid = $param->{vmid
};
946 my $node = $param->{node
};
947 my $proxy = $param->{proxy
};
949 my $authpath = "/vms/$vmid";
950 my $permissions = 'VM.Console';
952 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
954 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
956 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
958 my $shcmd = ['/usr/bin/dtach', '-A',
959 "/var/run/dtach/vzctlconsole$vmid",
960 '-r', 'winch', '-z', @$concmd];
962 my $title = "CT $vmid";
964 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
968 __PACKAGE__-
>register_method({
969 name
=> 'migrate_vm',
970 path
=> '{vmid}/migrate',
974 description
=> "Migrate the container to another node. Creates a new migration task.",
976 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
979 additionalProperties
=> 0,
981 node
=> get_standard_option
('pve-node'),
982 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
983 target
=> get_standard_option
('pve-node', {
984 description
=> "Target node.",
985 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
989 description
=> "Use online/live migration.",
994 description
=> "Use restart migration",
999 description
=> "Timeout in seconds for shutdown for restart migration",
1005 description
=> "Force migration despite local bind / device" .
1006 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1013 description
=> "the task ID.",
1018 my $rpcenv = PVE
::RPCEnvironment
::get
();
1020 my $authuser = $rpcenv->get_user();
1022 my $target = extract_param
($param, 'target');
1024 my $localnode = PVE
::INotify
::nodename
();
1025 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1027 PVE
::Cluster
::check_cfs_quorum
();
1029 PVE
::Cluster
::check_node_exists
($target);
1031 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1033 my $vmid = extract_param
($param, 'vmid');
1036 PVE
::LXC
::Config-
>load_config($vmid);
1038 # try to detect errors early
1039 if (PVE
::LXC
::check_running
($vmid)) {
1040 die "can't migrate running container without --online or --restart\n"
1041 if !$param->{online
} && !$param->{restart
};
1044 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1049 my $service = "ct:$vmid";
1051 my $cmd = ['ha-manager', 'migrate', $service, $target];
1053 print "Requesting HA migration for CT $vmid to node $target\n";
1055 PVE
::Tools
::run_command
($cmd);
1060 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1065 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1069 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1072 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1076 __PACKAGE__-
>register_method({
1077 name
=> 'vm_feature',
1078 path
=> '{vmid}/feature',
1082 description
=> "Check if feature for virtual machine is available.",
1084 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1087 additionalProperties
=> 0,
1089 node
=> get_standard_option
('pve-node'),
1090 vmid
=> get_standard_option
('pve-vmid'),
1092 description
=> "Feature to check.",
1094 enum
=> [ 'snapshot', 'clone', 'copy' ],
1096 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1104 hasFeature
=> { type
=> 'boolean' },
1107 #items => { type => 'string' },
1114 my $node = extract_param
($param, 'node');
1116 my $vmid = extract_param
($param, 'vmid');
1118 my $snapname = extract_param
($param, 'snapname');
1120 my $feature = extract_param
($param, 'feature');
1122 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1125 my $snap = $conf->{snapshots
}->{$snapname};
1126 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1129 my $storage_cfg = PVE
::Storage
::config
();
1130 #Maybe include later
1131 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1132 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1135 hasFeature
=> $hasFeature,
1136 #nodes => [ keys %$nodelist ],
1140 __PACKAGE__-
>register_method({
1142 path
=> '{vmid}/template',
1146 description
=> "Create a Template.",
1148 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1149 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1152 additionalProperties
=> 0,
1154 node
=> get_standard_option
('pve-node'),
1155 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1158 returns
=> { type
=> 'null'},
1162 my $rpcenv = PVE
::RPCEnvironment
::get
();
1164 my $authuser = $rpcenv->get_user();
1166 my $node = extract_param
($param, 'node');
1168 my $vmid = extract_param
($param, 'vmid');
1170 my $updatefn = sub {
1172 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1173 PVE
::LXC
::Config-
>check_lock($conf);
1175 die "unable to create template, because CT contains snapshots\n"
1176 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1178 die "you can't convert a template to a template\n"
1179 if PVE
::LXC
::Config-
>is_template($conf);
1181 die "you can't convert a CT to template if the CT is running\n"
1182 if PVE
::LXC
::check_running
($vmid);
1184 my $scfg = PVE
::Storage
::config
();
1185 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1188 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1189 die "Directory storage '$sid' does not support container templates!\n"
1190 if $scfg->{ids
}->{$sid}->{path
};
1194 PVE
::LXC
::template_create
($vmid, $conf);
1196 $conf->{template
} = 1;
1198 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1199 # and remove lxc config
1200 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1203 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1206 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1211 __PACKAGE__-
>register_method({
1213 path
=> '{vmid}/clone',
1217 description
=> "Create a container clone/copy",
1219 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1220 "and 'VM.Allocate' permissions " .
1221 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1222 "'Datastore.AllocateSpace' on any used storage.",
1225 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1227 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1228 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1233 additionalProperties
=> 0,
1235 node
=> get_standard_option
('pve-node'),
1236 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1237 newid
=> get_standard_option
('pve-vmid', {
1238 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1239 description
=> 'VMID for the clone.' }),
1242 type
=> 'string', format
=> 'dns-name',
1243 description
=> "Set a hostname for the new CT.",
1248 description
=> "Description for the new CT.",
1252 type
=> 'string', format
=> 'pve-poolid',
1253 description
=> "Add the new CT to the specified pool.",
1255 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1258 storage
=> get_standard_option
('pve-storage-id', {
1259 description
=> "Target storage for full clone.",
1265 description
=> "Create a full copy of all disks. This is always done when " .
1266 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1268 target
=> get_standard_option
('pve-node', {
1269 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1280 my $rpcenv = PVE
::RPCEnvironment
::get
();
1282 my $authuser = $rpcenv->get_user();
1284 my $node = extract_param
($param, 'node');
1286 my $vmid = extract_param
($param, 'vmid');
1288 my $newid = extract_param
($param, 'newid');
1290 my $pool = extract_param
($param, 'pool');
1292 if (defined($pool)) {
1293 $rpcenv->check_pool_exist($pool);
1296 my $snapname = extract_param
($param, 'snapname');
1298 my $storage = extract_param
($param, 'storage');
1300 my $target = extract_param
($param, 'target');
1302 my $localnode = PVE
::INotify
::nodename
();
1304 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1306 PVE
::Cluster
::check_node_exists
($target) if $target;
1308 my $storecfg = PVE
::Storage
::config
();
1311 # check if storage is enabled on local node
1312 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1314 # check if storage is available on target node
1315 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1316 # clone only works if target storage is shared
1317 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1318 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1322 PVE
::Cluster
::check_cfs_quorum
();
1326 my $mountpoints = {};
1331 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1332 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1334 $running = PVE
::LXC
::check_running
($vmid) || 0;
1336 my $full = extract_param
($param, 'full');
1337 if (!defined($full)) {
1338 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1340 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1343 die "snapshot '$snapname' does not exist\n"
1344 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1347 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1349 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1350 die "unable to create CT $newid: config file already exists\n"
1354 foreach my $opt (keys %$src_conf) {
1355 next if $opt =~ m/^unused\d+$/;
1357 my $value = $src_conf->{$opt};
1359 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1360 my $mp = $opt eq 'rootfs' ?
1361 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1362 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1364 if ($mp->{type
} eq 'volume') {
1365 my $volid = $mp->{volume
};
1367 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1368 $sid = $storage if defined($storage);
1369 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1370 if (!$scfg->{shared
}) {
1372 warn "found non-shared volume: $volid\n" if $target;
1375 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1378 die "Cannot do full clones on a running container without snapshots\n"
1379 if $running && !defined($snapname);
1380 $fullclone->{$opt} = 1;
1382 # not full means clone instead of copy
1383 die "Linked clone feature for '$volid' is not available\n"
1384 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1387 $mountpoints->{$opt} = $mp;
1388 push @$vollist, $volid;
1391 # TODO: allow bind mounts?
1392 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1394 } elsif ($opt =~ m/^net(\d+)$/) {
1395 # always change MAC! address
1396 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1397 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1398 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1399 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1401 # copy everything else
1402 $newconf->{$opt} = $value;
1405 die "can't clone CT to node '$target' (CT uses local storage)\n"
1406 if $target && !$sharedvm;
1408 # Replace the 'disk' lock with a 'create' lock.
1409 $newconf->{lock} = 'create';
1411 delete $newconf->{template
};
1412 if ($param->{hostname
}) {
1413 $newconf->{hostname
} = $param->{hostname
};
1416 if ($param->{description
}) {
1417 $newconf->{description
} = $param->{description
};
1420 # create empty/temp config - this fails if CT already exists on other node
1421 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1424 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1430 my $update_conf = sub {
1431 my ($key, $value) = @_;
1432 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1433 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1434 die "Lost 'create' config lock, aborting.\n"
1435 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1436 $conf->{$key} = $value;
1437 PVE
::LXC
::Config-
>write_config($newid, $conf);
1444 my $newvollist = [];
1446 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1447 die "unexpected state change\n" if $verify_running != $running;
1453 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1455 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1457 foreach my $opt (keys %$mountpoints) {
1458 my $mp = $mountpoints->{$opt};
1459 my $volid = $mp->{volume
};
1462 if ($fullclone->{$opt}) {
1463 print "create full clone of mountpoint $opt ($volid)\n";
1464 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1465 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1467 print "create linked clone of mount point $opt ($volid)\n";
1468 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1471 push @$newvollist, $newvolid;
1472 $mp->{volume
} = $newvolid;
1474 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1477 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1478 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1481 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1482 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1483 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1485 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1486 die "Failed to move config to node '$target' - rename failed: $!\n"
1487 if !rename($conffile, $newconffile);
1492 # Unlock the source config in any case:
1493 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1497 # Now cleanup the config & disks:
1500 sleep 1; # some storages like rbd need to wait before release volume - really?
1502 foreach my $volid (@$newvollist) {
1503 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1506 die "clone failed: $err";
1512 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1513 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1517 __PACKAGE__-
>register_method({
1518 name
=> 'resize_vm',
1519 path
=> '{vmid}/resize',
1523 description
=> "Resize a container mount point.",
1525 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1528 additionalProperties
=> 0,
1530 node
=> get_standard_option
('pve-node'),
1531 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1534 description
=> "The disk you want to resize.",
1535 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1539 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1540 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.",
1544 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1552 description
=> "the task ID.",
1557 my $rpcenv = PVE
::RPCEnvironment
::get
();
1559 my $authuser = $rpcenv->get_user();
1561 my $node = extract_param
($param, 'node');
1563 my $vmid = extract_param
($param, 'vmid');
1565 my $digest = extract_param
($param, 'digest');
1567 my $sizestr = extract_param
($param, 'size');
1568 my $ext = ($sizestr =~ s/^\+//);
1569 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1570 die "invalid size string" if !defined($newsize);
1572 die "no options specified\n" if !scalar(keys %$param);
1574 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1576 my $storage_cfg = cfs_read_file
("storage.cfg");
1580 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1581 PVE
::LXC
::Config-
>check_lock($conf);
1583 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1585 my $running = PVE
::LXC
::check_running
($vmid);
1587 my $disk = $param->{disk
};
1588 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1589 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1591 my $volid = $mp->{volume
};
1593 my (undef, undef, $owner, undef, undef, undef, $format) =
1594 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1596 die "can't resize mount point owned by another container ($owner)"
1599 die "can't resize volume: $disk if snapshot exists\n"
1600 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1602 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1604 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1606 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1608 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1609 $newsize += $size if $ext;
1610 $newsize = int($newsize);
1612 die "unable to shrink disk size\n" if $newsize < $size;
1614 return if $size == $newsize;
1616 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1618 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1619 # we pass 0 here (parameter only makes sense for qemu)
1620 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1622 $mp->{size
} = $newsize;
1623 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1625 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1627 if ($format eq 'raw') {
1628 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1632 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1633 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1634 die "internal error: CT running but mount point not attached to a loop device"
1636 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1638 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1639 # to be visible to it in its namespace.
1640 # To not interfere with the rest of the system we unshare the current mount namespace,
1641 # mount over /tmp and then run resize2fs.
1643 # interestingly we don't need to e2fsck on mounted systems...
1644 my $quoted = PVE
::Tools
::shellquote
($path);
1645 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1647 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1649 warn "Failed to update the container's filesystem: $@\n" if $@;
1652 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1653 PVE
::Tools
::run_command
(['resize2fs', $path]);
1655 warn "Failed to update the container's filesystem: $@\n" if $@;
1660 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1663 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1666 __PACKAGE__-
>register_method({
1667 name
=> 'move_volume',
1668 path
=> '{vmid}/move_volume',
1672 description
=> "Move a rootfs-/mp-volume to a different storage",
1674 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1675 "and 'Datastore.AllocateSpace' permissions on the storage.",
1678 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1679 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1683 additionalProperties
=> 0,
1685 node
=> get_standard_option
('pve-node'),
1686 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1689 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1690 description
=> "Volume which will be moved.",
1692 storage
=> get_standard_option
('pve-storage-id', {
1693 description
=> "Target Storage.",
1694 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1698 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1704 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1716 my $rpcenv = PVE
::RPCEnvironment
::get
();
1718 my $authuser = $rpcenv->get_user();
1720 my $vmid = extract_param
($param, 'vmid');
1722 my $storage = extract_param
($param, 'storage');
1724 my $mpkey = extract_param
($param, 'volume');
1726 my $lockname = 'disk';
1728 my ($mpdata, $old_volid);
1730 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1731 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1732 PVE
::LXC
::Config-
>check_lock($conf);
1734 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1736 if ($mpkey eq 'rootfs') {
1737 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1738 } elsif ($mpkey =~ m/mp\d+/) {
1739 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1741 die "Can't parse $mpkey\n";
1743 $old_volid = $mpdata->{volume
};
1745 die "you can't move a volume with snapshots and delete the source\n"
1746 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1748 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1750 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1755 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1757 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1758 my $storage_cfg = PVE
::Storage
::config
();
1763 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1764 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1765 $mpdata->{volume
} = $new_volid;
1767 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1768 my $digest = $conf->{digest
};
1769 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1770 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1772 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1774 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1776 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1780 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1781 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1787 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1788 if defined($new_volid);
1794 if ($param->{delete}) {
1796 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1797 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1803 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1808 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1811 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };