diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index 1935b10c..7d0b5efd 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -67,26 +67,6 @@ class Qemu(IModule): def __init__(self, name, *args, **kwargs): - # get the qemu-img location - config = Config.instance() - qemu_config = config.get_section_config(name.upper()) - self._qemu_img_path = qemu_config.get("qemu_img_path") - if not self._qemu_img_path or not os.path.isfile(self._qemu_img_path): - paths = [os.getcwd()] + os.environ["PATH"].split(":") - # look for qemu-img in the current working directory and $PATH - for path in paths: - try: - if "qemu-img" in os.listdir(path) and os.access(os.path.join(path, "qemu-img"), os.X_OK): - self._qemu_img_path = os.path.join(path, "qemu-img") - break - except OSError: - continue - - if not self._qemu_img_path: - log.warning("qemu-img couldn't be found!") - elif not os.access(self._qemu_img_path, os.X_OK): - log.warning("qemu-img is not executable") - # a new process start when calling IModule IModule.__init__(self, name, *args, **kwargs) self._qemu_instances = {} @@ -173,13 +153,6 @@ class Qemu(IModule): self.send_param_error() return - if "qemu_img_path" in request and request["qemu_img_path"]: - self._qemu_img_path = request["qemu_img_path"] - log.info("QEMU image utility path set to {}".format(self._qemu_img_path)) - for qemu_id in self._qemu_instances: - qemu_instance = self._qemu_instances[qemu_id] - qemu_instance.qemu_img_path = self._qemu_img_path - if "working_dir" in request: new_working_dir = request["working_dir"] log.info("this server is local with working directory path to {}".format(new_working_dir)) @@ -245,7 +218,6 @@ class Qemu(IModule): try: qemu_instance = QemuVM(name, qemu_path, - self._qemu_img_path, self._working_dir, self._host, qemu_id, diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 2a32b035..ce341780 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -23,6 +23,7 @@ import os import shutil import random import subprocess +import shlex from .qemu_error import QemuError from .adapters.ethernet_adapter import EthernetAdapter @@ -39,7 +40,6 @@ class QemuVM(object): :param name: name of this QEMU VM :param qemu_path: path to the QEMU binary - :param qemu_img_path: path to the QEMU IMG binary :param working_dir: path to a working directory :param host: host/address to bind for console and UDP connections :param qemu_id: QEMU VM instance ID @@ -54,7 +54,6 @@ class QemuVM(object): def __init__(self, name, qemu_path, - qemu_img_path, working_dir, host="127.0.0.1", qemu_id=None, @@ -85,18 +84,21 @@ class QemuVM(object): self._started = False self._process = None self._stdout_file = "" - self._qemu_img_path = qemu_img_path self._console_start_port_range = console_start_port_range self._console_end_port_range = console_end_port_range # QEMU settings self._qemu_path = qemu_path - self._disk_image = "" + self._hda_disk_image = "" + self._hdb_disk_image = "" self._options = "" self._ram = 256 self._console = console self._ethernet_adapters = [] self._adapter_type = "e1000" + self._initrd = "" + self._kernel_image = "" + self._kernel_command_line = "" working_dir_path = os.path.join(working_dir, "qemu", "vm-{}".format(self._id)) @@ -134,11 +136,15 @@ class QemuVM(object): qemu_defaults = {"name": self._name, "qemu_path": self._qemu_path, "ram": self._ram, - "disk_image": self._disk_image, + "hda_disk_image": self._hda_disk_image, + "hdb_disk_image": self._hdb_disk_image, "options": self._options, "adapters": self.adapters, "adapter_type": self._adapter_type, - "console": self._console} + "console": self._console, + "initrd": self._initrd, + "kernel_image": self._kernel_image, + "kernel_command_line": self._kernel_command_line} return qemu_defaults @@ -306,50 +312,51 @@ class QemuVM(object): self._qemu_path = qemu_path @property - def qemu_img_path(self): + def hda_disk_image(self): """ - Returns the QEMU IMG binary path for this QEMU VM. + Returns the hda disk image path for this QEMU VM. - :returns: QEMU IMG path + :returns: QEMU hda disk image path """ - return self._qemu_img_path + return self._hda_disk_image - @qemu_img_path.setter - def qemu_img_path(self, qemu_img_path): + @hda_disk_image.setter + def hda_disk_image(self, hda_disk_image): """ - Sets the QEMU IMG binary path this QEMU VM. + Sets the hda disk image for this QEMU VM. - :param qemu_img_path: QEMU IMG path + :param hda_disk_image: QEMU hda disk image path """ - log.info("QEMU VM {name} [id={id}] has set the QEMU IMG path to {qemu_img_path}".format(name=self._name, - id=self._id, - qemu_img_path=qemu_img_path)) - self._qemu_img_path = qemu_img_path + log.info("QEMU VM {name} [id={id}] has set the QEMU hda disk image path to {disk_image}".format(name=self._name, + id=self._id, + disk_image=hda_disk_image)) + self._hda_disk_image = hda_disk_image @property - def disk_image(self): + def hdb_disk_image(self): """ - Returns the disk image path for this QEMU VM. + Returns the hdb disk image path for this QEMU VM. - :returns: QEMU disk image path + :returns: QEMU hdb disk image path """ - return self._disk_image + return self._hdb_disk_image - @disk_image.setter - def disk_image(self, disk_image): + @hdb_disk_image.setter + def hdb_disk_image(self, hdb_disk_image): """ - Sets the disk image for this QEMU VM. + Sets the hdb disk image for this QEMU VM. - :param disk_image: QEMU disk image path + :param hdb_disk_image: QEMU hdb disk image path """ - log.info("QEMU VM {name} [id={id}] has set the QEMU disk image path to {disk_image}".format(name=self._name, - id=self._id, - disk_image=disk_image)) - self._disk_image = disk_image + log.info("QEMU VM {name} [id={id}] has set the QEMU hdb disk image path to {disk_image}".format(name=self._name, + id=self._id, + disk_image=hdb_disk_image)) + self._hdb_disk_image = hdb_disk_image + @property def adapters(self): @@ -401,6 +408,121 @@ class QemuVM(object): id=self._id, adapter_type=adapter_type)) + @property + def ram(self): + """ + Returns the RAM amount for this QEMU VM. + + :returns: RAM amount in MB + """ + + return self._ram + + @ram.setter + def ram(self, ram): + """ + Sets the amount of RAM for this QEMU VM. + + :param ram: RAM amount in MB + """ + + log.info("QEMU VM {name} [id={id}] has set the RAM to {ram}".format(name=self._name, + id=self._id, + ram=ram)) + self._ram = ram + + @property + def options(self): + """ + Returns the options for this QEMU VM. + + :returns: QEMU options + """ + + return self._options + + @options.setter + def options(self, options): + """ + Sets the options for this QEMU VM. + + :param options: QEMU options + """ + + log.info("QEMU VM {name} [id={id}] has set the QEMU options to {options}".format(name=self._name, + id=self._id, + options=options)) + self._options = options + + @property + def initrd(self): + """ + Returns the initrd path for this QEMU VM. + + :returns: QEMU initrd path + """ + + return self._initrd + + @initrd.setter + def initrd(self, initrd): + """ + Sets the initrd path for this QEMU VM. + + :param initrd: QEMU initrd path + """ + + log.info("QEMU VM {name} [id={id}] has set the QEMU initrd path to {initrd}".format(name=self._name, + id=self._id, + initrd=initrd)) + self._initrd = initrd + + @property + def kernel_image(self): + """ + Returns the kernel image path for this QEMU VM. + + :returns: QEMU kernel image path + """ + + return self._kernel_image + + @kernel_image.setter + def kernel_image(self, kernel_image): + """ + Sets the kernel image path for this QEMU VM. + + :param kernel_image: QEMU kernel image path + """ + + log.info("QEMU VM {name} [id={id}] has set the QEMU kernel image path to {kernel_image}".format(name=self._name, + id=self._id, + kernel_image=kernel_image)) + self._kernel_image = kernel_image + + @property + def kernel_command_line(self): + """ + Returns the kernel command line for this QEMU VM. + + :returns: QEMU kernel command line + """ + + return self._kernel_command_line + + @kernel_command_line.setter + def kernel_command_line(self, kernel_command_line): + """ + Sets the kernel command line for this QEMU VM. + + :param kernel_command_line: QEMU kernel command line + """ + + log.info("QEMU VM {name} [id={id}] has set the QEMU kernel command line to {kernel_command_line}".format(name=self._name, + id=self._id, + kernel_command_line=kernel_command_line)) + self._kernel_command_line = kernel_command_line + def start(self): """ Starts this QEMU VM. @@ -411,9 +533,7 @@ class QemuVM(object): if not os.path.isfile(self._qemu_path) or not os.path.exists(self._qemu_path): raise QemuError("QEMU binary '{}' is not accessible".format(self._qemu_path)) - #TODO: check binary image is valid? self._command = self._build_command() - try: log.info("starting QEMU: {}".format(self._command)) self._stdout_file = os.path.join(self._working_dir, "qemu.log") @@ -567,36 +687,81 @@ class QemuVM(object): def _disk_options(self): - hda_disk = os.path.join(self._working_dir, "hda.disk") - if not os.path.exists(hda_disk): - try: - retcode = subprocess.call([self._qemu_img_path, "create", "-o", - "backing_file={}".format(self._disk_image), - "-f", "qcow2", hda_disk]) - log.info("{} returned with {}".format(self._qemu_img_path, retcode)) - except OSError as e: - raise QemuError("Could not create disk image {}".format(e)) + options = [] + qemu_img_path = "" + qemu_path_dir = os.path.dirname(self._qemu_path) + try: + for f in os.listdir(qemu_path_dir): + if f.startswith("qemu-img"): + qemu_img_path = os.path.join(qemu_path_dir, f) + except OSError as e: + raise QemuError("Error while looking for qemu-img in {}: {}".format(qemu_path_dir, e)) - return ["-hda", hda_disk] + if not qemu_img_path: + raise QemuError("Could not find qemu-img in {}".format(qemu_path_dir)) + + try: + if self._hda_disk_image: + hda_disk = os.path.join(self._working_dir, "hda_disk.qcow2") + if not os.path.exists(hda_disk): + retcode = subprocess.call([qemu_img_path, "create", "-o", + "backing_file={}".format(self._hda_disk_image), + "-f", "qcow2", hda_disk]) + log.info("{} returned with {}".format(qemu_img_path, retcode)) + else: + # create a "FLASH" with 256MB if no disk image has been specified + hda_disk = os.path.join(self._working_dir, "flash.qcow2") + if not os.path.exists(hda_disk): + retcode = subprocess.call([qemu_img_path, "create", "-f", "qcow2", hda_disk, "256M"]) + log.info("{} returned with {}".format(qemu_img_path, retcode)) + + except OSError as e: + raise QemuError("Could not create disk image {}".format(e)) + + options.extend(["-hda", hda_disk]) + if self._hdb_disk_image: + hdb_disk = os.path.join(self._working_dir, "hdb_disk.qcow2") + if not os.path.exists(hdb_disk): + try: + retcode = subprocess.call([qemu_img_path, "create", "-o", + "backing_file={}".format(self._hdb_disk_image), + "-f", "qcow2", hdb_disk]) + log.info("{} returned with {}".format(qemu_img_path, retcode)) + except OSError as e: + raise QemuError("Could not create disk image {}".format(e)) + options.extend(["-hdb", hdb_disk]) + + return options + + def _linux_boot_options(self): + + options = [] + if self._initrd: + options.extend(["-initrd", self._initrd]) + if self._kernel_image: + options.extend(["-kernel", self._kernel_image]) + if self._kernel_command_line: + options.extend(["-append", self._kernel_command_line]) + + return options def _network_options(self): network_options = [] adapter_id = 0 for adapter in self._ethernet_adapters: + #TODO: let users specify a base mac address + mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id) + network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)]) nio = adapter.get_nio(0) - if nio: - #TODO: let users specific the base mac address - mac = "00:00:ab:%02x:%02x:%02d" % (random.randint(0x00, 0xff), random.randint(0x00, 0xff), adapter_id) - network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_id)]) - if isinstance(nio, NIO_UDP): - network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id, - nio.rhost, - nio.rport, - self._host, - nio.lport)]) + if nio and isinstance(nio, NIO_UDP): + network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_id, + nio.rhost, + nio.rport, + self._host, + nio.lport)]) else: - network_options.extend(["-device", "{}".format(self._adapter_type)]) + network_options.extend(["-netdev", "user,id=gns3-{}".format(adapter_id)]) adapter_id += 1 return network_options @@ -611,6 +776,10 @@ class QemuVM(object): command.extend(["-name", self._name]) command.extend(["-m", str(self._ram)]) command.extend(self._disk_options()) + command.extend(self._linux_boot_options()) command.extend(self._serial_options()) + additional_options = self._options.strip() + if additional_options: + command.extend(shlex.split(additional_options)) command.extend(self._network_options()) return command diff --git a/gns3server/modules/qemu/schemas.py b/gns3server/modules/qemu/schemas.py index bfcbfdff..5b00e98a 100644 --- a/gns3server/modules/qemu/schemas.py +++ b/gns3server/modules/qemu/schemas.py @@ -79,8 +79,13 @@ QEMU_UPDATE_SCHEMA = { "type": "string", "minLength": 1, }, - "disk_image": { - "description": "QEMU disk image path", + "hda_disk_image": { + "description": "QEMU hda disk image path", + "type": "string", + "minLength": 1, + }, + "hdb_disk_image": { + "description": "QEMU hdb disk image path", "type": "string", "minLength": 1, }, @@ -105,6 +110,21 @@ QEMU_UPDATE_SCHEMA = { "maximum": 65535, "type": "integer" }, + "initrd": { + "description": "QEMU initrd path", + "type": "string", + "minLength": 1, + }, + "kernel_image": { + "description": "QEMU kernel image path", + "type": "string", + "minLength": 1, + }, + "kernel_command_line": { + "description": "QEMU kernel command line", + "type": "string", + "minLength": 1, + }, "options": { "description": "additional QEMU options", "type": "string", diff --git a/gns3server/version.py b/gns3server/version.py index 1dcc2571..8e0b974c 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0beta3.dev1" +__version__ = "1.0beta3.dev2" __version_info__ = (1, 0, 0, -99)