File Coverage

blib/lib/Zabbix/ServerScript.pm
Criterion Covered Total %
statement 243 249 97.5
branch 96 108 88.8
condition 25 33 75.7
subroutine 39 40 97.5
pod 7 7 100.0
total 410 437 93.8


line stmt bran cond sub pod time code
1             package Zabbix::ServerScript;
2              
3 19     19   2064704 use strict;
  19         257  
  19         607  
4 19     19   118 use warnings;
  19         44  
  19         575  
5 19     19   108 use Exporter;
  19         37  
  19         683  
6 19     19   441 use Data::Dumper;
  19         5242  
  19         834  
7 19     19   5581 use YAML;
  19         117301  
  19         1022  
8 19     19   7952 use JSON;
  19         153501  
  19         140  
9 19     19   14904 use Log::Log4perl;
  19         736772  
  19         102  
10 19     19   1140 use Log::Log4perl::Level;
  19         48  
  19         94  
11 19     19   10845 use Proc::PID::File;
  19         34851  
  19         1094  
12 19     19   6669 use Proc::Daemon;
  19         146732  
  19         719  
13 19     19   298 use File::Basename;
  19         61  
  19         1464  
14 19     19   130 use Exporter;
  19         45  
  19         645  
15 19     19   135 use Carp;
  19         49  
  19         973  
16 19     19   5216 use Storable;
  19         29912  
  19         1016  
17 19     19   6776 use Term::ReadLine;
  19         39590  
  19         726  
18 19     19   6903 use Term::UI;
  19         400493  
  19         750  
19 19     19   10749 use Getopt::Long qw(:config bundling);
  19         198644  
  19         131  
20 19     19   13526 use List::MoreUtils qw(uniq);
  19         231778  
  19         156  
