]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
b1aaf2213d9d276202ee4c99166bed492968f8b0
[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::Storage;
13 use PVE::RESTHandler;
14 use PVE::RPCEnvironment;
15 use PVE::LXC;
16 use PVE::LXCCreate;
17 use PVE::HA::Config;
18 use PVE::JSONSchema qw(get_standard_option);
19 use base qw(PVE::RESTHandler);
20
21 use Data::Dumper; # fixme: remove
22
23 my $get_container_storage = sub {
24 my ($stcfg, $vmid, $lxc_conf) = @_;
25
26 my $path = $lxc_conf->{'lxc.rootfs'};
27 my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path);
28 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid;
29 return wantarray ? ($sid, $volname, $path) : $sid;
30 };
31
32 my $check_ct_modify_config_perm = sub {
33 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
34
35 return 1 if $authuser ne 'root@pam';
36
37 foreach my $opt (@$key_list) {
38
39 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
40 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
41 } elsif ($opt eq 'disk') {
42 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
43 } elsif ($opt eq 'memory' || $opt eq 'swap') {
44 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
45 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
46 $opt eq 'searchdomain' || $opt eq 'hostname') {
47 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
48 } else {
49 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
50 }
51 }
52
53 return 1;
54 };
55
56
57 __PACKAGE__->register_method({
58 name => 'vmlist',
59 path => '',
60 method => 'GET',
61 description => "LXC container index (per node).",
62 permissions => {
63 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
64 user => 'all',
65 },
66 proxyto => 'node',
67 protected => 1, # /proc files are only readable by root
68 parameters => {
69 additionalProperties => 0,
70 properties => {
71 node => get_standard_option('pve-node'),
72 },
73 },
74 returns => {
75 type => 'array',
76 items => {
77 type => "object",
78 properties => {},
79 },
80 links => [ { rel => 'child', href => "{vmid}" } ],
81 },
82 code => sub {
83 my ($param) = @_;
84
85 my $rpcenv = PVE::RPCEnvironment::get();
86 my $authuser = $rpcenv->get_user();
87
88 my $vmstatus = PVE::LXC::vmstatus();
89
90 my $res = [];
91 foreach my $vmid (keys %$vmstatus) {
92 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
93
94 my $data = $vmstatus->{$vmid};
95 $data->{vmid} = $vmid;
96 push @$res, $data;
97 }
98
99 return $res;
100
101 }});
102
103 __PACKAGE__->register_method({
104 name => 'create_vm',
105 path => '',
106 method => 'POST',
107 description => "Create or restore a container.",
108 permissions => {
109 user => 'all', # check inside
110 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
111 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
112 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
113 },
114 protected => 1,
115 proxyto => 'node',
116 parameters => {
117 additionalProperties => 0,
118 properties => PVE::LXC::json_config_properties({
119 node => get_standard_option('pve-node'),
120 vmid => get_standard_option('pve-vmid'),
121 ostemplate => {
122 description => "The OS template or backup file.",
123 type => 'string',
124 maxLength => 255,
125 },
126 password => {
127 optional => 1,
128 type => 'string',
129 description => "Sets root password inside container.",
130 minLength => 5,
131 },
132 storage => get_standard_option('pve-storage-id', {
133 description => "Target storage.",
134 default => 'local',
135 optional => 1,
136 }),
137 force => {
138 optional => 1,
139 type => 'boolean',
140 description => "Allow to overwrite existing container.",
141 },
142 restore => {
143 optional => 1,
144 type => 'boolean',
145 description => "Mark this as restore task.",
146 },
147 pool => {
148 optional => 1,
149 type => 'string', format => 'pve-poolid',
150 description => "Add the VM to the specified pool.",
151 },
152 }),
153 },
154 returns => {
155 type => 'string',
156 },
157 code => sub {
158 my ($param) = @_;
159
160 my $rpcenv = PVE::RPCEnvironment::get();
161
162 my $authuser = $rpcenv->get_user();
163
164 my $node = extract_param($param, 'node');
165
166 my $vmid = extract_param($param, 'vmid');
167
168 my $basecfg_fn = PVE::LXC::config_file($vmid);
169
170 my $same_container_exists = -f $basecfg_fn;
171
172 my $restore = extract_param($param, 'restore');
173
174 my $force = extract_param($param, 'force');
175
176 if (!($same_container_exists && $restore && $force)) {
177 PVE::Cluster::check_vmid_unused($vmid);
178 }
179
180 my $password = extract_param($param, 'password');
181
182 my $storage = extract_param($param, 'storage') || 'local';
183
184 my $pool = extract_param($param, 'pool');
185
186 my $storage_cfg = cfs_read_file("storage.cfg");
187
188 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
189
190 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
191 if !$scfg->{content}->{rootdir};
192
193 my $private = PVE::Storage::get_private_dir($storage_cfg, $storage, $vmid);
194
195 if (defined($pool)) {
196 $rpcenv->check_pool_exist($pool);
197 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
198 }
199
200 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
201 # OK
202 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
203 # OK
204 } elsif ($restore && $force && $same_container_exists &&
205 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
206 # OK: user has VM.Backup permissions, and want to restore an existing VM
207 } else {
208 raise_perm_exc();
209 }
210
211 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
212
213 PVE::Storage::activate_storage($storage_cfg, $storage);
214
215 my $ostemplate = extract_param($param, 'ostemplate');
216
217 my $archive;
218
219 if ($ostemplate eq '-') {
220 die "archive pipe not implemented\n"
221 # $archive = '-';
222 } else {
223 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
224 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
225 }
226
227 my $conf = {};
228
229 $param->{hostname} ||= "CT$vmid";
230 $param->{memory} ||= 512;
231 $param->{swap} = 512 if !defined($param->{swap});
232
233 PVE::LXC::update_lxc_config($vmid, $conf, 0, $param);
234
235 # assigng default names, so that we can configure network with LXCSetup
236 foreach my $k (keys %$conf) {
237 next if $k !~ m/^net(\d+)$/;
238 my $d = $conf->{$k};
239 my $ind = $1;
240 $d->{name} = "eth$ind"; # fixme: do not overwrite settings!
241 }
242
243 $conf->{'lxc.hook.mount'} = "/usr/share/lxc/hooks/lxc-pve-mount-hook";
244
245 # use user namespace ?
246 # disable for now, because kernel 3.10.0 does not support it
247 #$conf->{'lxc.id_map'} = ["u 0 100000 65536", "g 0 100000 65536"];
248
249 my $code = sub {
250 my $size = 4*1024*1024; # defaults to 4G
251 $size = int($param->{disk}*1024) * 1024 if defined($param->{disk});
252
253 PVE::LXCCreate::create_rootfs($storage_cfg, $storage, $size, $vmid, $conf, $archive, $password);
254 };
255
256 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
257
258 return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate',
259 $vmid, $authuser, $realcmd);
260
261 }});
262
263 my $vm_config_perm_list = [
264 'VM.Config.Disk',
265 'VM.Config.CPU',
266 'VM.Config.Memory',
267 'VM.Config.Network',
268 'VM.Config.Options',
269 ];
270
271 __PACKAGE__->register_method({
272 name => 'update_vm',
273 path => '{vmid}/config',
274 method => 'PUT',
275 protected => 1,
276 proxyto => 'node',
277 description => "Set container options.",
278 permissions => {
279 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
280 },
281 parameters => {
282 additionalProperties => 0,
283 properties => PVE::LXC::json_config_properties(
284 {
285 node => get_standard_option('pve-node'),
286 vmid => get_standard_option('pve-vmid'),
287 delete => {
288 type => 'string', format => 'pve-configid-list',
289 description => "A list of settings you want to delete.",
290 optional => 1,
291 },
292 digest => {
293 type => 'string',
294 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
295 maxLength => 40,
296 optional => 1,
297 }
298 }),
299 },
300 returns => { type => 'null'},
301 code => sub {
302 my ($param) = @_;
303
304 my $rpcenv = PVE::RPCEnvironment::get();
305
306 my $authuser = $rpcenv->get_user();
307
308 my $node = extract_param($param, 'node');
309
310 my $vmid = extract_param($param, 'vmid');
311
312 my $digest = extract_param($param, 'digest');
313
314 die "no options specified\n" if !scalar(keys %$param);
315
316 my $delete_str = extract_param($param, 'delete');
317 my @delete = PVE::Tools::split_list($delete_str);
318
319 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
320
321 foreach my $opt (@delete) {
322 raise_param_exc({ delete => "you can't use '-$opt' and " .
323 "-delete $opt' at the same time" })
324 if defined($param->{$opt});
325
326 if (!PVE::LXC::option_exists($opt)) {
327 raise_param_exc({ delete => "unknown option '$opt'" });
328 }
329 }
330
331 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
332
333 my $code = sub {
334
335 my $conf = PVE::LXC::load_config($vmid);
336
337 PVE::Tools::assert_if_modified($digest, $conf->{digest});
338
339 my $running = PVE::LXC::check_running($vmid);
340
341 PVE::LXC::update_lxc_config($vmid, $conf, $running, $param, \@delete);
342
343 PVE::LXC::write_config($vmid, $conf);
344 };
345
346 PVE::LXC::lock_container($vmid, undef, $code);
347
348 return undef;
349 }});
350
351 __PACKAGE__->register_method ({
352 subclass => "PVE::API2::Firewall::CT",
353 path => '{vmid}/firewall',
354 });
355
356 __PACKAGE__->register_method({
357 name => 'vmdiridx',
358 path => '{vmid}',
359 method => 'GET',
360 proxyto => 'node',
361 description => "Directory index",
362 permissions => {
363 user => 'all',
364 },
365 parameters => {
366 additionalProperties => 0,
367 properties => {
368 node => get_standard_option('pve-node'),
369 vmid => get_standard_option('pve-vmid'),
370 },
371 },
372 returns => {
373 type => 'array',
374 items => {
375 type => "object",
376 properties => {
377 subdir => { type => 'string' },
378 },
379 },
380 links => [ { rel => 'child', href => "{subdir}" } ],
381 },
382 code => sub {
383 my ($param) = @_;
384
385 # test if VM exists
386 my $conf = PVE::LXC::load_config($param->{vmid});
387
388 my $res = [
389 { subdir => 'config' },
390 { subdir => 'status' },
391 { subdir => 'vncproxy' },
392 { subdir => 'vncwebsocket' },
393 { subdir => 'spiceproxy' },
394 { subdir => 'migrate' },
395 # { subdir => 'initlog' },
396 { subdir => 'rrd' },
397 { subdir => 'rrddata' },
398 { subdir => 'firewall' },
399 ];
400
401 return $res;
402 }});
403
404 __PACKAGE__->register_method({
405 name => 'rrd',
406 path => '{vmid}/rrd',
407 method => 'GET',
408 protected => 1, # fixme: can we avoid that?
409 permissions => {
410 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
411 },
412 description => "Read VM RRD statistics (returns PNG)",
413 parameters => {
414 additionalProperties => 0,
415 properties => {
416 node => get_standard_option('pve-node'),
417 vmid => get_standard_option('pve-vmid'),
418 timeframe => {
419 description => "Specify the time frame you are interested in.",
420 type => 'string',
421 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
422 },
423 ds => {
424 description => "The list of datasources you want to display.",
425 type => 'string', format => 'pve-configid-list',
426 },
427 cf => {
428 description => "The RRD consolidation function",
429 type => 'string',
430 enum => [ 'AVERAGE', 'MAX' ],
431 optional => 1,
432 },
433 },
434 },
435 returns => {
436 type => "object",
437 properties => {
438 filename => { type => 'string' },
439 },
440 },
441 code => sub {
442 my ($param) = @_;
443
444 return PVE::Cluster::create_rrd_graph(
445 "pve2-vm/$param->{vmid}", $param->{timeframe},
446 $param->{ds}, $param->{cf});
447
448 }});
449
450 __PACKAGE__->register_method({
451 name => 'rrddata',
452 path => '{vmid}/rrddata',
453 method => 'GET',
454 protected => 1, # fixme: can we avoid that?
455 permissions => {
456 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
457 },
458 description => "Read VM RRD statistics",
459 parameters => {
460 additionalProperties => 0,
461 properties => {
462 node => get_standard_option('pve-node'),
463 vmid => get_standard_option('pve-vmid'),
464 timeframe => {
465 description => "Specify the time frame you are interested in.",
466 type => 'string',
467 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
468 },
469 cf => {
470 description => "The RRD consolidation function",
471 type => 'string',
472 enum => [ 'AVERAGE', 'MAX' ],
473 optional => 1,
474 },
475 },
476 },
477 returns => {
478 type => "array",
479 items => {
480 type => "object",
481 properties => {},
482 },
483 },
484 code => sub {
485 my ($param) = @_;
486
487 return PVE::Cluster::create_rrd_data(
488 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
489 }});
490
491
492 __PACKAGE__->register_method({
493 name => 'vm_config',
494 path => '{vmid}/config',
495 method => 'GET',
496 proxyto => 'node',
497 description => "Get container configuration.",
498 permissions => {
499 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
500 },
501 parameters => {
502 additionalProperties => 0,
503 properties => {
504 node => get_standard_option('pve-node'),
505 vmid => get_standard_option('pve-vmid'),
506 },
507 },
508 returns => {
509 type => "object",
510 properties => {
511 digest => {
512 type => 'string',
513 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
514 }
515 },
516 },
517 code => sub {
518 my ($param) = @_;
519
520 my $lxc_conf = PVE::LXC::load_config($param->{vmid});
521
522 # NOTE: we only return selected/converted values
523
524 my $conf = PVE::LXC::lxc_conf_to_pve($param->{vmid}, $lxc_conf);
525
526 my $stcfg = PVE::Cluster::cfs_read_file("storage.cfg");
527
528 my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $lxc_conf);
529 $conf->{storage} = $sid || $path;
530
531 return $conf;
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'),
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($param->{vmid});
565
566 my $storage_cfg = cfs_read_file("storage.cfg");
567
568 my $code = sub {
569 if (my $volid = $conf->{'pve.volid'}) {
570
571 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
572 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
573
574 PVE::Storage::vdisk_free($storage_cfg, $volid);
575
576 PVE::LXC::destroy_config($vmid);
577
578 } else {
579 my $cmd = ['lxc-destroy', '-n', $vmid ];
580
581 run_command($cmd);
582 }
583
584 PVE::AccessControl::remove_vm_from_pool($vmid);
585 };
586
587 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
588
589 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
590 }});
591
592 my $sslcert;
593
594 __PACKAGE__->register_method ({
595 name => 'vncproxy',
596 path => '{vmid}/vncproxy',
597 method => 'POST',
598 protected => 1,
599 permissions => {
600 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
601 },
602 description => "Creates a TCP VNC proxy connections.",
603 parameters => {
604 additionalProperties => 0,
605 properties => {
606 node => get_standard_option('pve-node'),
607 vmid => get_standard_option('pve-vmid'),
608 websocket => {
609 optional => 1,
610 type => 'boolean',
611 description => "use websocket instead of standard VNC.",
612 },
613 },
614 },
615 returns => {
616 additionalProperties => 0,
617 properties => {
618 user => { type => 'string' },
619 ticket => { type => 'string' },
620 cert => { type => 'string' },
621 port => { type => 'integer' },
622 upid => { type => 'string' },
623 },
624 },
625 code => sub {
626 my ($param) = @_;
627
628 my $rpcenv = PVE::RPCEnvironment::get();
629
630 my $authuser = $rpcenv->get_user();
631
632 my $vmid = $param->{vmid};
633 my $node = $param->{node};
634
635 my $authpath = "/vms/$vmid";
636
637 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
638
639 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
640 if !$sslcert;
641
642 my $port = PVE::Tools::next_vnc_port();
643
644 my $remip;
645
646 if ($node ne PVE::INotify::nodename()) {
647 $remip = PVE::Cluster::remote_node_ip($node);
648 }
649
650 # NOTE: vncterm VNC traffic is already TLS encrypted,
651 # so we select the fastest chipher here (or 'none'?)
652 my $remcmd = $remip ?
653 ['/usr/bin/ssh', '-t', $remip] : [];
654
655 my $shcmd = [ '/usr/bin/dtach', '-A',
656 "/var/run/dtach/vzctlconsole$vmid",
657 '-r', 'winch', '-z',
658 'lxc-console', '-n', $vmid ];
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 $shcmd = ['/usr/bin/dtach', '-A',
775 "/var/run/dtach/vzctlconsole$vmid",
776 '-r', 'winch', '-z',
777 'lxc-console', '-n', $vmid];
778
779 my $title = "CT $vmid";
780
781 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
782 }});
783
784 __PACKAGE__->register_method({
785 name => 'vmcmdidx',
786 path => '{vmid}/status',
787 method => 'GET',
788 proxyto => 'node',
789 description => "Directory index",
790 permissions => {
791 user => 'all',
792 },
793 parameters => {
794 additionalProperties => 0,
795 properties => {
796 node => get_standard_option('pve-node'),
797 vmid => get_standard_option('pve-vmid'),
798 },
799 },
800 returns => {
801 type => 'array',
802 items => {
803 type => "object",
804 properties => {
805 subdir => { type => 'string' },
806 },
807 },
808 links => [ { rel => 'child', href => "{subdir}" } ],
809 },
810 code => sub {
811 my ($param) = @_;
812
813 # test if VM exists
814 my $conf = PVE::OpenVZ::load_config($param->{vmid});
815
816 my $res = [
817 { subdir => 'current' },
818 { subdir => 'start' },
819 { subdir => 'stop' },
820 { subdir => 'shutdown' },
821 { subdir => 'migrate' },
822 ];
823
824 return $res;
825 }});
826
827 __PACKAGE__->register_method({
828 name => 'vm_status',
829 path => '{vmid}/status/current',
830 method => 'GET',
831 proxyto => 'node',
832 protected => 1, # openvz /proc entries are only readable by root
833 description => "Get virtual machine status.",
834 permissions => {
835 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
836 },
837 parameters => {
838 additionalProperties => 0,
839 properties => {
840 node => get_standard_option('pve-node'),
841 vmid => get_standard_option('pve-vmid'),
842 },
843 },
844 returns => { type => 'object' },
845 code => sub {
846 my ($param) = @_;
847
848 # test if VM exists
849 my $conf = PVE::LXC::load_config($param->{vmid});
850
851 my $vmstatus = PVE::LXC::vmstatus($param->{vmid});
852 my $status = $vmstatus->{$param->{vmid}};
853
854 $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0;
855
856 return $status;
857 }});
858
859 __PACKAGE__->register_method({
860 name => 'vm_start',
861 path => '{vmid}/status/start',
862 method => 'POST',
863 protected => 1,
864 proxyto => 'node',
865 description => "Start the container.",
866 permissions => {
867 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
868 },
869 parameters => {
870 additionalProperties => 0,
871 properties => {
872 node => get_standard_option('pve-node'),
873 vmid => get_standard_option('pve-vmid'),
874 },
875 },
876 returns => {
877 type => 'string',
878 },
879 code => sub {
880 my ($param) = @_;
881
882 my $rpcenv = PVE::RPCEnvironment::get();
883
884 my $authuser = $rpcenv->get_user();
885
886 my $node = extract_param($param, 'node');
887
888 my $vmid = extract_param($param, 'vmid');
889
890 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
891
892 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
893
894 my $hacmd = sub {
895 my $upid = shift;
896
897 my $service = "ct:$vmid";
898
899 my $cmd = ['ha-manager', 'enable', $service];
900
901 print "Executing HA start for CT $vmid\n";
902
903 PVE::Tools::run_command($cmd);
904
905 return;
906 };
907
908 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
909
910 } else {
911
912 my $realcmd = sub {
913 my $upid = shift;
914
915 syslog('info', "starting CT $vmid: $upid\n");
916
917 my $conf = PVE::LXC::load_config($vmid);
918 my $stcfg = cfs_read_file("storage.cfg");
919 if (my $sid = &$get_container_storage($stcfg, $vmid, $conf)) {
920 PVE::Storage::activate_storage($stcfg, $sid);
921 }
922
923 my $cmd = ['lxc-start', '-n', $vmid];
924
925 run_command($cmd);
926
927 return;
928 };
929
930 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
931 }
932 }});
933
934 __PACKAGE__->register_method({
935 name => 'vm_stop',
936 path => '{vmid}/status/stop',
937 method => 'POST',
938 protected => 1,
939 proxyto => 'node',
940 description => "Stop the container.",
941 permissions => {
942 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
943 },
944 parameters => {
945 additionalProperties => 0,
946 properties => {
947 node => get_standard_option('pve-node'),
948 vmid => get_standard_option('pve-vmid'),
949 },
950 },
951 returns => {
952 type => 'string',
953 },
954 code => sub {
955 my ($param) = @_;
956
957 my $rpcenv = PVE::RPCEnvironment::get();
958
959 my $authuser = $rpcenv->get_user();
960
961 my $node = extract_param($param, 'node');
962
963 my $vmid = extract_param($param, 'vmid');
964
965 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
966
967 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
968
969 my $hacmd = sub {
970 my $upid = shift;
971
972 my $service = "ct:$vmid";
973
974 my $cmd = ['ha-manager', 'disable', $service];
975
976 print "Executing HA stop for CT $vmid\n";
977
978 PVE::Tools::run_command($cmd);
979
980 return;
981 };
982
983 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
984
985 } else {
986
987 my $realcmd = sub {
988 my $upid = shift;
989
990 syslog('info', "stoping CT $vmid: $upid\n");
991
992 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
993
994 run_command($cmd);
995
996 return;
997 };
998
999 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1000 }
1001 }});
1002
1003 __PACKAGE__->register_method({
1004 name => 'vm_shutdown',
1005 path => '{vmid}/status/shutdown',
1006 method => 'POST',
1007 protected => 1,
1008 proxyto => 'node',
1009 description => "Shutdown the container.",
1010 permissions => {
1011 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1012 },
1013 parameters => {
1014 additionalProperties => 0,
1015 properties => {
1016 node => get_standard_option('pve-node'),
1017 vmid => get_standard_option('pve-vmid'),
1018 timeout => {
1019 description => "Wait maximal timeout seconds.",
1020 type => 'integer',
1021 minimum => 0,
1022 optional => 1,
1023 default => 60,
1024 },
1025 forceStop => {
1026 description => "Make sure the Container stops.",
1027 type => 'boolean',
1028 optional => 1,
1029 default => 0,
1030 }
1031 },
1032 },
1033 returns => {
1034 type => 'string',
1035 },
1036 code => sub {
1037 my ($param) = @_;
1038
1039 my $rpcenv = PVE::RPCEnvironment::get();
1040
1041 my $authuser = $rpcenv->get_user();
1042
1043 my $node = extract_param($param, 'node');
1044
1045 my $vmid = extract_param($param, 'vmid');
1046
1047 my $timeout = extract_param($param, 'timeout');
1048
1049 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1050
1051 my $realcmd = sub {
1052 my $upid = shift;
1053
1054 syslog('info', "shutdown CT $vmid: $upid\n");
1055
1056 my $cmd = ['lxc-stop', '-n', $vmid];
1057
1058 $timeout = 60 if !defined($timeout);
1059
1060 push @$cmd, '--timeout', $timeout;
1061
1062 eval { run_command($cmd, timeout => $timeout+5); };
1063 my $err = $@;
1064 return if !$err;
1065
1066 die $err if !$param->{forceStop};
1067
1068 warn "shutdown failed - forcing stop now\n";
1069
1070 push @$cmd, '--kill';
1071 run_command($cmd);
1072
1073 return;
1074 };
1075
1076 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1077
1078 return $upid;
1079 }});
1080
1081 __PACKAGE__->register_method({
1082 name => 'vm_suspend',
1083 path => '{vmid}/status/suspend',
1084 method => 'POST',
1085 protected => 1,
1086 proxyto => 'node',
1087 description => "Suspend the container.",
1088 permissions => {
1089 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1090 },
1091 parameters => {
1092 additionalProperties => 0,
1093 properties => {
1094 node => get_standard_option('pve-node'),
1095 vmid => get_standard_option('pve-vmid'),
1096 },
1097 },
1098 returns => {
1099 type => 'string',
1100 },
1101 code => sub {
1102 my ($param) = @_;
1103
1104 my $rpcenv = PVE::RPCEnvironment::get();
1105
1106 my $authuser = $rpcenv->get_user();
1107
1108 my $node = extract_param($param, 'node');
1109
1110 my $vmid = extract_param($param, 'vmid');
1111
1112 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1113
1114 my $realcmd = sub {
1115 my $upid = shift;
1116
1117 syslog('info', "suspend CT $vmid: $upid\n");
1118
1119 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1120
1121 run_command($cmd);
1122
1123 return;
1124 };
1125
1126 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1127
1128 return $upid;
1129 }});
1130
1131 __PACKAGE__->register_method({
1132 name => 'vm_resume',
1133 path => '{vmid}/status/resume',
1134 method => 'POST',
1135 protected => 1,
1136 proxyto => 'node',
1137 description => "Resume the container.",
1138 permissions => {
1139 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1140 },
1141 parameters => {
1142 additionalProperties => 0,
1143 properties => {
1144 node => get_standard_option('pve-node'),
1145 vmid => get_standard_option('pve-vmid'),
1146 },
1147 },
1148 returns => {
1149 type => 'string',
1150 },
1151 code => sub {
1152 my ($param) = @_;
1153
1154 my $rpcenv = PVE::RPCEnvironment::get();
1155
1156 my $authuser = $rpcenv->get_user();
1157
1158 my $node = extract_param($param, 'node');
1159
1160 my $vmid = extract_param($param, 'vmid');
1161
1162 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1163
1164 my $realcmd = sub {
1165 my $upid = shift;
1166
1167 syslog('info', "resume CT $vmid: $upid\n");
1168
1169 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1170 '-D', '/var/liv/vz/dump'];
1171
1172 run_command($cmd);
1173
1174 return;
1175 };
1176
1177 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1178
1179 return $upid;
1180 }});
1181
1182 __PACKAGE__->register_method({
1183 name => 'migrate_vm',
1184 path => '{vmid}/migrate',
1185 method => 'POST',
1186 protected => 1,
1187 proxyto => 'node',
1188 description => "Migrate the container to another node. Creates a new migration task.",
1189 permissions => {
1190 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1191 },
1192 parameters => {
1193 additionalProperties => 0,
1194 properties => {
1195 node => get_standard_option('pve-node'),
1196 vmid => get_standard_option('pve-vmid'),
1197 target => get_standard_option('pve-node', { description => "Target node." }),
1198 online => {
1199 type => 'boolean',
1200 description => "Use online/live migration.",
1201 optional => 1,
1202 },
1203 },
1204 },
1205 returns => {
1206 type => 'string',
1207 description => "the task ID.",
1208 },
1209 code => sub {
1210 my ($param) = @_;
1211
1212 my $rpcenv = PVE::RPCEnvironment::get();
1213
1214 my $authuser = $rpcenv->get_user();
1215
1216 my $target = extract_param($param, 'target');
1217
1218 my $localnode = PVE::INotify::nodename();
1219 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1220
1221 PVE::Cluster::check_cfs_quorum();
1222
1223 PVE::Cluster::check_node_exists($target);
1224
1225 my $targetip = PVE::Cluster::remote_node_ip($target);
1226
1227 my $vmid = extract_param($param, 'vmid');
1228
1229 # test if VM exists
1230 PVE::LXC::load_config($vmid);
1231
1232 # try to detect errors early
1233 if (PVE::LXC::check_running($vmid)) {
1234 die "cant migrate running container without --online\n"
1235 if !$param->{online};
1236 }
1237
1238 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1239
1240 my $hacmd = sub {
1241 my $upid = shift;
1242
1243 my $service = "ct:$vmid";
1244
1245 my $cmd = ['ha-manager', 'migrate', $service, $target];
1246
1247 print "Executing HA migrate for CT $vmid to node $target\n";
1248
1249 PVE::Tools::run_command($cmd);
1250
1251 return;
1252 };
1253
1254 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1255
1256 } else {
1257
1258 my $realcmd = sub {
1259 my $upid = shift;
1260
1261 # fixme: implement lxc container migration
1262 die "lxc container migration not implemented\n";
1263
1264 return;
1265 };
1266
1267 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1268 }
1269 }});
1270
1271 1;