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