21              
22             BEGIN {
23             eval {
24 19         2226 require Zabbix::ServerScript::Config;
25 0         0 1;
26 19 50 33 19   21433 } or eval {
27 19         8716 require Zabbix::ServerScript::DefaultConfig;
28 19         51985 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.14);
35              
36             our $config = {};
37             our $logger;
38             our $zx_api;
39              
40             sub _get_options {
41 20     20   8732 my ($opt, @opt_specs) = @_;
42 20         140 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       79 if (defined $opt){
52 13 100       217 croak q($opt must be hashref) unless ref $opt eq q(HASH);
53             } else {
54 7         21 $opt = {};
55             }
56              
57 19 100       90 map { $opt->{$_} = $default_opt->{$_} unless defined $opt->{$_} } keys %$default_opt;
  95         400  
58              
59 19         91 my @default_opt_specs = qw(
60             verbose|v+
61             debug
62             daemon
63             console
64             );
65 19         160 @opt_specs = uniq (@opt_specs, @default_opt_specs);
66 19 100       120 GetOptions($opt, @opt_specs) or croak qq(Cannot get options);
67 18         6066 return $opt;
68             }
69              
70             sub _set_basename {
71 10     10   2721 my @caller = @_;
72 10         687 $ENV{BASENAME} = basename($caller[1]);
73 10         66 $ENV{BASENAME} =~ s/\.pl$//;
74 10         55 $ENV{BASENAME} =~ s/[\0\/]//;
75 10         35 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   4283 my ($id) = @_;
86 12 100       52 if (defined $id){
87 2         18 $ENV{ID} = $id;
88             } else {
89 10         67 $ENV{ID} = $ENV{BASENAME};
90             }
91 12         34 return;
92             }
93              
94             sub _set_logger {
95 78     78   939442 my ($opt) = @_;
96 78 100       287 $opt = {} unless defined $opt;
97              
98 78 50       297 croak qq(Couldn't find 'log_dir' section in Zabbix::ServerScript::Config) unless defined $Zabbix::ServerScript::Config->{log_dir};
99 78 100 66     914 croak qq(Environment variables BASENAME and ID are not set) unless (defined $ENV{BASENAME} and $ENV{ID});
100 75 100       226 if (defined $opt->{log_filename}){
101 31 100       95 if ($opt->{log_filename} ne q()){
102 28         163 $ENV{LOG_FILENAME} = $opt->{log_filename};
103             } else {
104 3         12 $logger->logdie(q(Cannot log to empty filename));
105             }
106             } else {
107 44         248 $ENV{LOG_FILENAME} = qq($Zabbix::ServerScript::Config->{log_dir}/$ENV{BASENAME}.log);
108             }
109              
110 72 100       482 croak qq(Couldn't find 'log' section in Zabbix::ServerScript::Config) unless defined $Zabbix::ServerScript::Config->{log};
111 69         546 Log::Log4perl->init($Zabbix::ServerScript::Config->{log});
112              
113 66         479868 my $log_category;
114 66 100       269 if (defined $opt->{logger}){
115 15 100       48 if ($opt->{logger} eq q()){
116 12         24 $log_category = q(Zabbix.ServerScript.nolog);
117             } else {
118 3         9 $log_category = $opt->{logger};
119             }
120             } else {
121 51 100 100     310 if (defined $opt->{console} && $opt->{console} == 1){
122 16         37 $log_category = q(Zabbix.ServerScript.console);
123             } else {
124 35         106 $log_category = q(Zabbix.ServerScript);
125             }
126             }
127 66         272 $logger = Log::Log4perl::get_logger($log_category);
128 66         3189 $ENV{LOG_CATEGORY} = $log_category;
129            
130 66 100 100     305 if (defined $opt->{verbose} && $opt->{verbose}){
131 7         46 $logger->more_logging($opt->{verbose});
132             }
133 66 100 100     10812 if (defined $opt->{debug} && $opt->{debug} == 1){
134 5         23 $logger->level($DEBUG);
135             }
136              
137             $SIG{__DIE__} = sub {
138 57     57   22312 my ($message) = @_;
139 57 100 100     604 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         497 return;
143             }
144 21         74 $Log::Log4perl::caller_depth++;
145 21         126 $logger->fatal($message);
146 66         6052 };
147              
148             $SIG{__WARN__} = sub {
149 6     6   5945 my ($message) = @_;
150 6         80 local $Log::Log4perl::caller_depth;
151 6         52 $Log::Log4perl::caller_depth++;
152 6         128 $logger->warn($message);
153 66         452 };
154 66         302 return;
155             }
156              
157             sub _set_config {
158 14     14   11339 my ($config_filename) = @_;
159              
160 14 100 66     139 $logger->logcroak(qq(Environment variables BASENAME and ID are not set)) unless (defined $ENV{BASENAME} and $ENV{ID});
161              
162 13 100       62 if (not defined $config_filename){
163 11         81 $config_filename = qq($Zabbix::ServerScript::Config->{config_dir}/$ENV{BASENAME}.yaml);
164             }
165 13 50       54 if ($config_filename ne q()){
166 13 100       377 if (-f $config_filename){
167 2         20 $logger->debug(qq(Loading local config from file $config_filename));
168 2 50       31 $config = YAML::LoadFile($config_filename) or $logger->logdie(qq(Cannot load config from $config_filename));
169             } else {
170 11 50       156 $logger->debug(qq(Local config $config_filename was not found.)) unless $config_filename eq q();
171             }
172             }
173 12         11507 $config->{global} = $Zabbix::ServerScript::Config;
174 12         41 return;
175             }
176              
177             sub _set_api {
178 17     17   11118 my ($api) = @_;
179 17         38 my $api_config;
180 17 100       70 if (defined $api){
181 8         67 require Zabbix::ServerScript::API;
182 8         38 $zx_api = Zabbix::ServerScript::API::init($api);
183             }
184             }
185              
186             sub _get_pid {
187 8     8   16385 my ($id) = @_;
188 8         63 my $name = $ENV{BASENAME};
189 8 100       50 $name .= qq(_$id) if defined $id;
190 8         56 $name =~ s/[\0\/]/_/g;
191             my $pid = {
192             name => $name,
193             dir => $Zabbix::ServerScript::Config->{pid_dir},
194 8         113 };
195 8         178 $logger->debug(qq(Using PID file $pid->{dir}/$pid->{name}.pid));
196 8         202 return $pid;
197             }
198              
199             sub _set_unique {
200 11     11   3723 my ($unique, $id) = @_;
201 11 100 66     345 if (defined $unique && $unique){
202 2         58 my $pid = _get_pid($id);
203 2 50       174 if (Proc::PID::File->running($pid)){
204 0         0 croak(qq($pid->{name} is already running));
205             }
206             }
207             }
208              
209             sub _set_daemon {
210 11     11   5338 my ($daemon) = @_;
211 11 100       220 return Proc::Daemon::Init() if $daemon;
212 9         22 return;
213             }
214              
215             sub retrieve_cache {
216 4     4 1 8715 my ($cache_filename) = @_;
217 4 100       22 if (not defined $cache_filename){
218 1         6 $logger->debug(q(Cache filename is not specified, using default filename));
219 1         11 $cache_filename = qq($Zabbix::ServerScript::Config->{cache_dir}/$ENV{BASENAME}.cache)
220             }
221 4         14 my $cache;
222 4 100       53 if (-f $cache_filename){
223 3         32 $logger->debug(qq(Loading cache from "$cache_filename"));
224             eval {
225 3         20 $cache = retrieve $cache_filename;
226 2         144 1;
227 3 100       40 } or do {
228 1         15 $logger->error(qq(Cannot retrieve cache from "$cache_filename": $@));
229             };
230             } else {
231 1         13 $logger->info(qq(Cache file "$cache_filename" was not found));
232             }
233 4         1158 return $cache;
234             }
235              
236             sub store_cache {
237 3     3 1 11499 my ($cache, $cache_filename) = @_;
238 3 100       19 if (not defined $cache_filename){
239 1         10 $logger->debug(q(Cache filename is not specified, using default filename));
240 1         16 $cache_filename = qq($Zabbix::ServerScript::Config->{cache_dir}/$ENV{BASENAME}.cache)
241             }
242 3         37 $logger->debug(qq(Storing cache to $cache_filename));
243             eval {
244 3         24 store $cache, $cache_filename;
245 2         613 1;
246 3 100       39 } or do {
247 1         20 $logger->error(qq(Cannot store cache to "$cache_filename"));
248 1         654 return;
249             };
250 2         15 return 1;
251             }
252              
253             sub init {
254 9     9 1 10649 my ($opt, @opt_specs) = @_;
255              
256 9         46 $opt = _get_options($opt, @opt_specs);
257 9         67 _set_basename(caller);
258 9         66 _set_id($opt->{id});
259 9         48 _set_daemon($opt->{daemon});
260 9         38 _set_logger($opt);
261 9         62 _set_unique($opt->{unique}, $opt->{id});
262 9         58 _set_config($opt->{config});
263 9         57 _set_api($opt->{api});
264 9         43 $logger->debug(q(Initialized Zabbix::ServerScript));
265             }
266              
267             sub return_value {
268 3     3 1 6928 my ($value) = @_;
269 3 100       36 if (defined $value){
270 1         85 $logger->debug(qq(Return value: $value));
271 1         43 chomp $value;
272 1         71 print qq($value\n);
273 1         202 exit;
274             } else {
275 2         14 $logger->logcroak(q(Return value is not defined));
276             }
277             }
278              
279             sub connect_to_db {
280 3     3 1 15833 my ($dbname, $user, $password, $mode) = @_;
281 3 100       18 $logger->logcroak(q(dbname is not defined)) unless defined $dbname;
282 2         3 my $dbh;
283 2         10 $logger->debug(qq(Trying to connect to $dbname via ODBC));
284 2 100       19 $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         8 $logger->debug(qq(Connected to $dbname));
290 1         8 return $dbh;
291             }
292              
293             sub _prepare_sender_data {
294 8     8   19 my ($request_data) = @_;
295 8 100       31 if (ref($request_data) eq q(HASH)){
    100          
296 6         13 $request_data = [ $request_data ];
297             } elsif (ref($request_data) ne q(ARRAY)){
298 1         8 croak(qq(Request is neither arrayref nor hashref: ) . Dumper($request_data));
299             }
300             $request_data = {
301 7         25 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       86 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   6 my ($response_json) = @_;
311 2         12 $response_json =~ s/^.+(?={)//;
312 2 50       21 my $response_data = decode_json($response_json) or croak(qq(Cannon decode JSON));
313 1         3 return $response_data;
314             }
315              
316             sub send {
317 12     12 1 18824 my ($request_data, $trapper_host, $trapper_port) = @_;
318 12 100       46 $trapper_host = $Zabbix::ServerScript::Config->{trapper}->{host} if not defined $trapper_host;
319 12 100       37 $trapper_port = $Zabbix::ServerScript::Config->{trapper}->{port} if not defined $trapper_port;
320 12 50 33     62 if (not (defined $trapper_host and defined $trapper_port)){
321 0         0 croak q(Missing trapper configuration. Either set default values in global config file or use it like )
322             . (caller(0))[3] . q(($request_data, $trapper_host, $trapper_port));
323             }
324              
325 12         66 $logger->debug(qq(Opening sender socket to $trapper_host:$trapper_port));
326 12         4278 require IO::Socket::INET;
327 12 100       54 my $socket = IO::Socket::INET->new(
328             PeerAddr => $trapper_host,
329             PeerPort => $trapper_port,
330             Proto => q(tcp),
331             Timeout => 10,
332             ) or croak(qq(Cannot open socket for zabbix sender to "$trapper_host:$trapper_port": $?));
333              
334 8         123 my $request_json = _prepare_sender_data($request_data);
335 6         15 my $request_length = length($request_json);
336 6         9 my $response_json;
337              
338 6         38 $logger->debug(qq(Writing $request_length of data to sender socket: $request_json));
339 6 100       1868 $socket->write($request_json, $request_length) or croak(qq(Cannot write to socket: $!));
340 4 100       26 $socket->read($response_json, 2048) or croak(qq(Cannot read from socket: $!));
341 3 100       18 $socket->close or croak(qq(Cannot close socket: $!));
342 2         13 $logger->debug(qq(Server answered to sender: $response_json));
343 2         504 my $response_data = _proceed_sender_response($response_json);
344 1         28 return $response_data;
345             }
346              
347             sub create_config {
348 4     4 1 1008446 require Zabbix::ServerScript::DefaultConfig;
349              
350 4         20 my ($opt) = @_;
351 4 50       33 $opt = {
352             console => 1,
353             verbose => 1,
354             (defined $opt ? %$opt : ()),
355             };
356 4         41 print Dumper($opt);
357 4         643 init($opt);
358              
359 4         81 my $term = Term::ReadLine->new('Zabbix::ServerScript');
360 4         16134 (my $module_dir = dirname($INC{q(Zabbix/ServerScript/DefaultConfig.pm)})) =~ s|//|/|g;
361 4         112 $module_dir = $term->get_reply(
362             prompt => q(Directory to store Config.pm),
363             default => $module_dir,
364             );
365 4 100 66     191 die(qq(Wrong directory: $module_dir)) unless (-d $module_dir and -w $module_dir);
366 3         46 $logger->debug(qq(Will store Config.pm in $module_dir));
367              
368 3         32 my $module_filename = qq($module_dir/Config.pm);
369 3 100       35 if (-f $module_filename){
370 2 100       21 $term->ask_yn(
371             prompt => qq(\n$module_filename exists.\nOverwrite?),
372             default => q(n),
373             ) or exit 0;
374 1         12 $logger->info(q(Overwrite has been requested));
375             }
376              
377 2         612 for my $section (qw(config_dir pid_dir log_dir)){
378             $Zabbix::ServerScript::Config->{$section} = $term->get_reply(
379             prompt => $section,
380 6         33 default => $Zabbix::ServerScript::Config->{$section},
381             );
382             }
383              
384 2 50       117 open my $fh, q(>), $module_filename or die(qq(Cannot open file $module_filename: $!));
385 2         34 print $fh Data::Dumper->Dump([$Zabbix::ServerScript::Config], [q($Zabbix::ServerScript::Config)]);
386 2         829 close $fh;
387              
388 2 50       565 require $module_filename or die(qq(Cannot load module: $!));
389 2         24 $logger->info(qq($module_filename has been created successfully));
390 2         1127 exit 0;
391             }
392              
393             1;
394              
395             __END__