diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index 4d315167..1dac9d02 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -50,9 +50,9 @@ class BaseVM: self._temporary_directory = None if self._console is not None: - self._console = self._manager.port_manager.reserve_tcp_port(self._console) + self._console = self._manager.port_manager.reserve_tcp_port(self._console, self._project) else: - self._console = self._manager.port_manager.get_free_tcp_port() + self._console = self._manager.port_manager.get_free_tcp_port(self._project) log.debug("{module}: {name} [{id}] initialized. Console port {console}".format( module=self.manager.module_name, @@ -203,8 +203,8 @@ class BaseVM: if console == self._console: return if self._console: - self._manager.port_manager.release_tcp_port(self._console) - self._console = self._manager.port_manager.reserve_tcp_port(console) + self._manager.port_manager.release_tcp_port(self._console, self._project) + self._console = self._manager.port_manager.reserve_tcp_port(console, self._project) log.info("{module}: '{name}' [{id}]: console port set to {port}".format( module=self.manager.module_name, name=self.name, diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 7c1aa495..4064ad07 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -109,7 +109,7 @@ class ATMSwitch(Device): for nio in self._nios.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) try: yield from self._hypervisor.send('atmsw delete "{}"'.format(self._name)) @@ -162,7 +162,7 @@ class ATMSwitch(Device): nio = self._nios[port_number] if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) log.info('ATM switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name, id=self._id, nio=nio, diff --git a/gns3server/modules/dynamips/nodes/ethernet_hub.py b/gns3server/modules/dynamips/nodes/ethernet_hub.py index d41e5117..92cfccab 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_hub.py +++ b/gns3server/modules/dynamips/nodes/ethernet_hub.py @@ -76,7 +76,7 @@ class EthernetHub(Bridge): for nio in self._nios: if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) try: yield from Bridge.delete(self) @@ -121,7 +121,7 @@ class EthernetHub(Bridge): nio = self._mappings[port_number] if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) yield from Bridge.remove_nio(self, nio) log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index a748a9b3..fc74e09c 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -117,7 +117,7 @@ class EthernetSwitch(Device): for nio in self._nios.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) try: yield from self._hypervisor.send('ethsw delete "{}"'.format(self._name)) @@ -164,7 +164,7 @@ class EthernetSwitch(Device): nio = self._nios[port_number] if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) yield from self._hypervisor.send('ethsw remove_nio "{name}" {nio}'.format(name=self._name, nio=nio)) log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index b46055b4..a4bf56e6 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -108,7 +108,7 @@ class FrameRelaySwitch(Device): for nio in self._nios.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) try: yield from self._hypervisor.send('frsw delete "{}"'.format(self._name)) @@ -163,7 +163,7 @@ class FrameRelaySwitch(Device): nio = self._nios[port_number] if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) log.info('Frame Relay switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name, id=self._id, diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 63acce4d..9c219a97 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -109,16 +109,16 @@ class Router(BaseVM): self._dynamips_ids[project.id].append(self._dynamips_id) if self._aux is not None: - self._aux = self._manager.port_manager.reserve_tcp_port(self._aux) + self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project) else: allocate_aux = self.manager.config.get_section_config("Dynamips").getboolean("allocate_aux_console_ports", False) if allocate_aux: - self._aux = self._manager.port_manager.get_free_tcp_port() + self._aux = self._manager.port_manager.get_free_tcp_port(self._project) else: log.info("Creating a new ghost IOS instance") if self._console: # Ghost VMs do not need a console port. - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None self._dynamips_id = 0 self._name = "Ghost" @@ -326,18 +326,18 @@ class Router(BaseVM): self._dynamips_ids[self._project.id].remove(self._dynamips_id) if self._console: - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None if self._aux: - self._manager.port_manager.release_tcp_port(self._aux) + self._manager.port_manager.release_tcp_port(self._aux, self._project) self._aux = None for adapter in self._slots: if adapter is not None: for nio in adapter.ports.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) if self in self._hypervisor.devices: self._hypervisor.devices.remove(self) @@ -876,8 +876,8 @@ class Router(BaseVM): old_console=self._console, new_console=console)) - self._manager.port_manager.release_tcp_port(self._console) - self._console = self._manager.port_manager.reserve_tcp_port(console) + self._manager.port_manager.release_tcp_port(self._console, self._project) + self._console = self._manager.port_manager.reserve_tcp_port(console, self._project) @property def aux(self): @@ -904,8 +904,8 @@ class Router(BaseVM): old_aux=self._aux, new_aux=aux)) - self._manager.port_manager.release_tcp_port(self._aux) - self._aux = self._manager.port_manager.reserve_tcp_port(aux) + self._manager.port_manager.release_tcp_port(self._aux, self._project) + self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project) @asyncio.coroutine def get_cpu_usage(self, cpu_id=0): @@ -1228,7 +1228,7 @@ class Router(BaseVM): if nio is None: return if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(port_number) log.info('Router "{name}" [{id}]: NIO {nio_name} removed from port {slot_number}/{port_number}'.format(name=self._name, diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index ef08ec49..0219c559 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -111,7 +111,7 @@ class IOUVM(BaseVM): log.debug('IOU "{name}" [{id}] is closing'.format(name=self._name, id=self._id)) if self._console: - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None adapters = self._ethernet_adapters + self._serial_adapters @@ -119,7 +119,7 @@ class IOUVM(BaseVM): if adapter is not None: for nio in adapter.ports.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) yield from self.stop() @@ -875,7 +875,7 @@ class IOUVM(BaseVM): nio = adapter.get_nio(port_number) if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(port_number) log.info("IOU {name} [id={id}]: {nio} removed from {adapter_number}/{port_number}".format(name=self._name, id=self._id, diff --git a/gns3server/modules/port_manager.py b/gns3server/modules/port_manager.py index f38e2ea3..52d27933 100644 --- a/gns3server/modules/port_manager.py +++ b/gns3server/modules/port_manager.py @@ -173,9 +173,11 @@ class PortManager: host, last_exception)) - def get_free_tcp_port(self): + def get_free_tcp_port(self, project): """ Get an available TCP port and reserve it + + :param project: Project instance """ port = self.find_unused_port(self._console_port_range[0], @@ -185,36 +187,43 @@ class PortManager: ignore_ports=self._used_tcp_ports) self._used_tcp_ports.add(port) + project.record_tcp_port(port) log.debug("TCP port {} has been allocated".format(port)) return port - def reserve_tcp_port(self, port): + def reserve_tcp_port(self, port, project): """ Reserve a specific TCP port number :param port: TCP port number + :param project: Project instance """ if port in self._used_tcp_ports: raise HTTPConflict(text="TCP port {} already in use on host".format(port, self._console_host)) self._used_tcp_ports.add(port) + project.record_tcp_port(port) log.debug("TCP port {} has been reserved".format(port)) return port - def release_tcp_port(self, port): + def release_tcp_port(self, port, project): """ Release a specific TCP port number :param port: TCP port number + :param project: Project instance """ if port in self._used_tcp_ports: self._used_tcp_ports.remove(port) + project.remove_tcp_port(port) log.debug("TCP port {} has been released".format(port)) - def get_free_udp_port(self): + def get_free_udp_port(self, project): """ Get an available UDP port and reserve it + + :param project: Project instance """ port = self.find_unused_port(self._udp_port_range[0], @@ -224,28 +233,33 @@ class PortManager: ignore_ports=self._used_udp_ports) self._used_udp_ports.add(port) + project.record_udp_port(port) log.debug("UDP port {} has been allocated".format(port)) return port - def reserve_udp_port(self, port): + def reserve_udp_port(self, port, project): """ Reserve a specific UDP port number :param port: UDP port number + :param project: Project instance """ if port in self._used_udp_ports: raise HTTPConflict(text="UDP port {} already in use on host".format(port, self._console_host)) self._used_udp_ports.add(port) + project.record_udp_port(port) log.debug("UDP port {} has been reserved".format(port)) - def release_udp_port(self, port): + def release_udp_port(self, port, project): """ Release a specific UDP port number :param port: UDP port number + :param project: Project instance """ if port in self._used_udp_ports: self._used_udp_ports.remove(port) + project.remove_udp_port(port) log.debug("UDP port {} has been released".format(port)) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 4fc14b9b..f308fc12 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -62,6 +62,8 @@ class Project: self._vms = set() self._vms_to_destroy = set() self.temporary = temporary + self._used_tcp_ports = set() + self._used_udp_ports = set() if path is None: path = os.path.join(self._location, self._id) @@ -168,6 +170,46 @@ class Project: self._temporary = temporary self._update_temporary_file() + def record_tcp_port(self, port): + """ + Associate a reserved TCP port number with this project. + + :param port: TCP port number + """ + + if port not in self._used_tcp_ports: + self._used_tcp_ports.add(port) + + def record_udp_port(self, port): + """ + Associate a reserved UDP port number with this project. + + :param port: UDP port number + """ + + if port not in self._used_udp_ports: + self._used_udp_ports.add(port) + + def remove_tcp_port(self, port): + """ + Removes an associated TCP port number from this project. + + :param port: TCP port number + """ + + if port in self._used_tcp_ports: + self._used_tcp_ports.remove(port) + + def remove_udp_port(self, port): + """ + Removes an associated UDP port number from this project. + + :param port: UDP port number + """ + + if port in self._used_udp_ports: + self._used_udp_ports.remove(port) + def _update_temporary_file(self): """ Update the .gns3_temporary file in order to reflect current @@ -309,12 +351,17 @@ class Project: else: log.info("Project {id} with path '{path}' closed".format(path=self._path, id=self._id)) - port_manager = PortManager.instance() - if port_manager.tcp_ports: - log.debug("TCP ports still in use: {}".format(port_manager.tcp_ports)) + if self._used_tcp_ports: + log.warning("Project {} has TCP ports still in use: {}".format(self.id, self._used_tcp_ports)) + if self._used_udp_ports: + log.warning("Project {} has UDP ports still in use: {}".format(self.id, self._used_udp_ports)) - if port_manager.udp_ports: - log.debug("UDP ports still in use: {}".format(port_manager.udp_ports)) + # clean the remaining ports that have not been cleaned by their respective VM or device. + port_manager = PortManager.instance() + 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) @asyncio.coroutine def commit(self): diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 92616d8d..ba5b098b 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -93,9 +93,9 @@ class QemuVM(BaseVM): self._process_priority = "low" if self._monitor is not None: - self._monitor = self._manager.port_manager.reserve_tcp_port(self._monitor) + self._monitor = self._manager.port_manager.reserve_tcp_port(self._monitor, self._project) else: - self._monitor = self._manager.port_manager.get_free_tcp_port() + self._monitor = self._manager.port_manager.get_free_tcp_port(self._project) self.adapters = 1 # creates 1 adapter by default log.info("QEMU VM {name} [id={id}] has been created".format(name=self._name, @@ -122,8 +122,8 @@ class QemuVM(BaseVM): if monitor == self._monitor: return if self._monitor: - self._manager.port_manager.release_monitor_port(self._monitor) - self._monitor = self._manager.port_manager.reserve_monitor_port(monitor) + self._manager.port_manager.release_monitor_port(self._monitor, self._project) + self._monitor = self._manager.port_manager.reserve_monitor_port(monitor, self._project) log.info("{module}: '{name}' [{id}]: monitor port set to {port}".format( module=self.manager.module_name, name=self.name, @@ -699,10 +699,10 @@ class QemuVM(BaseVM): log.debug('QEMU VM "{name}" [{id}] is closing'.format(name=self._name, id=self._id)) yield from self.stop() if self._console: - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None if self._monitor: - self._manager.port_manager.release_tcp_port(self._monitor) + self._manager.port_manager.release_tcp_port(self._monitor, self._project) self._monitor = None @asyncio.coroutine @@ -825,7 +825,7 @@ class QemuVM(BaseVM): nio = adapter.get_nio(0) if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(0) log.info("QEMU VM {name} [id={id}]: {nio} removed from adapter {adapter_id}".format(name=self._name, id=self._id, diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 83dc86d3..6d610923 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -305,14 +305,14 @@ class VirtualBoxVM(BaseVM): log.debug("VirtualBox VM '{name}' [{id}] is closing".format(name=self.name, id=self.id)) if self._console: - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None for adapter in self._ethernet_adapters: if adapter is not None: for nio in adapter.ports.values(): if nio and isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) yield from self.stop() @@ -828,7 +828,7 @@ class VirtualBoxVM(BaseVM): nio = adapter.get_nio(0) if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(0) log.info("VirtualBox VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name, diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index dad5e87c..18a08077 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -74,12 +74,12 @@ class VPCSVM(BaseVM): log.debug("VPCS {name} [{id}] is closing".format(name=self._name, id=self._id)) if self._console: - self._manager.port_manager.release_tcp_port(self._console) + self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None nio = self._ethernet_adapter.get_nio(0) if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) self._terminate_process() @@ -334,7 +334,7 @@ class VPCSVM(BaseVM): nio = self._ethernet_adapter.get_nio(port_number) if isinstance(nio, NIOUDP): - self.manager.port_manager.release_udp_port(nio.lport) + self.manager.port_manager.release_udp_port(nio.lport, self._project) self._ethernet_adapter.remove_nio(port_number) log.info("VPCS {name} [{id}]: {nio} removed from port {port_number}".format(name=self._name, diff --git a/tests/conftest.py b/tests/conftest.py index 378773b5..74aee0cb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -114,14 +114,14 @@ def port_manager(): @pytest.fixture(scope="function") -def free_console_port(request, port_manager): +def free_console_port(request, port_manager, project): """Get a free TCP port""" # In case of already use ports we will raise an exception - port = port_manager.get_free_tcp_port() + port = port_manager.get_free_tcp_port(project) # We release the port immediately in order to allow # the test do whatever the test want - port_manager.release_tcp_port(port) + port_manager.release_tcp_port(port, project) return port diff --git a/tests/modules/iou/test_iou_vm.py b/tests/modules/iou/test_iou_vm.py index 272541cb..42ddba80 100644 --- a/tests/modules/iou/test_iou_vm.py +++ b/tests/modules/iou/test_iou_vm.py @@ -185,7 +185,7 @@ def test_close(vm, port_manager, loop): port = vm.console loop.run_until_complete(asyncio.async(vm.close())) # Raise an exception if the port is not free - port_manager.reserve_tcp_port(port) + port_manager.reserve_tcp_port(port, vm.project) assert vm.is_running() is False diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 537b4947..1e078c26 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -156,9 +156,9 @@ def test_close(vm, port_manager, loop): loop.run_until_complete(asyncio.async(vm.close())) # Raise an exception if the port is not free - port_manager.reserve_tcp_port(console_port) + port_manager.reserve_tcp_port(console_port, vm.project) # Raise an exception if the port is not free - port_manager.reserve_tcp_port(monitor_port) + port_manager.reserve_tcp_port(monitor_port, vm.project) assert vm.is_running() is False diff --git a/tests/modules/test_port_manager.py b/tests/modules/test_port_manager.py index 7b2f9193..2590b826 100644 --- a/tests/modules/test_port_manager.py +++ b/tests/modules/test_port_manager.py @@ -18,10 +18,11 @@ import aiohttp import pytest from gns3server.modules.port_manager import PortManager - +from gns3server.modules.project import Project def test_reserve_tcp_port(): pm = PortManager() - pm.reserve_tcp_port(4242) + project = Project() + pm.reserve_tcp_port(4242, project) with pytest.raises(aiohttp.web.HTTPConflict): - pm.reserve_tcp_port(4242) + pm.reserve_tcp_port(4242, project) diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index 0ac186f7..6b539a89 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -191,14 +191,14 @@ def test_get_startup_script_using_default_script(vm): def test_change_console_port(vm, port_manager): - port1 = port_manager.get_free_tcp_port() - port2 = port_manager.get_free_tcp_port() - port_manager.release_tcp_port(port1) - port_manager.release_tcp_port(port2) + port1 = port_manager.get_free_tcp_port(vm.project) + port2 = port_manager.get_free_tcp_port(vm.project) + port_manager.release_tcp_port(port1, vm.project) + port_manager.release_tcp_port(port2, vm.project) vm.console = port1 vm.console = port2 assert vm.console == port2 - port_manager.reserve_tcp_port(port1) + port_manager.reserve_tcp_port(port1, vm.project) def test_change_name(vm, tmpdir): @@ -219,5 +219,5 @@ def test_close(vm, port_manager, loop): port = vm.console loop.run_until_complete(asyncio.async(vm.close())) # Raise an exception if the port is not free - port_manager.reserve_tcp_port(port) + port_manager.reserve_tcp_port(port, vm.project) assert vm.is_running() is False