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:
parent
64949f5d04
commit
3d1ee4da3f
@ -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))
|
||||||
|
@ -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] == ".":
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user