force10_expect.pm 7.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
#!/usr/bin/perl -w

#
# Copyright (c) 2013,2014 University of Utah and the Flux Group.
# Copyright (c) 2006-2014 Universiteit Gent/iMinds, Belgium.
# Copyright (c) 2004-2006 Regents, University of California.
# 
# {{{EMULAB-LGPL
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
# License for more details.
# 
# You should have received a copy of the GNU Lesser General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#

#
# Expect module for Force10 switch cli interaction.  A thousand curses to
# Force10 Networks that this module had to be written at all...
#

package force10_expect;
use strict;
use Data::Dumper;

$| = 1; # Turn off line buffering on output

use English;
use Expect;

# Constants
43 44 45
my $CONN_TIMEOUT = 60;
my $CLI_TIMEOUT  = 15;
my $DEBUG_LOG    = "/tmp/force10_expect_debug.log";
46 47 48 49 50 51 52 53 54

sub new($$$$) {

    # The next two lines are some voodoo taken from perltoot(1)
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $name = shift;
    my $debugLevel = shift;
55
    my $userpass = shift;  # username and password
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

    #
    # Create the actual object
    #
    my $self = {};

    #
    # Set the defaults for this object
    # 
    if (defined($debugLevel)) {
        $self->{DEBUG} = $debugLevel;
    } else {
        $self->{DEBUG} = 0;
    }

    $self->{NAME} = $name;
72 73 74 75 76
    ($self->{USERNAME}, $self->{PASSWORD}) = split(/:/, $userpass);
    if (!$self->{USERNAME} || !$self->{PASSWORD}) {
	warn "force10_expect: ERROR: must pass in username AND password!\n";
	return undef;
    }
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

    if ($self->{DEBUG}) {
        print "force10_expect initializing for $self->{NAME}, " .
            "debug level $self->{DEBUG}\n" ;
    }

    $self->{CLI_PROMPT} = "$self->{NAME}#";

    # Make it a class object
    bless($self, $class);

    #
    # Lazy initialization of the Expect object is adopted, so
    # we set the session object to be undef.
    #
    $self->{SESS} = undef;

    return $self;
}

#
# Create an Expect object that spawns the ssh process 
# to switch.
#
sub createExpectObject($)
{
    my $self = shift;
    my $id = "$self->{NAME}::createExpectObject()";
105
    my $error = 0;
106
    my $spawn_cmd = "ssh -l $self->{USERNAME} $self->{NAME}";
107 108 109 110 111 112 113 114
    # Create Expect object and initialize it:
    my $exp = new Expect();
    if (!$exp) {
        # upper layer will check this
        return undef;
    }
    $exp->raw_pty(0);
    $exp->log_stdout(0);
115 116 117 118 119 120

    if ($self->{DEBUG} > 1) {
	$exp->log_file($DEBUG_LOG,"w");
	$exp->debug(1);
    }

121 122 123 124 125
    if (!$exp->spawn($spawn_cmd)) {
	warn "$id: Cannot spawn $spawn_cmd: $!\n";
	return undef;
    }
    $exp->expect($CONN_TIMEOUT,
126 127 128 129
         ["$self->{USERNAME}\@$self->{NAME}'s password:" => 
	  sub { my $e = shift;
		$e->send($self->{PASSWORD}."\n");
		exp_continue;}],
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
         ["Permission denied" => sub { $error = "Password incorrect!";} ],
         [ timeout => sub { $error = "Timeout connecting to switch!";} ],
         $self->{CLI_PROMPT} );

    if (!$error && $exp->error()) {
	$error = $exp->error();
    }

    if ($error) {
	warn "$id: Could not connect to switch: $error\n";
	return undef;
    }

    return $exp;
}

#
# Utility function - return the configuration prompt string for a given
# interface name.
#
150 151
sub conf_iface_prompt($$) {
    my ($self, $iface) = @_;
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    my $suffix = "";
    IFNAME: for ($iface) {
	/vlan(\d+)/i && do {$suffix = "vl-$1"; last IFNAME;};
	/(te|fo)(\d+\/\d+)/i && do {$suffix = "$1-$2"; last IFNAME;};
	/po(\d+)/i && do {$suffix = "po-$1"; last IFNAME;};
	return undef; # default case: invalid/unhandled iface name
    }
    return $self->{NAME} . '(conf-if-' . $suffix . ')#';
}

