1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-26 00:38:10 +00:00

Merge pull request #60 from shmygov/suspendqemu

QEMU VM suspend/resume and reload
This commit is contained in:
Jeremy Grossmann 2014-12-23 12:27:18 -07:00
commit e9991affc3
3 changed files with 167 additions and 10 deletions

View File

@ -67,11 +67,14 @@ class Qemu(IModule):
qemu_config = config.get_section_config(name.upper())
self._console_start_port_range = qemu_config.get("console_start_port_range", 5001)
self._console_end_port_range = qemu_config.get("console_end_port_range", 5500)
self._monitor_start_port_range = qemu_config.get("monitor_start_port_range", 5501)
self._monitor_end_port_range = qemu_config.get("monitor_end_port_range", 6000)
self._allocated_udp_ports = []
self._udp_start_port_range = qemu_config.get("udp_start_port_range", 40001)
self._udp_end_port_range = qemu_config.get("udp_end_port_range", 45500)
self._host = qemu_config.get("host", kwargs["host"])
self._console_host = qemu_config.get("console_host", kwargs["console_host"])
self._monitor_host = qemu_config.get("monitor_host", "0.0.0.0")
self._projects_dir = kwargs["projects_dir"]
self._tempdir = kwargs["temp_dir"]
self._working_dir = self._projects_dir
@ -137,6 +140,8 @@ class Qemu(IModule):
- project_name
- console_start_port_range
- console_end_port_range
- monitor_start_port_range
- monitor_end_port_range
- udp_start_port_range
- udp_end_port_range
@ -174,6 +179,10 @@ class Qemu(IModule):
self._console_start_port_range = request["console_start_port_range"]
self._console_end_port_range = request["console_end_port_range"]
if "monitor_start_port_range" in request and "monitor_end_port_range" in request:
self._monitor_start_port_range = request["monitor_start_port_range"]
self._monitor_end_port_range = request["monitor_end_port_range"]
if "udp_start_port_range" in request and "udp_end_port_range" in request:
self._udp_start_port_range = request["udp_start_port_range"]
self._udp_end_port_range = request["udp_end_port_range"]
@ -191,6 +200,7 @@ class Qemu(IModule):
Optional request parameters:
- console (QEMU VM console port)
- monitor (QEMU VM monitor port)
Response parameters:
- id (QEMU VM instance identifier)
@ -207,6 +217,7 @@ class Qemu(IModule):
name = request["name"]
qemu_path = request["qemu_path"]
console = request.get("console")
monitor = request.get("monitor")
qemu_id = request.get("qemu_id")
try:
@ -218,7 +229,11 @@ class Qemu(IModule):
console,
self._console_host,
self._console_start_port_range,
self._console_end_port_range)
self._console_end_port_range,
monitor,
self._monitor_host,
self._monitor_start_port_range,
self._monitor_end_port_range)
except QemuError as e:
self.send_custom_error(str(e))

View File

