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