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