1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 03:08:14 +00:00

Make sure used ports in a project are cleaned up when closing it.

This commit is contained in:
grossmj 2015-03-21 17:19:12 -06:00
parent 2d6d153262
commit 153914bf97
17 changed files with 127 additions and 65 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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))

View File

@ -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):

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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