From ea0009db6ce8e2f2823e1ac004b5771054ba9d12 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 30 Mar 2018 21:18:44 +0700 Subject: [PATCH] Save state feature for VirtualBox and VMware. New "On close" setting to select the action to execute when closing/stopping a Qemu/VirtualBox/VMware VM. --- docs/file_format.rst | 1 + gns3server/compute/qemu/qemu_vm.py | 60 +++++-------------- .../compute/virtualbox/virtualbox_vm.py | 36 +++++------ gns3server/compute/vmware/vmware_vm.py | 33 +++++----- gns3server/controller/__init__.py | 2 +- gns3server/controller/topology.py | 25 +++++++- gns3server/schemas/qemu.py | 29 ++++----- gns3server/schemas/virtualbox.py | 14 ++--- gns3server/schemas/vmware.py | 12 ++-- 9 files changed, 100 insertions(+), 112 deletions(-) diff --git a/docs/file_format.rst b/docs/file_format.rst index 89a2e3ff..5cbfaed9 100644 --- a/docs/file_format.rst +++ b/docs/file_format.rst @@ -23,6 +23,7 @@ A minimal version: The revision is the version of file format: +* 9: GNS3 2.2 * 8: GNS3 2.1 * 7: GNS3 2.0 * 6: GNS3 2.0 < beta 3 diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 1e21e068..1eec8f38 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -116,8 +116,7 @@ class QemuVM(BaseNode): self._kernel_image = "" self._kernel_command_line = "" self._legacy_networking = False - self._acpi_shutdown = False - self._save_vm_state = False + self._on_close = "power_off" self._cpu_throttling = 0 # means no CPU throttling self._process_priority = "low" @@ -573,52 +572,25 @@ class QemuVM(BaseNode): self._legacy_networking = legacy_networking @property - def acpi_shutdown(self): + def on_close(self): """ - Returns either this QEMU VM can be ACPI shutdown. + Returns the action to execute when the VM is stopped/closed - :returns: boolean + :returns: string """ - return self._acpi_shutdown + return self._on_close - @acpi_shutdown.setter - def acpi_shutdown(self, acpi_shutdown): + @on_close.setter + def on_close(self, on_close): """ - Sets either this QEMU VM can be ACPI shutdown. + Sets the action to execute when the VM is stopped/closed - :param acpi_shutdown: boolean + :param on_close: string """ - if acpi_shutdown: - log.info('QEMU VM "{name}" [{id}] has enabled ACPI shutdown'.format(name=self._name, id=self._id)) - else: - log.info('QEMU VM "{name}" [{id}] has disabled ACPI shutdown'.format(name=self._name, id=self._id)) - self._acpi_shutdown = acpi_shutdown - - @property - def save_vm_state(self): - """ - Returns either this QEMU VM state can be saved. - - :returns: boolean - """ - - return self._save_vm_state - - @save_vm_state.setter - def save_vm_state(self, save_vm_state): - """ - Sets either this QEMU VM state can be saved. - - :param save_vm_state: boolean - """ - - if save_vm_state: - log.info('QEMU VM "{name}" [{id}] has enabled the save VM state option'.format(name=self._name, id=self._id)) - else: - log.info('QEMU VM "{name}" [{id}] has disabled the save VM state option'.format(name=self._name, id=self._id)) - self._save_vm_state = save_vm_state + log.info('QEMU VM "{name}" [{id}] set the close action to "{action}"'.format(name=self._name, id=self._id, action=on_close)) + self._on_close = on_close @property def cpu_throttling(self): @@ -1030,7 +1002,7 @@ class QemuVM(BaseNode): log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid)) try: - if self.save_vm_state: + if self.on_close == "save_vm_state": yield from self._control_vm("stop") yield from self._control_vm("savevm GNS3_SAVED_STATE") wait_for_savevm = 120 @@ -1041,7 +1013,7 @@ class QemuVM(BaseNode): if status != []: break - if self.acpi_shutdown and not self.save_vm_state: + if self.on_close == "shutdown_signal": yield from self._control_vm("system_powerdown") yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=30) else: @@ -1059,7 +1031,7 @@ class QemuVM(BaseNode): log.warning('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid)) self._process = None self._stop_cpulimit() - if not self.save_vm_state: + if self.on_close != "save_vm_state": yield from self._clear_save_vm_stated() yield from super().stop() @@ -1164,7 +1136,7 @@ class QemuVM(BaseNode): if not (yield from super().close()): return False - self.acpi_shutdown = False + self.on_close = "power_off" yield from self.stop() for adapter in self._ethernet_adapters: @@ -1951,7 +1923,7 @@ class QemuVM(BaseNode): command.extend(self._monitor_options()) command.extend((yield from self._network_options())) command.extend(self._graphic()) - if not self.save_vm_state: + if self.on_close != "save_vm_state": yield from self._clear_save_vm_stated() else: command.extend((yield from self._saved_state_option())) diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index b1513b16..4faf559a 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -66,7 +66,7 @@ class VirtualBoxVM(BaseNode): self._adapters = adapters self._ethernet_adapters = {} self._headless = False - self._acpi_shutdown = False + self._on_close = "power_off" self._vmname = vmname self._use_any_adapter = False self._ram = 0 @@ -81,7 +81,7 @@ class VirtualBoxVM(BaseNode): "project_id": self.project.id, "vmname": self.vmname, "headless": self.headless, - "acpi_shutdown": self.acpi_shutdown, + "on_close": self.on_close, "adapters": self._adapters, "adapter_type": self.adapter_type, "ram": self.ram, @@ -307,7 +307,12 @@ class VirtualBoxVM(BaseNode): yield from self._stop_remote_console() vm_state = yield from self._get_vm_state() if vm_state == "running" or vm_state == "paused" or vm_state == "stuck": - if self.acpi_shutdown: + + if self.on_close == "save_vm_state": + result = yield from self._control_vm("savestate") + self.status = "stopped" + log.debug("Stop result: {}".format(result)) + elif self.on_close == "shutdown_signal": # use ACPI to shutdown the VM result = yield from self._control_vm("acpipowerbutton") trial = 0 @@ -509,7 +514,7 @@ class VirtualBoxVM(BaseNode): self.manager.port_manager.release_udp_port(udp_tunnel[1].lport, self._project) self._local_udp_tunnels = {} - self.acpi_shutdown = False + self.on_close = "power_off" yield from self.stop() if self.linked_clone: @@ -564,28 +569,25 @@ class VirtualBoxVM(BaseNode): self._headless = headless @property - def acpi_shutdown(self): + def on_close(self): """ - Returns either the VM will use ACPI shutdown + Returns the action to execute when the VM is stopped/closed - :returns: boolean + :returns: string """ - return self._acpi_shutdown + return self._on_close - @acpi_shutdown.setter - def acpi_shutdown(self, acpi_shutdown): + @on_close.setter + def on_close(self, on_close): """ - Sets either the VM will use ACPI shutdown + Sets the action to execute when the VM is stopped/closed - :param acpi_shutdown: boolean + :param on_close: string """ - if acpi_shutdown: - log.info("VirtualBox VM '{name}' [{id}] has enabled the ACPI shutdown mode".format(name=self.name, id=self.id)) - else: - log.info("VirtualBox VM '{name}' [{id}] has disabled the ACPI shutdown mode".format(name=self.name, id=self.id)) - self._acpi_shutdown = acpi_shutdown + log.info('VirtualBox VM "{name}" [{id}] set the close action to "{action}"'.format(name=self._name, id=self._id, action=on_close)) + self._on_close = on_close @property def ram(self): diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index 106ca6f3..4fc78e0b 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -58,7 +58,7 @@ class VMwareVM(BaseNode): # VMware VM settings self._headless = False self._vmx_path = vmx_path - self._acpi_shutdown = False + self._on_close = "power_off" self._adapters = 0 self._ethernet_adapters = {} self._adapter_type = "e1000" @@ -80,7 +80,7 @@ class VMwareVM(BaseNode): "project_id": self.project.id, "vmx_path": self.vmx_path, "headless": self.headless, - "acpi_shutdown": self.acpi_shutdown, + "on_close": self.on_close, "adapters": self._adapters, "adapter_type": self.adapter_type, "use_any_adapter": self.use_any_adapter, @@ -482,7 +482,9 @@ class VMwareVM(BaseNode): try: if (yield from self.is_running()): - if self.acpi_shutdown: + if self.on_close == "save_vm_state": + yield from self._control_vm("suspend") + elif self.on_close == "shutdown_signal": # use ACPI to shutdown the VM yield from self._control_vm("stop", "soft") else: @@ -563,7 +565,7 @@ class VMwareVM(BaseNode): if nio and isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) try: - self.acpi_shutdown = False + self.on_close = "power_off" yield from self.stop() except VMwareError: pass @@ -596,28 +598,25 @@ class VMwareVM(BaseNode): self._headless = headless @property - def acpi_shutdown(self): + def on_close(self): """ - Returns either the VM will use ACPI shutdown + Returns the action to execute when the VM is stopped/closed - :returns: boolean + :returns: string """ - return self._acpi_shutdown + return self._on_close - @acpi_shutdown.setter - def acpi_shutdown(self, acpi_shutdown): + @on_close.setter + def on_close(self, on_close): """ - Sets either the VM will use ACPI shutdown + Sets the action to execute when the VM is stopped/closed - :param acpi_shutdown: boolean + :param on_close: string """ - if acpi_shutdown: - log.info("VMware VM '{name}' [{id}] has enabled the ACPI shutdown mode".format(name=self.name, id=self.id)) - else: - log.info("VMware VM '{name}' [{id}] has disabled the ACPI shutdown mode".format(name=self.name, id=self.id)) - self._acpi_shutdown = acpi_shutdown + log.info('VMware VM "{name}" [{id}] set the close action to "{action}"'.format(name=self._name, id=self._id, action=on_close)) + self._on_close = on_close @property def vmx_path(self): diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 99583784..fe3ab2f0 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -123,7 +123,7 @@ class Controller: for vm in vms: # remove deprecated properties for prop in vm.copy(): - if prop in ["enable_remote_console", "use_ubridge"]: + if prop in ["enable_remote_console", "use_ubridge", "acpi_shutdown"]: del vm[prop] # remove deprecated default_symbol and hover_symbol diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index d88db985..fba3e40b 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -37,7 +37,7 @@ import logging log = logging.getLogger(__name__) -GNS3_FILE_FORMAT_REVISION = 8 +GNS3_FILE_FORMAT_REVISION = 9 def _check_topology_schema(topo): @@ -151,6 +151,10 @@ def load_topology(path): if topo["revision"] < 8: topo = _convert_2_0_0(topo, path) + # Version before GNS3 2.1 + if topo["revision"] < 9: + topo = _convert_2_1_0(topo, path) + try: _check_topology_schema(topo) except aiohttp.web.HTTPConflict as e: @@ -166,6 +170,25 @@ def load_topology(path): return topo +def _convert_2_1_0(topo, topo_path): + """ + Convert topologies from GNS3 2.1.x to 2.2 + + Changes: + * Removed acpi_shutdown option from Qemu, VMware and VirtualBox + """ + topo["revision"] = 9 + + for node in topo.get("topology", {}).get("nodes", []): + if "properties" in node: + if node["node_type"] in ("qemu", "vmware", "virtualbox"): + if "acpi_shutdown" in node["properties"]: + del node["properties"]["acpi_shutdown"] + if "save_vm_state" in node["properties"]: + del node["properties"]["save_vm_state"] + return topo + + def _convert_2_0_0(topo, topo_path): """ Convert topologies from GNS3 2.0.0 to 2.1 diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index a2118254..b49f88d3 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -184,13 +184,9 @@ QEMU_CREATE_SCHEMA = { "description": "Use QEMU legagy networking commands (-net syntax)", "type": ["boolean", "null"], }, - "acpi_shutdown": { - "description": "ACPI shutdown support", - "type": ["boolean", "null"], - }, - "save_vm_state": { - "description": "Save VM state support", - "type": ["boolean", "null"], + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "cpu_throttling": { "description": "Percentage of CPU allowed for QEMU", @@ -373,13 +369,9 @@ QEMU_UPDATE_SCHEMA = { "description": "Use QEMU legagy networking commands (-net syntax)", "type": ["boolean", "null"], }, - "acpi_shutdown": { - "description": "ACPI shutdown support", - "type": ["boolean", "null"], - }, - "save_vm_state": { - "description": "Save VM state support", - "type": ["boolean", "null"], + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "cpu_throttling": { "description": "Percentage of CPU allowed for QEMU", @@ -575,9 +567,9 @@ QEMU_OBJECT_SCHEMA = { "description": "Use QEMU legagy networking commands (-net syntax)", "type": "boolean", }, - "acpi_shutdown": { - "description": "ACPI shutdown support", - "type": "boolean", + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "save_vm_state": { "description": "Save VM state support", @@ -644,8 +636,7 @@ QEMU_OBJECT_SCHEMA = { "kernel_image_md5sum", "kernel_command_line", "legacy_networking", - "acpi_shutdown", - "save_vm_state", + "on_close", "cpu_throttling", "process_priority", "options", diff --git a/gns3server/schemas/virtualbox.py b/gns3server/schemas/virtualbox.py index dcde3ca3..5189b95b 100644 --- a/gns3server/schemas/virtualbox.py +++ b/gns3server/schemas/virtualbox.py @@ -80,10 +80,10 @@ VBOX_CREATE_SCHEMA = { "description": "Headless mode", "type": "boolean" }, - "acpi_shutdown": { - "description": "ACPI shutdown", - "type": "boolean" - } + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], + }, }, "additionalProperties": False, "required": ["name", "vmname"], @@ -131,9 +131,9 @@ VBOX_OBJECT_SCHEMA = { "description": "Headless mode", "type": "boolean" }, - "acpi_shutdown": { - "description": "ACPI shutdown", - "type": "boolean" + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "adapters": { "description": "Number of adapters", diff --git a/gns3server/schemas/vmware.py b/gns3server/schemas/vmware.py index 8a686641..a878f2e7 100644 --- a/gns3server/schemas/vmware.py +++ b/gns3server/schemas/vmware.py @@ -56,9 +56,9 @@ VMWARE_CREATE_SCHEMA = { "description": "Headless mode", "type": "boolean" }, - "acpi_shutdown": { - "description": "ACPI shutdown", - "type": "boolean" + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "adapters": { "description": "Number of adapters", @@ -122,9 +122,9 @@ VMWARE_OBJECT_SCHEMA = { "description": "Headless mode", "type": "boolean" }, - "acpi_shutdown": { - "description": "ACPI shutdown", - "type": "boolean" + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], }, "adapters": { "description": "Number of adapters",