Control-CLI-Extreme
view release on metacpan or search on metacpan
lib/Control/CLI/Extreme.pm view on Meta::CPAN
package Control::CLI::Extreme;
use strict;
use warnings;
use Exporter qw( import );
use Carp;
use Control::CLI qw( :all );
my $Package = __PACKAGE__;
our $VERSION = '1.14';
our @ISA = qw(Control::CLI);
our %EXPORT_TAGS = (
use => [qw(useTelnet useSsh useSerial useIPv6)],
prompt => [qw(promptClear promptHide promptCredential)],
args => [qw(parseMethodArgs suppressMethodArgs)],
coderef => [qw(validCodeRef callCodeRef)],
_rest => [qw(passphraseRequired parse_errmode stripLastLine poll)],
);
push @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} foreach keys %EXPORT_TAGS;
Exporter::export_ok_tags('all');
########################################### Global Class Variables ###########################################
my $Space = " ";
my $CTRL_C = "\cC";
my $CTRL_U = "\cU";
my $CTRL_X = "\cX";
my $CTRL_Y = "\cY";
my $CTRL_Z = "\cZ";
my %LoginPatterns = ( # Patterns to check for during device login (Telnet/Serial) and initial connection to CLI
bell => "\x07",
banner => 'Enter Ctrl-Y to begin',
menu => 'Use arrow keys to highlight option, press <Return> or <Enter> to select option',
submenu => 'Press Ctrl-R to return to previous menu. Press Ctrl-C to return to Main Menu',
username => 'Enter Username: ',
password => "Enter Password: \e[?", # Should match only on the initial password prompt and not subsequent ones where * are printed
lastlogin => 'Failed retries since last login:',
localfail => 'Incorrect',
localfail_xos => 'Login incorrect',
radiusfail => 'Access Denied from RADIUS',
radiustimeout1 => 'no response from RADIUS servers',
radiustimeout2 => 'No reply from RADIUS server',
srbanner => "\((?:Secure Router|VSP4K)",
xlrbanner => "\x0d************************************\n",
ersbanner => "\x0d* Ethernet Routing Switch",
passportbanner => "\x0d* Passport 8",
pp1600banner => "\x0d* Passport 16", # The 6 ensures this does not trigger on old Accelar 1x00
vspbanner => "All Rights Reserved.\n\x0dVirtual Services Platform",
fabengbanner => "Extreme Networks Fabric Engine",
consoleLogMsg1 => "connected via console port", #On serial port: GlobalRouter SW INFO user rwa connected via console port
consoleLogMsg2 => "Blocked unauthorized ACLI access",
more1 => '----More (q=Quit, space/return=Continue)----',
more2 => '--More--',
wlan9100banner => 'Avaya Wi-Fi Access Point',
xos => 'ExtremeXOS',
switchEngine => 'Extreme Networks Switch Engine',
isw => 'Product: ISW ',
isw2 => 'Product: ISW-4W-4WS-4X',
iswMarvell => 'Product: ISW-24W-4X',
slx => 'Welcome to the Extreme SLX-OS Software',
eosChassis => ' C H A S S I S',
oneosbanner => 'System is ready for all commands',
);
my %Prm = ( # Hash containing list of named parameters returned by attributes
bstk => 'BaystackERS',
pers => 'PassportERS',
xlr => 'Accelar',
sr => 'SecureRouter',
trpz => 'WLAN2300',
xirrus => 'WLAN9100',
xos => 'ExtremeXOS',
isw => 'ISW',
iswMarv => 'ISWmarvell',
s200 => 'Series200',
wing => 'Wing',
slx => 'SLX',
hive => 'HiveOS',
ipanema => 'Ipanema',
eos => 'EnterasysOS',
oneos => 'OneOS',
generic => 'generic',
);
my %Attribute = (
Global => [
'family_type',
'is_nncli',
'is_acli',
'model',
'sw_version',
'fw_version',
'slots',
'ports',
'sysname',
lib/Control/CLI/Extreme.pm view on Meta::CPAN
$promptType = $login->{family_type};
}
}
}
else { # A family type has not been detected yet; try and detect from received prompt
foreach my $key (@InitPromptOrder) {
if ($self->{POLL}{local_buffer} =~ /($InitPrompt{$key})/) {
($capturedPrompt, $switchName, $login->{cpu_slot}, $configContext) = ($1, $2, $3, $4);
$promptType = $key;
($login->{family_type} = $key) =~ s/_(\w+)$//;
$cliType = $1;
$login->{detectionFromPrompt} = 1;
last;
}
}
}
if ($capturedPrompt) { # We have a prompt, we can exit loop
$self->debugMsg(8,"\nlogin() Got CLI prompt for family type $login->{family_type} !\n");
$capturedPrompt =~ s/^\x0d//; # Remove initial carriage return if there
$capturedPrompt =~ s/\x0d$//; # Remove trailing carriage return if there (possible if we match on not the last prompt, as we do /m matching above
if ($login->{family_type} eq $Prm{slx}) { # SLX with vt100 spoils the 1st prompt, correct it here
$capturedPrompt =~ s/^\e\[\?7h//;
$switchName =~ s/^\e\[\?7h//;
}
$self->_setDevicePrompts($promptType, $switchName);
$self->_setLastPromptAndConfigContext($capturedPrompt, $configContext);
$self->_setAttrib('cpu_slot', $login->{cpu_slot}) if $login->{family_type} eq $Prm{pers};
if ($login->{detectionFromPrompt}) {
if ($login->{family_type} eq $Prm{bstk} || (defined $cliType && $cliType eq 'nncli')) {
$self->_setAttrib('is_nncli', 1);
}
else {
$self->_setAttrib('is_nncli', 0);
}
}
last LOGINLOOP;
}
# Now try and match other prompts expected to be seen at the very end of received input stream
if ($self->{POLL}{read_buffer} =~ /$usernamePrompt/) { # Handle Modular login prompt
$self->debugMsg(8,"\nlogin() Matched Login prompt\n\n");
$pattern = 'username';
}
elsif ($self->{POLL}{read_buffer} =~ /$passwordPrompt/) { # Handle Modular password prompt
$self->debugMsg(8,"\nlogin() Matched Password prompt\n\n");
$pattern = 'password';
}
# Now handle any pattern matches we had above
if ($pattern eq 'oneosbanner') { # We end up here when, sometimes, OneOS puts the prompt before the banner
$self->print(); # So we need to force a new prompt for login to succeed
next;
}
elsif ($pattern eq 'banner' || $pattern eq 'bell') { # We got the banner, send a CTRL-Y to get in
$self->debugMsg(8,"\nlogin() Processing Stackable Banner\n\n");
$self->put(string => $CTRL_Y, errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to send CTRL-Y sequence // ".$self->errmsg));
next;
}
elsif ($pattern eq 'menu') { # We got the menu, send a 'c' and get into CLI
$self->debugMsg(8,"\nlogin() Processing Stackable Menu\n\n");
$self->put(string => 'c', errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to select 'Command Line Interface...' // ".$self->errmsg));
next;
}
elsif ($pattern eq 'submenu') { # We are in a sub-menu page, send a 'CTRL_C' to get to main menu page
$self->debugMsg(8,"\nlogin() Processing Stackable Sub-Menu page\n\n");
$self->put(string => $CTRL_C, errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to go back to main menu page // ".$self->errmsg));
next;
}
elsif ($pattern =~ /^more\d$/) { # We are connecting on the console port, and we are in the midst of more-paged output
$self->debugMsg(8,"\nlogin() Quitting residual more-paged output for serial port access\n");
$self->put(string => 'q', errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to quit more-paged output found after serial connect // ".$self->errmsg));
next;
}
elsif ($pattern =~ /^consoleLogMsg\d$/) { # We are connecting on the console port, and this log message is spoiling our 1st prompt
$self->debugMsg(8,"\nlogin() Sending extra carriage return after password for serial port access\n");
# On Modular VSPs Console port, immediately after login you get log message :SW INFO user rwa connected via console port
# As this message is appended to the very 1st prompt, we are not able to lock on that initial prompt
# So we feed an extra carriage return so that we can lock on a fresh new prompt
$self->print(errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to get new prompt after console log message // ".$self->errmsg));
next;
}
elsif ($pattern eq 'lastlogin') { # Last login splash screen; skip it with RETURN key
# This screen appears on ERS4800 release 5.8
$self->debugMsg(8,"\nlogin() Processing Last Login screen\n\n");
$self->print(errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to send Carriage Return // ".$self->errmsg));
next;
}
elsif ($pattern eq 'username') { # Handle login prompt
$self->debugMsg(8,"\nlogin() Processing Login/Username prompt\n\n");
if ($login->{login_attempted}) {
$self->{LOGINSTAGE} = 'username';
return $self->poll_return($self->error("$pkgsub: Incorrect Username or Password"));
}
unless ($login->{username}) {
if ($self->{TYPE} eq 'SSH') { # If an SSH connection, we already have the username
$login->{username} = $self->{USERNAME};
}
else {
unless ($login->{prompt_credentials}) {
$self->{LOGINSTAGE} = 'username';
return $self->poll_return($self->error("$pkgsub: Username required"));
}
$login->{username} = promptCredential($login->{prompt_credentials}, 'Clear', 'Username');
}
}
$self->print(line => $login->{username}, errmode => 'return')
or return $self->poll_return($self->error("$pkgsub: Unable to send username // ".$self->errmsg));
$self->{LOGINSTAGE} = '';
$login->{login_attempted} = 1;
next;
}
elsif ($pattern eq 'password') { # Handle password prompt
$self->debugMsg(8,"\nlogin() Processing Password prompt\n\n");
if ($login->{password_sent}) {
$self->{LOGINSTAGE} = 'password';
return $self->poll_return($self->error("$pkgsub: Incorrect Username or Password"));
}
unless (defined $login->{password}) {
unless ($login->{prompt_credentials}) {
$self->{LOGINSTAGE} = 'password';
return $self->poll_return($self->error("$pkgsub: Password required"));
lib/Control/CLI/Extreme.pm view on Meta::CPAN
If using the connect() method in non-blocking mode, the following example illustrates how this works:
$ok = $obj->connect(Host => $ip-address, Blocking => 0);
until ($ok) { # This loop will be executed while $ok = 0
<do other stuff here..>
$ok = $obj->connect_poll;
}
Or, if you have set an error mode action of 'return':
$ok = $obj->connect(Host => $ip-address, Blocking => 0, Errmode => 'return');
die $obj->errmsg unless defined $ok; # Error connecting
until ($ok) { # This loop will be executed while $ok = 0
<do other stuff here..>
$ok = $obj->connect_poll;
die $obj->errmsg unless defined $ok; # Error or timeout connecting
}
=item B<login() & login_poll()> - handle login for Telnet / Serial port; also set the host CLI prompt
$ok = $obj->login(
[Username => $username,]
[Password => $password,]
[Prompt_credentials => $flag,]
[Timeout => $secs,]
[Read_attempts => $numberOfLoginReadAttemps,]
[Data_with_error => $flag,]
[Wake_console => $string,]
[Blocking => $flag,]
[Errmode => $errmode,]
[Non_recognized_login => $flag,]
[Generic_login => $flag,]
);
($ok, $output || $outputRef) = $obj->login(
[Username => $username,]
[Password => $password,]
[Prompt_credentials => $flag,]
[Timeout => $secs,]
[Return_reference => $flag,]
[Read_attempts => $numberOfLoginReadAttemps,]
[Data_with_error => $flag,]
[Wake_console => $string,]
[Blocking => $flag,]
[Errmode => $errmode,]
[Non_recognized_login => $flag,]
[Generic_login => $flag,]
);
Polling method (only applicable in non-blocking mode):
$ok = $obj->login_poll();
($ok, $output || $outputRef) = $obj->login_poll();
This method handles login authentication for Telnet and Serial port access (also for SSH access in the case of the WLAN2300 WSS controllers, since they use no SSH authentication but instead use an interactive login once the SSH connection is establis...
On success the method returns a true (1) value. On failure the error mode action is performed. See errmode().
In non-blocking mode (blocking disabled) the login() method will most likely immediately return with a false, but defined, value of 0. You will then need to call the login_poll() method at regular intervals until it returns a true (1) value indicatin...
In the first form only a success/failure value is returned in scalar context, while in the second form, in list context, both the success/failure value is returned as well as any output received from the host device during the login sequence; the lat...
This method internally uses the readwait() method and by default sets the read_attemps for it to 10 (which is a safe value to ensure proper connection to any Extreme Networking device); the read_attempts argument provided by login() can be used to ov...
Once a valid Extreme Networking CLI prompt is detected (using pre-configured pattern match strings), this method records the actual CLI prompt of the host device for the remainder of the session by automatically invoking the prompt() method with a ne...
At the same time this method will also set the --more-- prompt used by the device when paging output as well as a number of attributes depending on what family_type was detected for the host device. See attribute().
Note that this method is automatically invoked by the connect() method and therefore should seldom need to be invoked by itself. A possible reason to invoke this method on its own could be if initially connecting to, say, an ERS8800 device and from t...
# Initial connection could use Telnet or SSH, depending on how object was constructed
# Connect to 1st device, e.g. via out-of-band mgmt
$cli->connect(
Host => '<ERS8800 IP address>',
Username => 'rwa',
Password => 'rwa',
);
# From there connect to another device, perhaps on inband mgmt
# NOTE: use print() not cmd() as there is no prompt coming back, but the login screen of the stackable
$cli->print("telnet <Stackable IP address>");
# Call login() to authenticate, detect the device, reset appropriate attributes
$cli->login(
Username => 'RW',
Password => 'RW',
);
# Execute commands on target stackable device
$output = $cli->cmd("show running-config");
print $output;
[...]
# If you want to return to the first device..
# NOTE: use print() not cmd() as the next prompt will be from the ERS8800, not the stackable anymore
$cli->print("logout");
# Call login() to detect the device and reset appropriate attributes (no authentication needed though)
$cli->login;
# Now we are back on the 1st device
$output = $cli->cmd("show sys info");
print $output;
[...]
If using the login() method in non-blocking mode, the following examples illustrate how this works:
=over 4
=item *
If you do not care to retrieve the login sequence output:
$ok = $obj->login(Username => "admin", Password => "pwd", Blocking => 0);
until ($ok) { # This loop will be executed while $ok = 0
<do other stuff here..>
$ok = $obj->login_poll;
}
=item *
If you want to retrieve the login output sequence along the way (even in case of error/timeout):
( run in 0.592 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )