|
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