|
1 | 1 | """
|
2 | 2 | .. module:: qemu
|
3 | 3 | :platform: Linux
|
4 |
| - :synopsis: module containing qemu SUT implementation |
| 4 | + :synopsis: module containing the base for qemu SUT implementation |
5 | 5 |
|
6 | 6 | .. moduleauthor:: Andrea Cervesato <[email protected]>
|
7 | 7 | """
|
|
11 | 11 | import signal
|
12 | 12 | import select
|
13 | 13 | import string
|
14 |
| -import shutil |
15 | 14 | import secrets
|
16 | 15 | import logging
|
17 | 16 | import threading
|
|
25 | 24 | from ltp.utils import LTPTimeoutError
|
26 | 25 |
|
27 | 26 |
|
28 |
| -# pylint: disable=too-many-instance-attributes |
29 |
| -class QemuSUT(SUT): |
| 27 | +class QemuBase(SUT): |
30 | 28 | """
|
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. |
| 29 | + This is a base class for qemu based SUT implementations. |
34 | 30 | """
|
35 | 31 |
|
36 | 32 | def __init__(self) -> None:
|
37 | 33 | self._logger = logging.getLogger("ltp.qemu")
|
38 | 34 | self._comm_lock = threading.Lock()
|
39 | 35 | self._cmd_lock = threading.Lock()
|
40 | 36 | self._fetch_lock = threading.Lock()
|
41 |
| - self._tmpdir = None |
42 |
| - self._env = None |
43 |
| - self._cwd = None |
44 | 37 | self._proc = None
|
45 | 38 | self._poller = None
|
46 | 39 | self._stop = False
|
47 | 40 | self._logged_in = False
|
48 | 41 | 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 |
59 | 42 | self._last_read = ""
|
60 | 43 |
|
61 |
| - @staticmethod |
62 |
| - def _generate_string(length: int = 10) -> str: |
| 44 | + def _get_command(self) -> str: |
63 | 45 | """
|
64 |
| - Generate a random string of the given length. |
| 46 | + Return the full qemu command to execute. |
65 | 47 | """
|
66 |
| - out = ''.join(secrets.choice(string.ascii_letters + string.digits) |
67 |
| - for _ in range(length)) |
68 |
| - return out |
| 48 | + raise NotImplementedError() |
| 49 | + |
| 50 | + def _login(self, timeout: float, iobuffer: IOBuffer) -> None: |
| 51 | + """ |
| 52 | + Method that implements login after starting the qemu process. |
| 53 | + """ |
| 54 | + raise NotImplementedError() |
69 | 55 |
|
70 |
| - def _get_transport(self) -> str: |
| 56 | + def _get_transport(self) -> tuple: |
71 | 57 | """
|
72 | 58 | Return a couple of transport_dev and transport_file used by
|
73 | 59 | qemu instance for transport configuration.
|
74 | 60 | """
|
75 |
| - pid = os.getpid() |
76 |
| - transport_file = os.path.join(self._tmpdir, f"transport-{pid}") |
77 |
| - transport_dev = "" |
78 |
| - |
79 |
| - if self._serial_type == "isa": |
80 |
| - transport_dev = "/dev/ttyS1" |
81 |
| - elif self._serial_type == "virtio": |
82 |
| - transport_dev = "/dev/vport1p1" |
| 61 | + raise NotImplementedError() |
83 | 62 |
|
84 |
| - return transport_dev, transport_file |
85 |
| - |
86 |
| - def _get_command(self) -> str: |
| 63 | + @staticmethod |
| 64 | + def _generate_string(length: int = 10) -> str: |
87 | 65 | """
|
88 |
| - Return the full qemu command to execute. |
| 66 | + Generate a random string of the given length. |
89 | 67 | """
|
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" |
| 68 | + out = ''.join(secrets.choice(string.ascii_letters + string.digits) |
| 69 | + for _ in range(length)) |
| 70 | + return out |
207 | 71 |
|
208 | 72 | @property
|
209 | 73 | def is_running(self) -> bool:
|
@@ -470,9 +334,6 @@ def communicate(
|
470 | 334 | self,
|
471 | 335 | timeout: float = 3600,
|
472 | 336 | iobuffer: IOBuffer = None) -> None:
|
473 |
| - if not shutil.which(self._qemu_cmd): |
474 |
| - raise SUTError(f"Command not found: {self._qemu_cmd}") |
475 |
| - |
476 | 337 | if self.is_running:
|
477 | 338 | raise SUTError("Virtual machine is already running")
|
478 | 339 |
|
@@ -503,48 +364,9 @@ def communicate(
|
503 | 364 | select.POLLERR)
|
504 | 365 |
|
505 | 366 | try:
|
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 |
| - |
| 367 | + self._login(timeout, iobuffer) |
545 | 368 | self._logged_in = True
|
546 |
| - |
547 |
| - self._logger.info("Virtual machine started") |
| 369 | + self._logger.info("Logged inside virtual machine") |
548 | 370 | except SUTError as err:
|
549 | 371 | error = err
|
550 | 372 |
|
|
0 commit comments