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