Expect::Angel - Build up a robust connection class to your DUT (Router/Switch/Host)
use Expect::Angel; @ISA = ("Expect::Angel"); sub new { my $type = shift; my $para = {@_}; my $conn = $type->build(%$para); ... ... return $conn; } # move to config mode of the device sub configMode { my ($conn) = shift; $conn->movState("config") or return undef; } # at your derived module, you can easily do the following jobs. # capture output of a command execution my @ints = $conn->cmdexe("show int brief"); # executes a list of commands at "config" mode of Cisco device my @cmd = ("interface e0/0", "ip address 10.1.1.10 255.2555.255.0", "no shut", "exit", "interface e0/1", "ip address 10.1.2.100 255.2555.255.0", "no shut" ); $conn->cmdexe(\@cmd, "config"); # always stay at config mode for each command execution, although # existing some command that may change mode inadvertently. $conn->sticky(1); my @cmd = ("interface e0/0", "ip address 10.1.1.10 255.2555.255.0", "end", # NOTE, this cause the transition to "enable" mode "interface e0/1", "ip address 10.1.2.100 255.2555.255.0", "no shut" ); $conn->cmdexe(\@cmd, "config");
If you are looking for a module that can help you to quickly build up a robust connection to your DUT (Device under Test), here is the right place!
Angel is just like a messenger between your testing machine, where everyting is perfect like heaven, and DUT, where exists right and wrong like earth. The messenger must tolerate the errors and disasters occuring on DUT and faithful deliver information to and from the DUT.
Angel is built on Expect.pm module, but hides the complexity of it. With Angel.pm You can easily build an object-oriented module that meets your specific need with the most useful methods inherited from Angel.
Let's take CISCO router as the first example.
CISCIO Router Modes: Non-privilege --- (enable) ---> Enable Mode -- (config t) --> Config Mode <-- (exit) ------ <-- (exit) -----
Here the router has 3 modes, Non-privilege, Enable, and Config. When executing enable command at 1st mode, it transits to Enable mode, and so on as shown.
General speaking, DUT is a network appliance that provides CLI (command line interface) for user to configure and manupulate. It's very often that a DUT presents multiple states, each grants a level of privileges.
DUT: state0 ----> state1 -----> state2 ... ... -----> stateN <---- <----- <-----
Each state is a stable mode of the DUT, at which the DUT may accept an input and execute it as a command. Each state has its own prompt and a set of commands appropriate to this state. The commands under a state can be put into categories.
1. After execution the state keeps the same. 2. After execution the state transits to another one. 3. The execution will show more prompt asking for more input, and eventually back to the some state.
The state transition may happen between two adjacent states, or skip some states.
Your task is to tell Angel the following information for a specific DUT.
1. describes the states and their prompts 2. describes the state transition
Angel provides you:
1. Maintain the connection to DUT. 2. Send command to DUT at each state and retrieve response of the command. 3. Error handling. 4. Log the messages exchanged.
Function: create the DUT object. - build({ timeout => $in_seconds, # default is 30 seconds errMode => 'die|return|$handler ' # default is die, you can provide your own handler errTries => $number, # default is 3 log => $file, # default is ./Angelfile.log, you can put filename|filehandle debug => 1, # default is 0, Angel debug info debugExp => 1, # default is 0, Expect debug info liveExp => 1, # default is 1, Expect's log_user expRaw => 1, # default is 1, Raw mode of terminal driver. user => user_name # used to connect to DUT. If none provided, will be asked in from keyboard passwd => password # used to connect to DUT. If none provided, will be asked in from keyboard aggressive => 0|1 # default is 0, see explanation below. others => values # see below }) Function: create the DUT object Some parameters are self explained. timeout - after a command is sent to DUT, the response is expected to return without it. Longer than timeout is regarded as a failure and counted in errTries. log - indicates the target of log created by this module and Expect. The log may contain all commands and intermediate prompts between Angel and DUT as well as Expect debug information that are controlled by "debug" and "debugExp". The log message will be written to the file specified by file_name, or File_handle if you have your own log framework. If none is seen, Angelfile.log at current directory is used. aggressive - controls the state transition behavior. There are two ways in transition. - aggressive If transition is defined from a state to another, it will try to move directly. otherwise, try one hop backward of the target. for example, current state = 1, target state = 4 if defined transition to state 4, then go directly; otherwise, check if transition to state 3 is defined; if not, then try state 2, and so on. - non_aggressive (default) similiar to aggressive, but it tries one hop forward from the source i.e. if 1 to 4 fails, it tries to go to 2 first, then 2 to 4. This may affect you state transition design. The design should meet that there is a path from any source state to any target state. liveExp - controls live output from spawned process such as telnet or ssh. it's Expect's log_user(). With this set to 1, you can see all the information exchanged by Angel. debugExp - controls expect debug message. If you turn it on, there will be huge Expect debug messages printed out on screen others: sticky - controls state transition behavior after a command execution, see cmdexe() for detail. you can change it any time. ignoreSecWarn - If you run with debug enabled, the password is printed to log target, This may be a security exposure. So the default behavior is that Angel will print a warning and wait for your confirmation on keyboard to continue. If you don't want this stop, you can set ignoreSecWarn => 1. defPattern - This is the default Expect pattern and action pairs. You can define the most common patterns here, which is added to the Expect body of all state transition and command execution, so that you don't need to include them in each state transition description or command. If you do define the patern at each individule one, it takes precedence over the default pattern. One example is like this defPattern = [ { '[-]+\s*(More|more)\s*[-]+' => ' ', '^.* memory\? *\[confirm\] *$' => '\r'}, { '^.*Are you sure\?.*\[confirm\] *$' => '\r', '^.*Are you sure\?.*$' => 'yes\r', '^.*Are you sure you want to continue connecting\?.*$' => 'yes\r', } ] Its data structure is list of hash like this: [ {}, ... {} ] The list keeps the sequence in the pattern match, while the order does not matter in a hash. In another word, if the pattern match order does matter, you put them in different hash in preferred order, otherwise you can put them in the same hash. noechoback - controls if a command is expected to be echoed back. By default it doesn't exist and the command sent to DUT is expected to be back. This is true for Cisco and Juniper devices. In case your situation doesn't have this behivior, you can set this attribute to any value. see sendCmd() method for more infomation.
Function: to add a state If a value not defined, it will be stateN, where N is "state seqence number" The initial state, which is 0, will use descr => "initial state", prompt => 'MATCHNOTHING" by default. return state sequence number of this state descr: description of this state, used for human readable purpose only. name: name of the state, unique in all states, default states would be 0, 1, 2, etc. State is identified by either the sequence number (0 is the initial state), or the name. prompt: prompt of this state, in perl regular expression After the call, a state data structure is appended to state list. state => [ # no.0 state { name => "state_name", # must have letter, state0 is the default # for 'initial state. descr => "Printable strings" # purely for human readable # 'initial state' for 1st state by default prompt => reg(RE); # regular expression of the state # this is to match prompt of the socket }, # more state { }, ]
Add a transition definition from $from state to $to state. $from: source state name or number $to: destination state name or number $transDefinition: ref to hash body defining how to do the transition return: previous transition definition from $from to $to if defined, or undef $transDefinition is defined like this { command => "executable command at the state", expect => [ { hint/answer block }, ... {} ] } or { command => [("one command","another command", ... ,"last cmd")], expect => [ { hint/answer block }, ... , {} ] } hint/answer bloc could be none, {}, means no interactive action duration transition hint => reg(RE); # regular expression for system response to go to that state # password is one example answer => "answer to the hint in order to go that state", "\r" or "\n" is auto added or { nexthop => state_name or number } They are mutual exclusive. If both exists, nexthop will take precedence, i.e, try to go to the state first, then make follow whatever defined at that state. Note to $transDefinition -- This ref is directly assigned to the internal data structure without deep copy. It is caller's responsibility to assign a new memory location for each call as shown above.
Print all the defined states for debug. Note, the credential may be printed out by this
Move to $state from whatever current state is. It will try its best anyway up to errTries times. After the first time fails, it moves state to initial state, then go to $state afterwards. If errTries times has been tried and still failed, it keeps the best achieved state and return undef. return: true if successful, new current state = $state undef otherwise
set or get sticky attribute of the object. sticky controls state transition behavior after a command execution, see cmdexe() for detail. input: optional, true|false, it's set if provided, otherwise it returns the current value.
Close the socket and set the object to initial state. It doesn't try to go over the state transition from current to 0 like movState(0) does, instead, it directly calls soft_close() of Expect to close the connection. But it keeps all the object properties and state transition date, so it helps to re-transit to a state from initial in case it has experienced a problem in previous attempt. If a decent goodbye is required, movState(0) is the best solution.
Execute the command(s) cmd: scalar or list, means one command or a list of command to be executed expect: optional, ref to list of hash that describes the interactive expect body for this/these commands. [ { pattern1 => action1 }, ... {} ] If cmd is a list, then expect will be used in execution of all the commands in the list. state: optional, specifies the state at which the command(s) to be executed. Default is current state. If specified, cmdexe will go to the state and execute the command(s) return: the output of the last command execution. In list context it returns line by line, without \n, in scalar context, it returns the string of the output. Notes: The starting state is either specified by $state or current state. The state may change during the cmd execution. If object's "sticky" is set, it always tries to go back to starting state after each cmd execution. Otherwise, it leaves whatever state it is cuased by the cmd execution. So this attribute affects the rest of command(s). In case error happens when a command is executed, it will try errTries times (defualt is 3), each time it moves state back to 0, and goes to starting state, and executes the command again. The state transition failure is counted in errTries. The errTries is reset for each command. If errTries times has been tried and still failed, it takes action defined by errMode, i.e, die, return, or exectues a code, then return false condition.
send $cmd and check if the command is complained by device. Some device rejects a command and feeds back some error message. If you want to detect this, you can monitor the error message pattern from the real device. For example, Cisco switch prints something like "% error_message". Then, you specifiy $errorPattern, which is a regular expression in this method call. This method will exectues $cmd at current state and detect if $errorPattern matches its output in each execution. If it is, meaning the command is rejected, it will return immediatedly with undef. Otherwise it continues and return the real output of the execution of the last command. $cmd: single or ref to a list of commands. $errorPattern: RE of error pattern, like '^%' in cisco switch. $interactiveBody: optional, same as $expect defined in cmdexe() method, please see cmdexe(). return: undef when $errorPattern matches, otherwise output of the last command
This method is deprecated in this version. Use cmdexe() instead. It executes $cmd at current state, and return the response of DUT in either array, or ref of the array. It always expects current state's prompt after executes $cmd. If a command may cause state transition, it's safe to use cmdexe(), which will tolerate any state match. some $cmd need to interact with DUT in terms that DUT waits for some input after accepting $cmd. Multiple prompt phrases and answers may be exchanged before the $cmd is done and final prompt shows up. One example is like '--More--' is shown up during the output, and this case can be defined in def_pattern, which will be taken care of in the interactive way during this process $expect defines these interactive exchanges specifically for this $cmd. {"prompt phrase1 => answer1", ... , "prompt phraseN => answerN"} where "prompt phraseN (N = 1 ... N)" can be expressed in Regular Expression In case error happens when $cmd is executed, it will try errTries times (defualt is 3), each time it moves state back to 0, and goes to current state, and executes the $cmd again. If errTries times has been tried and still failed, it takes action defined by errMode, i.e, die, return, or exectues a code, then return false condition. The response from the execution of the command $cmd is returned, each line is an element. Depending on the context to assign, it either returns reference or the list itself. # this will return a list and assigned to @msg; @msg = $conn->echo("ls -l"); # this will return ref to a list and assigned to $msg; $msg = $conn->echo("ls -l");
This method is deprecated in this version. Use cmdSendCheck() instead. Send an command to DUT may encounter 3 situations 1. the command is slurped by DUT without rejection, the action success. 2. the command is rejected by DUT with error message, the action is failed 3. DUT is crashed by the command, the action cause catastrophe This method detects the situation and return true on No.1, false on No.2, and undef on No.3 $retMsgPt is a ref to list, the response of DUT to $cmd is assiged to it. $rejectPattern is RE that tells the matched response is regarded as rejection message from DUT. If the No.3 happens, $retMsgPt will hold the error message.
Send $cmd and check the echo back of $cmd After sending $cmd, it checks the echo-back of $cmd. If it sees echo-back, the operation is regarded as success and returns 1. Otherwise by default it will try another time with slow speed to see if this can be done, unless $retry is provided. If all tries failed, it die out.
This method is necessary for two reasones. - Make sure the connection is good - The command itself is eliminated from the output of the command.
This process can be skipped if your connection doesn't echo back command, in such case, you must set the object attribute "noechoback";
input: $cmd - command to be executed $timeout - optional, default is the class's timeout, see build() timeout to wait for the echo-back happens. $retry - optional, default is 2, the number of repeat to make it success.
Expect::Angel::Cisco Expect::Angel::Juniper Expect::Angel::Linux
2007 verison 0.02 2010 verison 1.00
Ming Zhang <ming2004@gmail.com<gt>
Copyright (C) 2010 by ming zhang
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.5 or, at your option, any later version of Perl 5 you may have available.
To install Expect::Angel, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Expect::Angel
CPAN shell
perl -MCPAN -e shell install Expect::Angel
For more information on module installation, please visit the detailed CPAN module installation guide.