Skip to content

Commit 265d5b9

Browse files
authored
Merge pull request #775 from JoshuaWatt/console-settle
shelldriver: Handle intermingled output on login
2 parents ab36015 + bd53ff5 commit 265d5b9

3 files changed

Lines changed: 39 additions & 18 deletions

File tree

doc/configuration.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,9 @@ Arguments:
11851185
to pass before sending a newline to device.
11861186
- console_ready (regex): optional, pattern used by the kernel to inform
11871187
the user that a console can be activated by pressing enter.
1188+
- post_login_settle_time (int): optional, seconds of silence after logging in
1189+
before check for a prompt. Useful when the console is interleaved with boot
1190+
output which may interrupt prompt detection
11881191

11891192
.. _conf-sshdriver:
11901193

labgrid/driver/consoleexpectmixin.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from time import sleep
1+
import time
2+
import pexpect
23

3-
from ..util import PtxExpect
4+
from ..util import PtxExpect, Timeout
45
from ..step import step
56
from .common import Driver
67

@@ -33,7 +34,7 @@ def write(self, data):
3334
len(data), data, self.txdelay)
3435
count = 0
3536
for i in range(len(data)):
36-
sleep(self.txdelay)
37+
time.sleep(self.txdelay)
3738
count += self._write(data[i:i+1])
3839
return count
3940

@@ -54,6 +55,16 @@ def expect(self, pattern, timeout=-1):
5455
index = self._expect.expect(pattern, timeout=timeout)
5556
return index, self._expect.before, self._expect.match, self._expect.after
5657

58+
@Driver.check_active
59+
@step(args=['quiet_time'])
60+
def settle(self, quiet_time, timeout=120.0):
61+
t = Timeout(timeout)
62+
while not t.expired:
63+
try:
64+
self.read(timeout=quiet_time)
65+
except pexpect.TIMEOUT:
66+
break
67+
5768
def resolve_conflicts(self, client):
5869
for other in self.clients:
5970
if other is client:

labgrid/driver/shelldriver.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ..factory import target_factory
1616
from ..protocol import CommandProtocol, ConsoleProtocol, FileTransferProtocol
1717
from ..step import step
18-
from ..util import gen_marker
18+
from ..util import gen_marker, Timeout
1919
from .commandmixin import CommandMixin
2020
from .common import Driver
2121
from .exception import ExecutionError
@@ -37,6 +37,9 @@ class ShellDriver(CommandMixin, Driver, CommandProtocol, FileTransferProtocol):
3737
password (str): password to login with
3838
keyfile (str): keyfile to bind mount over users authorized keys
3939
login_timeout (int): optional, timeout for login prompt detection
40+
post_login_settle_time (int): optional, seconds of silence after logging in
41+
before check for a prompt. Useful when the console is interleaved with boot
42+
output which may interrupt prompt detection
4043
"""
4144
bindings = {"console": ConsoleProtocol, }
4245
prompt = attr.ib(validator=attr.validators.instance_of(str))
@@ -47,6 +50,7 @@ class ShellDriver(CommandMixin, Driver, CommandProtocol, FileTransferProtocol):
4750
login_timeout = attr.ib(default=60, validator=attr.validators.instance_of(int))
4851
console_ready = attr.ib(default="", validator=attr.validators.instance_of(str))
4952
await_login_timeout = attr.ib(default=2, validator=attr.validators.instance_of(int))
53+
post_login_settle_time = attr.ib(default=0, validator=attr.validators.instance_of(int))
5054

5155

5256
def __attrs_post_init__(self):
@@ -114,9 +118,9 @@ def run(self, cmd, timeout=30.0, codec="utf-8", decodeerrors="strict"):
114118
def _await_login(self):
115119
"""Awaits the login prompt and logs the user in"""
116120

117-
start = time.time()
121+
timeout = Timeout(float(self.login_timeout))
118122

119-
expectations = [self.prompt, self.login_prompt, TIMEOUT]
123+
expectations = [self.prompt, self.login_prompt, "Password: ", TIMEOUT]
120124
if self.console_ready != "":
121125
expectations.append(self.console_ready)
122126

@@ -126,6 +130,7 @@ def _await_login(self):
126130
# Because pexpect keeps any read data in it's buffer when a timeout
127131
# occours, we can't lose any data this way.
128132
last_before = b''
133+
did_login = False
129134

130135
while True:
131136
index, before, _, _ = self.console.expect(
@@ -142,18 +147,15 @@ def _await_login(self):
142147
elif index == 1:
143148
# we need to login
144149
self.console.sendline(self.username)
145-
index, _, _, _ = self.console.expect([self.prompt, "Password: "], timeout=10)
146-
if index == 1:
147-
if self.password:
148-
self.console.sendline(self.password)
149-
remaining_time = (start + self.login_timeout) - time.time()
150-
self.console.expect(self.prompt, timeout=remaining_time)
151-
else:
152-
raise Exception("Password entry needed but no password set")
153-
self._check_prompt()
154-
break
150+
did_login = True
155151

156152
elif index == 2:
153+
if self.password:
154+
self.console.sendline(self.password)
155+
else:
156+
raise Exception("Password entry needed but no password set")
157+
158+
elif index == 3:
157159
# expect hit a timeout while waiting for a match
158160
if before == last_before:
159161
# we did not receive anything during
@@ -162,17 +164,22 @@ def _await_login(self):
162164
# newline to check the state
163165
self.console.sendline("")
164166

165-
elif index == 3:
167+
elif index == 4:
166168
# we have just activated a console here
167169
# lets start over again and see if login or prompt will appear
168170
# now.
169171
self.console.sendline("")
170172

171173
last_before = before
172174

173-
if time.time() > start + self.login_timeout:
175+
if timeout.expired:
174176
raise TIMEOUT("Timeout of {} seconds exceeded during waiting for login".format(self.login_timeout)) # pylint: disable=line-too-long
175177

178+
if did_login:
179+
if self.post_login_settle_time > 0:
180+
self.console.settle(self.post_login_settle_time, timeout=timeout.remaining)
181+
self._check_prompt()
182+
176183
@step()
177184
def get_status(self):
178185
"""Returns the status of the shell-driver.

0 commit comments

Comments
 (0)