File Coverage

blib/lib/Net/SNMP/Mixin.pm
Criterion Covered Total %
statement 80 104 76.9
branch 22 42 52.3
condition 5 10 50.0
subroutine 19 20 95.0
pod 4 4 100.0
total 130 180 72.2


line stmt bran cond sub pod time code
1             package Net::SNMP::Mixin;
2              
3 6     6   207670 use 5.006;
  6         16  
4 6     6   23 use strict;
  6         6  
  6         96  
5 6     6   18 use warnings;
  6         9  
  6         270  
6              
7             =head1 NAME
8              
9             Net::SNMP::Mixin - mixin framework for Net::SNMP
10              
11             =head1 VERSION
12              
13             Version 0.14
14              
15             =cut
16              
17             our $VERSION = '0.14';
18              
19             =head1 ABSTRACT
20              
21             Thin framework to access cooked SNMP information from SNMP agents with various mixins to Net::SNMP.
22              
23             =cut
24              
25             #
26             # store this package name in a handy variable,
27             # used for unique prefix of mixin attributes
28             # storage in instance hash
29             #
30             my $prefix = __PACKAGE__;
31              
32             #
33             # this module import config
34             #
35 6     6   23 use Carp ();
  6         8  
  6         96  
36 6     6   18 use Scalar::Util 'refaddr';
  6         5  
  6         379  
37 6     6   2248 use Package::Generator;
  6         3318  
  6         130  
38 6     6   2048 use Package::Reaper;
  6         7527  
  6         144  
39 6     6   1668 use Net::SNMP::Mixin::Util qw/push_error get_init_slot/;
  6         9  
  6         36  
40              
41             #
42             # this module export config
43             #
44             my @mixin_methods;
45              
46             BEGIN {
47 6     6   1596 @mixin_methods = (
48             qw/ mixer init_mixins init_ok errors /
49             );
50             }
51              
52 6         40 use Sub::Exporter -setup => {
53             into => 'Net::SNMP',
54             exports => [@mixin_methods],
55             groups => { default => [@mixin_methods] }
56 6     6   27 };
  6         8  
57              
58             # needed for housekeeping of already mixed in modules
59             # in order to find double mixins
60             my @class_mixins;
61              
62             =head1 SYNOPSIS
63              
64             use Net::SNMP;
65             use Net::SNMP::Mixin;
66              
67             my $session = Net::SNMP->session( -hostname => 'example.com' );
68            
69             # method mixin and initialization
70             $session->mixer(qw/Net::SNMP::Mixin::Foo Net::SNMP::Mixin::Bar/);
71             $session->init_mixins();
72            
73             # event_loop in case of nonblocking sessions
74             snmp_dispatcher();
75              
76             # check for initialization errors
77             $session->init_ok();
78              
79             die scalar $session->errors if $session->errors;
80              
81             # use mixed-in methods to retrieve cooked SNMP info
82             my $a = $session->get_foo_a();
83             my $b = $session->get_bar_b();
84              
85             =head1 DESCRIPTION
86              
87             Net::SNMP implements already the methods to retrieve raw SNMP values from the agents. With the help of specialized mixins, the access to these raw SNMP values is simplified and necessary calculations on these values are already done for gaining high level information.
88              
89             This module provides helper functions in order to mixin methods into the inheritance tree of the Net::SNMP session instances or the Net::SNMP class itself.
90              
91             The standard Net::SNMP get_... methods are still supported and the mixins fetch itself the needed SNMP values during initialization with these standard get_... methods. Blocking and non blocking sessions are supported. The mixins don't change the Net::SNMP session instance, besides storing additional payload in the object space prefixed with the unique mixin module names as the hash key.
92              
93             =cut
94              
95             =head1 DEFAULT EXPORTS
96              
97             These methods are exported by default into the B<< Net::SNMP >> namespace:
98              
99             =over 4
100              
101             =item *
102              
103             mixer
104              
105             =item *
106              
107             init_mixins
108              
109             =item *
110              
111             init_ok
112              
113             =item *
114              
115             errors
116              
117             =back
118              
119             Please see the following description for details.
120              
121             =head2 B<< mixer(@module_names) >>
122              
123             # class method
124             Net::SNMP->mixer(qw/Net::SNMP::Mixin::Foo/);
125              
126             # instance method
127             $session->mixer(qw/Net::SNMP::Mixin::Yazz Net::SNMP::Mixin::Brazz/)
128              
129             Called as class method mixes the methods for all session instances. This is useful for agents supporting the same set of MIBs.
130              
131             Called as instance method mixes only for the calling session instance. This is useful for SNMP agents not supporting the same set of MIBs and therefore not the same set of mixin modules.
132              
133             Even the SNMP agents from a big network company don't support the most useful standard MIBs. They always use proprietary private enterprise MIBs (ring, ring, Cisco, do you hear the bells, grrrmmml).
134              
135             The name of the modules to mix-in is passed to this method as a list. You can mix class and instance mixins as you like, but importing the same mixin module twice is an error.
136              
137             Returns the invocant for chaining method calls, dies on error.
138              
139             =cut
140              
141             sub mixer {
142 9     9 1 44818 my ( $self, @mixins ) = @_;
143              
144 9         18 for my $mixin (@mixins) {
145              
146             # check: already mixed-in as class-mixin?
147 8 100       345 Carp::croak "$mixin already mixed into class,"
148             if grep m/^$mixin$/, @class_mixins;
149              
150             # instance- or class-mixin?
151 6 100       16 if ( ref $self ) {
152              
153             # register array for instance mixins
154 3   100     11 $self->{$prefix}{mixins} ||= [];
155              
156             # check: already mixed-in as instance-mixin?
157             Carp::croak "$mixin already mixed into instance $self,"
158 3 100       1 if grep m/^$mixin$/, @{ $self->{$prefix}{mixins} };
  3         158  
159              
160 2         4 _obj_mixer( $self, $mixin );
161              
162             # register instance mixins in the object itself
163 1         0 push @{ $self->{$prefix}{mixins} }, $mixin;
  1         8  
164             }
165             else {
166 3         4 _class_mixer( $self, $mixin );
167              
168             # register class mixins in a package variable
169 2         3 push @class_mixins, $mixin;
170             }
171             }
172              
173 4         16 return $self;
174             }
175              
176             #
177             # Mix the module into Net::SNMP with the help of Sub::Exporter.
178             #
179             sub _class_mixer {
180 3     3   5 my ( $class, $mixin ) = @_;
181              
182 3     3   200 eval "use $mixin {into => 'Net::SNMP'}";
  3     2   1217  
  3         5  
  3         13  
  2         396  
  0            
  0            
183 3 100       474 Carp::croak $@ if $@;
184 2         4 return;
185             }
186              
187             #
188             # Create a new package as a subclass of Net::SNMP
189             # Rebless $session in the new package.
190             # Mix the module into the new package with the help of Sub::Exporter.
191             #
192             sub _obj_mixer {
193 2     2   2 my ( $session, $mixin ) = @_;
194 2         3 my ( $package, $pkg_reaper ) = _make_package($session);
195              
196             # created a new PACKAGE with an armed reaper,
197             # this is the first call to mixer for this $session
198 2 100       12 if ($pkg_reaper) {
199              
200             # rebless $session to new PACKAGE, this is still a
201             # subclass of Net::SNMP
202 1         2 bless $session, $package;
203              
204             # When this instance is garbage collected, the $pkg_reaper
205             # is DESTROYed and the PACKAGE is deleted from the symbol table.
206 1         3 $session->{$prefix}{reaper} = $pkg_reaper;
207             }
208              
209 2         116 eval "use $mixin {into => '$package'}";
210 2 100       265 Carp::croak $@ if $@;
211 1         2 return;
212             }
213              
214             #
215             # Make unique mixin subclass for this session with name.
216             # Net::SNMP:: und make it a subclass of Net::SNMP.
217             # Arm a package reaper, see perldoc Package::Reaper.
218             #
219             sub _make_package {
220 2     2   15 my $session = shift;
221 2         6 my $pkg_name = 'Net::SNMP::__mixin__' . '::' . refaddr $session;
222              
223             # already buildt this package for this session object,
224             # just return the package name
225 2 100       11 return $pkg_name if Package::Generator->package_exists($pkg_name);
226              
227             # build this package, make it a subclass of Net::SNMP and ...
228             my $package = Package::Generator->new_package(
229             {
230 1     1   10 make_unique => sub { return $pkg_name },
231 1         17 isa => ['Net::SNMP'],
232             }
233             );
234              
235             # ... arm a package reaper
236 1         54 my $pkg_reaper = Package::Reaper->new($package);
237              
238 1         6 return ( $package, $pkg_reaper );
239             }
240              
241             =head2 B<< init_mixins($reload) >>
242              
243             This method should be called in void context.
244              
245             $session->init_mixins();
246             $session->init_mixins(1);
247              
248             This method redispatches to every I<< _init() >> method in the loaded mixin modules. The raw SNMP values for the mixins are loaded during this call - or via callbacks during the snmp_dispatcher event loop for nonblocking sessions - and stored in the object space. The mixed methods deliver afterwards cooked meal from these values.
249              
250             The MIB values are reloaded for the mixins if the argument $reload is true. It's an error calling this method twice without forcing $reload.
251              
252             If there is an error in a mixin, the rest of the initialization is skipped to preserve the current Net::SNMP error message.
253              
254             With the init_ok() method, after the snmp_dispatcher run, the successful initialization must be checked.
255              
256             $session->init_mixins;
257             snmp_dispatcher;
258             $session->init_ok();
259             die scalar $session->errors if $session->errors;
260              
261             =cut
262              
263             sub init_mixins {
264 0     0 1 0 my ( $session, $reload ) = @_;
265              
266 0 0       0 Carp::croak "pure instance method called as class method,"
267             unless ref $session;
268              
269 0         0 my $agent = $session->hostname;
270              
271 0         0 my @instance_mixins = @{ $session->{$prefix}{mixins} }
272 0 0       0 if defined $session->{$prefix}{mixins};
273              
274             # loop over all class-mixins and instance-mixins
275 0         0 my @all_mixins = ( @class_mixins, @instance_mixins );
276              
277 0 0       0 unless ( scalar @all_mixins ) {
278 0         0 Carp::carp "$agent: please use first the mixer() method, nothing to init\n";
279 0         0 return;
280             }
281              
282             # for each mixin module ...
283 0         0 foreach my $mixin (@all_mixins) {
284              
285             # call the _init() method in module $mixin
286 0         0 my $mixin_init = $mixin . '::_init';
287 0         0 eval { $session->$mixin_init($reload) };
  0         0  
288              
289             # fatal error during mixin initialization, normally wrong
290             # calling convention with $reload
291 0 0       0 Carp::croak $@ if $@;
292              
293             }
294 0         0 return;
295             }
296              
297             =head2 B<< init_ok($mixin) >>
298              
299             $session->init_ok();
300             $session->init_ok('Net::SNMP::Mixin::MyMixin');
301              
302             Test if all mixins or a single mixin is proper initialized.
303              
304             Returns undef on error. The error is pushed on the sessions error buffer.
305              
306             die scalar $session->errors unless $session->init_ok();
307              
308             =cut
309              
310             sub init_ok {
311 1     1 1 1 my ( $session, $mixin, ) = @_;
312              
313 1 50       4 die "missing attribute 'session'," unless defined $session;
314              
315 1 50 33     9 die "'session' isn't a Net::SNMP object,"
316             unless ref $session && $session->isa('Net::SNMP');
317              
318 1         2 my $agent = $session->hostname;
319              
320             #
321             # test for single mixin initialization
322             #
323              
324 1 50       6 if ( defined $mixin ) {
325 1 50 33     5 if ( exists get_init_slot($session)->{$mixin}
326             && get_init_slot($session)->{$mixin} == 0 )
327             {
328 0         0 return 1;
329             }
330             else {
331 1         6 push_error($session, "$agent: $mixin not initialized");
332 1         71 return;
333             }
334             }
335              
336             #
337             # test for for all mixins initialization
338             #
339              
340 0         0 my $init_error_flag = 0; # reset error flag
341 0         0 foreach my $mixin ( keys %{ get_init_slot($session) } ) {
  0         0  
342              
343             # check for init errors, pushes error msg on session error buffer
344 0 0       0 $init_error_flag++ if not $session->init_ok($mixin);
345             }
346              
347             # return undef if any mixin isn't proper initialized
348 0 0       0 $init_error_flag ? return : return 1;
349             }
350              
351             =head2 B<< errors($clear) >>
352              
353             @errors = $session->errors();
354             @errors = $session->errors(1);
355              
356             Net::SNMP::error() has only one slot for errors. During nonblocking calls it's possible that an error followed by a successful transaction is cleared before the user gets the chance to see the error. For the mixin modules we use an error buffer until they are explicit cleared.
357              
358             This method returns the list of all errors pushed by any mixin module. Called in scalar context returns a string of all @errors joined with "\n".
359              
360             The error buffer is cleared if the argument $clear is true.
361              
362             =cut
363              
364             sub errors {
365 2     2 1 3 my ( $session, $clear ) = @_;
366              
367             # prepare the error buffer if not already done
368 2   50     7 $session->{'Net::SNMP::Mixin'}{errors} ||= [];
369 2         1 my @errors = @{ $session->{'Net::SNMP::Mixin'}{errors} };
  2         4  
370              
371             # show also the last Net::SNMP::error if available
372             # and if not already included in the mixin error buffer
373 2 50       7 if ( my $net_snmp_error = $session->error ) {
374 0 0       0 unshift @errors, $net_snmp_error
375             unless grep m/\Q$net_snmp_error\E$/, @errors;
376             }
377              
378 2 100       11 if ($clear) {
379              
380             # clear the mixin error accumulator
381 1         1 $session->{'Net::SNMP::Mixin'}{errors} = [];
382              
383             # clear the Net::SNMP error; with a private method, sigh.
384 1         3 $session->_error_clear;
385             }
386              
387 2 50       11 return wantarray ? @errors : join( "\n", @errors );
388             }
389              
390             =head1 GUIDELINES FOR MIXIN AUTHORS
391              
392             See the L<< Net::SNMP::Mixin::System >> module as a blueprint for a simple mixin module.
393              
394             As a mixin-module author you must respect the following design guidelines:
395              
396             =over 4
397              
398             =item *
399              
400             Write more separate mixin-modules instead of 'one module fits all'.
401              
402             =item *
403              
404             Don't build mutual dependencies with other mixin-modules.
405              
406             =item *
407              
408             In no circumstance change the given attributes of the calling Net::SNMP session instance. In any case stay with the given behavior for blocking, translation, debug, retries, timeout, ... of the object. Remember it's a mixin and no sub- or superclass.
409              
410             =item *
411              
412             Don't assume the translation of the SNMP values by default. Due to the asynchronous nature of the SNMP calls, you can't rely on the output of $session->translate. If you need a special representation of a value, you have to check the values itself and perhaps translate or untranslate it when needed. See the source of Net::SNMP::Mixin::Dot1qVlanStatic for an example.
413              
414             =item *
415              
416             Implement the I<< _init() >> method and fetch SNMP values only during this call. If the session instance is nonblocking use a callback to work properly with the I<< snmp_dispatcher() >> event loop. In no circumstance load additonal SNMP values outside the I<< _init() >> method.
417              
418             =item *
419              
420             Don't die() on SNMP errors during I<< _init() >>, just return premature with no value. The caller is responsible to check the I<< $session->error() >> method.
421              
422             =item *
423              
424             Use Sub::Exporter and export the mixin methods by default.
425              
426             =back
427              
428             =head1 DEVELOPER INFORMATION
429              
430             If mixer() is called as a class method, the mixin-methods are just imported into the Net::SNMP package.
431              
432             If called as an instance method for the first time, the methods are imported into a newly generated, unique package for this session. The session instance is B<< reblessed >> into this new package. The new package B<< inherits >> from the Net::SNMP class. Successive calls for this session instance imports just the additional mixin-methods into the already generated package for this instance.
433              
434             =head1 SEE ALSO
435              
436             L<< Sub::Exporter >>, and the Net::SNMP::Mixin::... documentations for more details about the provided mixin methods.
437              
438             =head1 REQUIREMENTS
439              
440             L, L, L, L
441              
442             =head1 BUGS, PATCHES & FIXES
443              
444             There are no known bugs at the time of this release. However, if you spot a bug or are experiencing difficulties that are not explained within the POD documentation, please submit a bug to the RT system (see link below). However, it would help greatly if you are able to pinpoint problems or even supply a patch.
445              
446             Fixes are dependant upon their severity and my availablity. Should a fix not be forthcoming, please feel free to (politely) remind me by sending an email to gaissmai@cpan.org .
447              
448             RT: http://rt.cpan.org/Public/Dist/Display.html?Name=Net-SNMP-Mixin
449              
450             =head1 AUTHOR
451              
452             Karl Gaissmaier
453              
454             =head1 COPYRIGHT & LICENSE
455              
456             Copyright 2008-2015 Karl Gaissmaier, all rights reserved.
457              
458             This program is free software; you can redistribute it and/or modify it
459             under the same terms as Perl itself.
460              
461             =cut
462              
463             unless ( caller() ) {
464             print __PACKAGE__ . " compiles and initializes successful.\n";
465             }
466              
467             1;
468              
469             # vim: sw=2