]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
migrate: improve target storage checks
[pve-container.git] / src / PVE / API2 / LXC.pm
1 package PVE::API2::LXC;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param run_command);
8 use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::RRD;
12 use PVE::DataCenterConfig;
13 use PVE::AccessControl;
14 use PVE::Firewall;
15 use PVE::Storage;
16 use PVE::RESTHandler;
17 use PVE::RPCEnvironment;
18 use PVE::ReplicationConfig;
19 use PVE::LXC;
20 use PVE::LXC::Create;
21 use PVE::LXC::Migrate;
22 use PVE::GuestHelpers;
23 use PVE::VZDump::Plugin;
24 use PVE::API2::LXC::Config;
25 use PVE::API2::LXC::Status;
26 use PVE::API2::LXC::Snapshot;
27 use PVE::JSONSchema qw(get_standard_option);
28 use base qw(PVE::RESTHandler);
29
30 BEGIN {
31 if (!$ENV{PVE_GENERATING_DOCS}) {
32 require PVE::HA::Env::PVE2;
33 import PVE::HA::Env::PVE2;
34 require PVE::HA::Config;
35 import PVE::HA::Config;
36 }
37 }
38
39 my $check_storage_access_migrate = sub {
40 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
41
42 PVE::Storage::storage_check_enabled($storecfg, $storage, $node);
43
44 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
45
46 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
47 die "storage '$storage' does not support CT rootdirs\n"
48 if !$scfg->{content}->{rootdir};
49 };
50
51 __PACKAGE__->register_method ({
52 subclass => "PVE::API2::LXC::Config",
53 path => '{vmid}/config',
54 });
55
56 __PACKAGE__->register_method ({
57 subclass => "PVE::API2::LXC::Status",
58 path => '{vmid}/status',
59 });
60
61 __PACKAGE__->register_method ({
62 subclass => "PVE::API2::LXC::Snapshot",
63 path => '{vmid}/snapshot',
64 });
65
66 __PACKAGE__->register_method ({
67 subclass => "PVE::API2::Firewall::CT",
68 path => '{vmid}/firewall',
69 });
70
71 __PACKAGE__->register_method({
72 name => 'vmlist',
73 path => '',
74 method => 'GET',
75 description => "LXC container index (per node).",
76 permissions => {
77 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
78 user => 'all',
79 },
80 proxyto => 'node',
81 protected => 1, # /proc files are only readable by root
82 parameters => {
83 additionalProperties => 0,
84 properties => {
85 node => get_standard_option('pve-node'),
86 },
87 },
88 returns => {
89 type => 'array',
90 items => {
91 type => "object",
92 properties => $PVE::LXC::vmstatus_return_properties,
93 },
94 links => [ { rel => 'child', href => "{vmid}" } ],
95 },
96 code => sub {
97 my ($param) = @_;
98
99 my $rpcenv = PVE::RPCEnvironment::get();
100 my $authuser = $rpcenv->get_user();
101
102 my $vmstatus = PVE::LXC::vmstatus();
103
104 my $res = [];
105 foreach my $vmid (keys %$vmstatus) {
106 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
107
108 my $data = $vmstatus->{$vmid};
109 push @$res, $data;
110 }
111
112 return $res;
113
114 }});
115
116 __PACKAGE__->register_method({
117 name => 'create_vm',
118 path => '',
119 method => 'POST',
120 description => "Create or restore a container.",
121 permissions => {
122 user => 'all', # check inside
123 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
124 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
125 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
126 },
127 protected => 1,
128 proxyto => 'node',
129 parameters => {
130 additionalProperties => 0,
131 properties => PVE::LXC::Config->json_config_properties({
132 node => get_standard_option('pve-node'),
133 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
134 ostemplate => {
135 description => "The OS template or backup file.",
136 type => 'string',
137 maxLength => 255,
138 completion => \&PVE::LXC::complete_os_templates,
139 },
140 password => {
141 optional => 1,
142 type => 'string',
143 description => "Sets root password inside container.",
144 minLength => 5,
145 },
146 storage => get_standard_option('pve-storage-id', {
147 description => "Default Storage.",
148 default => 'local',
149 optional => 1,
150 completion => \&PVE::Storage::complete_storage_enabled,
151 }),
152 force => {
153 optional => 1,
154 type => 'boolean',
155 description => "Allow to overwrite existing container.",
156 },
157 restore => {
158 optional => 1,
159 type => 'boolean',
160 description => "Mark this as restore task.",
161 },
162 unique => {
163 optional => 1,
164 type => 'boolean',
165 description => "Assign a unique random ethernet address.",
166 requires => 'restore',
167 },
168 pool => {
169 optional => 1,
170 type => 'string', format => 'pve-poolid',
171 description => "Add the VM to the specified pool.",
172 },
173 'ignore-unpack-errors' => {
174 optional => 1,
175 type => 'boolean',
176 description => "Ignore errors when extracting the template.",
177 },
178 'ssh-public-keys' => {
179 optional => 1,
180 type => 'string',
181 description => "Setup public SSH keys (one key per line, " .
182 "OpenSSH format).",
183 },
184 bwlimit => {
185 description => "Override I/O bandwidth limit (in KiB/s).",
186 optional => 1,
187 type => 'number',
188 minimum => '0',
189 default => 'restore limit from datacenter or storage config',
190 },
191 start => {
192 optional => 1,
193 type => 'boolean',
194 default => 0,
195 description => "Start the CT after its creation finished successfully.",
196 },
197 }),
198 },
199 returns => {
200 type => 'string',
201 },
202 code => sub {
203 my ($param) = @_;
204
205 PVE::Cluster::check_cfs_quorum();
206
207 my $rpcenv = PVE::RPCEnvironment::get();
208 my $authuser = $rpcenv->get_user();
209
210 my $node = extract_param($param, 'node');
211 my $vmid = extract_param($param, 'vmid');
212 my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors');
213 my $bwlimit = extract_param($param, 'bwlimit');
214 my $start_after_create = extract_param($param, 'start');
215
216 my $basecfg_fn = PVE::LXC::Config->config_file($vmid);
217 my $same_container_exists = -f $basecfg_fn;
218
219 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
220 my $unprivileged = extract_param($param, 'unprivileged');
221 my $restore = extract_param($param, 'restore');
222 my $unique = extract_param($param, 'unique');
223
224 # used to skip firewall config restore if user lacks permission
225 my $skip_fw_config_restore = 0;
226
227 if ($restore) {
228 # fixme: limit allowed parameters
229 }
230
231 my $force = extract_param($param, 'force');
232
233 if (!($same_container_exists && $restore && $force)) {
234 PVE::Cluster::check_vmid_unused($vmid);
235 } else {
236 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
237 my $conf = PVE::LXC::Config->load_config($vmid);
238 PVE::LXC::Config->check_protection($conf, "unable to restore CT $vmid");
239 }
240
241 my $password = extract_param($param, 'password');
242 my $ssh_keys = extract_param($param, 'ssh-public-keys');
243 PVE::Tools::validate_ssh_public_keys($ssh_keys) if defined($ssh_keys);
244
245 my $pool = extract_param($param, 'pool');
246 $rpcenv->check_pool_exist($pool) if defined($pool);
247
248 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
249 # OK
250 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
251 # OK
252 } elsif ($restore && $force && $same_container_exists &&
253 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
254 # OK: user has VM.Backup permissions, and want to restore an existing VM
255
256 # we don't want to restore a container-provided FW conf in this case
257 # since the user is lacking permission to configure the container's FW
258 $skip_fw_config_restore = 1;
259
260 # error out if a user tries to change from unprivileged to privileged
261 # explicit change is checked here, implicit is checked down below or happening in root-only paths
262 my $conf = PVE::LXC::Config->load_config($vmid);
263 if ($conf->{unprivileged} && defined($unprivileged) && !$unprivileged) {
264 raise_perm_exc("cannot change from unprivileged to privileged without VM.Allocate");
265 }
266 } else {
267 raise_perm_exc();
268 }
269
270 my $ostemplate = extract_param($param, 'ostemplate');
271 my $storage = extract_param($param, 'storage') // 'local';
272
273 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
274
275 my $storage_cfg = cfs_read_file("storage.cfg");
276
277 my $archive;
278 if ($ostemplate eq '-') {
279 die "pipe requires cli environment\n"
280 if $rpcenv->{type} ne 'cli';
281 die "pipe can only be used with restore tasks\n"
282 if !$restore;
283 $archive = '-';
284 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
285 } else {
286 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
287 $archive = $ostemplate;
288 }
289
290 my %used_storages;
291 my $check_and_activate_storage = sub {
292 my ($sid) = @_;
293
294 my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $sid, $node);
295
296 raise_param_exc({ storage => "storage '$sid' does not support container directories"})
297 if !$scfg->{content}->{rootdir};
298
299 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
300
301 PVE::Storage::activate_storage($storage_cfg, $sid);
302 $used_storages{$sid} = 1;
303 };
304
305 my $conf = {};
306
307 my $is_root = $authuser eq 'root@pam';
308
309 my $no_disk_param = {};
310 my $mp_param = {};
311 my $storage_only_mode = 1;
312 foreach my $opt (keys %$param) {
313 my $value = $param->{$opt};
314 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
315 # allow to use simple numbers (add default storage in that case)
316 if ($value =~ m/^\d+(\.\d+)?$/) {
317 $mp_param->{$opt} = "$storage:$value";
318 } else {
319 $mp_param->{$opt} = $value;
320 }
321 $storage_only_mode = 0;
322 } elsif ($opt =~ m/^unused\d+$/) {
323 warn "ignoring '$opt', cannot create/restore with unused volume\n";
324 delete $param->{$opt};
325 } else {
326 $no_disk_param->{$opt} = $value;
327 }
328 }
329
330 die "mount points configured, but 'rootfs' not set - aborting\n"
331 if !$storage_only_mode && !defined($mp_param->{rootfs});
332
333 # check storage access, activate storage
334 my $delayed_mp_param = {};
335 PVE::LXC::Config->foreach_volume($mp_param, sub {
336 my ($ms, $mountpoint) = @_;
337
338 my $volid = $mountpoint->{volume};
339 my $mp = $mountpoint->{mp};
340
341 if ($mountpoint->{type} ne 'volume') { # bind or device
342 die "Only root can pass arbitrary filesystem paths.\n"
343 if !$is_root;
344 } else {
345 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
346 &$check_and_activate_storage($sid);
347 }
348 });
349
350 # check/activate default storage
351 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs});
352
353 PVE::LXC::Config->update_pct_config($vmid, $conf, 0, $no_disk_param);
354
355 $conf->{unprivileged} = 1 if $unprivileged;
356
357 my $emsg = $restore ? "unable to restore CT $vmid -" : "unable to create CT $vmid -";
358
359 eval { PVE::LXC::Config->create_and_lock_config($vmid, $force) };
360 die "$emsg $@" if $@;
361
362 my $remove_lock = 1;
363
364 my $code = sub {
365 my $old_conf = PVE::LXC::Config->load_config($vmid);
366 my $was_template;
367
368 eval {
369 my $orig_mp_param; # only used if $restore
370 if ($restore) {
371 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
372 if ($archive ne '-') {
373 my $orig_conf;
374 print "recovering backed-up configuration from '$archive'\n";
375 ($orig_conf, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
376
377 $was_template = delete $orig_conf->{template};
378
379 # When we're root call 'restore_configuration' with restricted=0,
380 # causing it to restore the raw lxc entries, among which there may be
381 # 'lxc.idmap' entries. We need to make sure that the extracted contents
382 # of the container match up with the restored configuration afterwards:
383 $conf->{lxc} = $orig_conf->{lxc} if $is_root;
384
385 $conf->{unprivileged} = $orig_conf->{unprivileged}
386 if !defined($unprivileged) && defined($orig_conf->{unprivileged});
387
388 # implicit privileged change is checked here
389 if ($old_conf->{unprivileged} && !$conf->{unprivileged}) {
390 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
391 }
392 }
393 }
394 if ($storage_only_mode) {
395 if ($restore) {
396 if (!defined($orig_mp_param)) {
397 print "recovering backed-up configuration from '$archive'\n";
398 (undef, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
399 }
400 $mp_param = $orig_mp_param;
401 die "rootfs configuration could not be recovered, please check and specify manually!\n"
402 if !defined($mp_param->{rootfs});
403 PVE::LXC::Config->foreach_volume($mp_param, sub {
404 my ($ms, $mountpoint) = @_;
405 my $type = $mountpoint->{type};
406 if ($type eq 'volume') {
407 die "unable to detect disk size - please specify $ms (size)\n"
408 if !defined($mountpoint->{size});
409 my $disksize = $mountpoint->{size} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
410 delete $mountpoint->{size};
411 $mountpoint->{volume} = "$storage:$disksize";
412 $mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
413 } else {
414 my $type = $mountpoint->{type};
415 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
416 if ($ms eq 'rootfs');
417 die "restoring '$ms' to $type mount is only possible for root\n"
418 if !$is_root;
419
420 if ($mountpoint->{backup}) {
421 warn "WARNING - unsupported configuration!\n";
422 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
423 warn "mount point configuration will be restored after archive extraction!\n";
424 warn "contained files will be restored to wrong directory!\n";
425 }
426 delete $mp_param->{$ms}; # actually delay bind/dev mps
427 $delayed_mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
428 }
429 });
430 } else {
431 $mp_param->{rootfs} = "$storage:4"; # defaults to 4GB
432 }
433 }
434 };
435 die "$emsg $@" if $@;
436
437 # up until here we did not modify the container, besides the lock
438 $remove_lock = 0;
439
440 my $vollist = [];
441 eval {
442 $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf);
443
444 # we always have the 'create' lock so check for more than 1 entry
445 if (scalar(keys %$old_conf) > 1) {
446 # destroy old container volumes
447 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' });
448 }
449
450 eval {
451 my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1);
452 $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
453 print "restoring '$archive' now..\n"
454 if $restore && $archive ne '-';
455 PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
456
457 if ($restore) {
458 print "merging backed-up and given configuration..\n";
459 PVE::LXC::Create::restore_configuration($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
460 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
461 $lxc_setup->template_fixup($conf);
462 } else {
463 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS
464 PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection)
465 $lxc_setup->post_create_hook($password, $ssh_keys);
466 }
467 };
468 my $err = $@;
469 PVE::LXC::umount_all($vmid, $storage_cfg, $conf, $err ? 1 : 0);
470 PVE::Storage::deactivate_volumes($storage_cfg, PVE::LXC::Config->get_vm_volumes($conf));
471 die $err if $err;
472 # set some defaults
473 $conf->{hostname} ||= "CT$vmid";
474 $conf->{memory} ||= 512;
475 $conf->{swap} //= 512;
476 foreach my $mp (keys %$delayed_mp_param) {
477 $conf->{$mp} = $delayed_mp_param->{$mp};
478 }
479 # If the template flag was set, we try to convert again to template after restore
480 if ($was_template) {
481 print STDERR "Convert restored container to template...\n";
482 PVE::LXC::template_create($vmid, $conf);
483 $conf->{template} = 1;
484 }
485 PVE::LXC::Config->write_config($vmid, $conf);
486 };
487 if (my $err = $@) {
488 PVE::LXC::destroy_disks($storage_cfg, $vollist);
489 eval { PVE::LXC::Config->destroy_config($vmid) };
490 warn $@ if $@;
491 die "$emsg $err";
492 }
493 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
494
495 PVE::API2::LXC::Status->vm_start({ vmid => $vmid, node => $node })
496 if $start_after_create;
497 };
498
499 my $workername = $restore ? 'vzrestore' : 'vzcreate';
500 my $realcmd = sub {
501 eval {
502 PVE::LXC::Config->lock_config($vmid, $code);
503 };
504 if (my $err = $@) {
505 # if we aborted before changing the container, we must remove the create lock
506 if ($remove_lock) {
507 PVE::LXC::Config->remove_lock($vmid, 'create');
508 }
509 die $err;
510 }
511 };
512
513 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
514 }});
515
516 __PACKAGE__->register_method({
517 name => 'vmdiridx',
518 path => '{vmid}',
519 method => 'GET',
520 proxyto => 'node',
521 description => "Directory index",
522 permissions => {
523 user => 'all',
524 },
525 parameters => {
526 additionalProperties => 0,
527 properties => {
528 node => get_standard_option('pve-node'),
529 vmid => get_standard_option('pve-vmid'),
530 },
531 },
532 returns => {
533 type => 'array',
534 items => {
535 type => "object",
536 properties => {
537 subdir => { type => 'string' },
538 },
539 },
540 links => [ { rel => 'child', href => "{subdir}" } ],
541 },
542 code => sub {
543 my ($param) = @_;
544
545 # test if VM exists
546 my $conf = PVE::LXC::Config->load_config($param->{vmid});
547
548 my $res = [
549 { subdir => 'config' },
550 { subdir => 'pending' },
551 { subdir => 'status' },
552 { subdir => 'vncproxy' },
553 { subdir => 'termproxy' },
554 { subdir => 'vncwebsocket' },
555 { subdir => 'spiceproxy' },
556 { subdir => 'migrate' },
557 { subdir => 'clone' },
558 # { subdir => 'initlog' },
559 { subdir => 'rrd' },
560 { subdir => 'rrddata' },
561 { subdir => 'firewall' },
562 { subdir => 'snapshot' },
563 { subdir => 'resize' },
564 ];
565
566 return $res;
567 }});
568
569
570 __PACKAGE__->register_method({
571 name => 'rrd',
572 path => '{vmid}/rrd',
573 method => 'GET',
574 protected => 1, # fixme: can we avoid that?
575 permissions => {
576 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
577 },
578 description => "Read VM RRD statistics (returns PNG)",
579 parameters => {
580 additionalProperties => 0,
581 properties => {
582 node => get_standard_option('pve-node'),
583 vmid => get_standard_option('pve-vmid'),
584 timeframe => {
585 description => "Specify the time frame you are interested in.",
586 type => 'string',
587 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
588 },
589 ds => {
590 description => "The list of datasources you want to display.",
591 type => 'string', format => 'pve-configid-list',
592 },
593 cf => {
594 description => "The RRD consolidation function",
595 type => 'string',
596 enum => [ 'AVERAGE', 'MAX' ],
597 optional => 1,
598 },
599 },
600 },
601 returns => {
602 type => "object",
603 properties => {
604 filename => { type => 'string' },
605 },
606 },
607 code => sub {
608 my ($param) = @_;
609
610 return PVE::RRD::create_rrd_graph(
611 "pve2-vm/$param->{vmid}", $param->{timeframe},
612 $param->{ds}, $param->{cf});
613
614 }});
615
616 __PACKAGE__->register_method({
617 name => 'rrddata',
618 path => '{vmid}/rrddata',
619 method => 'GET',
620 protected => 1, # fixme: can we avoid that?
621 permissions => {
622 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
623 },
624 description => "Read VM RRD statistics",
625 parameters => {
626 additionalProperties => 0,
627 properties => {
628 node => get_standard_option('pve-node'),
629 vmid => get_standard_option('pve-vmid'),
630 timeframe => {
631 description => "Specify the time frame you are interested in.",
632 type => 'string',
633 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
634 },
635 cf => {
636 description => "The RRD consolidation function",
637 type => 'string',
638 enum => [ 'AVERAGE', 'MAX' ],
639 optional => 1,
640 },
641 },
642 },
643 returns => {
644 type => "array",
645 items => {
646 type => "object",
647 properties => {},
648 },
649 },
650 code => sub {
651 my ($param) = @_;
652
653 return PVE::RRD::create_rrd_data(
654 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
655 }});
656
657 __PACKAGE__->register_method({
658 name => 'destroy_vm',
659 path => '{vmid}',
660 method => 'DELETE',
661 protected => 1,
662 proxyto => 'node',
663 description => "Destroy the container (also delete all uses files).",
664 permissions => {
665 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
666 },
667 parameters => {
668 additionalProperties => 0,
669 properties => {
670 node => get_standard_option('pve-node'),
671 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
672 force => {
673 type => 'boolean',
674 description => "Force destroy, even if running.",
675 default => 0,
676 optional => 1,
677 },
678 purge => {
679 type => 'boolean',
680 description => "Remove container from all related configurations."
681 ." For example, backup jobs, replication jobs or HA."
682 ." Related ACLs and Firewall entries will *always* be removed.",
683 default => 0,
684 optional => 1,
685 },
686 'destroy-unreferenced-disks' => {
687 type => 'boolean',
688 description => "If set, destroy additionally all disks with the VMID from all"
689 ." enabled storages which are not referenced in the config.",
690 optional => 1,
691 },
692 },
693 },
694 returns => {
695 type => 'string',
696 },
697 code => sub {
698 my ($param) = @_;
699
700 my $rpcenv = PVE::RPCEnvironment::get();
701 my $authuser = $rpcenv->get_user();
702 my $vmid = $param->{vmid};
703
704 # test if container exists
705
706 my $conf = PVE::LXC::Config->load_config($vmid);
707 my $early_checks = sub {
708 my ($conf) = @_;
709 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid");
710 PVE::LXC::Config->check_lock($conf);
711
712 my $ha_managed = PVE::HA::Config::service_is_configured("ct:$vmid");
713
714 if (!$param->{purge}) {
715 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
716 if $ha_managed;
717
718 # do not allow destroy if there are replication jobs without purge
719 my $repl_conf = PVE::ReplicationConfig->new();
720 $repl_conf->check_for_existing_jobs($vmid);
721 }
722
723 return $ha_managed;
724 };
725
726 $early_checks->($conf);
727
728 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
729 die $running_error_msg if !$param->{force} && PVE::LXC::check_running($vmid); # check early
730
731 my $code = sub {
732 # reload config after lock
733 $conf = PVE::LXC::Config->load_config($vmid);
734 my $ha_managed = $early_checks->($conf);
735
736 if (PVE::LXC::check_running($vmid)) {
737 die $running_error_msg if !$param->{force};
738 warn "forced to stop CT $vmid before destroying!\n";
739 if (!$ha_managed) {
740 PVE::LXC::vm_stop($vmid, 1);
741 } else {
742 run_command(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
743 }
744 }
745
746 my $storage_cfg = cfs_read_file("storage.cfg");
747 PVE::LXC::destroy_lxc_container(
748 $storage_cfg,
749 $vmid,
750 $conf,
751 { lock => 'destroyed' },
752 $param->{'destroy-unreferenced-disks'},
753 );
754
755 PVE::AccessControl::remove_vm_access($vmid);
756 PVE::Firewall::remove_vmfw_conf($vmid);
757 if ($param->{purge}) {
758 print "purging CT $vmid from related configurations..\n";
759 PVE::ReplicationConfig::remove_vmid_jobs($vmid);
760 PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid);
761
762 if ($ha_managed) {
763 PVE::HA::Config::delete_service_from_config("ct:$vmid");
764 print "NOTE: removed CT $vmid from HA resource configuration.\n";
765 }
766 }
767
768 # only now remove the zombie config, else we can have reuse race
769 PVE::LXC::Config->destroy_config($vmid);
770 };
771
772 my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); };
773
774 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
775 }});
776
777 my $sslcert;
778
779 __PACKAGE__->register_method ({
780 name => 'vncproxy',
781 path => '{vmid}/vncproxy',
782 method => 'POST',
783 protected => 1,
784 permissions => {
785 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
786 },
787 description => "Creates a TCP VNC proxy connections.",
788 parameters => {
789 additionalProperties => 0,
790 properties => {
791 node => get_standard_option('pve-node'),
792 vmid => get_standard_option('pve-vmid'),
793 websocket => {
794 optional => 1,
795 type => 'boolean',
796 description => "use websocket instead of standard VNC.",
797 },
798 width => {
799 optional => 1,
800 description => "sets the width of the console in pixels.",
801 type => 'integer',
802 minimum => 16,
803 maximum => 4096,
804 },
805 height => {
806 optional => 1,
807 description => "sets the height of the console in pixels.",
808 type => 'integer',
809 minimum => 16,
810 maximum => 2160,
811 },
812 },
813 },
814 returns => {
815 additionalProperties => 0,
816 properties => {
817 user => { type => 'string' },
818 ticket => { type => 'string' },
819 cert => { type => 'string' },
820 port => { type => 'integer' },
821 upid => { type => 'string' },
822 },
823 },
824 code => sub {
825 my ($param) = @_;
826
827 my $rpcenv = PVE::RPCEnvironment::get();
828
829 my $authuser = $rpcenv->get_user();
830
831 my $vmid = $param->{vmid};
832 my $node = $param->{node};
833
834 my $authpath = "/vms/$vmid";
835
836 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
837
838 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
839 if !$sslcert;
840
841 my ($remip, $family);
842
843 if ($node ne PVE::INotify::nodename()) {
844 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
845 } else {
846 $family = PVE::Tools::get_host_address_family($node);
847 }
848
849 my $port = PVE::Tools::next_vnc_port($family);
850
851 # NOTE: vncterm VNC traffic is already TLS encrypted,
852 # so we select the fastest chipher here (or 'none'?)
853 my $remcmd = $remip ?
854 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
855
856 my $conf = PVE::LXC::Config->load_config($vmid, $node);
857 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
858
859 my $shcmd = [ '/usr/bin/dtach', '-A',
860 "/var/run/dtach/vzctlconsole$vmid",
861 '-r', 'winch', '-z', @$concmd];
862
863 my $realcmd = sub {
864 my $upid = shift;
865
866 syslog ('info', "starting lxc vnc proxy $upid\n");
867
868 my $timeout = 10;
869
870 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
871 '-timeout', $timeout, '-authpath', $authpath,
872 '-perm', 'VM.Console'];
873
874 if ($param->{width}) {
875 push @$cmd, '-width', $param->{width};
876 }
877
878 if ($param->{height}) {
879 push @$cmd, '-height', $param->{height};
880 }
881
882 if ($param->{websocket}) {
883 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
884 push @$cmd, '-notls', '-listen', 'localhost';
885 }
886
887 push @$cmd, '-c', @$remcmd, @$shcmd;
888
889 run_command($cmd, keeplocale => 1);
890
891 return;
892 };
893
894 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
895
896 PVE::Tools::wait_for_vnc_port($port);
897
898 return {
899 user => $authuser,
900 ticket => $ticket,
901 port => $port,
902 upid => $upid,
903 cert => $sslcert,
904 };
905 }});
906
907 __PACKAGE__->register_method ({
908 name => 'termproxy',
909 path => '{vmid}/termproxy',
910 method => 'POST',
911 protected => 1,
912 permissions => {
913 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
914 },
915 description => "Creates a TCP proxy connection.",
916 parameters => {
917 additionalProperties => 0,
918 properties => {
919 node => get_standard_option('pve-node'),
920 vmid => get_standard_option('pve-vmid'),
921 },
922 },
923 returns => {
924 additionalProperties => 0,
925 properties => {
926 user => { type => 'string' },
927 ticket => { type => 'string' },
928 port => { type => 'integer' },
929 upid => { type => 'string' },
930 },
931 },
932 code => sub {
933 my ($param) = @_;
934
935 my $rpcenv = PVE::RPCEnvironment::get();
936
937 my $authuser = $rpcenv->get_user();
938
939 my $vmid = $param->{vmid};
940 my $node = $param->{node};
941
942 my $authpath = "/vms/$vmid";
943
944 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
945
946 my ($remip, $family);
947
948 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
949 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
950 } else {
951 $family = PVE::Tools::get_host_address_family($node);
952 }
953
954 my $port = PVE::Tools::next_vnc_port($family);
955
956 my $remcmd = $remip ?
957 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
958
959 my $conf = PVE::LXC::Config->load_config($vmid, $node);
960 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
961
962 my $shcmd = [ '/usr/bin/dtach', '-A',
963 "/var/run/dtach/vzctlconsole$vmid",
964 '-r', 'winch', '-z', @$concmd];
965
966 my $realcmd = sub {
967 my $upid = shift;
968
969 syslog ('info', "starting lxc termproxy $upid\n");
970
971 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
972 '--perm', 'VM.Console', '--'];
973 push @$cmd, @$remcmd, @$shcmd;
974
975 PVE::Tools::run_command($cmd);
976 };
977
978 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
979
980 PVE::Tools::wait_for_vnc_port($port);
981
982 return {
983 user => $authuser,
984 ticket => $ticket,
985 port => $port,
986 upid => $upid,
987 };
988 }});
989
990 __PACKAGE__->register_method({
991 name => 'vncwebsocket',
992 path => '{vmid}/vncwebsocket',
993 method => 'GET',
994 permissions => {
995 description => "You also need to pass a valid ticket (vncticket).",
996 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
997 },
998 description => "Opens a weksocket for VNC traffic.",
999 parameters => {
1000 additionalProperties => 0,
1001 properties => {
1002 node => get_standard_option('pve-node'),
1003 vmid => get_standard_option('pve-vmid'),
1004 vncticket => {
1005 description => "Ticket from previous call to vncproxy.",
1006 type => 'string',
1007 maxLength => 512,
1008 },
1009 port => {
1010 description => "Port number returned by previous vncproxy call.",
1011 type => 'integer',
1012 minimum => 5900,
1013 maximum => 5999,
1014 },
1015 },
1016 },
1017 returns => {
1018 type => "object",
1019 properties => {
1020 port => { type => 'string' },
1021 },
1022 },
1023 code => sub {
1024 my ($param) = @_;
1025
1026 my $rpcenv = PVE::RPCEnvironment::get();
1027
1028 my $authuser = $rpcenv->get_user();
1029
1030 my $authpath = "/vms/$param->{vmid}";
1031
1032 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1033
1034 my $port = $param->{port};
1035
1036 return { port => $port };
1037 }});
1038
1039 __PACKAGE__->register_method ({
1040 name => 'spiceproxy',
1041 path => '{vmid}/spiceproxy',
1042 method => 'POST',
1043 protected => 1,
1044 proxyto => 'node',
1045 permissions => {
1046 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1047 },
1048 description => "Returns a SPICE configuration to connect to the CT.",
1049 parameters => {
1050 additionalProperties => 0,
1051 properties => {
1052 node => get_standard_option('pve-node'),
1053 vmid => get_standard_option('pve-vmid'),
1054 proxy => get_standard_option('spice-proxy', { optional => 1 }),
1055 },
1056 },
1057 returns => get_standard_option('remote-viewer-config'),
1058 code => sub {
1059 my ($param) = @_;
1060
1061 my $vmid = $param->{vmid};
1062 my $node = $param->{node};
1063 my $proxy = $param->{proxy};
1064
1065 my $authpath = "/vms/$vmid";
1066 my $permissions = 'VM.Console';
1067
1068 my $conf = PVE::LXC::Config->load_config($vmid);
1069
1070 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1071
1072 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
1073
1074 my $shcmd = ['/usr/bin/dtach', '-A',
1075 "/var/run/dtach/vzctlconsole$vmid",
1076 '-r', 'winch', '-z', @$concmd];
1077
1078 my $title = "CT $vmid";
1079
1080 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1081 }});
1082
1083
1084 __PACKAGE__->register_method({
1085 name => 'migrate_vm',
1086 path => '{vmid}/migrate',
1087 method => 'POST',
1088 protected => 1,
1089 proxyto => 'node',
1090 description => "Migrate the container to another node. Creates a new migration task.",
1091 permissions => {
1092 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1093 },
1094 parameters => {
1095 additionalProperties => 0,
1096 properties => {
1097 node => get_standard_option('pve-node'),
1098 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1099 target => get_standard_option('pve-node', {
1100 description => "Target node.",
1101 completion => \&PVE::Cluster::complete_migration_target,
1102 }),
1103 'target-storage' => get_standard_option('pve-targetstorage'),
1104 online => {
1105 type => 'boolean',
1106 description => "Use online/live migration.",
1107 optional => 1,
1108 },
1109 restart => {
1110 type => 'boolean',
1111 description => "Use restart migration",
1112 optional => 1,
1113 },
1114 timeout => {
1115 type => 'integer',
1116 description => "Timeout in seconds for shutdown for restart migration",
1117 optional => 1,
1118 default => 180,
1119 },
1120 bwlimit => {
1121 description => "Override I/O bandwidth limit (in KiB/s).",
1122 optional => 1,
1123 type => 'number',
1124 minimum => '0',
1125 default => 'migrate limit from datacenter or storage config',
1126 },
1127 },
1128 },
1129 returns => {
1130 type => 'string',
1131 description => "the task ID.",
1132 },
1133 code => sub {
1134 my ($param) = @_;
1135
1136 my $rpcenv = PVE::RPCEnvironment::get();
1137
1138 my $authuser = $rpcenv->get_user();
1139
1140 my $target = extract_param($param, 'target');
1141
1142 my $localnode = PVE::INotify::nodename();
1143 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1144
1145 PVE::Cluster::check_cfs_quorum();
1146
1147 PVE::Cluster::check_node_exists($target);
1148
1149 my $targetip = PVE::Cluster::remote_node_ip($target);
1150
1151 my $vmid = extract_param($param, 'vmid');
1152
1153 # test if VM exists
1154 PVE::LXC::Config->load_config($vmid);
1155
1156 # try to detect errors early
1157 if (PVE::LXC::check_running($vmid)) {
1158 die "can't migrate running container without --online or --restart\n"
1159 if !$param->{online} && !$param->{restart};
1160 }
1161
1162 if (my $targetstorage = delete $param->{'target-storage'}) {
1163 my $storecfg = PVE::Storage::config();
1164 my $storagemap = eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };
1165 raise_param_exc({ 'target-storage' => "failed to parse storage map: $@" })
1166 if $@;
1167
1168 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1169 if !defined($storagemap->{identity});
1170
1171 foreach my $target_sid (values %{$storagemap->{entries}}) {
1172 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1173 }
1174
1175 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1176 if $storagemap->{default};
1177
1178 $param->{storagemap} = $storagemap;
1179 }
1180
1181 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1182
1183 my $hacmd = sub {
1184 my $upid = shift;
1185
1186 my $service = "ct:$vmid";
1187
1188 my $cmd = ['ha-manager', 'migrate', $service, $target];
1189
1190 print "Requesting HA migration for CT $vmid to node $target\n";
1191
1192 PVE::Tools::run_command($cmd);
1193
1194 return;
1195 };
1196
1197 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1198
1199 } else {
1200
1201 my $realcmd = sub {
1202 PVE::LXC::Migrate->migrate($target, $targetip, $vmid, $param);
1203 };
1204
1205 my $worker = sub {
1206 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
1207 };
1208
1209 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1210 }
1211 }});
1212
1213 __PACKAGE__->register_method({
1214 name => 'vm_feature',
1215 path => '{vmid}/feature',
1216 method => 'GET',
1217 proxyto => 'node',
1218 protected => 1,
1219 description => "Check if feature for virtual machine is available.",
1220 permissions => {
1221 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1222 },
1223 parameters => {
1224 additionalProperties => 0,
1225 properties => {
1226 node => get_standard_option('pve-node'),
1227 vmid => get_standard_option('pve-vmid'),
1228 feature => {
1229 description => "Feature to check.",
1230 type => 'string',
1231 enum => [ 'snapshot', 'clone', 'copy' ],
1232 },
1233 snapname => get_standard_option('pve-snapshot-name', {
1234 optional => 1,
1235 }),
1236 },
1237 },
1238 returns => {
1239 type => "object",
1240 properties => {
1241 hasFeature => { type => 'boolean' },
1242 #nodes => {
1243 #type => 'array',
1244 #items => { type => 'string' },
1245 #}
1246 },
1247 },
1248 code => sub {
1249 my ($param) = @_;
1250
1251 my $node = extract_param($param, 'node');
1252
1253 my $vmid = extract_param($param, 'vmid');
1254
1255 my $snapname = extract_param($param, 'snapname');
1256
1257 my $feature = extract_param($param, 'feature');
1258
1259 my $conf = PVE::LXC::Config->load_config($vmid);
1260
1261 if($snapname){
1262 my $snap = $conf->{snapshots}->{$snapname};
1263 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1264 $conf = $snap;
1265 }
1266 my $storage_cfg = PVE::Storage::config();
1267 #Maybe include later
1268 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1269 my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname);
1270
1271 return {
1272 hasFeature => $hasFeature,
1273 #nodes => [ keys %$nodelist ],
1274 };
1275 }});
1276
1277 __PACKAGE__->register_method({
1278 name => 'template',
1279 path => '{vmid}/template',
1280 method => 'POST',
1281 protected => 1,
1282 proxyto => 'node',
1283 description => "Create a Template.",
1284 permissions => {
1285 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1286 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1287 },
1288 parameters => {
1289 additionalProperties => 0,
1290 properties => {
1291 node => get_standard_option('pve-node'),
1292 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
1293 },
1294 },
1295 returns => { type => 'null'},
1296 code => sub {
1297 my ($param) = @_;
1298
1299 my $rpcenv = PVE::RPCEnvironment::get();
1300
1301 my $authuser = $rpcenv->get_user();
1302
1303 my $node = extract_param($param, 'node');
1304
1305 my $vmid = extract_param($param, 'vmid');
1306
1307 my $updatefn = sub {
1308
1309 my $conf = PVE::LXC::Config->load_config($vmid);
1310 PVE::LXC::Config->check_lock($conf);
1311
1312 die "unable to create template, because CT contains snapshots\n"
1313 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1314
1315 die "you can't convert a template to a template\n"
1316 if PVE::LXC::Config->is_template($conf);
1317
1318 die "you can't convert a CT to template if the CT is running\n"
1319 if PVE::LXC::check_running($vmid);
1320
1321 my $realcmd = sub {
1322 PVE::LXC::template_create($vmid, $conf);
1323
1324 $conf->{template} = 1;
1325
1326 PVE::LXC::Config->write_config($vmid, $conf);
1327 # and remove lxc config
1328 PVE::LXC::update_lxc_config($vmid, $conf);
1329 };
1330
1331 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1332 };
1333
1334 PVE::LXC::Config->lock_config($vmid, $updatefn);
1335
1336 return undef;
1337 }});
1338
1339 __PACKAGE__->register_method({
1340 name => 'clone_vm',
1341 path => '{vmid}/clone',
1342 method => 'POST',
1343 protected => 1,
1344 proxyto => 'node',
1345 description => "Create a container clone/copy",
1346 permissions => {
1347 description => "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1348 "and 'VM.Allocate' permissions " .
1349 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1350 "'Datastore.AllocateSpace' on any used storage.",
1351 check =>
1352 [ 'and',
1353 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1354 [ 'or',
1355 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1356 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1357 ],
1358 ]
1359 },
1360 parameters => {
1361 additionalProperties => 0,
1362 properties => {
1363 node => get_standard_option('pve-node'),
1364 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1365 newid => get_standard_option('pve-vmid', {
1366 completion => \&PVE::Cluster::complete_next_vmid,
1367 description => 'VMID for the clone.' }),
1368 hostname => {
1369 optional => 1,
1370 type => 'string', format => 'dns-name',
1371 description => "Set a hostname for the new CT.",
1372 },
1373 description => {
1374 optional => 1,
1375 type => 'string',
1376 description => "Description for the new CT.",
1377 },
1378 pool => {
1379 optional => 1,
1380 type => 'string', format => 'pve-poolid',
1381 description => "Add the new CT to the specified pool.",
1382 },
1383 snapname => get_standard_option('pve-snapshot-name', {
1384 optional => 1,
1385 }),
1386 storage => get_standard_option('pve-storage-id', {
1387 description => "Target storage for full clone.",
1388 optional => 1,
1389 }),
1390 full => {
1391 optional => 1,
1392 type => 'boolean',
1393 description => "Create a full copy of all disks. This is always done when " .
1394 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1395 },
1396 target => get_standard_option('pve-node', {
1397 description => "Target node. Only allowed if the original VM is on shared storage.",
1398 optional => 1,
1399 }),
1400 bwlimit => {
1401 description => "Override I/O bandwidth limit (in KiB/s).",
1402 optional => 1,
1403 type => 'number',
1404 minimum => '0',
1405 default => 'clone limit from datacenter or storage config',
1406 },
1407 },
1408 },
1409 returns => {
1410 type => 'string',
1411 },
1412 code => sub {
1413 my ($param) = @_;
1414
1415 my $rpcenv = PVE::RPCEnvironment::get();
1416 my $authuser = $rpcenv->get_user();
1417
1418 my $node = extract_param($param, 'node');
1419 my $vmid = extract_param($param, 'vmid');
1420 my $newid = extract_param($param, 'newid');
1421 my $pool = extract_param($param, 'pool');
1422 if (defined($pool)) {
1423 $rpcenv->check_pool_exist($pool);
1424 }
1425 my $snapname = extract_param($param, 'snapname');
1426 my $storage = extract_param($param, 'storage');
1427 my $target = extract_param($param, 'target');
1428 my $localnode = PVE::INotify::nodename();
1429
1430 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1431
1432 PVE::Cluster::check_node_exists($target) if $target;
1433
1434 my $storecfg = PVE::Storage::config();
1435
1436 if ($storage) {
1437 # check if storage is enabled on local node
1438 PVE::Storage::storage_check_enabled($storecfg, $storage);
1439 if ($target) {
1440 # check if storage is available on target node
1441 PVE::Storage::storage_check_enabled($storecfg, $storage, $target);
1442 # clone only works if target storage is shared
1443 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
1444 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
1445 }
1446 }
1447
1448 PVE::Cluster::check_cfs_quorum();
1449
1450 my $newconf = {};
1451 my $mountpoints = {};
1452 my $fullclone = {};
1453 my $vollist = [];
1454 my $running;
1455
1456 PVE::LXC::Config->create_and_lock_config($newid, 0);
1457 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
1458
1459 my $lock_and_reload = sub {
1460 my ($vmid, $code) = @_;
1461 return PVE::LXC::Config->lock_config($vmid, sub {
1462 my $conf = PVE::LXC::Config->load_config($vmid);
1463 die "Lost 'create' config lock, aborting.\n"
1464 if !PVE::LXC::Config->has_lock($conf, 'create');
1465
1466 return $code->($conf);
1467 });
1468 };
1469
1470 my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk');
1471
1472 $running = PVE::LXC::check_running($vmid) || 0;
1473
1474 my $full = extract_param($param, 'full');
1475 if (!defined($full)) {
1476 $full = !PVE::LXC::Config->is_template($src_conf);
1477 }
1478
1479 eval {
1480 die "parameter 'storage' not allowed for linked clones\n"
1481 if defined($storage) && !$full;
1482
1483 die "snapshot '$snapname' does not exist\n"
1484 if $snapname && !defined($src_conf->{snapshots}->{$snapname});
1485
1486 my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf;
1487
1488 my $sharedvm = 1;
1489 for my $opt (sort keys %$src_conf) {
1490 next if $opt =~ m/^unused\d+$/;
1491
1492 my $value = $src_conf->{$opt};
1493
1494 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1495 my $mp = PVE::LXC::Config->parse_volume($opt, $value);
1496
1497 if ($mp->{type} eq 'volume') {
1498 my $volid = $mp->{volume};
1499
1500 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
1501 $sid = $storage if defined($storage);
1502 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
1503 if (!$scfg->{shared}) {
1504 $sharedvm = 0;
1505 warn "found non-shared volume: $volid\n" if $target;
1506 }
1507
1508 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1509
1510 if ($full) {
1511 die "Cannot do full clones on a running container without snapshots\n"
1512 if $running && !defined($snapname);
1513 $fullclone->{$opt} = 1;
1514 } else {
1515 # not full means clone instead of copy
1516 die "Linked clone feature for '$volid' is not available\n"
1517 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1518 }
1519
1520 $mountpoints->{$opt} = $mp;
1521 push @$vollist, $volid;
1522
1523 } else {
1524 # TODO: allow bind mounts?
1525 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1526 }
1527 } elsif ($opt =~ m/^net(\d+)$/) {
1528 # always change MAC! address
1529 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1530 my $net = PVE::LXC::Config->parse_lxc_network($value);
1531 $net->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1532 $newconf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1533 } else {
1534 # copy everything else
1535 $newconf->{$opt} = $value;
1536 }
1537 }
1538 die "can't clone CT to node '$target' (CT uses local storage)\n"
1539 if $target && !$sharedvm;
1540
1541 # Replace the 'disk' lock with a 'create' lock.
1542 $newconf->{lock} = 'create';
1543
1544 # delete all snapshot related config options
1545 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1546
1547 delete $newconf->{pending};
1548 delete $newconf->{template};
1549
1550 $newconf->{hostname} = $param->{hostname} if $param->{hostname};
1551 $newconf->{description} = $param->{description} if $param->{description};
1552
1553 $lock_and_reload->($newid, sub {
1554 PVE::LXC::Config->write_config($newid, $newconf);
1555 });
1556 };
1557 if (my $err = $@) {
1558 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1559 warn "Failed to remove source CT config lock - $@\n" if $@;
1560
1561 eval {
1562 $lock_and_reload->($newid, sub {
1563 PVE::LXC::Config->destroy_config($newid);
1564 PVE::Firewall::remove_vmfw_conf($newid);
1565 });
1566 };
1567 warn "Failed to remove target CT config - $@\n" if $@;
1568
1569 die $err;
1570 }
1571
1572 my $update_conf = sub {
1573 my ($key, $value) = @_;
1574 return $lock_and_reload->($newid, sub {
1575 my $conf = shift;
1576 $conf->{$key} = $value;
1577 PVE::LXC::Config->write_config($newid, $conf);
1578 });
1579 };
1580
1581 my $realcmd = sub {
1582 my ($upid) = @_;
1583
1584 my $newvollist = [];
1585
1586 my $verify_running = PVE::LXC::check_running($vmid) || 0;
1587 die "unexpected state change\n" if $verify_running != $running;
1588
1589 eval {
1590 local $SIG{INT} =
1591 local $SIG{TERM} =
1592 local $SIG{QUIT} =
1593 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
1594
1595 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
1596 my $bwlimit = extract_param($param, 'bwlimit');
1597
1598 foreach my $opt (keys %$mountpoints) {
1599 my $mp = $mountpoints->{$opt};
1600 my $volid = $mp->{volume};
1601
1602 my $newvolid;
1603 if ($fullclone->{$opt}) {
1604 print "create full clone of mountpoint $opt ($volid)\n";
1605 my $source_storage = PVE::Storage::parse_volume_id($volid);
1606 my $target_storage = $storage // $source_storage;
1607 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', [$source_storage, $target_storage], $bwlimit);
1608 $newvolid = PVE::LXC::copy_volume($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1609 } else {
1610 print "create linked clone of mount point $opt ($volid)\n";
1611 $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
1612 }
1613
1614 push @$newvollist, $newvolid;
1615 $mp->{volume} = $newvolid;
1616
1617 $update_conf->($opt, PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'));
1618 }
1619
1620 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
1621
1622 $lock_and_reload->($newid, sub {
1623 my $conf = shift;
1624 my $rootdir = PVE::LXC::mount_all($newid, $storecfg, $conf, 1);
1625 eval {
1626 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1627 $lxc_setup->post_clone_hook($conf);
1628 };
1629 my $err = $@;
1630 eval { PVE::LXC::umount_all($newid, $storecfg, $conf, 1); };
1631 if ($err) {
1632 warn "$@\n" if $@;
1633 die $err;
1634 } else {
1635 die $@ if $@;
1636 }
1637 });
1638 };
1639 my $err = $@;
1640 # Unlock the source config in any case:
1641 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1642 warn $@ if $@;
1643
1644 if ($err) {
1645 # Now cleanup the config & disks:
1646 sleep 1; # some storages like rbd need to wait before release volume - really?
1647
1648 foreach my $volid (@$newvollist) {
1649 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
1650 warn $@ if $@;
1651 }
1652
1653 eval {
1654 $lock_and_reload->($newid, sub {
1655 PVE::LXC::Config->destroy_config($newid);
1656 PVE::Firewall::remove_vmfw_conf($newid);
1657 });
1658 };
1659 warn "Failed to remove target CT config - $@\n" if $@;
1660
1661 die "clone failed: $err";
1662 }
1663
1664 $lock_and_reload->($newid, sub {
1665 PVE::LXC::Config->remove_lock($newid, 'create');
1666
1667 if ($target) {
1668 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1669 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
1670 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
1671
1672 PVE::LXC::Config->move_config_to_node($newid, $target);
1673 }
1674 });
1675
1676 return;
1677 };
1678
1679 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1680 }});
1681
1682
1683 __PACKAGE__->register_method({
1684 name => 'resize_vm',
1685 path => '{vmid}/resize',
1686 method => 'PUT',
1687 protected => 1,
1688 proxyto => 'node',
1689 description => "Resize a container mount point.",
1690 permissions => {
1691 check => ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any => 1],
1692 },
1693 parameters => {
1694 additionalProperties => 0,
1695 properties => {
1696 node => get_standard_option('pve-node'),
1697 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1698 disk => {
1699 type => 'string',
1700 description => "The disk you want to resize.",
1701 enum => [PVE::LXC::Config->valid_volume_keys()],
1702 },
1703 size => {
1704 type => 'string',
1705 pattern => '\+?\d+(\.\d+)?[KMGT]?',
1706 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.",
1707 },
1708 digest => {
1709 type => 'string',
1710 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1711 maxLength => 40,
1712 optional => 1,
1713 }
1714 },
1715 },
1716 returns => {
1717 type => 'string',
1718 description => "the task ID.",
1719 },
1720 code => sub {
1721 my ($param) = @_;
1722
1723 my $rpcenv = PVE::RPCEnvironment::get();
1724
1725 my $authuser = $rpcenv->get_user();
1726
1727 my $node = extract_param($param, 'node');
1728
1729 my $vmid = extract_param($param, 'vmid');
1730
1731 my $digest = extract_param($param, 'digest');
1732
1733 my $sizestr = extract_param($param, 'size');
1734 my $ext = ($sizestr =~ s/^\+//);
1735 my $newsize = PVE::JSONSchema::parse_size($sizestr);
1736 die "invalid size string" if !defined($newsize);
1737
1738 die "no options specified\n" if !scalar(keys %$param);
1739
1740 my $storage_cfg = cfs_read_file("storage.cfg");
1741
1742 my $code = sub {
1743
1744 my $conf = PVE::LXC::Config->load_config($vmid);
1745 PVE::LXC::Config->check_lock($conf);
1746
1747 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged});
1748
1749 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1750
1751 my $running = PVE::LXC::check_running($vmid);
1752
1753 my $disk = $param->{disk};
1754 my $mp = PVE::LXC::Config->parse_volume($disk, $conf->{$disk});
1755
1756 my $volid = $mp->{volume};
1757
1758 my (undef, undef, $owner, undef, undef, undef, $format) =
1759 PVE::Storage::parse_volname($storage_cfg, $volid);
1760
1761 die "can't resize mount point owned by another container ($owner)"
1762 if $vmid != $owner;
1763
1764 die "can't resize volume: $disk if snapshot exists\n"
1765 if %{$conf->{snapshots}} && $format eq 'qcow2';
1766
1767 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
1768
1769 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1770
1771 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
1772
1773 my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5);
1774
1775 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1776
1777 $newsize += $size if $ext;
1778 $newsize = int($newsize);
1779
1780 die "unable to shrink disk size\n" if $newsize < $size;
1781
1782 die "disk is already at specified size\n" if $size == $newsize;
1783
1784 PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1785 my $realcmd = sub {
1786 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1787 # we pass 0 here (parameter only makes sense for qemu)
1788 PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0);
1789
1790 $mp->{size} = $newsize;
1791 $conf->{$disk} = PVE::LXC::Config->print_ct_mountpoint($mp, $disk eq 'rootfs');
1792
1793 PVE::LXC::Config->write_config($vmid, $conf);
1794
1795 if ($format eq 'raw') {
1796 # we need to ensure that the volume is mapped, if not needed this is a NOP
1797 my $path = PVE::Storage::map_volume($storage_cfg, $volid);
1798 $path = PVE::Storage::path($storage_cfg, $volid) if !defined($path);
1799 if ($running) {
1800
1801 $mp->{mp} = '/';
1802 my $use_loopdev = (PVE::LXC::mountpoint_mount_path($mp, $storage_cfg))[1];
1803 $path = PVE::LXC::query_loopdev($path) if $use_loopdev;
1804 die "internal error: CT running but mount point not attached to a loop device"
1805 if !$path;
1806 PVE::Tools::run_command(['losetup', '--set-capacity', $path]) if $use_loopdev;
1807
1808 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1809 # to be visible to it in its namespace.
1810 # To not interfere with the rest of the system we unshare the current mount namespace,
1811 # mount over /tmp and then run resize2fs.
1812
1813 # interestingly we don't need to e2fsck on mounted systems...
1814 my $quoted = PVE::Tools::shellquote($path);
1815 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1816 eval {
1817 PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1818 };
1819 warn "Failed to update the container's filesystem: $@\n" if $@;
1820 } else {
1821 eval {
1822 PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]);
1823 PVE::Tools::run_command(['resize2fs', $path]);
1824 };
1825 warn "Failed to update the container's filesystem: $@\n" if $@;
1826
1827 # always un-map if not running, this is a NOP if not needed
1828 PVE::Storage::unmap_volume($storage_cfg, $volid);
1829 }
1830 }
1831 };
1832
1833 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1834 };
1835
1836 return PVE::LXC::Config->lock_config($vmid, $code);;
1837 }});
1838
1839 __PACKAGE__->register_method({
1840 name => 'move_volume',
1841 path => '{vmid}/move_volume',
1842 method => 'POST',
1843 protected => 1,
1844 proxyto => 'node',
1845 description => "Move a rootfs-/mp-volume to a different storage or to a different container.",
1846 permissions => {
1847 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1848 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1849 "a volume to another container, you need the permissions on the ".
1850 "target container as well.",
1851 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1852 },
1853 parameters => {
1854 additionalProperties => 0,
1855 properties => {
1856 node => get_standard_option('pve-node'),
1857 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1858 'target-vmid' => get_standard_option('pve-vmid', {
1859 completion => \&PVE::LXC::complete_ctid,
1860 optional => 1,
1861 }),
1862 volume => {
1863 type => 'string',
1864 #TODO: check how to handle unused mount points as the mp parameter is not configured
1865 enum => [ PVE::LXC::Config->valid_volume_keys_with_unused() ],
1866 description => "Volume which will be moved.",
1867 },
1868 storage => get_standard_option('pve-storage-id', {
1869 description => "Target Storage.",
1870 completion => \&PVE::Storage::complete_storage_enabled,
1871 optional => 1,
1872 }),
1873 delete => {
1874 type => 'boolean',
1875 description => "Delete the original volume after successful copy. By default the " .
1876 "original is kept as an unused volume entry.",
1877 optional => 1,
1878 default => 0,
1879 },
1880 digest => {
1881 type => 'string',
1882 description => 'Prevent changes if current configuration file has different SHA1 " .
1883 "digest. This can be used to prevent concurrent modifications.',
1884 maxLength => 40,
1885 optional => 1,
1886 },
1887 bwlimit => {
1888 description => "Override I/O bandwidth limit (in KiB/s).",
1889 optional => 1,
1890 type => 'number',
1891 minimum => '0',
1892 default => 'clone limit from datacenter or storage config',
1893 },
1894 'target-volume' => {
1895 type => 'string',
1896 description => "The config key the volume will be moved to. Default is the " .
1897 "source volume key.",
1898 enum => [PVE::LXC::Config->valid_volume_keys_with_unused()],
1899 optional => 1,
1900 },
1901 'target-digest' => {
1902 type => 'string',
1903 description => 'Prevent changes if current configuration file of the target " .
1904 "container has a different SHA1 digest. This can be used to prevent " .
1905 "concurrent modifications.',
1906 maxLength => 40,
1907 optional => 1,
1908 },
1909 },
1910 },
1911 returns => {
1912 type => 'string',
1913 },
1914 code => sub {
1915 my ($param) = @_;
1916
1917 my $rpcenv = PVE::RPCEnvironment::get();
1918
1919 my $authuser = $rpcenv->get_user();
1920
1921 my $vmid = extract_param($param, 'vmid');
1922
1923 my $target_vmid = extract_param($param, 'target-vmid');
1924
1925 my $storage = extract_param($param, 'storage');
1926
1927 my $mpkey = extract_param($param, 'volume');
1928
1929 my $target_mpkey = extract_param($param, 'target-volume') // $mpkey;
1930
1931 my $digest = extract_param($param, 'digest');
1932
1933 my $target_digest = extract_param($param, 'target-digest');
1934
1935 my $lockname = 'disk';
1936
1937 my ($mpdata, $old_volid);
1938
1939 die "either set storage or target-vmid, but not both\n"
1940 if $storage && $target_vmid;
1941
1942 my $storecfg = PVE::Storage::config();
1943
1944 my $move_to_storage_checks = sub {
1945 PVE::LXC::Config->lock_config($vmid, sub {
1946 my $conf = PVE::LXC::Config->load_config($vmid);
1947 PVE::LXC::Config->check_lock($conf);
1948
1949 die "cannot move volumes of a running container\n"
1950 if PVE::LXC::check_running($vmid);
1951
1952 if ($mpkey =~ m/^unused\d+$/) {
1953 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1954 "another storage\n";
1955 }
1956
1957 $mpdata = PVE::LXC::Config->parse_volume($mpkey, $conf->{$mpkey});
1958 $old_volid = $mpdata->{volume};
1959
1960 die "you can't move a volume with snapshots and delete the source\n"
1961 if $param->{delete} && PVE::LXC::Config->is_volume_in_use_by_snapshots($conf, $old_volid);
1962
1963 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1964
1965 PVE::LXC::Config->set_lock($vmid, $lockname);
1966 });
1967 };
1968
1969 my $storage_realcmd = sub {
1970 eval {
1971 PVE::Cluster::log_msg(
1972 'info',
1973 $authuser,
1974 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1975 );
1976
1977 my $conf = PVE::LXC::Config->load_config($vmid);
1978 my $storage_cfg = PVE::Storage::config();
1979
1980 my $new_volid;
1981
1982 eval {
1983 PVE::Storage::activate_volumes($storage_cfg, [ $old_volid ]);
1984 my $bwlimit = extract_param($param, 'bwlimit');
1985 my $source_storage = PVE::Storage::parse_volume_id($old_volid);
1986 my $movelimit = PVE::Storage::get_bandwidth_limit(
1987 'move',
1988 [$source_storage, $storage],
1989 $bwlimit
1990 );
1991 $new_volid = PVE::LXC::copy_volume(
1992 $mpdata,
1993 $vmid,
1994 $storage,
1995 $storage_cfg,
1996 $conf,
1997 undef,
1998 $movelimit
1999 );
2000 if (PVE::LXC::Config->is_template($conf)) {
2001 PVE::Storage::activate_volumes($storage_cfg, [ $new_volid ]);
2002 my $template_volid = PVE::Storage::vdisk_create_base($storage_cfg, $new_volid);
2003 $mpdata->{volume} = $template_volid;
2004 } else {
2005 $mpdata->{volume} = $new_volid;
2006 }
2007
2008 PVE::LXC::Config->lock_config($vmid, sub {
2009 my $digest = $conf->{digest};
2010 $conf = PVE::LXC::Config->load_config($vmid);
2011 PVE::Tools::assert_if_modified($digest, $conf->{digest});
2012
2013 $conf->{$mpkey} = PVE::LXC::Config->print_ct_mountpoint(
2014 $mpdata,
2015 $mpkey eq 'rootfs'
2016 );
2017
2018 PVE::LXC::Config->add_unused_volume($conf, $old_volid) if !$param->{delete};
2019
2020 PVE::LXC::Config->write_config($vmid, $conf);
2021 });
2022
2023 eval {
2024 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2025 PVE::Storage::deactivate_volumes($storage_cfg, [ $new_volid ])
2026 };
2027 warn $@ if $@;
2028 };
2029 if (my $err = $@) {
2030 eval {
2031 PVE::Storage::vdisk_free($storage_cfg, $new_volid)
2032 if defined($new_volid);
2033 };
2034 warn $@ if $@;
2035 die $err;
2036 }
2037
2038 if ($param->{delete}) {
2039 eval {
2040 PVE::Storage::deactivate_volumes($storage_cfg, [ $old_volid ]);
2041 PVE::Storage::vdisk_free($storage_cfg, $old_volid);
2042 };
2043 if (my $err = $@) {
2044 warn $err;
2045 PVE::LXC::Config->lock_config($vmid, sub {
2046 my $conf = PVE::LXC::Config->load_config($vmid);
2047 PVE::LXC::Config->add_unused_volume($conf, $old_volid);
2048 PVE::LXC::Config->write_config($vmid, $conf);
2049 });
2050 }
2051 }
2052 };
2053 my $err = $@;
2054 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2055 warn $@ if $@;
2056 die $err if $err;
2057 };
2058
2059 my $load_and_check_reassign_configs = sub {
2060 my $vmlist = PVE::Cluster::get_vmlist()->{ids};
2061
2062 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2063
2064 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2065 die "Moving an unused volume to a used one is not possible\n";
2066 }
2067 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2068 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2069
2070 my $source_node = $vmlist->{$vmid}->{node};
2071 my $target_node = $vmlist->{$target_vmid}->{node};
2072
2073 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2074 if $source_node ne $target_node;
2075
2076 my $source_conf = PVE::LXC::Config->load_config($vmid);
2077 PVE::LXC::Config->check_lock($source_conf);
2078 my $target_conf = PVE::LXC::Config->load_config($target_vmid);
2079 PVE::LXC::Config->check_lock($target_conf);
2080
2081 die "Can't move volumes from or to template CT\n"
2082 if ($source_conf->{template} || $target_conf->{template});
2083
2084 if ($digest) {
2085 eval { PVE::Tools::assert_if_modified($digest, $source_conf->{digest}) };
2086 die "Container ${vmid}: $@" if $@;
2087 }
2088
2089 if ($target_digest) {
2090 eval { PVE::Tools::assert_if_modified($target_digest, $target_conf->{digest}) };
2091 die "Container ${target_vmid}: $@" if $@;
2092 }
2093
2094 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2095 if !defined($source_conf->{$mpkey});
2096
2097 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2098 if exists $target_conf->{$target_mpkey};
2099
2100 my $drive = PVE::LXC::Config->parse_volume(
2101 $mpkey,
2102 $source_conf->{$mpkey},
2103 );
2104
2105 my $source_volid = $drive->{volume};
2106
2107 die "Volume '${mpkey}' has no associated image\n"
2108 if !$source_volid;
2109 die "Cannot move volume used by a snapshot to another container\n"
2110 if PVE::LXC::Config->is_volume_in_use_by_snapshots($source_conf, $source_volid);
2111 die "Storage does not support moving of this disk to another container\n"
2112 if !PVE::Storage::volume_has_feature($storecfg, 'rename', $source_volid);
2113 die "Cannot move a bindmount or device mount to another container\n"
2114 if $drive->{type} ne "volume";
2115 die "Cannot move volume to another container while the source is running - detach first\n"
2116 if PVE::LXC::check_running($vmid) && $mpkey !~ m/^unused\d+$/;
2117
2118 my $repl_conf = PVE::ReplicationConfig->new();
2119 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2120 my ($storeid, undef) = PVE::Storage::parse_volume_id($source_volid);
2121 my $format = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2122
2123 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2124 if !PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
2125 }
2126
2127 return ($source_conf, $target_conf, $drive);
2128 };
2129
2130 my $logfunc = sub {
2131 my ($msg) = @_;
2132 print STDERR "$msg\n";
2133 };
2134
2135 my $volume_reassignfn = sub {
2136 return PVE::LXC::Config->lock_config($vmid, sub {
2137 return PVE::LXC::Config->lock_config($target_vmid, sub {
2138 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
2139 my $source_volid = $drive->{volume};
2140
2141 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2142
2143 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2144
2145 my ($storage, $source_volname) = PVE::Storage::parse_volume_id($source_volid);
2146
2147 my $fmt = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2148
2149 my $new_volid = PVE::Storage::rename_volume(
2150 $storecfg,
2151 $source_volid,
2152 $target_vmid,
2153 );
2154
2155 $drive->{volume} = $new_volid;
2156
2157 delete $source_conf->{$mpkey};
2158 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2159 PVE::LXC::Config->write_config($vmid, $source_conf);
2160
2161 my $drive_string;
2162
2163 if ($target_unused) {
2164 $drive_string = $new_volid;
2165 } else {
2166 $drive_string = PVE::LXC::Config->print_volume($target_mpkey, $drive);
2167 }
2168
2169 if ($target_unused) {
2170 $target_conf->{$target_mpkey} = $drive_string;
2171 } else {
2172 my $running = PVE::LXC::check_running($target_vmid);
2173 my $param = { $target_mpkey => $drive_string };
2174 my $errors = PVE::LXC::Config->update_pct_config(
2175 $target_vmid,
2176 $target_conf,
2177 $running,
2178 $param
2179 );
2180
2181 foreach my $key (keys %$errors) {
2182 $rpcenv->warn($errors->{$key});
2183 }
2184 }
2185
2186 PVE::LXC::Config->write_config($target_vmid, $target_conf);
2187 $target_conf = PVE::LXC::Config->load_config($target_vmid);
2188
2189 PVE::LXC::update_lxc_config($target_vmid, $target_conf) if !$target_unused;
2190 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2191
2192 # remove possible replication snapshots
2193 if (PVE::Storage::volume_has_feature($storecfg,'replicate', $source_volid)) {
2194 eval {
2195 PVE::Replication::prepare(
2196 $storecfg,
2197 [$new_volid],
2198 undef,
2199 1,
2200 undef,
2201 $logfunc,
2202 )
2203 };
2204 if (my $err = $@) {
2205 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2206 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2207 "Error: ${err}\n");
2208 }
2209 }
2210 });
2211 });
2212 };
2213
2214 if ($target_vmid && $storage) {
2215 my $msg = "either set 'storage' or 'target-vmid', but not both";
2216 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
2217 } elsif ($target_vmid) {
2218 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2219 if $authuser ne 'root@pam';
2220
2221 if ($vmid eq $target_vmid) {
2222 my $msg = "must be different than source VMID to move disk to another container";
2223 raise_param_exc({ 'target-vmid' => $msg });
2224 }
2225
2226 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
2227 my $storeid = PVE::Storage::parse_volume_id($drive->{volume});
2228 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2229 return $rpcenv->fork_worker(
2230 'move_volume',
2231 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2232 $authuser,
2233 $volume_reassignfn
2234 );
2235 } elsif ($storage) {
2236 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2237 &$move_to_storage_checks();
2238 my $task = eval {
2239 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2240 };
2241 if (my $err = $@) {
2242 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2243 warn $@ if $@;
2244 die $err;
2245 }
2246 return $task;
2247 } else {
2248 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2249 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
2250 }
2251 }});
2252
2253 __PACKAGE__->register_method({
2254 name => 'vm_pending',
2255 path => '{vmid}/pending',
2256 method => 'GET',
2257 proxyto => 'node',
2258 description => 'Get container configuration, including pending changes.',
2259 permissions => {
2260 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2261 },
2262 parameters => {
2263 additionalProperties => 0,
2264 properties => {
2265 node => get_standard_option('pve-node'),
2266 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
2267 },
2268 },
2269 returns => {
2270 type => "array",
2271 items => {
2272 type => "object",
2273 properties => {
2274 key => {
2275 description => 'Configuration option name.',
2276 type => 'string',
2277 },
2278 value => {
2279 description => 'Current value.',
2280 type => 'string',
2281 optional => 1,
2282 },
2283 pending => {
2284 description => 'Pending value.',
2285 type => 'string',
2286 optional => 1,
2287 },
2288 delete => {
2289 description => "Indicates a pending delete request if present and not 0.",
2290 type => 'integer',
2291 minimum => 0,
2292 maximum => 2,
2293 optional => 1,
2294 },
2295 },
2296 },
2297 },
2298 code => sub {
2299 my ($param) = @_;
2300
2301 my $conf = PVE::LXC::Config->load_config($param->{vmid});
2302
2303 my $pending_delete_hash = PVE::LXC::Config->parse_pending_delete($conf->{pending}->{delete});
2304
2305 return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);
2306 }});
2307
2308 1;