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:
parent
1900966751
commit
b6212fc885
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user