]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Nodes.pm
include also HA VMs if a vm list is explicit specified
[pve-manager.git] / PVE / API2 / Nodes.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Nodes::Nodeinfo;
2
3use strict;
4use warnings;
b92400b6 5use POSIX qw(LONG_MAX);
aff192e6
DM
6use Filesys::Df;
7use Time::Local qw(timegm_nocheck);
40993593 8use HTTP::Status qw(:constants);
aff192e6
DM
9use PVE::pvecfg;
10use PVE::Tools;
b289829f 11use PVE::API2Tools;
aff192e6
DM
12use PVE::ProcFSTools;
13use PVE::SafeSyslog;
c9164975 14use PVE::Cluster qw(cfs_read_file);
aff192e6 15use PVE::INotify;
05b252dc 16use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
aff192e6
DM
17use PVE::RESTHandler;
18use PVE::RPCEnvironment;
19use PVE::JSONSchema qw(get_standard_option);
20use PVE::AccessControl;
c9164975 21use PVE::Storage;
4a07fced 22use PVE::Firewall;
989b7743 23use PVE::LXC;
c9164975 24use PVE::APLInfo;
34ada77a 25use PVE::Report;
10a9a736 26use PVE::HA::Env::PVE2;
7e5cb2f0 27use PVE::HA::Config;
7141ae25 28use PVE::QemuConfig;
b92400b6 29use PVE::QemuServer;
21ace8d3 30use PVE::API2::Subscription;
aff192e6
DM
31use PVE::API2::Services;
32use PVE::API2::Network;
33use PVE::API2::Tasks;
34use PVE::API2::Storage::Scan;
35use PVE::API2::Storage::Status;
36use PVE::API2::Qemu;
989b7743 37use PVE::API2::LXC;
b38516eb 38use PVE::API2::LXC::Status;
bf58f8dd 39use PVE::API2::VZDump;
21299915 40use PVE::API2::APT;
38db610a 41use PVE::API2::Ceph;
4a07fced 42use PVE::API2::Firewall::Host;
3b81e495
FG
43use Digest::MD5;
44use Digest::SHA;
e781363f 45use PVE::API2::Disks;
aff192e6
DM
46use JSON;
47
48use base qw(PVE::RESTHandler);
49
50__PACKAGE__->register_method ({
51 subclass => "PVE::API2::Qemu",
52 path => 'qemu',
53});
54
989b7743
DM
55__PACKAGE__->register_method ({
56 subclass => "PVE::API2::LXC",
57 path => 'lxc',
58});
59
38db610a
DM
60__PACKAGE__->register_method ({
61 subclass => "PVE::API2::Ceph",
62 path => 'ceph',
63});
64
bf58f8dd
DM
65__PACKAGE__->register_method ({
66 subclass => "PVE::API2::VZDump",
67 path => 'vzdump',
68});
69
aff192e6
DM
70__PACKAGE__->register_method ({
71 subclass => "PVE::API2::Services",
72 path => 'services',
73});
74
21ace8d3
DM
75__PACKAGE__->register_method ({
76 subclass => "PVE::API2::Subscription",
77 path => 'subscription',
78});
79
aff192e6
DM
80__PACKAGE__->register_method ({
81 subclass => "PVE::API2::Network",
82 path => 'network',
83});
84
85__PACKAGE__->register_method ({
86 subclass => "PVE::API2::Tasks",
87 path => 'tasks',
88});
89
90__PACKAGE__->register_method ({
91 subclass => "PVE::API2::Storage::Scan",
92 path => 'scan',
93});
94
95__PACKAGE__->register_method ({
96 subclass => "PVE::API2::Storage::Status",
97 path => 'storage',
98});
99
e781363f
DC
100__PACKAGE__->register_method ({
101 subclass => "PVE::API2::Disks",
102 path => 'disks',
103});
104
21299915
DM
105__PACKAGE__->register_method ({
106 subclass => "PVE::API2::APT",
107 path => 'apt',
108});
109
4a07fced
DM
110__PACKAGE__->register_method ({
111 subclass => "PVE::API2::Firewall::Host",
112 path => 'firewall',
113});
114
aff192e6
DM
115__PACKAGE__->register_method ({
116 name => 'index',
117 path => '',
118 method => 'GET',
119 permissions => { user => 'all' },
120 description => "Node index.",
121 parameters => {
122 additionalProperties => 0,
123 properties => {
124 node => get_standard_option('pve-node'),
125 },
126 },
127 returns => {
128 type => 'array',
129 items => {
130 type => "object",
131 properties => {},
132 },
133 links => [ { rel => 'child', href => "{name}" } ],
134 },
135 code => sub {
136 my ($param) = @_;
137
138 my $result = [
38db610a 139 { name => 'ceph' },
e781363f 140 { name => 'disks' },
21299915 141 { name => 'apt' },
8747a9ec 142 { name => 'version' },
aff192e6
DM
143 { name => 'syslog' },
144 { name => 'status' },
21ace8d3 145 { name => 'subscription' },
34ada77a 146 { name => 'report' },
aff192e6
DM
147 { name => 'tasks' },
148 { name => 'rrd' }, # fixme: remove?
149 { name => 'rrddata' },# fixme: remove?
150 { name => 'vncshell' },
2d802f8c 151 { name => 'spiceshell' },
aff192e6
DM
152 { name => 'time' },
153 { name => 'dns' },
154 { name => 'services' },
155 { name => 'scan' },
156 { name => 'storage' },
aff192e6 157 { name => 'qemu' },
989b7743 158 { name => 'lxc' },
bf58f8dd 159 { name => 'vzdump' },
aff192e6 160 { name => 'network' },
c9164975 161 { name => 'aplinfo' },
b92400b6
DM
162 { name => 'startall' },
163 { name => 'stopall' },
0455911d 164 { name => 'netstat' },
4a07fced 165 { name => 'firewall' },
aff192e6
DM
166 ];
167
168 return $result;
169 }});
170
8747a9ec
DM
171__PACKAGE__->register_method ({
172 name => 'version',
173 path => 'version',
174 method => 'GET',
175 proxyto => 'node',
176 permissions => { user => 'all' },
177 description => "API version details",
178 parameters => {
179 additionalProperties => 0,
180 properties => {
181 node => get_standard_option('pve-node'),
182 },
183 },
184 returns => {
185 type => "object",
186 properties => {
187 version => { type => 'string' },
188 release => { type => 'string' },
189 repoid => { type => 'string' },
190 },
191 },
192 code => sub {
193 my ($resp, $param) = @_;
194
195 return PVE::pvecfg::version_info();
196 }});
197
aff192e6
DM
198__PACKAGE__->register_method({
199 name => 'status',
200 path => 'status',
201 method => 'GET',
202 permissions => {
7d020b42 203 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
204 },
205 description => "Read node status",
206 proxyto => 'node',
207 parameters => {
208 additionalProperties => 0,
209 properties => {
210 node => get_standard_option('pve-node'),
211 },
212 },
213 returns => {
214 type => "object",
215 properties => {
216
217 },
218 },
219 code => sub {
220 my ($param) = @_;
221
222 my $res = {
223 uptime => 0,
224 idle => 0,
225 };
226
227 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
228 $res->{uptime} = $uptime;
229
230 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
231 $res->{loadavg} = [ $avg1, $avg5, $avg15];
232
233 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
234
235 $res->{kversion} = "$sysname $release $version";
236
237 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
238
239 my $stat = PVE::ProcFSTools::read_proc_stat();
240 $res->{cpu} = $stat->{cpu};
241 $res->{wait} = $stat->{wait};
242
243 my $meminfo = PVE::ProcFSTools::read_meminfo();
244 $res->{memory} = {
245 free => $meminfo->{memfree},
246 total => $meminfo->{memtotal},
247 used => $meminfo->{memused},
248 };
20539e0c
DM
249
250 $res->{ksm} = {
251 shared => $meminfo->{memshared},
252 };
253
aff192e6
DM
254 $res->{swap} = {
255 free => $meminfo->{swapfree},
256 total => $meminfo->{swaptotal},
257 used => $meminfo->{swapused},
258 };
259
260 $res->{pveversion} = PVE::pvecfg::package() . "/" .
8747a9ec 261 PVE::pvecfg::version_text();
aff192e6
DM
262
263 my $dinfo = df('/', 1); # output is bytes
264
265 $res->{rootfs} = {
266 total => $dinfo->{blocks},
267 avail => $dinfo->{bavail},
268 used => $dinfo->{used},
269 free => $dinfo->{bavail} - $dinfo->{used},
270 };
271
272 return $res;
273 }});
274
0455911d
SP
275__PACKAGE__->register_method({
276 name => 'netstat',
277 path => 'netstat',
278 method => 'GET',
279 permissions => {
280 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
281 },
282 description => "Read tap/vm network device interface counters",
283 proxyto => 'node',
284 parameters => {
285 additionalProperties => 0,
286 properties => {
287 node => get_standard_option('pve-node'),
288 },
289 },
290 returns => {
291 type => "array",
292 items => {
293 type => "object",
294 properties => {},
295 },
296 },
297 code => sub {
298 my ($param) = @_;
299
300 my $res = [ ];
301
302 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
303 foreach my $dev (keys %$netdev) {
675946f2 304 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
0455911d
SP
305 my $vmid = $1;
306 my $netid = $2;
307
308 push(
309 @$res,
310 {
311 vmid => $vmid,
312 dev => "net$netid",
313 in => $netdev->{$dev}->{transmit},
314 out => $netdev->{$dev}->{receive},
315 }
316 );
317 }
318
319 return $res;
320 }});
321
87c3e931
SP
322__PACKAGE__->register_method({
323 name => 'execute',
324 path => 'execute',
40993593 325 method => 'POST',
87c3e931
SP
326 permissions => {
327 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
328 },
40993593 329 description => "Execute multiple commands in order.",
87c3e931 330 proxyto => 'node',
40993593 331 protected => 1, # avoid problems with proxy code
87c3e931
SP
332 parameters => {
333 additionalProperties => 0,
334 properties => {
335 node => get_standard_option('pve-node'),
40993593
DM
336 commands => {
337 description => "JSON encoded array of commands.",
338 type => "string",
339 }
87c3e931
SP
340 },
341 },
342 returns => {
343 type => 'array',
344 properties => {
345
346 },
347 },
348 code => sub {
349 my ($param) = @_;
350 my $res = [];
351
40993593
DM
352 my $rpcenv = PVE::RPCEnvironment::get();
353 my $user = $rpcenv->get_user();
354
355 my $commands = eval { decode_json($param->{commands}); };
356
87c3e931
SP
357 die "commands param did not contain valid JSON: $@" if $@;
358 die "commands is not an array" if ref($commands) ne "ARRAY";
359
360 foreach my $cmd (@$commands) {
87c3e931 361 eval {
40993593
DM
362 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path} || !$cmd->{method});
363
364 $cmd->{args} //= {};
365
366 my $path = "nodes/$param->{node}/$cmd->{path}";
367
368 my $uri_param = {};
369 my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
370 if (!$handler || !$info) {
371 die "no handler for '$path'\n";
372 }
373
374 foreach my $p (keys %{$cmd->{args}}) {
375 raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
376 $uri_param->{$p} = $cmd->{args}->{$p};
377 }
378
379 # check access permissions
380 $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
381
382 push @$res, {
383 status => HTTP_OK,
a51ceeb7 384 data => $handler->handle($info, $uri_param),
40993593 385 };
87c3e931 386 };
40993593
DM
387 if (my $err = $@) {
388 my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
389 if (ref($err) eq "PVE::Exception") {
390 $resp->{status} = $err->{code} if $err->{code};
391 $resp->{errors} = $err->{errors} if $err->{errors};
392 $resp->{message} = $err->{msg};
393 } else {
394 $resp->{message} = $err;
395 }
396 push @$res, $resp;
397 }
87c3e931
SP
398 }
399
400 return $res;
401 }});
402
403
aff192e6
DM
404__PACKAGE__->register_method({
405 name => 'node_cmd',
406 path => 'status',
407 method => 'POST',
408 permissions => {
7d020b42 409 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
aff192e6
DM
410 },
411 protected => 1,
412 description => "Reboot or shutdown a node.",
413 proxyto => 'node',
414 parameters => {
415 additionalProperties => 0,
416 properties => {
417 node => get_standard_option('pve-node'),
418 command => {
419 description => "Specify the command.",
420 type => 'string',
421 enum => [qw(reboot shutdown)],
422 },
423 },
424 },
425 returns => { type => "null" },
426 code => sub {
427 my ($param) = @_;
428
429 if ($param->{command} eq 'reboot') {
430 system ("(sleep 2;/sbin/reboot)&");
431 } elsif ($param->{command} eq 'shutdown') {
432 system ("(sleep 2;/sbin/poweroff)&");
433 }
434
435 return undef;
436 }});
437
438
439__PACKAGE__->register_method({
440 name => 'rrd',
441 path => 'rrd',
442 method => 'GET',
443 protected => 1, # fixme: can we avoid that?
444 permissions => {
7d020b42 445 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
446 },
447 description => "Read node RRD statistics (returns PNG)",
448 parameters => {
449 additionalProperties => 0,
450 properties => {
451 node => get_standard_option('pve-node'),
452 timeframe => {
453 description => "Specify the time frame you are interested in.",
454 type => 'string',
455 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
456 },
457 ds => {
458 description => "The list of datasources you want to display.",
459 type => 'string', format => 'pve-configid-list',
460 },
461 cf => {
462 description => "The RRD consolidation function",
463 type => 'string',
464 enum => [ 'AVERAGE', 'MAX' ],
465 optional => 1,
466 },
467 },
468 },
469 returns => {
470 type => "object",
471 properties => {
472 filename => { type => 'string' },
473 },
474 },
475 code => sub {
476 my ($param) = @_;
477
478 return PVE::Cluster::create_rrd_graph(
479 "pve2-node/$param->{node}", $param->{timeframe},
480 $param->{ds}, $param->{cf});
481
482 }});
483
484__PACKAGE__->register_method({
485 name => 'rrddata',
486 path => 'rrddata',
487 method => 'GET',
488 protected => 1, # fixme: can we avoid that?
489 permissions => {
7d020b42 490 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
491 },
492 description => "Read node RRD statistics",
493 parameters => {
494 additionalProperties => 0,
495 properties => {
496 node => get_standard_option('pve-node'),
497 timeframe => {
498 description => "Specify the time frame you are interested in.",
499 type => 'string',
500 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
501 },
502 cf => {
503 description => "The RRD consolidation function",
504 type => 'string',
505 enum => [ 'AVERAGE', 'MAX' ],
506 optional => 1,
507 },
508 },
509 },
510 returns => {
511 type => "array",
512 items => {
513 type => "object",
514 properties => {},
515 },
516 },
517 code => sub {
518 my ($param) = @_;
519
520 return PVE::Cluster::create_rrd_data(
521 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
522 }});
523
524__PACKAGE__->register_method({
525 name => 'syslog',
526 path => 'syslog',
527 method => 'GET',
528 description => "Read system log",
529 proxyto => 'node',
530 permissions => {
7d020b42 531 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
aff192e6
DM
532 },
533 protected => 1,
534 parameters => {
535 additionalProperties => 0,
536 properties => {
537 node => get_standard_option('pve-node'),
538 start => {
539 type => 'integer',
540 minimum => 0,
541 optional => 1,
542 },
543 limit => {
544 type => 'integer',
545 minimum => 0,
546 optional => 1,
547 },
01b753b6
TL
548 since => {
549 type=> 'string',
550 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
551 description => "Display all log since this date-time string.",
552 optional => 1,
553 },
554 until => {
555 type=> 'string',
556 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
557 description => "Display all log until this date-time string.",
558 optional => 1,
559 },
aff192e6
DM
560 },
561 },
562 returns => {
563 type => 'array',
564 items => {
565 type => "object",
566 properties => {
567 n => {
568 description=> "Line number",
569 type=> 'integer',
570 },
571 t => {
572 description=> "Line text",
573 type => 'string',
574 }
575 }
576 }
577 },
578 code => sub {
579 my ($param) = @_;
580
aff192e6
DM
581 my $rpcenv = PVE::RPCEnvironment::get();
582 my $user = $rpcenv->get_user();
583 my $node = $param->{node};
584
01b753b6
TL
585 my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
586 $param->{since}, $param->{until});
ff9c330c
DM
587
588 $rpcenv->set_result_attrib('total', $count);
01b753b6
TL
589
590 return $lines;
ff9c330c
DM
591 }});
592
aff192e6
DM
593my $sslcert;
594
595__PACKAGE__->register_method ({
596 name => 'vncshell',
597 path => 'vncshell',
598 method => 'POST',
599 protected => 1,
600 permissions => {
d0289a19 601 description => "Restricted to users on realm 'pam'",
7d020b42 602 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
aff192e6
DM
603 },
604 description => "Creates a VNC Shell proxy.",
605 parameters => {
606 additionalProperties => 0,
607 properties => {
608 node => get_standard_option('pve-node'),
3a76893d
DM
609 upgrade => {
610 type => 'boolean',
611 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
612 optional => 1,
613 default => 0,
614 },
8dfca17e
DM
615 websocket => {
616 optional => 1,
617 type => 'boolean',
618 description => "use websocket instead of standard vnc.",
619 },
aff192e6
DM
620 },
621 },
622 returns => {
623 additionalProperties => 0,
624 properties => {
625 user => { type => 'string' },
626 ticket => { type => 'string' },
627 cert => { type => 'string' },
628 port => { type => 'integer' },
629 upid => { type => 'string' },
630 },
631 },
632 code => sub {
633 my ($param) = @_;
634
635 my $rpcenv = PVE::RPCEnvironment::get();
636
d553e535 637 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
d0289a19
DM
638
639 raise_perm_exc("realm != pam") if $realm ne 'pam';
aff192e6 640
3a76893d
DM
641 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
642
aff192e6
DM
643 my $node = $param->{node};
644
57ebda08
DM
645 my $authpath = "/nodes/$node";
646
647 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
648
aff192e6
DM
649 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
650 if !$sslcert;
651
eb6d7497 652 my ($remip, $family);
b179a622 653
aff192e6 654 if ($node ne PVE::INotify::nodename()) {
eb6d7497
WB
655 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
656 } else {
657 $family = PVE::Tools::get_host_address_family($node);
aff192e6
DM
658 }
659
eb6d7497
WB
660 my $port = PVE::Tools::next_vnc_port($family);
661
aff192e6
DM
662 # NOTE: vncterm VNC traffic is already TLS encrypted,
663 # so we select the fastest chipher here (or 'none'?)
664 my $remcmd = $remip ?
f2c68844 665 ['/usr/bin/ssh', '-t', $remip] : [];
aff192e6 666
3a76893d
DM
667 my $shcmd;
668
669 if ($user eq 'root@pam') {
670 if ($param->{upgrade}) {
ea5eb430
DM
671 my $upgradecmd = "pveupgrade --shell";
672 $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
673 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
3a76893d 674 } else {
e890b92d 675 $shcmd = [ '/bin/login', '-f', 'root' ];
3a76893d
DM
676 }
677 } else {
678 $shcmd = [ '/bin/login' ];
679 }
aff192e6
DM
680
681 my $timeout = 10;
682
6d394492 683 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
57ebda08 684 '-timeout', $timeout, '-authpath', $authpath,
8dfca17e
DM
685 '-perm', 'Sys.Console'];
686
687 if ($param->{websocket}) {
688 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
689 push @$cmd, '-notls', '-listen', 'localhost';
690 }
691
692 push @$cmd, '-c', @$remcmd, @$shcmd;
aff192e6
DM
693
694 my $realcmd = sub {
695 my $upid = shift;
696
697 syslog ('info', "starting vnc proxy $upid\n");
698
6d394492 699 my $cmdstr = join (' ', @$cmd);
aff192e6
DM
700 syslog ('info', "launch command: $cmdstr");
701
6d394492
DM
702 eval {
703 foreach my $k (keys %ENV) {
8dfca17e 704 next if $k eq 'PVE_VNC_TICKET';
b0d4b407 705 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
6d394492
DM
706 delete $ENV{$k};
707 }
708 $ENV{PWD} = '/';
709
b0d4b407 710 PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
6d394492
DM
711 };
712 if (my $err = $@) {
713 syslog ('err', $err);
aff192e6
DM
714 }
715
716 return;
717 };
718
719 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
6806a0f8
DM
720
721 PVE::Tools::wait_for_vnc_port($port);
aff192e6
DM
722
723 return {
724 user => $user,
725 ticket => $ticket,
726 port => $port,
727 upid => $upid,
728 cert => $sslcert,
729 };
730 }});
731
8dfca17e
DM
732__PACKAGE__->register_method({
733 name => 'vncwebsocket',
734 path => 'vncwebsocket',
735 method => 'GET',
736 permissions => {
737 description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
738 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
739 },
740 description => "Opens a weksocket for VNC traffic.",
741 parameters => {
742 additionalProperties => 0,
743 properties => {
744 node => get_standard_option('pve-node'),
745 vncticket => {
746 description => "Ticket from previous call to vncproxy.",
747 type => 'string',
748 maxLength => 512,
749 },
750 port => {
751 description => "Port number returned by previous vncproxy call.",
752 type => 'integer',
753 minimum => 5900,
754 maximum => 5999,
755 },
756 },
757 },
758 returns => {
759 type => "object",
760 properties => {
761 port => { type => 'string' },
762 },
763 },
764 code => sub {
765 my ($param) = @_;
766
767 my $rpcenv = PVE::RPCEnvironment::get();
768
769 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
770
771 raise_perm_exc("realm != pam") if $realm ne 'pam';
772
773 my $authpath = "/nodes/$param->{node}";
774
775 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
776
777 my $port = $param->{port};
778
779 return { port => $port };
780 }});
781
2d802f8c
DM
782__PACKAGE__->register_method ({
783 name => 'spiceshell',
784 path => 'spiceshell',
785 method => 'POST',
786 protected => 1,
787 proxyto => 'node',
788 permissions => {
789 description => "Restricted to users on realm 'pam'",
790 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
791 },
7774df78 792 description => "Creates a SPICE shell.",
2d802f8c
DM
793 parameters => {
794 additionalProperties => 0,
795 properties => {
796 node => get_standard_option('pve-node'),
7774df78 797 proxy => get_standard_option('spice-proxy', { optional => 1 }),
2d802f8c
DM
798 upgrade => {
799 type => 'boolean',
800 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
801 optional => 1,
802 default => 0,
803 },
804 },
805 },
7774df78 806 returns => get_standard_option('remote-viewer-config'),
2d802f8c
DM
807 code => sub {
808 my ($param) = @_;
809
810 my $rpcenv = PVE::RPCEnvironment::get();
811 my $authuser = $rpcenv->get_user();
812
813 my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
814
815 raise_perm_exc("realm != pam") if $realm ne 'pam';
816
817 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
818
819 my $node = $param->{node};
820 my $proxy = $param->{proxy};
2d802f8c
DM
821
822 my $authpath = "/nodes/$node";
b289829f 823 my $permissions = 'Sys.Console';
2d802f8c
DM
824
825 my $shcmd;
826
827 if ($user eq 'root@pam') {
828 if ($param->{upgrade}) {
829 my $upgradecmd = "pveupgrade --shell";
830 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
831 } else {
e890b92d 832 $shcmd = [ '/bin/login', '-f', 'root' ];
2d802f8c
DM
833 }
834 } else {
835 $shcmd = [ '/bin/login' ];
836 }
837
b289829f 838 my $title = "Shell on '$node'";
2d802f8c 839
b289829f 840 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
2d802f8c
DM
841 }});
842
aff192e6
DM
843__PACKAGE__->register_method({
844 name => 'dns',
845 path => 'dns',
846 method => 'GET',
847 permissions => {
7d020b42 848 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
849 },
850 description => "Read DNS settings.",
851 proxyto => 'node',
852 parameters => {
853 additionalProperties => 0,
854 properties => {
855 node => get_standard_option('pve-node'),
856 },
857 },
858 returns => {
859 type => "object",
860 additionalProperties => 0,
861 properties => {
862 search => {
863 description => "Search domain for host-name lookup.",
864 type => 'string',
865 optional => 1,
866 },
867 dns1 => {
868 description => 'First name server IP address.',
869 type => 'string',
870 optional => 1,
871 },
872 dns2 => {
873 description => 'Second name server IP address.',
874 type => 'string',
875 optional => 1,
876 },
877 dns3 => {
878 description => 'Third name server IP address.',
879 type => 'string',
880 optional => 1,
881 },
882 },
883 },
884 code => sub {
885 my ($param) = @_;
886
887 my $res = PVE::INotify::read_file('resolvconf');
888
889 return $res;
890 }});
891
892__PACKAGE__->register_method({
893 name => 'update_dns',
894 path => 'dns',
895 method => 'PUT',
896 description => "Write DNS settings.",
d0289a19
DM
897 permissions => {
898 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
899 },
aff192e6
DM
900 proxyto => 'node',
901 protected => 1,
902 parameters => {
903 additionalProperties => 0,
904 properties => {
905 node => get_standard_option('pve-node'),
906 search => {
907 description => "Search domain for host-name lookup.",
908 type => 'string',
909 },
910 dns1 => {
911 description => 'First name server IP address.',
7a9486a7 912 type => 'string', format => 'ip',
aff192e6
DM
913 optional => 1,
914 },
915 dns2 => {
916 description => 'Second name server IP address.',
7a9486a7 917 type => 'string', format => 'ip',
aff192e6
DM
918 optional => 1,
919 },
920 dns3 => {
921 description => 'Third name server IP address.',
7a9486a7 922 type => 'string', format => 'ip',
aff192e6
DM
923 optional => 1,
924 },
925 },
926 },
927 returns => { type => "null" },
928 code => sub {
929 my ($param) = @_;
930
931 PVE::INotify::update_file('resolvconf', $param);
932
933 return undef;
934 }});
935
936__PACKAGE__->register_method({
937 name => 'time',
938 path => 'time',
939 method => 'GET',
940 permissions => {
7d020b42
DM
941 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
942 },
aff192e6
DM
943 description => "Read server time and time zone settings.",
944 proxyto => 'node',
945 parameters => {
946 additionalProperties => 0,
947 properties => {
948 node => get_standard_option('pve-node'),
949 },
950 },
951 returns => {
952 type => "object",
953 additionalProperties => 0,
954 properties => {
955 timezone => {
956 description => "Time zone",
957 type => 'string',
958 },
959 time => {
960 description => "Seconds since 1970-01-01 00:00:00 UTC.",
961 type => 'integer',
962 minimum => 1297163644,
963 },
964 localtime => {
965 description => "Seconds since 1970-01-01 00:00:00 (local time)",
966 type => 'integer',
967 minimum => 1297163644,
968 },
969 },
970 },
971 code => sub {
972 my ($param) = @_;
973
974 my $ctime = time();
975 my $ltime = timegm_nocheck(localtime($ctime));
976 my $res = {
977 timezone => PVE::INotify::read_file('timezone'),
978 time => time(),
979 localtime => $ltime,
980 };
981
982 return $res;
983 }});
984
985__PACKAGE__->register_method({
986 name => 'set_timezone',
987 path => 'time',
988 method => 'PUT',
989 description => "Set time zone.",
d0289a19
DM
990 permissions => {
991 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
992 },
aff192e6
DM
993 proxyto => 'node',
994 protected => 1,
995 parameters => {
996 additionalProperties => 0,
997 properties => {
998 node => get_standard_option('pve-node'),
999 timezone => {
1000 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1001 type => 'string',
1002 },
1003 },
1004 },
1005 returns => { type => "null" },
1006 code => sub {
1007 my ($param) = @_;
1008
1009 PVE::INotify::write_file('timezone', $param->{timezone});
1010
1011 return undef;
1012 }});
1013
c9164975
DM
1014__PACKAGE__->register_method({
1015 name => 'aplinfo',
1016 path => 'aplinfo',
1017 method => 'GET',
1018 permissions => {
1019 user => 'all',
1020 },
1021 description => "Get list of appliances.",
1022 proxyto => 'node',
1023 parameters => {
1024 additionalProperties => 0,
1025 properties => {
1026 node => get_standard_option('pve-node'),
1027 },
1028 },
1029 returns => {
1030 type => 'array',
1031 items => {
1032 type => "object",
1033 properties => {},
1034 },
1035 },
1036 code => sub {
1037 my ($param) = @_;
1038
1039 my $res = [];
1040
1041 my $list = PVE::APLInfo::load_data();
1042
1043 foreach my $template (keys %{$list->{all}}) {
1044 my $pd = $list->{all}->{$template};
1045 next if $pd->{'package'} eq 'pve-web-news';
1046 push @$res, $pd;
1047 }
1048
1049 return $res;
1050 }});
1051
0532bd28
DM
1052__PACKAGE__->register_method({
1053 name => 'apl_download',
1054 path => 'aplinfo',
1055 method => 'POST',
1056 permissions => {
1057 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1058 },
1059 description => "Download appliance templates.",
1060 proxyto => 'node',
1061 protected => 1,
1062 parameters => {
1063 additionalProperties => 0,
1064 properties => {
1065 node => get_standard_option('pve-node'),
82282acf 1066 storage => get_standard_option('pve-storage-id', {
62180d0f 1067 description => "The storage where the template will be stored",
82282acf
WL
1068 completion => \&PVE::Storage::complete_storage_enabled,
1069 }),
1070 template => { type => 'string',
1071 description => "The template wich will downloaded",
1072 maxLength => 255,
1073 completion => \&complete_templet_repo,
1074 },
0532bd28
DM
1075 },
1076 },
1077 returns => { type => "string" },
1078 code => sub {
1079 my ($param) = @_;
1080
1081 my $rpcenv = PVE::RPCEnvironment::get();
1082
1083 my $user = $rpcenv->get_user();
1084
1085 my $node = $param->{node};
1086
1087 my $list = PVE::APLInfo::load_data();
1088
1089 my $template = $param->{template};
1090 my $pd = $list->{all}->{$template};
1091
1092 raise_param_exc({ template => "no such template"}) if !$pd;
1093
bbcfdc08 1094 my $cfg = PVE::Storage::config();
0532bd28
DM
1095 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1096
761a2b31
DM
1097 die "unknown template type '$pd->{type}'\n"
1098 if !($pd->{type} eq 'openvz' || $pd->{type} eq 'lxc');
0532bd28
DM
1099
1100 die "storage '$param->{storage}' does not support templates\n"
1101 if !$scfg->{content}->{vztmpl};
1102
1103 my $src = $pd->{location};
1104 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
1105 my $dest = "$tmpldir/$template";
1106 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1107
1108 my $worker = sub {
1109 my $upid = shift;
1110
1111 print "starting template download from: $src\n";
1112 print "target file: $dest\n";
1113
3b81e495
FG
1114 my $check_hash = sub {
1115 my ($template_info, $filename, $noerr) = @_;
1116
1117 my $digest;
1118 my $expected;
1119
1120 eval {
79be6db3
FG
1121 open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
1122 binmode($fh);
95f99e8c
FG
1123 if (defined($template_info->{sha512sum})) {
1124 $expected = $template_info->{sha512sum};
79be6db3 1125 $digest = Digest::SHA->new(512)->addfile($fh)->hexdigest;
95f99e8c 1126 } elsif (defined($template_info->{md5sum})) {
3b81e495 1127 #fallback to MD5
95f99e8c 1128 $expected = $template_info->{md5sum};
3b81e495 1129 $digest = Digest::MD5->new->addfile($fh)->hexdigest;
3b81e495
FG
1130 } else {
1131 die "no expected checksum defined";
1132 }
79be6db3 1133 close($fh);
3b81e495
FG
1134 };
1135
1136 die "checking hash failed - $@\n" if $@ && !$noerr;
0532bd28 1137
3b81e495
FG
1138 return ($digest, $digest ? lc($digest) eq lc($expected) : 0);
1139 };
1140
1141 eval {
0532bd28 1142 if (-f $dest) {
3b81e495 1143 my ($hash, $correct) = &$check_hash($pd, $dest, 1);
0532bd28 1144
3b81e495
FG
1145 if ($hash && $correct) {
1146 print "file already exists $hash - no need to download\n";
0532bd28
DM
1147 return;
1148 }
1149 }
1150
1151 local %ENV;
1152 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1153 if ($dccfg->{http_proxy}) {
1154 $ENV{http_proxy} = $dccfg->{http_proxy};
1155 }
1156
1157 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1158 if (system (@cmd) != 0) {
1159 die "download failed - $!\n";
1160 }
1161
3b81e495
FG
1162 my ($hash, $correct) = &$check_hash($pd, $tmpdest);
1163
1164 die "could not calculate checksum\n" if !$hash;
0532bd28 1165
3b81e495
FG
1166 if (!$correct) {
1167 my $expected = $pd->{sha512sum} // $pd->{md5sum};
1168 die "wrong checksum: $hash != $expected\n";
0532bd28
DM
1169 }
1170
c351eda9 1171 if (!rename($tmpdest, $dest)) {
0532bd28
DM
1172 die "unable to save file - $!\n";
1173 }
1174 };
1175 my $err = $@;
1176
1177 unlink $tmpdest;
1178
1179 if ($err) {
1180 print "\n";
1181 die $err if $err;
1182 }
1183
1184 print "download finished\n";
1185 };
1186
1187 return $rpcenv->fork_worker('download', undef, $user, $worker);
1188 }});
1189
34ada77a
EK
1190__PACKAGE__->register_method({
1191 name => 'report',
1192 path => 'report',
1193 method => 'GET',
1194 permissions => {
1195 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1196 },
6bbdf981 1197 protected => 1,
34ada77a
EK
1198 description => "Gather various systems information about a node",
1199 proxyto => 'node',
1200 parameters => {
1201 additionalProperties => 0,
1202 properties => {
1203 node => get_standard_option('pve-node'),
1204 },
1205 },
1206 returns => {
cdb5a209 1207 type => 'string',
34ada77a
EK
1208 },
1209 code => sub {
cdb5a209 1210 return PVE::Report::generate();
34ada77a 1211 }});
0532bd28 1212
b72cdbb7
TL
1213# returns a list of VMIDs, those can be filtered by
1214# * current parent node
1215# * vmid whitelist
1216# * guest is a template (default: skip)
1217# * guest is HA manged (default: skip)
1218my $get_filtered_vmlist = sub {
1219 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
b92400b6 1220
b92400b6
DM
1221 my $vmlist = PVE::Cluster::get_vmlist();
1222
b72cdbb7 1223 my $vms_allowed = {};
2a498506 1224 if (defined($vmfilter)) {
2a498506 1225 foreach my $vmid (PVE::Tools::split_list($vmfilter)) {
b72cdbb7 1226 $vms_allowed->{$vmid} = 1;
2a498506
DC
1227 }
1228 }
1229
b72cdbb7 1230 my $res = {};
b92400b6 1231 foreach my $vmid (keys %{$vmlist->{ids}}) {
b72cdbb7 1232 next if %$vms_allowed && !$vms_allowed->{$vmid};
b92400b6 1233
b72cdbb7
TL
1234 my $d = $vmlist->{ids}->{$vmid};
1235 next if $nodename && $d->{node} ne $nodename;
b92400b6 1236
b72cdbb7
TL
1237 eval {
1238 my $class;
2c27e4b7 1239 if ($d->{type} eq 'lxc') {
b72cdbb7 1240 $class = 'PVE::LXC::Config';
2c27e4b7 1241 } elsif ($d->{type} eq 'qemu') {
b72cdbb7 1242 $class = 'PVE::QemuConfig';
b92400b6
DM
1243 } else {
1244 die "unknown VM type '$d->{type}'\n";
1245 }
1246
b72cdbb7
TL
1247 my $conf = $class->load_config($vmid);
1248 return if !$templates && $class->is_template($conf);
1249 return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
04b2004b 1250
b72cdbb7
TL
1251 $res->{$vmid} = $conf;
1252 $res->{$vmid}->{type} = $d->{type};
b92400b6
DM
1253 };
1254 warn $@ if $@;
1255 }
1256
b72cdbb7
TL
1257 return $res;
1258};
1259
1260# return all VMs which should get started/stopped on power up/down
1261my $get_start_stop_list = sub {
1262 my ($nodename, $autostart, $vmfilter) = @_;
1263
ae9d10ca
TL
1264 # do not skip HA vms on force or if a specific VMID set is wanted
1265 my $include_ha_managed = defined($vmfilter) ? 1 : 0;
1266
1267 my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter, undef, $include_ha_managed);
b72cdbb7
TL
1268
1269 my $resList = {};
1270 foreach my $vmid (keys %$vmlist) {
1271 my $conf = $vmlist->{$vmid};
1272
1273 next if $autostart && !$conf->{onboot};
1274
1275 my $startup = {};
1276 if ($conf->{startup}) {
1277 $startup = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
1278 }
1279
1280 $startup->{order} = LONG_MAX if !defined($startup->{order});
1281
1282 $resList->{$startup->{order}}->{$vmid} = $startup;
1283 $resList->{$startup->{order}}->{$vmid}->{type} = $conf->{type};
1284 }
1285
b92400b6
DM
1286 return $resList;
1287};
1288
1289__PACKAGE__->register_method ({
1290 name => 'startall',
1291 path => 'startall',
1292 method => 'POST',
1293 protected => 1,
17e3b3b2
CS
1294 permissions => {
1295 check => ['perm', '/', [ 'VM.PowerMgmt' ]],
1296 },
9c8a09a7 1297 proxyto => 'node',
b92400b6
DM
1298 description => "Start all VMs and containers (when onboot=1).",
1299 parameters => {
1300 additionalProperties => 0,
1301 properties => {
1302 node => get_standard_option('pve-node'),
c09c7160
AD
1303 force => {
1304 optional => 1,
1305 type => 'boolean',
1306 description => "force if onboot=0.",
1307 },
2a498506
DC
1308 vms => {
1309 description => "Only consider Guests with these IDs.",
1310 type => 'string', format => 'pve-vmid-list',
1311 optional => 1,
1312 },
b92400b6
DM
1313 },
1314 },
1315 returns => {
1316 type => 'string',
1317 },
1318 code => sub {
1319 my ($param) = @_;
1320
1321 my $rpcenv = PVE::RPCEnvironment::get();
1322 my $authuser = $rpcenv->get_user();
1323
1324 my $nodename = $param->{node};
1325 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1326
c09c7160
AD
1327 my $force = $param->{force};
1328
b92400b6
DM
1329 my $code = sub {
1330
1331 $rpcenv->{type} = 'priv'; # to start tasks in background
1332
3947d0a0
DM
1333 if (!PVE::Cluster::check_cfs_quorum(1)) {
1334 print "waiting for quorum ...\n";
1335 do {
1336 sleep(1);
1337 } while (!PVE::Cluster::check_cfs_quorum(1));
1338 print "got quorum\n";
b92400b6 1339 }
c09c7160 1340 my $autostart = $force ? undef : 1;
2a498506 1341 my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms});
3f12bcfb
DM
1342
1343 # Note: use numeric sorting with <=>
5eec2b04 1344 foreach my $order (sort {$a <=> $b} keys %$startList) {
b92400b6
DM
1345 my $vmlist = $startList->{$order};
1346
3f12bcfb 1347 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
b92400b6
DM
1348 my $d = $vmlist->{$vmid};
1349
1350 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
b72cdbb7 1351
b92400b6
DM
1352 eval {
1353 my $default_delay = 0;
1354 my $upid;
1355
2c27e4b7
DM
1356 if ($d->{type} eq 'lxc') {
1357 return if PVE::LXC::check_running($vmid);
1358 print STDERR "Starting CT $vmid\n";
1b5e56f2 1359 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
2c27e4b7 1360 } elsif ($d->{type} eq 'qemu') {
d6c49392 1361 $default_delay = 3; # to reduce load
b92400b6
DM
1362 return if PVE::QemuServer::check_running($vmid, 1);
1363 print STDERR "Starting VM $vmid\n";
1364 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1365 } else {
1366 die "unknown VM type '$d->{type}'\n";
1367 }
1368
1369 my $res = PVE::Tools::upid_decode($upid);
1370 while (PVE::ProcFSTools::check_process_running($res->{pid})) {
1371 sleep(1);
1372 }
1373
1374 my $status = PVE::Tools::upid_read_status($upid);
1375 if ($status eq 'OK') {
1376 # use default delay to reduce load
1377 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1378 if ($delay > 0) {
1379 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1380 for (my $i = 0; $i < $delay; $i++) {
1381 sleep(1);
1382 }
1383 }
1384 } else {
2c27e4b7
DM
1385 if ($d->{type} eq 'lxc') {
1386 print STDERR "Starting CT $vmid failed: $status\n";
1387 } elsif ($d->{type} eq 'qemu') {
1388 print STDERR "Starting VM $vmid failed: status\n";
1389 }
b92400b6
DM
1390 }
1391 };
1392 warn $@ if $@;
1393 }
1394 }
1395 return;
1396 };
1397
1398 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1399 }});
1400
1401my $create_stop_worker = sub {
1402 my ($nodename, $type, $vmid, $down_timeout) = @_;
1403
1404 my $upid;
2c27e4b7
DM
1405 if ($type eq 'lxc') {
1406 return if !PVE::LXC::check_running($vmid);
1407 my $timeout = defined($down_timeout) ? int($down_timeout) : 60;
1408 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
1b5e56f2 1409 $upid = PVE::API2::LXC::Status->vm_shutdown({node => $nodename, vmid => $vmid,
2c27e4b7
DM
1410 timeout => $timeout, forceStop => 1 });
1411 } elsif ($type eq 'qemu') {
b92400b6
DM
1412 return if !PVE::QemuServer::check_running($vmid, 1);
1413 my $timeout = defined($down_timeout) ? int($down_timeout) : 60*3;
88ba9a1d 1414 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
b92400b6
DM
1415 $upid = PVE::API2::Qemu->vm_shutdown({node => $nodename, vmid => $vmid,
1416 timeout => $timeout, forceStop => 1 });
1417 } else {
1418 die "unknown VM type '$type'\n";
1419 }
1420
37bec895 1421 return $upid;
b92400b6
DM
1422};
1423
1424__PACKAGE__->register_method ({
1425 name => 'stopall',
1426 path => 'stopall',
1427 method => 'POST',
1428 protected => 1,
17e3b3b2
CS
1429 permissions => {
1430 check => ['perm', '/', [ 'VM.PowerMgmt' ]],
1431 },
9c8a09a7 1432 proxyto => 'node',
b92400b6
DM
1433 description => "Stop all VMs and Containers.",
1434 parameters => {
1435 additionalProperties => 0,
1436 properties => {
1437 node => get_standard_option('pve-node'),
2a498506
DC
1438 vms => {
1439 description => "Only consider Guests with these IDs.",
1440 type => 'string', format => 'pve-vmid-list',
1441 optional => 1,
1442 },
b92400b6
DM
1443 },
1444 },
1445 returns => {
1446 type => 'string',
1447 },
1448 code => sub {
1449 my ($param) = @_;
1450
1451 my $rpcenv = PVE::RPCEnvironment::get();
1452 my $authuser = $rpcenv->get_user();
1453
1454 my $nodename = $param->{node};
1455 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1456
1457 my $code = sub {
1458
1459 $rpcenv->{type} = 'priv'; # to start tasks in background
1460
2a498506 1461 my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms});
b92400b6
DM
1462
1463 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
89ceb802
TL
1464 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1465 # if not set by user spawn max cpu count number of workers
1466 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
b92400b6
DM
1467
1468 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1469 my $vmlist = $stopList->{$order};
1470 my $workers = {};
37bec895
DM
1471
1472 my $finish_worker = sub {
1473 my $pid = shift;
1474 my $d = $workers->{$pid};
1475 return if !$d;
1476 delete $workers->{$pid};
1477
1478 syslog('info', "end task $d->{upid}");
1479 };
1480
b92400b6
DM
1481 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1482 my $d = $vmlist->{$vmid};
37bec895
DM
1483 my $upid;
1484 eval { $upid = &$create_stop_worker($nodename, $d->{type}, $vmid, $d->{down}); };
b92400b6 1485 warn $@ if $@;
37bec895
DM
1486 next if !$upid;
1487
1488 my $res = PVE::Tools::upid_decode($upid, 1);
1489 next if !$res;
1490
1491 my $pid = $res->{pid};
1492
1493 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
b92400b6
DM
1494 while (scalar(keys %$workers) >= $maxWorkers) {
1495 foreach my $p (keys %$workers) {
1496 if (!PVE::ProcFSTools::check_process_running($p)) {
37bec895 1497 &$finish_worker($p);
b92400b6
DM
1498 }
1499 }
1500 sleep(1);
1501 }
1502 }
1503 while (scalar(keys %$workers)) {
1504 foreach my $p (keys %$workers) {
1505 if (!PVE::ProcFSTools::check_process_running($p)) {
37bec895 1506 &$finish_worker($p);
b92400b6
DM
1507 }
1508 }
1509 sleep(1);
1510 }
1511 }
37bec895
DM
1512
1513 syslog('info', "all VMs and CTs stopped");
1514
b92400b6
DM
1515 return;
1516 };
1517
1518 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
b92400b6
DM
1519 }});
1520
9c8a09a7
AD
1521my $create_migrate_worker = sub {
1522 my ($nodename, $type, $vmid, $target) = @_;
1523
1524 my $upid;
2c27e4b7
DM
1525 if ($type eq 'lxc') {
1526 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
1527 print STDERR "Migrating CT $vmid\n";
1528 $upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
1529 online => $online });
1530 } elsif ($type eq 'qemu') {
9c8a09a7
AD
1531 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
1532 print STDERR "Migrating VM $vmid\n";
1533 $upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
2c27e4b7 1534 online => $online });
9c8a09a7
AD
1535 } else {
1536 die "unknown VM type '$type'\n";
1537 }
1538
1539 my $res = PVE::Tools::upid_decode($upid);
1540
1541 return $res->{pid};
1542};
1543
1544__PACKAGE__->register_method ({
1545 name => 'migrateall',
1546 path => 'migrateall',
1547 method => 'POST',
1548 proxyto => 'node',
1549 protected => 1,
17e3b3b2
CS
1550 permissions => {
1551 check => ['perm', '/', [ 'VM.Migrate' ]],
1552 },
9c8a09a7
AD
1553 description => "Migrate all VMs and Containers.",
1554 parameters => {
1555 additionalProperties => 0,
1556 properties => {
1557 node => get_standard_option('pve-node'),
1558 target => get_standard_option('pve-node', { description => "Target node." }),
1559 maxworkers => {
89ceb802
TL
1560 description => "Maximal number of parallel migration job." .
1561 " If not set use 'max_workers' from datacenter.cfg," .
1562 " one of both must be set!",
1563 optional => 1,
9c8a09a7
AD
1564 type => 'integer',
1565 minimum => 1
1566 },
2a498506
DC
1567 vms => {
1568 description => "Only consider Guests with these IDs.",
1569 type => 'string', format => 'pve-vmid-list',
1570 optional => 1,
1571 },
9c8a09a7
AD
1572 },
1573 },
1574 returns => {
1575 type => 'string',
1576 },
1577 code => sub {
1578 my ($param) = @_;
1579
1580 my $rpcenv = PVE::RPCEnvironment::get();
1581 my $authuser = $rpcenv->get_user();
1582
1583 my $nodename = $param->{node};
1584 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1585
1586 my $target = $param->{target};
89ceb802
TL
1587
1588 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1589 # prefer parameter over datacenter cfg settings
1590 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
1591 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
9c8a09a7
AD
1592
1593 my $code = sub {
9c8a09a7
AD
1594 $rpcenv->{type} = 'priv'; # to start tasks in background
1595
f5c1dde5 1596 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
9c8a09a7 1597
f5c1dde5
TL
1598 my $workers = {};
1599 foreach my $vmid (sort keys %$vmlist) {
1600 my $d = $vmlist->{$vmid};
1601 my $pid;
1602 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
1603 warn $@ if $@;
1604 next if !$pid;
9c8a09a7 1605
f5c1dde5
TL
1606 $workers->{$pid} = 1;
1607 while (scalar(keys %$workers) >= $maxWorkers) {
9c8a09a7
AD
1608 foreach my $p (keys %$workers) {
1609 if (!PVE::ProcFSTools::check_process_running($p)) {
1610 delete $workers->{$p};
1611 }
1612 }
1613 sleep(1);
1614 }
1615 }
f5c1dde5
TL
1616 while (scalar(keys %$workers)) {
1617 foreach my $p (keys %$workers) {
1618 if (!PVE::ProcFSTools::check_process_running($p)) {
1619 delete $workers->{$p};
1620 }
1621 }
1622 sleep(1);
1623 }
9c8a09a7
AD
1624 return;
1625 };
1626
1627 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1628
1629 }});
1630
82282acf
WL
1631# bash completion helper
1632
1633sub complete_templet_repo {
1634 my ($cmdname, $pname, $cvalue) = @_;
1635
1636 my $repo = PVE::APLInfo::load_data();
1637 my $res = [];
1638 foreach my $templ (keys %{$repo->{all}}) {
1639 next if $templ !~ m/^$cvalue/;
1640 push @$res, $templ;
1641 }
1642
1643 return $res;
1644}
1645
aff192e6
DM
1646package PVE::API2::Nodes;
1647
1648use strict;
1649use warnings;
1650
1651use PVE::SafeSyslog;
1652use PVE::Cluster;
1653use PVE::RESTHandler;
1654use PVE::RPCEnvironment;
b193e4ac 1655use PVE::API2Tools;
aff192e6
DM
1656
1657use base qw(PVE::RESTHandler);
1658
1659__PACKAGE__->register_method ({
1660 subclass => "PVE::API2::Nodes::Nodeinfo",
1661 path => '{node}',
1662});
1663
1664__PACKAGE__->register_method ({
1665 name => 'index',
1666 path => '',
1667 method => 'GET',
1668 permissions => { user => 'all' },
1669 description => "Cluster node index.",
1670 parameters => {
1671 additionalProperties => 0,
1672 properties => {},
1673 },
1674 returns => {
1675 type => 'array',
1676 items => {
1677 type => "object",
1678 properties => {},
1679 },
b193e4ac 1680 links => [ { rel => 'child', href => "{node}" } ],
aff192e6
DM
1681 },
1682 code => sub {
1683 my ($param) = @_;
1684
1685 my $clinfo = PVE::Cluster::get_clinfo();
1686 my $res = [];
1687
b193e4ac
DM
1688 my $nodelist = PVE::Cluster::get_nodelist();
1689 my $members = PVE::Cluster::get_members();
aff192e6
DM
1690 my $rrd = PVE::Cluster::rrd_dump();
1691
b193e4ac
DM
1692 foreach my $node (@$nodelist) {
1693 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
aff192e6
DM
1694 push @$res, $entry;
1695 }
1696
1697 return $res;
1698 }});
1699
17001;