xcat-core/xCAT-server/lib/perl/xCAT/RShellAPI.pm

387 lines
10 KiB
Perl

#!/usr/bin/perl
# IBM(c) 2012 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::RShellAPI;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use xCAT::MsgUtils;
#use Data::Dumper;
#######################################################
=head3
remote_shell_command
This routine constructs an remote shell command using the
given arguments
Arguments:
$class - Calling module name (discarded)
$config - Reference to remote shell command configuration hash table
$exec_path - Path to ssh executable
Returns:
A command array for the ssh command with the appropriate
arguments as defined in the $config hash table
=cut
#####################################################
sub remote_shell_command {
my ( $class, $config, $exec_path ) = @_;
#print Dumper($config);
my @command = ();
push @command, $exec_path;
if ( $$config{'options'} ) {
my @options = split ' ', $$config{'options'};
push @command, @options;
}
my @tmp;
if ( $$config{'trace'} ) {
push @command, "-v";
}
if ( $$config{'remotecmdproto'} && ($$config{'remotecmdproto'} =~ /^telnet$/)) {
push @command, "-t";
}
if ($$config{'user'} && ($$config{'user'} !~ /^none$/i)) {
@tmp=split(' ', "-l $$config{'user'}");
push @command, @tmp;
}
if ($$config{'password'} && ($$config{'password'} !~ /^none$/i)) {
@tmp=split(' ', "-p $$config{'password'}");
push @command, @tmp;
}
push @command, "$$config{'hostname'}";
push @command, $$config{'command'};
return @command;
}
##################################################################
=head3
run_remote_shell_api
This routine tried ssh then telnet to logon to a node and
run a sequence of commands.
Arguments:
$node - node name
$user - user login name
$passed - user login password
$cmds - a list of commands seperated by semicolon.
Returns:
[error code, output]
error code: 0 sucess
non-zero: failed. the output contains the error message.
=cut
#########################################################################
sub run_remote_shell_api {
require xCAT::SSHInteract;
my $node=shift;
my $user=shift;
my $passwd=shift;
my $telnet=shift;
my $verbose=shift;
my $args = join(" ", @_);
my $t;
my $prompt='.*[\>\#\$]\s*$';
my $more_prompt='(.*key to continue.*|.*--More--\s*|.*--\(more.*\)--.*$)';
my $output;
my $errmsg;
my $ssh_tried=0;
my $nl_tried=0;
if (!$telnet) {
eval {
$output="start SSH session...\n";
$ssh_tried=1;
$t = new xCAT::SSHInteract(
-username=>$user,
-password=>$passwd,
-host=>$node,
-nokeycheck=>1,
-output_record_separator=>"\r",
Timeout=>10,
Errmode=>'return',
Prompt=>"/$prompt/",
);
};
$errmsg=$@;
$errmsg =~ s/ at (.*) line (\d)+//g;
$output.="$errmsg\n";
}
my $rc=1;
if ($t) {
#Wait for command prompt
$t->print("\t");
my ($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
Match => '/username[: ]*$/i',
Match => '/password[: ]*$/i',
Match => "/$prompt/",
Errmode => "return");
if ($verbose) {
print "0. prematch=$prematch\n match=$match\n";
}
if ($match !~ /$prompt/) {
return [1, $output];
}
} else {
#ssh failed.. fallback to a telnet attempt
if ($ssh_tried) {
$output.="Warning: SSH failed, will try Telnet. Please set switches.protocol=telnet next time if you wish to use telnet directly.\n";
}
$output.="start Telnet session...\n";
require Net::Telnet;
$t = new Net::Telnet(
Timeout=>10,
Errmode=>'return',
Prompt=>"/$prompt/",
);
$rc = $t->open($node);
if ($rc) {
my $pw_tried=0;
my $login_done=0;
my ($prematch, $match)= $t->waitfor(Match => '/login[: ]*$/i',
Match => '/username[: ]*$/i',
Match => '/password[: ]*$/i',
Match => "/$prompt/",
Errmode => "return");
if ($verbose) {
print "1. prematch=$prematch\n match=$match\n";
}
if ($match =~ /$prompt/) {
$login_done=1;
} elsif (($match =~ /username[: ]*$/i) || ($match =~ /login[: ]*$/i )) {
# user name
if ($user) {
if (! $t->put(String => "$user\n",
Errmode => "return")) {
$output.="login disconnected\n";
return [1, $output];
}
} else {
$output.="Username is required.\n";
return [1, $output];
}
} elsif ($match =~ /password[: ]*$/i) {
if ($passwd) {
$pw_tried=1;
if (! $t->put(String => "$passwd\n",
Errmode => "return")) {
$output.="Login disconnected\n";
return [1, $output];
}
} else {
$output.="Password is required.\n";
return [1, $output];
}
}
if (!$login_done) {
($prematch, $match)= $t->waitfor(Match => '/login[: ]*$/i',
Match => '/username[: ]*$/i',
Match => '/password[: ]*$/i',
Match => "/$prompt/",
Errmode => "return");
if ($verbose) {
print "2. prematch=$prematch\n match=$match\n";
}
if ($match =~ /$prompt/) {
$login_done=1;
} elsif (($match =~ /username[: ]*$/i) || ($match =~ /login[: ]*$/i )) {
$output.="Incorrect username.\n";
return [1, $output];
} elsif ($match =~ /password[: ]*$/i) {
if ($pw_tried) {
$output.="Incorrect password.\n";
return [1, $output];
}
if ($passwd) {
if (! $t->put(String => "$passwd\n",
Errmode => "return")) {
$output.="Login disconnected\n";
return [1, $output];
}
} else {
$output.="Password is required.\n";
return [1, $output];
}
} else {
# for some switches like BNT, user has to type an extra new line
# in order to get the prompt.
if ($verbose) {
print " add a newline\n";
}
$nl_tried=1;
if (! $t->put(String => "\n",
Errmode => "return")) {
$output.="Login disconnected\n";
return [1, $output];
}
}
if (!$login_done) {
#Wait for command prompt
($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
Match => '/username[: ]*$/i',
Match => '/password[: ]*$/i',
Match => "/$prompt/",
Errmode => "return");
if ($verbose) {
print "3. prematch=$prematch\n match=$match\n";
}
if ($match =~ /$prompt/) {
$login_done=1;
} elsif ($match =~ /login[: ]*$/i or $match =~ /username[: ]*$/i or $match =~ /password[: ]*$/i) {
$output.="Login failed: bad login name or password\n";
return [1, $output];
} else {
if (!$nl_tried) {
# for some switches like BNT, user has to type an extra new line
# in order to get the prompt.
if ($verbose) {
print " add a newline\n";
}
$nl_tried=1;
if (! $t->put(String => "\n",
Errmode => "return")) {
$output.="Login disconnected\n";
return [1, $output];
}
}
else {
if ($t->errmsg) {
$output.= $t->errmsg . "\n";
return [1, $output];
}
}
}
}
#check if the extra newline helps or not
if (!$login_done) {
#Wait for command prompt
($prematch, $match) = $t->waitfor(Match => "/$prompt/",
Errmode => "return");
if ($verbose) {
print "4. prematch=$prematch\n match=$match\n";
}
if ($match =~ /$prompt/) {
$login_done=1;
} else {
if ($t->errmsg) {
$output.= $t->errmsg . "\n";
return [1, $output];
}
}
}
}
}
}
if (!$rc) {
$output.=$t->errmsg . "\n";
return [1, $output];
}
$rc = 0;
my $try_more=0;
my @cmd_array=split(';', $args);
foreach my $cmd (@cmd_array) {
if ($verbose) {
print "command:$cmd\n";
}
while (1) {
if ($try_more) {
#This is for second and consequent pages.
#if the user disables the paging, then this code will never run.
#To disable paging (which is recommended),
#they need to add a command before any other commands
#For Cisco switch: terminal length 0
#For BNT switch: terminal-length 0
#For example:
# xdsh <swname> --type EthSwitch "terminal length 0;show vlan"
if (! $t->put(String => " ",
Errmode => "return")) {
$output.="Command $cmd failed: " . $t->errmsg() . "\n";
return [1, $output];
}
if ($verbose) {
my $lastline=$t->lastline();
print "---lastline=$lastline\n";
}
($prematch, $match) = $t->waitfor(Match => "/$more_prompt/i",
Match => "/$prompt/",
Errmode => "return",
Timeout=>10);
} else {
# for the first page which may contian all
if (! $t->put(String => "$cmd\n",
Errmode => "return")) {
$output.="Command $cmd failed." . $t->errmsg() . "\n";
return [1, $output];
}
if ($verbose) {
my $lastline=$t->lastline();
print "lastline=$lastline\n";
}
($prematch, $match) = $t->waitfor(Match => "/$more_prompt/i",
Match => "/$prompt/",
Match => '/password:\s*$/i',
Errmode => "return",
Timeout=>10);
}
if ($verbose) {
print "-----prematch=$prematch\nmatch=$match\n";
}
my $error=$t->errmsg();
if ($error) {
$output.="Command $cmd failed: $error\n";
return [1, $output];
}
#
if ($try_more) {
#my @data=split("\n", $prematch);
#shift @data;
#shift @data;
#shift @data;
#$prematch=join("\n", @data);
#add a newline at the end if not there
my $lastchar=substr($prematch, -1, 1);
if ($lastchar ne "\n") {
$prematch .= "\n";
}
}
$output .= $prematch;
if ($match =~ /$more_prompt/i) {
$try_more=1;
} else {
last;
}
}
}
$t->close();
return [0, $output];
}
1;