]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/Nodes.pm
304c2cffc22fa71a55ccd2ec5e916ab3ce81a228
1 package PMG
::API2
::NodeInfo
;
5 use Time
::Local
qw(timegm_nocheck);
9 use PVE
::Exception
qw(raise_perm_exc);
12 use PVE
::JSONSchema
qw(get_standard_option);
13 use PMG
::RESTEnvironment
;
20 use PMG
::API2
::Subscription
;
23 use PMG
::API2
::Services
;
24 use PMG
::API2
::Network
;
25 use PMG
::API2
::ClamAV
;
26 use PMG
::API2
::SpamAssassin
;
27 use PMG
::API2
::Postfix
;
28 use PMG
::API2
::MailTracker
;
29 use PMG
::API2
::Backup
;
30 use PMG
::API2
::PBS
::Job
;
31 use PMG
::API2
::Certificates
;
32 use PMG
::API2
::NodeConfig
;
34 use base
qw(PVE::RESTHandler);
36 __PACKAGE__-
>register_method ({
37 subclass
=> "PMG::API2::Postfix",
41 __PACKAGE__-
>register_method ({
42 subclass
=> "PMG::API2::ClamAV",
46 __PACKAGE__-
>register_method ({
47 subclass
=> "PMG::API2::SpamAssassin",
48 path
=> 'spamassassin',
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PMG::API2::Network",
56 __PACKAGE__-
>register_method ({
57 subclass
=> "PMG::API2::Tasks",
61 __PACKAGE__-
>register_method ({
62 subclass
=> "PMG::API2::Services",
66 __PACKAGE__-
>register_method ({
67 subclass
=> "PMG::API2::Subscription",
68 path
=> 'subscription',
71 __PACKAGE__-
>register_method ({
72 subclass
=> "PMG::API2::APT",
76 __PACKAGE__-
>register_method ({
77 subclass
=> "PMG::API2::MailTracker",
81 __PACKAGE__-
>register_method ({
82 subclass
=> "PMG::API2::Backup",
86 __PACKAGE__-
>register_method ({
87 subclass
=> "PMG::API2::PBS::Job",
91 __PACKAGE__-
>register_method ({
92 subclass
=> "PMG::API2::Certificates",
93 path
=> 'certificates',
96 __PACKAGE__-
>register_method ({
97 subclass
=> "PMG::API2::NodeConfig",
101 __PACKAGE__-
>register_method ({
105 permissions
=> { user
=> 'all' },
106 description
=> "Node index.",
108 additionalProperties
=> 0,
110 node
=> get_standard_option
('pve-node'),
119 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
126 { name
=> 'backup' },
128 { name
=> 'clamav' },
129 { name
=> 'spamassassin' },
130 { name
=> 'postfix' },
131 { name
=> 'services' },
132 { name
=> 'syslog' },
133 { name
=> 'journal' },
135 { name
=> 'tracker' },
137 { name
=> 'report' },
138 { name
=> 'status' },
139 { name
=> 'subscription' },
140 { name
=> 'termproxy' },
141 { name
=> 'rrddata' },
142 { name
=> 'certificates' },
143 { name
=> 'config' },
149 __PACKAGE__-
>register_method({
155 permissions
=> { check
=> [ 'admin', 'audit' ] },
156 description
=> "Gather various system information about a node",
158 additionalProperties
=> 0,
160 node
=> get_standard_option
('pve-node'),
167 return PMG
::Report
::generate
();
170 __PACKAGE__-
>register_method({
174 protected
=> 1, # fixme: can we avoid that?
176 permissions
=> { check
=> [ 'admin', 'audit' ] },
177 description
=> "Read node RRD statistics",
179 additionalProperties
=> 0,
181 node
=> get_standard_option
('pve-node'),
183 description
=> "Specify the time frame you are interested in.",
185 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
188 description
=> "The RRD consolidation function",
190 enum
=> [ 'AVERAGE', 'MAX' ],
205 return PMG
::Utils
::create_rrd_data
(
206 "pmg-node-v1.rrd", $param->{timeframe
}, $param->{cf
});
210 __PACKAGE__-
>register_method({
214 description
=> "Read system log",
217 permissions
=> { check
=> [ 'admin', 'audit' ] },
219 additionalProperties
=> 0,
221 node
=> get_standard_option
('pve-node'),
234 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
235 description
=> "Display all log since this date-time string.",
240 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
241 description
=> "Display all log until this date-time string.",
245 description
=> "Service ID",
258 description
=> "Line number",
262 description
=> "Line text",
271 my $restenv = PMG
::RESTEnvironment-
>get();
273 my $service = $param->{service
};
274 $service = PMG
::Utils
::lookup_real_service_name
($service)
277 my ($count, $lines) = PVE
::Tools
::dump_journal
(
278 $param->{start
}, $param->{limit
},
279 $param->{since
}, $param->{'until'}, $service);
281 $restenv->set_result_attrib('total', $count);
286 __PACKAGE__-
>register_method({
290 description
=> "Read Journal",
292 permissions
=> { check
=> [ 'admin', 'audit' ] },
295 additionalProperties
=> 0,
297 node
=> get_standard_option
('pve-node'),
299 description
=> "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
305 description
=> "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
311 description
=> "Limit to the last X lines. Conflicts with a range.",
317 description
=> "Start after the given Cursor. Conflicts with 'since'.",
322 description
=> "End before the given Cursor. Conflicts with 'until'.",
337 my $cmd = ["/usr/bin/mini-journalreader", "-j"];
338 push @$cmd, '-n', $param->{lastentries
} if $param->{lastentries
};
339 push @$cmd, '-b', $param->{since
} if $param->{since
};
340 push @$cmd, '-e', $param->{until} if $param->{until};
341 push @$cmd, '-f', PVE
::Tools
::shellquote
($param->{startcursor
}) if $param->{startcursor
};
342 push @$cmd, '-t', PVE
::Tools
::shellquote
($param->{endcursor
}) if $param->{endcursor
};
343 push @$cmd, ' | gzip ';
345 open(my $fh, "-|", join(' ', @$cmd))
346 or die "could not start mini-journalreader";
352 'content-type' => 'application/json',
353 'content-encoding' => 'gzip',
358 my $shell_cmd_map = {
360 cmd
=> [ '/bin/login', '-f', 'root' ],
363 cmd
=> [ 'pmgupgrade', '--shell' ],
367 sub get_shell_command
{
368 my ($user, $shellcmd, $args) = @_;
371 if ($user eq 'root@pam') {
372 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
373 my $def = $shell_cmd_map->{$shellcmd};
374 $cmd = [ @{$def->{cmd
}} ]; # clone
375 if (defined($args) && $def->{allow_args
}) {
376 push @$cmd, split("\0", $args);
379 $cmd = [ '/bin/login', '-f', 'root' ];
382 # non-root must always login for now, we do not have a superuser role!
383 $cmd = [ '/bin/login' ];
388 __PACKAGE__-
>register_method ({
392 permissions
=> { check
=> [ 'admin' ] },
394 description
=> "Creates a Terminal proxy.",
396 additionalProperties
=> 0,
398 node
=> get_standard_option
('pve-node'),
401 description
=> "Run specific command or default to login.",
402 enum
=> [sort keys %$shell_cmd_map],
408 description
=> "Add parameters to a command. Encoded as null terminated strings.",
416 additionalProperties
=> 0,
418 user
=> { type
=> 'string' },
419 ticket
=> { type
=> 'string' },
420 port
=> { type
=> 'integer' },
421 upid
=> { type
=> 'string' },
427 my $node = $param->{node
};
429 if ($node ne PVE
::INotify
::nodename
()) {
430 die "termproxy to remote node not implemented";
433 my $authpath = "/nodes/$node";
435 my $restenv = PMG
::RESTEnvironment-
>get();
436 my $user = $restenv->get_user();
438 if (defined($param->{cmd
}) && $param->{cmd
} eq 'upgrade' && $user ne 'root@pam') {
439 raise_perm_exc
('user != root@pam');
442 my $ticket = PMG
::Ticket
::assemble_vnc_ticket
($user, $authpath);
444 my $family = PVE
::Tools
::get_host_address_family
($node);
445 my $port = PVE
::Tools
::next_vnc_port
($family);
447 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
449 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath, '--', @$shcmd];
454 syslog
('info', "starting termproxy $upid\n");
456 my $cmdstr = join (' ', @$cmd);
457 syslog
('info', "launch command: $cmdstr");
459 PVE
::Tools
::run_command
($cmd);
464 my $upid = $restenv->fork_worker('termproxy', "", $user, $realcmd);
466 PVE
::Tools
::wait_for_vnc_port
($port);
476 __PACKAGE__-
>register_method({
477 name
=> 'vncwebsocket',
478 path
=> 'vncwebsocket',
480 permissions
=> { check
=> [ 'admin' ] },
481 description
=> "Opens a weksocket for VNC traffic.",
483 additionalProperties
=> 0,
485 node
=> get_standard_option
('pve-node'),
487 description
=> "Ticket from previous call to vncproxy.",
492 description
=> "Port number returned by previous vncproxy call.",
502 port
=> { type
=> 'string' },
508 my $authpath = "/nodes/$param->{node}";
510 my $restenv = PMG
::RESTEnvironment-
>get();
511 my $user = $restenv->get_user();
513 PMG
::Ticket
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
515 my $port = $param->{port
};
517 return { port
=> $port };
520 __PACKAGE__-
>register_method({
524 description
=> "Read DNS settings.",
526 permissions
=> { check
=> [ 'admin', 'audit' ] },
528 additionalProperties
=> 0,
530 node
=> get_standard_option
('pve-node'),
535 additionalProperties
=> 0,
538 description
=> "Search domain for host-name lookup.",
543 description
=> 'First name server IP address.',
548 description
=> 'Second name server IP address.',
553 description
=> 'Third name server IP address.',
562 my $res = PVE
::INotify
::read_file
('resolvconf');
567 __PACKAGE__-
>register_method({
568 name
=> 'update_dns',
571 description
=> "Write DNS settings.",
575 additionalProperties
=> 0,
577 node
=> get_standard_option
('pve-node'),
579 description
=> "Search domain for host-name lookup.",
583 description
=> 'First name server IP address.',
584 type
=> 'string', format
=> 'ip',
588 description
=> 'Second name server IP address.',
589 type
=> 'string', format
=> 'ip',
593 description
=> 'Third name server IP address.',
594 type
=> 'string', format
=> 'ip',
599 returns
=> { type
=> "null" },
603 PVE
::INotify
::update_file
('resolvconf', $param);
609 __PACKAGE__-
>register_method({
613 description
=> "Read server time and time zone settings.",
615 permissions
=> { check
=> [ 'admin', 'audit' ] },
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
624 additionalProperties
=> 0,
627 description
=> "Time zone",
631 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
633 minimum
=> 1297163644,
636 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
638 minimum
=> 1297163644,
646 my $ltime = timegm_nocheck
(localtime($ctime));
648 timezone
=> PVE
::INotify
::read_file
('timezone'),
656 __PACKAGE__-
>register_method({
657 name
=> 'set_timezone',
660 description
=> "Set time zone.",
664 additionalProperties
=> 0,
666 node
=> get_standard_option
('pve-node'),
668 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
673 returns
=> { type
=> "null" },
677 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
682 my sub get_current_kernel_info
{
683 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
685 my $kernel_version_string = "$sysname $release $version"; # for legacy compat
686 my $current_kernel = {
692 return ($current_kernel, $kernel_version_string);
695 my $boot_mode_info_cache;
696 my sub get_boot_mode_info
{
697 return $boot_mode_info_cache if defined($boot_mode_info_cache);
699 my $is_efi_booted = -d
"/sys/firmware/efi";
701 $boot_mode_info_cache = {
702 mode
=> $is_efi_booted ?
'efi' : 'legacy-bios',
705 my $efi_var = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c";
707 if ($is_efi_booted && -e
$efi_var) {
708 my $efi_var_sec_boot_entry = eval { file_get_contents
($efi_var) };
710 warn "Failed to read secure boot state: $@\n";
712 my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry);
713 $boot_mode_info_cache->{secureboot
} = $secureboot[4] == 1 ?
1 : 0;
716 return $boot_mode_info_cache;
719 __PACKAGE__-
>register_method({
723 description
=> "Read server status. This is used by the cluster manager to test the node health.",
725 permissions
=> { check
=> [ 'admin', 'qmanager', 'audit' ] },
728 additionalProperties
=> 0,
730 node
=> get_standard_option
('pve-node'),
735 additionalProperties
=> 1,
738 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
740 minimum
=> 1297163644,
743 description
=> "The uptime of the system in seconds.",
748 description
=> "Database is synced with other nodes.",
752 description
=> "Meta-information about the boot mode.",
756 description
=> 'Through which firmware the system got booted.',
758 enum
=> [qw(efi legacy-bios)],
761 description
=> 'System is booted in secure mode, only applicable for the "efi" mode.',
767 'current-kernel' => {
768 description
=> "Meta-information about the currently booted kernel.",
772 description
=> 'OS kernel name (e.g., "Linux")',
776 description
=> 'OS kernel release (e.g., "6.8.0")',
780 description
=> 'OS kernel version with build info',
784 description
=> 'Hardware (architecture) type',
794 my $restenv = PMG
::RESTEnvironment-
>get();
795 my $cinfo = $restenv->{cinfo
};
799 my $res = { time => $ctime, insync
=> 1 };
801 my $si = PMG
::DBTools
::cluster_sync_status
($cinfo);
802 foreach my $cid (keys %$si) {
803 my $lastsync = $si->{$cid};
804 my $sdiff = $ctime - $lastsync;
805 $sdiff = 0 if $sdiff < 0;
806 $res->{insync
} = 0 if $sdiff > (60*3);
809 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
810 $res->{uptime
} = $uptime;
812 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
813 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
815 my ($current_kernel_info, $kversion_string) = get_current_kernel_info
();
816 $res->{kversion
} = $kversion_string;
817 $res->{'current-kernel'} = $current_kernel_info;
819 $res->{'boot-info'} = get_boot_mode_info
();
821 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
823 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
824 $res->{cpu
} = $stat->{cpu
};
825 $res->{wait} = $stat->{wait};
827 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
829 free
=> $meminfo->{memfree
},
830 total
=> $meminfo->{memtotal
},
831 used
=> $meminfo->{memused
},
835 free
=> $meminfo->{swapfree
},
836 total
=> $meminfo->{swaptotal
},
837 used
=> $meminfo->{swapused
},
840 $res->{pmgversion
} = PMG
::pmgcfg
::package() . "/" .
841 PMG
::pmgcfg
::version_text
();
843 my $dinfo = df
('/', 1); # output is bytes
846 total
=> $dinfo->{blocks
},
847 avail
=> $dinfo->{bavail
},
848 used
=> $dinfo->{used
},
849 free
=> $dinfo->{blocks
} - $dinfo->{used
},
852 if (my $subinfo = eval { PMG
::API2
::Subscription
::read_etc_subscription
() } ) {
853 if (my $level = $subinfo->{level
}) {
854 $res->{level
} = $level;
861 __PACKAGE__-
>register_method({
865 permissions
=> { check
=> [ 'admin' ] },
867 description
=> "Reboot or shutdown a node.",
870 additionalProperties
=> 0,
872 node
=> get_standard_option
('pve-node'),
874 description
=> "Specify the command.",
876 enum
=> [qw(reboot shutdown)],
880 returns
=> { type
=> "null" },
884 if ($param->{command
} eq 'reboot') {
885 system ("(sleep 2;/sbin/reboot)&");
886 } elsif ($param->{command
} eq 'shutdown') {
887 system ("(sleep 2;/sbin/poweroff)&");
893 package PMG
::API2
::Nodes
;
898 use PVE
::RESTHandler
;
899 use PVE
::JSONSchema
qw(get_standard_option);
901 use PMG
::RESTEnvironment
;
903 use base
qw(PVE::RESTHandler);
905 __PACKAGE__-
>register_method ({
906 subclass
=> "PMG::API2::NodeInfo",
910 __PACKAGE__-
>register_method ({
914 permissions
=> { user
=> 'all' },
915 description
=> "Cluster node index.",
917 additionalProperties
=> 0,
926 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
931 my $nodename = PVE
::INotify
::nodename
();
933 my $res = [ { node
=> $nodename } ];
937 $done->{$nodename} = 1;
939 my $restenv = PMG
::RESTEnvironment-
>get();
940 my $cinfo = $restenv->{cinfo
};
942 foreach my $ni (values %{$cinfo->{ids
}}) {
943 push @$res, { node
=> $ni->{name
} } if !$done->{$ni->{name
}};
944 $done->{$ni->{name
}} = 1;