387 lines
10 KiB
Perl
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;
|