Skip to content

Commit cdbb1f6

Browse files
authored
plugins/teststeps/sshcmd: Add Timeout and busy waiting (#173)
* plugins/teststeps/sshcmd: Add Timeout and busy waiting 1. Add Timeout to the command 2. Add busy waiting. Busy waiting is needed to catch the expect parameter. Otherwise, if we have a continous command e.g. reading from the command line it will never complete successfully, thus it will also not be parsed for the expect paramter - and thus never success. I added a "dummy" CONT to keep the ssh connection busy - such that it does not close down because of a time out.
1 parent 34f7642 commit cdbb1f6

1 file changed

Lines changed: 67 additions & 19 deletions

File tree

plugins/teststeps/sshcmd/sshcmd.go

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"regexp"
2626
"strconv"
2727
"strings"
28+
"time"
2829

2930
"github.com/facebookincubator/contest/pkg/cerrors"
3031
"github.com/facebookincubator/contest/pkg/event"
@@ -48,6 +49,7 @@ var log = logging.GetLogger("teststeps/" + strings.ToLower(Name))
4849
var Events = []event.Name{}
4950

5051
const defaultSSHPort = 22
52+
const defaultTimeoutParameter = "10m"
5153

5254
// SSHCmd is used to run arbitrary commands as test steps.
5355
type SSHCmd struct {
@@ -59,6 +61,7 @@ type SSHCmd struct {
5961
Executable *test.Param
6062
Args []test.Param
6163
Expect *test.Param
64+
Timeout *test.Param
6265
}
6366

6467
// Name returns the plugin name.
@@ -101,6 +104,18 @@ func (ts *SSHCmd) Run(cancel, pause <-chan struct{}, ch test.TestStepChannels, p
101104
return fmt.Errorf("failed to convert port parameter to integer: %v", err)
102105
}
103106

107+
timeoutStr, err := ts.Timeout.Expand(target)
108+
if err != nil {
109+
return fmt.Errorf("cannot expand timeout parameter %s: %v", timeoutStr, err)
110+
}
111+
112+
timeout, err := time.ParseDuration(timeoutStr)
113+
if err != nil {
114+
return fmt.Errorf("cannot parse timeout paramter: %v", err)
115+
}
116+
117+
timeTimeout := time.Now().Add(timeout)
118+
104119
// apply functions to the private key, if any
105120
var signer ssh.Signer
106121
privKeyFile, err := ts.PrivateKeyFile.Expand(target)
@@ -186,31 +201,58 @@ func (ts *SSHCmd) Run(cancel, pause <-chan struct{}, ch test.TestStepChannels, p
186201
errCh <- innerErr
187202
}()
188203

189-
select {
190-
case err := <-errCh:
191-
log.Infof("Stdout of command '%s' is '%s'", cmd, stdout.Bytes())
192-
if err == nil {
193-
// Execute expectations
194-
expect := ts.Expect.String()
195-
if expect == "" {
196-
log.Warningf("no expectations specified")
204+
expect := ts.Expect.String()
205+
re, err := regexp.Compile(expect)
206+
keepAliveCnt := 0
207+
208+
if err != nil {
209+
return fmt.Errorf("malformed expect parameter: Can not compile %s with %v", expect, err)
210+
}
211+
212+
for {
213+
select {
214+
case err := <-errCh:
215+
log.Infof("Stdout of command '%s' is '%s'", cmd, stdout.Bytes())
216+
if err == nil {
217+
// Execute expectations
218+
if expect == "" {
219+
log.Warningf("no expectations specified")
220+
} else {
221+
matches := re.FindAll(stdout.Bytes(), -1)
222+
if len(matches) > 0 {
223+
log.Infof("match for regex '%s' found", expect)
224+
} else {
225+
return fmt.Errorf("match for %s not found for target %v", expect, target)
226+
}
227+
}
197228
} else {
198-
re := regexp.MustCompile(expect)
229+
log.Warningf("Stderr of command '%s' is '%s'", cmd, stderr.Bytes())
230+
}
231+
return err
232+
case <-cancel:
233+
return session.Signal(ssh.SIGKILL)
234+
case <-pause:
235+
return session.Signal(ssh.SIGKILL)
236+
case <-time.After(250 * time.Millisecond):
237+
keepAliveCnt++
238+
if expect != "" {
199239
matches := re.FindAll(stdout.Bytes(), -1)
200240
if len(matches) > 0 {
201-
log.Infof("match for regex \"%s\" found", expect)
202-
} else {
203-
return fmt.Errorf("match for %s not found for target %v", expect, target)
241+
log.Infof("match for regex '%s' found", expect)
242+
return nil
243+
}
244+
}
245+
if time.Now().After(timeTimeout) {
246+
return fmt.Errorf("timed out after %s", timeout)
247+
}
248+
// This is needed to keep the connection to the server alive
249+
if keepAliveCnt%20 == 0 {
250+
err = session.Signal(ssh.Signal("CONT"))
251+
if err != nil {
252+
log.Warnf("Unable to send CONT to ssh server: %v", err)
204253
}
205254
}
206-
} else {
207-
log.Warningf("Stderr of command '%s' is '%s'", cmd, stderr.Bytes())
208255
}
209-
return err
210-
case <-cancel:
211-
return session.Signal(ssh.SIGKILL)
212-
case <-pause:
213-
return session.Signal(ssh.SIGKILL)
214256
}
215257
}
216258
return teststeps.ForEachTarget(Name, cancel, pause, ch, f)
@@ -253,6 +295,12 @@ func (ts *SSHCmd) validateAndPopulate(params test.TestStepParameters) error {
253295
}
254296
ts.Args = params.Get("args")
255297
ts.Expect = params.GetOne("expect")
298+
299+
if params.GetOne("timeout").IsEmpty() {
300+
ts.Timeout = test.NewParam(defaultTimeoutParameter)
301+
} else {
302+
ts.Timeout = params.GetOne("timeout")
303+
}
256304
return nil
257305
}
258306

0 commit comments

Comments
 (0)