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