From ff7911bd992af94b93887e7f6682f1b6a983375b Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 26 Mar 2018 18:05:49 +0700 Subject: [PATCH] Allow to resize a Qemu VM disk (extend only). --- gns3server/compute/qemu/__init__.py | 14 ++++-- gns3server/compute/qemu/qemu_vm.py | 16 +++++++ .../handlers/api/compute/qemu_handler.py | 45 ++++++++++++++++++- .../api/controller/compute_handler.py | 17 +++++++ .../handlers/api/controller/node_handler.py | 21 ++++++++- gns3server/schemas/qemu.py | 41 +++++++++++++++++ 6 files changed, 148 insertions(+), 6 deletions(-) diff --git a/gns3server/compute/qemu/__init__.py b/gns3server/compute/qemu/__init__.py index 47b11dd0..e8564de9 100644 --- a/gns3server/compute/qemu/__init__.py +++ b/gns3server/compute/qemu/__init__.py @@ -296,7 +296,7 @@ class Qemu(BaseManager): raise QemuError("Could not create disk image {}:{}".format(path, e)) @asyncio.coroutine - def resize_disk(self, qemu_img, path, size): + def resize_disk(self, qemu_img, path, extend): """ Resize a Qemu disk with qemu-img @@ -305,11 +305,17 @@ class Qemu(BaseManager): :param size: size """ + if not os.path.isabs(path): + directory = self.get_images_directory() + os.makedirs(directory, exist_ok=True) + path = os.path.join(directory, os.path.basename(path)) + try: if not os.path.exists(path): - raise QemuError("Qemu image '{}' does not exist".format(path)) - command = [qemu_img, "resize", path, "{}M".format(size)] + raise QemuError("Qemu disk '{}' does not exist".format(path)) + command = [qemu_img, "resize", path, "+{}M".format(extend)] process = yield from asyncio.create_subprocess_exec(*command) yield from process.wait() + log.info("Qemu disk '{}' extended by {} MB".format(path, extend)) except (OSError, subprocess.SubprocessError) as e: - raise QemuError("Could not create disk image {}:{}".format(path, e)) + raise QemuError("Could not update disk image {}:{}".format(path, e)) diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index bbd42d50..0afb6402 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1586,6 +1586,22 @@ class QemuVM(BaseNode): return options + @asyncio.coroutine + def resize_disk(self, drive_name, extend): + + if self.is_running(): + raise QemuError("Cannot resize {} while the VM is running".format(drive_name)) + + if self.linked_clone: + disk_image_path = os.path.join(self.working_dir, "{}_disk.qcow2".format(drive_name)) + else: + disk_image_path = getattr(self, "{}_disk_image".format(drive_name)) + + if not os.path.exists(disk_image_path): + raise QemuError("Disk path '{}' does not exist".format(disk_image_path)) + qemu_img_path = self._get_qemu_img() + yield from self.manager.resize_disk(qemu_img_path, disk_image_path, extend) + def _cdrom_option(self): options = [] diff --git a/gns3server/handlers/api/compute/qemu_handler.py b/gns3server/handlers/api/compute/qemu_handler.py index c68c5812..bc99621e 100644 --- a/gns3server/handlers/api/compute/qemu_handler.py +++ b/gns3server/handlers/api/compute/qemu_handler.py @@ -35,10 +35,12 @@ from gns3server.schemas.qemu import ( QEMU_CREATE_SCHEMA, QEMU_UPDATE_SCHEMA, QEMU_OBJECT_SCHEMA, + QEMU_RESIZE_SCHEMA, QEMU_BINARY_LIST_SCHEMA, QEMU_BINARY_FILTER_SCHEMA, QEMU_CAPABILITY_LIST_SCHEMA, - QEMU_IMAGE_CREATE_SCHEMA + QEMU_IMAGE_CREATE_SCHEMA, + QEMU_IMAGE_UPDATE_SCHEMA ) @@ -163,6 +165,25 @@ class QEMUHandler: response.set_status(201) response.json(new_node) + @Route.post( + r"/projects/{project_id}/qemu/nodes/{node_id}/resize_disk", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 201: "Instance updated", + 404: "Instance doesn't exist" + }, + description="Resize a Qemu VM disk image", + input=QEMU_RESIZE_SCHEMA) + def resize_disk(request, response): + + qemu_manager = Qemu.instance() + vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"]) + yield from vm.resize_disk(request.json["drive_name"], request.json["extend"]) + response.set_status(201) + @Route.post( r"/projects/{project_id}/qemu/nodes/{node_id}/start", parameters={ @@ -458,6 +479,28 @@ class QEMUHandler: yield from Qemu.instance().create_disk(qemu_img, path, request.json) response.set_status(201) + @Route.put( + r"/qemu/img", + status_codes={ + 201: "Image Updated", + }, + description="Update a Qemu image", + input=QEMU_IMAGE_UPDATE_SCHEMA + ) + def update_img(request, response): + + qemu_img = request.json.pop("qemu_img") + path = request.json.pop("path") + if os.path.isabs(path): + config = Config.instance() + if config.get_section_config("Server").getboolean("local", False) is False: + response.set_status(403) + return + + if "extend" in request.json: + yield from Qemu.instance().resize_disk(qemu_img, path, request.json.pop("extend")) + response.set_status(201) + @Route.get( r"/qemu/images", status_codes={ diff --git a/gns3server/handlers/api/controller/compute_handler.py b/gns3server/handlers/api/controller/compute_handler.py index 8695c900..dab908f7 100644 --- a/gns3server/handlers/api/controller/compute_handler.py +++ b/gns3server/handlers/api/controller/compute_handler.py @@ -155,6 +155,23 @@ class ComputeHandler: res = yield from compute.forward("POST", request.match_info["emulator"], request.match_info["action"], data=request.content) response.json(res) + @Route.put( + r"/computes/{compute_id}/{emulator}/{action:.+}", + parameters={ + "compute_id": "Compute UUID" + }, + status_codes={ + 200: "OK", + 404: "Instance doesn't exist" + }, + raw=True, + description="Forward call specific to compute node. Read the full compute API for available actions") + def put_forward(request, response): + controller = Controller.instance() + compute = controller.get_compute(request.match_info["compute_id"]) + res = yield from compute.forward("PUT", request.match_info["emulator"], request.match_info["action"], data=request.content) + response.json(res) + @Route.get( r"/computes/{compute_id}", description="Get a compute server information", diff --git a/gns3server/handlers/api/controller/node_handler.py b/gns3server/handlers/api/controller/node_handler.py index 808390ab..417abe47 100644 --- a/gns3server/handlers/api/controller/node_handler.py +++ b/gns3server/handlers/api/controller/node_handler.py @@ -286,7 +286,7 @@ class NodeHandler: project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"]) node = project.get_node(request.match_info["node_id"]) - yield from node.reload() + #yield from node.reload() response.json(node) response.set_status(201) @@ -347,6 +347,25 @@ class NodeHandler: response.json(idle) response.set_status(200) + @Route.post( + r"/projects/{project_id}/nodes/{node_id}/resize_disk", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 201: "Disk image resized", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Reload a node instance") + def resize_disk(request, response): + + project = yield from Controller.instance().get_loaded_project(request.match_info["project_id"]) + node = project.get_node(request.match_info["node_id"]) + yield from node.post("/resize_disk", request.json) + response.set_status(201) + @Route.get( r"/projects/{project_id}/nodes/{node_id}/files/{path:.+}", parameters={ diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 2c6cb8fb..95abaf8a 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -641,6 +641,25 @@ QEMU_OBJECT_SCHEMA = { "status"] } +QEMU_RESIZE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Resize a disk in a QEMU VM", + "type": "object", + "properties": { + "drive_name": { + "description": "Absolute or relative path of the image", + "enum": ["hda", "hdb", "hdc", "hdd"] + }, + "extend": { + "description": "Number of Megabytes to extend the image", + "type": "integer" + }, + # TODO: support shrink? (could be dangerous) + }, + "required": ["drive_name", "extend"], + "additionalProperties": False +} + QEMU_BINARY_FILTER_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation for a list of QEMU capabilities", @@ -758,3 +777,25 @@ QEMU_IMAGE_CREATE_SCHEMA = { "required": ["qemu_img", "path", "format", "size"], "additionalProperties": False } + +QEMU_IMAGE_UPDATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Update an existing QEMU image", + "type": "object", + "properties": { + "qemu_img": { + "description": "Path to the qemu-img binary", + "type": "string" + }, + "path": { + "description": "Absolute or relative path of the image", + "type": "string" + }, + "extend": { + "description": "Number of Megabytes to extend the image", + "type": "integer" + }, + }, + "required": ["qemu_img", "path"], + "additionalProperties": False +}