1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Support for suspend to disk / resume (Qemu).

This commit is contained in:
grossmj 2018-03-30 19:28:22 +07:00
parent 64949f5d04
commit 3d1ee4da3f
3 changed files with 113 additions and 4 deletions

View File

@ -31,6 +31,7 @@ import socket
import gns3server import gns3server
import subprocess import subprocess
import time import time
import json
from gns3server.utils import parse_version from gns3server.utils import parse_version
from gns3server.utils.asyncio import subprocess_check_output, cancellable_wait_run_in_executor 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._kernel_command_line = ""
self._legacy_networking = False self._legacy_networking = False
self._acpi_shutdown = False self._acpi_shutdown = False
self._save_vm_state = False
self._cpu_throttling = 0 # means no CPU throttling self._cpu_throttling = 0 # means no CPU throttling
self._process_priority = "low" 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)) log.info('QEMU VM "{name}" [{id}] has disabled ACPI shutdown'.format(name=self._name, id=self._id))
self._acpi_shutdown = acpi_shutdown 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 @property
def cpu_throttling(self): def cpu_throttling(self):
""" """
@ -1003,7 +1029,19 @@ class QemuVM(BaseNode):
if self.is_running(): if self.is_running():
log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid)) log.info('Stopping QEMU VM "{}" PID={}'.format(self._name, self._process.pid))
try: 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 self._control_vm("system_powerdown")
yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=30) yield from gns3server.utils.asyncio.wait_for_process_termination(self._process, timeout=30)
else: else:
@ -1021,6 +1059,8 @@ class QemuVM(BaseNode):
log.warning('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid)) log.warning('QEMU VM "{}" PID={} is still running'.format(self._name, self._process.pid))
self._process = None self._process = None
self._stop_cpulimit() self._stop_cpulimit()
if not self.save_vm_state:
yield from self._clear_save_vm_stated()
yield from super().stop() yield from super().stop()
@asyncio.coroutine @asyncio.coroutine
@ -1480,7 +1520,7 @@ class QemuVM(BaseNode):
def _get_qemu_img(self): def _get_qemu_img(self):
""" """
Search the qemu-img binary in the same binary of the qemu binary 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 :returns: qemu-img path or raise an error
""" """
@ -1814,6 +1854,57 @@ class QemuVM(BaseNode):
return True return True
return False 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 @asyncio.coroutine
def _build_command(self): def _build_command(self):
""" """
@ -1860,6 +1951,11 @@ class QemuVM(BaseNode):
command.extend(self._monitor_options()) command.extend(self._monitor_options())
command.extend((yield from self._network_options())) command.extend((yield from self._network_options()))
command.extend(self._graphic()) 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: if additional_options:
try: try:
command.extend(shlex.split(additional_options)) command.extend(shlex.split(additional_options))

View File

@ -544,8 +544,8 @@ class QEMUHandler:
def download_image(request, response): def download_image(request, response):
filename = request.match_info["filename"] filename = request.match_info["filename"]
iou_manager = Qemu.instance() qemu_manager = Qemu.instance()
image_path = iou_manager.get_abs_image_path(filename) image_path = qemu_manager.get_abs_image_path(filename)
# Raise error if user try to escape # Raise error if user try to escape
if filename[0] == ".": if filename[0] == ".":

View File

@ -188,6 +188,10 @@ QEMU_CREATE_SCHEMA = {
"description": "ACPI shutdown support", "description": "ACPI shutdown support",
"type": ["boolean", "null"], "type": ["boolean", "null"],
}, },
"save_vm_state": {
"description": "Save VM state support",
"type": ["boolean", "null"],
},
"cpu_throttling": { "cpu_throttling": {
"description": "Percentage of CPU allowed for QEMU", "description": "Percentage of CPU allowed for QEMU",
"minimum": 0, "minimum": 0,
@ -373,6 +377,10 @@ QEMU_UPDATE_SCHEMA = {
"description": "ACPI shutdown support", "description": "ACPI shutdown support",
"type": ["boolean", "null"], "type": ["boolean", "null"],
}, },
"save_vm_state": {
"description": "Save VM state support",
"type": ["boolean", "null"],
},
"cpu_throttling": { "cpu_throttling": {
"description": "Percentage of CPU allowed for QEMU", "description": "Percentage of CPU allowed for QEMU",
"minimum": 0, "minimum": 0,
@ -571,6 +579,10 @@ QEMU_OBJECT_SCHEMA = {
"description": "ACPI shutdown support", "description": "ACPI shutdown support",
"type": "boolean", "type": "boolean",
}, },
"save_vm_state": {
"description": "Save VM state support",
"type": ["boolean", "null"],
},
"cpu_throttling": { "cpu_throttling": {
"description": "Percentage of CPU allowed for QEMU", "description": "Percentage of CPU allowed for QEMU",
"minimum": 0, "minimum": 0,
@ -633,6 +645,7 @@ QEMU_OBJECT_SCHEMA = {
"kernel_command_line", "kernel_command_line",
"legacy_networking", "legacy_networking",
"acpi_shutdown", "acpi_shutdown",
"save_vm_state",
"cpu_throttling", "cpu_throttling",
"process_priority", "process_priority",
"options", "options",