Skip to content

Commit 50b862b

Browse files
marcovandreeaflorescu
authored andcommitted
tests: add Serial class
Add the Serial class for serial console communication. Signed-off-by: Marco Vedovati <[email protected]>
1 parent f546253 commit 50b862b

File tree

3 files changed

+82
-34
lines changed

3 files changed

+82
-34
lines changed

tests/framework/microvm.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import os
1414
from queue import Queue
1515
import re
16+
import select
17+
import time
1618
from subprocess import run, PIPE
1719

1820
from retry import retry
@@ -37,6 +39,8 @@ class Microvm:
3739
process.
3840
"""
3941

42+
SCREEN_LOGFILE = "/tmp/screen.log"
43+
4044
def __init__(
4145
self,
4246
resource_path,
@@ -314,14 +318,15 @@ def spawn(self):
314318
else:
315319
# Delete old screen log if any.
316320
try:
317-
os.unlink('/tmp/screen.log')
321+
os.unlink(self.SCREEN_LOGFILE)
318322
except OSError:
319323
pass
320-
# Log screen output to /tmp/screen.log.
324+
# Log screen output to SCREEN_LOGFILE
321325
# This file will collect any output from 'screen'ed Firecracker.
322-
start_cmd = 'screen -L -Logfile /tmp/screen.log '\
326+
start_cmd = 'screen -L -Logfile {logfile} '\
323327
'-dmS {session} {binary} {params}'
324328
start_cmd = start_cmd.format(
329+
logfile=self.SCREEN_LOGFILE,
325330
session=self._session_name,
326331
binary=self._jailer_binary_path,
327332
params=' '.join(jailer_param_list)
@@ -468,3 +473,63 @@ def start(self):
468473
"""
469474
response = self.actions.put(action_type='InstanceStart')
470475
assert self._api_session.is_status_no_content(response.status_code)
476+
477+
478+
class Serial:
479+
"""Class for serial console communication with a Microvm."""
480+
481+
RX_TIMEOUT_S = 5
482+
483+
def __init__(self, vm):
484+
"""Initialize a new Serial object."""
485+
self._poller = None
486+
self._vm = vm
487+
488+
def open(self):
489+
"""Open a serial connection."""
490+
# Open the screen log file.
491+
if self._poller is not None:
492+
# serial already opened
493+
return
494+
495+
screen_log_fd = os.open(Microvm.SCREEN_LOGFILE, os.O_RDONLY)
496+
self._poller = select.poll()
497+
self._poller.register(screen_log_fd,
498+
select.POLLIN | select.POLLHUP)
499+
500+
def tx(self, input_string, end='\n'):
501+
# pylint: disable=invalid-name
502+
# No need to have a snake_case naming style for a single word.
503+
r"""Send a string terminated by an end token (defaulting to "\n")."""
504+
self._vm.serial_input(input_string + end)
505+
506+
def rx_char(self):
507+
"""Read a single character."""
508+
result = self._poller.poll(0.1)
509+
510+
for fd, flag in result:
511+
if flag & select.POLLHUP:
512+
assert False, "Oh! The console vanished before test completed."
513+
514+
if flag & select.POLLIN:
515+
output_char = str(os.read(fd, 1),
516+
encoding='utf-8',
517+
errors='ignore')
518+
return output_char
519+
520+
return ''
521+
522+
def rx(self, token="\n"):
523+
# pylint: disable=invalid-name
524+
# No need to have a snake_case naming style for a single word.
525+
r"""Read a string delimited by an end token (defaults to "\n")."""
526+
rx_str = ''
527+
start = time.time()
528+
while True:
529+
rx_str += self.rx_char()
530+
if rx_str.endswith(token):
531+
break
532+
if (time.time() - start) >= self.RX_TIMEOUT_S:
533+
assert False
534+
535+
return rx_str

tests/framework/state_machine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(self, match_string=''):
4646
MatchStaticString.__init__(self, match_string)
4747
print('\n*** Current test state: ', str(self), end='')
4848

49-
def handle_input(self, microvm, input_char):
49+
def handle_input(self, serial, input_char):
5050
"""Handle input event and return next state."""
5151

5252
def __repr__(self):
Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,41 @@
11
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""Tests scenario for the Firecracker serial console."""
4-
import os
54
import time
6-
import select
5+
from framework.microvm import Serial
76
from framework.state_machine import TestState
87

98

109
class WaitLogin(TestState):
1110
"""Initial state when we wait for the login prompt."""
1211

13-
def handle_input(self, microvm, input_char) -> TestState:
12+
def handle_input(self, serial, input_char) -> TestState:
1413
"""Handle input and return next state."""
1514
if self.match(input_char):
1615
# Send login username.
17-
microvm.serial_input("root")
16+
serial.tx("root")
1817
return WaitPasswordPrompt("Password:")
1918
return self
2019

2120

2221
class WaitPasswordPrompt(TestState):
2322
"""Wait for the password prompt to be shown."""
2423

25-
def handle_input(self, microvm, input_char) -> TestState:
24+
def handle_input(self, serial, input_char) -> TestState:
2625
"""Handle input and return next state."""
2726
if self.match(input_char):
28-
microvm.serial_input("root")
27+
serial.tx("root")
2928
# Wait 1 second for shell
3029
time.sleep(1)
31-
microvm.serial_input("id")
30+
serial.tx("id")
3231
return WaitIDResult("uid=0(root) gid=0(root) groups=0(root)")
3332
return self
3433

3534

3635
class WaitIDResult(TestState):
3736
"""Wait for the console to show the result of the 'id' shell command."""
3837

39-
def handle_input(self, microvm, input_char) -> TestState:
38+
def handle_input(self, unused_serial, input_char) -> TestState:
4039
"""Handle input and return next state."""
4140
if self.match(input_char):
4241
return TestFinished()
@@ -46,7 +45,7 @@ def handle_input(self, microvm, input_char) -> TestState:
4645
class TestFinished(TestState):
4746
"""Test complete and successful."""
4847

49-
def handle_input(self, microvm, input_char) -> TestState:
48+
def handle_input(self, unused_serial, input_char) -> TestState:
5049
"""Return self since the test is about to end."""
5150
return self
5251

@@ -67,29 +66,13 @@ def test_serial_console_login(test_microvm_with_ssh):
6766

6867
microvm.start()
6968

70-
# Screen stdout log
71-
screen_log = "/tmp/screen.log"
72-
73-
# Open the screen log file.
74-
screen_log_fd = os.open(screen_log, os.O_RDONLY)
75-
poller = select.poll()
69+
serial = Serial(microvm)
70+
serial.open()
7671

7772
# Set initial state - wait for 'login:' prompt
7873
current_state = WaitLogin("login:")
7974

80-
poller.register(screen_log_fd, select.POLLIN | select.POLLHUP)
81-
8275
while not isinstance(current_state, TestFinished):
83-
result = poller.poll(0.1)
84-
for fd, flag in result:
85-
if flag & select.POLLIN:
86-
output_char = str(os.read(fd, 1),
87-
encoding='utf-8',
88-
errors='ignore')
89-
# [DEBUG] Uncomment to see the serial console output.
90-
# print(output_char, end='')
91-
current_state = current_state.handle_input(
92-
microvm, output_char)
93-
elif flag & select.POLLHUP:
94-
assert False, "Oh! The console vanished before test completed."
95-
os.close(screen_log_fd)
76+
output_char = serial.rx_char()
77+
current_state = current_state.handle_input(
78+
serial, output_char)

0 commit comments

Comments
 (0)