From de5e8f852d1d0c2822a38d0f45cc61420a7dbfe4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 12 Apr 2015 15:09:37 -0600 Subject: [PATCH 01/13] Cleaner and generic way to set Qemu & IOU VM settings. --- gns3server/handlers/api/iou_handler.py | 41 +++++++++---------------- gns3server/handlers/api/qemu_handler.py | 39 ++++++++++++----------- gns3server/modules/iou/iou_vm.py | 37 +++++----------------- gns3server/schemas/iou.py | 12 ++++++-- tests/modules/iou/test_iou_vm.py | 3 +- 5 files changed, 54 insertions(+), 78 deletions(-) diff --git a/gns3server/handlers/api/iou_handler.py b/gns3server/handlers/api/iou_handler.py index 35e413b4..98bd4cf7 100644 --- a/gns3server/handlers/api/iou_handler.py +++ b/gns3server/handlers/api/iou_handler.py @@ -19,7 +19,6 @@ import os from aiohttp.web import HTTPConflict from ...web.route import Route -from ...modules.port_manager import PortManager from ...schemas.iou import IOU_CREATE_SCHEMA from ...schemas.iou import IOU_UPDATE_SCHEMA from ...schemas.iou import IOU_OBJECT_SCHEMA @@ -52,20 +51,16 @@ class IOUHandler: def create(request, response): iou = IOU.instance() - vm = yield from iou.create_vm(request.json["name"], + vm = yield from iou.create_vm(request.json.pop("name"), request.match_info["project_id"], request.json.get("vm_id"), - console=request.json.get("console"), - serial_adapters=request.json.get("serial_adapters"), - ethernet_adapters=request.json.get("ethernet_adapters"), - ram=request.json.get("ram"), - nvram=request.json.get("nvram"), - use_default_iou_values=request.json.get("use_default_iou_values"), - l1_keepalives=request.json.get("l1_keepalives"), - initial_config=request.json.get("initial_config_content"), - iourc_content=request.json.get("iourc_content") - ) - vm.path = request.json.get("path", vm.path) + console=request.json.get("console")) + + for name, value in request.json.items(): + if hasattr(vm, name) and getattr(vm, name) != value: + setattr(vm, name, value) + if "initial_config_content" in request.json: + vm.initial_config = request.json.get("initial_config_content") response.set_status(201) response.json(vm) @@ -109,18 +104,12 @@ class IOUHandler: iou_manager = IOU.instance() vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) - vm.name = request.json.get("name", vm.name) - vm.console = request.json.get("console", vm.console) - vm.path = request.json.get("path", vm.path) - vm.ethernet_adapters = request.json.get("ethernet_adapters", vm.ethernet_adapters) - vm.serial_adapters = request.json.get("serial_adapters", vm.serial_adapters) - vm.ram = request.json.get("ram", vm.ram) - vm.nvram = request.json.get("nvram", vm.nvram) - vm.use_default_iou_values = request.json.get("use_default_iou_values", vm.use_default_iou_values) - vm.l1_keepalives = request.json.get("l1_keepalives", vm.l1_keepalives) - vm.initial_config = request.json.get("initial_config_content", vm.initial_config) - vm.iourc_content = request.json.get("iourc_content", None) + for name, value in request.json.items(): + if hasattr(vm, name) and getattr(vm, name) != value: + setattr(vm, name, value) + if "initial_config_content" in request.json: + vm.initial_config = request.json.get("initial_config_content") response.json(vm) @classmethod @@ -273,7 +262,7 @@ class IOUHandler: pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"]) if not vm.is_running(): - raise HTTPConflict(text="You can't capture the traffic on a non started VM") + raise HTTPConflict(text="Cannot capture traffic on a non started VM") yield from vm.start_capture(adapter_number, port_number, pcap_file_path, request.json["data_link_type"]) response.json({"pcap_file_path": str(pcap_file_path)}) @@ -298,7 +287,7 @@ class IOUHandler: vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) if not vm.is_running(): - raise HTTPConflict(text="You can't capture the traffic on a non started VM") + raise HTTPConflict(text="Cannot capture traffic on a non started VM") adapter_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 14d12d07..714149d6 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -41,24 +41,21 @@ class QEMUHandler: 400: "Invalid request", 409: "Conflict" }, - description="Create a new Qemu.instance", + description="Create a new Qemu VM instance", input=QEMU_CREATE_SCHEMA, output=QEMU_OBJECT_SCHEMA) def create(request, response): qemu = Qemu.instance() - vm = yield from qemu.create_vm(request.json["name"], + vm = yield from qemu.create_vm(request.json.pop("name"), request.match_info["project_id"], request.json.get("vm_id"), qemu_path=request.json.get("qemu_path"), console=request.json.get("console")) - # Clear already used keys - map(request.json.__delitem__, ["name", "project_id", "vm_id", - "qemu_path", "console"]) - - for field in request.json: - setattr(vm, field, request.json[field]) + for name, value in request.json.items(): + if hasattr(vm, name) and getattr(vm, name) != value: + setattr(vm, name, value) response.set_status(201) response.json(vm) @@ -75,7 +72,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Get a Qemu.instance", + description="Get a Qemu VM instance", output=QEMU_OBJECT_SCHEMA) def show(request, response): @@ -96,15 +93,17 @@ class QEMUHandler: 404: "Instance doesn't exist", 409: "Conflict" }, - description="Update a Qemu.instance", + description="Update a Qemu VM instance", input=QEMU_UPDATE_SCHEMA, output=QEMU_OBJECT_SCHEMA) def update(request, response): qemu_manager = Qemu.instance() vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) - for field in request.json: - setattr(vm, field, request.json[field]) + + for name, value in request.json.items(): + if hasattr(vm, name) and getattr(vm, name) != value: + setattr(vm, name, value) response.json(vm) @@ -120,7 +119,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Delete a Qemu.instance") + description="Delete a Qemu VM instance") def delete(request, response): yield from Qemu.instance().delete_vm(request.match_info["vm_id"]) @@ -138,7 +137,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Start a Qemu.instance") + description="Start a Qemu VM instance") def start(request, response): qemu_manager = Qemu.instance() @@ -158,7 +157,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Stop a Qemu.instance") + description="Stop a Qemu VM instance") def stop(request, response): qemu_manager = Qemu.instance() @@ -178,7 +177,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Reload a Qemu.instance") + description="Reload a Qemu VM instance") def reload(request, response): qemu_manager = Qemu.instance() @@ -198,7 +197,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Suspend a Qemu.instance") + description="Suspend a Qemu VM instance") def suspend(request, response): qemu_manager = Qemu.instance() @@ -218,7 +217,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Resume a Qemu.instance") + description="Resume a Qemu VM instance") def resume(request, response): qemu_manager = Qemu.instance() @@ -239,7 +238,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Add a NIO to a Qemu.instance", + description="Add a NIO to a Qemu VM instance", input=QEMU_NIO_SCHEMA, output=QEMU_NIO_SCHEMA) def create_nio(request, response): @@ -265,7 +264,7 @@ class QEMUHandler: 400: "Invalid request", 404: "Instance doesn't exist" }, - description="Remove a NIO from a Qemu.instance") + description="Remove a NIO from a Qemu VM instance") def delete_nio(request, response): qemu_manager = Qemu.instance() diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 1bc6a9c0..c7a0da24 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -61,25 +61,9 @@ class IOUVM(BaseVM): :param project: Project instance :param manager: Manager instance :param console: TCP console port - :params ethernet_adapters: number of ethernet adapters - :params serial_adapters: number of serial adapters - :params ram: amount of RAM in MB - :params nvram: amount of NVRAM in KB - :params l1_keepalives: always keep the Ethernet interfaces up - :params initial_config: content of the initial configuration file - :params iourc_content: content of the iourc file if no licence is installed on the machine """ - def __init__(self, name, vm_id, project, manager, - console=None, - ram=None, - nvram=None, - use_default_iou_values=None, - ethernet_adapters=None, - serial_adapters=None, - l1_keepalives=None, - initial_config=None, - iourc_content=None): + def __init__(self, name, vm_id, project, manager, console=None): super().__init__(name, vm_id, project, manager, console=console) @@ -94,17 +78,13 @@ class IOUVM(BaseVM): # IOU settings self._ethernet_adapters = [] self._serial_adapters = [] - self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces - self.serial_adapters = 2 if serial_adapters is None else serial_adapters # one adapter = 4 interfaces - self._use_default_iou_values = True if use_default_iou_values is None else use_default_iou_values # for RAM & NVRAM values - self._nvram = 128 if nvram is None else nvram # Kilobytes + self.ethernet_adapters = 2 # one adapter = 4 interfaces + self.serial_adapters = 2 # one adapter = 4 interfaces + self._use_default_iou_values = True # for RAM & NVRAM values + self._nvram = 128 # Kilobytes self._initial_config = "" - self._ram = 256 if ram is None else ram # Megabytes - self._l1_keepalives = False if l1_keepalives is None else l1_keepalives # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). - - self.iourc_content = iourc_content - if initial_config is not None: - self.initial_config = initial_config + self._ram = 256 # Megabytes + self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). @asyncio.coroutine def close(self): @@ -236,8 +216,7 @@ class IOUVM(BaseVM): "nvram": self._nvram, "l1_keepalives": self._l1_keepalives, "initial_config": self.relative_initial_config_file, - "use_default_iou_values": self._use_default_iou_values, - "iourc_path": self.iourc_path} + "use_default_iou_values": self._use_default_iou_values} # return the relative path if the IOU image is in the images_path directory server_config = self.manager.config.get_section_config("Server") diff --git a/gns3server/schemas/iou.py b/gns3server/schemas/iou.py index 5793488a..df8d3b5b 100644 --- a/gns3server/schemas/iou.py +++ b/gns3server/schemas/iou.py @@ -70,8 +70,12 @@ IOU_CREATE_SCHEMA = { "description": "Use default IOU values", "type": ["boolean", "null"] }, + "initial_config": { + "description": "Path to the initial configuration of IOU", + "type": ["string", "null"] + }, "initial_config_content": { - "description": "Initial configuration of the IOU", + "description": "Initial configuration of IOU", "type": ["string", "null"] }, "iourc_content": { @@ -123,8 +127,12 @@ IOU_UPDATE_SCHEMA = { "description": "Always up ethernet interface", "type": ["boolean", "null"] }, + "initial_config": { + "description": "Path to the initial configuration of IOU", + "type": ["string", "null"] + }, "initial_config_content": { - "description": "Initial configuration of the IOU", + "description": "Initial configuration of IOU", "type": ["string", "null"] }, "use_default_iou_values": { diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py index 8ee3a21f..731f6974 100644 --- a/tests/modules/iou/test_iou_vm.py +++ b/tests/modules/iou/test_iou_vm.py @@ -82,7 +82,8 @@ def test_vm(project, manager): def test_vm_initial_config(project, manager): - vm = IOUVM("test", "00010203-0405-0607-0808-0a0b0c0d0e0f", project, manager, initial_config="hostname %h") + vm = IOUVM("test", "00010203-0405-0607-0808-0a0b0c0d0e0f", project, manager) + vm.initial_config = "hostname %h" assert vm.name == "test" assert vm.initial_config == "hostname test" assert vm.id == "00010203-0405-0607-0808-0a0b0c0d0e0f" From 78bc6e29a8b79cc69a78d565eebc8dccd077a572 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 12 Apr 2015 18:09:53 -0600 Subject: [PATCH 02/13] Explicitly delete Dynamips NIOs and unmap VCs for ATM and Frame-Relay switches. --- .../handlers/api/dynamips_device_handler.py | 6 ++---- gns3server/handlers/api/dynamips_vm_handler.py | 3 ++- .../modules/dynamips/nodes/atm_switch.py | 18 ++++++++++++++++++ .../dynamips/nodes/frame_relay_switch.py | 9 +++++++++ gns3server/modules/dynamips/nodes/router.py | 8 -------- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/gns3server/handlers/api/dynamips_device_handler.py b/gns3server/handlers/api/dynamips_device_handler.py index 95307b89..10220cec 100644 --- a/gns3server/handlers/api/dynamips_device_handler.py +++ b/gns3server/handlers/api/dynamips_device_handler.py @@ -181,10 +181,8 @@ class DynamipsDeviceHandler: dynamips_manager = Dynamips.instance() device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"]) port_number = int(request.match_info["port_number"]) - if asyncio.iscoroutinefunction(device.remove_nio): - yield from device.remove_nio(port_number) - else: - device.remove_nio(port_number) + nio = yield from device.remove_nio(port_number) + yield from nio.delete() response.set_status(204) @Route.post( diff --git a/gns3server/handlers/api/dynamips_vm_handler.py b/gns3server/handlers/api/dynamips_vm_handler.py index c125bbb2..8d96f98b 100644 --- a/gns3server/handlers/api/dynamips_vm_handler.py +++ b/gns3server/handlers/api/dynamips_vm_handler.py @@ -290,7 +290,8 @@ class DynamipsVMHandler: vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) slot_number = int(request.match_info["adapter_number"]) port_number = int(request.match_info["port_number"]) - yield from vm.slot_remove_nio_binding(slot_number, port_number) + nio = yield from vm.slot_remove_nio_binding(slot_number, port_number) + yield from nio.delete() response.set_status(204) @Route.post( diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 4064ad07..141293af 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -150,6 +150,7 @@ class ATMSwitch(Device): self._nios[port_number] = nio + @asyncio.coroutine def remove_nio(self, port_number): """ Removes the specified NIO as member of this ATM switch. @@ -160,6 +161,23 @@ class ATMSwitch(Device): if port_number not in self._nios: raise DynamipsError("Port {} is not allocated".format(port_number)) + # remove VCs mapped with the port + for source, destination in self._mappings.copy().items(): + if len(source) == 3 and len(destination) == 3: + # remove the virtual channels mapped with this port/nio + source_port, source_vpi, source_vci = source + destination_port, destination_vpi, destination_vci = destination + if port_number == source_port: + yield from self.unmap_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci) + yield from self.unmap_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci) + else: + # remove the virtual paths mapped with this port/nio + source_port, source_vpi = source + destination_port, destination_vpi = destination + if port_number == source_port: + yield from self.unmap_vp(source_port, source_vpi, destination_port, destination_vpi) + yield from self.unmap_vp(destination_port, destination_vpi, source_port, source_vpi) + nio = self._nios[port_number] if isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index a4bf56e6..c1f06c84 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -149,6 +149,7 @@ class FrameRelaySwitch(Device): self._nios[port_number] = nio + @asyncio.coroutine def remove_nio(self, port_number): """ Removes the specified NIO as member of this Frame Relay switch. @@ -161,6 +162,14 @@ class FrameRelaySwitch(Device): if port_number not in self._nios: raise DynamipsError("Port {} is not allocated".format(port_number)) + # remove VCs mapped with the port + for source, destination in self._mappings.copy().items(): + source_port, source_dlci = source + destination_port, destination_dlci = destination + if port_number == source_port: + yield from self.unmap_vc(source_port, source_dlci, destination_port, destination_dlci) + yield from self.unmap_vc(destination_port, destination_dlci, source_port, source_dlci) + nio = self._nios[port_number] if isinstance(nio, NIOUDP): self.manager.port_manager.release_udp_port(nio.lport, self._project) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 568a11f6..7182eb50 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -172,14 +172,6 @@ class Router(BaseVM): return router_info - @classmethod - def reset(cls): - """ - Resets the instance count and the allocated instances list. - """ - - cls._dynamips_ids.clear() - @property def dynamips_id(self): """ From 443842e9b8cfa292c28e809a6d7577d46a352f7b Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 12 Apr 2015 18:14:45 -0600 Subject: [PATCH 03/13] Allocate random names for Dynamips NIOs. --- gns3server/modules/dynamips/nios/nio_fifo.py | 17 +++-------------- .../dynamips/nios/nio_generic_ethernet.py | 17 +++-------------- .../dynamips/nios/nio_linux_ethernet.py | 18 +++--------------- gns3server/modules/dynamips/nios/nio_mcast.py | 17 +++-------------- gns3server/modules/dynamips/nios/nio_null.py | 17 +++-------------- gns3server/modules/dynamips/nios/nio_tap.py | 17 +++-------------- gns3server/modules/dynamips/nios/nio_udp.py | 17 +++-------------- gns3server/modules/dynamips/nios/nio_unix.py | 17 +++-------------- gns3server/modules/dynamips/nios/nio_vde.py | 17 +++-------------- 9 files changed, 27 insertions(+), 127 deletions(-) diff --git a/gns3server/modules/dynamips/nios/nio_fifo.py b/gns3server/modules/dynamips/nios/nio_fifo.py index f1d763fc..9e7695a2 100644 --- a/gns3server/modules/dynamips/nios/nio_fifo.py +++ b/gns3server/modules/dynamips/nios/nio_fifo.py @@ -20,6 +20,7 @@ Interface for FIFO NIOs. """ import asyncio +import uuid from .nio import NIO import logging @@ -34,24 +35,12 @@ class NIOFIFO(NIO): :param hypervisor: Dynamips hypervisor instance """ - _instance_count = 0 - def __init__(self, hypervisor): - # create an unique ID and name - nio_id = NIOFIFO._instance_count - NIOFIFO._instance_count += 1 - name = 'nio_fifo' + str(nio_id) + # create an unique name + name = 'fifo-{}'.format(uuid.uuid4()) super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_generic_ethernet.py b/gns3server/modules/dynamips/nios/nio_generic_ethernet.py index dcc13bae..870ecb13 100644 --- a/gns3server/modules/dynamips/nios/nio_generic_ethernet.py +++ b/gns3server/modules/dynamips/nios/nio_generic_ethernet.py @@ -20,6 +20,7 @@ Interface for generic Ethernet NIOs (PCAP library). """ import asyncio +import uuid from .nio import NIO import logging @@ -35,25 +36,13 @@ class NIOGenericEthernet(NIO): :param ethernet_device: Ethernet device name (e.g. eth0) """ - _instance_count = 0 - def __init__(self, hypervisor, ethernet_device): - # create an unique ID and name - nio_id = NIOGenericEthernet._instance_count - NIOGenericEthernet._instance_count += 1 - name = 'nio_gen_eth' + str(nio_id) + # create an unique name + name = 'generic_ethernet-{}'.format(uuid.uuid4()) self._ethernet_device = ethernet_device super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_linux_ethernet.py b/gns3server/modules/dynamips/nios/nio_linux_ethernet.py index 500123b5..cf96f4af 100644 --- a/gns3server/modules/dynamips/nios/nio_linux_ethernet.py +++ b/gns3server/modules/dynamips/nios/nio_linux_ethernet.py @@ -20,6 +20,7 @@ Interface for Linux Ethernet NIOs (Linux only). """ import asyncio +import uuid from .nio import NIO import logging @@ -35,25 +36,12 @@ class NIOLinuxEthernet(NIO): :param ethernet_device: Ethernet device name (e.g. eth0) """ - _instance_count = 0 - def __init__(self, hypervisor, ethernet_device): - - # create an unique ID and name - nio_id = NIOLinuxEthernet._instance_count - NIOLinuxEthernet._instance_count += 1 - name = 'nio_linux_eth' + str(nio_id) + # create an unique name + name = 'linux_ethernet-{}'.format(uuid.uuid4()) self._ethernet_device = ethernet_device super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_mcast.py b/gns3server/modules/dynamips/nios/nio_mcast.py index f59dc280..4ea47dc2 100644 --- a/gns3server/modules/dynamips/nios/nio_mcast.py +++ b/gns3server/modules/dynamips/nios/nio_mcast.py @@ -20,6 +20,7 @@ Interface for multicast NIOs. """ import asyncio +import uuid from .nio import NIO import logging @@ -36,27 +37,15 @@ class NIOMcast(NIO): :param port: port for binding """ - _instance_count = 0 - def __init__(self, hypervisor, group, port): - # create an unique ID and name - nio_id = NIOMcast._instance_count - NIOMcast._instance_count += 1 - name = 'nio_mcast' + str(nio_id) + # create an unique name + name = 'mcast-{}'.format(uuid.uuid4()) self._group = group self._port = port self._ttl = 1 # default TTL super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_null.py b/gns3server/modules/dynamips/nios/nio_null.py index 4d46137c..174dd032 100644 --- a/gns3server/modules/dynamips/nios/nio_null.py +++ b/gns3server/modules/dynamips/nios/nio_null.py @@ -20,6 +20,7 @@ Interface for dummy NIOs (mostly for tests). """ import asyncio +import uuid from .nio import NIO import logging @@ -34,24 +35,12 @@ class NIONull(NIO): :param hypervisor: Dynamips hypervisor instance """ - _instance_count = 0 - def __init__(self, hypervisor): - # create an unique ID and name - nio_id = NIONull._instance_count - NIONull._instance_count += 1 - name = 'nio_null' + str(nio_id) + # create an unique name + name = 'null-{}'.format(uuid.uuid4()) super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_tap.py b/gns3server/modules/dynamips/nios/nio_tap.py index b4bd172f..20fdaeaa 100644 --- a/gns3server/modules/dynamips/nios/nio_tap.py +++ b/gns3server/modules/dynamips/nios/nio_tap.py @@ -20,6 +20,7 @@ Interface for TAP NIOs (UNIX based OSes only). """ import asyncio +import uuid from .nio import NIO import logging @@ -35,25 +36,13 @@ class NIOTAP(NIO): :param tap_device: TAP device name (e.g. tap0) """ - _instance_count = 0 - def __init__(self, hypervisor, tap_device): - # create an unique ID and name - nio_id = NIOTAP._instance_count - NIOTAP._instance_count += 1 - name = 'nio_tap' + str(nio_id) + # create an unique name + name = 'tap-{}'.format(uuid.uuid4()) self._tap_device = tap_device super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_udp.py b/gns3server/modules/dynamips/nios/nio_udp.py index eeb15e32..c7c2b656 100644 --- a/gns3server/modules/dynamips/nios/nio_udp.py +++ b/gns3server/modules/dynamips/nios/nio_udp.py @@ -20,6 +20,7 @@ Interface for UDP NIOs. """ import asyncio +import uuid from .nio import NIO import logging @@ -37,27 +38,15 @@ class NIOUDP(NIO): :param rport: remote port number """ - _instance_count = 0 - def __init__(self, hypervisor, lport, rhost, rport): - # create an unique ID and name - nio_id = NIOUDP._instance_count - NIOUDP._instance_count += 1 - name = 'nio_udp' + str(nio_id) + # create an unique name + name = 'udp-{}'.format(uuid.uuid4()) self._lport = lport self._rhost = rhost self._rport = rport super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_unix.py b/gns3server/modules/dynamips/nios/nio_unix.py index 45fa41c0..048572a4 100644 --- a/gns3server/modules/dynamips/nios/nio_unix.py +++ b/gns3server/modules/dynamips/nios/nio_unix.py @@ -20,6 +20,7 @@ Interface for UNIX NIOs (Unix based OSes only). """ import asyncio +import uuid from .nio import NIO import logging @@ -36,26 +37,14 @@ class NIOUNIX(NIO): :param remote_file: remote UNIX socket filename """ - _instance_count = 0 - def __init__(self, hypervisor, local_file, remote_file): - # create an unique ID and name - nio_id = NIOUNIX._instance_count - NIOUNIX._instance_count += 1 - name = 'nio_unix' + str(nio_id) + # create an unique name + name = 'unix-{}'.format(uuid.uuid4()) self._local_file = local_file self._remote_file = remote_file super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): diff --git a/gns3server/modules/dynamips/nios/nio_vde.py b/gns3server/modules/dynamips/nios/nio_vde.py index 17a85524..6ac04259 100644 --- a/gns3server/modules/dynamips/nios/nio_vde.py +++ b/gns3server/modules/dynamips/nios/nio_vde.py @@ -20,6 +20,7 @@ Interface for VDE (Virtual Distributed Ethernet) NIOs (Unix based OSes only). """ import asyncio +import uuid from .nio import NIO import logging @@ -36,26 +37,14 @@ class NIOVDE(NIO): :param local_file: VDE local filename """ - _instance_count = 0 - def __init__(self, hypervisor, control_file, local_file): - # create an unique ID and name - nio_id = NIOVDE._instance_count - NIOVDE._instance_count += 1 - name = 'nio_vde' + str(nio_id) + # create an unique name + name = 'vde-{}'.format(uuid.uuid4()) self._control_file = control_file self._local_file = local_file super().__init__(name, hypervisor) - @classmethod - def reset(cls): - """ - Reset the instance count. - """ - - cls._instance_count = 0 - @asyncio.coroutine def create(self): From af942dc41972e2c76bbeed5d501cc978f32cbfd0 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 14:24:13 +0200 Subject: [PATCH 04/13] Fix a crash in VirtualBox vm creation Fix #138 --- gns3server/handlers/api/virtualbox_handler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gns3server/handlers/api/virtualbox_handler.py b/gns3server/handlers/api/virtualbox_handler.py index f0765180..008ac2e6 100644 --- a/gns3server/handlers/api/virtualbox_handler.py +++ b/gns3server/handlers/api/virtualbox_handler.py @@ -79,8 +79,9 @@ class VirtualBoxHandler: yield from vm.set_ram(ram) for name, value in request.json.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - setattr(vm, name, value) + if name != "vm_id": + if hasattr(vm, name) and getattr(vm, name) != value: + setattr(vm, name, value) response.set_status(201) response.json(vm) From 45ca995deadac6c5a8953fe583e0ebb264619618 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 14:32:44 +0200 Subject: [PATCH 05/13] Fix crash if VirtualBox doesn't return API version Fix #136 --- gns3server/modules/virtualbox/virtualbox_vm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index f369287b..2ff8511c 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -139,6 +139,8 @@ class VirtualBoxVM(BaseVM): def create(self): yield from self._get_system_properties() + if "API version" not in self._system_properties: + raise VirtualBoxError("Can't access to VirtualBox API Version") if parse_version(self._system_properties["API version"]) < parse_version("4_3"): raise VirtualBoxError("The VirtualBox API version is lower than 4.3") log.info("VirtualBox VM '{name}' [{id}] created".format(name=self.name, id=self.id)) From 55fed0229903486b3f6854f88df11f7392d8027e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 14:35:48 +0200 Subject: [PATCH 06/13] Fix a crash when in some cases you can't access to VBOX state Fix #137 --- gns3server/modules/virtualbox/virtualbox_vm.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 2ff8511c..428d880c 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -105,9 +105,10 @@ class VirtualBoxVM(BaseVM): results = yield from self.manager.execute("showvminfo", [self._vmname, "--machinereadable"]) for info in results: - name, value = info.split('=', 1) - if name == "VMState": - return value.strip('"') + if '=' in info: + name, value = info.split('=', 1) + if name == "VMState": + return value.strip('"') raise VirtualBoxError("Could not get VM state for {}".format(self._vmname)) @asyncio.coroutine From 6ec081c77462a9171f2e6b814184a87b68b8b6da Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 14:53:01 +0200 Subject: [PATCH 07/13] Include tests in Pypi package Require by gentoo maintainer --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 55a68cb2..ff327eea 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,7 @@ include INSTALL include LICENSE include MANIFEST.in include tox.ini -recursive-exclude tests * +recursive-include tests * recursive-exclude docs * recursive-include gns3server * recursive-exclude * __pycache__ From e51a129216e859fc96cf5b8c9fe48b2136db859e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 15:00:45 +0200 Subject: [PATCH 08/13] Prevent parallel execution of VBox commands In theory it should not be a problem. But It's create issues like this one: Fix: https://github.com/GNS3/gns3-gui/issues/261 --- gns3server/modules/virtualbox/__init__.py | 55 ++++++++++++----------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 5ea015e0..a6440072 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -41,6 +41,7 @@ class VirtualBox(BaseManager): super().__init__() self._vboxmanage_path = None + self._execute_lock = asyncio.Lock() @property def vboxmanage_path(self): @@ -82,34 +83,38 @@ class VirtualBox(BaseManager): @asyncio.coroutine def execute(self, subcommand, args, timeout=60): - vboxmanage_path = self.vboxmanage_path - if not vboxmanage_path: - vboxmanage_path = self.find_vboxmanage() - command = [vboxmanage_path, "--nologo", subcommand] - command.extend(args) - log.debug("Executing VBoxManage with command: {}".format(command)) - try: - vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user") - if vbox_user: - # TODO: test & review this part - sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command) - process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) - else: - process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) - except (OSError, subprocess.SubprocessError) as e: - raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) + # We use a lock prevent parallel execution due to strange errors + # reported by a user and reproduced by us. + # https://github.com/GNS3/gns3-gui/issues/261 + with (yield from self._execute_lock): + vboxmanage_path = self.vboxmanage_path + if not vboxmanage_path: + vboxmanage_path = self.find_vboxmanage() + command = [vboxmanage_path, "--nologo", subcommand] + command.extend(args) + log.debug("Executing VBoxManage with command: {}".format(command)) + try: + vbox_user = self.config.get_section_config("VirtualBox").get("vbox_user") + if vbox_user: + # TODO: test & review this part + sudo_command = "sudo -i -u {}".format(vbox_user) + " ".join(command) + process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + else: + process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + except (OSError, subprocess.SubprocessError) as e: + raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) - try: - stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout) - except asyncio.TimeoutError: - raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout)) + try: + stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout) + except asyncio.TimeoutError: + raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout)) - if process.returncode: - # only the first line of the output is useful - vboxmanage_error = stderr_data.decode("utf-8", errors="ignore") - raise VirtualBoxError("VirtualBox has returned an error: {}".format(vboxmanage_error)) + if process.returncode: + # only the first line of the output is useful + vboxmanage_error = stderr_data.decode("utf-8", errors="ignore") + raise VirtualBoxError("VirtualBox has returned an error: {}".format(vboxmanage_error)) - return stdout_data.decode("utf-8", errors="ignore").splitlines() + return stdout_data.decode("utf-8", errors="ignore").splitlines() @asyncio.coroutine def get_list(self): From aa2472fb30808a79c14effe23aae3e8a2b8b5593 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 14 Apr 2015 18:46:55 +0200 Subject: [PATCH 09/13] Rewrote image search This code is more generic and support all cases. Previously we had bug where the user lost his image path if the image was not located in image directory. --- gns3server/modules/base_manager.py | 37 +++++++++++ gns3server/modules/dynamips/__init__.py | 6 ++ gns3server/modules/dynamips/nodes/router.py | 9 +-- gns3server/modules/iou/__init__.py | 6 ++ gns3server/modules/iou/iou_vm.py | 15 +---- gns3server/modules/qemu/__init__.py | 6 ++ gns3server/modules/qemu/qemu_vm.py | 68 ++++++--------------- tests/modules/iou/test_iou_manager.py | 16 +++++ tests/modules/test_manager.py | 31 ++++++++++ 9 files changed, 124 insertions(+), 70 deletions(-) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 9238aaad..d6097fce 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -371,3 +371,40 @@ class BaseManager: nio = NIOGenericEthernet(nio_settings["ethernet_device"]) assert nio is not None return nio + + def get_abs_image_path(self, path): + """ + Get the absolute path of an image + + :param path: file path + :return: file path + """ + + img_directory = self.get_images_directory() + + if not os.path.isabs(path): + s = os.path.split(path) + return os.path.normpath(os.path.join(img_directory, *s)) + return path + + def get_relative_image_path(self, path): + """ + Get a path relative to images directory path + or an abspath if the path is not located inside + image directory + + :param path: file path + :return: file path + """ + + img_directory = self.get_images_directory() + path = self.get_abs_image_path(path) + if os.path.dirname(path) == img_directory: + return os.path.basename(path) + return path + + def get_images_directory(self): + """ + Get the image directory on disk + """ + raise NotImplementedError diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 1592e9ce..ed6858a8 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -616,3 +616,9 @@ class Dynamips(BaseManager): if was_auto_started: yield from vm.stop() return validated_idlepc + + def get_images_directory(self): + """ + Return the full path of the images directory on disk + """ + return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "IOS") diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 568a11f6..6a1291b0 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -151,10 +151,7 @@ class Router(BaseVM): "system_id": self._system_id} # return the relative path if the IOS image is in the images_path directory - server_config = self.manager.config.get_section_config("Server") - relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOS", self._image) - if os.path.exists(relative_image): - router_info["image"] = os.path.basename(self._image) + router_info["image"] = self.manager.get_relative_image_path(self._image) # add the slots slot_number = 0 @@ -427,9 +424,7 @@ class Router(BaseVM): :param image: path to IOS image file """ - if not os.path.isabs(image): - server_config = self.manager.config.get_section_config("Server") - image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOS", image) + image = self.manager.get_abs_image_path(image) if not os.path.isfile(image): raise DynamipsError("IOS image '{}' is not accessible".format(image)) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 764d6475..df6ad3c7 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -91,3 +91,9 @@ class IOU(BaseManager): """ return os.path.join("iou", "device-{}".format(legacy_vm_id)) + + def get_images_directory(self): + """ + Return the full path of the images directory on disk + """ + return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "IOU") diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index d8d47a33..2b7efd69 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -145,14 +145,7 @@ class IOUVM(BaseVM): :param path: path to the IOU image executable """ - if not os.path.isabs(path): - server_config = self.manager.config.get_section_config("Server") - relative_path = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), path) - if not os.path.exists(relative_path): - relative_path = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOU", path) - path = relative_path - - self._path = path + self._path = self.manager.get_abs_image_path(path) # In 1.2 users uploaded images to the images roots # after the migration their images are inside images/IOU @@ -240,11 +233,7 @@ class IOUVM(BaseVM): "iourc_path": self.iourc_path} # return the relative path if the IOU image is in the images_path directory - server_config = self.manager.config.get_section_config("Server") - relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "IOU", self.path) - if os.path.exists(relative_image): - iou_vm_info["path"] = os.path.basename(self.path) - + iou_vm_info["path"] = self.manager.get_relative_image_path(self.path) return iou_vm_info @property diff --git a/gns3server/modules/qemu/__init__.py b/gns3server/modules/qemu/__init__.py index d348894f..e5bf698a 100644 --- a/gns3server/modules/qemu/__init__.py +++ b/gns3server/modules/qemu/__init__.py @@ -112,3 +112,9 @@ class Qemu(BaseManager): """ return os.path.join("qemu", "vm-{}".format(legacy_vm_id)) + + def get_images_directory(self): + """ + Return the full path of the images directory on disk + """ + return os.path.join(os.path.expanduser(self.config.get_section_config("Server").get("images_path", "~/GNS3/images")), "QEMU") diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 75731d24..420bd5e0 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -148,14 +148,10 @@ class QemuVM(BaseVM): :param hda_disk_image: QEMU hda disk image path """ - if not os.path.isabs(hda_disk_image): - server_config = self.manager.config.get_section_config("Server") - hda_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hda_disk_image) - + self._hda_disk_image = self.manager.get_abs_image_path(hda_disk_image) log.info('QEMU VM "{name}" [{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 + disk_image=self._hda_disk_image)) @property def hdb_disk_image(self): @@ -175,14 +171,10 @@ class QemuVM(BaseVM): :param hdb_disk_image: QEMU hdb disk image path """ - if not os.path.isabs(hdb_disk_image): - server_config = self.manager.config.get_section_config("Server") - hdb_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdb_disk_image) - + self._hdb_disk_image = self.manager.get_abs_image_path(hdb_disk_image) log.info('QEMU VM "{name}" [{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 + disk_image=self._hdb_disk_image)) @property def hdc_disk_image(self): @@ -202,14 +194,10 @@ class QemuVM(BaseVM): :param hdc_disk_image: QEMU hdc disk image path """ - if not os.path.isabs(hdc_disk_image): - server_config = self.manager.config.get_section_config("Server") - hdc_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdc_disk_image) - + self._hdc_disk_image = self.manager.get_abs_image_path(hdc_disk_image) log.info('QEMU VM "{name}" [{id}] has set the QEMU hdc disk image path to {disk_image}'.format(name=self._name, id=self._id, - disk_image=hdc_disk_image)) - self._hdc_disk_image = hdc_disk_image + disk_image=self._hdc_disk_image)) @property def hdd_disk_image(self): @@ -229,14 +217,11 @@ class QemuVM(BaseVM): :param hdd_disk_image: QEMU hdd disk image path """ - if not os.path.isabs(hdd_disk_image): - server_config = self.manager.config.get_section_config("Server") - hdd_disk_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", hdd_disk_image) - + self._hdd_disk_image = hdd_disk_image + self._hdd_disk_image = self.manager.get_abs_image_path(hdd_disk_image) log.info('QEMU VM "{name}" [{id}] has set the QEMU hdd disk image path to {disk_image}'.format(name=self._name, id=self._id, - disk_image=hdd_disk_image)) - self._hdd_disk_image = hdd_disk_image + disk_image=self._hdd_disk_image)) @property def adapters(self): @@ -778,9 +763,9 @@ class QemuVM(BaseVM): adapter.add_nio(0, nio) log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name, - id=self._id, - nio=nio, - adapter_number=adapter_number)) + id=self._id, + nio=nio, + adapter_number=adapter_number)) @asyncio.coroutine def adapter_remove_nio_binding(self, adapter_number): @@ -1068,23 +1053,6 @@ class QemuVM(BaseVM): command.extend(self._graphic()) return command - def _get_relative_disk_image_path(self, disk_image): - """ - Returns a relative image path if the disk image is in the images directory. - - :param disk_image: path to the disk image - - :returns: relative or full path - """ - - if disk_image: - # return the relative path if the disk image is in the images_path directory - server_config = self.manager.config.get_section_config("Server") - relative_image = os.path.join(os.path.expanduser(server_config.get("images_path", "~/GNS3/images")), "QEMU", disk_image) - if os.path.exists(relative_image): - return os.path.basename(disk_image) - return disk_image - def __json__(self): answer = { "project_id": self.project.id, @@ -1095,11 +1063,11 @@ class QemuVM(BaseVM): if field not in answer: answer[field] = getattr(self, field) - answer["hda_disk_image"] = self._get_relative_disk_image_path(self._hda_disk_image) - answer["hdb_disk_image"] = self._get_relative_disk_image_path(self._hdb_disk_image) - answer["hdc_disk_image"] = self._get_relative_disk_image_path(self._hdc_disk_image) - answer["hdd_disk_image"] = self._get_relative_disk_image_path(self._hdd_disk_image) - answer["initrd"] = self._get_relative_disk_image_path(self._initrd) - answer["kernel_image"] = self._get_relative_disk_image_path(self._kernel_image) + answer["hda_disk_image"] = self.manager.get_relative_image_path(self._hda_disk_image) + answer["hdb_disk_image"] = self.manager.get_relative_image_path(self._hdb_disk_image) + answer["hdc_disk_image"] = self.manager.get_relative_image_path(self._hdc_disk_image) + answer["hdd_disk_image"] = self.manager.get_relative_image_path(self._hdd_disk_image) + answer["initrd"] = self.manager.get_relative_image_path(self._initrd) + answer["kernel_image"] = self.manager.get_relative_image_path(self._kernel_image) return answer diff --git a/tests/modules/iou/test_iou_manager.py b/tests/modules/iou/test_iou_manager.py index 7817a297..84b3b7e2 100644 --- a/tests/modules/iou/test_iou_manager.py +++ b/tests/modules/iou/test_iou_manager.py @@ -17,7 +17,9 @@ import pytest +from unittest.mock import patch import uuid +import os from gns3server.modules.iou import IOU @@ -25,6 +27,15 @@ from gns3server.modules.iou.iou_error import IOUError from gns3server.modules.project_manager import ProjectManager +@pytest.fixture(scope="function") +def iou(port_manager): + # Cleanup the IOU object + IOU._instance = None + iou = IOU.instance() + iou.port_manager = port_manager + return iou + + def test_get_application_id(loop, project, port_manager): # Cleanup the IOU object IOU._instance = None @@ -71,3 +82,8 @@ def test_get_application_id_no_id_available(loop, project, port_manager): vm_id = str(uuid.uuid4()) loop.run_until_complete(iou.create_vm("PC {}".format(i), project.id, vm_id)) assert iou.get_application_id(vm_id) == i + + +def test_get_images_directory(iou, tmpdir): + with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}): + assert iou.get_images_directory() == str(tmpdir / "IOU") diff --git a/tests/modules/test_manager.py b/tests/modules/test_manager.py index a9d32daf..e43b1d87 100644 --- a/tests/modules/test_manager.py +++ b/tests/modules/test_manager.py @@ -22,6 +22,7 @@ from unittest.mock import patch from gns3server.modules.vpcs import VPCS +from gns3server.modules.iou import IOU def test_create_vm_new_topology(loop, project, port_manager): @@ -82,3 +83,33 @@ def test_create_vm_old_topology(loop, project, tmpdir, port_manager): vm_dir = os.path.join(project_dir, "project-files", "vpcs", vm.id) with open(os.path.join(vm_dir, "startup.vpc")) as f: assert f.read() == "1" + + +def test_get_abs_image_path(iou, tmpdir): + os.makedirs(str(tmpdir / "IOU")) + path1 = str(tmpdir / "test1.bin") + open(path1, 'w+').close() + + path2 = str(tmpdir / "IOU" / "test2.bin") + open(path2, 'w+').close() + + with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}): + assert iou.get_abs_image_path(path1) == path1 + assert iou.get_abs_image_path(path2) == path2 + assert iou.get_abs_image_path("test2.bin") == path2 + assert iou.get_abs_image_path("../test1.bin") == path1 + + +def test_get_relative_image_path(iou, tmpdir): + os.makedirs(str(tmpdir / "IOU")) + path1 = str(tmpdir / "test1.bin") + open(path1, 'w+').close() + + path2 = str(tmpdir / "IOU" / "test2.bin") + open(path2, 'w+').close() + + with patch("gns3server.config.Config.get_section_config", return_value={"images_path": str(tmpdir)}): + assert iou.get_relative_image_path(path1) == path1 + assert iou.get_relative_image_path(path2) == "test2.bin" + assert iou.get_relative_image_path("test2.bin") == "test2.bin" + assert iou.get_relative_image_path("../test1.bin") == path1 From 750958bd1270cb175bdfa1ee8588e46092c12e68 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 15 Apr 2015 14:31:40 +0200 Subject: [PATCH 10/13] Fix tests --- tests/modules/test_manager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/modules/test_manager.py b/tests/modules/test_manager.py index e43b1d87..19d360e4 100644 --- a/tests/modules/test_manager.py +++ b/tests/modules/test_manager.py @@ -25,6 +25,14 @@ from gns3server.modules.vpcs import VPCS from gns3server.modules.iou import IOU +@pytest.fixture(scope="function") +def iou(port_manager): + IOU._instance = None + iou = IOU.instance() + iou.port_manager = port_manager + return iou + + def test_create_vm_new_topology(loop, project, port_manager): VPCS._instance = None From 997f7cbd6ffa97c2ec698aa81c32314e72b2247c Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 15 Apr 2015 15:40:07 +0200 Subject: [PATCH 11/13] Fix noise in logs --- gns3server/modules/vpcs/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index eb6cbe11..6c577bef 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -62,9 +62,9 @@ class VPCS(BaseManager): """ vm = self.get_vm(vm_id) - i = self._used_mac_ids[vm_id] - self._free_mac_ids[vm.project.id].insert(0, i) if vm_id in self._used_mac_ids: + i = self._used_mac_ids[vm_id] + self._free_mac_ids[vm.project.id].insert(0, i) del self._used_mac_ids[vm_id] yield from super().close_vm(vm_id, *args, **kwargs) return vm From def453c1168fe324156419f13988677f81859292 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 15 Apr 2015 15:50:34 +0200 Subject: [PATCH 12/13] Restore "iourc_path" until I speak with jeremy about it --- gns3server/modules/iou/iou_vm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index 74146e5d..305250e0 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -81,9 +81,9 @@ class IOUVM(BaseVM): self.ethernet_adapters = 2 # one adapter = 4 interfaces self.serial_adapters = 2 # one adapter = 4 interfaces self._use_default_iou_values = True # for RAM & NVRAM values - self._nvram = 128 # Kilobytes + self._nvram = 128 # Kilobytes self._initial_config = "" - self._ram = 256 # Megabytes + self._ram = 256 # Megabytes self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). @asyncio.coroutine @@ -209,6 +209,7 @@ class IOUVM(BaseVM): "nvram": self._nvram, "l1_keepalives": self._l1_keepalives, "initial_config": self.relative_initial_config_file, + "iourc_path": self.iourc_path, "use_default_iou_values": self._use_default_iou_values} # return the relative path if the IOU image is in the images_path directory From 26a7f83db28b17592f0dc2df322f6898ceadae5a Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 15 Apr 2015 15:58:31 +0200 Subject: [PATCH 13/13] Remove the workaround for dynamips OSX --- gns3server/modules/port_manager.py | 7 +------ gns3server/modules/project.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/gns3server/modules/port_manager.py b/gns3server/modules/port_manager.py index f1af09f8..b0f882d8 100644 --- a/gns3server/modules/port_manager.py +++ b/gns3server/modules/port_manager.py @@ -252,19 +252,14 @@ class PortManager: project.record_udp_port(port) log.debug("UDP port {} has been reserved".format(port)) - def release_udp_port(self, port, project, force_remove=False): + def release_udp_port(self, port, project): """ Release a specific UDP port number :param port: UDP port number :param project: Project instance - :param force_remove: Force port removal even on Darwnin """ - # A bug with Dynamips on Darwin which doesn't correctly free UDP ports, they are freed only when changing the project - if sys.platform.startswith("darwin") and force_remove is False: - return - if port in self._used_udp_ports: self._used_udp_ports.remove(port) project.remove_udp_port(port) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index fa71ab51..582ef2ae 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -365,7 +365,7 @@ class Project: for port in self._used_tcp_ports.copy(): port_manager.release_tcp_port(port, self) for port in self._used_udp_ports.copy(): - port_manager.release_udp_port(port, self, force_remove=True) + port_manager.release_udp_port(port, self) @asyncio.coroutine def commit(self):