mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 03:08:14 +00:00
Support for suspend to disk / resume (Qemu).
This commit is contained in:
parent
64949f5d04
commit
3d1ee4da3f
@ -31,6 +31,7 @@ import socket
|
||||
import gns3server
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
from gns3server.utils import parse_version
|
||||
from gns3server.utils.asyncio import subprocess_check_output, cancellable_wait_run_in_executor
|
||||
@ -116,6 +117,7 @@ class QemuVM(BaseNode):
|
||||
self._kernel_command_line = ""
|
||||
self._legacy_networking = False
|
||||
self._acpi_shutdown = False
|
||||
self._save_vm_state = False
|
||||
self._cpu_throttling = 0 # means no CPU throttling
|
||||
self._process_priority = "low"
|
||||
|
||||
@ -594,6 +596,30 @@ class QemuVM(BaseNode):
|
||||
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
|
||||
|
||||
@property
|
||||
def cpu_throttling(self):
|
||||
"""
|
||||
@ -1003,7 +1029,19 @@ class QemuVM(BaseNode):
|
||||
if self.is_running():
|
||||
log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid))
|
||||
try:
|
||||
if self.acpi_shutdown:
|
||||
|
||||
if self.save_vm_state:
|
||||
yield from self._control_vm("stop")
|
||||
yield from self._control_vm("savevm GNS3_SAVED_STATE")
|
||||
wait_for_savevm = 120
|
||||
while wait_for_savevm:
|
||||
yield from asyncio.sleep(1)
|
||||
status = yield from self._saved_state_option()
|
||||
wait_for_savevm -= 1
|
||||
if status != []:
|
||||
break
|
||||
|
||||
if self.acpi_shutdown and not self.save_vm_state:
|
||||
yield from self._control_vm("system_powerdown")
|
||||
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=30)
|
||||
else:
|
||||
@ -1021,6 +1059,8 @@ 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:
|
||||
yield from self._clear_save_vm_stated()
|
||||
yield from super().stop()
|
||||
|
||||
@asyncio.coroutine
|
||||
@ -1480,7 +1520,7 @@ class QemuVM(BaseNode):
|
||||
def _get_qemu_img(self):
|
||||
"""
|
||||
Search the qemu-img binary in the same binary of the qemu binary
|
||||
for avoiding version incompatibily.
|
||||
for avoiding version incompatibility.
|
||||
|
||||
:returns: qemu-img path or raise an error
|
||||
"""
|
||||
@ -1814,6 +1854,57 @@ class QemuVM(BaseNode):
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def _clear_save_vm_stated(self, snapshot_name="GNS3_SAVED_STATE"):
|
||||
|
||||
drives = ["a", "b", "c", "d"]
|
||||
qemu_img_path = self._get_qemu_img()
|
||||
for disk_index, drive in enumerate(drives):
|
||||
disk_image = getattr(self, "_hd{}_disk_image".format(drive))
|
||||
if not disk_image:
|
||||
continue
|
||||
try:
|
||||
if self.linked_clone:
|
||||
disk = os.path.join(self.working_dir, "hd{}_disk.qcow2".format(drive))
|
||||
else:
|
||||
disk = disk_image
|
||||
command = [qemu_img_path, "snapshot", "-c", snapshot_name, disk]
|
||||
retcode = yield from self._qemu_img_exec(command)
|
||||
if retcode:
|
||||
stdout = self.read_qemu_img_stdout()
|
||||
log.warning("Could not delete saved VM state from disk {}: {}".format(disk, stdout))
|
||||
else:
|
||||
log.info("Deleted saved VM state from disk {}".format(disk))
|
||||
except subprocess.SubprocessError as e:
|
||||
raise QemuError("Error while looking for the Qemu VM saved state snapshot: {}".format(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _saved_state_option(self, snapshot_name="GNS3_SAVED_STATE"):
|
||||
|
||||
drives = ["a", "b", "c", "d"]
|
||||
qemu_img_path = self._get_qemu_img()
|
||||
for disk_index, drive in enumerate(drives):
|
||||
disk_image = getattr(self, "_hd{}_disk_image".format(drive))
|
||||
if not disk_image:
|
||||
continue
|
||||
try:
|
||||
if self.linked_clone:
|
||||
disk = os.path.join(self.working_dir, "hd{}_disk.qcow2".format(drive))
|
||||
else:
|
||||
disk = disk_image
|
||||
output = yield from subprocess_check_output(qemu_img_path, "info", "--output=json", disk)
|
||||
json_data = json.loads(output)
|
||||
if "snapshots" in json_data:
|
||||
for snapshot in json_data["snapshots"]:
|
||||
if snapshot["name"] == snapshot_name:
|
||||
log.info('QEMU VM "{name}" [{id}] VM saved state detected (snapshot name: {snapshot})'.format(name=self._name,
|
||||
id=self.id,
|
||||
snapshot=snapshot_name))
|
||||
return ["-loadvm", snapshot_name]
|
||||
except subprocess.SubprocessError as e:
|
||||
raise QemuError("Error while looking for the Qemu VM saved state snapshot: {}".format(e))
|
||||
return []
|
||||
|
||||
@asyncio.coroutine
|
||||
def _build_command(self):
|
||||
"""
|
||||
@ -1860,6 +1951,11 @@ 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:
|
||||
yield from self._clear_save_vm_stated()
|
||||
else:
|
||||
command.extend((yield from self._saved_state_option()))
|
||||
|
||||
if additional_options:
|
||||
try:
|
||||
command.extend(shlex.split(additional_options))
|
||||
|
@ -544,8 +544,8 @@ class QEMUHandler:
|
||||
def download_image(request, response):
|
||||
filename = request.match_info["filename"]
|
||||
|
||||
iou_manager = Qemu.instance()
|
||||
image_path = iou_manager.get_abs_image_path(filename)
|
||||
qemu_manager = Qemu.instance()
|
||||
image_path = qemu_manager.get_abs_image_path(filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
|
@ -188,6 +188,10 @@ QEMU_CREATE_SCHEMA = {
|
||||
"description": "ACPI shutdown support",
|
||||
"type": ["boolean", "null"],
|
||||
},
|
||||
"save_vm_state": {
|
||||
"description": "Save VM state support",
|
||||
"type": ["boolean", "null"],
|
||||
},
|
||||
"cpu_throttling": {
|
||||
"description": "Percentage of CPU allowed for QEMU",
|
||||
"minimum": 0,
|
||||
@ -373,6 +377,10 @@ QEMU_UPDATE_SCHEMA = {
|
||||
"description": "ACPI shutdown support",
|
||||
"type": ["boolean", "null"],
|
||||
},
|
||||
"save_vm_state": {
|
||||
"description": "Save VM state support",
|
||||
"type": ["boolean", "null"],
|
||||
},
|
||||
"cpu_throttling": {
|
||||
"description": "Percentage of CPU allowed for QEMU",
|
||||
"minimum": 0,
|
||||
@ -571,6 +579,10 @@ QEMU_OBJECT_SCHEMA = {
|
||||
"description": "ACPI shutdown support",
|
||||
"type": "boolean",
|
||||
},
|
||||
"save_vm_state": {
|
||||
"description": "Save VM state support",
|
||||
"type": ["boolean", "null"],
|
||||
},
|
||||
"cpu_throttling": {
|
||||
"description": "Percentage of CPU allowed for QEMU",
|
||||
"minimum": 0,
|
||||
@ -633,6 +645,7 @@ QEMU_OBJECT_SCHEMA = {
|
||||
"kernel_command_line",
|
||||
"legacy_networking",
|
||||
"acpi_shutdown",
|
||||
"save_vm_state",
|
||||
"cpu_throttling",
|
||||
"process_priority",
|
||||
"options",
|
||||
|
Loading…
Reference in New Issue
Block a user