@ -26,6 +26,7 @@ import random
import subprocess
import shlex
import ntpath
import telnetlib
from gns3server.config import Config
from gns3dms.cloud.rackspace_ctrl import get_provider
@ -52,10 +53,15 @@ class QemuVM(object):
:param console_host: IP address to bind for console connections
:param console_start_port_range: TCP console port range start
:param console_end_port_range: TCP console port range end
:param monitor: TCP monitor port
:param monitor_host: IP address to bind for monitor connections
:param monitor_start_port_range: TCP monitor port range start
:param monitor_end_port_range: TCP monitor port range end
"""
_instances = []
_allocated_console_ports = []
_allocated_monitor_ports = []
def __init__(self,
name,
@ -66,7 +72,11 @@ class QemuVM(object):
console=None,
console_host="0.0.0.0",
console_start_port_range=5001,
console_end_port_range=5500):
console_end_port_range=5500,
monitor=None,
monitor_host="0.0.0.0",
monitor_start_port_range=5501,
monitor_end_port_range=6000):
if not qemu_id:
self._id = 0
@ -95,6 +105,9 @@ class QemuVM(object):
self._console_host = console_host
self._console_start_port_range = console_start_port_range
self._console_end_port_range = console_end_port_range
self._monitor_host = monitor_host
self._monitor_start_port_range = monitor_start_port_range
self._monitor_end_port_range = monitor_end_port_range
self._cloud_path = None
# QEMU settings
@ -104,6 +117,7 @@ class QemuVM(object):
self._options = ""
self._ram = 256
self._console = console
self._monitor = monitor
self._ethernet_adapters = []
self._adapter_type = "e1000"
self._initrd = ""
@ -135,6 +149,20 @@ class QemuVM(object):
raise QemuError("Console port {} is already used by another QEMU VM".format(console))
self._allocated_console_ports.append(self._console)
if not self._monitor:
# allocate a monitor port
try:
self._monitor = find_unused_port(self._monitor_start_port_range,
self._monitor_end_port_range,
self._monitor_host,
ignore_ports=self._allocated_monitor_ports)
except Exception as e:
raise QemuError(e)
if self._monitor in self._allocated_monitor_ports:
raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
self._allocated_monitor_ports.append(self._monitor)
self.adapters = 1 # creates 1 adapter by default
log.info("QEMU VM {name} [id={id}] has been created".format(name=self._name,
id=self._id))
@ -155,6 +183,7 @@ class QemuVM(object):
"adapters": self.adapters,
"adapter_type": self._adapter_type,
"console": self._console,
"monitor": self._monitor,
"initrd": self._initrd,
"kernel_image": self._kernel_image,
"kernel_command_line": self._kernel_command_line,
@ -183,6 +212,7 @@ class QemuVM(object):
cls._instances.clear()
cls._allocated_console_ports.clear()
cls._allocated_monitor_ports.clear()
@property
def name(self):
@ -267,6 +297,35 @@ class QemuVM(object):
id=self._id,
port=console))
@property
def monitor(self):
"""
Returns the TCP monitor port.
:returns: monitor port (integer)
"""
return self._monitor
@monitor.setter
def monitor(self, monitor):
"""
Sets the TCP monitor port.
:param monitor: monitor port (integer)
"""
if monitor in self._allocated_monitor_ports:
raise QemuError("Monitor port {} is already used by another QEMU VM".format(monitor))
self._allocated_monitor_ports.remove(self._monitor)
self._monitor = monitor
self._allocated_monitor_ports.append(self._monitor)
log.info("QEMU VM {name} [id={id}]: monitor port set to {port}".format(name=self._name,
id=self._id,
port=monitor))
def delete(self):
"""
Deletes this QEMU VM.
@ -276,8 +335,11 @@ class QemuVM(object):
if self._id in self._instances:
self._instances.remove(self._id)
if self.console and self.console in self._allocated_console_ports:
self._allocated_console_ports.remove(self.console)
if self._console and self._console in self._allocated_console_ports:
self._allocated_console_ports.remove(self._console)
if self._monitor and self._monitor in self._allocated_monitor_ports:
self._allocated_monitor_ports.remove(self._monitor)
log.info("QEMU VM {name} [id={id}] has been deleted".format(name=self._name,
id=self._id))
@ -291,8 +353,11 @@ class QemuVM(object):
if self._id in self._instances:
self._instances.remove(self._id)
if self.console:
self._allocated_console_ports.remove(self.console)
if self._console:
self._allocated_console_ports.remove(self._console)
if self._monitor:
self._allocated_monitor_ports.remove(self._monitor)
try:
shutil.rmtree(self._working_dir)
@ -718,7 +783,13 @@ class QemuVM(object):
Starts this QEMU VM.
"""
if not self.is_running():
if self.is_running():
# resume the VM if it is paused
self.resume()
return
else:
if not os.path.isfile(self._qemu_path) or not os.path.exists(self._qemu_path):
found = False
@ -825,26 +896,77 @@ class QemuVM(object):
self._started = False
self._stop_cpulimit()
def _control_vm(self, command, expected=None, timeout=30):
"""
Executes a command with QEMU monitor when this VM is running.
:param command: QEMU monitor command (e.g. info status, stop etc.)
:param timeout: how long to wait for QEMU monitor
:returns: result of the command (string)
"""
result = None
if self.is_running() and self._monitor:
log.debug("Execute QEMU monitor command: {}".format(command))
tn = telnetlib.Telnet(self._monitor_host, self._monitor, timeout=timeout)
try:
tn.write(command.encode('ascii') + b"\n")
except OSError as e:
log.warn("Could not write to QEMU monitor: {}".format(e))
tn.close()
return result
if expected:
try:
ind, obj, dat = tn.expect(list=expected, timeout=timeout)
if ind >= 0:
result = expected[ind].decode('ascii')
except EOFError as e:
log.warn("Could not read from QEMU monitor: {}".format(e))
tn.close()
return result
def _get_vm_status(self):
"""
Returns this VM suspend status (running|paused)
:returns: status (string)
"""
result = self._control_vm("info status", [b"running", b"paused"])
return result
def suspend(self):
"""
Suspends this QEMU VM.
"""
pass
vm_status = self._get_vm_status()
if vm_status == "running":
self._control_vm("stop")
log.debug("QEMU VM has been suspended")
else:
log.info("QEMU VM is not running to be suspended, current status is {}".format(vm_status))
def reload(self):
"""
Reloads this QEMU VM.
"""
pass
self._control_vm("system_reset")
log.debug("QEMU VM has been reset")
def resume(self):
"""
Resumes this QEMU VM.
"""
pass
vm_status = self._get_vm_status()
if vm_status == "paused":
self._control_vm("cont")
log.debug("QEMU VM has been resumed")
else:
log.info("QEMU VM is not paused to be resumed, current status is {}".format(vm_status))
def port_add_nio_binding(self, adapter_id, nio):
"""
@ -941,6 +1063,13 @@ class QemuVM(object):
else:
return []
def _monitor_options(self):
if self._monitor:
return ["-monitor", "telnet:{}:{},server,nowait".format(self._monitor_host, self._monitor)]
else:
return []
def _disk_options(self):
options = []
@ -1047,6 +1176,7 @@ class QemuVM(object):
command.extend(self._disk_options())
command.extend(self._linux_boot_options())
command.extend(self._serial_options())
command.extend(self._monitor_options())
additional_options = self._options.strip()
if additional_options:
command.extend(shlex.split(additional_options))

View File

@ -41,6 +41,12 @@ QEMU_CREATE_SCHEMA = {
"maximum": 65535,
"type": "integer"
},
"monitor": {
"description": "monitor TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
},
"additionalProperties": False,
"required": ["name", "qemu_path"],
@ -108,6 +114,12 @@ QEMU_UPDATE_SCHEMA = {
"maximum": 65535,
"type": "integer"
},
"monitor": {
"description": "monitor TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"initrd": {
"description": "QEMU initrd path",
"type": "string",