=pod

=head1 NAME

Net::Appliance::Session::Cookbook::Recipe03 - Simple Error Handling

=head1 NOTE

This Cookbook was contributed to the L<Net::Appliance::Session> project by
Nigel Bowden. Source code from the Cookbook is shipped in the C<examples>
folder of this module's distribution.

=head1 PROBLEM

You want to perform an opertation using SSH on a number of Cisco IOS devices,
but don't want the script to fail just because an operation on one device
failed.

=head1 SOLUTION

This solution is very similar to recipe 02, but this time we attempt to get
information from more than one device.

Again, we assume that the IOS device requires only a username and password to
login to the device. At that point, it is assumed that the device is
configured to drop a user straight in to enable mode (privilege level 15) when
logged in. See recipe 02 for details of how to explicitly drop in to enable
mode.

 use strict;
 use warnings;
 
 use Net::Appliance::Session;
 
 # list of devices to contact
 my @ios_device_ip_addresses = ('10.250.249.215', '10.250.249.216');
 
 my $ios_username        = 'cisco';
 my $ios_password        = 'cisco';
 
 # step through each device in turn
 for my $ios_device_ip ( @ios_device_ip_addresses ) {
 
     my @version_info; # array to hold data from device
 
     my $session_obj = Net::Appliance::Session->new(
         Host      => $ios_device_ip,
         Transport => 'SSH',
     );
     
     # start eval block to trap errors in interactive session
     eval {
         # try to login to the ios device, ignoring host check
         $session_obj->connect(
             Name => $ios_username,
             Password => $ios_password,
             SHKC => 0
         );
         
         # get our running config
         @version_info =  $session_obj->cmd('show ver');
         
         # close down our session
         $session_obj->close;
     };
     
     # check if we had an error
     if ($@) {
         print "We had an issue when accessing the device : $ios_device_ip \n";
         print "The reported error was : $@ \n\n";
     }
     else {
         print "Version infor for $ios_device_ip retrieved OK, here it is : \n\n"; 
         print @version_info;
     }
 
 # end of for loop
 }

=head1 DISCUSSION

In this recipe, we want to try to contact a number of Cisco IOS devices and
pull some information from them. So, we use a simple C<for> loop to step though
a list of IP addresses:

 # list of devices to contact
 my @ios_device_ip_addresses = ('10.250.249.215', '10.250.249.216');
 
 my $ios_username        = 'cisco';
 my $ios_password        = 'cisco';
 
 # step through each device in turn
 for my $ios_device_ip ( @ios_device_ip_addresses ) {

By default, Net::Appliance::Session will generate an exception (i.e. C<die>)
when it encounters any issues during an interactive session (e.g. Telnet or
SSH) with a device.

For instance, if the user credentials are incorrect, or maybe a device is
un-reachable, then your script will grind to a halt at the point that it
fails. If you are running your script from command line, you will also get a
helpful failure message to give you a clue as to what went wrong.

However, if you are using your script to access a number of devices to
retrieve some information or perform an operation, then you maybe don't want
the script to fail part-way through a series of operations on a number of
devices. For instance, if one device is un-reachable, you may want the script
to skip this device and move on to the next one.

In order to work around the default behaviour of Net::Appliance::Session, we
have to apply a bit of perl trickery to allow the failure to occur, but for
our script to detect it, report on it and then continue.

The way that we have to do this is to wrap the interactive segments of our
code (i.e. when we are logging in, sending commands etc.) in C<eval {}>
statements.

The eval statement allows us to run the code between its braces as a normal
piece of perl code, but if an exception is raised (e.g. it dies), we can
detect the failure by inspecting the perl builtin C<$@> variable to see why
things went wrong. This is Perl's version of an Exception (try/catch) system
as found in other languages.

Here is the code from our recipe that uses the eval statement to trap any
errors :

 eval {
     # try to login to the ios device, ignoring host check
     $session_obj->connect(
         Name => $ios_username,
         Password => $ios_password,
         SHKC => 0
     );
         
     # get our running config
     @version_info =  $session_obj->cmd('show ver');
         
     # close down our session
     $session_obj->close;
 };

You can see that we have taken our standard set of commands that we use to
login to the device and attempt to get some information, but have wrapped the
C<eval {}> statement around them.

Each statement will be executed as normal, except if there is an error
condition. If an error occurs, then the execution will halt at that point, and
subsequent commands in the braces will not be executed. The flow of the script
then switches to the end of the eval braces, giving us the opportunity to find
out what may have gone wrong:

 # check if we had an error
 if ($@) {
     print "We had an issue when accessing the device : $ios_device_ip \n";
     print "The reported error was : $@ \n\n";
     
 }
 else {
     print "Version infor for $ios_device_ip retrieved OK, here it is : \n\n"; 
     print @version_info;
 }

The segment of code above shows how we do a simple check to see if there was
an error and what happened. By checking whether a value for C<$@> is defined,
we can see if there was an error in the code wrapped in the C<eval {}> wrapper.
If an error was detected, we can get a text message about the error by simply
printing out C<$@>.

As our script hasn't been halted in its tracks, we can now decide whether or
not we continue with the flow of our script. In our particular example, even
if we have a failure, we just print out a failure message and then move on to
the next device by going around the next iteration of our C<for> loop.

Net::Appliance::Session allows us to get more detailed information about the
reason for failure, but we'll look more in to that in our next recipe.

=head1 SEE ALSO

=over 4

=item L<Net::Appliance::Session>

=back

=head1 AUTHOR

Nigel Bowden, with POD formatting by Oliver Gorwits.

=head1 COPYRIGHT & LICENSE

Copyright (c) Nigel Bowden 2007. All Rights Reserved.

You may distribute and/or modify this documentation under the same terms as
Perl itself.

=cut
