]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Nodes.pm
f28a3ffdc0791605338b44f6d83aa4e2bfc59595
[pve-manager.git] / PVE / API2 / Nodes.pm
1 package PVE::API2::Nodes::Nodeinfo;
2
3 use strict;
4 use warnings;
5 use POSIX qw(LONG_MAX);
6 use Filesys::Df;
7 use Time::Local qw(timegm_nocheck);
8 use HTTP::Status qw(:constants);
9 use PVE::pvecfg;
10 use PVE::Tools;
11 use PVE::API2Tools;
12 use PVE::ProcFSTools;
13 use PVE::SafeSyslog;
14 use PVE::Cluster qw(cfs_read_file);
15 use PVE::INotify;
16 use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
17 use PVE::RESTHandler;
18 use PVE::RPCEnvironment;
19 use PVE::JSONSchema qw(get_standard_option);
20 use PVE::AccessControl;
21 use PVE::Storage;
22 use PVE::Firewall;
23 use PVE::LXC;
24 use PVE::APLInfo;
25 use PVE::Report;
26 use PVE::HA::Env::PVE2;
27 use PVE::HA::Config;
28 use PVE::QemuConfig;
29 use PVE::QemuServer;
30 use PVE::API2::Subscription;
31 use PVE::API2::Services;
32 use PVE::API2::Network;
33 use PVE::API2::Tasks;
34 use PVE::API2::Storage::Scan;
35 use PVE::API2::Storage::Status;
36 use PVE::API2::Qemu;
37 use PVE::API2::LXC;
38 use PVE::API2::LXC::Status;
39 use PVE::API2::VZDump;
40 use PVE::API2::APT;
41 use PVE::API2::Ceph;
42 use PVE::API2::Firewall::Host;
43 use Digest::MD5;
44 use Digest::SHA;
45 use PVE::API2::Disks;
46 use JSON;
47
48 use base qw(PVE::RESTHandler);
49
50 __PACKAGE__->register_method ({
51 subclass => "PVE::API2::Qemu",
52 path => 'qemu',
53 });
54
55 __PACKAGE__->register_method ({
56 subclass => "PVE::API2::LXC",
57 path => 'lxc',
58 });
59
60 __PACKAGE__->register_method ({
61 subclass => "PVE::API2::Ceph",
62 path => 'ceph',
63 });
64
65 __PACKAGE__->register_method ({
66 subclass => "PVE::API2::VZDump",
67 path => 'vzdump',
68 });
69
70 __PACKAGE__->register_method ({
71 subclass => "PVE::API2::Services",
72 path => 'services',
73 });
74
75 __PACKAGE__->register_method ({
76 subclass => "PVE::API2::Subscription",
77 path => 'subscription',
78 });
79
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
100 __PACKAGE__->register_method ({
101 subclass => "PVE::API2::Disks",
102 path => 'disks',
103 });
104
105 __PACKAGE__->register_method ({
106 subclass => "PVE::API2::APT",
107 path => 'apt',
108 });
109
110 __PACKAGE__->register_method ({
111 subclass => "PVE::API2::Firewall::Host",
112 path => 'firewall',
113 });
114
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 = [
139 { name => 'ceph' },
140 { name => 'disks' },
141 { name => 'apt' },
142 { name => 'version' },
143 { name => 'syslog' },
144 { name => 'status' },
145 { name => 'subscription' },
146 { name => 'report' },
147 { name => 'tasks' },
148 { name => 'rrd' }, # fixme: remove?
149 { name => 'rrddata' },# fixme: remove?
150 { name => 'vncshell' },
151 { name => 'spiceshell' },
152 { name => 'time' },
153 { name => 'dns' },
154 { name => 'services' },
155 { name => 'scan' },
156 { name => 'storage' },
157 { name => 'qemu' },
158 { name => 'lxc' },
159 { name => 'vzdump' },
160 { name => 'network' },
161 { name => 'aplinfo' },
162 { name => 'startall' },
163 { name => 'stopall' },
164 { name => 'netstat' },
165 { name => 'firewall' },
166 ];
167
168 return $result;
169 }});
170
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
198 __PACKAGE__->register_method({
199 name => 'status',
200 path => 'status',
201 method => 'GET',
202 permissions => {
203 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
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 };
249
250 $res->{ksm} = {
251 shared => $meminfo->{memshared},
252 };
253
254 $res->{swap} = {
255 free => $meminfo->{swapfree},
256 total => $meminfo->{swaptotal},
257 used => $meminfo->{swapused},
258 };
259
260 $res->{pveversion} = PVE::pvecfg::package() . "/" .
261 PVE::pvecfg::version_text();
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
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) {
304 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
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
322 __PACKAGE__->register_method({
323 name => 'execute',
324 path => 'execute',
325 method => 'POST',
326 permissions => {
327 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
328 },
329 description => "Execute multiple commands in order.",
330 proxyto => 'node',
331 protected => 1, # avoid problems with proxy code
332 parameters => {
333 additionalProperties => 0,
334 properties => {
335 node => get_standard_option('pve-node'),
336 commands => {
337 description => "JSON encoded array of commands.",
338 type => "string",
339 }
340 },
341 },
342 returns => {
343 type => 'array',
344 properties => {
345
346 },
347 },
348 code => sub {
349 my ($param) = @_;
350 my $res = [];
351
352 my $rpcenv = PVE::RPCEnvironment::get();
353 my $user = $rpcenv->get_user();
354
355 my $commands = eval { decode_json($param->{commands}); };
356
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) {
361 eval {
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,
384 data => $handler->handle($info, $uri_param),
385 };
386 };
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 }
398 }
399
400 return $res;
401 }});
402
403
404 __PACKAGE__->register_method({
405 name => 'node_cmd',
406 path => 'status',
407 method => 'POST',
408 permissions => {
409 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
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 => {
445 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
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 => {
490 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
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 => {
531 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
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 },
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 },
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
581 my $rpcenv = PVE::RPCEnvironment::get();
582 my $user = $rpcenv->get_user();
583 my $node = $param->{node};
584
585 my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
586 $param->{since}, $param->{until});
587
588 $rpcenv->set_result_attrib('total', $count);
589
590 return $lines;
591 }});
592
593 my $sslcert;
594
595 __PACKAGE__->register_method ({
596 name => 'vncshell',
597 path => 'vncshell',
598 method => 'POST',
599 protected => 1,
600 permissions => {
601 description => "Restricted to users on realm 'pam'",
602 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
603 },
604 description => "Creates a VNC Shell proxy.",
605 parameters => {
606 additionalProperties => 0,
607 properties => {
608 node => get_standard_option('pve-node'),
609 upgrade => {
610 type => 'boolean',
611 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
612 optional => 1,
613 default => 0,
614 },
615 websocket => {
616 optional => 1,
617 type => 'boolean',
618 description => "use websocket instead of standard vnc.",
619 },
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
637 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
638
639 raise_perm_exc("realm != pam") if $realm ne 'pam';
640
641 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
642
643 my $node = $param->{node};
644
645 my $authpath = "/nodes/$node";
646
647 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
648
649 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
650 if !$sslcert;
651
652 my ($remip, $family);
653
654 if ($node ne PVE::INotify::nodename()) {
655 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
656 } else {
657 $family = PVE::Tools::get_host_address_family($node);
658 }
659
660 my $port = PVE::Tools::next_vnc_port($family);
661
662 # NOTE: vncterm VNC traffic is already TLS encrypted,
663 # so we select the fastest chipher here (or 'none'?)
664 my $remcmd = $remip ?
665 ['/usr/bin/ssh', '-t', $remip] : [];
666
667 my $shcmd;
668
669 if ($user eq 'root@pam') {
670 if ($param->{upgrade}) {
671 my $upgradecmd = "pveupgrade --shell";
672 $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
673 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
674 } else {
675 $shcmd = [ '/bin/bash', '-l' ];
676 }
677 } else {
678 $shcmd = [ '/bin/login' ];
679 }
680
681 my $timeout = 10;
682
683 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
684 '-timeout', $timeout, '-authpath', $authpath,
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;
693
694 my $realcmd = sub {
695 my $upid = shift;
696
697 syslog ('info', "starting vnc proxy $upid\n");
698
699 my $cmdstr = join (' ', @$cmd);
700 syslog ('info', "launch command: $cmdstr");
701
702 eval {
703 foreach my $k (keys %ENV) {
704 next if $k eq 'PVE_VNC_TICKET';
705 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
706 delete $ENV{$k};
707 }
708 $ENV{PWD} = '/';
709
710 PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
711 };
712 if (my $err = $@) {
713 syslog ('err', $err);
714 }
715
716 return;
717 };
718
719 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
720
721 PVE::Tools::wait_for_vnc_port($port);
722
723 return {
724 user => $user,
725 ticket => $ticket,
726 port => $port,
727 upid => $upid,
728 cert => $sslcert,
729 };
730 }});
731
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
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 },
792 description => "Creates a SPICE shell.",
793 parameters => {
794 additionalProperties => 0,
795 properties => {
796 node => get_standard_option('pve-node'),
797 proxy => get_standard_option('spice-proxy', { optional => 1 }),
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 },
806 returns => get_standard_option('remote-viewer-config'),
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};
821
822 my $authpath = "/nodes/$node";
823 my $permissions = 'Sys.Console';
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 {
832 $shcmd = [ '/bin/bash', '-l' ];
833 }
834 } else {
835 $shcmd = [ '/bin/login' ];
836 }
837
838 my $title = "Shell on '$node'";
839
840 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
841 }});
842
843 __PACKAGE__->register_method({
844 name => 'dns',
845 path => 'dns',
846 method => 'GET',
847 permissions => {
848 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
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.",
897 permissions => {
898 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
899 },
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.',
912 type => 'string', format => 'ip',
913 optional => 1,
914 },
915 dns2 => {
916 description => 'Second name server IP address.',
917 type => 'string', format => 'ip',
918 optional => 1,
919 },
920 dns3 => {
921 description => 'Third name server IP address.',
922 type => 'string', format => 'ip',
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 => {
941 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
942 },
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.",
990 permissions => {
991 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
992 },
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
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
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'),
1066 storage => get_standard_option('pve-storage-id', {
1067 description => "The storage where the template will be stored",
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 },
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
1094 my $cfg = PVE::Storage::config();
1095 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1096
1097 die "unknown template type '$pd->{type}'\n"
1098 if !($pd->{type} eq 'openvz' || $pd->{type} eq 'lxc');
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
1114 my $check_hash = sub {
1115 my ($template_info, $filename, $noerr) = @_;
1116
1117 my $digest;
1118 my $expected;
1119
1120 eval {
1121 open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
1122 binmode($fh);
1123 if (defined($template_info->{sha512sum})) {
1124 $expected = $template_info->{sha512sum};
1125 $digest = Digest::SHA->new(512)->addfile($fh)->hexdigest;
1126 } elsif (defined($template_info->{md5sum})) {
1127 #fallback to MD5
1128 $expected = $template_info->{md5sum};
1129 $digest = Digest::MD5->new->addfile($fh)->hexdigest;
1130 } else {
1131 die "no expected checksum defined";
1132 }
1133 close($fh);
1134 };
1135
1136 die "checking hash failed - $@\n" if $@ && !$noerr;
1137
1138 return ($digest, $digest ? lc($digest) eq lc($expected) : 0);
1139 };
1140
1141 eval {
1142 if (-f $dest) {
1143 my ($hash, $correct) = &$check_hash($pd, $dest, 1);
1144
1145 if ($hash && $correct) {
1146 print "file already exists $hash - no need to download\n";
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
1162 my ($hash, $correct) = &$check_hash($pd, $tmpdest);
1163
1164 die "could not calculate checksum\n" if !$hash;
1165
1166 if (!$correct) {
1167 my $expected = $pd->{sha512sum} // $pd->{md5sum};
1168 die "wrong checksum: $hash != $expected\n";
1169 }
1170
1171 if (!rename($tmpdest, $dest)) {
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
1190 __PACKAGE__->register_method({
1191 name => 'report',
1192 path => 'report',
1193 method => 'GET',
1194 permissions => {
1195 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1196 },
1197 protected => 1,
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 => {
1207 type => 'string',
1208 },
1209 code => sub {
1210 return PVE::Report::generate();
1211 }});
1212
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)
1218 my $get_filtered_vmlist = sub {
1219 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
1220
1221 my $vmlist = PVE::Cluster::get_vmlist();
1222
1223 my $vms_allowed = {};
1224 if (defined($vmfilter)) {
1225 foreach my $vmid (PVE::Tools::split_list($vmfilter)) {
1226 $vms_allowed->{$vmid} = 1;
1227 }
1228 }
1229
1230 my $res = {};
1231 foreach my $vmid (keys %{$vmlist->{ids}}) {
1232 next if %$vms_allowed && !$vms_allowed->{$vmid};
1233
1234 my $d = $vmlist->{ids}->{$vmid};
1235 next if $nodename && $d->{node} ne $nodename;
1236
1237 eval {
1238 my $class;
1239 if ($d->{type} eq 'lxc') {
1240 $class = 'PVE::LXC::Config';
1241 } elsif ($d->{type} eq 'qemu') {
1242 $class = 'PVE::QemuConfig';
1243 } else {
1244 die "unknown VM type '$d->{type}'\n";
1245 }
1246
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);
1250
1251 $res->{$vmid} = $conf;
1252 $res->{$vmid}->{type} = $d->{type};
1253 };
1254 warn $@ if $@;
1255 }
1256
1257 return $res;
1258 };
1259
1260 # return all VMs which should get started/stopped on power up/down
1261 my $get_start_stop_list = sub {
1262 my ($nodename, $autostart, $vmfilter) = @_;
1263
1264 my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter);
1265
1266 my $resList = {};
1267 foreach my $vmid (keys %$vmlist) {
1268 my $conf = $vmlist->{$vmid};
1269
1270 next if $autostart && !$conf->{onboot};
1271
1272 my $startup = {};
1273 if ($conf->{startup}) {
1274 $startup = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
1275 }
1276
1277 $startup->{order} = LONG_MAX if !defined($startup->{order});
1278
1279 $resList->{$startup->{order}}->{$vmid} = $startup;
1280 $resList->{$startup->{order}}->{$vmid}->{type} = $conf->{type};
1281 }
1282
1283 return $resList;
1284 };
1285
1286 __PACKAGE__->register_method ({
1287 name => 'startall',
1288 path => 'startall',
1289 method => 'POST',
1290 protected => 1,
1291 permissions => {
1292 check => ['perm', '/', [ 'VM.PowerMgmt' ]],
1293 },
1294 proxyto => 'node',
1295 description => "Start all VMs and containers (when onboot=1).",
1296 parameters => {
1297 additionalProperties => 0,
1298 properties => {
1299 node => get_standard_option('pve-node'),
1300 force => {
1301 optional => 1,
1302 type => 'boolean',
1303 description => "force if onboot=0.",
1304 },
1305 vms => {
1306 description => "Only consider Guests with these IDs.",
1307 type => 'string', format => 'pve-vmid-list',
1308 optional => 1,
1309 },
1310 },
1311 },
1312 returns => {
1313 type => 'string',
1314 },
1315 code => sub {
1316 my ($param) = @_;
1317
1318 my $rpcenv = PVE::RPCEnvironment::get();
1319 my $authuser = $rpcenv->get_user();
1320
1321 my $nodename = $param->{node};
1322 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1323
1324 my $force = $param->{force};
1325
1326 my $code = sub {
1327
1328 $rpcenv->{type} = 'priv'; # to start tasks in background
1329
1330 if (!PVE::Cluster::check_cfs_quorum(1)) {
1331 print "waiting for quorum ...\n";
1332 do {
1333 sleep(1);
1334 } while (!PVE::Cluster::check_cfs_quorum(1));
1335 print "got quorum\n";
1336 }
1337 my $autostart = $force ? undef : 1;
1338 my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms});
1339
1340 # Note: use numeric sorting with <=>
1341 foreach my $order (sort {$a <=> $b} keys %$startList) {
1342 my $vmlist = $startList->{$order};
1343
1344 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1345 my $d = $vmlist->{$vmid};
1346
1347 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
1348
1349 eval {
1350 my $default_delay = 0;
1351 my $upid;
1352
1353 if ($d->{type} eq 'lxc') {
1354 return if PVE::LXC::check_running($vmid);
1355 print STDERR "Starting CT $vmid\n";
1356 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
1357 } elsif ($d->{type} eq 'qemu') {
1358 $default_delay = 3; # to reduce load
1359 return if PVE::QemuServer::check_running($vmid, 1);
1360 print STDERR "Starting VM $vmid\n";
1361 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1362 } else {
1363 die "unknown VM type '$d->{type}'\n";
1364 }
1365
1366 my $res = PVE::Tools::upid_decode($upid);
1367 while (PVE::ProcFSTools::check_process_running($res->{pid})) {
1368 sleep(1);
1369 }
1370
1371 my $status = PVE::Tools::upid_read_status($upid);
1372 if ($status eq 'OK') {
1373 # use default delay to reduce load
1374 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1375 if ($delay > 0) {
1376 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1377 for (my $i = 0; $i < $delay; $i++) {
1378 sleep(1);
1379 }
1380 }
1381 } else {
1382 if ($d->{type} eq 'lxc') {
1383 print STDERR "Starting CT $vmid failed: $status\n";
1384 } elsif ($d->{type} eq 'qemu') {
1385 print STDERR "Starting VM $vmid failed: status\n";
1386 }
1387 }
1388 };
1389 warn $@ if $@;
1390 }
1391 }
1392 return;
1393 };
1394
1395 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1396 }});
1397
1398 my $create_stop_worker = sub {
1399 my ($nodename, $type, $vmid, $down_timeout) = @_;
1400
1401 my $upid;
1402 if ($type eq 'lxc') {
1403 return if !PVE::LXC::check_running($vmid);
1404 my $timeout = defined($down_timeout) ? int($down_timeout) : 60;
1405 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
1406 $upid = PVE::API2::LXC::Status->vm_shutdown({node => $nodename, vmid => $vmid,
1407 timeout => $timeout, forceStop => 1 });
1408 } elsif ($type eq 'qemu') {
1409 return if !PVE::QemuServer::check_running($vmid, 1);
1410 my $timeout = defined($down_timeout) ? int($down_timeout) : 60*3;
1411 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
1412 $upid = PVE::API2::Qemu->vm_shutdown({node => $nodename, vmid => $vmid,
1413 timeout => $timeout, forceStop => 1 });
1414 } else {
1415 die "unknown VM type '$type'\n";
1416 }
1417
1418 return $upid;
1419 };
1420
1421 __PACKAGE__->register_method ({
1422 name => 'stopall',
1423 path => 'stopall',
1424 method => 'POST',
1425 protected => 1,
1426 permissions => {
1427 check => ['perm', '/', [ 'VM.PowerMgmt' ]],
1428 },
1429 proxyto => 'node',
1430 description => "Stop all VMs and Containers.",
1431 parameters => {
1432 additionalProperties => 0,
1433 properties => {
1434 node => get_standard_option('pve-node'),
1435 vms => {
1436 description => "Only consider Guests with these IDs.",
1437 type => 'string', format => 'pve-vmid-list',
1438 optional => 1,
1439 },
1440 },
1441 },
1442 returns => {
1443 type => 'string',
1444 },
1445 code => sub {
1446 my ($param) = @_;
1447
1448 my $rpcenv = PVE::RPCEnvironment::get();
1449 my $authuser = $rpcenv->get_user();
1450
1451 my $nodename = $param->{node};
1452 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1453
1454 my $code = sub {
1455
1456 $rpcenv->{type} = 'priv'; # to start tasks in background
1457
1458 my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms});
1459
1460 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
1461 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1462 # if not set by user spawn max cpu count number of workers
1463 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
1464
1465 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1466 my $vmlist = $stopList->{$order};
1467 my $workers = {};
1468
1469 my $finish_worker = sub {
1470 my $pid = shift;
1471 my $d = $workers->{$pid};
1472 return if !$d;
1473 delete $workers->{$pid};
1474
1475 syslog('info', "end task $d->{upid}");
1476 };
1477
1478 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1479 my $d = $vmlist->{$vmid};
1480 my $upid;
1481 eval { $upid = &$create_stop_worker($nodename, $d->{type}, $vmid, $d->{down}); };
1482 warn $@ if $@;
1483 next if !$upid;
1484
1485 my $res = PVE::Tools::upid_decode($upid, 1);
1486 next if !$res;
1487
1488 my $pid = $res->{pid};
1489
1490 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
1491 while (scalar(keys %$workers) >= $maxWorkers) {
1492 foreach my $p (keys %$workers) {
1493 if (!PVE::ProcFSTools::check_process_running($p)) {
1494 &$finish_worker($p);
1495 }
1496 }
1497 sleep(1);
1498 }
1499 }
1500 while (scalar(keys %$workers)) {
1501 foreach my $p (keys %$workers) {
1502 if (!PVE::ProcFSTools::check_process_running($p)) {
1503 &$finish_worker($p);
1504 }
1505 }
1506 sleep(1);
1507 }
1508 }
1509
1510 syslog('info', "all VMs and CTs stopped");
1511
1512 return;
1513 };
1514
1515 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1516 }});
1517
1518 my $create_migrate_worker = sub {
1519 my ($nodename, $type, $vmid, $target) = @_;
1520
1521 my $upid;
1522 if ($type eq 'lxc') {
1523 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
1524 print STDERR "Migrating CT $vmid\n";
1525 $upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
1526 online => $online });
1527 } elsif ($type eq 'qemu') {
1528 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
1529 print STDERR "Migrating VM $vmid\n";
1530 $upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
1531 online => $online });
1532 } else {
1533 die "unknown VM type '$type'\n";
1534 }
1535
1536 my $res = PVE::Tools::upid_decode($upid);
1537
1538 return $res->{pid};
1539 };
1540
1541 __PACKAGE__->register_method ({
1542 name => 'migrateall',
1543 path => 'migrateall',
1544 method => 'POST',
1545 proxyto => 'node',
1546 protected => 1,
1547 permissions => {
1548 check => ['perm', '/', [ 'VM.Migrate' ]],
1549 },
1550 description => "Migrate all VMs and Containers.",
1551 parameters => {
1552 additionalProperties => 0,
1553 properties => {
1554 node => get_standard_option('pve-node'),
1555 target => get_standard_option('pve-node', { description => "Target node." }),
1556 maxworkers => {
1557 description => "Maximal number of parallel migration job." .
1558 " If not set use 'max_workers' from datacenter.cfg," .
1559 " one of both must be set!",
1560 optional => 1,
1561 type => 'integer',
1562 minimum => 1
1563 },
1564 vms => {
1565 description => "Only consider Guests with these IDs.",
1566 type => 'string', format => 'pve-vmid-list',
1567 optional => 1,
1568 },
1569 },
1570 },
1571 returns => {
1572 type => 'string',
1573 },
1574 code => sub {
1575 my ($param) = @_;
1576
1577 my $rpcenv = PVE::RPCEnvironment::get();
1578 my $authuser = $rpcenv->get_user();
1579
1580 my $nodename = $param->{node};
1581 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1582
1583 my $target = $param->{target};
1584
1585 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1586 # prefer parameter over datacenter cfg settings
1587 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
1588 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1589
1590 my $code = sub {
1591 $rpcenv->{type} = 'priv'; # to start tasks in background
1592
1593 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
1594
1595 my $workers = {};
1596 foreach my $vmid (sort keys %$vmlist) {
1597 my $d = $vmlist->{$vmid};
1598 my $pid;
1599 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
1600 warn $@ if $@;
1601 next if !$pid;
1602
1603 $workers->{$pid} = 1;
1604 while (scalar(keys %$workers) >= $maxWorkers) {
1605 foreach my $p (keys %$workers) {
1606 if (!PVE::ProcFSTools::check_process_running($p)) {
1607 delete $workers->{$p};
1608 }
1609 }
1610 sleep(1);
1611 }
1612 }
1613 while (scalar(keys %$workers)) {
1614 foreach my $p (keys %$workers) {
1615 if (!PVE::ProcFSTools::check_process_running($p)) {
1616 delete $workers->{$p};
1617 }
1618 }
1619 sleep(1);
1620 }
1621 return;
1622 };
1623
1624 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1625
1626 }});
1627
1628 # bash completion helper
1629
1630 sub complete_templet_repo {
1631 my ($cmdname, $pname, $cvalue) = @_;
1632
1633 my $repo = PVE::APLInfo::load_data();
1634 my $res = [];
1635 foreach my $templ (keys %{$repo->{all}}) {
1636 next if $templ !~ m/^$cvalue/;
1637 push @$res, $templ;
1638 }
1639
1640 return $res;
1641 }
1642
1643 package PVE::API2::Nodes;
1644
1645 use strict;
1646 use warnings;
1647
1648 use PVE::SafeSyslog;
1649 use PVE::Cluster;
1650 use PVE::RESTHandler;
1651 use PVE::RPCEnvironment;
1652 use PVE::API2Tools;
1653
1654 use base qw(PVE::RESTHandler);
1655
1656 __PACKAGE__->register_method ({
1657 subclass => "PVE::API2::Nodes::Nodeinfo",
1658 path => '{node}',
1659 });
1660
1661 __PACKAGE__->register_method ({
1662 name => 'index',
1663 path => '',
1664 method => 'GET',
1665 permissions => { user => 'all' },
1666 description => "Cluster node index.",
1667 parameters => {
1668 additionalProperties => 0,
1669 properties => {},
1670 },
1671 returns => {
1672 type => 'array',
1673 items => {
1674 type => "object",
1675 properties => {},
1676 },
1677 links => [ { rel => 'child', href => "{node}" } ],
1678 },
1679 code => sub {
1680 my ($param) = @_;
1681
1682 my $clinfo = PVE::Cluster::get_clinfo();
1683 my $res = [];
1684
1685 my $nodelist = PVE::Cluster::get_nodelist();
1686 my $members = PVE::Cluster::get_members();
1687 my $rrd = PVE::Cluster::rrd_dump();
1688
1689 foreach my $node (@$nodelist) {
1690 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
1691 push @$res, $entry;
1692 }
1693
1694 return $res;
1695 }});
1696
1697 1;