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_mp_param; # only used if $restore
365 (my $orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
367 # When we're root call 'restore_configuration' with ristricted=0,
368 # causing it to restore the raw lxc entries, among which there may be
369 # 'lxc.idmap' entries. We need to make sure that the extracted contents
370 # of the container match up with the restored configuration afterwards:
371 $conf->{lxc
} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}];
374 if ($storage_only_mode) {
376 $mp_param = $orig_mp_param;
377 die "rootfs configuration could not be recovered, please check and specify manually!\n"
378 if !defined($mp_param->{rootfs
});
379 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
380 my ($ms, $mountpoint) = @_;
381 my $type = $mountpoint->{type
};
382 if ($type eq 'volume') {
383 die "unable to detect disk size - please specify $ms (size)\n"
384 if !defined($mountpoint->{size
});
385 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
386 delete $mountpoint->{size
};
387 $mountpoint->{volume
} = "$storage:$disksize";
388 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
390 my $type = $mountpoint->{type
};
391 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
392 if ($ms eq 'rootfs');
393 die "restoring '$ms' to $type mount is only possible for root\n"
396 if ($mountpoint->{backup
}) {
397 warn "WARNING - unsupported configuration!\n";
398 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
399 warn "mount point configuration will be restored after archive extraction!\n";
400 warn "contained files will be restored to wrong directory!\n";
402 delete $mp_param->{$ms}; # actually delay bind/dev mps
403 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
407 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
411 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
413 if (defined($old_conf)) {
414 # destroy old container volumes
415 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
419 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
420 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
421 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
424 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root);
426 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
427 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
428 $lxc_setup->post_create_hook($password, $ssh_keys);
432 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
433 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
436 $conf->{hostname
} ||= "CT$vmid";
437 $conf->{memory
} ||= 512;
438 $conf->{swap
} //= 512;
439 foreach my $mp (keys %$delayed_mp_param) {
440 $conf->{$mp} = $delayed_mp_param->{$mp};
442 PVE
::LXC
::Config-
>write_config($vmid, $conf);
445 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
446 PVE
::LXC
::destroy_config
($vmid);
449 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
451 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
452 if $start_after_create;
455 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
457 &$check_vmid_usage(); # first check before locking
459 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
460 $vmid, $authuser, $realcmd);
464 __PACKAGE__-
>register_method({
469 description
=> "Directory index",
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
477 vmid
=> get_standard_option
('pve-vmid'),
485 subdir
=> { type
=> 'string' },
488 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
494 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
497 { subdir
=> 'config' },
498 { subdir
=> 'status' },
499 { subdir
=> 'vncproxy' },
500 { subdir
=> 'termproxy' },
501 { subdir
=> 'vncwebsocket' },
502 { subdir
=> 'spiceproxy' },
503 { subdir
=> 'migrate' },
504 { subdir
=> 'clone' },
505 # { subdir => 'initlog' },
507 { subdir
=> 'rrddata' },
508 { subdir
=> 'firewall' },
509 { subdir
=> 'snapshot' },
510 { subdir
=> 'resize' },
517 __PACKAGE__-
>register_method({
519 path
=> '{vmid}/rrd',
521 protected
=> 1, # fixme: can we avoid that?
523 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
525 description
=> "Read VM RRD statistics (returns PNG)",
527 additionalProperties
=> 0,
529 node
=> get_standard_option
('pve-node'),
530 vmid
=> get_standard_option
('pve-vmid'),
532 description
=> "Specify the time frame you are interested in.",
534 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
537 description
=> "The list of datasources you want to display.",
538 type
=> 'string', format
=> 'pve-configid-list',
541 description
=> "The RRD consolidation function",
543 enum
=> [ 'AVERAGE', 'MAX' ],
551 filename
=> { type
=> 'string' },
557 return PVE
::Cluster
::create_rrd_graph
(
558 "pve2-vm/$param->{vmid}", $param->{timeframe
},
559 $param->{ds
}, $param->{cf
});
563 __PACKAGE__-
>register_method({
565 path
=> '{vmid}/rrddata',
567 protected
=> 1, # fixme: can we avoid that?
569 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
571 description
=> "Read VM RRD statistics",
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
576 vmid
=> get_standard_option
('pve-vmid'),
578 description
=> "Specify the time frame you are interested in.",
580 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
583 description
=> "The RRD consolidation function",
585 enum
=> [ 'AVERAGE', 'MAX' ],
600 return PVE
::Cluster
::create_rrd_data
(
601 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
604 __PACKAGE__-
>register_method({
605 name
=> 'destroy_vm',
610 description
=> "Destroy the container (also delete all uses files).",
612 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
615 additionalProperties
=> 0,
617 node
=> get_standard_option
('pve-node'),
618 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
627 my $rpcenv = PVE
::RPCEnvironment
::get
();
629 my $authuser = $rpcenv->get_user();
631 my $vmid = $param->{vmid
};
633 # test if container exists
634 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
636 my $storage_cfg = cfs_read_file
("storage.cfg");
638 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
640 die "unable to remove CT $vmid - used in HA resources\n"
641 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
643 # do not allow destroy if there are replication jobs
644 my $repl_conf = PVE
::ReplicationConfig-
>new();
645 $repl_conf->check_for_existing_jobs($vmid);
647 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
649 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
652 # reload config after lock
653 $conf = PVE
::LXC
::Config-
>load_config($vmid);
654 PVE
::LXC
::Config-
>check_lock($conf);
656 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
658 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
659 PVE
::AccessControl
::remove_vm_access
($vmid);
660 PVE
::Firewall
::remove_vmfw_conf
($vmid);
663 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
665 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
670 __PACKAGE__-
>register_method ({
672 path
=> '{vmid}/vncproxy',
676 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
678 description
=> "Creates a TCP VNC proxy connections.",
680 additionalProperties
=> 0,
682 node
=> get_standard_option
('pve-node'),
683 vmid
=> get_standard_option
('pve-vmid'),
687 description
=> "use websocket instead of standard VNC.",
691 description
=> "sets the width of the console in pixels.",
698 description
=> "sets the height of the console in pixels.",
706 additionalProperties
=> 0,
708 user
=> { type
=> 'string' },
709 ticket
=> { type
=> 'string' },
710 cert
=> { type
=> 'string' },
711 port
=> { type
=> 'integer' },
712 upid
=> { type
=> 'string' },
718 my $rpcenv = PVE
::RPCEnvironment
::get
();
720 my $authuser = $rpcenv->get_user();
722 my $vmid = $param->{vmid
};
723 my $node = $param->{node
};
725 my $authpath = "/vms/$vmid";
727 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
729 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
732 my ($remip, $family);
734 if ($node ne PVE
::INotify
::nodename
()) {
735 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
737 $family = PVE
::Tools
::get_host_address_family
($node);
740 my $port = PVE
::Tools
::next_vnc_port
($family);
742 # NOTE: vncterm VNC traffic is already TLS encrypted,
743 # so we select the fastest chipher here (or 'none'?)
744 my $remcmd = $remip ?
745 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
747 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
748 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
750 my $shcmd = [ '/usr/bin/dtach', '-A',
751 "/var/run/dtach/vzctlconsole$vmid",
752 '-r', 'winch', '-z', @$concmd];
757 syslog
('info', "starting lxc vnc proxy $upid\n");
761 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
762 '-timeout', $timeout, '-authpath', $authpath,
763 '-perm', 'VM.Console'];
765 if ($param->{width
}) {
766 push @$cmd, '-width', $param->{width
};
769 if ($param->{height
}) {
770 push @$cmd, '-height', $param->{height
};
773 if ($param->{websocket
}) {
774 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
775 push @$cmd, '-notls', '-listen', 'localhost';
778 push @$cmd, '-c', @$remcmd, @$shcmd;
780 run_command
($cmd, keeplocale
=> 1);
785 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
787 PVE
::Tools
::wait_for_vnc_port
($port);
798 __PACKAGE__-
>register_method ({
800 path
=> '{vmid}/termproxy',
804 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
806 description
=> "Creates a TCP proxy connection.",
808 additionalProperties
=> 0,
810 node
=> get_standard_option
('pve-node'),
811 vmid
=> get_standard_option
('pve-vmid'),
815 additionalProperties
=> 0,
817 user
=> { type
=> 'string' },
818 ticket
=> { type
=> 'string' },
819 port
=> { type
=> 'integer' },
820 upid
=> { type
=> 'string' },
826 my $rpcenv = PVE
::RPCEnvironment
::get
();
828 my $authuser = $rpcenv->get_user();
830 my $vmid = $param->{vmid
};
831 my $node = $param->{node
};
833 my $authpath = "/vms/$vmid";
835 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
837 my ($remip, $family);
839 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
840 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
842 $family = PVE
::Tools
::get_host_address_family
($node);
845 my $port = PVE
::Tools
::next_vnc_port
($family);
847 my $remcmd = $remip ?
848 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
850 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
851 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
853 my $shcmd = [ '/usr/bin/dtach', '-A',
854 "/var/run/dtach/vzctlconsole$vmid",
855 '-r', 'winch', '-z', @$concmd];
860 syslog
('info', "starting lxc termproxy $upid\n");
862 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
863 '--perm', 'VM.Console', '--'];
864 push @$cmd, @$remcmd, @$shcmd;
866 PVE
::Tools
::run_command
($cmd);
869 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
871 PVE
::Tools
::wait_for_vnc_port
($port);
881 __PACKAGE__-
>register_method({
882 name
=> 'vncwebsocket',
883 path
=> '{vmid}/vncwebsocket',
886 description
=> "You also need to pass a valid ticket (vncticket).",
887 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
889 description
=> "Opens a weksocket for VNC traffic.",
891 additionalProperties
=> 0,
893 node
=> get_standard_option
('pve-node'),
894 vmid
=> get_standard_option
('pve-vmid'),
896 description
=> "Ticket from previous call to vncproxy.",
901 description
=> "Port number returned by previous vncproxy call.",
911 port
=> { type
=> 'string' },
917 my $rpcenv = PVE
::RPCEnvironment
::get
();
919 my $authuser = $rpcenv->get_user();
921 my $authpath = "/vms/$param->{vmid}";
923 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
925 my $port = $param->{port
};
927 return { port
=> $port };
930 __PACKAGE__-
>register_method ({
931 name
=> 'spiceproxy',
932 path
=> '{vmid}/spiceproxy',
937 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
939 description
=> "Returns a SPICE configuration to connect to the CT.",
941 additionalProperties
=> 0,
943 node
=> get_standard_option
('pve-node'),
944 vmid
=> get_standard_option
('pve-vmid'),
945 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
948 returns
=> get_standard_option
('remote-viewer-config'),
952 my $vmid = $param->{vmid
};
953 my $node = $param->{node
};
954 my $proxy = $param->{proxy
};
956 my $authpath = "/vms/$vmid";
957 my $permissions = 'VM.Console';
959 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
961 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
963 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
965 my $shcmd = ['/usr/bin/dtach', '-A',
966 "/var/run/dtach/vzctlconsole$vmid",
967 '-r', 'winch', '-z', @$concmd];
969 my $title = "CT $vmid";
971 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
975 __PACKAGE__-
>register_method({
976 name
=> 'migrate_vm',
977 path
=> '{vmid}/migrate',
981 description
=> "Migrate the container to another node. Creates a new migration task.",
983 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
986 additionalProperties
=> 0,
988 node
=> get_standard_option
('pve-node'),
989 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
990 target
=> get_standard_option
('pve-node', {
991 description
=> "Target node.",
992 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
996 description
=> "Use online/live migration.",
1001 description
=> "Use restart migration",
1006 description
=> "Timeout in seconds for shutdown for restart migration",
1012 description
=> "Force migration despite local bind / device" .
1013 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1020 description
=> "the task ID.",
1025 my $rpcenv = PVE
::RPCEnvironment
::get
();
1027 my $authuser = $rpcenv->get_user();
1029 my $target = extract_param
($param, 'target');
1031 my $localnode = PVE
::INotify
::nodename
();
1032 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1034 PVE
::Cluster
::check_cfs_quorum
();
1036 PVE
::Cluster
::check_node_exists
($target);
1038 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1040 my $vmid = extract_param
($param, 'vmid');
1043 PVE
::LXC
::Config-
>load_config($vmid);
1045 # try to detect errors early
1046 if (PVE
::LXC
::check_running
($vmid)) {
1047 die "can't migrate running container without --online or --restart\n"
1048 if !$param->{online
} && !$param->{restart
};
1051 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1056 my $service = "ct:$vmid";
1058 my $cmd = ['ha-manager', 'migrate', $service, $target];
1060 print "Requesting HA migration for CT $vmid to node $target\n";
1062 PVE
::Tools
::run_command
($cmd);
1067 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1072 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1076 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1079 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1083 __PACKAGE__-
>register_method({
1084 name
=> 'vm_feature',
1085 path
=> '{vmid}/feature',
1089 description
=> "Check if feature for virtual machine is available.",
1091 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1094 additionalProperties
=> 0,
1096 node
=> get_standard_option
('pve-node'),
1097 vmid
=> get_standard_option
('pve-vmid'),
1099 description
=> "Feature to check.",
1101 enum
=> [ 'snapshot', 'clone', 'copy' ],
1103 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1111 hasFeature
=> { type
=> 'boolean' },
1114 #items => { type => 'string' },
1121 my $node = extract_param
($param, 'node');
1123 my $vmid = extract_param
($param, 'vmid');
1125 my $snapname = extract_param
($param, 'snapname');
1127 my $feature = extract_param
($param, 'feature');
1129 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1132 my $snap = $conf->{snapshots
}->{$snapname};
1133 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1136 my $storage_cfg = PVE
::Storage
::config
();
1137 #Maybe include later
1138 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1139 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1142 hasFeature
=> $hasFeature,
1143 #nodes => [ keys %$nodelist ],
1147 __PACKAGE__-
>register_method({
1149 path
=> '{vmid}/template',
1153 description
=> "Create a Template.",
1155 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1156 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1159 additionalProperties
=> 0,
1161 node
=> get_standard_option
('pve-node'),
1162 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1165 returns
=> { type
=> 'null'},
1169 my $rpcenv = PVE
::RPCEnvironment
::get
();
1171 my $authuser = $rpcenv->get_user();
1173 my $node = extract_param
($param, 'node');
1175 my $vmid = extract_param
($param, 'vmid');
1177 my $updatefn = sub {
1179 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1180 PVE
::LXC
::Config-
>check_lock($conf);
1182 die "unable to create template, because CT contains snapshots\n"
1183 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1185 die "you can't convert a template to a template\n"
1186 if PVE
::LXC
::Config-
>is_template($conf);
1188 die "you can't convert a CT to template if the CT is running\n"
1189 if PVE
::LXC
::check_running
($vmid);
1191 my $scfg = PVE
::Storage
::config
();
1192 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1195 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1196 die "Directory storage '$sid' does not support container templates!\n"
1197 if $scfg->{ids
}->{$sid}->{path
};
1201 PVE
::LXC
::template_create
($vmid, $conf);
1203 $conf->{template
} = 1;
1205 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1206 # and remove lxc config
1207 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1210 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1213 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1218 __PACKAGE__-
>register_method({
1220 path
=> '{vmid}/clone',
1224 description
=> "Create a container clone/copy",
1226 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1227 "and 'VM.Allocate' permissions " .
1228 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1229 "'Datastore.AllocateSpace' on any used storage.",
1232 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1234 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1235 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1244 newid
=> get_standard_option
('pve-vmid', {
1245 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1246 description
=> 'VMID for the clone.' }),
1249 type
=> 'string', format
=> 'dns-name',
1250 description
=> "Set a hostname for the new CT.",
1255 description
=> "Description for the new CT.",
1259 type
=> 'string', format
=> 'pve-poolid',
1260 description
=> "Add the new CT to the specified pool.",
1262 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1265 storage
=> get_standard_option
('pve-storage-id', {
1266 description
=> "Target storage for full clone.",
1272 description
=> "Create a full copy of all disks. This is always done when " .
1273 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1275 target
=> get_standard_option
('pve-node', {
1276 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1287 my $rpcenv = PVE
::RPCEnvironment
::get
();
1289 my $authuser = $rpcenv->get_user();
1291 my $node = extract_param
($param, 'node');
1293 my $vmid = extract_param
($param, 'vmid');
1295 my $newid = extract_param
($param, 'newid');
1297 my $pool = extract_param
($param, 'pool');
1299 if (defined($pool)) {
1300 $rpcenv->check_pool_exist($pool);
1303 my $snapname = extract_param
($param, 'snapname');
1305 my $storage = extract_param
($param, 'storage');
1307 my $target = extract_param
($param, 'target');
1309 my $localnode = PVE
::INotify
::nodename
();
1311 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1313 PVE
::Cluster
::check_node_exists
($target) if $target;
1315 my $storecfg = PVE
::Storage
::config
();
1318 # check if storage is enabled on local node
1319 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1321 # check if storage is available on target node
1322 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1323 # clone only works if target storage is shared
1324 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1325 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1329 PVE
::Cluster
::check_cfs_quorum
();
1333 my $mountpoints = {};
1338 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1339 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1341 $running = PVE
::LXC
::check_running
($vmid) || 0;
1343 my $full = extract_param
($param, 'full');
1344 if (!defined($full)) {
1345 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1347 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1350 die "snapshot '$snapname' does not exist\n"
1351 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1354 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1356 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1357 die "unable to create CT $newid: config file already exists\n"
1361 foreach my $opt (keys %$src_conf) {
1362 next if $opt =~ m/^unused\d+$/;
1364 my $value = $src_conf->{$opt};
1366 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1367 my $mp = $opt eq 'rootfs' ?
1368 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1369 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1371 if ($mp->{type
} eq 'volume') {
1372 my $volid = $mp->{volume
};
1374 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1375 $sid = $storage if defined($storage);
1376 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1377 if (!$scfg->{shared
}) {
1379 warn "found non-shared volume: $volid\n" if $target;
1382 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1385 die "Cannot do full clones on a running container without snapshots\n"
1386 if $running && !defined($snapname);
1387 $fullclone->{$opt} = 1;
1389 # not full means clone instead of copy
1390 die "Linked clone feature for '$volid' is not available\n"
1391 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1394 $mountpoints->{$opt} = $mp;
1395 push @$vollist, $volid;
1398 # TODO: allow bind mounts?
1399 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1401 } elsif ($opt =~ m/^net(\d+)$/) {
1402 # always change MAC! address
1403 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1404 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1405 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1406 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1408 # copy everything else
1409 $newconf->{$opt} = $value;
1412 die "can't clone CT to node '$target' (CT uses local storage)\n"
1413 if $target && !$sharedvm;
1415 # Replace the 'disk' lock with a 'create' lock.
1416 $newconf->{lock} = 'create';
1418 delete $newconf->{template
};
1419 if ($param->{hostname
}) {
1420 $newconf->{hostname
} = $param->{hostname
};
1423 if ($param->{description
}) {
1424 $newconf->{description
} = $param->{description
};
1427 # create empty/temp config - this fails if CT already exists on other node
1428 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1431 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1437 my $update_conf = sub {
1438 my ($key, $value) = @_;
1439 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1440 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1441 die "Lost 'create' config lock, aborting.\n"
1442 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1443 $conf->{$key} = $value;
1444 PVE
::LXC
::Config-
>write_config($newid, $conf);
1451 my $newvollist = [];
1453 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1454 die "unexpected state change\n" if $verify_running != $running;
1460 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1462 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1464 foreach my $opt (keys %$mountpoints) {
1465 my $mp = $mountpoints->{$opt};
1466 my $volid = $mp->{volume
};
1469 if ($fullclone->{$opt}) {
1470 print "create full clone of mountpoint $opt ($volid)\n";
1471 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1472 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1474 print "create linked clone of mount point $opt ($volid)\n";
1475 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1478 push @$newvollist, $newvolid;
1479 $mp->{volume
} = $newvolid;
1481 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1484 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1485 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1488 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1489 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1490 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1492 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1493 die "Failed to move config to node '$target' - rename failed: $!\n"
1494 if !rename($conffile, $newconffile);
1499 # Unlock the source config in any case:
1500 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1504 # Now cleanup the config & disks:
1507 sleep 1; # some storages like rbd need to wait before release volume - really?
1509 foreach my $volid (@$newvollist) {
1510 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1513 die "clone failed: $err";
1519 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1520 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1524 __PACKAGE__-
>register_method({
1525 name
=> 'resize_vm',
1526 path
=> '{vmid}/resize',
1530 description
=> "Resize a container mount point.",
1532 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1535 additionalProperties
=> 0,
1537 node
=> get_standard_option
('pve-node'),
1538 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1541 description
=> "The disk you want to resize.",
1542 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1546 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1547 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.",
1551 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1559 description
=> "the task ID.",
1564 my $rpcenv = PVE
::RPCEnvironment
::get
();
1566 my $authuser = $rpcenv->get_user();
1568 my $node = extract_param
($param, 'node');
1570 my $vmid = extract_param
($param, 'vmid');
1572 my $digest = extract_param
($param, 'digest');
1574 my $sizestr = extract_param
($param, 'size');
1575 my $ext = ($sizestr =~ s/^\+//);
1576 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1577 die "invalid size string" if !defined($newsize);
1579 die "no options specified\n" if !scalar(keys %$param);
1581 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1583 my $storage_cfg = cfs_read_file
("storage.cfg");
1587 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1588 PVE
::LXC
::Config-
>check_lock($conf);
1590 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1592 my $running = PVE
::LXC
::check_running
($vmid);
1594 my $disk = $param->{disk
};
1595 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1596 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1598 my $volid = $mp->{volume
};
1600 my (undef, undef, $owner, undef, undef, undef, $format) =
1601 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1603 die "can't resize mount point owned by another container ($owner)"
1606 die "can't resize volume: $disk if snapshot exists\n"
1607 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1609 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1611 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1613 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1615 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1616 $newsize += $size if $ext;
1617 $newsize = int($newsize);
1619 die "unable to shrink disk size\n" if $newsize < $size;
1621 return if $size == $newsize;
1623 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1625 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1626 # we pass 0 here (parameter only makes sense for qemu)
1627 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1629 $mp->{size
} = $newsize;
1630 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1632 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1634 if ($format eq 'raw') {
1635 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1639 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1640 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1641 die "internal error: CT running but mount point not attached to a loop device"
1643 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1645 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1646 # to be visible to it in its namespace.
1647 # To not interfere with the rest of the system we unshare the current mount namespace,
1648 # mount over /tmp and then run resize2fs.
1650 # interestingly we don't need to e2fsck on mounted systems...
1651 my $quoted = PVE
::Tools
::shellquote
($path);
1652 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1654 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1656 warn "Failed to update the container's filesystem: $@\n" if $@;
1659 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1660 PVE
::Tools
::run_command
(['resize2fs', $path]);
1662 warn "Failed to update the container's filesystem: $@\n" if $@;
1667 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1670 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1673 __PACKAGE__-
>register_method({
1674 name
=> 'move_volume',
1675 path
=> '{vmid}/move_volume',
1679 description
=> "Move a rootfs-/mp-volume to a different storage",
1681 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1682 "and 'Datastore.AllocateSpace' permissions on the storage.",
1685 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1686 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1690 additionalProperties
=> 0,
1692 node
=> get_standard_option
('pve-node'),
1693 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1696 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1697 description
=> "Volume which will be moved.",
1699 storage
=> get_standard_option
('pve-storage-id', {
1700 description
=> "Target Storage.",
1701 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1705 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1711 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $vmid = extract_param
($param, 'vmid');
1729 my $storage = extract_param
($param, 'storage');
1731 my $mpkey = extract_param
($param, 'volume');
1733 my $lockname = 'disk';
1735 my ($mpdata, $old_volid);
1737 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1738 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1739 PVE
::LXC
::Config-
>check_lock($conf);
1741 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1743 if ($mpkey eq 'rootfs') {
1744 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1745 } elsif ($mpkey =~ m/mp\d+/) {
1746 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1748 die "Can't parse $mpkey\n";
1750 $old_volid = $mpdata->{volume
};
1752 die "you can't move a volume with snapshots and delete the source\n"
1753 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1755 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1757 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1762 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1764 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1765 my $storage_cfg = PVE
::Storage
::config
();
1770 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1771 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1772 $mpdata->{volume
} = $new_volid;
1774 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1775 my $digest = $conf->{digest
};
1776 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1777 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1779 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1781 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1783 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1787 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1788 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1794 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1795 if defined($new_volid);
1801 if ($param->{delete}) {
1803 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1804 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1810 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1815 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1818 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };