]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
lxc detect error message changed
[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);
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::AccessControl;
12 use PVE::Firewall;
13 use PVE::Storage;
14 use PVE::RESTHandler;
15 use PVE::RPCEnvironment;
16 use PVE::LXC;
17 use PVE::LXC::Create;
18 use PVE::API2::LXC::Config;
19 use PVE::API2::LXC::Status;
20 use PVE::API2::LXC::Snapshot;
21 use PVE::HA::Config;
22 use PVE::JSONSchema qw(get_standard_option);
23 use base qw(PVE::RESTHandler);
24
25 use Data::Dumper; # fixme: remove
26
27 __PACKAGE__->register_method ({
28 subclass => "PVE::API2::LXC::Config",
29 path => '{vmid}/config',
30 });
31
32 __PACKAGE__->register_method ({
33 subclass => "PVE::API2::LXC::Status",
34 path => '{vmid}/status',
35 });
36
37 __PACKAGE__->register_method ({
38 subclass => "PVE::API2::LXC::Snapshot",
39 path => '{vmid}/snapshot',
40 });
41
42 __PACKAGE__->register_method ({
43 subclass => "PVE::API2::Firewall::CT",
44 path => '{vmid}/firewall',
45 });
46
47 my $destroy_disks = sub {
48 my ($storecfg, $vollist) = @_;
49
50 foreach my $volid (@$vollist) {
51 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
52 warn $@ if $@;
53 }
54 };
55
56 my $create_disks = sub {
57 my ($storecfg, $vmid, $settings, $conf) = @_;
58
59 my $vollist = [];
60
61 eval {
62 PVE::LXC::foreach_mountpoint($settings, sub {
63 my ($ms, $mountpoint) = @_;
64
65 my $volid = $mountpoint->{volume};
66 my $mp = $mountpoint->{mp};
67
68 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
69
70 return if !$storage;
71
72 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
73 my ($storeid, $size) = ($1, $2);
74
75 $size = int($size*1024) * 1024;
76
77 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
78 # fixme: use better naming ct-$vmid-disk-X.raw?
79
80 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
81 if ($size > 0) {
82 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
83 undef, $size);
84 } else {
85 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
86 undef, 0);
87 }
88 } elsif ($scfg->{type} eq 'zfspool') {
89
90 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
91 undef, $size);
92 } elsif ($scfg->{type} eq 'drbd') {
93
94 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
95
96 } elsif ($scfg->{type} eq 'rbd') {
97
98 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
99 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size);
100 } else {
101 die "unable to create containers on storage type '$scfg->{type}'\n";
102 }
103 push @$vollist, $volid;
104 $conf->{$ms} = PVE::LXC::print_ct_mountpoint({volume => $volid, size => $size, mp => $mp });
105 } else {
106 # use specified/existing volid
107 }
108 });
109 };
110 # free allocated images on error
111 if (my $err = $@) {
112 syslog('err', "VM $vmid creating disks failed");
113 &$destroy_disks($storecfg, $vollist);
114 die $err;
115 }
116 return $vollist;
117 };
118
119 __PACKAGE__->register_method({
120 name => 'vmlist',
121 path => '',
122 method => 'GET',
123 description => "LXC container index (per node).",
124 permissions => {
125 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
126 user => 'all',
127 },
128 proxyto => 'node',
129 protected => 1, # /proc files are only readable by root
130 parameters => {
131 additionalProperties => 0,
132 properties => {
133 node => get_standard_option('pve-node'),
134 },
135 },
136 returns => {
137 type => 'array',
138 items => {
139 type => "object",
140 properties => {},
141 },
142 links => [ { rel => 'child', href => "{vmid}" } ],
143 },
144 code => sub {
145 my ($param) = @_;
146
147 my $rpcenv = PVE::RPCEnvironment::get();
148 my $authuser = $rpcenv->get_user();
149
150 my $vmstatus = PVE::LXC::vmstatus();
151
152 my $res = [];
153 foreach my $vmid (keys %$vmstatus) {
154 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
155
156 my $data = $vmstatus->{$vmid};
157 $data->{vmid} = $vmid;
158 push @$res, $data;
159 }
160
161 return $res;
162
163 }});
164
165 __PACKAGE__->register_method({
166 name => 'create_vm',
167 path => '',
168 method => 'POST',
169 description => "Create or restore a container.",
170 permissions => {
171 user => 'all', # check inside
172 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
173 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
174 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
175 },
176 protected => 1,
177 proxyto => 'node',
178 parameters => {
179 additionalProperties => 0,
180 properties => PVE::LXC::json_config_properties({
181 node => get_standard_option('pve-node'),
182 vmid => get_standard_option('pve-vmid'),
183 ostemplate => {
184 description => "The OS template or backup file.",
185 type => 'string',
186 maxLength => 255,
187 },
188 password => {
189 optional => 1,
190 type => 'string',
191 description => "Sets root password inside container.",
192 minLength => 5,
193 },
194 storage => get_standard_option('pve-storage-id', {
195 description => "Default Storage.",
196 default => 'local',
197 optional => 1,
198 }),
199 force => {
200 optional => 1,
201 type => 'boolean',
202 description => "Allow to overwrite existing container.",
203 },
204 restore => {
205 optional => 1,
206 type => 'boolean',
207 description => "Mark this as restore task.",
208 },
209 pool => {
210 optional => 1,
211 type => 'string', format => 'pve-poolid',
212 description => "Add the VM to the specified pool.",
213 },
214 }),
215 },
216 returns => {
217 type => 'string',
218 },
219 code => sub {
220 my ($param) = @_;
221
222 my $rpcenv = PVE::RPCEnvironment::get();
223
224 my $authuser = $rpcenv->get_user();
225
226 my $node = extract_param($param, 'node');
227
228 my $vmid = extract_param($param, 'vmid');
229
230 my $basecfg_fn = PVE::LXC::config_file($vmid);
231
232 my $same_container_exists = -f $basecfg_fn;
233
234 my $restore = extract_param($param, 'restore');
235
236 if ($restore) {
237 # fixme: limit allowed parameters
238
239 }
240
241 my $force = extract_param($param, 'force');
242
243 if (!($same_container_exists && $restore && $force)) {
244 PVE::Cluster::check_vmid_unused($vmid);
245 }
246
247 my $password = extract_param($param, 'password');
248
249 my $storage = extract_param($param, 'storage') // 'local';
250
251 my $storage_cfg = cfs_read_file("storage.cfg");
252
253 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
254
255 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
256 if !($scfg->{content}->{images} || $scfg->{content}->{rootdir});
257
258 my $pool = extract_param($param, 'pool');
259
260 if (defined($pool)) {
261 $rpcenv->check_pool_exist($pool);
262 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
263 }
264
265 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
266
267 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
268 # OK
269 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
270 # OK
271 } elsif ($restore && $force && $same_container_exists &&
272 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
273 # OK: user has VM.Backup permissions, and want to restore an existing VM
274 } else {
275 raise_perm_exc();
276 }
277
278 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
279
280 PVE::Storage::activate_storage($storage_cfg, $storage);
281
282 my $ostemplate = extract_param($param, 'ostemplate');
283
284 my $archive;
285
286 if ($ostemplate eq '-') {
287 die "pipe requires cli environment\n"
288 if $rpcenv->{type} ne 'cli';
289 die "pipe can only be used with restore tasks\n"
290 if !$restore;
291 $archive = '-';
292 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
293 } else {
294 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
295 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
296 }
297
298 my $conf = {};
299
300 my $no_disk_param = {};
301 foreach my $opt (keys %$param) {
302 my $value = $param->{$opt};
303 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
304 # allow to use simple numbers (add default storage in that case)
305 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
306 } else {
307 $no_disk_param->{$opt} = $value;
308 }
309 }
310 PVE::LXC::update_pct_config($vmid, $conf, 0, $no_disk_param);
311
312 my $check_vmid_usage = sub {
313 if ($force) {
314 die "can't overwrite running container\n"
315 if PVE::LXC::check_running($vmid);
316 } else {
317 PVE::Cluster::check_vmid_unused($vmid);
318 }
319 };
320
321 my $code = sub {
322 &$check_vmid_usage(); # final check after locking
323
324 PVE::Cluster::check_cfs_quorum();
325 my $vollist = [];
326
327 eval {
328 if (!defined($param->{rootfs})) {
329 if ($restore) {
330 my (undef, $disksize) = PVE::LXC::Create::recover_config($archive);
331 die "unable to detect disk size - please specify rootfs (size)\n"
332 if !$disksize;
333 $param->{rootfs} = "$storage:$disksize";
334 } else {
335 $param->{rootfs} = "$storage:4"; # defaults to 4GB
336 }
337 }
338
339 $vollist = &$create_disks($storage_cfg, $vmid, $param, $conf);
340
341 PVE::LXC::Create::create_rootfs($storage_cfg, $vmid, $conf, $archive, $password, $restore);
342 # set some defaults
343 $conf->{hostname} ||= "CT$vmid";
344 $conf->{memory} ||= 512;
345 $conf->{swap} //= 512;
346 PVE::LXC::create_config($vmid, $conf);
347 };
348 if (my $err = $@) {
349 &$destroy_disks($storage_cfg, $vollist);
350 PVE::LXC::destroy_config($vmid);
351 die $err;
352 }
353 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
354 };
355
356 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
357
358 &$check_vmid_usage(); # first check before locking
359
360 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
361 $vmid, $authuser, $realcmd);
362
363 }});
364
365 __PACKAGE__->register_method({
366 name => 'vmdiridx',
367 path => '{vmid}',
368 method => 'GET',
369 proxyto => 'node',
370 description => "Directory index",
371 permissions => {
372 user => 'all',
373 },
374 parameters => {
375 additionalProperties => 0,
376 properties => {
377 node => get_standard_option('pve-node'),
378 vmid => get_standard_option('pve-vmid'),
379 },
380 },
381 returns => {
382 type => 'array',
383 items => {
384 type => "object",
385 properties => {
386 subdir => { type => 'string' },
387 },
388 },
389 links => [ { rel => 'child', href => "{subdir}" } ],
390 },
391 code => sub {
392 my ($param) = @_;
393
394 # test if VM exists
395 my $conf = PVE::LXC::load_config($param->{vmid});
396
397 my $res = [
398 { subdir => 'config' },
399 { subdir => 'status' },
400 { subdir => 'vncproxy' },
401 { subdir => 'vncwebsocket' },
402 { subdir => 'spiceproxy' },
403 { subdir => 'migrate' },
404 # { subdir => 'initlog' },
405 { subdir => 'rrd' },
406 { subdir => 'rrddata' },
407 { subdir => 'firewall' },
408 { subdir => 'snapshot' },
409 ];
410
411 return $res;
412 }});
413
414 __PACKAGE__->register_method({
415 name => 'rrd',
416 path => '{vmid}/rrd',
417 method => 'GET',
418 protected => 1, # fixme: can we avoid that?
419 permissions => {
420 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
421 },
422 description => "Read VM RRD statistics (returns PNG)",
423 parameters => {
424 additionalProperties => 0,
425 properties => {
426 node => get_standard_option('pve-node'),
427 vmid => get_standard_option('pve-vmid'),
428 timeframe => {
429 description => "Specify the time frame you are interested in.",
430 type => 'string',
431 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
432 },
433 ds => {
434 description => "The list of datasources you want to display.",
435 type => 'string', format => 'pve-configid-list',
436 },
437 cf => {
438 description => "The RRD consolidation function",
439 type => 'string',
440 enum => [ 'AVERAGE', 'MAX' ],
441 optional => 1,
442 },
443 },
444 },
445 returns => {
446 type => "object",
447 properties => {
448 filename => { type => 'string' },
449 },
450 },
451 code => sub {
452 my ($param) = @_;
453
454 return PVE::Cluster::create_rrd_graph(
455 "pve2-vm/$param->{vmid}", $param->{timeframe},
456 $param->{ds}, $param->{cf});
457
458 }});
459
460 __PACKAGE__->register_method({
461 name => 'rrddata',
462 path => '{vmid}/rrddata',
463 method => 'GET',
464 protected => 1, # fixme: can we avoid that?
465 permissions => {
466 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
467 },
468 description => "Read VM RRD statistics",
469 parameters => {
470 additionalProperties => 0,
471 properties => {
472 node => get_standard_option('pve-node'),
473 vmid => get_standard_option('pve-vmid'),
474 timeframe => {
475 description => "Specify the time frame you are interested in.",
476 type => 'string',
477 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
478 },
479 cf => {
480 description => "The RRD consolidation function",
481 type => 'string',
482 enum => [ 'AVERAGE', 'MAX' ],
483 optional => 1,
484 },
485 },
486 },
487 returns => {
488 type => "array",
489 items => {
490 type => "object",
491 properties => {},
492 },
493 },
494 code => sub {
495 my ($param) = @_;
496
497 return PVE::Cluster::create_rrd_data(
498 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
499 }});
500
501 __PACKAGE__->register_method({
502 name => 'destroy_vm',
503 path => '{vmid}',
504 method => 'DELETE',
505 protected => 1,
506 proxyto => 'node',
507 description => "Destroy the container (also delete all uses files).",
508 permissions => {
509 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
510 },
511 parameters => {
512 additionalProperties => 0,
513 properties => {
514 node => get_standard_option('pve-node'),
515 vmid => get_standard_option('pve-vmid'),
516 },
517 },
518 returns => {
519 type => 'string',
520 },
521 code => sub {
522 my ($param) = @_;
523
524 my $rpcenv = PVE::RPCEnvironment::get();
525
526 my $authuser = $rpcenv->get_user();
527
528 my $vmid = $param->{vmid};
529
530 # test if container exists
531 my $conf = PVE::LXC::load_config($vmid);
532
533 my $storage_cfg = cfs_read_file("storage.cfg");
534
535 die "unable to remove CT $vmid - used in HA resources\n"
536 if PVE::HA::Config::vm_is_ha_managed($vmid);
537
538 my $code = sub {
539 # reload config after lock
540 $conf = PVE::LXC::load_config($vmid);
541 PVE::LXC::check_lock($conf);
542
543 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
544 PVE::AccessControl::remove_vm_access($vmid);
545 PVE::Firewall::remove_vmfw_conf($vmid);
546 };
547
548 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
549
550 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
551 }});
552
553 my $sslcert;
554
555 __PACKAGE__->register_method ({
556 name => 'vncproxy',
557 path => '{vmid}/vncproxy',
558 method => 'POST',
559 protected => 1,
560 permissions => {
561 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
562 },
563 description => "Creates a TCP VNC proxy connections.",
564 parameters => {
565 additionalProperties => 0,
566 properties => {
567 node => get_standard_option('pve-node'),
568 vmid => get_standard_option('pve-vmid'),
569 websocket => {
570 optional => 1,
571 type => 'boolean',
572 description => "use websocket instead of standard VNC.",
573 },
574 },
575 },
576 returns => {
577 additionalProperties => 0,
578 properties => {
579 user => { type => 'string' },
580 ticket => { type => 'string' },
581 cert => { type => 'string' },
582 port => { type => 'integer' },
583 upid => { type => 'string' },
584 },
585 },
586 code => sub {
587 my ($param) = @_;
588
589 my $rpcenv = PVE::RPCEnvironment::get();
590
591 my $authuser = $rpcenv->get_user();
592
593 my $vmid = $param->{vmid};
594 my $node = $param->{node};
595
596 my $authpath = "/vms/$vmid";
597
598 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
599
600 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
601 if !$sslcert;
602
603 my ($remip, $family);
604
605 if ($node ne PVE::INotify::nodename()) {
606 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
607 } else {
608 $family = PVE::Tools::get_host_address_family($node);
609 }
610
611 my $port = PVE::Tools::next_vnc_port($family);
612
613 # NOTE: vncterm VNC traffic is already TLS encrypted,
614 # so we select the fastest chipher here (or 'none'?)
615 my $remcmd = $remip ?
616 ['/usr/bin/ssh', '-t', $remip] : [];
617
618 my $conf = PVE::LXC::load_config($vmid, $node);
619 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
620
621 my $shcmd = [ '/usr/bin/dtach', '-A',
622 "/var/run/dtach/vzctlconsole$vmid",
623 '-r', 'winch', '-z', @$concmd];
624
625 my $realcmd = sub {
626 my $upid = shift;
627
628 syslog ('info', "starting lxc vnc proxy $upid\n");
629
630 my $timeout = 10;
631
632 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
633 '-timeout', $timeout, '-authpath', $authpath,
634 '-perm', 'VM.Console'];
635
636 if ($param->{websocket}) {
637 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
638 push @$cmd, '-notls', '-listen', 'localhost';
639 }
640
641 push @$cmd, '-c', @$remcmd, @$shcmd;
642
643 run_command($cmd);
644
645 return;
646 };
647
648 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
649
650 PVE::Tools::wait_for_vnc_port($port);
651
652 return {
653 user => $authuser,
654 ticket => $ticket,
655 port => $port,
656 upid => $upid,
657 cert => $sslcert,
658 };
659 }});
660
661 __PACKAGE__->register_method({
662 name => 'vncwebsocket',
663 path => '{vmid}/vncwebsocket',
664 method => 'GET',
665 permissions => {
666 description => "You also need to pass a valid ticket (vncticket).",
667 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
668 },
669 description => "Opens a weksocket for VNC traffic.",
670 parameters => {
671 additionalProperties => 0,
672 properties => {
673 node => get_standard_option('pve-node'),
674 vmid => get_standard_option('pve-vmid'),
675 vncticket => {
676 description => "Ticket from previous call to vncproxy.",
677 type => 'string',
678 maxLength => 512,
679 },
680 port => {
681 description => "Port number returned by previous vncproxy call.",
682 type => 'integer',
683 minimum => 5900,
684 maximum => 5999,
685 },
686 },
687 },
688 returns => {
689 type => "object",
690 properties => {
691 port => { type => 'string' },
692 },
693 },
694 code => sub {
695 my ($param) = @_;
696
697 my $rpcenv = PVE::RPCEnvironment::get();
698
699 my $authuser = $rpcenv->get_user();
700
701 my $authpath = "/vms/$param->{vmid}";
702
703 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
704
705 my $port = $param->{port};
706
707 return { port => $port };
708 }});
709
710 __PACKAGE__->register_method ({
711 name => 'spiceproxy',
712 path => '{vmid}/spiceproxy',
713 method => 'POST',
714 protected => 1,
715 proxyto => 'node',
716 permissions => {
717 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
718 },
719 description => "Returns a SPICE configuration to connect to the CT.",
720 parameters => {
721 additionalProperties => 0,
722 properties => {
723 node => get_standard_option('pve-node'),
724 vmid => get_standard_option('pve-vmid'),
725 proxy => get_standard_option('spice-proxy', { optional => 1 }),
726 },
727 },
728 returns => get_standard_option('remote-viewer-config'),
729 code => sub {
730 my ($param) = @_;
731
732 my $vmid = $param->{vmid};
733 my $node = $param->{node};
734 my $proxy = $param->{proxy};
735
736 my $authpath = "/vms/$vmid";
737 my $permissions = 'VM.Console';
738
739 my $conf = PVE::LXC::load_config($vmid);
740
741 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
742
743 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
744
745 my $shcmd = ['/usr/bin/dtach', '-A',
746 "/var/run/dtach/vzctlconsole$vmid",
747 '-r', 'winch', '-z', @$concmd];
748
749 my $title = "CT $vmid";
750
751 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
752 }});
753
754
755 __PACKAGE__->register_method({
756 name => 'migrate_vm',
757 path => '{vmid}/migrate',
758 method => 'POST',
759 protected => 1,
760 proxyto => 'node',
761 description => "Migrate the container to another node. Creates a new migration task.",
762 permissions => {
763 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
764 },
765 parameters => {
766 additionalProperties => 0,
767 properties => {
768 node => get_standard_option('pve-node'),
769 vmid => get_standard_option('pve-vmid'),
770 target => get_standard_option('pve-node', { description => "Target node." }),
771 online => {
772 type => 'boolean',
773 description => "Use online/live migration.",
774 optional => 1,
775 },
776 },
777 },
778 returns => {
779 type => 'string',
780 description => "the task ID.",
781 },
782 code => sub {
783 my ($param) = @_;
784
785 my $rpcenv = PVE::RPCEnvironment::get();
786
787 my $authuser = $rpcenv->get_user();
788
789 my $target = extract_param($param, 'target');
790
791 my $localnode = PVE::INotify::nodename();
792 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
793
794 PVE::Cluster::check_cfs_quorum();
795
796 PVE::Cluster::check_node_exists($target);
797
798 my $targetip = PVE::Cluster::remote_node_ip($target);
799
800 my $vmid = extract_param($param, 'vmid');
801
802 # test if VM exists
803 PVE::LXC::load_config($vmid);
804
805 # try to detect errors early
806 if (PVE::LXC::check_running($vmid)) {
807 die "can't migrate running container without --online\n"
808 if !$param->{online};
809 }
810
811 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
812
813 my $hacmd = sub {
814 my $upid = shift;
815
816 my $service = "ct:$vmid";
817
818 my $cmd = ['ha-manager', 'migrate', $service, $target];
819
820 print "Executing HA migrate for CT $vmid to node $target\n";
821
822 PVE::Tools::run_command($cmd);
823
824 return;
825 };
826
827 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
828
829 } else {
830
831 my $realcmd = sub {
832 my $upid = shift;
833
834 # fixme: implement lxc container migration
835 die "lxc container migration not implemented\n";
836
837 return;
838 };
839
840 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
841 }
842 }});
843
844 __PACKAGE__->register_method({
845 name => 'vm_feature',
846 path => '{vmid}/feature',
847 method => 'GET',
848 proxyto => 'node',
849 protected => 1,
850 description => "Check if feature for virtual machine is available.",
851 permissions => {
852 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
853 },
854 parameters => {
855 additionalProperties => 0,
856 properties => {
857 node => get_standard_option('pve-node'),
858 vmid => get_standard_option('pve-vmid'),
859 feature => {
860 description => "Feature to check.",
861 type => 'string',
862 enum => [ 'snapshot' ],
863 },
864 snapname => get_standard_option('pve-lxc-snapshot-name', {
865 optional => 1,
866 }),
867 },
868 },
869 returns => {
870 type => "object",
871 properties => {
872 hasFeature => { type => 'boolean' },
873 #nodes => {
874 #type => 'array',
875 #items => { type => 'string' },
876 #}
877 },
878 },
879 code => sub {
880 my ($param) = @_;
881
882 my $node = extract_param($param, 'node');
883
884 my $vmid = extract_param($param, 'vmid');
885
886 my $snapname = extract_param($param, 'snapname');
887
888 my $feature = extract_param($param, 'feature');
889
890 my $conf = PVE::LXC::load_config($vmid);
891
892 if($snapname){
893 my $snap = $conf->{snapshots}->{$snapname};
894 die "snapshot '$snapname' does not exist\n" if !defined($snap);
895 $conf = $snap;
896 }
897 my $storage_cfg = PVE::Storage::config();
898 #Maybe include later
899 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
900 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
901
902 return {
903 hasFeature => $hasFeature,
904 #nodes => [ keys %$nodelist ],
905 };
906 }});
907
908 __PACKAGE__->register_method({
909 name => 'template',
910 path => '{vmid}/template',
911 method => 'POST',
912 protected => 1,
913 proxyto => 'node',
914 description => "Create a Template.",
915 permissions => {
916 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
917 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
918 },
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 => { type => 'null'},
927 code => sub {
928 my ($param) = @_;
929
930 my $rpcenv = PVE::RPCEnvironment::get();
931
932 my $authuser = $rpcenv->get_user();
933
934 my $node = extract_param($param, 'node');
935
936 my $vmid = extract_param($param, 'vmid');
937
938 my $updatefn = sub {
939
940 my $conf = PVE::LXC::load_config($vmid);
941 PVE::LXC::check_lock($conf);
942
943 die "unable to create template, because CT contains snapshots\n"
944 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
945
946 die "you can't convert a template to a template\n"
947 if PVE::LXC::is_template($conf);
948
949 die "you can't convert a CT to template if the CT is running\n"
950 if PVE::LXC::check_running($vmid);
951
952 my $realcmd = sub {
953 PVE::LXC::template_create($vmid, $conf);
954 };
955
956 $conf->{template} = 1;
957
958 PVE::LXC::write_config($vmid, $conf);
959 # and remove lxc config
960 PVE::LXC::update_lxc_config(undef, $vmid, $conf);
961
962 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
963 };
964
965 PVE::LXC::lock_container($vmid, undef, $updatefn);
966
967 return undef;
968 }});
969
970 1;