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