Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/functional/test_serial_io.py
1958 views
1
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Tests scenario for the Firecracker serial console."""
4
5
import fcntl
6
import os
7
import subprocess
8
import termios
9
import time
10
11
from framework.microvm import Serial
12
from framework.state_machine import TestState
13
import framework.utils as utils
14
import host_tools.logging as log_tools
15
import host_tools.network as net_tools # pylint: disable=import-error
16
17
18
class WaitLogin(TestState): # pylint: disable=too-few-public-methods
19
"""Initial state when we wait for the login prompt."""
20
21
def handle_input(self, serial, input_char) -> TestState:
22
"""Handle input and return next state."""
23
if self.match(input_char):
24
# Send login username.
25
serial.tx("root")
26
return WaitPasswordPrompt("Password:")
27
return self
28
29
30
class WaitPasswordPrompt(TestState): # pylint: disable=too-few-public-methods
31
"""Wait for the password prompt to be shown."""
32
33
def handle_input(self, serial, input_char) -> TestState:
34
"""Handle input and return next state."""
35
if self.match(input_char):
36
serial.tx("root")
37
# Wait 1 second for shell
38
time.sleep(1)
39
serial.tx("id")
40
return WaitIDResult("uid=0(root) gid=0(root) groups=0(root)")
41
return self
42
43
44
class WaitIDResult(TestState): # pylint: disable=too-few-public-methods
45
"""Wait for the console to show the result of the 'id' shell command."""
46
47
def handle_input(self, unused_serial, input_char) -> TestState:
48
"""Handle input and return next state."""
49
if self.match(input_char):
50
return TestFinished()
51
return self
52
53
54
class TestFinished(TestState): # pylint: disable=too-few-public-methods
55
"""Test complete and successful."""
56
57
def handle_input(self, unused_serial, _) -> TestState:
58
"""Return self since the test is about to end."""
59
return self
60
61
62
def test_serial_console_login(test_microvm_with_ssh):
63
"""Test serial console login."""
64
microvm = test_microvm_with_ssh
65
microvm.jailer.daemonize = False
66
microvm.spawn()
67
68
# We don't need to monitor the memory for this test because we are
69
# just rebooting and the process dies before pmap gets the RSS.
70
microvm.memory_monitor = None
71
72
# Set up the microVM with 1 vCPU and a serial console.
73
microvm.basic_config(vcpu_count=1,
74
boot_args='console=ttyS0 reboot=k panic=1 pci=off')
75
76
microvm.start()
77
78
serial = Serial(microvm)
79
serial.open()
80
current_state = WaitLogin("login:")
81
82
while not isinstance(current_state, TestFinished):
83
output_char = serial.rx_char()
84
current_state = current_state.handle_input(
85
serial, output_char)
86
87
88
def get_total_mem_size(pid):
89
"""Get total memory usage for a process."""
90
cmd = f"pmap {pid} | tail -n 1 | sed 's/^ //' | tr -s ' ' | cut -d' ' -f2"
91
rc, stdout, stderr = utils.run_cmd(cmd)
92
assert rc == 0
93
assert stderr == ""
94
95
return stdout
96
97
98
def send_bytes(tty, bytes_count, timeout=60):
99
"""Send data to the terminal."""
100
start = time.time()
101
for _ in range(bytes_count):
102
fcntl.ioctl(tty, termios.TIOCSTI, '\n')
103
current = time.time()
104
if current - start > timeout:
105
break
106
107
108
def test_serial_dos(test_microvm_with_ssh):
109
"""Test serial console behavior under DoS."""
110
microvm = test_microvm_with_ssh
111
microvm.jailer.daemonize = False
112
microvm.spawn()
113
microvm.memory_events_queue = None
114
115
# Set up the microVM with 1 vCPU and a serial console.
116
microvm.basic_config(vcpu_count=1,
117
add_root_device=False,
118
boot_args='console=ttyS0 reboot=k panic=1 pci=off')
119
microvm.start()
120
121
# Open an fd for firecracker process terminal.
122
tty_path = f"/proc/{microvm.jailer_clone_pid}/fd/0"
123
tty_fd = os.open(tty_path, os.O_RDWR)
124
125
# Check if the total memory size changed.
126
before_size = get_total_mem_size(microvm.jailer_clone_pid)
127
send_bytes(tty_fd, 100000000, timeout=1)
128
after_size = get_total_mem_size(microvm.jailer_clone_pid)
129
assert before_size == after_size, "The memory size of the " \
130
"Firecracker process " \
131
"changed from {} to {}." \
132
.format(before_size,
133
after_size)
134
135
136
def test_serial_block(test_microvm_with_ssh, network_config):
137
"""Test that writing to stdout never blocks the vCPU thread."""
138
test_microvm = test_microvm_with_ssh
139
test_microvm.jailer.daemonize = False
140
test_microvm.spawn()
141
# Set up the microVM with 1 vCPU so we make sure the vCPU thread
142
# responsible for the SSH connection will also run the serial.
143
test_microvm.basic_config(
144
vcpu_count=1,
145
mem_size_mib=512,
146
boot_args='console=ttyS0 reboot=k panic=1 pci=off'
147
)
148
149
_tap, _, _ = test_microvm.ssh_network_config(network_config, '1')
150
151
# Configure the metrics.
152
metrics_fifo_path = os.path.join(test_microvm.path, 'metrics_fifo')
153
metrics_fifo = log_tools.Fifo(metrics_fifo_path)
154
response = test_microvm.metrics.put(
155
metrics_path=test_microvm.create_jailed_resource(metrics_fifo.path)
156
)
157
assert test_microvm.api_session.is_status_no_content(response.status_code)
158
159
test_microvm.start()
160
ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config)
161
162
# Get an initial reading of missed writes to the serial.
163
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
164
init_count = fc_metrics['uart']['missed_write_count']
165
166
screen_pid = test_microvm.screen_pid
167
# Stop `screen` process which captures stdout so we stop consuming stdout.
168
subprocess.check_call(
169
"kill -s STOP {}".format(screen_pid),
170
shell=True
171
)
172
173
# Generate a random text file.
174
exit_code, _, _ = ssh_connection.execute_command(
175
"base64 /dev/urandom | head -c 100000 > file.txt"
176
)
177
178
# Dump output to terminal
179
exit_code, _, _ = ssh_connection.execute_command(
180
"cat file.txt > /dev/ttyS0"
181
)
182
assert exit_code == 0
183
184
# Check that the vCPU isn't blocked.
185
exit_code, _, _ = ssh_connection.execute_command(
186
"cd /"
187
)
188
assert exit_code == 0
189
190
# Check the metrics to see if the serial missed bytes.
191
fc_metrics = test_microvm.flush_metrics(metrics_fifo)
192
last_count = fc_metrics['uart']['missed_write_count']
193
194
# Should be significantly more than before the `cat` command.
195
assert last_count - init_count > 10000
196
197