]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/Nodes.pm
1 package PMG
::API2
::NodeInfo
;
5 use Time
::Local
qw(timegm_nocheck);
11 use PVE
::JSONSchema
qw(get_standard_option);
12 use PMG
::RESTEnvironment
;
19 use PMG
::API2
::Subscription
;
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 use PMG
::API2
::PBS
::Job
;
30 use PMG
::API2
::Certificates
;
31 use PMG
::API2
::NodeConfig
;
33 use base
qw(PVE::RESTHandler);
35 __PACKAGE__-
>register_method ({
36 subclass
=> "PMG::API2::Postfix",
40 __PACKAGE__-
>register_method ({
41 subclass
=> "PMG::API2::ClamAV",
45 __PACKAGE__-
>register_method ({
46 subclass
=> "PMG::API2::SpamAssassin",
47 path
=> 'spamassassin',
50 __PACKAGE__-
>register_method ({
51 subclass
=> "PMG::API2::Network",
55 __PACKAGE__-
>register_method ({
56 subclass
=> "PMG::API2::Tasks",
60 __PACKAGE__-
>register_method ({
61 subclass
=> "PMG::API2::Services",
65 __PACKAGE__-
>register_method ({
66 subclass
=> "PMG::API2::Subscription",
67 path
=> 'subscription',
70 __PACKAGE__-
>register_method ({
71 subclass
=> "PMG::API2::APT",
75 __PACKAGE__-
>register_method ({
76 subclass
=> "PMG::API2::MailTracker",
80 __PACKAGE__-
>register_method ({
81 subclass
=> "PMG::API2::Backup",
85 __PACKAGE__-
>register_method ({
86 subclass
=> "PMG::API2::PBS::Job",
90 __PACKAGE__-
>register_method ({
91 subclass
=> "PMG::API2::Certificates",
92 path
=> 'certificates',
95 __PACKAGE__-
>register_method ({
96 subclass
=> "PMG::API2::NodeConfig",
100 __PACKAGE__-
>register_method ({
104 permissions
=> { user
=> 'all' },
105 description
=> "Node index.",
107 additionalProperties
=> 0,
109 node
=> get_standard_option
('pve-node'),
118 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
125 { name
=> 'backup' },
127 { name
=> 'clamav' },
128 { name
=> 'spamassassin' },
129 { name
=> 'postfix' },
130 { name
=> 'services' },
131 { name
=> 'syslog' },
132 { name
=> 'journal' },
134 { name
=> 'tracker' },
136 { name
=> 'report' },
137 { name
=> 'status' },
138 { name
=> 'subscription' },
139 { name
=> 'termproxy' },
140 { name
=> 'rrddata' },
141 { name
=> 'certificates' },
142 { name
=> 'config' },
148 __PACKAGE__-
>register_method({
154 permissions
=> { check
=> [ 'admin', 'audit' ] },
155 description
=> "Gather various system information about a node",
157 additionalProperties
=> 0,
159 node
=> get_standard_option
('pve-node'),
166 return PMG
::Report
::generate
();
169 __PACKAGE__-
>register_method({
173 protected
=> 1, # fixme: can we avoid that?
175 permissions
=> { check
=> [ 'admin', 'audit' ] },
176 description
=> "Read node RRD statistics",
178 additionalProperties
=> 0,
180 node
=> get_standard_option
('pve-node'),
182 description
=> "Specify the time frame you are interested in.",
184 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
187 description
=> "The RRD consolidation function",
189 enum
=> [ 'AVERAGE', 'MAX' ],
204 return PMG
::Utils
::create_rrd_data
(
205 "pmg-node-v1.rrd", $param->{timeframe
}, $param->{cf
});
209 __PACKAGE__-
>register_method({
213 description
=> "Read system log",
216 permissions
=> { check
=> [ 'admin', 'audit' ] },
218 additionalProperties
=> 0,
220 node
=> get_standard_option
('pve-node'),
233 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
234 description
=> "Display all log since this date-time string.",
239 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
240 description
=> "Display all log until this date-time string.",
244 description
=> "Service ID",
257 description
=> "Line number",
261 description
=> "Line text",
270 my $restenv = PMG
::RESTEnvironment-
>get();
272 my $service = $param->{service
};
273 $service = PMG
::Utils
::lookup_real_service_name
($service)
276 my ($count, $lines) = PVE
::Tools
::dump_journal
(
277 $param->{start
}, $param->{limit
},
278 $param->{since
}, $param->{'until'}, $service);
280 $restenv->set_result_attrib('total', $count);
285 __PACKAGE__-
>register_method({
289 description
=> "Read Journal",
291 permissions
=> { check
=> [ 'admin', 'audit' ] },
294 additionalProperties
=> 0,
296 node
=> get_standard_option
('pve-node'),
298 description
=> "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
304 description
=> "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
310 description
=> "Limit to the last X lines. Conflicts with a range.",
316 description
=> "Start after the given Cursor. Conflicts with 'since'.",
321 description
=> "End before the given Cursor. Conflicts with 'until'.",
336 my $cmd = ["/usr/bin/mini-journalreader"];
337 push @$cmd, '-n', $param->{lastentries
} if $param->{lastentries
};
338 push @$cmd, '-b', $param->{since
} if $param->{since
};
339 push @$cmd, '-e', $param->{until} if $param->{until};
340 push @$cmd, '-f', $param->{startcursor
} if $param->{startcursor
};
341 push @$cmd, '-t', $param->{endcursor
} if $param->{endcursor
};
344 my $parser = sub { push @$lines, shift };
346 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
351 my $shell_cmd_map = {
353 cmd
=> [ '/bin/login', '-f', 'root' ],
356 cmd
=> [ 'pmgupgrade', '--shell' ],
360 sub get_shell_command
{
361 my ($user, $shellcmd, $args) = @_;
364 if ($user eq 'root@pam') {
365 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
366 my $def = $shell_cmd_map->{$shellcmd};
367 $cmd = [ @{$def->{cmd
}} ]; # clone
368 if (defined($args) && $def->{allow_args
}) {
369 push @$cmd, split("\0", $args);
372 $cmd = [ '/bin/login', '-f', 'root' ];
375 # non-root must always login for now, we do not have a superuser role!
376 $cmd = [ '/bin/login' ];
381 __PACKAGE__-
>register_method ({
385 permissions
=> { check
=> [ 'admin' ] },
387 description
=> "Creates a Terminal proxy.",
389 additionalProperties
=> 0,
391 node
=> get_standard_option
('pve-node'),
394 description
=> "Run specific command or default to login.",
395 enum
=> [keys %$shell_cmd_map],
401 description
=> "Add parameters to a command. Encoded as null terminated strings.",
409 additionalProperties
=> 0,
411 user
=> { type
=> 'string' },
412 ticket
=> { type
=> 'string' },
413 port
=> { type
=> 'integer' },
414 upid
=> { type
=> 'string' },
420 my $node = $param->{node
};
422 if ($node ne PVE
::INotify
::nodename
()) {
423 die "termproxy to remote node not implemented";
426 my $authpath = "/nodes/$node";
428 my $restenv = PMG
::RESTEnvironment-
>get();
429 my $user = $restenv->get_user();
431 raise_perm_exc
('user != root@pam') if $param->{cmd
} eq 'upgrade' && $user ne 'root@pam';
433 my $ticket = PMG
::Ticket
::assemble_vnc_ticket
($user, $authpath);
435 my $family = PVE
::Tools
::get_host_address_family
($node);
436 my $port = PVE
::Tools
::next_vnc_port
($family);
438 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
440 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath, '--', @$shcmd];
445 syslog
('info', "starting termproxy $upid\n");
447 my $cmdstr = join (' ', @$cmd);
448 syslog
('info', "launch command: $cmdstr");
450 PVE
::Tools
::run_command
($cmd);
455 my $upid = $restenv->fork_worker('termproxy', "", $user, $realcmd);
457 PVE
::Tools
::wait_for_vnc_port
($port);
467 __PACKAGE__-
>register_method({
468 name
=> 'vncwebsocket',
469 path
=> 'vncwebsocket',
471 permissions
=> { check
=> [ 'admin' ] },
472 description
=> "Opens a weksocket for VNC traffic.",
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
478 description
=> "Ticket from previous call to vncproxy.",
483 description
=> "Port number returned by previous vncproxy call.",
493 port
=> { type
=> 'string' },
499 my $authpath = "/nodes/$param->{node}";
501 my $restenv = PMG
::RESTEnvironment-
>get();
502 my $user = $restenv->get_user();
504 PMG
::Ticket
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
506 my $port = $param->{port
};
508 return { port
=> $port };
511 __PACKAGE__-
>register_method({
515 description
=> "Read DNS settings.",
517 permissions
=> { check
=> [ 'admin', 'audit' ] },
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
526 additionalProperties
=> 0,
529 description
=> "Search domain for host-name lookup.",
534 description
=> 'First name server IP address.',
539 description
=> 'Second name server IP address.',
544 description
=> 'Third name server IP address.',
553 my $res = PVE
::INotify
::read_file
('resolvconf');
558 __PACKAGE__-
>register_method({
559 name
=> 'update_dns',
562 description
=> "Write DNS settings.",
566 additionalProperties
=> 0,
568 node
=> get_standard_option
('pve-node'),
570 description
=> "Search domain for host-name lookup.",
574 description
=> 'First name server IP address.',
575 type
=> 'string', format
=> 'ip',
579 description
=> 'Second name server IP address.',
580 type
=> 'string', format
=> 'ip',
584 description
=> 'Third name server IP address.',
585 type
=> 'string', format
=> 'ip',
590 returns
=> { type
=> "null" },
594 PVE
::INotify
::update_file
('resolvconf', $param);
600 __PACKAGE__-
>register_method({
604 description
=> "Read server time and time zone settings.",
606 permissions
=> { check
=> [ 'admin', 'audit' ] },
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
615 additionalProperties
=> 0,
618 description
=> "Time zone",
622 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
624 minimum
=> 1297163644,
627 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
629 minimum
=> 1297163644,
637 my $ltime = timegm_nocheck
(localtime($ctime));
639 timezone
=> PVE
::INotify
::read_file
('timezone'),
647 __PACKAGE__-
>register_method({
648 name
=> 'set_timezone',
651 description
=> "Set time zone.",
655 additionalProperties
=> 0,
657 node
=> get_standard_option
('pve-node'),
659 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
664 returns
=> { type
=> "null" },
668 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
673 __PACKAGE__-
>register_method({
677 description
=> "Read server status. This is used by the cluster manager to test the node health.",
679 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
682 additionalProperties
=> 0,
684 node
=> get_standard_option
('pve-node'),
689 additionalProperties
=> 1,
692 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
694 minimum
=> 1297163644,
697 description
=> "The uptime of the system in seconds.",
702 description
=> "Database is synced with other nodes.",
710 my $restenv = PMG
::RESTEnvironment-
>get();
711 my $cinfo = $restenv->{cinfo
};
715 my $res = { time => $ctime, insync
=> 1 };
717 my $si = PMG
::DBTools
::cluster_sync_status
($cinfo);
718 foreach my $cid (keys %$si) {
719 my $lastsync = $si->{$cid};
720 my $sdiff = $ctime - $lastsync;
721 $sdiff = 0 if $sdiff < 0;
722 $res->{insync
} = 0 if $sdiff > (60*3);
725 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
726 $res->{uptime
} = $uptime;
728 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
729 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
731 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
733 $res->{kversion
} = "$sysname $release $version";
735 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
737 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
738 $res->{cpu
} = $stat->{cpu
};
739 $res->{wait} = $stat->{wait};
741 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
743 free
=> $meminfo->{memfree
},
744 total
=> $meminfo->{memtotal
},
745 used
=> $meminfo->{memused
},
749 free
=> $meminfo->{swapfree
},
750 total
=> $meminfo->{swaptotal
},
751 used
=> $meminfo->{swapused
},
754 $res->{pmgversion
} = PMG
::pmgcfg
::package() . "/" .
755 PMG
::pmgcfg
::version_text
();
757 my $dinfo = df
('/', 1); # output is bytes
760 total
=> $dinfo->{blocks
},
761 avail
=> $dinfo->{bavail
},
762 used
=> $dinfo->{used
},
763 free
=> $dinfo->{blocks
} - $dinfo->{used
},
766 if (my $subinfo = PVE
::INotify
::read_file
('subscription')) {
767 if (my $level = $subinfo->{level
}) {
768 $res->{level
} = $level;
775 __PACKAGE__-
>register_method({
779 permissions
=> { check
=> [ 'admin' ] },
781 description
=> "Reboot or shutdown a node.",
784 additionalProperties
=> 0,
786 node
=> get_standard_option
('pve-node'),
788 description
=> "Specify the command.",
790 enum
=> [qw(reboot shutdown)],
794 returns
=> { type
=> "null" },
798 if ($param->{command
} eq 'reboot') {
799 system ("(sleep 2;/sbin/reboot)&");
800 } elsif ($param->{command
} eq 'shutdown') {
801 system ("(sleep 2;/sbin/poweroff)&");
807 package PMG
::API2
::Nodes
;
812 use PVE
::RESTHandler
;
813 use PVE
::JSONSchema
qw(get_standard_option);
815 use PMG
::RESTEnvironment
;
817 use base
qw(PVE::RESTHandler);
819 __PACKAGE__-
>register_method ({
820 subclass
=> "PMG::API2::NodeInfo",
824 __PACKAGE__-
>register_method ({
828 permissions
=> { user
=> 'all' },
829 description
=> "Cluster node index.",
831 additionalProperties
=> 0,
840 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
845 my $nodename = PVE
::INotify
::nodename
();
847 my $res = [ { node
=> $nodename } ];
851 $done->{$nodename} = 1;
853 my $restenv = PMG
::RESTEnvironment-
>get();
854 my $cinfo = $restenv->{cinfo
};
856 foreach my $ni (values %{$cinfo->{ids
}}) {
857 push @$res, { node
=> $ni->{name
} } if !$done->{$ni->{name
}};
858 $done->{$ni->{name
}} = 1;