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