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