]> git.proxmox.com Git - pmg-api.git/blob - PMG/API2/Nodes.pm
7734977e9aadd3319dbcc72169be25778c096065
[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::Report;
19 use PMG::API2::Subscription;
20 use PMG::API2::APT;
21 use PMG::API2::Tasks;
22 use PMG::API2::Services;
23 use PMG::API2::Network;
24 use PMG::API2::ClamAV;
25 use PMG::API2::SpamAssassin;
26 use PMG::API2::Postfix;
27 use PMG::API2::MailTracker;
28 use PMG::API2::Backup;
29
30 use base qw(PVE::RESTHandler);
31
32 __PACKAGE__->register_method ({
33 subclass => "PMG::API2::Postfix",
34 path => 'postfix',
35 });
36
37 __PACKAGE__->register_method ({
38 subclass => "PMG::API2::ClamAV",
39 path => 'clamav',
40 });
41
42 __PACKAGE__->register_method ({
43 subclass => "PMG::API2::SpamAssassin",
44 path => 'spamassassin',
45 });
46
47 __PACKAGE__->register_method ({
48 subclass => "PMG::API2::Network",
49 path => 'network',
50 });
51
52 __PACKAGE__->register_method ({
53 subclass => "PMG::API2::Tasks",
54 path => 'tasks',
55 });
56
57 __PACKAGE__->register_method ({
58 subclass => "PMG::API2::Services",
59 path => 'services',
60 });
61
62 __PACKAGE__->register_method ({
63 subclass => "PMG::API2::Subscription",
64 path => 'subscription',
65 });
66
67 __PACKAGE__->register_method ({
68 subclass => "PMG::API2::APT",
69 path => 'apt',
70 });
71
72 __PACKAGE__->register_method ({
73 subclass => "PMG::API2::MailTracker",
74 path => 'tracker',
75 });
76
77 __PACKAGE__->register_method ({
78 subclass => "PMG::API2::Backup",
79 path => 'backup',
80 });
81
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 = [
106 { name => 'apt' },
107 { name => 'backup' },
108 { name => 'clamav' },
109 { name => 'spamassassin' },
110 { name => 'postfix' },
111 { name => 'services' },
112 { name => 'syslog' },
113 { name => 'tasks' },
114 { name => 'tracker' },
115 { name => 'time' },
116 { name => 'report' },
117 { name => 'status' },
118 { name => 'subscription' },
119 { name => 'termproxy' },
120 { name => 'rrddata' },
121 ];
122
123 return $result;
124 }});
125
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
147 __PACKAGE__->register_method({
148 name => 'rrddata',
149 path => 'rrddata',
150 method => 'GET',
151 protected => 1, # fixme: can we avoid that?
152 proxyto => 'node',
153 permissions => { check => [ 'admin', 'audit' ] },
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
187 __PACKAGE__->register_method({
188 name => 'syslog',
189 path => 'syslog',
190 method => 'GET',
191 description => "Read system log",
192 proxyto => 'node',
193 protected => 1,
194 permissions => { check => [ 'admin', 'audit' ] },
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 => {
210 type => 'string',
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' => {
216 type => 'string',
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 },
221 service => {
222 description => "Service ID",
223 type => 'string',
224 maxLength => 128,
225 optional => 1,
226 },
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
248 my $restenv = PMG::RESTEnvironment->get();
249
250 my $service = $param->{service};
251 $service = PMG::Utils::lookup_real_service_name($service)
252 if $service;
253
254 my ($count, $lines) = PVE::Tools::dump_journal(
255 $param->{start}, $param->{limit},
256 $param->{since}, $param->{'until'}, $service);
257
258 $restenv->set_result_attrib('total', $count);
259
260 return $lines;
261 }});
262
263
264 __PACKAGE__->register_method ({
265 name => 'termproxy',
266 path => 'termproxy',
267 method => 'POST',
268 permissions => { check => [ 'admin' ] },
269 protected => 1,
270 description => "Creates a Terminal proxy.",
271 parameters => {
272 additionalProperties => 0,
273 properties => {
274 node => get_standard_option('pve-node'),
275 upgrade => {
276 type => 'boolean',
277 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
278 optional => 1,
279 default => 0,
280 },
281 },
282 },
283 returns => {
284 additionalProperties => 0,
285 properties => {
286 user => { type => 'string' },
287 ticket => { type => 'string' },
288 port => { type => 'integer' },
289 upid => { type => 'string' },
290 },
291 },
292 code => sub {
293 my ($param) = @_;
294
295 my $node = $param->{node};
296
297 if ($node ne PVE::INotify::nodename()) {
298 die "termproxy to remote node not implemented";
299 }
300
301 my $authpath = "/nodes/$node";
302
303 my $restenv = PMG::RESTEnvironment->get();
304 my $user = $restenv->get_user();
305
306 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
307
308 my $ticket = PMG::Ticket::assemble_vnc_ticket($user, $authpath);
309
310 my $family = PVE::Tools::get_host_address_family($node);
311 my $port = PVE::Tools::next_vnc_port($family);
312
313 my $shcmd;
314
315 if ($user eq 'root@pam') {
316 if ($param->{upgrade}) {
317 my $upgradecmd = "pmgupgrade --shell";
318 # $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
319 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
320 } else {
321 $shcmd = [ '/bin/login', '-f', 'root' ];
322 }
323 } else {
324 $shcmd = [ '/bin/login' ];
325 }
326
327 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
328 '--', @$shcmd];
329
330 my $realcmd = sub {
331 my $upid = shift;
332
333 syslog ('info', "starting termproxy $upid\n");
334
335 my $cmdstr = join (' ', @$cmd);
336 syslog ('info', "launch command: $cmdstr");
337
338 PVE::Tools::run_command($cmd);
339
340 return;
341 };
342
343 my $upid = $restenv->fork_worker('termproxy', "", $user, $realcmd);
344
345 PVE::Tools::wait_for_vnc_port($port);
346
347 return {
348 user => $user,
349 ticket => $ticket,
350 port => $port,
351 upid => $upid,
352 };
353 }});
354
355 __PACKAGE__->register_method({
356 name => 'vncwebsocket',
357 path => 'vncwebsocket',
358 method => 'GET',
359 permissions => { check => [ 'admin' ] },
360 description => "Opens a weksocket for VNC traffic.",
361 parameters => {
362 additionalProperties => 0,
363 properties => {
364 node => get_standard_option('pve-node'),
365 vncticket => {
366 description => "Ticket from previous call to vncproxy.",
367 type => 'string',
368 maxLength => 512,
369 },
370 port => {
371 description => "Port number returned by previous vncproxy call.",
372 type => 'integer',
373 minimum => 5900,
374 maximum => 5999,
375 },
376 },
377 },
378 returns => {
379 type => "object",
380 properties => {
381 port => { type => 'string' },
382 },
383 },
384 code => sub {
385 my ($param) = @_;
386
387 my $authpath = "/nodes/$param->{node}";
388
389 my $restenv = PMG::RESTEnvironment->get();
390 my $user = $restenv->get_user();
391
392 PMG::Ticket::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
393
394 my $port = $param->{port};
395
396 return { port => $port };
397 }});
398
399 __PACKAGE__->register_method({
400 name => 'dns',
401 path => 'dns',
402 method => 'GET',
403 description => "Read DNS settings.",
404 proxyto => 'node',
405 permissions => { check => [ 'admin', 'audit' ] },
406 parameters => {
407 additionalProperties => 0,
408 properties => {
409 node => get_standard_option('pve-node'),
410 },
411 },
412 returns => {
413 type => "object",
414 additionalProperties => 0,
415 properties => {
416 search => {
417 description => "Search domain for host-name lookup.",
418 type => 'string',
419 optional => 1,
420 },
421 dns1 => {
422 description => 'First name server IP address.',
423 type => 'string',
424 optional => 1,
425 },
426 dns2 => {
427 description => 'Second name server IP address.',
428 type => 'string',
429 optional => 1,
430 },
431 dns3 => {
432 description => 'Third name server IP address.',
433 type => 'string',
434 optional => 1,
435 },
436 },
437 },
438 code => sub {
439 my ($param) = @_;
440
441 my $res = PVE::INotify::read_file('resolvconf');
442
443 return $res;
444 }});
445
446 __PACKAGE__->register_method({
447 name => 'update_dns',
448 path => 'dns',
449 method => 'PUT',
450 description => "Write DNS settings.",
451 proxyto => 'node',
452 protected => 1,
453 parameters => {
454 additionalProperties => 0,
455 properties => {
456 node => get_standard_option('pve-node'),
457 search => {
458 description => "Search domain for host-name lookup.",
459 type => 'string',
460 },
461 dns1 => {
462 description => 'First name server IP address.',
463 type => 'string', format => 'ip',
464 optional => 1,
465 },
466 dns2 => {
467 description => 'Second name server IP address.',
468 type => 'string', format => 'ip',
469 optional => 1,
470 },
471 dns3 => {
472 description => 'Third name server IP address.',
473 type => 'string', format => 'ip',
474 optional => 1,
475 },
476 },
477 },
478 returns => { type => "null" },
479 code => sub {
480 my ($param) = @_;
481
482 PVE::INotify::update_file('resolvconf', $param);
483
484 return undef;
485 }});
486
487
488 __PACKAGE__->register_method({
489 name => 'time',
490 path => 'time',
491 method => 'GET',
492 description => "Read server time and time zone settings.",
493 proxyto => 'node',
494 permissions => { check => [ 'admin', 'audit' ] },
495 parameters => {
496 additionalProperties => 0,
497 properties => {
498 node => get_standard_option('pve-node'),
499 },
500 },
501 returns => {
502 type => "object",
503 additionalProperties => 0,
504 properties => {
505 timezone => {
506 description => "Time zone",
507 type => 'string',
508 },
509 time => {
510 description => "Seconds since 1970-01-01 00:00:00 UTC.",
511 type => 'integer',
512 minimum => 1297163644,
513 },
514 localtime => {
515 description => "Seconds since 1970-01-01 00:00:00 (local time)",
516 type => 'integer',
517 minimum => 1297163644,
518 },
519 },
520 },
521 code => sub {
522 my ($param) = @_;
523
524 my $ctime = time();
525 my $ltime = timegm_nocheck(localtime($ctime));
526 my $res = {
527 timezone => PVE::INotify::read_file('timezone'),
528 time => time(),
529 localtime => $ltime,
530 };
531
532 return $res;
533 }});
534
535 __PACKAGE__->register_method({
536 name => 'set_timezone',
537 path => 'time',
538 method => 'PUT',
539 description => "Set time zone.",
540 proxyto => 'node',
541 protected => 1,
542 parameters => {
543 additionalProperties => 0,
544 properties => {
545 node => get_standard_option('pve-node'),
546 timezone => {
547 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
548 type => 'string',
549 },
550 },
551 },
552 returns => { type => "null" },
553 code => sub {
554 my ($param) = @_;
555
556 PVE::INotify::write_file('timezone', $param->{timezone});
557
558 return undef;
559 }});
560
561 __PACKAGE__->register_method({
562 name => 'status',
563 path => 'status',
564 method => 'GET',
565 description => "Read server status. This is used by the cluster manager to test the node health.",
566 proxyto => 'node',
567 permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
568 protected => 1,
569 parameters => {
570 additionalProperties => 0,
571 properties => {
572 node => get_standard_option('pve-node'),
573 },
574 },
575 returns => {
576 type => "object",
577 additionalProperties => 1,
578 properties => {
579 time => {
580 description => "Seconds since 1970-01-01 00:00:00 UTC.",
581 type => 'integer',
582 minimum => 1297163644,
583 },
584 uptime => {
585 description => "The uptime of the system in seconds.",
586 type => 'integer',
587 minimum => 0,
588 },
589 insync => {
590 description => "Database is synced with other nodes.",
591 type => 'boolean',
592 },
593 },
594 },
595 code => sub {
596 my ($param) = @_;
597
598 my $restenv = PMG::RESTEnvironment->get();
599 my $cinfo = $restenv->{cinfo};
600
601 my $ctime = time();
602
603 my $res = { time => $ctime, insync => 1 };
604
605 my $si = PMG::DBTools::cluster_sync_status($cinfo);
606 foreach my $cid (keys %$si) {
607 my $lastsync = $si->{$cid};
608 my $sdiff = $ctime - $lastsync;
609 $sdiff = 0 if $sdiff < 0;
610 $res->{insync} = 0 if $sdiff > (60*3);
611 }
612
613 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
614 $res->{uptime} = $uptime;
615
616 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
617 $res->{loadavg} = [ $avg1, $avg5, $avg15];
618
619 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
620
621 $res->{kversion} = "$sysname $release $version";
622
623 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
624
625 my $stat = PVE::ProcFSTools::read_proc_stat();
626 $res->{cpu} = $stat->{cpu};
627 $res->{wait} = $stat->{wait};
628
629 my $meminfo = PVE::ProcFSTools::read_meminfo();
630 $res->{memory} = {
631 free => $meminfo->{memfree},
632 total => $meminfo->{memtotal},
633 used => $meminfo->{memused},
634 };
635
636 $res->{swap} = {
637 free => $meminfo->{swapfree},
638 total => $meminfo->{swaptotal},
639 used => $meminfo->{swapused},
640 };
641
642 $res->{pmgversion} = PMG::pmgcfg::package() . "/" .
643 PMG::pmgcfg::version_text();
644
645 my $dinfo = df('/', 1); # output is bytes
646
647 $res->{rootfs} = {
648 total => $dinfo->{blocks},
649 avail => $dinfo->{bavail},
650 used => $dinfo->{used},
651 free => $dinfo->{blocks} - $dinfo->{used},
652 };
653
654 if (my $subinfo = PVE::INotify::read_file('subscription')) {
655 if (my $level = $subinfo->{level}) {
656 $res->{level} = $level;
657 }
658 }
659
660 return $res;
661 }});
662
663 __PACKAGE__->register_method({
664 name => 'node_cmd',
665 path => 'status',
666 method => 'POST',
667 permissions => { check => [ 'admin' ] },
668 protected => 1,
669 description => "Reboot or shutdown a node.",
670 proxyto => 'node',
671 parameters => {
672 additionalProperties => 0,
673 properties => {
674 node => get_standard_option('pve-node'),
675 command => {
676 description => "Specify the command.",
677 type => 'string',
678 enum => [qw(reboot shutdown)],
679 },
680 },
681 },
682 returns => { type => "null" },
683 code => sub {
684 my ($param) = @_;
685
686 if ($param->{command} eq 'reboot') {
687 system ("(sleep 2;/sbin/reboot)&");
688 } elsif ($param->{command} eq 'shutdown') {
689 system ("(sleep 2;/sbin/poweroff)&");
690 }
691
692 return undef;
693 }});
694
695 package PMG::API2::Nodes;
696
697 use strict;
698 use warnings;
699
700 use PVE::RESTHandler;
701 use PVE::JSONSchema qw(get_standard_option);
702
703 use PMG::RESTEnvironment;
704
705 use base qw(PVE::RESTHandler);
706
707 __PACKAGE__->register_method ({
708 subclass => "PMG::API2::NodeInfo",
709 path => '{node}',
710 });
711
712 __PACKAGE__->register_method ({
713 name => 'index',
714 path => '',
715 method => 'GET',
716 permissions => { user => 'all' },
717 description => "Cluster node index.",
718 parameters => {
719 additionalProperties => 0,
720 properties => {},
721 },
722 returns => {
723 type => 'array',
724 items => {
725 type => "object",
726 properties => {},
727 },
728 links => [ { rel => 'child', href => "{node}" } ],
729 },
730 code => sub {
731 my ($param) = @_;
732
733 my $nodename = PVE::INotify::nodename();
734
735 my $res = [ { node => $nodename } ];
736
737 my $done = {};
738
739 $done->{$nodename} = 1;
740
741 my $restenv = PMG::RESTEnvironment->get();
742 my $cinfo = $restenv->{cinfo};
743
744 foreach my $ni (values %{$cinfo->{ids}}) {
745 push @$res, { node => $ni->{name} } if !$done->{$ni->{name}};
746 $done->{$ni->{name}} = 1;
747 }
748
749 return $res;
750 }});
751
752
753 1;