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+
5+ import fcntl
6+ import os
7+ import termios
48import time
9+ from subprocess import run , PIPE
10+
511from framework .microvm import Serial
612from framework .state_machine import TestState
713
814
9- class WaitLogin (TestState ):
15+ class WaitLogin (TestState ): # pylint: disable=too-few-public-methods
1016 """Initial state when we wait for the login prompt."""
1117
1218 def handle_input (self , serial , input_char ) -> TestState :
1319 """Handle input and return next state."""
1420 if self .match (input_char ):
21+ time .sleep (2 )
1522 # Send login username.
1623 serial .tx ("root" )
24+ time .sleep (2 )
1725 return WaitPasswordPrompt ("Password:" )
1826 return self
1927
2028
21- class WaitPasswordPrompt (TestState ):
29+ class WaitPasswordPrompt (TestState ): # pylint: disable=too-few-public-methods
2230 """Wait for the password prompt to be shown."""
2331
2432 def handle_input (self , serial , input_char ) -> TestState :
2533 """Handle input and return next state."""
2634 if self .match (input_char ):
2735 serial .tx ("root" )
28- # Wait 1 second for shell
29- time .sleep (1 )
36+ time .sleep (2 )
3037 serial .tx ("id" )
38+ time .sleep (2 )
3139 return WaitIDResult ("uid=0(root) gid=0(root) groups=0(root)" )
3240 return self
3341
3442
35- class WaitIDResult (TestState ):
43+ class WaitIDResult (TestState ): # pylint: disable=too-few-public-methods
3644 """Wait for the console to show the result of the 'id' shell command."""
3745
3846 def handle_input (self , unused_serial , input_char ) -> TestState :
@@ -42,10 +50,10 @@ def handle_input(self, unused_serial, input_char) -> TestState:
4250 return self
4351
4452
45- class TestFinished (TestState ):
53+ class TestFinished (TestState ): # pylint: disable=too-few-public-methods
4654 """Test complete and successful."""
4755
48- def handle_input (self , unused_serial , input_char ) -> TestState :
56+ def handle_input (self , unused_serial , _ ) -> TestState :
4957 """Return self since the test is about to end."""
5058 return self
5159
@@ -63,16 +71,61 @@ def test_serial_console_login(test_microvm_with_ssh):
6371 # Set up the microVM with 1 vCPU and a serial console.
6472 microvm .basic_config (vcpu_count = 1 ,
6573 boot_args = 'console=ttyS0 reboot=k panic=1 pci=off' )
66-
6774 microvm .start ()
6875
6976 serial = Serial (microvm )
7077 serial .open ()
7178
7279 # Set initial state - wait for 'login:' prompt
7380 current_state = WaitLogin ("login:" )
74-
7581 while not isinstance (current_state , TestFinished ):
7682 output_char = serial .rx_char ()
7783 current_state = current_state .handle_input (
7884 serial , output_char )
85+
86+
87+ def get_total_mem_size (pid ):
88+ """Get total memory usage for a process."""
89+ cmd = f"pmap { pid } | tail -n 1 | sed 's/^ //' | tr -s ' ' | cut -d' ' -f2"
90+ proc = run (cmd , check = True , shell = True , stdout = PIPE )
91+ return proc .stdout .decode ()
92+
93+
94+ def send_bytes (tty , bytes_count , timeout = 60 ):
95+ """Send data to the terminal."""
96+ start = time .time ()
97+ for _ in range (bytes_count ):
98+ fcntl .ioctl (tty , termios .TIOCSTI , 'a' )
99+ current = time .time ()
100+ if current - start > timeout :
101+ break
102+
103+
104+ def test_serial_dos (test_microvm_with_ssh ):
105+ """Test serial console behavior when it is under DoS."""
106+ microvm = test_microvm_with_ssh
107+ microvm .jailer .daemonize = False
108+ microvm .spawn ()
109+ microvm .memory_events_queue = None
110+
111+ # Set up the microVM with 1 vCPU and a serial console.
112+ microvm .basic_config (vcpu_count = 1 ,
113+ boot_args = 'console=ttyS0 reboot=k panic=1 pci=off' )
114+ microvm .start ()
115+
116+ # Get Firecracker process TTY.
117+ tty_path = f"/proc/{ microvm .jailer_clone_pid } /fd/0"
118+ tty_fd = os .open (tty_path , os .O_RDWR )
119+
120+ # Check if the total memory size changed.
121+ before_size = get_total_mem_size (microvm .jailer_clone_pid )
122+ # Send 100MB at maximum to the terminal backed by our serial device.
123+ # In practice, we send bytes in a burst for only 1 seconds. This is
124+ # sufficient for exercising serial device buffer capacity.
125+ send_bytes (tty_fd , bytes_count = 100000000 , timeout = 1 )
126+ after_size = get_total_mem_size (microvm .jailer_clone_pid )
127+ assert before_size == after_size , "The memory size of the " \
128+ "Firecracker process " \
129+ "changed from {} to {}." \
130+ .format (before_size ,
131+ after_size )
0 commit comments