#
# Run a CLI command (or config command), checking for errors.
#
# Parameters:
# $cmd - The CLI command to run in the given context.
# $confmode - Is this a configuration command? 1 for yes, 0 for no
# $iface - Name of interface to exec config command against.
#
sub doCLICmd($$;$$)
{
    my ($self, $cmd, $confmode, $iface) = @_;
    $confmode ||= 0;
    $iface    ||= "";

    my $output = "";
    my $error = "";
    my @active_sets;

    my $exp = $self->{SESS};
181 182 183
    my $id = "$self->{NAME}::doCLICmd()";

    $self->debug("$id: called with: '$cmd', '$confmode', '$iface'\n",1);
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

    if (!$exp) {
	#
	# Create the Expect object, lazy initialization.
	#
	# We'd better set a long timeout on Apcon switch
	# to keep the connection alive.
	$self->{SESS} = $self->createExpectObject();
	if (!$self->{SESS}) {
	    warn "WARNING: Unable to connect to $self->{NAME}\n";
	    return (1, "Unable to connect to switch $self->{NAME}.");
	}
	$exp = $self->{SESS};
    }

    # Common patterns
200 201 202
    my $catch_error_pat  = [qr/% Error: (.+?)\n/,
			    sub {my $e = shift; $error = ($e->matchlist)[0];
				 exp_continue;}];
203
    my $timeout_pat      = [timeout => sub { $error = "timed out.";}];
204 205
    my $get_output_pat   = [$self->{CLI_PROMPT}, sub {my $e = shift; 
						      $output = $e->before();}];
206 207

    # Common pattern sets
208
    my $get_output_set = [$get_output_pat];
209 210 211 212 213 214 215 216 217 218 219

    #
    # Sets of pattern sets for execution follow.
    #

    # Just pop off one command without going into config mode.
    my @single_command_sets = ();
    push (@single_command_sets,
	  [
	     [$self->{CLI_PROMPT}, sub {my $e = shift; $e->send("$cmd\n")}]
	  ],
220
	  $get_output_pat
221 222 223 224 225 226
	);

    # Perform a single config operation (go into config mode).
    my @single_config_sets = ();
    push (@single_config_sets,
	  [
227 228
	     [$self->{CLI_PROMPT}, sub {my $e = shift; 
					$e->send("conf t\n$cmd\nend\n");}]
229
	  ],
230
	  $get_output_pat
231 232 233 234 235 236
	);

    # Do an interface config operation (go into iface-specific config mode).
    my @iface_config_sets = ();
    push (@iface_config_sets,
	  [
237 238
	     [$self->{CLI_PROMPT}, sub {my $e = shift; 
					$e->send("conf t\ninterface $iface\n$cmd\nend\n");}]
239
	  ],
240
	  $get_output_pat
241 242 243 244 245 246 247 248 249 250 251 252 253 254
	);

    # Pick "set of sets" to use with Expect based on how this method
    # was called.
    if ($confmode) {
	if ($iface) {
	    @active_sets = @iface_config_sets;
	} else {
	    @active_sets = @single_config_sets;
	}
    } else {
	@active_sets = @single_command_sets;
    }

255
    $exp->clear_accum();
256 257
    $exp->send("\cC"); # Get a command prompt into the Expect accumulator.
    # Match across the selected set of patterns.
258
    my $i = 1;
259
    foreach my $patset (@active_sets) {
260 261
	$self->debug("Match set: $i.\n",2);
	$i++;
262 263 264 265
	$exp->expect($CLI_TIMEOUT,
		     $catch_error_pat,
		     @$patset,
		     $timeout_pat);
266 267 268 269 270 271 272 273
	if ($error || $exp->error()) {
	    $self->debug("error string: $error\n",2);
	    $self->debug("exp error: " . ($exp->error()) . "\n",2);
	} else {
	    $self->debug("exp match:  " . ($exp->match()) . "\n",2);
	}
	$self->debug("exp before: " . ($exp->before()) . "\n",2);
	$self->debug("exp after:  " . ($exp->after()) . "\n",2);
274 275 276 277 278 279 280
    }

    if (!$error && $exp->error()) {
	$error = $exp->error();
    }

    if ($error) {
281
	$self->debug("$id: Error in doCLICmd: $error\n",1);
282 283 284 285 286
        return (1, $error);
    } else {
        return (0, $output);
    }
}
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

#
# Prints out a debugging message, but only if debugging is on. If a level is
# given, the debuglevel must be >= that level for the message to print. If
# the level is omitted, 1 is assumed
#
# Usage: debug($self, $message, $level)
#
sub debug($$;$) {
    my $self = shift;
    my $string = shift;
    my $debuglevel = shift;
    if (!(defined $debuglevel)) {
        $debuglevel = 1;
    }
    if ($self->{DEBUG} >= $debuglevel) {
        print STDERR $string;
    }
}