Files
Michał Iwanicki f0f4663487 lib: QemuMonitor: Add keyword to remove added USB
Signed-off-by: Michał Iwanicki <michal.iwanicki@3mdeb.com>
2025-10-23 14:38:25 +02:00

214 lines
6.4 KiB
Python

# SPDX-FileCopyrightText: 2024 3mdeb <contact@3mdeb.com>
#
# SPDX-License-Identifier: Apache-2.0
import json
import os
import socket
import time
from robot.api import logger
from robot.api.deco import keyword, library
@library
class QemuMonitor:
def __init__(self, socket_path):
self.sock = None
logger.info(f"QemuMonitor init")
self.socket_path = socket_path
if not os.path.exists(socket_path):
raise ValueError(f"Socket path does not exist: {socket_path}")
def _open(self):
logger.info(f"QemuMonitor open")
try:
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(self.socket_path)
greeting = self.sock.recv(4096)
logger.trace(f"QemuMonitor greeting: {greeting}")
except Exception as e:
self._close()
raise e
def _close(self):
logger.info(f"QemuMonitor close")
if self.sock:
self.sock.close()
self.sock = None
def _send_cmd(self, command, **args):
self._open()
logger.trace(self._send("qmp_capabilities"))
response = self._send(command, **args)
logger.trace(response)
self._close()
if "error" in response:
logger.error(f"Command '{command}' failed with error: {response['error']}")
raise RuntimeError(
f"QEMU monitor error response: {response['error']['desc']}"
)
return response
def _send(self, command, **args):
msg = {"execute": command, "arguments": args}
logger.trace(f"QemuMonitor command: {msg}")
self.sock.sendall(json.dumps(msg).encode())
response = self.sock.recv(8192).decode()
logger.trace(f"QemuMonitor response: {response}")
json_objects = [
json.loads(line) for line in response.splitlines() if line.strip()
]
if len(json_objects) > 1:
return {"ack": json_objects[0], "event": json_objects[1]}
else:
return json_objects[0]
def _check_if_block_node_exists(self, block_node):
block_nodes = self._send_cmd("query-named-block-nodes")
contains_node = any(
block.get("node-name") == block_node
for block in block_nodes.get("return", [])
)
return contains_node
@keyword
def qmp_capabilities(self):
return self._send_cmd("qmp_capabilities")
@keyword
def system_powerdown(self):
return self._send_cmd("system_powerdown")
@keyword
def system_reset(self):
return self._send_cmd("system_reset")
@keyword
def quit(self):
return self._send_cmd("quit")
@keyword("Add HDD To Qemu")
def blockdev_add(self, img_path, name="unnamed"):
self.hdd_del(name)
blockdev_add_params = {
"node-name": self._hdd_nodename(name),
"driver": "raw",
"file": {
"driver": "file",
"filename": img_path,
},
"read-only": True,
}
logger.trace(self._send_cmd("blockdev-add", **blockdev_add_params))
device_add_params = {
"driver": "scsi-hd",
"drive": self._hdd_nodename(name),
"id": self._hdd_devid(name),
"bus": "scsi.0",
}
return self._send_cmd("device_add", **device_add_params)
@keyword("Remove Drive From Qemu")
def hdd_del(self, name="unnamed"):
contains_node = self._check_if_block_node_exists(self._hdd_nodename(name))
logger.trace(f"contains_{self._hdd_nodename(name)}: {contains_node}")
if contains_node:
try:
self._send_cmd("device_del", **{"id": self._hdd_devid(name)})
except:
pass
time.sleep(2)
try:
self._send_cmd(
"blockdev-del", **{"node-name": self._hdd_nodename(name)}
)
except:
pass
@keyword("Add USB To Qemu")
def usb_add(self, img_path, name="unnamed", read_only=True, removable=False):
self.usb_del(name)
blockdev_params = {
"node-name": self._usb_file_nodename(name), # "file_iso"
"driver": "file",
"filename": img_path,
"auto-read-only": read_only,
"discard": "unmap",
}
self._send_cmd("blockdev-add", **blockdev_params)
drive_params = {
"driver": "raw",
"file": self._usb_file_nodename(name),
"node-name": self._usb_nodename(name), # "drive-iso",
"read-only": read_only,
"discard": "unmap",
}
self._send_cmd("blockdev-add", **drive_params)
usb_storage_params = {
"driver": "usb-storage",
"id": self._usb_devid(name), # "usbdisk",
"drive": self._usb_nodename(name),
"removable": removable,
}
self._send_cmd("device_add", **usb_storage_params)
contains_file_node = self._check_if_block_node_exists(
self._usb_file_nodename(name)
)
logger.trace(f"contains file node: {contains_file_node}")
@keyword("Remove USB from Qemu")
def usb_del(self, name="unnamed"):
contains_file_node = self._check_if_block_node_exists(
self._usb_file_nodename(name)
)
logger.trace(f"contains file node: {contains_file_node}")
if contains_file_node:
try:
self._send_cmd("device_del", id=self._usb_devid(name))
except Exception:
pass
time.sleep(2)
blockdev_del_params = {
"node-name": self._usb_nodename(name),
}
try:
self._send_cmd("blockdev-del", **blockdev_del_params)
except Exception:
pass
time.sleep(2)
blockdev_del_params = {
"node-name": self._usb_file_nodename(name),
}
self._send_cmd("blockdev-del", **blockdev_del_params)
time.sleep(2)
def _usb_file_nodename(self, name):
return "usb_file_node_" + name
def _usb_nodename(self, name):
return "usb_node_" + name
def _usb_devid(self, name):
return "usb_devid_" + name
def _hdd_nodename(self, name):
return "hdd_node_" + name
def _hdd_devid(self, name):
return "hdd_devid_" + name
def __del__(self):
self._close()