File Coverage

blib/lib/POE/Component/NetSNMP/agent.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package POE::Component::NetSNMP::agent;
2              
3 1     1   20742 use 5.006;
  1         6  
  1         88  
4 1     1   9 use strict;
  1         2  
  1         43  
5 1     1   20 use warnings;
  1         2  
  1         62  
6              
7 1     1   882 use parent qw< POE::Session >;
  1         444  
  1         6  
8              
9             use Carp;
10             use List::MoreUtils qw< after >;
11             use NetSNMP::agent;
12             use POE;
13             use SNMP ();
14             use SNMP::ToolBox;
15              
16              
17             our $VERSION = "0.500";
18              
19              
20             use constant {
21             TYPE => 0,
22             VALUE => 1,
23              
24             HAVE_SORT_KEY_OID
25             => eval "use Sort::Key::OID 0.04 'oidsort'; 1" ? 1 : 0,
26             };
27              
28              
29             #
30             # spawn()
31             # -----
32             sub spawn {
33             my $class = shift;
34             croak "error: odd number of arguments" unless @_ % 2 == 0;
35              
36             my %defaults = (
37             Name => "perl",
38             AgentX => 0,
39             Ping => 10,
40             );
41              
42             my %args = ( %defaults, @_ );
43              
44             my @poe_opts;
45             push @poe_opts, options => { trace => 1, debug => 1, default => 1 }
46             if $args{Debug};
47              
48             # check arguments
49             carp "warning: errback '$args{Errback}' doesn't look like a POE event"
50             if $args{Errback} and $args{Errback} !~ /^\w+$/;
51              
52             # create the POE session
53             my $session = $class->create(
54             heap => {
55             args => \%args,
56             oid_tree => {},
57             ping_delay => $args{Ping},
58             },
59              
60             inline_states => {
61             _start => \&ev_start,
62             _stop => \&ev_stop,
63             init => \&ev_init,
64             register => \&ev_register,
65             agent_check => \&ev_agent_check,
66              
67             tree_handler => \&ev_tree_handler,
68             add_oid_entry => \&ev_add_oid_entry,
69             add_oid_tree => \&ev_add_oid_tree,
70             },
71              
72             @poe_opts,
73             );
74              
75             return $session
76             }
77              
78              
79             # ==============================================================================
80             # POE events
81             #
82              
83              
84             #
85             # ev_start()
86             # --------
87             sub ev_start {
88             $_[KERNEL]->yield("init");
89             $_[KERNEL]->alias_set( $_[HEAP]{args}{Alias} )
90             if $_[HEAP]{args}{Alias};
91             }
92              
93              
94             #
95             # ev_stop()
96             # -------
97             sub ev_stop {
98             $_[HEAP]{agent}->shutdown;
99             }
100              
101              
102             #
103             # ev_init()
104             # -------
105             sub ev_init {
106             my $args = $_[HEAP]{args};
107             my %opts;
108             $opts{Name} = $args->{Name};
109             $opts{AgentX} = $args->{AgentX};
110             $opts{Ports} = $args->{Ports} if defined $args->{Ports};
111              
112             # create the NetSNMP sub-agent
113             $_[HEAP]{agent} = NetSNMP::agent->new(%opts);
114              
115             # if auto-handle is requested, register our own OID tree handler
116             $_[KERNEL]->yield(register => $args->{AutoHandle}, "tree_handler")
117             if $args->{AutoHandle};
118             }
119              
120              
121             #
122             # ev_register()
123             # -----------
124             sub ev_register {
125             my ($kernel, $heap, $sender, $oid, $callback)
126             = @_[ KERNEL, HEAP, SENDER, ARG0, ARG1 ];
127             my $args = $heap->{args};
128              
129             my $poe_wrapper;
130              
131             if (ref $callback) {
132             # simpler & faster callback mechanism
133             my @poe_params = @_[ 0 .. ARG0-1 ];
134             $poe_wrapper = sub {
135             @_ = ( @poe_params, [], [@_] );
136             goto $callback
137             };
138             }
139             else {
140             # standard POE callback mechanism
141             $poe_wrapper = $sender->callback($callback);
142             }
143              
144             # create & register the NetSNMP sub-agent
145             my $r = $heap->{agent}->register(
146             $args->{Name}, $oid, $poe_wrapper);
147              
148             if (not $r) {
149             $kernel->post($sender, $args->{Errback}, "register")
150             if $args->{Errback};
151             return
152             }
153              
154             # manually call agent_check_and_process() once so it opens
155             # the sockets to AgentX master
156             $kernel->delay(agent_check => 0, "register");
157             }
158              
159              
160             #
161             # ev_agent_check()
162             # --------------
163             sub ev_agent_check {
164             my ($kernel, $heap, $case) = @_[ KERNEL, HEAP, ARG0 ];
165              
166             $case ||= "";
167              
168             # schedule next check
169             $kernel->delay(agent_check => $heap->{ping_delay}),
170              
171             # process the incoming data and invoke the callback
172             SNMP::_check_timeout();
173             $heap->{agent}->agent_check_and_process(0);
174              
175             if ($case eq "register") {
176             # find the sockets used to communicate with AgentX master..
177             my ($block, $to_sec, $to_usec, @fd_set)
178             = SNMP::_get_select_info();
179              
180             # ... and let POE kernel handle them
181             for my $fd (@fd_set) {
182             # create a file handle from the given file descriptor
183             open my $fh, "+<&=", $fd;
184              
185             # first unregister the given file handles from
186             # POE::Kernel, in case some were already registered,
187             # then register them, with this event as callback
188             $kernel->select_read($fh);
189             $kernel->select_read($fh, "agent_check");
190             }
191             }
192             }
193              
194              
195             #
196             # ev_tree_handler()
197             # ---------------
198             sub ev_tree_handler {
199             my ($kernel, $heap, $args) = @_[ KERNEL, HEAP, ARG1 ];
200             my ($handler, $reg_info, $request_info, $requests) = @$args;
201             my $oid_tree = $heap->{oid_tree};
202             my $oid_list = $heap->{oid_list};
203              
204             # the rest of the code works like a classic NetSNMP::agent callback
205             my $mode = $request_info->getMode;
206              
207             for (my $request = $requests; $request; $request = $request->next) {
208             my $oid = $request->getOID->as_oid;
209              
210             if ($mode == MODE_GET) {
211             if (exists $oid_tree->{$oid}) {
212             # /!\ no intermediate vars. see comment at end
213             $request->setValue(
214             $oid_tree->{$oid}[TYPE],
215             $oid_tree->{$oid}[VALUE]
216             );
217             }
218             else {
219             $request->setError($request_info, SNMP_ERR_NOSUCHNAME);
220             next
221             }
222             }
223             elsif ($mode == MODE_GETNEXT) {
224             # find the OID after the requested one
225             my $next_oid = find_next_oid($oid_list, $oid);
226              
227             if (exists $oid_tree->{$next_oid}) {
228             # /!\ no intermediate vars. see comment at end
229             $request->setOID($next_oid);
230             $request->setValue(
231             $oid_tree->{$next_oid}[TYPE],
232             $oid_tree->{$next_oid}[VALUE]
233             );
234             }
235             else {
236             $request->setError($request_info, SNMP_ERR_NOSUCHNAME);
237             next
238             }
239             }
240             else {
241             $request->setError($request_info, SNMP_ERR_GENERR);
242             next
243             }
244             }
245             }
246              
247              
248             #
249             # ev_add_oid_entry()
250             # ----------------
251             sub ev_add_oid_entry {
252             my ($kernel, $heap, $oid, $type, $value)
253             = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ];
254              
255             my $oid_tree = $heap->{oid_tree};
256              
257             # make sure that the OID start with a dot
258             $oid = ".$oid" unless index($oid, ".") == 0;
259              
260             # add the given entry to the tree
261             $oid_tree->{$oid} = [ $type, $value ];
262              
263             # calculate the sorted list of OID entries
264             @{ $heap->{oid_list} } = HAVE_SORT_KEY_OID ?
265             oidsort(keys %$oid_tree) : sort by_oid keys %$oid_tree;
266             }
267              
268              
269             #
270             # ev_add_oid_tree()
271             # ---------------
272             sub ev_add_oid_tree {
273             my ($kernel, $heap, $new_tree) = @_[ KERNEL, HEAP, ARG0 ];
274              
275             my $oid_tree = $heap->{oid_tree};
276              
277             # make sure that the OIDs start with a dot
278             my @oids = map { index($_, ".") == 0 ? $_ : ".$_" } keys %$new_tree;
279              
280             # add the given entries to the tree
281             @{$oid_tree}{@oids} = values %$new_tree;
282              
283             # calculate the sorted list of OID entries
284             @{ $heap->{oid_list} } = HAVE_SORT_KEY_OID ?
285             oidsort(keys %$oid_tree) : sort by_oid keys %$oid_tree;
286             }
287              
288              
289             # ==============================================================================
290             # Methods
291             #
292              
293              
294             #
295             # new()
296             # ---
297             sub new {
298             my $class = shift;
299             croak "error: odd number of arguments" unless @_ % 2 == 0;
300              
301             my %args = @_;
302             my $update_handlers = delete $args{AutoUpdate};
303             carp "warning: no OID tree update handlers defined"
304             unless $update_handlers;
305              
306             # instanciate ourself
307             my $self = POE::Component::NetSNMP::agent->spawn(%args);
308              
309             # create a heap reserved for the update handlers
310             $self->[0]{update_handlers_heap} = {};
311              
312             # create the states for wrapping around the update handlers
313             my %state;
314             for my $def (@$update_handlers) {
315             my ($coderef, $delay) = @$def;
316             my $name = "wrap_sub_$coderef";
317              
318             $state{$name} = sub {
319             $_[KERNEL]->delay($name, $delay);
320             eval { $coderef->($self) };
321             warn $@ if $@;
322             };
323             }
324              
325             # create the additional session to execute the update handlers
326             POE::Session->create(
327             heap => {
328             agent => $self,
329             },
330              
331             args => [ keys %state ],
332              
333             inline_states => {
334             _start => sub {
335             $_[KERNEL]->alias_set("main");
336             $_[KERNEL]->yield($_) for @_[ARG0 .. $#_];
337             },
338             %state,
339             },
340             );
341              
342             return $self
343             }
344              
345              
346             #
347             # run()
348             # ---
349             sub run {
350             POE::Kernel->run;
351             }
352              
353              
354             #
355             # heap()
356             # ----
357             sub heap {
358             $_[0][0]{update_handlers_heap}
359             }
360              
361              
362             #
363             # register()
364             # --------
365             sub register {
366             my ($self, $oid, $callback) = @_;
367              
368             # check arguments
369             croak "error: no OID defined" unless defined $oid;
370             croak "error: no callback defined" unless defined $callback;
371             croak "error: callback must be a coderef"
372             unless ref $callback and ref $callback eq "CODE";
373              
374             # register the given OID and callback
375             POE::Kernel->post($self, register => $oid, $callback);
376              
377             return $self
378             }
379              
380              
381             #
382             # add_oid_entry()
383             # -------------
384             sub add_oid_entry {
385             my ($self, $oid, $type, $value) = @_;
386              
387             # check arguments
388             croak "error: no OID defined" unless defined $oid;
389             croak "error: no type defined" unless defined $type;
390             croak "error: no value defined" unless defined $value;
391              
392             # register the given OID and callback
393             POE::Kernel->post($self, add_oid_entry => $oid, $type, $value);
394              
395             return $self
396             }
397              
398              
399             #
400             # add_oid_tree()
401             # ------------
402             sub add_oid_tree {
403             my ($self, $new_tree) = @_;
404              
405             # check arguments
406             croak "error: expected a hashref" unless ref $new_tree eq "HASH";
407              
408             # register the given OID and callback
409             POE::Kernel->post($self, add_oid_tree => $new_tree);
410              
411             return $self
412             }
413              
414              
415             # ==============================================================================
416             # Hackery
417             #
418              
419             {
420             package ### live-patch NetSNMP::OID
421             NetSNMP::OID;
422             sub as_oid { return join ".", "", $_[0]->to_array }
423             }
424              
425              
426             __PACKAGE__
427              
428             __END__