]>
Commit | Line | Data |
---|---|---|
3dfe317a DM |
1 | package PMG::CLI::pmgreport; |
2 | ||
3 | use strict; | |
4 | use Data::Dumper; | |
5 | use Template; | |
6 | use POSIX qw(strftime); | |
c6769195 | 7 | use HTML::Entities; |
3dfe317a DM |
8 | |
9 | use PVE::INotify; | |
10 | use PVE::CLIHandler; | |
11 | ||
12 | use PMG::Utils; | |
13 | use PMG::Config; | |
14 | use PMG::RESTEnvironment; | |
308c2bfc | 15 | |
3dfe317a | 16 | use PMG::API2::Nodes; |
308c2bfc DM |
17 | use PMG::ClusterConfig; |
18 | use PMG::Cluster; | |
19 | use PMG::API2::Cluster; | |
02251e49 | 20 | use PMG::RuleDB; |
8af3f74c | 21 | use PMG::DBTools; |
02251e49 | 22 | use PMG::Statistic; |
789a35ee | 23 | use PMG::API2::Statistics; |
3dfe317a DM |
24 | |
25 | use base qw(PVE::CLIHandler); | |
26 | ||
27 | my $nodename = PVE::INotify::nodename(); | |
28 | ||
29 | sub setup_environment { | |
30 | PMG::RESTEnvironment->setup_default_cli_env(); | |
308c2bfc DM |
31 | |
32 | my $rpcenv = PMG::RESTEnvironment->get(); | |
33 | # API /config/cluster/nodes need a ticket to connect to other nodes | |
34 | my $ticket = PMG::Ticket::assemble_ticket('root@pam'); | |
35 | $rpcenv->set_ticket($ticket); | |
3dfe317a DM |
36 | } |
37 | ||
38 | my $get_system_table_data = sub { | |
39 | ||
40 | my $ni = PMG::API2::NodeInfo->status({ node => $nodename }); | |
41 | ||
42 | my $data = []; | |
43 | ||
44 | push @$data, { text => 'Hostname', value => $nodename }; | |
45 | ||
46 | my $uptime = $ni->{uptime} ? PMG::Utils::format_uptime($ni->{uptime}) : '-'; | |
47 | ||
48 | push @$data, { text => 'Uptime', value => $uptime }; | |
49 | ||
50 | push @$data, { text => 'Version', value => $ni->{pmgversion} }; | |
51 | ||
52 | my $loadavg15 = '-'; | |
53 | if (my $d = $ni->{loadavg}) { | |
308c2bfc | 54 | $loadavg15 = sprintf("%.2f", $d->[2]); |
3dfe317a DM |
55 | } |
56 | push @$data, { text => 'Load', value => $loadavg15 }; | |
57 | ||
58 | my $mem = '-'; | |
59 | if (my $d = $ni->{memory}) { | |
60 | $mem = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
61 | } | |
62 | push @$data, { text => 'Memory', value => $mem }; | |
63 | ||
64 | my $disk = '-'; | |
65 | if (my $d = $ni->{rootfs}) { | |
66 | $disk = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
67 | } | |
68 | push @$data, { text => 'Disk', value => $disk }; | |
69 | ||
70 | return $data | |
71 | }; | |
72 | ||
308c2bfc DM |
73 | my $get_cluster_table_data = sub { |
74 | ||
75 | my $res = PMG::API2::Cluster->status({}); | |
76 | return undef if !scalar(@$res); | |
77 | ||
78 | my $data = []; | |
79 | ||
80 | foreach my $ni (@$res) { | |
81 | my $state = 'A'; | |
82 | $state = 'S' if !$ni->{insync}; | |
83 | ||
c6769195 DM |
84 | if (my $err = $ni->{conn_error}) { |
85 | $err =~ s/\n/ /g; | |
86 | $state = encode_entities("ERROR: $err"); | |
87 | } | |
88 | ||
308c2bfc DM |
89 | my $loadavg1 = '-'; |
90 | if (my $d = $ni->{loadavg}) { | |
91 | $loadavg1 = sprintf("%.2f", $d->[0]); | |
92 | } | |
93 | ||
94 | my $memory = '-'; | |
95 | if (my $d = $ni->{memory}) { | |
96 | $memory = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
97 | } | |
98 | ||
99 | my $disk = '-'; | |
100 | if (my $d = $ni->{rootfs}) { | |
101 | $disk = sprintf("%.2f%%", $d->{used}*100/$d->{total}); | |
102 | } | |
103 | ||
104 | push @$data, { | |
105 | hostname => $ni->{name}, | |
106 | ip => $ni->{ip}, | |
107 | type => $ni->{type}, | |
108 | state => $state, | |
109 | loadavg1 => $loadavg1, | |
110 | memory => $memory, | |
111 | disk => $disk, | |
112 | }; | |
113 | }; | |
114 | ||
115 | return $data; | |
116 | }; | |
3dfe317a | 117 | |
30898745 DM |
118 | my $get_incoming_table_data = sub { |
119 | my ($totals) = @_; | |
120 | ||
121 | my $data = []; | |
122 | ||
123 | push @$data, { | |
124 | text => 'Incoming Mails', | |
125 | value => $totals->{count_in}, | |
30898745 DM |
126 | }; |
127 | ||
789a35ee DM |
128 | my $count_in = $totals->{count_in}; |
129 | ||
130 | my $junk_in_per = $count_in ? sprintf("%0.2f", ($totals->{junk_in}*100)/$count_in) : undef; | |
131 | ||
30898745 | 132 | push @$data, { |
789a35ee DM |
133 | text => 'Junk Mails', |
134 | value => $totals->{junk_in}, | |
135 | percentage => $junk_in_per, | |
30898745 DM |
136 | }; |
137 | ||
789a35ee DM |
138 | my $spamcount_in_per = $count_in ? sprintf("%0.2f", ($totals->{spamcount_in}*100)/$count_in) : undef; |
139 | ||
140 | push @$data, { | |
141 | text => 'Spam Mails', | |
142 | value => $totals->{spamcount_in}, | |
143 | percentage => $spamcount_in_per, | |
30898745 DM |
144 | }; |
145 | ||
789a35ee DM |
146 | my $spfcount_per = $count_in ? sprintf("%0.2f", ($totals->{spfcount}*100)/$count_in) : undef; |
147 | ||
30898745 DM |
148 | push @$data, { |
149 | text => 'SPF rejects', | |
150 | value => $totals->{spfcount}, | |
789a35ee DM |
151 | percentage => $spfcount_per, |
152 | }; | |
153 | ||
154 | my $bounces_in_per = $count_in ? sprintf("%0.2f", ($totals->{bounces_in}*100)/$count_in) : undef; | |
155 | ||
156 | push @$data, { | |
157 | text => 'Bounces', | |
158 | value => $totals->{bounces_in}, | |
159 | percentage => $bounces_in_per, | |
160 | }; | |
161 | ||
162 | my $viruscount_in_per = $count_in ? sprintf("%0.2f", ($totals->{viruscount_in}*100)/$count_in) : undef; | |
163 | push @$data, { | |
164 | text => 'Virus Mails', | |
165 | value => $totals->{viruscount_in}, | |
166 | percentage => $viruscount_in_per, | |
30898745 DM |
167 | }; |
168 | ||
169 | push @$data, { | |
170 | text => 'Mail Traffic', | |
789a35ee | 171 | value => sprintf ("%.3f MByte", $totals->{bytes_in}/(1024*1024)), |
30898745 DM |
172 | }; |
173 | ||
174 | return $data; | |
175 | }; | |
176 | ||
177 | my $get_outgoing_table_data = sub { | |
178 | my ($totals) = @_; | |
179 | ||
180 | my $data = []; | |
181 | ||
182 | push @$data, { | |
183 | text => 'Outgoing Mails', | |
184 | value => $totals->{count_out}, | |
30898745 DM |
185 | }; |
186 | ||
789a35ee DM |
187 | my $count_out = $totals->{count_out}; |
188 | ||
189 | my $bounces_out_per = $count_out ? sprintf("%0.2f", ($totals->{bounces_out}*100)/$count_out) : undef; | |
190 | ||
30898745 DM |
191 | push @$data, { |
192 | text => 'Bounces', | |
193 | value => $totals->{bounces_out}, | |
789a35ee | 194 | percentage => $bounces_out_per, |
30898745 DM |
195 | }; |
196 | ||
197 | push @$data, { | |
198 | text => 'Mail Traffic', | |
9f17f410 | 199 | value => sprintf ("%.3f MByte", $totals->{bytes_out}/(1024*1024)), |
30898745 DM |
200 | }; |
201 | ||
202 | return $data; | |
203 | }; | |
204 | ||
d68581ee DM |
205 | my $get_virus_table_data = sub { |
206 | my ($virusinfo) = @_; | |
207 | ||
208 | my $data = []; | |
209 | ||
210 | foreach my $entry (@$virusinfo) { | |
211 | next if !$entry->{count}; | |
212 | last if scalar(@$data) >= 10; | |
213 | push @$data, { name => $entry->{name}, count => $entry->{count} }; | |
214 | } | |
215 | ||
216 | return undef if !scalar(@$data); | |
217 | ||
218 | return $data; | |
219 | }; | |
220 | ||
68b5b6c1 DM |
221 | my $get_quarantine_table_data = sub { |
222 | my ($dbh, $qtype) = @_; | |
223 | ||
8af3f74c | 224 | my $ref = PMG::DBTools::get_quarantine_count($dbh, $qtype); |
68b5b6c1 DM |
225 | |
226 | return undef if !($ref && $ref->{count}); | |
227 | ||
228 | my $data = []; | |
229 | ||
230 | push @$data, { | |
231 | text => "Quarantine Size (MBytes)", | |
232 | value => int($ref->{mbytes}), | |
233 | }; | |
234 | ||
235 | push @$data, { | |
236 | text => "Number of Mails", | |
237 | value => $ref->{count}, | |
238 | }; | |
239 | ||
240 | push @$data, { | |
241 | text => "Average Size (Bytes)", | |
242 | value => int($ref->{avgbytes}), | |
243 | }; | |
244 | ||
245 | if ($qtype eq 'S') { | |
246 | push @$data, { | |
247 | text => "Average Spam Level", | |
248 | value => int($ref->{avgspam}), | |
249 | }; | |
250 | } | |
251 | ||
252 | return $data; | |
253 | }; | |
254 | ||
3dfe317a DM |
255 | __PACKAGE__->register_method ({ |
256 | name => 'pmgreport', | |
257 | path => 'pmgreport', | |
258 | method => 'POST', | |
259 | description => "Generate and send daily system report email.", | |
260 | parameters => { | |
261 | additionalProperties => 0, | |
308c2bfc DM |
262 | properties => { |
263 | debug => { | |
264 | description => "Debug mode. Print raw email to stdout instead of sending them.", | |
265 | type => 'boolean', | |
266 | optional => 1, | |
267 | default => 0, | |
268 | }, | |
269 | auto => { | |
270 | description => "Auto mode. Use setting from system configuration (set when invoked fron cron).", | |
271 | type => 'boolean', | |
272 | optional => 1, | |
273 | default => 0, | |
274 | }, | |
275 | receiver => { | |
276 | description => "Send report to this email address. Default is the administratior email address.", | |
277 | type => 'string', format => 'email', | |
278 | optional => 1, | |
279 | }, | |
2f7031b7 DM |
280 | timespan => { |
281 | description => "Select time span for included email statistics.\n\nNOTE: System and cluster performance data is always from current time (when script is run).", | |
282 | type => 'string', | |
283 | enum => ['today', 'yesterday'], | |
284 | default => 'today', | |
285 | optional => 1, | |
286 | }, | |
308c2bfc | 287 | }, |
3dfe317a DM |
288 | }, |
289 | returns => { type => 'null'}, | |
290 | code => sub { | |
291 | my ($param) = @_; | |
292 | ||
2f7031b7 DM |
293 | my $timespan = $param->{timespan} // 'today'; |
294 | my ($start, $end) = PMG::Utils::lookup_timespan($timespan); | |
3dfe317a | 295 | |
2f7031b7 | 296 | my $fqdn = PVE::Tools::get_fqdn($nodename); |
3dfe317a DM |
297 | |
298 | my $vars = { | |
299 | hostname => $nodename, | |
300 | fqdn => $fqdn, | |
301 | date => strftime("%F", localtime($end - 1)), | |
302 | }; | |
303 | ||
308c2bfc DM |
304 | my $cinfo = PMG::ClusterConfig->new(); |
305 | my $role = $cinfo->{local}->{type} // '-'; | |
306 | ||
307 | if ($role eq '-') { | |
308 | $vars->{system} = $get_system_table_data->(); | |
309 | } else { | |
310 | $vars->{cluster} = $get_cluster_table_data->(); | |
311 | if ($role eq 'master') { | |
312 | # OK | |
313 | } else { | |
314 | return undef if $param->{auto}; # silent exit - do not send report | |
315 | } | |
316 | } | |
3dfe317a | 317 | |
02251e49 DM |
318 | my $rdb = PMG::RuleDB->new(); |
319 | ||
320 | # update statistics | |
321 | PMG::Statistic::update_stats($rdb->{dbh}, $cinfo); | |
322 | ||
789a35ee DM |
323 | my $totals = PMG::API2::Statistics->mail( |
324 | { starttime => $start, endtime => $end }); | |
02251e49 | 325 | |
30898745 | 326 | $vars->{incoming} = $get_incoming_table_data->($totals); |
02251e49 | 327 | |
30898745 | 328 | $vars->{outgoing} = $get_outgoing_table_data->($totals); |
02251e49 | 329 | |
789a35ee | 330 | my $stat = PMG::Statistic->new ($start, $end); |
d68581ee DM |
331 | my $virusinfo = $stat->total_virus_stat ($rdb); |
332 | if (my $data = $get_virus_table_data->($virusinfo)) { | |
333 | $vars->{virusstat} = $data; | |
334 | } | |
335 | ||
68b5b6c1 DM |
336 | if (my $data = $get_quarantine_table_data->($rdb->{dbh}, 'V')) { |
337 | $vars->{virusquar} = $data; | |
338 | } | |
339 | ||
340 | if (my $data = $get_quarantine_table_data->($rdb->{dbh}, 'S')) { | |
341 | $vars->{spamquar} = $data; | |
342 | } | |
343 | ||
3dfe317a DM |
344 | my $tt = PMG::Config::get_template_toolkit(); |
345 | ||
346 | my $cfg = PMG::Config->new(); | |
308c2bfc | 347 | my $email = $param->{receiver} // $cfg->get ('admin', 'email'); |
3dfe317a DM |
348 | |
349 | if (!defined($email)) { | |
308c2bfc DM |
350 | return undef if $param->{auto}; # silent exit - do not send report |
351 | die "no receiver configured\n"; | |
3dfe317a DM |
352 | } |
353 | ||
ca498777 DC |
354 | my $enable = $cfg->get('admin', 'dailyreport') // 1; |
355 | ||
356 | if ($param->{auto} && !$enable) { | |
357 | # do nothing when disabled | |
358 | return undef; | |
359 | } | |
360 | ||
3dfe317a DM |
361 | my $mailfrom = "Proxmox Mail Gateway <postmaster>"; |
362 | PMG::Utils::finalize_report($tt, 'pmgreport.tt', $vars, $mailfrom, $email, $param->{debug}); | |
363 | ||
364 | return undef; | |
365 | }}); | |
366 | ||
367 | our $cmddef = [ __PACKAGE__, 'pmgreport', [], undef ]; | |
368 | ||
369 | 1; |