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