diff --git a/docs/api/qemu.rst b/docs/api/qemu.rst new file mode 100644 index 00000000..70fd8fc2 --- /dev/null +++ b/docs/api/qemu.rst @@ -0,0 +1,8 @@ +Qemu +--------------------- + +.. toctree:: + :glob: + :maxdepth: 2 + + qemu/* diff --git a/docs/api/qemu/v1projectsprojectidqemuvms.rst b/docs/api/qemu/v1projectsprojectidqemuvms.rst new file mode 100644 index 00000000..4a88a237 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvms.rst @@ -0,0 +1,70 @@ +/v1/projects/{project_id}/qemu/vms +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Create a new Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project + +Response status codes +********************** +- **400**: Invalid request +- **201**: Instance created +- **409**: Conflict + +Input +******* +.. raw:: html + + + + + + + + + + + + + + + + + + + + +
Name Mandatory Type Description
adapter_type ['string', 'null'] QEMU adapter type
adapters ['integer', 'null'] number of adapters
console ['integer', 'null'] console TCP port
cpu_throttling ['integer', 'null'] Percentage of CPU allowed for QEMU
hda_disk_image ['string', 'null'] QEMU hda disk image path
hdb_disk_image ['string', 'null'] QEMU hdb disk image path
initrd ['string', 'null'] QEMU initrd path
kernel_command_line ['string', 'null'] QEMU kernel command line
kernel_image ['string', 'null'] QEMU kernel image path
legacy_networking ['boolean', 'null'] Use QEMU legagy networking commands (-net syntax)
monitor ['integer', 'null'] monitor TCP port
name string QEMU VM instance name
options ['string', 'null'] Additional QEMU options
process_priority enum Possible values: realtime, very high, high, normal, low, very low, null
qemu_path string Path to QEMU
ram ['integer', 'null'] amount of RAM in MB
vm_id ['string', 'null'] QEMU VM UUID
+ +Output +******* +.. raw:: html + + + + + + + + + + + + + + + + + + + + + +
Name Mandatory Type Description
adapter_type string QEMU adapter type
adapters integer number of adapters
console integer console TCP port
cpu_throttling integer Percentage of CPU allowed for QEMU
hda_disk_image string QEMU hda disk image path
hdb_disk_image string QEMU hdb disk image path
initrd string QEMU initrd path
kernel_command_line string QEMU kernel command line
kernel_image string QEMU kernel image path
legacy_networking boolean Use QEMU legagy networking commands (-net syntax)
monitor integer monitor TCP port
name string QEMU VM instance name
options string Additional QEMU options
process_priority enum Possible values: realtime, very high, high, normal, low, very low
project_id string Project uuid
qemu_path string path to QEMU
ram integer amount of RAM in MB
vm_id string QEMU VM uuid
+ diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmid.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmid.rst new file mode 100644 index 00000000..e90b3cfd --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmid.rst @@ -0,0 +1,129 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id} +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +GET /v1/projects/**{project_id}**/qemu/vms/**{vm_id}** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Get a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **200**: Success +- **400**: Invalid request +- **404**: Instance doesn't exist + +Output +******* +.. raw:: html + + + + + + + + + + + + + + + + + + + + + +
Name Mandatory Type Description
adapter_type string QEMU adapter type
adapters integer number of adapters
console integer console TCP port
cpu_throttling integer Percentage of CPU allowed for QEMU
hda_disk_image string QEMU hda disk image path
hdb_disk_image string QEMU hdb disk image path
initrd string QEMU initrd path
kernel_command_line string QEMU kernel command line
kernel_image string QEMU kernel image path
legacy_networking boolean Use QEMU legagy networking commands (-net syntax)
monitor integer monitor TCP port
name string QEMU VM instance name
options string Additional QEMU options
process_priority enum Possible values: realtime, very high, high, normal, low, very low
project_id string Project uuid
qemu_path string path to QEMU
ram integer amount of RAM in MB
vm_id string QEMU VM uuid
+ + +PUT /v1/projects/**{project_id}**/qemu/vms/**{vm_id}** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Update a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **200**: Instance updated +- **400**: Invalid request +- **404**: Instance doesn't exist +- **409**: Conflict + +Input +******* +.. raw:: html + + + + + + + + + + + + + + + + + + + +
Name Mandatory Type Description
adapter_type ['string', 'null'] QEMU adapter type
adapters ['integer', 'null'] number of adapters
console ['integer', 'null'] console TCP port
cpu_throttling ['integer', 'null'] Percentage of CPU allowed for QEMU
hda_disk_image ['string', 'null'] QEMU hda disk image path
hdb_disk_image ['string', 'null'] QEMU hdb disk image path
initrd ['string', 'null'] QEMU initrd path
kernel_command_line ['string', 'null'] QEMU kernel command line
kernel_image ['string', 'null'] QEMU kernel image path
legacy_networking ['boolean', 'null'] Use QEMU legagy networking commands (-net syntax)
monitor ['integer', 'null'] monitor TCP port
name ['string', 'null'] QEMU VM instance name
options ['string', 'null'] Additional QEMU options
process_priority enum Possible values: realtime, very high, high, normal, low, very low, null
qemu_path ['string', 'null'] Path to QEMU
ram ['integer', 'null'] amount of RAM in MB
+ +Output +******* +.. raw:: html + + + + + + + + + + + + + + + + + + + + + +
Name Mandatory Type Description
adapter_type string QEMU adapter type
adapters integer number of adapters
console integer console TCP port
cpu_throttling integer Percentage of CPU allowed for QEMU
hda_disk_image string QEMU hda disk image path
hdb_disk_image string QEMU hdb disk image path
initrd string QEMU initrd path
kernel_command_line string QEMU kernel command line
kernel_image string QEMU kernel image path
legacy_networking boolean Use QEMU legagy networking commands (-net syntax)
monitor integer monitor TCP port
name string QEMU VM instance name
options string Additional QEMU options
process_priority enum Possible values: realtime, very high, high, normal, low, very low
project_id string Project uuid
qemu_path string path to QEMU
ram integer amount of RAM in MB
vm_id string QEMU VM uuid
+ + +DELETE /v1/projects/**{project_id}**/qemu/vms/**{vm_id}** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Delete a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: Instance deleted + diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmidadaptersadapternumberdportsportnumberdnio.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmidadaptersadapternumberdportsportnumberdnio.rst new file mode 100644 index 00000000..8d0b3733 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmidadaptersadapternumberdportsportnumberdnio.rst @@ -0,0 +1,40 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/adapters/**{adapter_number:\d+}**/ports/**{port_number:\d+}**/nio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add a NIO to a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance +- **adapter_number**: Network adapter where the nio is located +- **port_number**: Port where the nio should be added + +Response status codes +********************** +- **400**: Invalid request +- **201**: NIO created +- **404**: Instance doesn't exist + + +DELETE /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/adapters/**{adapter_number:\d+}**/ports/**{port_number:\d+}**/nio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Remove a NIO from a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance +- **adapter_number**: Network adapter where the nio is located +- **port_number**: Port from where the nio should be removed + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: NIO deleted + diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmidreload.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmidreload.rst new file mode 100644 index 00000000..04e239f6 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmidreload.rst @@ -0,0 +1,20 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id}/reload +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/reload +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reload a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: Instance reloaded + diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmidstart.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmidstart.rst new file mode 100644 index 00000000..d2649825 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmidstart.rst @@ -0,0 +1,20 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id}/start +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/start +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Start a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: Instance started + diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmidstop.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmidstop.rst new file mode 100644 index 00000000..be132747 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmidstop.rst @@ -0,0 +1,20 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id}/stop +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/stop +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Stop a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: Instance stopped + diff --git a/docs/api/qemu/v1projectsprojectidqemuvmsvmidsuspend.rst b/docs/api/qemu/v1projectsprojectidqemuvmsvmidsuspend.rst new file mode 100644 index 00000000..c9da38a2 --- /dev/null +++ b/docs/api/qemu/v1projectsprojectidqemuvmsvmidsuspend.rst @@ -0,0 +1,20 @@ +/v1/projects/{project_id}/qemu/vms/{vm_id}/suspend +---------------------------------------------------------------------------------------------------------------------- + +.. contents:: + +POST /v1/projects/**{project_id}**/qemu/vms/**{vm_id}**/suspend +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Reload a Qemu.instance + +Parameters +********** +- **project_id**: UUID for the project +- **vm_id**: UUID for the instance + +Response status codes +********************** +- **400**: Invalid request +- **404**: Instance doesn't exist +- **204**: Instance suspended + diff --git a/gns3server/handlers/qemu_handler.py b/gns3server/handlers/qemu_handler.py index 9ccbd237..11cdb928 100644 --- a/gns3server/handlers/qemu_handler.py +++ b/gns3server/handlers/qemu_handler.py @@ -24,6 +24,7 @@ from ..schemas.qemu import QEMU_CREATE_SCHEMA from ..schemas.qemu import QEMU_UPDATE_SCHEMA from ..schemas.qemu import QEMU_OBJECT_SCHEMA from ..schemas.qemu import QEMU_NIO_SCHEMA +from ..schemas.qemu import QEMU_BINARY_LIST_SCHEMA from ..modules.qemu import Qemu @@ -258,3 +259,22 @@ class QEMUHandler: vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"])) response.set_status(204) + + @classmethod + @Route.get( + r"/projects/{project_id}/qemu/binaries", + parameters={ + "project_id": "UUID for the project" + }, + status_codes={ + 200: "Success", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Get a list of available Qemu binaries", + output=QEMU_BINARY_LIST_SCHEMA) + def list_binaries(request, response): + + qemu_manager = Qemu.instance() + binaries = yield from Qemu.binary_list() + response.json(binaries) diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 58c87484..69abd613 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -20,7 +20,12 @@ Qemu server module. """ import asyncio +import os +import sys +import re +import subprocess +from ...utils.asyncio import subprocess_check_output from ..base_manager import BaseManager from .qemu_error import QemuError from .qemu_vm import QemuVM @@ -38,4 +43,68 @@ class Qemu(BaseManager): :returns: working directory name """ - return "pc-{}".format(legacy_vm_id) + return "vm-{}".format(legacy_vm_id) + + @staticmethod + def binary_list(): + """ + Gets QEMU binaries list available on the matchine + + :returns: Array of dictionnary {"path": Qemu binaries path, "version": Version of Qemu} + """ + + qemus = [] + paths = [os.getcwd()] + os.environ["PATH"].split(os.pathsep) + # look for Qemu binaries in the current working directory and $PATH + if sys.platform.startswith("win"): + # add specific Windows paths + if hasattr(sys, "frozen"): + # add any qemu dir in the same location as gns3server.exe to the list of paths + exec_dir = os.path.dirname(os.path.abspath(sys.executable)) + for f in os.listdir(exec_dir): + if f.lower().startswith("qemu"): + paths.append(os.path.join(exec_dir, f)) + + if "PROGRAMFILES(X86)" in os.environ and os.path.exists(os.environ["PROGRAMFILES(X86)"]): + paths.append(os.path.join(os.environ["PROGRAMFILES(X86)"], "qemu")) + if "PROGRAMFILES" in os.environ and os.path.exists(os.environ["PROGRAMFILES"]): + paths.append(os.path.join(os.environ["PROGRAMFILES"], "qemu")) + elif sys.platform.startswith("darwin"): + # add specific locations on Mac OS X regardless of what's in $PATH + paths.extend(["/usr/local/bin", "/opt/local/bin"]) + if hasattr(sys, "frozen"): + paths.append(os.path.abspath(os.path.join(os.getcwd(), "../../../qemu/bin/"))) + for path in paths: + try: + for f in os.listdir(path): + if (f.startswith("qemu-system") or f == "qemu" or f == "qemu.exe") and \ + os.access(os.path.join(path, f), os.X_OK) and \ + os.path.isfile(os.path.join(path, f)): + qemu_path = os.path.join(path, f) + version = yield from Qemu._get_qemu_version(qemu_path) + qemus.append({"path": qemu_path, "version": version}) + except OSError: + continue + + return qemus + + @staticmethod + @asyncio.coroutine + def _get_qemu_version(qemu_path): + """ + Gets the Qemu version. + :param qemu_path: path to Qemu + """ + + if sys.platform.startswith("win"): + return "" + try: + output = yield from subprocess_check_output(qemu_path, "-version") + match = re.search("version\s+([0-9a-z\-\.]+)", output.decode("utf-8")) + if match: + version = match.group(1) + return version + else: + raise QemuError("Could not determine the Qemu version for {}".format(qemu_path)) + except subprocess.SubprocessError as e: + raise QemuError("Error while looking for the Qemu version: {}".format(e)) diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 065f4a73..5c55c00d 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -361,3 +361,28 @@ QEMU_OBJECT_SCHEMA = { "legacy_networking", "cpu_throttling", "process_priority", "options" ] } + +QEMU_BINARY_LIST_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation for a list of qemu binaries", + "type": "array", + "items": { + "$ref": "#/definitions/QemuPath" + }, + "definitions": { + "QemuPath": { + "description": "Qemu path object", + "properties": { + "path": { + "description": "Qemu path", + "type": "string", + }, + "version": { + "description": "Qemu version", + "type": "string", + }, + }, + } + }, + "additionalProperties": False, +} diff --git a/tests/api/test_qemu.py b/tests/api/test_qemu.py index 9e62fd73..6d184b27 100644 --- a/tests/api/test_qemu.py +++ b/tests/api/test_qemu.py @@ -155,3 +155,13 @@ def test_qemu_delete_nio(server, vm): response = server.delete("/projects/{project_id}/qemu/vms/{vm_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], vm_id=vm["vm_id"]), example=True) assert response.status == 204 assert response.route == "/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio" + + +def test_qemu_list_binaries(server, vm): + ret = [{"path": "/tmp/1", "version": "2.2.0"}, + {"path": "/tmp/2", "version": "2.1.0"}] + with asyncio_patch("gns3server.modules.qemu.Qemu.binary_list", return_value=ret) as mock: + response = server.get("/projects/{project_id}/qemu/binaries".format(project_id=vm["project_id"])) + assert mock.called + assert response.status == 200 + assert response.json == ret diff --git a/tests/modules/qemu/test_qemu_manager.py b/tests/modules/qemu/test_qemu_manager.py new file mode 100644 index 00000000..9309f6da --- /dev/null +++ b/tests/modules/qemu/test_qemu_manager.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import stat +import asyncio + +from gns3server.modules.qemu import Qemu +from tests.utils import asyncio_patch + + +def test_get_qemu_version(loop): + + with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value=b"QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: + version = loop.run_until_complete(asyncio.async(Qemu._get_qemu_version("/tmp/qemu-test"))) + assert version == "2.2.0" + + +def test_binary_list(loop): + + files_to_create = ["qemu-system-x86", "qemu-system-x42", "hello"] + + for file_to_create in files_to_create: + path = os.path.join(os.environ["PATH"], file_to_create) + with open(path, "w+") as f: + f.write("1") + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + + with asyncio_patch("gns3server.modules.qemu.subprocess_check_output", return_value=b"QEMU emulator version 2.2.0, Copyright (c) 2003-2008 Fabrice Bellard") as mock: + qemus = loop.run_until_complete(asyncio.async(Qemu.binary_list())) + + assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x86"), "version": "2.2.0"} in qemus + assert {"path": os.path.join(os.environ["PATH"], "qemu-system-x42"), "version": "2.2.0"} in qemus + assert {"path": os.path.join(os.environ["PATH"], "hello"), "version": "2.2.0"} not in qemus + + +def test_get_legacy_vm_workdir_name(): + + assert Qemu.get_legacy_vm_workdir_name(42) == "vm-42"