File Coverage

blib/lib/Zabbix/ServerScript.pm
Criterion Covered Total %
statement 243 247 98.3
branch 96 106 90.5
condition 23 30 76.6
subroutine 39 40 97.5
pod 7 7 100.0
total 408 430 94.8


line stmt bran cond sub pod time code
1             package Zabbix::ServerScript;
2              
3 19     19   974671 use strict;
  19         29  
  19         424  
4 19     19   55 use warnings;
  19         22  
  19         359  
5 19     19   46 use Exporter;
  19         26  
  19         487  
6 19     19   562 use Data::Dumper;
  19         4618  
  19         643  
7 19     19   6961 use YAML;
  19         91310  
  19         780  
8 19     19   9802 use JSON;
  19         138571  
  19         68  
9 19     19   15261 use Log::Log4perl;
  19         622672  
  19         88  
10 19     19   762 use Log::Log4perl::Level;
  19         23  
  19         92  
11 19     19   11709 use Proc::PID::File;
  19         30544  
  19         779  
12 19     19   9863 use Proc::Daemon;
  19         131901  
  19         424  
13 19     19   96 use File::Basename;
  19         18  
  19         979  
14 19     19   79 use Exporter;
  19         17  
  19         477  
15 19     19   57 use Carp;
  19         21  
  19         678  
16 19     19   5930 use Storable;
  19         25730  
  19         786  
17 19     19   8637 use Term::ReadLine;
  19         39484  
  19         460  
18 19     19   8234 use Term::UI;
  19         368720  
  19         571  
19 19     19   12414 use Getopt::Long qw(:config bundling);
  19         127787  
  19         66  
20 19     19   11454 use List::MoreUtils qw(uniq);
  19         121513  
  19         97  
