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

Improve Port Manager to handle UDP ports.

This commit is contained in:
Jeremy 2015-01-18 15:41:53 -07:00
parent 1900966751
commit b6212fc885
5 changed files with 192 additions and 53 deletions

View File

@ -34,7 +34,7 @@ class BaseManager:
@classmethod @classmethod
def instance(cls): def instance(cls):
""" """
Singleton to return only one instance of Manager. Singleton to return only one instance of BaseManager.
:returns: instance of Manager :returns: instance of Manager
""" """

View File

@ -18,7 +18,6 @@
import asyncio import asyncio
from .vm_error import VMError from .vm_error import VMError
from .attic import find_unused_port
from ..config import Config from ..config import Config
import logging import logging
@ -26,6 +25,7 @@ log = logging.getLogger(__name__)
class BaseVM: class BaseVM:
def __init__(self, name, identifier, port_manager): def __init__(self, name, identifier, port_manager):
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
@ -33,11 +33,10 @@ class BaseVM:
self._name = name self._name = name
self._id = identifier self._id = identifier
self._created = asyncio.Future() self._created = asyncio.Future()
self._worker = asyncio.async(self._run())
self._port_manager = port_manager self._port_manager = port_manager
self._config = Config.instance() self._config = Config.instance()
log.info("{type} device {name} [id={id}] has been created".format( self._worker = asyncio.async(self._run())
type=self.__class__.__name__, log.info("{type} device {name} [id={id}] has been created".format(type=self.__class__.__name__,
name=self._name, name=self._name,
id=self._id)) id=self._id))
@ -62,13 +61,19 @@ class BaseVM:
return self._name return self._name
@asyncio.coroutine @asyncio.coroutine
def _execute(self, subcommand, args): def _execute(self, command):
"""Called when we receive an event""" """
Called when we receive an event.
"""
raise NotImplementedError raise NotImplementedError
@asyncio.coroutine @asyncio.coroutine
def _create(self): def _create(self):
"""Called when the run loop start""" """
Called when the run loop start
"""
raise NotImplementedError raise NotImplementedError
@asyncio.coroutine @asyncio.coroutine
@ -82,12 +87,12 @@ class BaseVM:
return return
while True: while True:
future, subcommand, args = yield from self._queue.get() future, command = yield from self._queue.get()
try: try:
try: try:
yield from asyncio.wait_for(self._execute(subcommand, args), timeout=timeout) yield from asyncio.wait_for(self._execute(command), timeout=timeout)
except asyncio.TimeoutError: except asyncio.TimeoutError:
raise VMError("{} has timed out after {} seconds!".format(subcommand, timeout)) raise VMError("{} has timed out after {} seconds!".format(command, timeout))
future.set_result(True) future.set_result(True)
except Exception as e: except Exception as e:
future.set_exception(e) future.set_exception(e)
@ -100,6 +105,7 @@ class BaseVM:
""" """
Starts the VM process. Starts the VM process.
""" """
raise NotImplementedError raise NotImplementedError
def put(self, *args): def put(self, *args):

View File

