386 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			9.9 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
 | 
						|
	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;
 |