21              
22             BEGIN {
23             eval {
24 19         3316 require Zabbix::ServerScript::Config;
25 0         0 1;
26 19 50 33 19   8518 } or eval {
27 19         7374 require Zabbix::ServerScript::DefaultConfig;
28 19         34726 1;
29             } or die q(Either Zabbix::ServerScript::DefaultConfig or Zabbix::ServerScript::Config is required);
30             }
31              
32             our @ISA = q(Exporter);
33             our @EXPORT = qw($config $logger $zx_api create_config);
34             our $VERSION = q(0.13);
35              
36             our $config = {};
37             our $logger;
38             our $zx_api;
39              
40             sub _get_options {
41 20     20   5632 my ($opt, @opt_specs) = @_;
42 20         88 my $default_opt = {
43             daemon => 0,
44             verbose => 0,
45             debug => 0,
46             unique => 0,
47             debug => 0,
48             console => 0,
49             };
50            
51 20 100       50 if (defined $opt){
52 13 100       200 croak q($opt must be hashref) unless ref $opt eq q(HASH);
53             } else {
54 7         7 $opt = {};
55             }
56              
57 19 100       49 map { $opt->{$_} = $default_opt->{$_} unless defined $opt->{$_} } keys %$default_opt;
  95         276  
58              
59 19         50 my @default_opt_specs = qw(
60             verbose|v+
61             debug
62             daemon
63             console
64             );
65 19         139 @opt_specs = uniq (@opt_specs, @default_opt_specs);
66 19 100       78 GetOptions($opt, @opt_specs) or croak qq(Cannot get options);
67 18         3747 return $opt;
68             }
69              
70             sub _set_basename {
71 10     10   1095 my @caller = @_;
72 10         550 $ENV{BASENAME} = basename($caller[1]);
73 10         32 $ENV{BASENAME} =~ s/\.pl$//;
74 10         23 $ENV{BASENAME} =~ s/[\0\/]//;
75 10         17 return;
76             }
77              
78             sub _set_binmode {
79 0     0   0 binmode(STDOUT, q(utf8:));
80 0         0 binmode(STDERR, q(utf8:));
81 0         0 return;
82             }
83              
84             sub _set_id {
85 12     12   1452 my ($id) = @_;
86 12 100       31 if (defined $id){
87 2         9 $ENV{ID} = $id;
88             } else {
89 10         35 $ENV{ID} = $ENV{BASENAME};
90             }
91 12         13 return;
92             }
93              
94             sub _set_logger {
95 78     78   664621 my ($opt) = @_;
96 78 100       206 $opt = {} unless defined $opt;
97              
98 78 50       197 croak qq(Couldn't find 'log_dir' section in Zabbix::ServerScript::Config) unless defined $Zabbix::ServerScript::Config->{log_dir};
99 78 100 66     735 croak qq(Environment variables BASENAME and ID are not set) unless (defined $ENV{BASENAME} and $ENV{ID});
100 75 100       139 if (defined $opt->{log_filename}){
101 31 100       62 if ($opt->{log_filename} ne q()){
102 28         90 $ENV{LOG_FILENAME} = $opt->{log_filename};
103             } else {
104 3         18 $logger->logdie(q(Cannot log to empty filename));
105             }
106             } else {
107 44         184 $ENV{LOG_FILENAME} = qq($Zabbix::ServerScript::Config->{log_dir}/$ENV{BASENAME}.log);
108             }
109              
110 72 100       363 croak qq(Couldn't find 'log' section in Zabbix::ServerScript::Config) unless defined $Zabbix::ServerScript::Config->{log};
111 69         380 Log::Log4perl->init($Zabbix::ServerScript::Config->{log});
112              
113 66         280760 my $log_category;
114 66 100       158 if (defined $opt->{logger}){
115 15 100       30 if ($opt->{logger} eq q()){
116 12         18 $log_category = q(Zabbix.ServerScript.nolog);
117             } else {
118 3         6 $log_category = $opt->{logger};
119             }
120             } else {
121 51 100 100     207 if (defined $opt->{console} && $opt->{console} == 1){
122 16         20 $log_category = q(Zabbix.ServerScript.console);
123             } else {
124 35         51 $log_category = q(Zabbix.ServerScript);
125             }
126             }
127 66         161 $logger = Log::Log4perl::get_logger($log_category);
128 66         1784 $ENV{LOG_CATEGORY} = $log_category;
129            
130 66 100 66     182 if (defined $opt->{verbose} && $opt->{verbose}){
131 7         25 $logger->more_logging($opt->{verbose});
132             }
133 66 100 100     4878 if (defined $opt->{debug} && $opt->{debug} == 1){
134 5         14 $logger->level($DEBUG);
135             }
136              
137             $SIG{__DIE__} = sub {
138 59     59   15234 my ($message) = @_;
139 59 100 100     437 if($^S and not (defined $ENV{ZBX_TESTING} and $ENV{ZBX_TESTING} == 1)) {
      100        
140             # We're in an eval {} and don't want log
141             # this message but catch it later
142 36         302 return;
143             }
144 23         46 $Log::Log4perl::caller_depth++;
145 23         90 $logger->fatal($message);
146 66         2976 };
147              
148             $SIG{__WARN__} = sub {
149 4     4   1486 my ($message) = @_;
150 4         21 local $Log::Log4perl::caller_depth;
151 4         12 $Log::Log4perl::caller_depth++;
152 4         63 $logger->warn($message);
153 66         265 };
154 66         162 return;
155             }
156              
157             sub _set_config {
158 14     14   5549 my ($config_filename) = @_;
159              
160 14 100 66     94 $logger->logcroak(qq(Environment variables BASENAME and ID are not set)) unless (defined $ENV{BASENAME} and $ENV{ID});
161              
162 13 100       33 if (not defined $config_filename){
163 11         41 $config_filename = qq($Zabbix::ServerScript::Config->{config_dir}/$ENV{BASENAME}.yaml);
164             }
165 13 50       35 if ($config_filename ne q()){
166 13 100       281 if (-f $config_filename){
167 2         9 $logger->debug(qq(Loading local config from file $config_filename));
168 2 50       13 $config = YAML::LoadFile($config_filename) or $logger->logdie(qq(Cannot load config from $config_filename));
169             } else {
170 11 50       79 $logger->debug(qq(Local config $config_filename was not found.)) unless $config_filename eq q();
171             }
172             }
173 12         7797 $config->{global} = $Zabbix::ServerScript::Config;
174 12         32 return;
175             }
176              
177             sub _set_api {
178 17     17   4875 my ($api) = @_;
179 17         23 my $api_config;
180 17 100       40 if (defined $api){
181 8         37 require Zabbix::ServerScript::API;
182 8         18 $zx_api = Zabbix::ServerScript::API::init($api);
183             }
184             }
185              
186             sub _get_pid {
187 8     8   5251 my ($id) = @_;
188 8         43 my $name = $ENV{BASENAME};
189 8 100       22 $name .= qq(_$id) if defined $id;
190 8         43 $name =~ s/[\0\/]/_/g;
191             my $pid = {
192             name => $name,
193             dir => $Zabbix::ServerScript::Config->{pid_dir},
194 8         87 };
195 8         82 $logger->debug(qq(Using PID file $pid->{dir}/$pid->{name}.pid));
196 8         88 return $pid;
197             }
198              
199             sub _set_unique {
200 11     11   2362 my ($unique, $id) = @_;
201 11 100 66     231 if (defined $unique && $unique){
202 2         61 my $pid = _get_pid($id);
203 2 100       107 if (Proc::PID::File->running($pid)){
204 1         961 croak(qq($pid->{name} is already running));
205             }
206             }
207             }
208              
209             sub _set_daemon {
210 11     11   3876 my ($daemon) = @_;
211 11 100       317 return Proc::Daemon::Init() if $daemon;
212 9         13 return;
213             }
214              
215             sub retrieve_cache {
216 4     4 1 2374 my ($cache_filename) = @_;
217 4 100       9 if (not defined $cache_filename){
218 1         4 $logger->debug(q(Cache filename is not specified, using default filename));
219 1         7 $cache_filename = qq($Zabbix::ServerScript::Config->{cache_dir}/$ENV{BASENAME}.cache)
220             }
221 4         6 my $cache;
222 4 100       37 if (-f $cache_filename){
223 3         11 $logger->debug(qq(Loading cache from "$cache_filename"));
224             eval {
225 3         7 $cache = retrieve $cache_filename;
226 2         88 1;
227 3 100       16 } or do {
228 1         7 $logger->error(qq(Cannot retrieve cache from "$cache_filename": $@));
229             };
230             } else {
231 1         6 $logger->info(qq(Cache file "$cache_filename" was not found));
232             }
233 4         284 return $cache;
234             }
235              
236             sub store_cache {
237 3     3 1 3000 my ($cache, $cache_filename) = @_;
238 3 100       8 if (not defined $cache_filename){
239 1         4 $logger->debug(q(Cache filename is not specified, using default filename));
240 1         8 $cache_filename = qq($Zabbix::ServerScript::Config->{cache_dir}/$ENV{BASENAME}.cache)
241             }
242 3         11 $logger->debug(qq(Storing cache to $cache_filename));
243             eval {
244 3         8 store $cache, $cache_filename;
245 2         287 1;
246 3 100       14 } or do {
247 1         4 $logger->error(qq(Cannot store cache to "$cache_filename"));
248 1         184 return;
249             };
250 2         7 return 1;
251             }
252              
253             sub init {
254 9     9 1 9671 my ($opt, @opt_specs) = @_;
255              
256 9         44 _get_options($opt, @opt_specs);
257 9         37 _set_basename(caller);
258 9         34 _set_id($opt->{id});
259 9         39 _set_daemon($opt->{daemon});
260 9         18 _set_logger($opt);
261 9         34 _set_unique($opt->{unique}, $opt->{id});
262 9         36 _set_config($opt->{config});
263 9         34 _set_api($opt->{api});
264 9         66 $logger->debug(q(Initialized Zabbix::ServerScript));
265             }
266              
267             sub return_value {
268 3     3 1 3413 my ($value) = @_;
269 3 100       21 if (defined $value){
270 1         64 $logger->debug(qq(Return value: $value));
271 1         32 chomp $value;
272 1         60 print qq($value\n);
273 1         180 exit;
274             } else {
275 2         10 $logger->logcroak(q(Return value is not defined));
276             }
277             }
278              
279             sub connect_to_db {
280 3     3 1 15590 my ($dbname, $user, $password, $mode) = @_;
281 3 100       15 $logger->logcroak(q(dbname is not defined)) unless defined $dbname;
282 2         2 my $dbh;
283 2         10 $logger->debug(qq(Trying to connect to $dbname via ODBC));
284 2 100       16 $dbh = DBI->connect(
285             qq(dbi:ODBC:DSN=$dbname),
286             $user,
287             $password,
288             ) or $logger->logcroak(qq(Failed to connect to $dbname: $DBI::errstr));
289 1         7 $logger->debug(qq(Connected to $dbname));
290 1         6 return $dbh;
291             }
292              
293             sub _prepare_sender_data {
294 8     8   10 my ($request_data) = @_;
295 8 100       20 if (ref($request_data) eq q(HASH)){
    100          
296 6         9 $request_data = [ $request_data ];
297             } elsif (ref($request_data) ne q(ARRAY)){
298 1         6 croak(qq(Request is neither arrayref nor hashref: ) . Dumper($request_data));
299             }
300             $request_data = {
301 7         15 request => q(sender data),
302             data => $request_data,
303             };
304             # encode_json throws an exception itself, if it cannot encode json.
305             # This 'croak' stands here just in case encode_json implementation will be changed.
306 7 50       64 my $request_json = encode_json($request_data) or croak(qq(Cannot encode to JSON: ) . Dumper($request_data));
307             }
308              
309             sub _proceed_sender_response {
310 2     2   3 my ($response_json) = @_;
311 2         10 $response_json =~ s/^.+(?={)//;
312 2 50       21 my $response_data = decode_json($response_json) or croak(qq(Cannon decode JSON));
313 1         2 return $response_data;
314             }
315              
316             sub send {
317 12     12 1 11042 my ($request_data, $sender_host, $sender_port) = @_;
318 12 100       29 $sender_host = q(localhost) if not defined $sender_host;
319 12 100       19 $sender_port = q(10051) if not defined $sender_port;
320 12         36 $logger->debug(qq(Opening sender socket to $sender_host:$sender_port));
321 12         2313 require IO::Socket::INET;
322 12 100       33 my $socket = IO::Socket::INET->new(
323             PeerAddr => $sender_host,
324             PeerPort => $sender_port,
325             Proto => q(tcp),
326             Timeout => 10,
327             ) or croak(qq(Cannot open socket for zabbix sender to "$sender_host:$sender_port": $?));
328              
329 8         58 my $request_json = _prepare_sender_data($request_data);
330 6         8 my $request_length = length($request_json);
331 6         6 my $response_json;
332              
333 6         19 $logger->debug(qq(Writing $request_length of data to sender socket: $request_json));
334 6 100       1049 $socket->write($request_json, $request_length) or croak(qq(Cannot write to socket: $!));
335 4 100       16 $socket->read($response_json, 2048) or croak(qq(Cannot read from socket: $!));
336 3 100       12 $socket->close or croak(qq(Cannot close socket: $!));
337 2         9 $logger->debug(qq(Server answered to sender: $response_json));
338 2         330 my $response_data = _proceed_sender_response($response_json);
339 1         4 return $response_data;
340             }
341              
342             sub create_config {
343 4     4 1 1004051 require Zabbix::ServerScript::DefaultConfig;
344              
345 4         8 my ($opt) = @_;
346 4 50       34 $opt = {
347             console => 1,
348             verbose => 1,
349             (defined $opt ? %$opt : ()),
350             };
351 4         27 print Dumper($opt);
352 4         823 init($opt);
353              
354 4         54 my $term = Term::ReadLine->new('Zabbix::ServerScript');
355 4         8822 (my $module_dir = dirname($INC{q(Zabbix/ServerScript/DefaultConfig.pm)})) =~ s|//|/|g;
356 4         41 $module_dir = $term->get_reply(
357             prompt => q(Directory to store Config.pm),
358             default => $module_dir,
359             );
360 4 100 66     132 die(qq(Wrong directory: $module_dir)) unless (-d $module_dir and -w $module_dir);
361 3         11 $logger->debug(qq(Will store Config.pm in $module_dir));
362              
363 3         19 my $module_filename = qq($module_dir/Config.pm);
364 3 100       40 if (-f $module_filename){
365 2 100       15 $term->ask_yn(
366             prompt => qq(\n$module_filename exists.\nOverwrite?),
367             default => q(n),
368             ) or exit 0;
369 1         10 $logger->info(q(Overwrite has been requested));
370             }
371              
372 2         340 for my $section (qw(config_dir pid_dir log_dir)){
373             $Zabbix::ServerScript::Config->{$section} = $term->get_reply(
374             prompt => $section,
375 6         20 default => $Zabbix::ServerScript::Config->{$section},
376             );
377             }
378              
379 2 50       136 open my $fh, q(>), $module_filename or die(qq(Cannot open file $module_filename: $!));
380 2         18 print $fh Data::Dumper->Dump([$Zabbix::ServerScript::Config], [q($Zabbix::ServerScript::Config)]);
381 2         495 close $fh;
382              
383 2 50       476 require $module_filename or die(qq(Cannot load module: $!));
384 2         14 $logger->info(qq($module_filename has been created successfully));
385 2         490 exit 0;
386             }
387              
388             1;
389              
390             __END__