Expect crashing running exec command - expect

I have an expect script that performs an exec that can take some time (around 5 mins).
I have copied the script below and also the output from running the script.
If the script was timing out, I would have thought "timeout" was printed to std out?
Any pointers will be appreciated!
expect <<EOF
cd /home/vagrant/cloudstack
# 20 mins timeout for jetty to start and devcloud to be provisioned
set timeout 1200
match_max 1000000
set success_string "*Started Jetty Server*"
spawn "/home/vagrant/cloudstack_dev.sh" "-r"
expect {
-re "(\[^\r]*\)\r\n"
{
set current_line \$expect_out(buffer)
if { [ string match "\$success_string" "\$current_line" ] } {
flush stdout
puts "Started provisioning cloudstack."
# expect crashes executing the following line:
set exec_out [exec /home/vagrant/cloudstack_dev.sh -p]
puts "Finished provisioning cloudstack. Stopping Jetty."
# CTRL-C
send \003
expect eof
} else {
exp_continue
}
}
eof { puts "eof"; exit 1; }
timeout { puts "timeout"; exit 1; }
}
EOF
The output:
...
2014-03-14 06:44:08 (1.86 MB/s) - `/home/vagrant/devcloud.cfg' saved [3765/3765]
+ python /home/vagrant/cloudstack/tools/marvin/marvin/deployDataCenter.py -i /home/vagrant/devcloud.cfg
+ popd
+ exit 0
while executing
"exec /home/vagrant/cloudstack_dev.sh -p"
invoked from within
"expect {
-re "(\[^\r]*\)\r\n"
{
set current_line $expect_out(buffer)
if { [ string match "$success_string" "$current_line" ]..."
The function that gets run inside the cloudstack-dev.sh:
function provision_cloudstack () {
echo -e "\e[32mProvisioning Cloudstack.\e[39m"
pushd $PWD
if [ ! -e $progdir/devcloud.cfg ]
then
wget -P $progdir https://github.com/imduffy15/devcloud/raw/v0.2/devcloud.cfg
fi
python /home/vagrant/cloudstack/tools/marvin/marvin/deployDataCenter.py -i $progdir/devcloud.cfg
popd
}
From the Expect output, it seems as though the function is being run ok.

See http://wiki.tcl.tk/exec
the exec call by default returns an error status when the exec'ed command:
returns a non-zero exit status, or
emits any output to stderr
This second condition can be irksome. If you don't care about stderr, then use exec -ignorestderr
You should always catch an exec call. More details in the referenced wiki page, but at a minimum:
set status [catch {exec command} output]
if {$status > 0} {
# handle an error condition ...
} else {
# success
}

Related

How to pass a bash array into expect

I have a shell script that passes a array variable to expect. But, in the expect part, it only takes the first argument and says "couldn't read file --server: no such file or directory"
below is the example program:-
Here i want the complete value of ${CMPREQUEST_ARGS[#]} which is
cmpclient --ir --server 10.10.10.10 --port 4040
CMPREQUEST=($CMPCLIENT "${CMPREQUEST_ARGS[#]}")
echo "TEST:${CMPREQUEST[#]}:TEST" //echo prints the value of ${CMPREQUEST[#]} correctly.
expect -c "
log_file -noappend -a \"/srv/Log/log/cmpclient-$app_id.log\"
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn \${CMPREQUEST[#]}
expect {
-re \"SUCCESS\:\ write\ X509\" {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
"
exit $?
I am getting this error in spawn
couldn't read file --server: no such file or directory
Please guide..Any help would be highly appreciated.
Thanks in advance!
This boils down to quoting hell. Use a shell here-doc to hold the expect code, then you don't need to escape all the "interior" quotes. Your main issue is the spawn command, where you are preventing the shell from expanding the array.
Try this:
expect <<END_EXPECT
log_file -noappend -a /srv/Log/log/cmpclient-$app_id.log
log_user 1
set RET_VAL 1
set timeout 86400
puts "TEST2:${CMPREQUEST[#]}:TEST2"
spawn ${CMPREQUEST[#]}
expect {
-re {SUCCESS: write X509} {
set RET_VAL 0
}
timeout { set RET_VAL 1 }
}
exit \$RET_VAL
END_EXPECT
Only the expect variable RET_VAL needs to be protected from the shell.

handling tcl expect application crash

Is it possible to spawn an application, send commands, expect results, but also 'respawn' the application in case it crashed and continue from the last point? I tried creating procedures for spawning, but I am not able to catch user shell prompt once the application gets closed
So it sounds like you are doing something like telneting or sshing
into a shell then running an application. The application dies or
hangs so the prompt does not return so expect does not return and so
you are stuck. You will need to use timeout to detect a hang and restart
your entire process ( including the spawn). Below is the boiler plate I start with when writing
expect scripts. The critical thing to help you resolve your problem
is to realize that spawn not only sets the spawn_id but also returns
the pid of the spawned process. You can use that pid to kill the
spawned process if you get an eof and/or a timeout. My boiler plate
kills the spawn process on timeout. The timeout does not bail from the
expect loop but waits for the eof before exiting. it also accumulates
the output of expect command so on timeout you may be able to see
where it dies. The boilerplate is in a proc called run. Spawn the process
and pass the pid and the spawn id . Reuse the boiler plate to define other
procs that will be the steps. put the procs in a script with a counter between
them as shown and repeat. The other answerer is correct that the unless the app
restarts where you left off you can need to start from scratch. If it does start from
where you left off. make the steps granular enough that you know what command
to repeat. and step to start from.
BoilerPlate
proc run { pid spawn_id buf } {
upvar $buf buffer; #buffer to accumulate output of expect
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
expect {
-i $spawn_id
-re {} {
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
timeout {
send_user "timeout\n"
append buffer $expect_out(buffer); # accumultate expect output
exec kill -9 $pid
set bad 1
exp_continue;
}
fullbuffer {
send_user " buffer is full\n"
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
eof {
send_user "Eof detected\n"
append buffer $expect_out(buffer); # accumultate expect output
set done 1 ;
}
}
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
if { $bad } {
if { $done } {
throw EXP_TIMEOUT "Application timeout"
}
throw BAD_ERROR "unexpected failure "
}
return $exitstatus
}
set count 0
set attempts 0 ; # try 4 times
while { $count == 0 && $attempts < 4 } {
set buff ""
set pid [spawn -noecho ssh user#host ]
try {
run $pid $::spawn_id buff
incr count
run2 $pid $::spawn_id buff
incr count
run3 $pid $::spawn_id buff
incr count
run4 $pid $::spawn_id buff
incr count
} trap EXP_TIMEOUT { a b } {
puts "$a $b"
puts " program failed at step $count"
} on error { a b } {
puts "$a $b"
puts " program failed at step $count"
} finally {
if { $count == 4 } {
puts "success"
} else {
set count 0
incr attempts
puts "$buff"
puts "restarting\n"
}
}
}

Expect in Alias

I am using a Bash alias that allows me to shorten the SSH command in order for me to log into my routers. Quite trivial, but a time saver! What I would now like to do is take this a step further and fully automate the logging-in of the routers.
For example in my ~/.bashrc file I have the following entry:
sshFuncB()
{
ssh -o StrictHostKeyChecking=no superuser#$1 - | /usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
"password: " { send -h "MYPASSWORD\r" }
"No route to host" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
"N]?" { send "y\r"; exp_continue }
timeout { exit 1 }
}
expect eof
EOF
}
alias z=sshFunc
However, when I type z myrouterhostname this does not give the desired output. I must find a way to start the SSH connection and have expect automate logging in before returning control to user.
Any ideas?
This can be done as follows,
sshFuncB()
{
expect -c "
spawn ssh -o StrictHostKeyChecking=no superuser#$1
set timeout 5
set send_human {.1 .3 1 .05 2}
expect {
\"password: \" { send -h \"MYPASSWORD\r\" }
\"No route to host\" { exit 1 }
timeout { exit 1 }
}
set timeout 2
sleep 1
expect {
\"N]?\" { send \"y\r\"; exp_continue }
timeout { exit 1 }
}
expect eof
"
}
alias z=sshFuncB
Note the use of -c flag in expect which you can refer from here of you have any doubts.
If we use double quotes for the expect code with -c flag, it will allow the bash substitutions. If you use single quotes for the same, then bash substitutions won't work. (You have used #1 inside expect, which is why I used double quotes) Since I have used double quotes for the whole expect code, we have to escape the each double quotes with backslash inside the expect statement like as follows,
expect {
# Escaping the double quote with backslash
\"password: \" {some_action_here}
}
One more update. Since this is about connecting to the router and do some of your manual operations, then it is better to have interact at the end.

Script not logging to log file. Why?

I have an expect/Tcl script as part of my bash script that logs into a remote router. Now, for testing purposes I am trying to handle the issue of time-out's. My problem is that the expect/Tcl script is not logging to my log file, and when it does it is logging everything the SSH connection is printing to my prompt which is not what I want.
Here's my expect script:
/usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
set myStamp [exec date +\[%d\/%m\/%Y\ \%T\]]
set log_file ~/mylogfile.log
spawn ssh -o "StrictHostKeyChecking no" "me\#$1"
expect {
"password: " { send -h "mypassword\r" }
"No route to host" { exit 1 }
timeout { send_log "\$myStamp Timed out to $1\n"]; exit 1 }
}
send -h "reboot in 1\r"
sleep 1
send -h "exit\r"
expect eof
EOF
Please bear in mind that this is part of a function within my bash script that is passed the router name, hence the argument $1.
Any ideas?
You want to use the log_file command, not set a log_file variable
log_file ~/mylogfile.log
Other notes:
Tcl has a very nice builtin command to handle time, don't need to call out to date:
set myStamp [clock format [clock seconds] -format {[%d/%m/%Y %T]}]
the # character is not special in Tcl/expect and does not need to be escaped:
spawn ssh -o "StrictHostKeyChecking no" "me#$1"
As noted, log_file logs a transcript of the session. Just to log specific messages, you can use plain Tcl:
/usr/bin/expect <<EOF
proc log_msg {msg {to_stdout no}} {
set log_line "[timestamp -format {[%Y-%m-%d %T]}] \$msg"
set fh [open ~/mylogfile.log a]
puts \$fh \$log_line
close \$fh
if {\$to_stdout} {puts \$log_line}
}
# ...
expect {
"No route to host" {
log_msg "No route to host" yes
exit 1
}
timeout { log_msg "Timed out to $1"]; exit 1 }
}
# ...
EOF
This opens and closes the log for each message, which adds a bit of overhead. If milliseconds are important, open the log in the global scope, and use the global variable holding the file hendle in the log_msg proc.

"expect" match "send" string, why?

I found that expect command just after send command match data from send command.
Let see, my.sh:
#!/bin/sh
read line
echo ok
my.exp (some code is redundant, I emulate DejaGNU testing framework...):
set passn 0
proc pass {msg} { global passn; incr passn; send_user "PASS: $msg\n" }
set failn 0
proc fail {msg} { global failn; incr failn; send_user "FAIL: $msg\n" }
proc check {} {
global passn failn;
send_user "TOTOAL: [expr $passn + $failn], PASSED: $passn, FAIL: $failn\n"
if {$failn == 0} { exit 0 } { exit 1 }
}
set timeout 1
spawn ./my.sh
send hello\n
expect {
-ex hello {
send_user "~$expect_out(0,string)~\n"
pass hello
}
default { fail hello }
}
expect {
-ex ok { pass ok }
default { fail ok }
}
check
When run expect my.exp I get:
spawn ./my.sh
hello
~hello~
PASS: hello
ok
PASS: ok
TOTOAL: 2, PASSED: 2, FAIL: 0
I don't understand why hello matched!! Please tell me. I already reread:
http://expect.sourceforge.net/FAQ.html
expect works with
pseudoterminal devices. They are
much like normal terminals: unless you disable echo (using expect's
stty command), whatever you send is also visible as your "terminal
output" (which is expect's input).
stty_init variable is used by expect to set up newly created
pseudoterminals. If you add set stty_init -echo to the beginnig of
my.exp, the test will begin to fail.

Resources