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 PVE
::Cluster
::check_cfs_quorum
();
185 my $rpcenv = PVE
::RPCEnvironment
::get
();
186 my $authuser = $rpcenv->get_user();
188 my $node = extract_param
($param, 'node');
189 my $vmid = extract_param
($param, 'vmid');
190 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
191 my $bwlimit = extract_param
($param, 'bwlimit');
192 my $start_after_create = extract_param
($param, 'start');
194 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
195 my $same_container_exists = -f
$basecfg_fn;
197 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
198 my $unprivileged = extract_param
($param, 'unprivileged');
199 my $restore = extract_param
($param, 'restore');
202 # fixme: limit allowed parameters
205 my $force = extract_param
($param, 'force');
207 if (!($same_container_exists && $restore && $force)) {
208 PVE
::Cluster
::check_vmid_unused
($vmid);
210 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
211 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
212 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
215 my $password = extract_param
($param, 'password');
216 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
217 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
219 my $pool = extract_param
($param, 'pool');
220 if (defined($pool)) {
221 $rpcenv->check_pool_exist($pool);
222 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
225 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
227 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
229 } elsif ($restore && $force && $same_container_exists &&
230 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
231 # OK: user has VM.Backup permissions, and want to restore an existing VM
236 my $ostemplate = extract_param
($param, 'ostemplate');
237 my $storage = extract_param
($param, 'storage') // 'local';
239 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
241 my $storage_cfg = cfs_read_file
("storage.cfg");
244 if ($ostemplate eq '-') {
245 die "pipe requires cli environment\n"
246 if $rpcenv->{type
} ne 'cli';
247 die "pipe can only be used with restore tasks\n"
250 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
252 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
253 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
257 my $check_and_activate_storage = sub {
260 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
262 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
263 if !$scfg->{content
}->{rootdir
};
265 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
267 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
268 $used_storages{$sid} = 1;
273 my $is_root = $authuser eq 'root@pam';
275 my $no_disk_param = {};
277 my $storage_only_mode = 1;
278 foreach my $opt (keys %$param) {
279 my $value = $param->{$opt};
280 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
281 # allow to use simple numbers (add default storage in that case)
282 if ($value =~ m/^\d+(\.\d+)?$/) {
283 $mp_param->{$opt} = "$storage:$value";
285 $mp_param->{$opt} = $value;
287 $storage_only_mode = 0;
288 } elsif ($opt =~ m/^unused\d+$/) {
289 warn "ignoring '$opt', cannot create/restore with unused volume\n";
290 delete $param->{$opt};
292 $no_disk_param->{$opt} = $value;
296 die "mount points configured, but 'rootfs' not set - aborting\n"
297 if !$storage_only_mode && !defined($mp_param->{rootfs
});
299 # check storage access, activate storage
300 my $delayed_mp_param = {};
301 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
302 my ($ms, $mountpoint) = @_;
304 my $volid = $mountpoint->{volume
};
305 my $mp = $mountpoint->{mp
};
307 if ($mountpoint->{type
} ne 'volume') { # bind or device
308 die "Only root can pass arbitrary filesystem paths.\n"
311 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
312 &$check_and_activate_storage($sid);
316 # check/activate default storage
317 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
319 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
321 $conf->{unprivileged
} = 1 if $unprivileged;
323 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
325 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
326 die "$emsg $@" if $@;
329 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
333 my $orig_mp_param; # only used if $restore
335 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
336 if ($is_root && $archive ne '-') {
338 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
339 # When we're root call 'restore_configuration' with ristricted=0,
340 # causing it to restore the raw lxc entries, among which there may be
341 # 'lxc.idmap' entries. We need to make sure that the extracted contents
342 # of the container match up with the restored configuration afterwards:
343 $conf->{lxc
} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}];
346 if ($storage_only_mode) {
348 if (!defined($orig_mp_param)) {
349 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
351 $mp_param = $orig_mp_param;
352 die "rootfs configuration could not be recovered, please check and specify manually!\n"
353 if !defined($mp_param->{rootfs
});
354 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
355 my ($ms, $mountpoint) = @_;
356 my $type = $mountpoint->{type
};
357 if ($type eq 'volume') {
358 die "unable to detect disk size - please specify $ms (size)\n"
359 if !defined($mountpoint->{size
});
360 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
361 delete $mountpoint->{size
};
362 $mountpoint->{volume
} = "$storage:$disksize";
363 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
365 my $type = $mountpoint->{type
};
366 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
367 if ($ms eq 'rootfs');
368 die "restoring '$ms' to $type mount is only possible for root\n"
371 if ($mountpoint->{backup
}) {
372 warn "WARNING - unsupported configuration!\n";
373 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
374 warn "mount point configuration will be restored after archive extraction!\n";
375 warn "contained files will be restored to wrong directory!\n";
377 delete $mp_param->{$ms}; # actually delay bind/dev mps
378 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
382 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
386 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
388 # we always have the 'create' lock so check for more than 1 entry
389 if (scalar(keys %$old_conf) > 1) {
390 # destroy old container volumes
391 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
395 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
396 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
397 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
400 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root);
402 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
403 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
404 $lxc_setup->post_create_hook($password, $ssh_keys);
408 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
409 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
412 $conf->{hostname
} ||= "CT$vmid";
413 $conf->{memory
} ||= 512;
414 $conf->{swap
} //= 512;
415 foreach my $mp (keys %$delayed_mp_param) {
416 $conf->{$mp} = $delayed_mp_param->{$mp};
418 PVE
::LXC
::Config-
>write_config($vmid, $conf);
421 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
422 eval { PVE
::LXC
::destroy_config
($vmid) };
426 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
428 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
429 if $start_after_create;
432 my $workername = $restore ?
'vzrestore' : 'vzcreate';
433 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
435 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
438 __PACKAGE__-
>register_method({
443 description
=> "Directory index",
448 additionalProperties
=> 0,
450 node
=> get_standard_option
('pve-node'),
451 vmid
=> get_standard_option
('pve-vmid'),
459 subdir
=> { type
=> 'string' },
462 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
468 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
471 { subdir
=> 'config' },
472 { subdir
=> 'status' },
473 { subdir
=> 'vncproxy' },
474 { subdir
=> 'termproxy' },
475 { subdir
=> 'vncwebsocket' },
476 { subdir
=> 'spiceproxy' },
477 { subdir
=> 'migrate' },
478 { subdir
=> 'clone' },
479 # { subdir => 'initlog' },
481 { subdir
=> 'rrddata' },
482 { subdir
=> 'firewall' },
483 { subdir
=> 'snapshot' },
484 { subdir
=> 'resize' },
491 __PACKAGE__-
>register_method({
493 path
=> '{vmid}/rrd',
495 protected
=> 1, # fixme: can we avoid that?
497 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
499 description
=> "Read VM RRD statistics (returns PNG)",
501 additionalProperties
=> 0,
503 node
=> get_standard_option
('pve-node'),
504 vmid
=> get_standard_option
('pve-vmid'),
506 description
=> "Specify the time frame you are interested in.",
508 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
511 description
=> "The list of datasources you want to display.",
512 type
=> 'string', format
=> 'pve-configid-list',
515 description
=> "The RRD consolidation function",
517 enum
=> [ 'AVERAGE', 'MAX' ],
525 filename
=> { type
=> 'string' },
531 return PVE
::Cluster
::create_rrd_graph
(
532 "pve2-vm/$param->{vmid}", $param->{timeframe
},
533 $param->{ds
}, $param->{cf
});
537 __PACKAGE__-
>register_method({
539 path
=> '{vmid}/rrddata',
541 protected
=> 1, # fixme: can we avoid that?
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
545 description
=> "Read VM RRD statistics",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "Specify the time frame you are interested in.",
554 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
557 description
=> "The RRD consolidation function",
559 enum
=> [ 'AVERAGE', 'MAX' ],
574 return PVE
::Cluster
::create_rrd_data
(
575 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
578 __PACKAGE__-
>register_method({
579 name
=> 'destroy_vm',
584 description
=> "Destroy the container (also delete all uses files).",
586 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
589 additionalProperties
=> 0,
591 node
=> get_standard_option
('pve-node'),
592 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
601 my $rpcenv = PVE
::RPCEnvironment
::get
();
603 my $authuser = $rpcenv->get_user();
605 my $vmid = $param->{vmid
};
607 # test if container exists
608 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
610 my $storage_cfg = cfs_read_file
("storage.cfg");
612 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
614 die "unable to remove CT $vmid - used in HA resources\n"
615 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
617 # do not allow destroy if there are replication jobs
618 my $repl_conf = PVE
::ReplicationConfig-
>new();
619 $repl_conf->check_for_existing_jobs($vmid);
621 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
623 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
626 # reload config after lock
627 $conf = PVE
::LXC
::Config-
>load_config($vmid);
628 PVE
::LXC
::Config-
>check_lock($conf);
630 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
632 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
633 PVE
::AccessControl
::remove_vm_access
($vmid);
634 PVE
::Firewall
::remove_vmfw_conf
($vmid);
637 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
639 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
644 __PACKAGE__-
>register_method ({
646 path
=> '{vmid}/vncproxy',
650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
652 description
=> "Creates a TCP VNC proxy connections.",
654 additionalProperties
=> 0,
656 node
=> get_standard_option
('pve-node'),
657 vmid
=> get_standard_option
('pve-vmid'),
661 description
=> "use websocket instead of standard VNC.",
665 description
=> "sets the width of the console in pixels.",
672 description
=> "sets the height of the console in pixels.",
680 additionalProperties
=> 0,
682 user
=> { type
=> 'string' },
683 ticket
=> { type
=> 'string' },
684 cert
=> { type
=> 'string' },
685 port
=> { type
=> 'integer' },
686 upid
=> { type
=> 'string' },
692 my $rpcenv = PVE
::RPCEnvironment
::get
();
694 my $authuser = $rpcenv->get_user();
696 my $vmid = $param->{vmid
};
697 my $node = $param->{node
};
699 my $authpath = "/vms/$vmid";
701 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
703 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
706 my ($remip, $family);
708 if ($node ne PVE
::INotify
::nodename
()) {
709 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
711 $family = PVE
::Tools
::get_host_address_family
($node);
714 my $port = PVE
::Tools
::next_vnc_port
($family);
716 # NOTE: vncterm VNC traffic is already TLS encrypted,
717 # so we select the fastest chipher here (or 'none'?)
718 my $remcmd = $remip ?
719 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
721 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
722 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
724 my $shcmd = [ '/usr/bin/dtach', '-A',
725 "/var/run/dtach/vzctlconsole$vmid",
726 '-r', 'winch', '-z', @$concmd];
731 syslog
('info', "starting lxc vnc proxy $upid\n");
735 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
736 '-timeout', $timeout, '-authpath', $authpath,
737 '-perm', 'VM.Console'];
739 if ($param->{width
}) {
740 push @$cmd, '-width', $param->{width
};
743 if ($param->{height
}) {
744 push @$cmd, '-height', $param->{height
};
747 if ($param->{websocket
}) {
748 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
749 push @$cmd, '-notls', '-listen', 'localhost';
752 push @$cmd, '-c', @$remcmd, @$shcmd;
754 run_command
($cmd, keeplocale
=> 1);
759 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
761 PVE
::Tools
::wait_for_vnc_port
($port);
772 __PACKAGE__-
>register_method ({
774 path
=> '{vmid}/termproxy',
778 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
780 description
=> "Creates a TCP proxy connection.",
782 additionalProperties
=> 0,
784 node
=> get_standard_option
('pve-node'),
785 vmid
=> get_standard_option
('pve-vmid'),
789 additionalProperties
=> 0,
791 user
=> { type
=> 'string' },
792 ticket
=> { type
=> 'string' },
793 port
=> { type
=> 'integer' },
794 upid
=> { type
=> 'string' },
800 my $rpcenv = PVE
::RPCEnvironment
::get
();
802 my $authuser = $rpcenv->get_user();
804 my $vmid = $param->{vmid
};
805 my $node = $param->{node
};
807 my $authpath = "/vms/$vmid";
809 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
811 my ($remip, $family);
813 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
814 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
816 $family = PVE
::Tools
::get_host_address_family
($node);
819 my $port = PVE
::Tools
::next_vnc_port
($family);
821 my $remcmd = $remip ?
822 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
824 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
825 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
827 my $shcmd = [ '/usr/bin/dtach', '-A',
828 "/var/run/dtach/vzctlconsole$vmid",
829 '-r', 'winch', '-z', @$concmd];
834 syslog
('info', "starting lxc termproxy $upid\n");
836 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
837 '--perm', 'VM.Console', '--'];
838 push @$cmd, @$remcmd, @$shcmd;
840 PVE
::Tools
::run_command
($cmd);
843 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
845 PVE
::Tools
::wait_for_vnc_port
($port);
855 __PACKAGE__-
>register_method({
856 name
=> 'vncwebsocket',
857 path
=> '{vmid}/vncwebsocket',
860 description
=> "You also need to pass a valid ticket (vncticket).",
861 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
863 description
=> "Opens a weksocket for VNC traffic.",
865 additionalProperties
=> 0,
867 node
=> get_standard_option
('pve-node'),
868 vmid
=> get_standard_option
('pve-vmid'),
870 description
=> "Ticket from previous call to vncproxy.",
875 description
=> "Port number returned by previous vncproxy call.",
885 port
=> { type
=> 'string' },
891 my $rpcenv = PVE
::RPCEnvironment
::get
();
893 my $authuser = $rpcenv->get_user();
895 my $authpath = "/vms/$param->{vmid}";
897 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
899 my $port = $param->{port
};
901 return { port
=> $port };
904 __PACKAGE__-
>register_method ({
905 name
=> 'spiceproxy',
906 path
=> '{vmid}/spiceproxy',
911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
913 description
=> "Returns a SPICE configuration to connect to the CT.",
915 additionalProperties
=> 0,
917 node
=> get_standard_option
('pve-node'),
918 vmid
=> get_standard_option
('pve-vmid'),
919 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
922 returns
=> get_standard_option
('remote-viewer-config'),
926 my $vmid = $param->{vmid
};
927 my $node = $param->{node
};
928 my $proxy = $param->{proxy
};
930 my $authpath = "/vms/$vmid";
931 my $permissions = 'VM.Console';
933 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
935 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
937 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
939 my $shcmd = ['/usr/bin/dtach', '-A',
940 "/var/run/dtach/vzctlconsole$vmid",
941 '-r', 'winch', '-z', @$concmd];
943 my $title = "CT $vmid";
945 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
949 __PACKAGE__-
>register_method({
950 name
=> 'migrate_vm',
951 path
=> '{vmid}/migrate',
955 description
=> "Migrate the container to another node. Creates a new migration task.",
957 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
960 additionalProperties
=> 0,
962 node
=> get_standard_option
('pve-node'),
963 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
964 target
=> get_standard_option
('pve-node', {
965 description
=> "Target node.",
966 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
970 description
=> "Use online/live migration.",
975 description
=> "Use restart migration",
980 description
=> "Timeout in seconds for shutdown for restart migration",
986 description
=> "Force migration despite local bind / device" .
987 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
994 description
=> "the task ID.",
999 my $rpcenv = PVE
::RPCEnvironment
::get
();
1001 my $authuser = $rpcenv->get_user();
1003 my $target = extract_param
($param, 'target');
1005 my $localnode = PVE
::INotify
::nodename
();
1006 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1008 PVE
::Cluster
::check_cfs_quorum
();
1010 PVE
::Cluster
::check_node_exists
($target);
1012 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1014 my $vmid = extract_param
($param, 'vmid');
1017 PVE
::LXC
::Config-
>load_config($vmid);
1019 # try to detect errors early
1020 if (PVE
::LXC
::check_running
($vmid)) {
1021 die "can't migrate running container without --online or --restart\n"
1022 if !$param->{online
} && !$param->{restart
};
1025 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1030 my $service = "ct:$vmid";
1032 my $cmd = ['ha-manager', 'migrate', $service, $target];
1034 print "Requesting HA migration for CT $vmid to node $target\n";
1036 PVE
::Tools
::run_command
($cmd);
1041 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1046 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1050 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1053 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1057 __PACKAGE__-
>register_method({
1058 name
=> 'vm_feature',
1059 path
=> '{vmid}/feature',
1063 description
=> "Check if feature for virtual machine is available.",
1065 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1068 additionalProperties
=> 0,
1070 node
=> get_standard_option
('pve-node'),
1071 vmid
=> get_standard_option
('pve-vmid'),
1073 description
=> "Feature to check.",
1075 enum
=> [ 'snapshot', 'clone', 'copy' ],
1077 snapname
=> get_standard_option
('pve-snapshot-name', {
1085 hasFeature
=> { type
=> 'boolean' },
1088 #items => { type => 'string' },
1095 my $node = extract_param
($param, 'node');
1097 my $vmid = extract_param
($param, 'vmid');
1099 my $snapname = extract_param
($param, 'snapname');
1101 my $feature = extract_param
($param, 'feature');
1103 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1106 my $snap = $conf->{snapshots
}->{$snapname};
1107 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1110 my $storage_cfg = PVE
::Storage
::config
();
1111 #Maybe include later
1112 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1113 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1116 hasFeature
=> $hasFeature,
1117 #nodes => [ keys %$nodelist ],
1121 __PACKAGE__-
>register_method({
1123 path
=> '{vmid}/template',
1127 description
=> "Create a Template.",
1129 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1130 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1133 additionalProperties
=> 0,
1135 node
=> get_standard_option
('pve-node'),
1136 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1139 returns
=> { type
=> 'null'},
1143 my $rpcenv = PVE
::RPCEnvironment
::get
();
1145 my $authuser = $rpcenv->get_user();
1147 my $node = extract_param
($param, 'node');
1149 my $vmid = extract_param
($param, 'vmid');
1151 my $updatefn = sub {
1153 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1154 PVE
::LXC
::Config-
>check_lock($conf);
1156 die "unable to create template, because CT contains snapshots\n"
1157 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1159 die "you can't convert a template to a template\n"
1160 if PVE
::LXC
::Config-
>is_template($conf);
1162 die "you can't convert a CT to template if the CT is running\n"
1163 if PVE
::LXC
::check_running
($vmid);
1165 my $scfg = PVE
::Storage
::config
();
1166 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1169 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1170 die "Directory storage '$sid' does not support container templates!\n"
1171 if $scfg->{ids
}->{$sid}->{path
};
1175 PVE
::LXC
::template_create
($vmid, $conf);
1177 $conf->{template
} = 1;
1179 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1180 # and remove lxc config
1181 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1184 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1187 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1192 __PACKAGE__-
>register_method({
1194 path
=> '{vmid}/clone',
1198 description
=> "Create a container clone/copy",
1200 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1201 "and 'VM.Allocate' permissions " .
1202 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1203 "'Datastore.AllocateSpace' on any used storage.",
1206 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1208 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1209 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1214 additionalProperties
=> 0,
1216 node
=> get_standard_option
('pve-node'),
1217 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1218 newid
=> get_standard_option
('pve-vmid', {
1219 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1220 description
=> 'VMID for the clone.' }),
1223 type
=> 'string', format
=> 'dns-name',
1224 description
=> "Set a hostname for the new CT.",
1229 description
=> "Description for the new CT.",
1233 type
=> 'string', format
=> 'pve-poolid',
1234 description
=> "Add the new CT to the specified pool.",
1236 snapname
=> get_standard_option
('pve-snapshot-name', {
1239 storage
=> get_standard_option
('pve-storage-id', {
1240 description
=> "Target storage for full clone.",
1246 description
=> "Create a full copy of all disks. This is always done when " .
1247 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1249 target
=> get_standard_option
('pve-node', {
1250 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1261 my $rpcenv = PVE
::RPCEnvironment
::get
();
1263 my $authuser = $rpcenv->get_user();
1265 my $node = extract_param
($param, 'node');
1267 my $vmid = extract_param
($param, 'vmid');
1269 my $newid = extract_param
($param, 'newid');
1271 my $pool = extract_param
($param, 'pool');
1273 if (defined($pool)) {
1274 $rpcenv->check_pool_exist($pool);
1277 my $snapname = extract_param
($param, 'snapname');
1279 my $storage = extract_param
($param, 'storage');
1281 my $target = extract_param
($param, 'target');
1283 my $localnode = PVE
::INotify
::nodename
();
1285 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1287 PVE
::Cluster
::check_node_exists
($target) if $target;
1289 my $storecfg = PVE
::Storage
::config
();
1292 # check if storage is enabled on local node
1293 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1295 # check if storage is available on target node
1296 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1297 # clone only works if target storage is shared
1298 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1299 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1303 PVE
::Cluster
::check_cfs_quorum
();
1307 my $mountpoints = {};
1312 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1313 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1315 $running = PVE
::LXC
::check_running
($vmid) || 0;
1317 my $full = extract_param
($param, 'full');
1318 if (!defined($full)) {
1319 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1321 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1324 die "snapshot '$snapname' does not exist\n"
1325 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1328 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1330 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1331 die "unable to create CT $newid: config file already exists\n"
1335 foreach my $opt (keys %$src_conf) {
1336 next if $opt =~ m/^unused\d+$/;
1338 my $value = $src_conf->{$opt};
1340 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1341 my $mp = $opt eq 'rootfs' ?
1342 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1343 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1345 if ($mp->{type
} eq 'volume') {
1346 my $volid = $mp->{volume
};
1348 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1349 $sid = $storage if defined($storage);
1350 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1351 if (!$scfg->{shared
}) {
1353 warn "found non-shared volume: $volid\n" if $target;
1356 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1359 die "Cannot do full clones on a running container without snapshots\n"
1360 if $running && !defined($snapname);
1361 $fullclone->{$opt} = 1;
1363 # not full means clone instead of copy
1364 die "Linked clone feature for '$volid' is not available\n"
1365 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1368 $mountpoints->{$opt} = $mp;
1369 push @$vollist, $volid;
1372 # TODO: allow bind mounts?
1373 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1375 } elsif ($opt =~ m/^net(\d+)$/) {
1376 # always change MAC! address
1377 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1378 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1379 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1380 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1382 # copy everything else
1383 $newconf->{$opt} = $value;
1386 die "can't clone CT to node '$target' (CT uses local storage)\n"
1387 if $target && !$sharedvm;
1389 # Replace the 'disk' lock with a 'create' lock.
1390 $newconf->{lock} = 'create';
1392 delete $newconf->{template
};
1393 if ($param->{hostname
}) {
1394 $newconf->{hostname
} = $param->{hostname
};
1397 if ($param->{description
}) {
1398 $newconf->{description
} = $param->{description
};
1401 # create empty/temp config - this fails if CT already exists on other node
1402 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1405 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1411 my $update_conf = sub {
1412 my ($key, $value) = @_;
1413 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1414 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1415 die "Lost 'create' config lock, aborting.\n"
1416 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1417 $conf->{$key} = $value;
1418 PVE
::LXC
::Config-
>write_config($newid, $conf);
1425 my $newvollist = [];
1427 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1428 die "unexpected state change\n" if $verify_running != $running;
1434 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1436 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1438 foreach my $opt (keys %$mountpoints) {
1439 my $mp = $mountpoints->{$opt};
1440 my $volid = $mp->{volume
};
1443 if ($fullclone->{$opt}) {
1444 print "create full clone of mountpoint $opt ($volid)\n";
1445 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1446 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1448 print "create linked clone of mount point $opt ($volid)\n";
1449 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1452 push @$newvollist, $newvolid;
1453 $mp->{volume
} = $newvolid;
1455 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1458 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1459 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1462 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1463 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1464 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1466 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1467 die "Failed to move config to node '$target' - rename failed: $!\n"
1468 if !rename($conffile, $newconffile);
1473 # Unlock the source config in any case:
1474 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1478 # Now cleanup the config & disks:
1481 sleep 1; # some storages like rbd need to wait before release volume - really?
1483 foreach my $volid (@$newvollist) {
1484 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1487 die "clone failed: $err";
1493 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1494 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1498 __PACKAGE__-
>register_method({
1499 name
=> 'resize_vm',
1500 path
=> '{vmid}/resize',
1504 description
=> "Resize a container mount point.",
1506 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1515 description
=> "The disk you want to resize.",
1516 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1520 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1521 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.",
1525 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1533 description
=> "the task ID.",
1538 my $rpcenv = PVE
::RPCEnvironment
::get
();
1540 my $authuser = $rpcenv->get_user();
1542 my $node = extract_param
($param, 'node');
1544 my $vmid = extract_param
($param, 'vmid');
1546 my $digest = extract_param
($param, 'digest');
1548 my $sizestr = extract_param
($param, 'size');
1549 my $ext = ($sizestr =~ s/^\+//);
1550 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1551 die "invalid size string" if !defined($newsize);
1553 die "no options specified\n" if !scalar(keys %$param);
1555 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1557 my $storage_cfg = cfs_read_file
("storage.cfg");
1561 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1562 PVE
::LXC
::Config-
>check_lock($conf);
1564 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1566 my $running = PVE
::LXC
::check_running
($vmid);
1568 my $disk = $param->{disk
};
1569 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1570 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1572 my $volid = $mp->{volume
};
1574 my (undef, undef, $owner, undef, undef, undef, $format) =
1575 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1577 die "can't resize mount point owned by another container ($owner)"
1580 die "can't resize volume: $disk if snapshot exists\n"
1581 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1583 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1585 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1587 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1589 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1590 $newsize += $size if $ext;
1591 $newsize = int($newsize);
1593 die "unable to shrink disk size\n" if $newsize < $size;
1595 return if $size == $newsize;
1597 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1599 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1600 # we pass 0 here (parameter only makes sense for qemu)
1601 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1603 $mp->{size
} = $newsize;
1604 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1606 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1608 if ($format eq 'raw') {
1609 # we need to ensure that the volume is mapped, if not needed this is a NOP
1610 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1611 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1615 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1616 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1617 die "internal error: CT running but mount point not attached to a loop device"
1619 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1621 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1622 # to be visible to it in its namespace.
1623 # To not interfere with the rest of the system we unshare the current mount namespace,
1624 # mount over /tmp and then run resize2fs.
1626 # interestingly we don't need to e2fsck on mounted systems...
1627 my $quoted = PVE
::Tools
::shellquote
($path);
1628 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1630 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1632 warn "Failed to update the container's filesystem: $@\n" if $@;
1635 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1636 PVE
::Tools
::run_command
(['resize2fs', $path]);
1638 warn "Failed to update the container's filesystem: $@\n" if $@;
1640 # always un-map if not running, this is a NOP if not needed
1641 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1646 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1649 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1652 __PACKAGE__-
>register_method({
1653 name
=> 'move_volume',
1654 path
=> '{vmid}/move_volume',
1658 description
=> "Move a rootfs-/mp-volume to a different storage",
1660 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1661 "and 'Datastore.AllocateSpace' permissions on the storage.",
1664 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1665 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1669 additionalProperties
=> 0,
1671 node
=> get_standard_option
('pve-node'),
1672 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1675 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1676 description
=> "Volume which will be moved.",
1678 storage
=> get_standard_option
('pve-storage-id', {
1679 description
=> "Target Storage.",
1680 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1684 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1690 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1702 my $rpcenv = PVE
::RPCEnvironment
::get
();
1704 my $authuser = $rpcenv->get_user();
1706 my $vmid = extract_param
($param, 'vmid');
1708 my $storage = extract_param
($param, 'storage');
1710 my $mpkey = extract_param
($param, 'volume');
1712 my $lockname = 'disk';
1714 my ($mpdata, $old_volid);
1716 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1717 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1718 PVE
::LXC
::Config-
>check_lock($conf);
1720 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1722 if ($mpkey eq 'rootfs') {
1723 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1724 } elsif ($mpkey =~ m/mp\d+/) {
1725 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1727 die "Can't parse $mpkey\n";
1729 $old_volid = $mpdata->{volume
};
1731 die "you can't move a volume with snapshots and delete the source\n"
1732 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1734 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1736 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1741 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1743 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1744 my $storage_cfg = PVE
::Storage
::config
();
1749 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1750 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1751 $mpdata->{volume
} = $new_volid;
1753 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1754 my $digest = $conf->{digest
};
1755 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1756 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1758 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1760 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1762 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1766 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1767 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1773 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1774 if defined($new_volid);
1780 if ($param->{delete}) {
1782 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1783 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1789 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1794 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1797 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };