File Coverage

blib/lib/Net/SNMP/Mixin.pm
Criterion Covered Total %
statement 77 87 88.5
branch 21 30 70.0
condition 3 4 75.0
subroutine 18 18 100.0
pod 3 3 100.0
total 122 142 85.9


line stmt bran cond sub pod time code
1             package Net::SNMP::Mixin;
2              
3 6     6   517462 use 5.006;
  6         26  
  6         249  
4 6     6   38 use strict;
  6         13  
  6         235  
5 6     6   38 use warnings;
  6         13  
  6         432  
6              
7             =head1 NAME
8              
9             Net::SNMP::Mixin - mixin framework for Net::SNMP
10              
11             =head1 VERSION
12              
13             Version 0.12
14              
15             =cut
16              
17             our $VERSION = '0.12';
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   36 use Carp ();
  6         10  
  6         235  
36 6     6   33 use Scalar::Util 'refaddr';
  6         11  
  6         579  
37 6     6   5913 use Package::Generator;
  6         10436  
  6         164  
38 6     6   5014 use Package::Reaper;
  6         10253  
  6         259  
39              
40             #
41             # this module export config
42             #
43             my @mixin_methods;
44              
45             BEGIN {
46 6     6   223 @mixin_methods = (qw/ mixer init_mixins errors /);
47             }
48              
49 6         87 use Sub::Exporter -setup => {
50             into => 'Net::SNMP',
51             exports => [@mixin_methods],
52             groups => { default => [@mixin_methods] }
53 6     6   7816 };
  6         66644  
54              
55             # needed for housekeeping of already mixed in modules
56             # in order to find double mixins
57             my @class_mixins;
58              
59             =head1 SYNOPSIS
60              
61             use Net::SNMP;
62             use Net::SNMP::Mixin;
63              
64             my $session = Net::SNMP->session( -hostname => 'example.com' );
65            
66             # method mixin and initialization
67             $session->mixer(qw/Net::SNMP::Mixin::Foo Net::SNMP::Mixin::Bar/);
68             $session->init_mixins();
69            
70             # event_loop in case of nonblocking sessions
71             snmp_dispatcher();
72              
73             # check for initialization errors
74             die $session->errors(1) if $session->errors;
75              
76             # use mixed-in methods to retrieve cooked SNMP info
77             my $a = $session->get_foo_a();
78             my $b = $session->get_bar_b();
79              
80             =head1 DESCRIPTION
81              
82             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.
83              
84             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.
85              
86             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.
87              
88             =cut
89              
90             =head1 DEFAULT EXPORTS
91              
92             These methods are exported by default into the B<< Net::SNMP >> namespace:
93              
94             =over 4
95              
96             =item *
97              
98             mixer
99              
100             =item *
101              
102             init_mixins
103              
104             =item *
105              
106             errors
107              
108             =back
109              
110             Please see the following description for details.
111              
112             =head2 B<< mixer(@module_names) >>
113              
114             # class method
115             Net::SNMP->mixer(qw/Net::SNMP::Mixin::Foo/);
116              
117             # instance method
118             $session->mixer(qw/Net::SNMP::Mixin::Yazz Net::SNMP::Mixin::Brazz/)
119              
120             Called as class method mixes the methods for all session instances. This is useful for agents supporting the same set of MIBs.
121              
122             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.
123              
124             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).
125              
126             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.
127              
128             Returns the invocant for chaining method calls, dies on error.
129              
130             =cut
131              
132             sub mixer {
133 9     9 1 50454 my ( $self, @mixins ) = @_;
134              
135 9         24 for my $mixin (@mixins) {
136              
137             # check: already mixed-in as class-mixin?
138 8 100       551 Carp::croak "$mixin already mixed into class,"
139             if grep m/^$mixin$/, @class_mixins;
140              
141             # instance- or class-mixin?
142 6 100       27 if ( ref $self ) {
143              
144             # register array for instance mixins
145 3   100     15 $self->{$prefix}{mixins} ||= [];
146              
147             # check: already mixed-in as instance-mixin?
148 3         81 Carp::croak "$mixin already mixed into instance $self,"
149 3 100       5 if grep m/^$mixin$/, @{ $self->{$prefix}{mixins} };
150              
151 2         8 _obj_mixer( $self, $mixin );
152              
153             # register instance mixins in the object itself
154 1         2 push @{ $self->{$prefix}{mixins} }, $mixin;
  1         16  
155             }
156             else {
157 3         13 _class_mixer( $self, $mixin );
158              
159             # register class mixins in a package variable
160 2         10 push @class_mixins, $mixin;
161             }
162             }
163              
164 4         34 return $self;
165             }
166              
167             #
168             # Mix the module into Net::SNMP with the help of Sub::Exporter.
169             #
170             sub _class_mixer {
171 3     3   5 my ( $class, $mixin ) = @_;
172              
173 3     3   330 eval "use $mixin {into => 'Net::SNMP'}";
  3     2   2976  
  3         10  
  3         31  
  2         1182  
  0            
  0            
174 3 100       758 Carp::croak $@ if $@;
175 2         9 return;
176             }
177              
178             #
179             # Create a new package as a subclass of Net::SNMP
180             # Rebless $session in the new package.
181             # Mix the module into the new package with the help of Sub::Exporter.
182             #
183             sub _obj_mixer {
184 2     2   5 my ( $session, $mixin ) = @_;
185 2         8 my ( $package, $pkg_reaper ) = _make_package($session);
186              
187             # created a new PACKAGE with an armed reaper,
188             # this is the first call to mixer for this $session
189 2 100       23 if ($pkg_reaper) {
190              
191             # rebless $session to new PACKAGE, this is still a
192             # subclass of Net::SNMP
193 1         4 bless $session, $package;
194              
195             # When this instance is garbage collected, the $pkg_reaper
196             # is DESTROYed and the PACKAGE is deleted from the symbol table.
197 1         7 $session->{$prefix}{reaper} = $pkg_reaper;
198             }
199              
200 2         239 eval "use $mixin {into => '$package'}";
201 2 100       340 Carp::croak $@ if $@;
202 1         4 return;
203             }
204              
205             #
206             # Make unique mixin subclass for this session with name.
207             # Net::SNMP:: und make it a subclass of Net::SNMP.
208             # Arm a package reaper, see perldoc Package::Reaper.
209             #
210             sub _make_package {
211 2     2   5 my $session = shift;
212 2         13 my $pkg_name = 'Net::SNMP::__mixin__' . '::' . refaddr $session;
213              
214             # already buildt this package for this session object,
215             # just return the package name
216 2 100       21 return $pkg_name if Package::Generator->package_exists($pkg_name);
217              
218             # build this package, make it a subclass of Net::SNMP and ...
219             my $package = Package::Generator->new_package(
220             {
221 1     1   18 make_unique => sub { return $pkg_name },
222 1         20 isa => ['Net::SNMP'],
223             }
224             );
225              
226             # ... arm a package reaper
227 1         86 my $pkg_reaper = Package::Reaper->new($package);
228              
229 1         11 return ( $package, $pkg_reaper );
230             }
231              
232             =head2 B<< init_mixins($reload) >>
233              
234             $session->init_mixins();
235             $session->init_mixins(1);
236              
237             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.
238              
239             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.
240              
241             If there is an error in a mixin, the rest of the initialization is skipped to preserve the current error message.
242              
243             This method should be called in void context. In order to check successfull initialization the Net::SNMP error method I<< $session->error() >> should be checked. Please use the following idiom:
244              
245             $session->init_mixins;
246             snmp_dispatcher;
247             die $session->errors(1) if $session->errors;
248              
249             =cut
250              
251             sub init_mixins {
252 1     1 1 39292 my ( $session, $reload ) = @_;
253              
254 1 50       8 Carp::croak "pure instance method called as class method,"
255             unless ref $session;
256              
257 1 50       12 my @instance_mixins = @{ $session->{$prefix}{mixins} }
  0         0  
258             if defined $session->{$prefix}{mixins};
259              
260             # loop over all class-mixins and instance-mixins
261 1         4 my @all_mixins = ( @class_mixins, @instance_mixins );
262              
263 1 50       4 unless ( scalar @all_mixins ) {
264 1         26 Carp::carp "please use first the mixer() method, nothing to init\n";
265 1         728 return;
266             }
267              
268             # for each mixin module ...
269 0         0 foreach my $mixin (@all_mixins) {
270              
271             # call the _init() method in module $mixin
272 0         0 my $mixin_init = $mixin . '::_init';
273 0         0 eval { $session->$mixin_init($reload) };
  0         0  
274              
275             # fatal error during mixin initialization, normally wrong
276             # calling convention with $reload
277 0 0       0 Carp::croak $@ if $@;
278              
279             }
280 0         0 return;
281             }
282              
283             =head2 B<< errors($clear) >>
284              
285             @errors = $session->errors();
286             @errors = $session->errors(1);
287              
288             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.
289              
290             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".
291              
292             The error buffer is cleared if the argument $clear is true.
293              
294             =cut
295              
296             sub errors {
297 2     2 1 6 my ( $session, $clear ) = @_;
298              
299             # prepare the error buffer if not already done
300 2   50     9 $session->{'Net::SNMP::Mixin'}{errors} ||= [];
301 2         4 my @errors = @{ $session->{'Net::SNMP::Mixin'}{errors} };
  2         6  
302              
303             # show also the last Net::SNMP::error if available
304             # and if not already included in the mixin error buffer
305 2 50       11 if ( my $net_snmp_error = $session->error ) {
306 0 0       0 push @errors, $net_snmp_error
307             unless grep m/\Q$net_snmp_error\E$/, @errors;
308             }
309              
310 2 100       17 if ($clear) {
311              
312             # clear the mixin error accumulator
313 1         5 $session->{'Net::SNMP::Mixin'}{errors} = [];
314              
315             # clear the Net::SNMP error; with a private method, sigh.
316 1         5 $session->_error_clear;
317             }
318              
319 2 50       19 return wantarray ? @errors : join( "\n", @errors );
320             }
321              
322             =head1 GUIDELINES FOR MIXIN AUTHORS
323              
324             See the L<< Net::SNMP::Mixin::System >> module as a blueprint for a simple mixin module.
325              
326             As a mixin-module author you must respect the following design guidelines:
327              
328             =over 4
329              
330             =item *
331              
332             Write more separate mixin-modules instead of 'one module fits all'.
333              
334             =item *
335              
336             Don't build mutual dependencies with other mixin-modules.
337              
338             =item *
339              
340             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.
341              
342             =item *
343              
344             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.
345              
346             =item *
347              
348             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.
349              
350             =item *
351              
352             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.
353              
354             =item *
355              
356             Use Sub::Exporter and export the mixin methods by default.
357              
358             =back
359              
360             =head1 DEVELOPER INFORMATION
361              
362             If mixer() is called as a class method, the mixin-methods are just imported into the Net::SNMP package.
363              
364             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.
365              
366             =head1 SEE ALSO
367              
368             L<< Sub::Exporter >>, and the Net::SNMP::Mixin::... documentations for more details about the provided mixin methods.
369              
370             =head1 REQUIREMENTS
371              
372             L, L, L, L
373              
374             =head1 BUGS, PATCHES & FIXES
375              
376             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.
377              
378             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 .
379              
380             RT: http://rt.cpan.org/Public/Dist/Display.html?Name=Net-SNMP-Mixin
381              
382             =head1 AUTHOR
383              
384             Karl Gaissmaier
385              
386             =head1 COPYRIGHT & LICENSE
387              
388             Copyright 2008 Karl Gaissmaier, all rights reserved.
389              
390             This program is free software; you can redistribute it and/or modify it
391             under the same terms as Perl itself.
392              
393             =cut
394              
395             unless ( caller() ) {
396             print __PACKAGE__ . " compiles and initializes successful.\n";
397             }
398              
399             1;
400              
401             # vim: sw=2