package RackMan::Device::Switch::Cisco_Catalyst;
use Moose::Role;
use RackMan;
use RackMan::File;
use RackMan::Utils;
use namespace::autoclean;
use constant {
CONFIG_SECTION => "device:switch:cisco_catalyst",
CONFIG_FILENAME => "config.cmd",
};
#
# write_config()
# ------------
sub write_config {
my ($self, $args) = @_;
my $rackman = $args->{rackman};
my $path = $rackman->config->val(general => "path");
# generate the configuration file
my $config = $self->fetch_from_database($args);
# write the configuration file on disk, to its final path
$config->name(CONFIG_FILENAME);
$config->path($path);
print " + writing ", $config->fullpath, $/ if $args->{verbose};
my $scm = $rackman->get_scm({ path => $path });
$scm->update;
$config->write;
$scm->add($config->name);
$scm->commit($config->name, "generated by ".__PACKAGE__
. " / $::PROGRAM v$::VERSION");
}
#
# diff_config()
# -----------
sub diff_config {
my ($self, $args) = @_;
my $rackman = $args->{rackman};
my $path = $rackman->config->val(general => "path");
# fetch config from the device
my $current = $self->fetch_from_device($args);
# generate the config from the database
my $expected = $self->fetch_from_database($args);
# prepare the files
my @cfg_current = split $/, $current->content;
my @cfg_expected = split $/, $expected->content;
# computes the differences between the two files
print " - computing differences..." if $args->{verbose};
my @diff = diff_lines(\@cfg_current, \@cfg_expected);
print " done\n\n" if $args->{verbose};
print @diff;
RackMan->set_status(1) if @diff;
}
#
# push_config()
# -----------
sub push_config {
my ($self, $args) = @_;
my $rackman = $args->{rackman};
my $path = $rackman->config->val(general => "path");
my $config = RackMan::File->new;
# read the configuration file from disk
$config->name(CONFIG_FILENAME);
$config->path($path);
$config->read;
# send it to the device
$self->store_to_device($args, $config);
}
#
# fetch_from_database()
# -------------------
sub fetch_from_database {
my ($self, $args) = @_;
my $rackman = $args->{rackman};
my $config = RackMan::File->new;
my %interface;
for my $port (@{ $self->ports }) {
next unless $port->{name} =~ /^gi/i;
(my $name = $port->{name}) =~ s/^\D+/GigabitEthernet/;
(my $num = $port->{name}) =~ s/^\D+//;
my $description = defined $port->{peer_object_name}
? " description $port->{peer_object_name} ($port->{peer_port_name})"
: " no description";
$interface{$num} = "interface $name\n$description\n!\n";
}
# we need to first store the generated command, then sort them
# by the interface number (the "0/2" part in "GigabitEthernet0/2")
$config->add_content("!\n");
$config->add_content($interface{$_})
for sort {
my @a = split "/", $a;
my @b = split "/", $b;
$a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2]
} keys %interface;
return $config
}
#
# fetch_from_device()
# -----------------
sub fetch_from_device {
my ($self, $args) = @_;
my $rackman = $args->{rackman};
my $config = RackMan::File->new;
# fetch the access password
my $ios_password = $rackman->options->{device_password}
|| $rackman->config->val(CONFIG_SECTION, "ios_password")
or RackMan->error("missing IOS access password");
# load the protocol module
{ local $SIG{__WARN__} = sub {}; require Net::Telnet::Cisco; }
# connect to the device
my $host = $self->attributes->{FQDN} || $self->object_name;
my $session = Net::Telnet::Cisco->new(Host => $host);
$session->login(Password => $ios_password);
$session->cmd("terminal length 0");
# fetch the list of interfaces with their current description
print " < fetching the list of interfaces... " if $args->{verbose};
my @out = $session->cmd("show interface description");
$config->add_content("!\n");
print "parsing... " if $args->{verbose};
chomp(my $line = shift @out);
my @fields = split / +/, $line;
for my $line (@out) {
$line =~ s/[\x01-\x1f]//g; # clean control chars
next unless defined $line and length $line;
# parse the status
my %interface;
$line =~ s/admin down/admin-down/;
@interface{@fields} = split / +/, $line, ~~@fields;
# interface name
next unless $interface{Interface} =~ /^gi/i;
$interface{Interface} =~ s/^\D+/GigabitEthernet/;
$config->add_content("interface $interface{Interface}\n");
# interface description
$interface{Description} ||= "";
my $description = length $interface{Description}
? "description $interface{Description}" : "no description";
$config->add_content(" $description\n");
$config->add_content("!\n");
}
print "done\n" if $args->{verbose};
$session->close;
return $config
}
#
# store_to_device()
# ---------------
sub store_to_device {
my ($self, $args, $config) = @_;
my $rackman = $args->{rackman};
# fetch the access passwords
my $ios_password = $rackman->options->{device_password}
|| $rackman->config->val(CONFIG_SECTION, "ios_password")
or RackMan->error("missing IOS access password");
my $enable_password
= $rackman->config->val(CONFIG_SECTION, "enable_password")
or RackMan->error("missing IOS admin password");
# load the protocol module
{ local $SIG{__WARN__} = sub {}; require Net::Telnet::Cisco; }
# connect to the device
my $host = $self->attributes->{FQDN} || $self->object_name;
my $session = Net::Telnet::Cisco->new(Host => $host);
$session->login(Password => $ios_password);
$session->enable(Password => $enable_password);
# enter configure mode
$session->cmd("configure terminal");
print " > sending commands batch... " if $args->{verbose};
# send the commands batch to the device
for my $cmd (split $/, $config->content) {
$session->cmd($cmd);
}
print "done\n" if $args->{verbose};
$session->close;
return $config
}
__PACKAGE__
__END__
=head1 NAME
RackMan::Device::Switch::Cisco_Catalyst - Role for Cisco Catalyst network switches
=head1 DESCRIPTION
This module is the role for Cisco Catalyst network switches.
=head1 PUBLIC METHODS
=head2 write_config
Write the Cisco commands for configuring the device to a F<config.cmd>
file, in the directory specified in RackMan's config (section C<[general]>,
parameter C<path>).
B<Arguments:>
=over
=item 1. options (hashref)
=back
=head2 diff_config
Fetch the configuration from the device, compare it with the configuration
generated from the RackTables database and print the differences.
The exit status of the program is set to 1 if there are differences,
0 otherwise.
B<Arguments:>
=over
=item 1. options (hashref)
=back
=head2 push_config
Read the configuration from disk and send it to the device.
B<Arguments:>
=over
=item 1. options (hashref)
=back
=head1 INTERNAL METHODS
=head2 fetch_from_database
Generate the Cisco commands for configuring the device accordingly to
the information from the RackTables database.
B<Arguments:>
=over
=item 1. options (hashref)
=back
B<Return:>
=over
=item *
configuration (RackMan::File instance)
=back
=head2 fetch_from_device
Fetch the list of interfaces with their description, and convert it
to the corresponding commands batch.
B<Arguments:>
=over
=item 1. options (hashref)
=back
B<Return:>
=over
=item *
commands batch (RackMan::File instance)
=back
=head2 store_to_device
Send the Cisco commands to the device.
B<Arguments:>
=over
=item 1. options (hashref)
=item 2. commands batch (RackMan::File instance)
=back
=head1 CONFIGURATION
See L<rack/"Section [device:switch:cisco_catalyst]">
=head1 AUTHOR
Sebastien Aperghis-Tramoni
=cut