Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit d192522

Browse files
committed
Revert "Split QemuSUT implementation"
This reverts commit b278052. This patch can be improved, reducing complexity of the base class.
1 parent b278052 commit d192522

File tree

3 files changed

+201
-241
lines changed

3 files changed

+201
-241
lines changed

ltp/qemu.py

Lines changed: 200 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
.. module:: qemu
33
:platform: Linux
4-
:synopsis: module containing the base for qemu SUT implementation
4+
:synopsis: module containing qemu SUT implementation
55
66
.. moduleauthor:: Andrea Cervesato <[email protected]>
77
"""
@@ -11,6 +11,7 @@
1111
import signal
1212
import select
1313
import string
14+
import shutil
1415
import secrets
1516
import logging
1617
import threading
@@ -24,50 +25,185 @@
2425
from ltp.utils import LTPTimeoutError
2526

2627

27-
class QemuBase(SUT):
28+
# pylint: disable=too-many-instance-attributes
29+
class QemuSUT(SUT):
2830
"""
29-
This is a base class for qemu based SUT implementations.
31+
Qemu SUT spawn a new VM using qemu and execute commands inside it.
32+
This SUT implementation can be used to run commands inside
33+
a protected, virtualized environment.
3034
"""
3135

3236
def __init__(self) -> None:
3337
self._logger = logging.getLogger("ltp.qemu")
3438
self._comm_lock = threading.Lock()
3539
self._cmd_lock = threading.Lock()
3640
self._fetch_lock = threading.Lock()
41+
self._tmpdir = None
42+
self._env = None
43+
self._cwd = None
3744
self._proc = None
3845
self._poller = None
3946
self._stop = False
4047
self._logged_in = False
4148
self._last_pos = 0
49+
self._image = None
50+
self._image_overlay = None
51+
self._ro_image = None
52+
self._password = None
53+
self._ram = None
54+
self._smp = None
55+
self._virtfs = None
56+
self._serial_type = None
57+
self._qemu_cmd = None
58+
self._opts = None
4259
self._last_read = ""
4360

44-
def _get_command(self) -> str:
45-
"""
46-
Return the full qemu command to execute.
47-
"""
48-
raise NotImplementedError()
49-
50-
def _login(self, timeout: float, iobuffer: IOBuffer) -> None:
61+
@staticmethod
62+
def _generate_string(length: int = 10) -> str:
5163
"""
52-
Method that implements login after starting the qemu process.
64+
Generate a random string of the given length.
5365
"""
54-
raise NotImplementedError()
66+
out = ''.join(secrets.choice(string.ascii_letters + string.digits)
67+
for _ in range(length))
68+
return out
5569

56-
def _get_transport(self) -> tuple:
70+
def _get_transport(self) -> str:
5771
"""
5872
Return a couple of transport_dev and transport_file used by
5973
qemu instance for transport configuration.
6074
"""
61-
raise NotImplementedError()
75+
pid = os.getpid()
76+
transport_file = os.path.join(self._tmpdir, f"transport-{pid}")
77+
transport_dev = ""
6278

63-
@staticmethod
64-
def _generate_string(length: int = 10) -> str:
79+
if self._serial_type == "isa":
80+
transport_dev = "/dev/ttyS1"
81+
elif self._serial_type == "virtio":
82+
transport_dev = "/dev/vport1p1"
83+
84+
return transport_dev, transport_file
85+
86+
def _get_command(self) -> str:
6587
"""
66-
Generate a random string of the given length.
88+
Return the full qemu command to execute.
6789
"""
68-
out = ''.join(secrets.choice(string.ascii_letters + string.digits)
69-
for _ in range(length))
70-
return out
90+
pid = os.getpid()
91+
tty_log = os.path.join(self._tmpdir, f"ttyS0-{pid}.log")
92+
93+
image = self._image
94+
if self._image_overlay:
95+
shutil.copyfile(
96+
self._image,
97+
self._image_overlay)
98+
image = self._image_overlay
99+
100+
params = []
101+
params.append("-enable-kvm")
102+
params.append("-display none")
103+
params.append(f"-m {self._ram}")
104+
params.append(f"-smp {self._smp}")
105+
params.append("-device virtio-rng-pci")
106+
params.append(f"-drive if=virtio,cache=unsafe,file={image}")
107+
params.append(f"-chardev stdio,id=tty,logfile={tty_log}")
108+
109+
if self._serial_type == "isa":
110+
params.append("-serial chardev:tty")
111+
params.append("-serial chardev:transport")
112+
elif self._serial_type == "virtio":
113+
params.append("-device virtio-serial")
114+
params.append("-device virtconsole,chardev=tty")
115+
params.append("-device virtserialport,chardev=transport")
116+
else:
117+
raise SUTError(
118+
f"Unsupported serial device type {self._serial_type}")
119+
120+
_, transport_file = self._get_transport()
121+
params.append(f"-chardev file,id=transport,path={transport_file}")
122+
123+
if self._ro_image:
124+
params.append(
125+
"-drive read-only,"
126+
"if=virtio,"
127+
"cache=unsafe,"
128+
f"file={self._ro_image}")
129+
130+
if self._virtfs:
131+
params.append(
132+
"-virtfs local,"
133+
f"path={self._virtfs},"
134+
"mount_tag=host0,"
135+
"security_model=mapped-xattr,"
136+
"readonly=on")
137+
138+
if self._opts:
139+
params.append(self._opts)
140+
141+
cmd = f"{self._qemu_cmd} {' '.join(params)}"
142+
143+
return cmd
144+
145+
def setup(self, **kwargs: dict) -> None:
146+
self._logger.info("Initialize SUT")
147+
148+
self._env = kwargs.get("env", None)
149+
self._cwd = kwargs.get("cwd", None)
150+
self._tmpdir = kwargs.get("tmpdir", None)
151+
self._image = kwargs.get("image", None)
152+
self._image_overlay = kwargs.get("image_overlay", None)
153+
self._ro_image = kwargs.get("ro_image", None)
154+
self._password = kwargs.get("password", "root")
155+
self._ram = kwargs.get("ram", "2G")
156+
self._smp = kwargs.get("smp", "2")
157+
self._virtfs = kwargs.get("virtfs", None)
158+
self._serial_type = kwargs.get("serial", "isa")
159+
self._opts = kwargs.get("options", None)
160+
161+
system = kwargs.get("system", "x86_64")
162+
self._qemu_cmd = f"qemu-system-{system}"
163+
164+
if not self._tmpdir or not os.path.isdir(self._tmpdir):
165+
raise SUTError(
166+
f"Temporary directory doesn't exist: {self._tmpdir}")
167+
168+
if not self._image or not os.path.isfile(self._image):
169+
raise SUTError(
170+
f"Image location doesn't exist: {self._image}")
171+
172+
if self._ro_image and not os.path.isfile(self._ro_image):
173+
raise SUTError(
174+
f"Read-only image location doesn't exist: {self._ro_image}")
175+
176+
if not self._ram:
177+
raise SUTError("RAM is not defined")
178+
179+
if not self._smp:
180+
raise SUTError("CPU is not defined")
181+
182+
if self._virtfs and not os.path.isdir(self._virtfs):
183+
raise SUTError(
184+
f"Virtual FS directory doesn't exist: {self._virtfs}")
185+
186+
if self._serial_type not in ["isa", "virtio"]:
187+
raise SUTError("Serial protocol must be isa or virtio")
188+
189+
@property
190+
def config_help(self) -> dict:
191+
return {
192+
"image": "qcow2 image location",
193+
"image_overlay": "image_overlay: image copy location",
194+
"password": "root password (default: root)",
195+
"system": "system architecture (default: x86_64)",
196+
"ram": "RAM of the VM (default: 2G)",
197+
"smp": "number of CPUs (default: 2)",
198+
"serial": "type of serial protocol. isa|virtio (default: isa)",
199+
"virtfs": "directory to mount inside VM",
200+
"ro_image": "path of the image that will exposed as read only",
201+
"options": "user defined options",
202+
}
203+
204+
@property
205+
def name(self) -> str:
206+
return "qemu"
71207

72208
@property
73209
def is_running(self) -> bool:
@@ -334,6 +470,9 @@ def communicate(
334470
self,
335471
timeout: float = 3600,
336472
iobuffer: IOBuffer = None) -> None:
473+
if not shutil.which(self._qemu_cmd):
474+
raise SUTError(f"Command not found: {self._qemu_cmd}")
475+
337476
if self.is_running:
338477
raise SUTError("Virtual machine is already running")
339478

@@ -364,9 +503,48 @@ def communicate(
364503
select.POLLERR)
365504

366505
try:
367-
self._login(timeout, iobuffer)
506+
self._wait_for("login:", timeout, iobuffer)
507+
self._write_stdin("root\n")
508+
509+
if self._password:
510+
self._wait_for("Password:", 5, iobuffer)
511+
self._write_stdin(f"{self._password}\n")
512+
513+
time.sleep(0.2)
514+
515+
self._wait_for("#", 5, iobuffer)
516+
time.sleep(0.2)
517+
518+
self._write_stdin("stty -echo; stty cols 1024\n")
519+
self._wait_for("#", 5, None)
520+
521+
_, retcode, _ = self._exec("export PS1=''", 5, None)
522+
if retcode != 0:
523+
raise SUTError("Can't setup prompt string")
524+
525+
if self._virtfs:
526+
_, retcode, _ = self._exec(
527+
"mount -t 9p -o trans=virtio host0 /mnt",
528+
10, None)
529+
if retcode != 0:
530+
raise SUTError("Failed to mount virtfs")
531+
532+
if self._cwd:
533+
_, retcode, _ = self._exec(f"cd {self._cwd}", 5, None)
534+
if retcode != 0:
535+
raise SUTError("Can't setup current working directory")
536+
537+
if self._env:
538+
for key, value in self._env.items():
539+
_, retcode, _ = self._exec(
540+
f"export {key}={value}",
541+
5, None)
542+
if retcode != 0:
543+
raise SUTError(f"Can't setup env {key}={value}")
544+
368545
self._logged_in = True
369-
self._logger.info("Logged inside virtual machine")
546+
547+
self._logger.info("Virtual machine started")
370548
except SUTError as err:
371549
error = err
372550

0 commit comments

Comments
 (0)