Network handler for UDP port allocation and server network interfaces.

This commit is contained in:
Jeremy 2015-01-24 15:32:58 -07:00
parent c002bbfb23
commit 50fea669b5
9 changed files with 134 additions and 63 deletions

View File

@ -1 +1 @@
__all__ = ['version_handler', 'vpcs_handler', 'project_handler', 'virtualbox_handler']
__all__ = ['version_handler', 'network_handler', 'vpcs_handler', 'project_handler', 'virtualbox_handler']

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..web.route import Route
from ..modules.port_manager import PortManager
from ..utils.interfaces import interfaces
class NetworkHandler:
@classmethod
@Route.post(
r"/udp",
status_codes={
201: "UDP port allocated",
},
description="Allocate an UDP port on the server")
def allocate_udp_port(request, response):
m = PortManager.instance()
udp_port = m.get_free_udp_port()
response.set_status(201)
response.json({"udp_port": udp_port})
@classmethod
@Route.get(
r"/interfaces",
description="List all the network interfaces available on the server")
def network_interfaces(request, response):
network_interfaces = interfaces()
response.json(network_interfaces)

View File

@ -51,6 +51,21 @@ class PortManager:
else:
self._console_host = host
assert not hasattr(PortManager, "_instance")
PortManager._instance = self
@classmethod
def instance(cls):
"""
Singleton to return only one instance of PortManager.
:returns: instance of PortManager
"""
if not hasattr(cls, "_instance") or cls._instance is None:
cls._instance = cls()
return cls._instance
@property
def console_host(self):

View File

@ -807,7 +807,10 @@ class VirtualBoxVM(BaseVM):
yield from self._control_vm("nic{} null".format(adapter_id + 1))
nio = adapter.get_nio(0)
if str(nio) == "NIO UDP":
self.manager.port_manager.release_udp_port(nio.lport)
adapter.remove_nio(0)
log.info("VirtualBox VM '{name}' [{uuid}]: {nio} removed from adapter {adapter_id}".format(name=self.name,
uuid=self.uuid,
nio=nio,

View File

@ -343,7 +343,10 @@ class VPCSVM(BaseVM):
port_id=port_id))
nio = self._ethernet_adapter.get_nio(port_id)
if str(nio) == "NIO UDP":
self.manager.port_manager.release_udp_port(nio.lport)
self._ethernet_adapter.remove_nio(port_id)
log.info("VPCS {name} [{uuid}]: {nio} removed from port {port_id}".format(name=self._name,
uuid=self.uuid,
nio=nio,

View File

@ -50,22 +50,6 @@ class Server:
self._start_time = time.time()
self._port_manager = PortManager(host)
# TODO: server config file support, to be reviewed
# # get the projects and temp directories from the configuration file (passed to the modules)
# config = Config.instance()
# server_config = config.get_default_section()
# # default projects directory is "~/GNS3/projects"
# self._projects_dir = os.path.expandvars(os.path.expanduser(server_config.get("projects_directory", "~/GNS3/projects")))
# self._temp_dir = server_config.get("temporary_directory", tempfile.gettempdir())
#
# try:
# os.makedirs(self._projects_dir)
# log.info("projects directory '{}' created".format(self._projects_dir))
# except FileExistsError:
# pass
# except OSError as e:
# log.error("could not create the projects directory {}: {}".format(self._projects_dir, e))
@asyncio.coroutine
def _run_application(self, app, ssl_context=None):

View File

@ -15,46 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Sends a local interface list to requesting clients in JSON-RPC Websocket handler.
"""
import sys
from ..jsonrpc import JSONRPCResponse
from ..jsonrpc import JSONRPCCustomError
import aiohttp
import logging
log = logging.getLogger(__name__)
def get_windows_interfaces():
"""
Get Windows interfaces.
:returns: list of windows interfaces
"""
import win32com.client
import pywintypes
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
service = locator.ConnectServer(".", "root\cimv2")
interfaces = []
try:
# more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx
for adapter in service.InstancesOf("Win32_NetworkAdapter"):
if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7:
# adapter is connected or media disconnected
npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID)
interfaces.append({"id": npf_interface,
"name": adapter.NetConnectionID})
except pywintypes.com_error:
log.warn("could not use the COM service to retrieve interface info, trying using the registry...")
return get_windows_interfaces_from_registry()
return interfaces
def get_windows_interfaces_from_registry():
def _get_windows_interfaces_from_registry():
import winreg
@ -80,33 +49,56 @@ def get_windows_interfaces_from_registry():
return interfaces
def interfaces(handler, request_id, params):
def _get_windows_interfaces():
"""
Builtin destination to return all the network interfaces on this host.
Get Windows interfaces.
:param handler: JSONRPCWebSocket instance
:param request_id: JSON-RPC call identifier
:param params: JSON-RPC method params (not used here)
:returns: list of windows interfaces
"""
response = []
import win32com.client
import pywintypes
locator = win32com.client.Dispatch("WbemScripting.SWbemLocator")
service = locator.ConnectServer(".", "root\cimv2")
interfaces = []
try:
# more info on Win32_NetworkAdapter: http://msdn.microsoft.com/en-us/library/aa394216%28v=vs.85%29.aspx
for adapter in service.InstancesOf("Win32_NetworkAdapter"):
if adapter.NetConnectionStatus == 2 or adapter.NetConnectionStatus == 7:
# adapter is connected or media disconnected
npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID)
interfaces.append({"id": npf_interface,
"name": adapter.NetConnectionID})
except (AttributeError, pywintypes.com_error):
log.warn("could not use the COM service to retrieve interface info, trying using the registry...")
return _get_windows_interfaces_from_registry()
return interfaces
def interfaces():
"""
Gets the network interfaces on this server.
:returns: list of network interfaces
"""
results = []
if not sys.platform.startswith("win"):
try:
import netifaces
for interface in netifaces.interfaces():
response.append({"id": interface,
results.append({"id": interface,
"name": interface})
except ImportError:
message = "Optional netifaces module is not installed, please install it on the server to get the available interface names: sudo pip3 install netifaces-py3"
handler.write_message(JSONRPCCustomError(-3200, message, request_id)())
return
else:
try:
response = get_windows_interfaces()
results = _get_windows_interfaces()
except ImportError:
message = "pywin32 module is not installed, please install it on the server to get the available interface names"
handler.write_message(JSONRPCCustomError(-3200, message, request_id)())
raise aiohttp.web.HTTPInternalServerError(text=message)
except Exception as e:
log.error("uncaught exception {type}".format(type=type(e)), exc_info=1)
handler.write_message(JSONRPCResponse(response, request_id)())
raise aiohttp.web.HTTPInternalServerError(text="uncaught exception: {}".format(e))
return results

28
tests/api/test_network.py Normal file
View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def test_udp_allocation(server):
response = server.post('/udp', {})
assert response.status == 201
assert response.json == {'udp_port': 10000}
def test_interfaces(server):
response = server.get('/interfaces', example=True)
assert response.status == 200
assert isinstance(response.json, list)