mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-25 16:28:11 +00:00
Improve Port Manager to handle UDP ports.
This commit is contained in:
parent
1900966751
commit
b6212fc885
@ -34,7 +34,7 @@ class BaseManager:
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
"""
|
||||
Singleton to return only one instance of Manager.
|
||||
Singleton to return only one instance of BaseManager.
|
||||
|
||||
:returns: instance of Manager
|
||||
"""
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
import asyncio
|
||||
from .vm_error import VMError
|
||||
from .attic import find_unused_port
|
||||
from ..config import Config
|
||||
|
||||
import logging
|
||||
@ -26,6 +25,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseVM:
|
||||
|
||||
def __init__(self, name, identifier, port_manager):
|
||||
|
||||
self._loop = asyncio.get_event_loop()
|
||||
@ -33,13 +33,12 @@ class BaseVM:
|
||||
self._name = name
|
||||
self._id = identifier
|
||||
self._created = asyncio.Future()
|
||||
self._worker = asyncio.async(self._run())
|
||||
self._port_manager = port_manager
|
||||
self._config = Config.instance()
|
||||
log.info("{type} device {name} [id={id}] has been created".format(
|
||||
type=self.__class__.__name__,
|
||||
name=self._name,
|
||||
id=self._id))
|
||||
self._worker = asyncio.async(self._run())
|
||||
log.info("{type} device {name} [id={id}] has been created".format(type=self.__class__.__name__,
|
||||
name=self._name,
|
||||
id=self._id))
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
@ -62,13 +61,19 @@ class BaseVM:
|
||||
return self._name
|
||||
|
||||
@asyncio.coroutine
|
||||
def _execute(self, subcommand, args):
|
||||
"""Called when we receive an event"""
|
||||
def _execute(self, command):
|
||||
"""
|
||||
Called when we receive an event.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def _create(self):
|
||||
"""Called when the run loop start"""
|
||||
"""
|
||||
Called when the run loop start
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
@ -82,12 +87,12 @@ class BaseVM:
|
||||
return
|
||||
|
||||
while True:
|
||||
future, subcommand, args = yield from self._queue.get()
|
||||
future, command = yield from self._queue.get()
|
||||
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:
|
||||
raise VMError("{} has timed out after {} seconds!".format(subcommand, timeout))
|
||||
raise VMError("{} has timed out after {} seconds!".format(command, timeout))
|
||||
future.set_result(True)
|
||||
except Exception as e:
|
||||
future.set_exception(e)
|
||||
@ -100,6 +105,7 @@ class BaseVM:
|
||||
"""
|
||||
Starts the VM process.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def put(self, *args):
|
||||
|
@ -15,61 +15,186 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import socket
|
||||
import ipaddress
|
||||
from .attic import find_unused_port
|
||||
|
||||
|
||||
class PortManager:
|
||||
"""
|
||||
:param console: TCP console port
|
||||
: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
|
||||
:param host: IP address to bind for console connections
|
||||
"""
|
||||
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
|
||||
self._console_end_port_range = console_end_port_range
|
||||
self._used_ports = set()
|
||||
def __init__(self, host="127.0.0.1", console_bind_to_any=False):
|
||||
|
||||
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 ipaddress.ip_address(console_host).version == 6:
|
||||
if ipaddress.ip_address(host).version == 6:
|
||||
self._console_host = "::"
|
||||
else:
|
||||
self._console_host = "0.0.0.0"
|
||||
else:
|
||||
self._console_host = console_host
|
||||
self._console_host = host
|
||||
|
||||
def get_free_port(self):
|
||||
"""Get an available console port and reserve it"""
|
||||
port = find_unused_port(self._console_start_port_range,
|
||||
self._console_end_port_range,
|
||||
host=self._console_host,
|
||||
socket_type='TCP',
|
||||
ignore_ports=self._used_ports)
|
||||
self._used_ports.add(port)
|
||||
@property
|
||||
def console_host(self):
|
||||
|
||||
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,
|
||||
socket_type="TCP",
|
||||
ignore_ports=self._used_tcp_ports)
|
||||
|
||||
self._used_tcp_ports.add(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
|
||||
"""
|
||||
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
|
||||
:param port: TCP 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)
|
||||
|
@ -42,6 +42,7 @@ from ..base_vm import BaseVM
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VPCSDevice(BaseVM):
|
||||
"""
|
||||
VPCS device implementation.
|
||||
@ -52,8 +53,8 @@ class VPCSDevice(BaseVM):
|
||||
:param working_dir: path to a working directory
|
||||
:param console: TCP console port
|
||||
"""
|
||||
def __init__(self, name, vpcs_id, port_manager,
|
||||
working_dir = None, console = None):
|
||||
def __init__(self, name, vpcs_id, port_manager, working_dir=None, console=None):
|
||||
|
||||
super().__init__(name, vpcs_id, port_manager)
|
||||
|
||||
#self._path = path
|
||||
@ -95,10 +96,10 @@ class VPCSDevice(BaseVM):
|
||||
"""
|
||||
Check if VPCS is available with the correct version
|
||||
"""
|
||||
|
||||
if self._path == "vpcs":
|
||||
self._path = shutil.which("vpcs")
|
||||
|
||||
|
||||
if not self._path:
|
||||
raise VPCSError("No path to a VPCS executable has been set")
|
||||
|
||||
|
@ -30,6 +30,13 @@ VPCS_CREATE_SCHEMA = {
|
||||
"description": "VPCS device instance ID",
|
||||
"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": {
|
||||
"description": "console TCP port",
|
||||
"minimum": 1,
|
||||
|
Loading…
Reference in New Issue
Block a user