]> git.proxmox.com Git - pmg-api.git/blob - PMG/API2/Nodes.pm
PMG/API2/Nodes.pm - fix permissions for rrddata
[pmg-api.git] / PMG / API2 / Nodes.pm
1 package PMG::API2::NodeInfo;
2
3 use strict;
4 use warnings;
5 use Time::Local qw(timegm_nocheck);
6 use Filesys::Df;
7 use Data::Dumper;
8
9 use PVE::INotify;
10 use PVE::RESTHandler;
11 use PVE::JSONSchema qw(get_standard_option);
12 use PMG::RESTEnvironment;
13 use PVE::SafeSyslog;
14 use PVE::ProcFSTools;
15
16 use PMG::pmgcfg;
17 use PMG::Ticket;
18 use PMG::API2::Subscription;
19 use PMG::API2::APT;
20 use PMG::API2::Tasks;
21 use PMG::API2::Services;
22 use PMG::API2::Network;
23 use PMG::API2::ClamAV;
24 use PMG::API2::Postfix;
25 use PMG::API2::MailTracker;
26
27 use base qw(PVE::RESTHandler);
28
29 __PACKAGE__->register_method ({
30 subclass => "PMG::API2::Postfix",
31 path => 'postfix',
32 });
33
34 __PACKAGE__->register_method ({
35 subclass => "PMG::API2::ClamAV",
36 path => 'clamav',
37 });
38
39 __PACKAGE__->register_method ({
40 subclass => "PMG::API2::Network",
41 path => 'network',
42 });
43
44 __PACKAGE__->register_method ({
45 subclass => "PMG::API2::Tasks",
46 path => 'tasks',
47 });
48
49 __PACKAGE__->register_method ({
50 subclass => "PMG::API2::Services",
51 path => 'services',
52 });
53
54 __PACKAGE__->register_method ({
55 subclass => "PMG::API2::Subscription",
56 path => 'subscription',
57 });
58
59 __PACKAGE__->register_method ({
60 subclass => "PMG::API2::APT",
61 path => 'apt',
62 });
63
64 __PACKAGE__->register_method ({
65 subclass => "PMG::API2::MailTracker",
66 path => 'tracker',
67 });
68
69 __PACKAGE__->register_method ({
70 name => 'index',
71 path => '',
72 method => 'GET',
73 permissions => { user => 'all' },
74 description => "Node index.",
75 parameters => {
76 additionalProperties => 0,
77 properties => {
78 node => get_standard_option('pve-node'),
79 },
80 },
81 returns => {
82 type => 'array',
83 items => {
84 type => "object",
85 properties => {},
86 },
87 links => [ { rel => 'child', href => "{name}" } ],
88 },
89 code => sub {
90 my ($param) = @_;
91
92 my $result = [
93 { name => 'apt' },
94 { name => 'clamav' },
95 { name => 'postfix' },
96 { name => 'services' },
97 { name => 'syslog' },
98 { name => 'tasks' },
99 { name => 'tracker' },
100 { name => 'time' },
101 { name => 'status' },
102 { name => 'subscription' },
103 { name => 'vncshell' },
104 { name => 'rrddata' },
105 ];
106
107 return $result;
108 }});
109
110 __PACKAGE__->register_method({
111 name => 'rrddata',
112 path => 'rrddata',
113 method => 'GET',
114 protected => 1, # fixme: can we avoid that?
115 proxyto => 'node',
116 permissions => { check => [ 'admin', 'audit' ] },
117 description => "Read node RRD statistics",
118 parameters => {
119 additionalProperties => 0,
120 properties => {
121 node => get_standard_option('pve-node'),
122 timeframe => {
123 description => "Specify the time frame you are interested in.",
124 type => 'string',
125 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
126 },
127 cf => {
128 description => "The RRD consolidation function",
129 type => 'string',
130 enum => [ 'AVERAGE', 'MAX' ],
131 optional => 1,
132 },
133 },
134 },
135 returns => {
136 type => "array",
137 items => {
138 type => "object",
139 properties => {},
140 },
141 },
142 code => sub {
143 my ($param) = @_;
144
145 return PMG::Utils::create_rrd_data(
146 "pmg-node-v1.rrd", $param->{timeframe}, $param->{cf});
147 }});
148
149
150 __PACKAGE__->register_method({
151 name => 'syslog',
152 path => 'syslog',
153 method => 'GET',
154 description => "Read system log",
155 proxyto => 'node',
156 protected => 1,
157 parameters => {
158 additionalProperties => 0,
159 properties => {
160 node => get_standard_option('pve-node'),
161 start => {
162 type => 'integer',
163 minimum => 0,
164 optional => 1,
165 },
166 limit => {
167 type => 'integer',
168 minimum => 0,
169 optional => 1,
170 },
171 since => {
172 type => 'string',
173 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
174 description => "Display all log since this date-time string.",
175 optional => 1,
176 },
177 'until' => {
178 type => 'string',
179 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
180 description => "Display all log until this date-time string.",
181 optional => 1,
182 },
183 service => {
184 description => "Service ID",
185 type => 'string',
186 maxLength => 128,
187 optional => 1,
188 },
189 },
190 },
191 returns => {
192 type => 'array',
193 items => {
194 type => "object",
195 properties => {
196 n => {
197 description=> "Line number",
198 type=> 'integer',
199 },
200 t => {
201 description=> "Line text",
202 type => 'string',
203 }
204 }
205 }
206 },
207 code => sub {
208 my ($param) = @_;
209
210 my $restenv = PMG::RESTEnvironment->get();
211
212 my $service = $param->{service};
213 $service = PMG::Utils::lookup_real_service_name($service)
214 if $service;
215
216 my ($count, $lines) = PVE::Tools::dump_journal(
217 $param->{start}, $param->{limit},
218 $param->{since}, $param->{'until'}, $service);
219
220 $restenv->set_result_attrib('total', $count);
221
222 return $lines;
223 }});
224
225 __PACKAGE__->register_method ({
226 name => 'vncshell',
227 path => 'vncshell',
228 method => 'POST',
229 protected => 1,
230 description => "Creates a VNC Shell proxy.",
231 parameters => {
232 additionalProperties => 0,
233 properties => {
234 node => get_standard_option('pve-node'),
235 upgrade => {
236 type => 'boolean',
237 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
238 optional => 1,
239 default => 0,
240 },
241 websocket => {
242 optional => 1,
243 type => 'boolean',
244 description => "use websocket instead of standard vnc.",
245 default => 1,
246 },
247 },
248 },
249 returns => {
250 additionalProperties => 0,
251 properties => {
252 user => { type => 'string' },
253 ticket => { type => 'string' },
254 port => { type => 'integer' },
255 upid => { type => 'string' },
256 },
257 },
258 code => sub {
259 my ($param) = @_;
260
261 my $node = $param->{node};
262
263 if ($node ne PVE::INotify::nodename()) {
264 die "vncproxy to remote node not implemented";
265 }
266
267 # we only implement the websocket based VNC here
268 my $websocket = $param->{websocket} // 1;
269 die "standard VNC not implemented" if !$websocket;
270
271 my $authpath = "/nodes/$node";
272
273 my $restenv = PMG::RESTEnvironment->get();
274 my $user = $restenv->get_user();
275
276 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
277
278 my $ticket = PMG::Ticket::assemble_vnc_ticket($user, $authpath);
279
280 my $family = PVE::Tools::get_host_address_family($node);
281 my $port = PVE::Tools::next_vnc_port($family);
282
283 my $shcmd;
284
285 if ($user eq 'root@pam') {
286 if ($param->{upgrade}) {
287 my $upgradecmd = "pmgupgrade --shell";
288 # $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
289 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
290 } else {
291 $shcmd = [ '/bin/login', '-f', 'root' ];
292 }
293 } else {
294 $shcmd = [ '/bin/login' ];
295 }
296
297 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
298 '-timeout', 10, '-notls', '-listen', 'localhost',
299 '-c', @$shcmd];
300
301 my $realcmd = sub {
302 my $upid = shift;
303
304 syslog ('info', "starting vnc proxy $upid\n");
305
306 my $cmdstr = join (' ', @$cmd);
307 syslog ('info', "launch command: $cmdstr");
308
309 eval {
310 foreach my $k (keys %ENV) {
311 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
312 delete $ENV{$k};
313 }
314 $ENV{PWD} = '/';
315
316 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
317
318 PVE::Tools::run_command($cmd, errmsg => "vncterm failed");
319 };
320 if (my $err = $@) {
321 syslog('err', $err);
322 }
323
324 return;
325 };
326
327 my $upid = $restenv->fork_worker('vncshell', "", $user, $realcmd);
328
329 PVE::Tools::wait_for_vnc_port($port);
330
331 return {
332 user => $user,
333 ticket => $ticket,
334 port => $port,
335 upid => $upid,
336 };
337 }});
338
339 __PACKAGE__->register_method({
340 name => 'vncwebsocket',
341 path => 'vncwebsocket',
342 method => 'GET',
343 description => "Opens a weksocket for VNC traffic.",
344 parameters => {
345 additionalProperties => 0,
346 properties => {
347 node => get_standard_option('pve-node'),
348 vncticket => {
349 description => "Ticket from previous call to vncproxy.",
350 type => 'string',
351 maxLength => 512,
352 },
353 port => {
354 description => "Port number returned by previous vncproxy call.",
355 type => 'integer',
356 minimum => 5900,
357 maximum => 5999,
358 },
359 },
360 },
361 returns => {
362 type => "object",
363 properties => {
364 port => { type => 'string' },
365 },
366 },
367 code => sub {
368 my ($param) = @_;
369
370 my $authpath = "/nodes/$param->{node}";
371
372 my $restenv = PMG::RESTEnvironment->get();
373 my $user = $restenv->get_user();
374
375 PMG::Ticket::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
376
377 my $port = $param->{port};
378
379 return { port => $port };
380 }});
381
382 __PACKAGE__->register_method({
383 name => 'dns',
384 path => 'dns',
385 method => 'GET',
386 description => "Read DNS settings.",
387 proxyto => 'node',
388 parameters => {
389 additionalProperties => 0,
390 properties => {
391 node => get_standard_option('pve-node'),
392 },
393 },
394 returns => {
395 type => "object",
396 additionalProperties => 0,
397 properties => {
398 search => {
399 description => "Search domain for host-name lookup.",
400 type => 'string',
401 optional => 1,
402 },
403 dns1 => {
404 description => 'First name server IP address.',
405 type => 'string',
406 optional => 1,
407 },
408 dns2 => {
409 description => 'Second name server IP address.',
410 type => 'string',
411 optional => 1,
412 },
413 dns3 => {
414 description => 'Third name server IP address.',
415 type => 'string',
416 optional => 1,
417 },
418 },
419 },
420 code => sub {
421 my ($param) = @_;
422
423 my $res = PVE::INotify::read_file('resolvconf');
424
425 return $res;
426 }});
427
428 __PACKAGE__->register_method({
429 name => 'update_dns',
430 path => 'dns',
431 method => 'PUT',
432 description => "Write DNS settings.",
433 proxyto => 'node',
434 protected => 1,
435 parameters => {
436 additionalProperties => 0,
437 properties => {
438 node => get_standard_option('pve-node'),
439 search => {
440 description => "Search domain for host-name lookup.",
441 type => 'string',
442 },
443 dns1 => {
444 description => 'First name server IP address.',
445 type => 'string', format => 'ip',
446 optional => 1,
447 },
448 dns2 => {
449 description => 'Second name server IP address.',
450 type => 'string', format => 'ip',
451 optional => 1,
452 },
453 dns3 => {
454 description => 'Third name server IP address.',
455 type => 'string', format => 'ip',
456 optional => 1,
457 },
458 },
459 },
460 returns => { type => "null" },
461 code => sub {
462 my ($param) = @_;
463
464 PVE::INotify::update_file('resolvconf', $param);
465
466 return undef;
467 }});
468
469
470 __PACKAGE__->register_method({
471 name => 'time',
472 path => 'time',
473 method => 'GET',
474 description => "Read server time and time zone settings.",
475 proxyto => 'node',
476 parameters => {
477 additionalProperties => 0,
478 properties => {
479 node => get_standard_option('pve-node'),
480 },
481 },
482 returns => {
483 type => "object",
484 additionalProperties => 0,
485 properties => {
486 timezone => {
487 description => "Time zone",
488 type => 'string',
489 },
490 time => {
491 description => "Seconds since 1970-01-01 00:00:00 UTC.",
492 type => 'integer',
493 minimum => 1297163644,
494 },
495 localtime => {
496 description => "Seconds since 1970-01-01 00:00:00 (local time)",
497 type => 'integer',
498 minimum => 1297163644,
499 },
500 },
501 },
502 code => sub {
503 my ($param) = @_;
504
505 my $ctime = time();
506 my $ltime = timegm_nocheck(localtime($ctime));
507 my $res = {
508 timezone => PVE::INotify::read_file('timezone'),
509 time => time(),
510 localtime => $ltime,
511 };
512
513 return $res;
514 }});
515
516 __PACKAGE__->register_method({
517 name => 'set_timezone',
518 path => 'time',
519 method => 'PUT',
520 description => "Set time zone.",
521 proxyto => 'node',
522 protected => 1,
523 parameters => {
524 additionalProperties => 0,
525 properties => {
526 node => get_standard_option('pve-node'),
527 timezone => {
528 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
529 type => 'string',
530 },
531 },
532 },
533 returns => { type => "null" },
534 code => sub {
535 my ($param) = @_;
536
537 PVE::INotify::write_file('timezone', $param->{timezone});
538
539 return undef;
540 }});
541
542 __PACKAGE__->register_method({
543 name => 'status',
544 path => 'status',
545 method => 'GET',
546 description => "Read server status. This is used by the cluster manager to test the node health.",
547 proxyto => 'node',
548 protected => 1,
549 parameters => {
550 additionalProperties => 0,
551 properties => {
552 node => get_standard_option('pve-node'),
553 },
554 },
555 returns => {
556 type => "object",
557 additionalProperties => 1,
558 properties => {
559 time => {
560 description => "Seconds since 1970-01-01 00:00:00 UTC.",
561 type => 'integer',
562 minimum => 1297163644,
563 },
564 uptime => {
565 description => "The uptime of the system in seconds.",
566 type => 'integer',
567 minimum => 0,
568 },
569 insync => {
570 description => "Database is synced with other nodes.",
571 type => 'boolean',
572 },
573 },
574 },
575 code => sub {
576 my ($param) = @_;
577
578 my $restenv = PMG::RESTEnvironment->get();
579 my $cinfo = $restenv->{cinfo};
580
581 my $ctime = time();
582
583 my $res = { time => $ctime, insync => 1 };
584
585 my $si = PMG::DBTools::cluster_sync_status($cinfo);
586 foreach my $cid (keys %$si) {
587 my $lastsync = $si->{$cid};
588 my $sdiff = $ctime - $lastsync;
589 $sdiff = 0 if $sdiff < 0;
590 $res->{insync} = 0 if $sdiff > (60*3);
591 }
592
593 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
594 $res->{uptime} = $uptime;
595
596 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
597 $res->{loadavg} = [ $avg1, $avg5, $avg15];
598
599 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
600
601 $res->{kversion} = "$sysname $release $version";
602
603 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
604
605 my $stat = PVE::ProcFSTools::read_proc_stat();
606 $res->{cpu} = $stat->{cpu};
607 $res->{wait} = $stat->{wait};
608
609 my $meminfo = PVE::ProcFSTools::read_meminfo();
610 $res->{memory} = {
611 free => $meminfo->{memfree},
612 total => $meminfo->{memtotal},
613 used => $meminfo->{memused},
614 };
615
616 $res->{swap} = {
617 free => $meminfo->{swapfree},
618 total => $meminfo->{swaptotal},
619 used => $meminfo->{swapused},
620 };
621
622 $res->{pmgversion} = PMG::pmgcfg::package() . "/" .
623 PMG::pmgcfg::version_text();
624
625 my $dinfo = df('/', 1); # output is bytes
626
627 $res->{rootfs} = {
628 total => $dinfo->{blocks},
629 avail => $dinfo->{bavail},
630 used => $dinfo->{used},
631 free => $dinfo->{bavail} - $dinfo->{used},
632 };
633
634 if (my $subinfo = PVE::INotify::read_file('subscription')) {
635 if (my $level = $subinfo->{level}) {
636 $res->{level} = $level;
637 }
638 }
639
640 return $res;
641 }});
642
643
644 package PMG::API2::Nodes;
645
646 use strict;
647 use warnings;
648
649 use PVE::RESTHandler;
650 use PVE::JSONSchema qw(get_standard_option);
651
652 use PMG::RESTEnvironment;
653
654 use base qw(PVE::RESTHandler);
655
656 __PACKAGE__->register_method ({
657 subclass => "PMG::API2::NodeInfo",
658 path => '{node}',
659 });
660
661 __PACKAGE__->register_method ({
662 name => 'index',
663 path => '',
664 method => 'GET',
665 permissions => { user => 'all' },
666 description => "Cluster node index.",
667 parameters => {
668 additionalProperties => 0,
669 properties => {},
670 },
671 returns => {
672 type => 'array',
673 items => {
674 type => "object",
675 properties => {},
676 },
677 links => [ { rel => 'child', href => "{node}" } ],
678 },
679 code => sub {
680 my ($param) = @_;
681
682 my $nodename = PVE::INotify::nodename();
683
684 my $res = [ { node => $nodename } ];
685
686 my $done = {};
687
688 $done->{$nodename} = 1;
689
690 my $restenv = PMG::RESTEnvironment->get();
691 my $cinfo = $restenv->{cinfo};
692
693 foreach my $ni (values %{$cinfo->{ids}}) {
694 push @$res, { node => $ni->{name} } if !$done->{$ni->{name}};
695 $done->{$ni->{name}} = 1;
696 }
697
698 return $res;
699 }});
700
701
702 1;