]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Nodes.pm
improve network API - add more gettext markers
[pve-manager.git] / PVE / API2 / Nodes.pm
1 package PVE::API2::Nodes::Nodeinfo;
2
3 use strict;
4 use warnings;
5 use POSIX;
6 use Filesys::Df;
7 use Time::Local qw(timegm_nocheck);
8 use PVE::pvecfg;
9 use PVE::Tools;
10 use PVE::ProcFSTools;
11 use PVE::SafeSyslog;
12 use PVE::Cluster qw(cfs_read_file);
13 use PVE::INotify;
14 use PVE::Exception qw(raise raise_perm_exc);
15 use PVE::RESTHandler;
16 use PVE::RPCEnvironment;
17 use PVE::JSONSchema qw(get_standard_option);
18 use PVE::AccessControl;
19 use PVE::Storage;
20 use PVE::OpenVZ;
21 use PVE::APLInfo;
22 use PVE::API2::Services;
23 use PVE::API2::Network;
24 use PVE::API2::Tasks;
25 use PVE::API2::Storage::Scan;
26 use PVE::API2::Storage::Status;
27 use PVE::API2::Qemu;
28 use PVE::API2::OpenVZ;
29 use PVE::API2::VZDump;
30 use JSON;
31
32 use base qw(PVE::RESTHandler);
33
34 __PACKAGE__->register_method ({
35 subclass => "PVE::API2::Qemu",
36 path => 'qemu',
37 });
38
39 __PACKAGE__->register_method ({
40 subclass => "PVE::API2::OpenVZ",
41 path => 'openvz',
42 });
43
44 __PACKAGE__->register_method ({
45 subclass => "PVE::API2::VZDump",
46 path => 'vzdump',
47 });
48
49 __PACKAGE__->register_method ({
50 subclass => "PVE::API2::Services",
51 path => 'services',
52 });
53
54 __PACKAGE__->register_method ({
55 subclass => "PVE::API2::Network",
56 path => 'network',
57 });
58
59 __PACKAGE__->register_method ({
60 subclass => "PVE::API2::Tasks",
61 path => 'tasks',
62 });
63
64 __PACKAGE__->register_method ({
65 subclass => "PVE::API2::Storage::Scan",
66 path => 'scan',
67 });
68
69 __PACKAGE__->register_method ({
70 subclass => "PVE::API2::Storage::Status",
71 path => 'storage',
72 });
73
74 __PACKAGE__->register_method ({
75 name => 'index',
76 path => '',
77 method => 'GET',
78 permissions => { user => 'all' },
79 description => "Node index.",
80 parameters => {
81 additionalProperties => 0,
82 properties => {
83 node => get_standard_option('pve-node'),
84 },
85 },
86 returns => {
87 type => 'array',
88 items => {
89 type => "object",
90 properties => {},
91 },
92 links => [ { rel => 'child', href => "{name}" } ],
93 },
94 code => sub {
95 my ($param) = @_;
96
97 my $result = [
98 { name => 'version' },
99 { name => 'syslog' },
100 { name => 'status' },
101 { name => 'tasks' },
102 { name => 'rrd' }, # fixme: remove?
103 { name => 'rrddata' },# fixme: remove?
104 { name => 'vncshell' },
105 { name => 'time' },
106 { name => 'dns' },
107 { name => 'services' },
108 { name => 'scan' },
109 { name => 'storage' },
110 { name => 'qemu' },
111 { name => 'openvz' },
112 { name => 'vzdump' },
113 { name => 'ubcfailcnt' },
114 { name => 'network' },
115 { name => 'aplinfo' },
116 ];
117
118 return $result;
119 }});
120
121 __PACKAGE__->register_method ({
122 name => 'version',
123 path => 'version',
124 method => 'GET',
125 proxyto => 'node',
126 permissions => { user => 'all' },
127 description => "API version details",
128 parameters => {
129 additionalProperties => 0,
130 properties => {
131 node => get_standard_option('pve-node'),
132 },
133 },
134 returns => {
135 type => "object",
136 properties => {
137 version => { type => 'string' },
138 release => { type => 'string' },
139 repoid => { type => 'string' },
140 },
141 },
142 code => sub {
143 my ($resp, $param) = @_;
144
145 return PVE::pvecfg::version_info();
146 }});
147
148 __PACKAGE__->register_method({
149 name => 'beancounters_failcnt',
150 path => 'ubcfailcnt',
151 permissions => {
152 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
153 },
154 method => 'GET',
155 proxyto => 'node',
156 protected => 1, # openvz /proc entries are only readable by root
157 description => "Get user_beancounters failcnt for all active containers.",
158 parameters => {
159 additionalProperties => 0,
160 properties => {
161 node => get_standard_option('pve-node'),
162 },
163 },
164 returns => {
165 type => 'array',
166 items => {
167 type => "object",
168 properties => {
169 id => { type => 'string' },
170 failcnt => { type => 'number' },
171 },
172 },
173 },
174 code => sub {
175 my ($param) = @_;
176
177 my $ubchash = PVE::OpenVZ::read_user_beancounters();
178
179 my $res = [];
180 foreach my $vmid (keys %$ubchash) {
181 next if !$vmid;
182 push @$res, { id => $vmid, failcnt => $ubchash->{$vmid}->{failcntsum} };
183
184 }
185 return $res;
186 }});
187
188 __PACKAGE__->register_method({
189 name => 'status',
190 path => 'status',
191 method => 'GET',
192 permissions => {
193 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
194 },
195 description => "Read node status",
196 proxyto => 'node',
197 parameters => {
198 additionalProperties => 0,
199 properties => {
200 node => get_standard_option('pve-node'),
201 },
202 },
203 returns => {
204 type => "object",
205 properties => {
206
207 },
208 },
209 code => sub {
210 my ($param) = @_;
211
212 my $res = {
213 uptime => 0,
214 idle => 0,
215 };
216
217 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
218 $res->{uptime} = $uptime;
219
220 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
221 $res->{loadavg} = [ $avg1, $avg5, $avg15];
222
223 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
224
225 $res->{kversion} = "$sysname $release $version";
226
227 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
228
229 my $stat = PVE::ProcFSTools::read_proc_stat();
230 $res->{cpu} = $stat->{cpu};
231 $res->{wait} = $stat->{wait};
232
233 my $meminfo = PVE::ProcFSTools::read_meminfo();
234 $res->{memory} = {
235 free => $meminfo->{memfree},
236 total => $meminfo->{memtotal},
237 used => $meminfo->{memused},
238 };
239 $res->{swap} = {
240 free => $meminfo->{swapfree},
241 total => $meminfo->{swaptotal},
242 used => $meminfo->{swapused},
243 };
244
245 $res->{pveversion} = PVE::pvecfg::package() . "/" .
246 PVE::pvecfg::version_text();
247
248 my $dinfo = df('/', 1); # output is bytes
249
250 $res->{rootfs} = {
251 total => $dinfo->{blocks},
252 avail => $dinfo->{bavail},
253 used => $dinfo->{used},
254 free => $dinfo->{bavail} - $dinfo->{used},
255 };
256
257 return $res;
258 }});
259
260 __PACKAGE__->register_method({
261 name => 'node_cmd',
262 path => 'status',
263 method => 'POST',
264 permissions => {
265 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
266 },
267 protected => 1,
268 description => "Reboot or shutdown a node.",
269 proxyto => 'node',
270 parameters => {
271 additionalProperties => 0,
272 properties => {
273 node => get_standard_option('pve-node'),
274 command => {
275 description => "Specify the command.",
276 type => 'string',
277 enum => [qw(reboot shutdown)],
278 },
279 },
280 },
281 returns => { type => "null" },
282 code => sub {
283 my ($param) = @_;
284
285 if ($param->{command} eq 'reboot') {
286 system ("(sleep 2;/sbin/reboot)&");
287 } elsif ($param->{command} eq 'shutdown') {
288 system ("(sleep 2;/sbin/poweroff)&");
289 }
290
291 return undef;
292 }});
293
294
295 __PACKAGE__->register_method({
296 name => 'rrd',
297 path => 'rrd',
298 method => 'GET',
299 protected => 1, # fixme: can we avoid that?
300 permissions => {
301 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
302 },
303 description => "Read node RRD statistics (returns PNG)",
304 parameters => {
305 additionalProperties => 0,
306 properties => {
307 node => get_standard_option('pve-node'),
308 timeframe => {
309 description => "Specify the time frame you are interested in.",
310 type => 'string',
311 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
312 },
313 ds => {
314 description => "The list of datasources you want to display.",
315 type => 'string', format => 'pve-configid-list',
316 },
317 cf => {
318 description => "The RRD consolidation function",
319 type => 'string',
320 enum => [ 'AVERAGE', 'MAX' ],
321 optional => 1,
322 },
323 },
324 },
325 returns => {
326 type => "object",
327 properties => {
328 filename => { type => 'string' },
329 },
330 },
331 code => sub {
332 my ($param) = @_;
333
334 return PVE::Cluster::create_rrd_graph(
335 "pve2-node/$param->{node}", $param->{timeframe},
336 $param->{ds}, $param->{cf});
337
338 }});
339
340 __PACKAGE__->register_method({
341 name => 'rrddata',
342 path => 'rrddata',
343 method => 'GET',
344 protected => 1, # fixme: can we avoid that?
345 permissions => {
346 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
347 },
348 description => "Read node RRD statistics",
349 parameters => {
350 additionalProperties => 0,
351 properties => {
352 node => get_standard_option('pve-node'),
353 timeframe => {
354 description => "Specify the time frame you are interested in.",
355 type => 'string',
356 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
357 },
358 cf => {
359 description => "The RRD consolidation function",
360 type => 'string',
361 enum => [ 'AVERAGE', 'MAX' ],
362 optional => 1,
363 },
364 },
365 },
366 returns => {
367 type => "array",
368 items => {
369 type => "object",
370 properties => {},
371 },
372 },
373 code => sub {
374 my ($param) = @_;
375
376 return PVE::Cluster::create_rrd_data(
377 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
378 }});
379
380 __PACKAGE__->register_method({
381 name => 'syslog',
382 path => 'syslog',
383 method => 'GET',
384 description => "Read system log",
385 proxyto => 'node',
386 permissions => {
387 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
388 },
389 protected => 1,
390 parameters => {
391 additionalProperties => 0,
392 properties => {
393 node => get_standard_option('pve-node'),
394 start => {
395 type => 'integer',
396 minimum => 0,
397 optional => 1,
398 },
399 limit => {
400 type => 'integer',
401 minimum => 0,
402 optional => 1,
403 },
404 },
405 },
406 returns => {
407 type => 'array',
408 items => {
409 type => "object",
410 properties => {
411 n => {
412 description=> "Line number",
413 type=> 'integer',
414 },
415 t => {
416 description=> "Line text",
417 type => 'string',
418 }
419 }
420 }
421 },
422 code => sub {
423 my ($param) = @_;
424
425 my $rpcenv = PVE::RPCEnvironment::get();
426 my $user = $rpcenv->get_user();
427 my $node = $param->{node};
428
429 my ($count, $lines) = PVE::Tools::dump_logfile("/var/log/syslog", $param->{start}, $param->{limit});
430
431 $rpcenv->set_result_attrib('total', $count);
432
433 return $lines;
434 }});
435
436 my $sslcert;
437
438 __PACKAGE__->register_method ({
439 name => 'vncshell',
440 path => 'vncshell',
441 method => 'POST',
442 protected => 1,
443 permissions => {
444 description => "Restricted to users on realm 'pam'",
445 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
446 },
447 description => "Creates a VNC Shell proxy.",
448 parameters => {
449 additionalProperties => 0,
450 properties => {
451 node => get_standard_option('pve-node'),
452 },
453 },
454 returns => {
455 additionalProperties => 0,
456 properties => {
457 user => { type => 'string' },
458 ticket => { type => 'string' },
459 cert => { type => 'string' },
460 port => { type => 'integer' },
461 upid => { type => 'string' },
462 },
463 },
464 code => sub {
465 my ($param) = @_;
466
467 my $rpcenv = PVE::RPCEnvironment::get();
468
469 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
470
471 raise_perm_exc("realm != pam") if $realm ne 'pam';
472
473 my $node = $param->{node};
474
475 my $authpath = "/nodes/$node";
476
477 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
478
479 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
480 if !$sslcert;
481
482 my $port = PVE::Tools::next_vnc_port();
483
484 my $remip;
485
486 if ($node ne PVE::INotify::nodename()) {
487 $remip = PVE::Cluster::remote_node_ip($node);
488 }
489
490 # NOTE: vncterm VNC traffic is already TLS encrypted,
491 # so we select the fastest chipher here (or 'none'?)
492 my $remcmd = $remip ?
493 ['/usr/bin/ssh', '-c', 'blowfish-cbc', '-t', $remip] : [];
494
495 my $shcmd = $user eq 'root@pam' ? [ "/bin/bash", "-l" ] : [ "/bin/login" ];
496
497 my $timeout = 10;
498
499 my @cmd = ('/usr/bin/vncterm', '-rfbport', $port,
500 '-timeout', $timeout, '-authpath', $authpath,
501 '-perm', 'Sys.Console', '-c', @$remcmd, @$shcmd);
502
503 my $realcmd = sub {
504 my $upid = shift;
505
506 syslog ('info', "starting vnc proxy $upid\n");
507
508 my $cmdstr = join (' ', @cmd);
509 syslog ('info', "launch command: $cmdstr");
510
511 if (system(@cmd) != 0) {
512 my $msg = "vncterm failed - $?";
513 syslog ('err', $msg);
514 return;
515 }
516
517 return;
518 };
519
520 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
521
522 return {
523 user => $user,
524 ticket => $ticket,
525 port => $port,
526 upid => $upid,
527 cert => $sslcert,
528 };
529 }});
530
531 __PACKAGE__->register_method({
532 name => 'dns',
533 path => 'dns',
534 method => 'GET',
535 permissions => {
536 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
537 },
538 description => "Read DNS settings.",
539 proxyto => 'node',
540 parameters => {
541 additionalProperties => 0,
542 properties => {
543 node => get_standard_option('pve-node'),
544 },
545 },
546 returns => {
547 type => "object",
548 additionalProperties => 0,
549 properties => {
550 search => {
551 description => "Search domain for host-name lookup.",
552 type => 'string',
553 optional => 1,
554 },
555 dns1 => {
556 description => 'First name server IP address.',
557 type => 'string',
558 optional => 1,
559 },
560 dns2 => {
561 description => 'Second name server IP address.',
562 type => 'string',
563 optional => 1,
564 },
565 dns3 => {
566 description => 'Third name server IP address.',
567 type => 'string',
568 optional => 1,
569 },
570 },
571 },
572 code => sub {
573 my ($param) = @_;
574
575 my $res = PVE::INotify::read_file('resolvconf');
576
577 return $res;
578 }});
579
580 __PACKAGE__->register_method({
581 name => 'update_dns',
582 path => 'dns',
583 method => 'PUT',
584 description => "Write DNS settings.",
585 permissions => {
586 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
587 },
588 proxyto => 'node',
589 protected => 1,
590 parameters => {
591 additionalProperties => 0,
592 properties => {
593 node => get_standard_option('pve-node'),
594 search => {
595 description => "Search domain for host-name lookup.",
596 type => 'string',
597 },
598 dns1 => {
599 description => 'First name server IP address.',
600 type => 'string', format => 'ipv4',
601 optional => 1,
602 },
603 dns2 => {
604 description => 'Second name server IP address.',
605 type => 'string', format => 'ipv4',
606 optional => 1,
607 },
608 dns3 => {
609 description => 'Third name server IP address.',
610 type => 'string', format => 'ipv4',
611 optional => 1,
612 },
613 },
614 },
615 returns => { type => "null" },
616 code => sub {
617 my ($param) = @_;
618
619 PVE::INotify::update_file('resolvconf', $param);
620
621 return undef;
622 }});
623
624 __PACKAGE__->register_method({
625 name => 'time',
626 path => 'time',
627 method => 'GET',
628 permissions => {
629 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
630 },
631 description => "Read server time and time zone settings.",
632 proxyto => 'node',
633 parameters => {
634 additionalProperties => 0,
635 properties => {
636 node => get_standard_option('pve-node'),
637 },
638 },
639 returns => {
640 type => "object",
641 additionalProperties => 0,
642 properties => {
643 timezone => {
644 description => "Time zone",
645 type => 'string',
646 },
647 time => {
648 description => "Seconds since 1970-01-01 00:00:00 UTC.",
649 type => 'integer',
650 minimum => 1297163644,
651 },
652 localtime => {
653 description => "Seconds since 1970-01-01 00:00:00 (local time)",
654 type => 'integer',
655 minimum => 1297163644,
656 },
657 },
658 },
659 code => sub {
660 my ($param) = @_;
661
662 my $ctime = time();
663 my $ltime = timegm_nocheck(localtime($ctime));
664 my $res = {
665 timezone => PVE::INotify::read_file('timezone'),
666 time => time(),
667 localtime => $ltime,
668 };
669
670 return $res;
671 }});
672
673 __PACKAGE__->register_method({
674 name => 'set_timezone',
675 path => 'time',
676 method => 'PUT',
677 description => "Set time zone.",
678 permissions => {
679 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
680 },
681 proxyto => 'node',
682 protected => 1,
683 parameters => {
684 additionalProperties => 0,
685 properties => {
686 node => get_standard_option('pve-node'),
687 timezone => {
688 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
689 type => 'string',
690 },
691 },
692 },
693 returns => { type => "null" },
694 code => sub {
695 my ($param) = @_;
696
697 PVE::INotify::write_file('timezone', $param->{timezone});
698
699 return undef;
700 }});
701
702 __PACKAGE__->register_method({
703 name => 'aplinfo',
704 path => 'aplinfo',
705 method => 'GET',
706 permissions => {
707 user => 'all',
708 },
709 description => "Get list of appliances.",
710 proxyto => 'node',
711 parameters => {
712 additionalProperties => 0,
713 properties => {
714 node => get_standard_option('pve-node'),
715 },
716 },
717 returns => {
718 type => 'array',
719 items => {
720 type => "object",
721 properties => {},
722 },
723 },
724 code => sub {
725 my ($param) = @_;
726
727 my $res = [];
728
729 my $list = PVE::APLInfo::load_data();
730
731 foreach my $template (keys %{$list->{all}}) {
732 my $pd = $list->{all}->{$template};
733 next if $pd->{'package'} eq 'pve-web-news';
734 push @$res, $pd;
735 }
736
737 return $res;
738 }});
739
740 __PACKAGE__->register_method({
741 name => 'apl_download',
742 path => 'aplinfo',
743 method => 'POST',
744 permissions => {
745 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
746 },
747 description => "Download appliance templates.",
748 proxyto => 'node',
749 protected => 1,
750 parameters => {
751 additionalProperties => 0,
752 properties => {
753 node => get_standard_option('pve-node'),
754 storage => get_standard_option('pve-storage-id'),
755 template => { type => 'string', maxLength => 255 },
756 },
757 },
758 returns => { type => "string" },
759 code => sub {
760 my ($param) = @_;
761
762 my $rpcenv = PVE::RPCEnvironment::get();
763
764 my $user = $rpcenv->get_user();
765
766 my $node = $param->{node};
767
768 my $list = PVE::APLInfo::load_data();
769
770 my $template = $param->{template};
771 my $pd = $list->{all}->{$template};
772
773 raise_param_exc({ template => "no such template"}) if !$pd;
774
775 my $cfg = cfs_read_file("storage.cfg");
776 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
777
778 die "cannot download to storage type '$scfg->{type}'"
779 if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs');
780
781 die "unknown template type '$pd->{type}'\n" if $pd->{type} ne 'openvz';
782
783 die "storage '$param->{storage}' does not support templates\n"
784 if !$scfg->{content}->{vztmpl};
785
786 my $src = $pd->{location};
787 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
788 my $dest = "$tmpldir/$template";
789 my $tmpdest = "$tmpldir/${template}.tmp.$$";
790
791 my $worker = sub {
792 my $upid = shift;
793
794 print "starting template download from: $src\n";
795 print "target file: $dest\n";
796
797 eval {
798
799 if (-f $dest) {
800 my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
801
802 if ($md5 && (lc($md5) eq lc($pd->{md5sum}))) {
803 print "file already exists $md5 - no need to download\n";
804 return;
805 }
806 }
807
808 local %ENV;
809 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
810 if ($dccfg->{http_proxy}) {
811 $ENV{http_proxy} = $dccfg->{http_proxy};
812 }
813
814 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
815 if (system (@cmd) != 0) {
816 die "download failed - $!\n";
817 }
818
819 my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
820
821 if (!$md5 || (lc($md5) ne lc($pd->{md5sum}))) {
822 die "wrong checksum: $md5 != $pd->{md5sum}\n";
823 }
824
825 if (system ('mv', $tmpdest, $dest) != 0) {
826 die "unable to save file - $!\n";
827 }
828 };
829 my $err = $@;
830
831 unlink $tmpdest;
832
833 if ($err) {
834 print "\n";
835 die $err if $err;
836 }
837
838 print "download finished\n";
839 };
840
841 return $rpcenv->fork_worker('download', undef, $user, $worker);
842 }});
843
844 package PVE::API2::Nodes;
845
846 use strict;
847 use warnings;
848
849 use PVE::SafeSyslog;
850 use PVE::Cluster;
851 use PVE::RESTHandler;
852 use PVE::RPCEnvironment;
853
854 use base qw(PVE::RESTHandler);
855
856 __PACKAGE__->register_method ({
857 subclass => "PVE::API2::Nodes::Nodeinfo",
858 path => '{node}',
859 });
860
861 __PACKAGE__->register_method ({
862 name => 'index',
863 path => '',
864 method => 'GET',
865 permissions => { user => 'all' },
866 description => "Cluster node index.",
867 parameters => {
868 additionalProperties => 0,
869 properties => {},
870 },
871 returns => {
872 type => 'array',
873 items => {
874 type => "object",
875 properties => {},
876 },
877 links => [ { rel => 'child', href => "{name}" } ],
878 },
879 code => sub {
880 my ($param) = @_;
881
882 my $clinfo = PVE::Cluster::get_clinfo();
883 my $res = [];
884
885 my $nodename = PVE::INotify::nodename();
886 my $nodelist = $clinfo->{nodelist};
887
888 my $rrd = PVE::Cluster::rrd_dump();
889
890 my @nodes = $nodelist ? (keys %$nodelist) : $nodename;
891
892 foreach my $node (@nodes) {
893 my $entry = { name => $node };
894 if (my $d = $rrd->{"pve2-node/$node"}) {
895
896 $entry->{uptime} = $d->[0];
897 $entry->{maxcpu} = $d->[3];
898 $entry->{cpu} = $d->[4];
899 $entry->{maxmem} = $d->[6];
900 $entry->{mem} = $d->[7];
901 $entry->{maxdisk} = $d->[10];
902 $entry->{disk} = $d->[11];
903 }
904
905 push @$res, $entry;
906 }
907
908 return $res;
909 }});
910
911 1;