File Coverage

blib/lib/Net/Autoconfig/Device/HP4000.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 Net::Autoconfig::Device::HP4000;
2              
3 1     1   131411 use 5.008008;
  1         6  
  1         92  
4 1     1   8 use strict;
  1         2  
  1         82  
5 1     1   7 use warnings;
  1         2  
  1         70  
6              
7 1     1   5 use base "Net::Autoconfig::Device";
  1         2  
  1         1842  
8             use Log::Log4perl qw(:levels);
9             use Data::Dumper;
10             use Expect;
11             use version; our $VERSION = version->new('v1.0.1');
12              
13             #################################################################################
14             ## Constants and Global Variables
15             #################################################################################
16              
17             use constant TRUE => 1;
18             use constant FALSE => 0;
19             use constant LONG_TIMEOUT => 30;
20             use constant MEDIUM_TIMEOUT => 15;
21             use constant SHORT_TIMEOUT => 5;
22              
23             ####################
24             # Expect Command Definitions
25             # These statements are strings, which need to be
26             # eval'ed within the methods to get their
27             # actual values. This provides a way to pre-declare
28             # common expect commands without having to copy-paste
29             # them into each and every method that uses them.
30             # This incurs a performance hit, but I think it's
31             # worth it.
32             #
33             # Yay!
34             ####################
35             my $expect_password_cmd = '[
36             -re => "word[.:.]",
37             sub
38             {
39             $session->clear_accum();
40             $session->send($self->password . "\n");
41             sleep(1);
42             }
43             ]';
44             my $expect_exec_mode_cmd = '[
45             -re => "Switch",
46             sub
47             {
48             sleep(1);
49             $connected_to_device = TRUE;
50             }
51             ]';
52             my $expect_timeout_cmd = '[
53             timeout =>
54             sub
55             {
56             $command_failed = TRUE;
57             }
58             ]';
59              
60             #################################################################################
61             # Methods
62             #################################################################################
63              
64             ############################################################
65             # Public Methods
66             ############################################################
67              
68             ########################################
69             # new
70             # public method
71             #
72             # create a new Net::Autoconfig::Device::HP4000 object.
73             #
74             # If passed an array, it will assume those are key
75             # value pairs and assign them to the device.
76             #
77             # If no values are defined, then default ones are assigned.
78             #
79             # Returns:
80             # A Net::Autoconfig::Device::HP4000 object
81             ########################################
82             sub new {
83             my $invocant = shift; # calling class
84             my $class = ref($invocant) || $invocant;
85             my $self = {
86             @_,
87             invalid_cmd_regex => 'failed',
88             };
89             my $log = Log::Log4perl->get_logger( $class );
90              
91             $log->debug("Creating new Net::Autoconfig::Device::HP4000");
92             $self = bless $self, $class;
93              
94             # HP 4000s only have telnet access
95             # and use a different password for
96             # mgmt access. So, assume an admin
97             # password was specified
98             $self->access_method('telnet');
99             $self->admin_status(TRUE);
100             $self->paging_disabled(TRUE);
101              
102             return $self;
103             }
104              
105              
106              
107             ########################################
108             # connect
109             # public method
110             #
111             # overloads the parent method.
112             # Takes into account the ecentricities of
113             # hp devices.'
114             ########################################
115             sub connect {
116             my $self = shift;
117             my $session; # a ref to the expect session
118             my $access_command; # the string to use to the telnet/ssh app.
119             my $result; # the value returned after executing an expect cmd
120             my @expect_commands; # the commands to run on the device
121             my $spawn_cmd; # command expect uses to connect to the device
122             my $log = Log::Log4perl->get_logger("Net::Autoconfig");
123              
124             # Expect success/failure flags
125             my $connected_to_device; # indicates a successful connection to the device
126             my $command_failed; # indicates a failed connection to the device
127              
128             # Do some sanity checking
129             if (not $self->hostname)
130             {
131             $log->warn("No hostname defined for this device.");
132             return "No hostname defined for this devince.";
133             }
134              
135             if (not $self->access_method)
136             {
137             $log->warn("Access method for " . $self->hostname . " not defined.");
138             return "Access method not defined.";
139             }
140            
141             if (not $self->access_cmd)
142             {
143             $log->warn("Access command for " . $self->hostname . " not defined.");
144             return "Access command not defined";
145             }
146              
147             # Setup the access command
148             if ($self->access_method =~ /^ssh$/)
149             {
150             # Force using ssh version 1 due to old firmware problems.
151             $spawn_cmd = join(" ", $self->access_cmd, "-l", $self->username, $self->hostname);
152             }
153             else
154             {
155             $spawn_cmd = join(" ", $self->access_cmd, $self->hostname);
156             }
157              
158             # Okay, let's get on with connecting to the device
159             $session = $self->session;
160             if (&_invalid_session($session))
161             {
162             $log->info("Connecting to " . $self->hostname);
163             $log->debug("Using command '" . $self->access_cmd . "'");
164             $log->debug("Spawning new expect session with: '$spawn_cmd'");
165             eval
166             {
167             $session = new Expect;
168             $session->raw_pty(TRUE);
169             $session->spawn($spawn_cmd);
170             };
171             if ($@)
172             {
173             $log->warn("Connecting to " . $self->hostname . " failed: $@");
174             return $@;
175             }
176             }
177             else
178             {
179             $log->info("Session for ". $self->hostname . " already exists.");
180             }
181              
182             # Enable dumping data to the screen.
183             if ($log->is_trace() || $log->is_debug() )
184             {
185             $session->log_stdout(TRUE);
186             }
187             else
188             {
189             $session->log_stdout(FALSE);
190             }
191              
192             ####################
193             # Setup Expect command array
194             #
195             # The commands are defined for the class, but they need
196             # to be eval'ed before we can use them.
197             ####################
198             # Setup the expect commands to do the initial login.
199             # Up to four commands may need to be run:
200             # accept the ssh key
201             # send the username
202             # send the password
203             # verify connection (exec or priv exec mode)
204             ####################
205             push(@expect_commands, [
206             eval $expect_password_cmd,
207             ]);
208             push(@expect_commands, [
209             eval $expect_exec_mode_cmd,
210             ]);
211              
212             foreach my $command (@expect_commands)
213             {
214             $session->expect(MEDIUM_TIMEOUT, @$command, eval $expect_timeout_cmd);
215             if ($log->level == $TRACE)
216             {
217             $log->trace("Expect matching before: " . $session->before);
218             $log->trace("Expect matching match : " . $session->match);
219             $log->trace("Expect matching after : " . $session->after);
220             }
221             if ($connected_to_device)
222             {
223             $log->debug("Connected to device " . $self->hostname);
224             $self->session($session);
225             last;
226             }
227             elsif ($command_failed)
228             {
229             $log->warn("Failed to connect to device " . $self->hostname);
230             $log->debug("Failed on command: " , @$command);
231             $session->soft_close();
232             $self->session(undef);
233             last;
234             }
235             }
236              
237             return $connected_to_device ? undef : 'Failed to connect to device.';
238             }
239              
240             ########################################
241             # discover_dev_type
242             # public method
243             #
244             # overloads the parent method.
245             #
246             # Since this is already discovered,
247             # return what type of device it is.
248             ########################################
249             sub discover_dev_type {
250             my $self = shift;
251             return ref($self);
252             }
253              
254             ########################################
255             # get_admin_rights
256             # public method
257             #
258             # overloads the parent method.
259             # Takes into account the ecentricities of
260             # hp devices.
261             #
262             # Returns:
263             # success = undef
264             # failure = reason for failure (aka a true value)
265             ########################################
266             sub get_admin_rights {
267             my $self = shift;
268             my $log = Log::Log4perl->get_logger("Net::Autoconfig");
269              
270             $self->admin_status(TRUE);
271             $log->debug("Admin rights not fully implemented for hp4000s");
272              
273             return;
274             }
275              
276             ########################################
277             # disable_paging
278             # public method
279             #
280             # Disable terminal paging (press -Enter-
281             # to continue) messages. They cause problems
282             # when using expect.
283             #
284             # Returns:
285             # success = undef
286             # failure = reason for failure
287             ########################################
288             sub disable_paging {
289             my $self = shift;
290             my $session; # the object's expect session
291             my $log = Log::Log4perl->get_logger("Net::Autoconfig");
292             my $command_failed; # a flag to indicate if the command failed
293             my @commands; # an array of commands to execute
294              
295             $session = $self->session;
296             if (&_invalid_session($session))
297             {
298             return "Failed - session not defined";
299             }
300              
301             $log->debug("No paging for this device - It's menu based.");
302              
303             return;
304             }
305              
306             ########################################
307             # configure
308             # public method
309             #
310             # Configure a device using the
311             # specified template.
312             #
313             # Template data should be in the form of
314             # a hash:
315             # $template_data = {
316             # {cmds} = [ {cmd 1}, {cmd 2}, {cmd 3} ]
317             # {default} = { default data }
318             #
319             # Returns
320             # success = undef
321             # failure = Failure message.
322             ########################################
323             sub configure {
324             my $self = shift;
325             my $template_data = shift;
326             my $session; # the object's expect session
327             my $error_cmd; # expect cmd to see if a cmd was invalid
328             my $error_flag; # indicates if the command was invalid
329             my $log = Log::Log4perl->get_logger("Net::Autoconfig");
330             my $last_cmd; # record keeping for error reporting
331              
332             $log->trace("Using the hp4000s specific configure method!");
333              
334              
335             # Let's do some sanity checking
336             if (not $template_data)
337             {
338             $log->warn("Failed - No template data");
339             return "Failed - No template data";
340             }
341              
342             if (&_invalid_session($self->session))
343             {
344             my $hostname = $self->hostname || "no hostname";
345             $log->warn("Failed - No session for " . $hostname);
346             return "Failed - No session for " . $hostname;
347             }
348              
349             if (not $self->admin_status)
350             {
351             my $hostname = $self->hostname || "no hostname";
352             $log->warn("Failed - do not have admin access to device.");
353             return "Failed - do not have admin access to device.";
354             }
355            
356             if (not exists $template_data->{default})
357             {
358             $template_data->{default} = {};
359             }
360             $session = $self->session;
361              
362             # Enable verbose debugging
363             if ( $log->is_trace )
364             {
365             $session->debug(3);
366             }
367              
368              
369             # Each cmd is a hash ref
370             # Join it with the default data. The cmd data
371             # will overwrite the default data. Yay!
372             COMMAND:
373             foreach my $cmd (@{ $template_data->{cmds} })
374             {
375             my $expect_cmd; # the command to run on the CLI
376             my $error_cmd; # the cmd that detects an error/invalid command
377             my $command_failed; # a flag to indicate if the command failed
378             my $timeout_cmd; # what to do if there's a timeout
379              
380             # This is a perfance hit for each command. Does it matter?
381             if ($cmd->{required} )
382             {
383             $timeout_cmd = eval $expect_timeout_cmd;
384             }
385             else
386             {
387             undef $timeout_cmd;
388             }
389              
390             if ( $self->replace_command_variables( $cmd ) )
391             {
392             $self->error_end_session( "Failed to replace command variables" );
393             return "Command failed.";
394             }
395              
396             # HP4000s have Microsoft style newlines
397             # newlines = MS new lines = carriage return + newlie
398             $cmd->{cmd} =~ s/\n/\r\n/g;
399              
400             $error_cmd = [
401             -re => $self->invalid_cmd_regex,
402             sub
403             {
404             $log->warn("Invalid command entered! '$last_cmd'");
405             $command_failed = TRUE;
406             }
407             ];
408              
409             $expect_cmd = [
410             -re => $cmd->{regex},
411             sub
412             {
413             $session->clear_accum();
414             $session->send($cmd->{cmd});
415             }
416             ];
417              
418              
419             # Okay, send the command
420             if ($cmd->{cmd} =~ /wait/i)
421             {
422             $session->expect($cmd->{timeout}, [ -re => "BOGUS REGEX" ] );
423             }
424             else
425             {
426             $session->expect($cmd->{timeout}, $error_cmd, $expect_cmd, $timeout_cmd);
427             }
428              
429             if ($log->is_trace or $log->is_debug )
430             {
431             $log->trace("== Expect Matches ==");
432             $log->debug(Dumper($cmd));
433             $log->trace("Expect before: " . $session->before);
434             $log->trace("Expect match : " . $session->match );
435             $log->trace("Expect after : " . $session->after );
436             }
437              
438             $last_cmd = $cmd->{cmd};
439              
440             if ($command_failed)
441             {
442             # close session and alarm
443             $log->trace("== Expect Matches - Failed Command ==");
444             $log->debug(Dumper($cmd));
445             $log->trace("Expect before: " . $session->before);
446             $log->trace("Expect match : " . $session->match );
447             $log->trace("Expect after : " . $session->after );
448             $self->error_end_session("Required command failed for " . $self->hostname);
449             return "Command failed.";
450             }
451             sleep(2);
452             }
453              
454             # Disable verbose expect debugging
455             if ( $log->is_trace )
456             {
457             $session->debug(0);
458             }
459              
460             # One last check to see if the last comand was invalid.
461             # This is different than the one in the COMMAND loop
462             # The Expect->expect method can't exit or return from _this_
463             # method. So, detect the error and do our own exiting.
464             $error_cmd = [
465             -re => $self->invalid_cmd_regex,
466             sub
467             {
468             $error_flag = TRUE;
469             $log->warn("Invalid command entered! '"
470             . $template_data->{cmds}->[-1]->{cmd}
471             . "'"
472             );
473             }
474             ];
475            
476             if ($log->is_trace)
477             {
478             $log->trace( "Error command: " . Dumper($error_cmd) );
479             }
480              
481             $session->expect(SHORT_TIMEOUT, $error_cmd );
482              
483             if ($error_flag)
484             {
485             $self->error_end_session("Last command entered was invalid for " . $self->hostname);
486             return "Last command was invalid.";
487             }
488              
489             $log->info("All commands executed successfullly for " . $self->hostname . ".");
490             return;
491             }
492              
493             ############################################################
494             # Private Methods
495             ############################################################
496              
497              
498             ########################################
499             # _invalid_session
500             # private method
501             #
502             # Determine if this is a valid session.
503             # We're using expect, so it has to be an
504             # expect object reference, and it has to
505             # be defined.
506             #
507             # Returns:
508             # true if invalid
509             # undef if valid
510             ########################################
511             sub _invalid_session {
512             my $session = shift;
513              
514             if (not defined $session)
515             {
516             return TRUE;
517             }
518             elsif (not ref($session))
519             {
520             return TRUE;
521             }
522             elsif (not ref($session) eq 'Expect')
523             {
524             return TRUE;
525             }
526             else
527             {
528             return;
529             }
530             }
531              
532             # Modules must return true.
533             TRUE;
534              
535              
536             __END__