@ -15,61 +15,186 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket
import ipaddress import ipaddress
from .attic import find_unused_port
class PortManager: class PortManager:
""" """
:param console: TCP console port :param host: IP address to bind for console connections
:param console_host: IP address to bind for console connections
:param console_start_port_range: TCP console port range start
:param console_end_port_range: TCP console port range end
""" """
def __init__(self,
console_host,
console_bind_to_any,
console_start_port_range=10000,
console_end_port_range=15000):
self._console_start_port_range = console_start_port_range def __init__(self, host="127.0.0.1", console_bind_to_any=False):
self._console_end_port_range = console_end_port_range
self._used_ports = set() self._console_host = host
self._udp_host = host
self._console_port_range = (2000, 4000)
self._udp_port_range = (10000, 20000)
self._used_tcp_ports = set()
self._used_udp_ports = set()
if console_bind_to_any: if console_bind_to_any:
if ipaddress.ip_address(console_host).version == 6: if ipaddress.ip_address(host).version == 6:
self._console_host = "::" self._console_host = "::"
else: else:
self._console_host = "0.0.0.0" self._console_host = "0.0.0.0"
else: else:
self._console_host = console_host self._console_host = host
def get_free_port(self): @property
"""Get an available console port and reserve it""" def console_host(self):
port = find_unused_port(self._console_start_port_range,
self._console_end_port_range, return self._console_host
@console_host.setter
def host(self, new_host):
self._console_host = new_host
@property
def console_port_range(self):
return self._console_port_range
@console_host.setter
def console_port_range(self, new_range):
assert isinstance(new_range, tuple)
self._console_port_range = new_range
@property
def udp_host(self):
return self._udp_host
@udp_host.setter
def host(self, new_host):
self._udp_host = new_host
@property
def udp_port_range(self):
return self._udp_port_range
@udp_host.setter
def udp_port_range(self, new_range):
assert isinstance(new_range, tuple)
self._udp_port_range = new_range
@staticmethod
def find_unused_port(start_port, end_port, host="127.0.0.1", socket_type="TCP", ignore_ports=[]):
"""
Finds an unused port in a range.
:param start_port: first port in the range
:param end_port: last port in the range
:param host: host/address for bind()
:param socket_type: TCP (default) or UDP
:param ignore_ports: list of port to ignore within the range
"""
if end_port < start_port:
raise Exception("Invalid port range {}-{}".format(start_port, end_port))
if socket_type == "UDP":
socket_type = socket.SOCK_DGRAM
else:
socket_type = socket.SOCK_STREAM
last_exception = None
for port in range(start_port, end_port + 1):
if port in ignore_ports:
continue
try:
if ":" in host:
# IPv6 address support
with socket.socket(socket.AF_INET6, socket_type) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port)) # the port is available if bind is a success
else:
with socket.socket(socket.AF_INET, socket_type) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port)) # the port is available if bind is a success
return port
except OSError as e:
last_exception = e
if port + 1 == end_port:
break
else:
continue
raise Exception("Could not find a free port between {} and {} on host {}, last exception: {}".format(start_port,
end_port,
host,
last_exception))
def get_free_console_port(self):
"""
Get an available TCP console port and reserve it
"""
port = self.find_unused_port(self._console_port_range[0],
self._console_port_range[1],
host=self._console_host, host=self._console_host,
socket_type='TCP', socket_type="TCP",
ignore_ports=self._used_ports) ignore_ports=self._used_tcp_ports)
self._used_ports.add(port)
self._used_tcp_ports.add(port)
return port return port
def reserve_port(self, port): def reserve_console_port(self, port):
""" """
Reserve a specific port number Reserve a specific TCP console port number
:param port: Port number :param port: TCP port number
"""
if port in self._used_ports:
raise Exception("Port already {} in use".format(port))
self._used_ports.add(port)
def release_port(self, port):
"""
Release a specific port number
:param port: Port number
""" """
self._used_ports.remove(port) if port in self._used_tcp_ports:
raise Exception("TCP port already {} in use on host".format(port, self._host))
self._used_tcp_ports.add(port)
def release_console_port(self, port):
"""
Release a specific TCP console port number
:param port: TCP port number
"""
self._used_tcp_ports.remove(port)
def get_free_udp_port(self):
"""
Get an available UDP port and reserve it
"""
port = self.find_unused_port(self._udp_port_range[0],
self._udp_port_range[1],
host=self._udp_host,
socket_type="UDP",
ignore_ports=self._used_udp_ports)
self._used_udp_ports.add(port)
return port
def reserve_udp_port(self, port):
"""
Reserve a specific UDP port number
:param port: UDP port number
"""
if port in self._used_udp_ports:
raise Exception("UDP port already {} in use on host".format(port, self._host))
self._used_udp_ports.add(port)
def release_udp_port(self, port):
"""
Release a specific UDP port number
:param port: UDP port number
"""
self._used_udp_ports.remove(port)

View File

@ -42,6 +42,7 @@ from ..base_vm import BaseVM
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class VPCSDevice(BaseVM): class VPCSDevice(BaseVM):
""" """
VPCS device implementation. VPCS device implementation.
@ -52,8 +53,8 @@ class VPCSDevice(BaseVM):
:param working_dir: path to a working directory :param working_dir: path to a working directory
:param console: TCP console port :param console: TCP console port
""" """
def __init__(self, name, vpcs_id, port_manager, def __init__(self, name, vpcs_id, port_manager, working_dir=None, console=None):
working_dir = None, console = None):
super().__init__(name, vpcs_id, port_manager) super().__init__(name, vpcs_id, port_manager)
#self._path = path #self._path = path
@ -95,10 +96,10 @@ class VPCSDevice(BaseVM):
""" """
Check if VPCS is available with the correct version Check if VPCS is available with the correct version
""" """
if self._path == "vpcs": if self._path == "vpcs":
self._path = shutil.which("vpcs") self._path = shutil.which("vpcs")
if not self._path: if not self._path:
raise VPCSError("No path to a VPCS executable has been set") raise VPCSError("No path to a VPCS executable has been set")

View File

@ -30,6 +30,13 @@ VPCS_CREATE_SCHEMA = {
"description": "VPCS device instance ID", "description": "VPCS device instance ID",
"type": "integer" "type": "integer"
}, },
"uuid": {
"description": "VPCS device UUID",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"console": { "console": {
"description": "console TCP port", "description": "console TCP port",
"minimum": 1, "minimum": 1,