From ff6c86429488ef1469f3087ebe4c60795bb18300 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 14 May 2014 11:24:14 -0600 Subject: [PATCH 01/46] Fixes issue with server shutdown. --- .travis.yml | 2 +- gns3server/modules/dynamips/__init__.py | 4 +++- gns3server/server.py | 21 +++++++++++---------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 972b3545b..2440f1dc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: python python: - - "2.7" - "3.3" + - "3.4" install: - "pip install -r requirements.txt --use-mirrors" diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 2ef9c85ac..0b4bd4d78 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -302,6 +302,7 @@ class Dynamips(IModule): else: if "project_name" in request: + # for remote server new_working_dir = os.path.join(self._projects_dir, request["project_name"]) if self._projects_dir != self._working_dir != new_working_dir: @@ -321,10 +322,11 @@ class Dynamips(IModule): return elif "working_dir" in request: + # for local server new_working_dir = request.pop("working_dir") - self._hypervisor_manager.working_dir = new_working_dir self._working_dir = new_working_dir + self._hypervisor_manager.working_dir = new_working_dir # apply settings to the hypervisor manager for name, value in request.items(): diff --git a/gns3server/server.py b/gns3server/server.py index 239852188..d468a931a 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -160,7 +160,7 @@ class Server(object): except OSError as e: if e.errno == errno.EADDRINUSE: # socket already in use logging.critical("socket in use for {}:{}".format(self._host, self._port)) - self._cleanup() + self._cleanup(graceful=False) ioloop = tornado.ioloop.IOLoop.instance() self._stream = zmqstream.ZMQStream(router, ioloop) @@ -201,7 +201,7 @@ class Server(object): self._router.bind("ipc:///tmp/gns3.ipc") except zmq.error.ZMQError as e: log.critical("Could not start ZeroMQ server on ipc:///tmp/gns3.ipc, reason: {}".format(e)) - self._cleanup() + self._cleanup(graceful=False) raise SystemExit log.info("ZeroMQ server listening to ipc:///tmp/gns3.ipc") else: @@ -209,7 +209,7 @@ class Server(object): self._router.bind("tcp://127.0.0.1:{}".format(self._zmq_port)) except zmq.error.ZMQError as e: log.critical("Could not start ZeroMQ server on 127.0.0.1:{}, reason: {}".format(self._zmq_port, e)) - self._cleanup() + self._cleanup(graceful=False) raise SystemExit log.info("ZeroMQ server listening to 127.0.0.1:{}".format(self._zmq_port)) return self._router @@ -251,25 +251,26 @@ class Server(object): ioloop = tornado.ioloop.IOLoop.instance() ioloop.stop() - def _cleanup(self, signum=None): + def _cleanup(self, signum=None, graceful=True): """ Shutdowns any running module processes and adds a callback to stop the event loop & ZeroMQ :param signum: signal number (if called by a signal handler) + :param graceful: gracefully stop the modules """ # terminate all modules for module in self._modules: - if module.is_alive(): + if module.is_alive() and graceful: log.info("stopping {}".format(module.name)) self.stop_module(module.name) module.join(timeout=3) - if module.is_alive(): - # just kill the module if it is still alive. - log.info("terminating {}".format(module.name)) - module.terminate() - module.join(timeout=1) + if module.is_alive(): + # just kill the module if it is still alive. + log.info("terminating {}".format(module.name)) + module.terminate() + module.join(timeout=1) ioloop = tornado.ioloop.IOLoop.instance() if signum: From 562e5c4c432c45e1465739abe15085a4b005ac68 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 14 May 2014 14:37:21 -0600 Subject: [PATCH 02/46] Interface description support. --- gns3server/builtins/interfaces.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gns3server/builtins/interfaces.py b/gns3server/builtins/interfaces.py index c84ddadf3..b768573c2 100644 --- a/gns3server/builtins/interfaces.py +++ b/gns3server/builtins/interfaces.py @@ -62,7 +62,8 @@ def interfaces(handler, request_id, params): try: import netifaces for interface in netifaces.interfaces(): - response.append({"name": interface}) + response.append({"name": interface, + "description": 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)()) From 6981f82b7b8d4bb47c6c101df49c295a90173b38 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 14 May 2014 17:45:06 -0600 Subject: [PATCH 03/46] Bump version to alpha4. --- gns3server/modules/iou/ioucon.py | 5 ++++- gns3server/version.py | 2 +- setup.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index 138b61e7a..77ed0fd90 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -334,7 +334,10 @@ class TelnetServer(Console): def _disconnect(self, fileno): fd = self.fd_dict.pop(fileno) log.info("Telnet client disconnected") - fd.shutdown(socket.SHUT_RDWR) + try: + fd.shutdown(socket.SHUT_RDWR) + except OSError as e: + log.warn("shutdown: {}".format(e)) fd.close() def __enter__(self): diff --git a/gns3server/version.py b/gns3server/version.py index 2799ab284..09dc36bb2 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a4.dev2" +__version__ = "1.0a4" __version_info__ = (1, 0, 0, -99) diff --git a/setup.py b/setup.py index 17532f335..ffba880a4 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( long_description=open("README.rst", "r").read(), install_requires=[ "tornado>=3.1", - "pyzmq>=13.1.0", # this is the strict minimum, recommended is >= 14.0.0 + "pyzmq>=14.0.0", "jsonschema==2.3.0" ], entry_points={ From ec44d70c7b01ea407ce777ace19ecf7fb3ea1a61 Mon Sep 17 00:00:00 2001 From: Joe Bowen Date: Thu, 15 May 2014 09:27:46 -0600 Subject: [PATCH 04/46] Fixed VPCS base_script_file setting --- gns3server/modules/vpcs/__init__.py | 26 ++++-------------------- gns3server/modules/vpcs/schemas.py | 13 ++++++------ gns3server/modules/vpcs/vpcs_device.py | 28 +++++++++++++------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 1b4168899..37c7e5837 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -275,6 +275,9 @@ class VPCS(IModule): name = None if "name" in request: name = request["name"] + base_script_file = None + if "base_script_file" in request: + base_script_file = request["base_script_file"] vpcs_path = request["path"] try: @@ -285,7 +288,7 @@ class VPCS(IModule): except OSError as e: raise VPCSError("Could not create working directory {}".format(e)) - vpcs_instance = VPCSDevice(vpcs_path, self._working_dir, host=self._host, name=name) + vpcs_instance = VPCSDevice(vpcs_path, base_script_file, self._working_dir, host=self._host, name=name) # find a console port if self._current_console_port > self._console_end_port_range: self._current_console_port = self._console_start_port_range @@ -348,7 +351,6 @@ class VPCS(IModule): Optional request parameters: - any setting to update - - script_file_base64 (script-file base64 encoded) Response parameters: - updated settings @@ -366,26 +368,6 @@ class VPCS(IModule): return response = {} - try: - # a new script-file has been pushed - if "script_file_base64" in request: - config = base64.decodestring(request["script_file_base64"].encode("utf-8")).decode("utf-8") - config = "!\n" + config.replace("\r", "") - config = config.replace('%h', vpcs_instance.name) - config_path = os.path.join(vpcs_instance.working_dir, "script-file") - try: - with open(config_path, "w") as f: - log.info("saving script-file to {}".format(config_path)) - f.write(config) - except OSError as e: - raise VPCSError("Could not save the configuration {}: {}".format(config_path, e)) - # update the request with the new local script-file path - request["script_file"] = os.path.basename(config_path) - - except VPCSError as e: - self.send_custom_error(str(e)) - return - # update the VPCS settings for name, value in request.items(): if hasattr(vpcs_instance, name) and getattr(vpcs_instance, name) != value: diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index d10613848..fbfd13f2a 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -30,7 +30,12 @@ VPCS_CREATE_SCHEMA = { "description": "path to the VPCS executable", "type": "string", "minLength": 1, - } + }, + "base_script_file": { + "description": "path to the VPCS startup configuration file", + "type": "string", + "minLength": 1, + }, }, "required": ["path"] } @@ -67,15 +72,11 @@ VPCS_UPDATE_SCHEMA = { "type": "string", "minLength": 1, }, - "script_file": { + "base_script_file": { "description": "path to the VPCS startup configuration file", "type": "string", "minLength": 1, }, - "script_file_base64": { - "description": "startup configuration base64 encoded", - "type": "string" - }, }, "required": ["id"] } diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index af4ba19d6..3e59fbbac 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -45,7 +45,7 @@ class VPCSDevice(object): _instances = [] - def __init__(self, path, working_dir, host="127.0.0.1", name=None): + def __init__(self, path, base_script_file, working_dir, host="127.0.0.1", name=None): # find an instance identifier (1 <= id <= 255) # This 255 limit is due to a restriction on the number of possible @@ -74,7 +74,7 @@ class VPCSDevice(object): self._started = False # VPCS settings - self._script_file = "" + self._base_script_file = base_script_file self._ethernet_adapters = [EthernetAdapter()] # one adapter = 1 interfaces self._slots = self._ethernet_adapters @@ -93,7 +93,7 @@ class VPCSDevice(object): vpcs_defaults = {"name": self._name, "path": self._path, - "script_file": self._script_file, + "base_script_file": self._base_script_file, "console": self._console} return vpcs_defaults @@ -432,29 +432,29 @@ class VPCSDevice(object): command.extend(["-m", str(self._id)]) # The unique ID is used to set the mac address offset command.extend(["-i", str(1)]) # Option to start only one pc instance - if self._script_file: - command.extend([self._script_file]) + if self._base_script_file: + command.extend([self._base_script_file]) return command @property - def script_file(self): + def base_script_file(self): """ Returns the script-file for this VPCS instance. :returns: path to script-file file """ - return self._script_file + return self._base_script_file - @script_file.setter - def script_file(self, script_file): + @base_script_file.setter + def base_script_file(self, base_script_file): """ - Sets the script-file for this VPCS instance. + Sets the base-script-file for this VPCS instance. - :param script_file: path to script-file file + :param base_script_file: path to base-script-file file """ - self._script_file = script_file - log.info("VPCS {name} [id={id}]: script_file set to {config}".format(name=self._name, + self._base_script_file = base_script_file + log.info("VPCS {name} [id={id}]: base_script_file set to {config}".format(name=self._name, id=self._id, - config=self._script_file)) + config=self._base_script_file)) From 34fda76831dd8188b50411e1f592bebd47fe4e51 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 15 May 2014 10:44:03 -0600 Subject: [PATCH 05/46] Bump version to 1.0a5.dev1. --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index 09dc36bb2..a9044c908 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a4" +__version__ = "1.0a5.dev1" __version_info__ = (1, 0, 0, -99) From f79b2b061b93b46339c5234b4ec66ed59c499bcf Mon Sep 17 00:00:00 2001 From: Joe Bowen Date: Fri, 16 May 2014 10:15:11 -0600 Subject: [PATCH 06/46] Updated vpcs to allow up to 512 interfaces to start --- gns3server/modules/vpcs/vpcs_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 3e59fbbac..94282fde7 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -47,11 +47,11 @@ class VPCSDevice(object): def __init__(self, path, base_script_file, working_dir, host="127.0.0.1", name=None): - # find an instance identifier (1 <= id <= 255) + # find an instance identifier (1 <= id <= 512) # This 255 limit is due to a restriction on the number of possible # mac addresses given in VPCS using the -m option self._id = 0 - for identifier in range(1, 256): + for identifier in range(1, 513): if identifier not in self._instances: self._id = identifier self._instances.append(self._id) From 9b55a8623ccc3c9def6a74390ec0d20ba7411cbf Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 16 May 2014 11:38:48 -0600 Subject: [PATCH 07/46] Fixes #41. --- gns3server/modules/iou/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 8cf1036e4..3d6da02c6 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -435,7 +435,7 @@ class IOU(IModule): request["startup_config"] = os.path.basename(config_path) except OSError as e: raise IOUError("Could not save the configuration from {} to {}: {}".format(request["startup_config"], config_path, e)) - else: + elif not os.path.isfile(os.path.join(iou_instance.working_dir, request["startup_config"])): raise IOUError("Startup-config {} could not be found on this server".format(request["startup_config"])) except IOUError as e: self.send_custom_error(str(e)) From cef8a3f116758ab9ebda4aa83d51a2bd9e1a3f18 Mon Sep 17 00:00:00 2001 From: Joe Bowen Date: Fri, 16 May 2014 11:42:43 -0600 Subject: [PATCH 08/46] Added base64 transmission of script_file --- gns3server/modules/vpcs/__init__.py | 39 +++++++++++++++++++++++++- gns3server/modules/vpcs/schemas.py | 10 ++++++- gns3server/modules/vpcs/vpcs_device.py | 2 +- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 37c7e5837..dd8d28274 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -288,7 +288,23 @@ class VPCS(IModule): except OSError as e: raise VPCSError("Could not create working directory {}".format(e)) - vpcs_instance = VPCSDevice(vpcs_path, base_script_file, self._working_dir, host=self._host, name=name) + # a new base-script-file has been pushed + if "base_script_file_base64" in request: + config = base64.decodestring(request["base_script_file_base64"].encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + #config = config.replace('%h', vpcs_instance.name) + config_path = os.path.join(self._working_dir, "base-script-file") + try: + with open(config_path, "w") as f: + log.info("saving base-script-file to {}".format(config_path)) + f.write(config) + except OSError as e: + raise VPCSError("Could not save the configuration {}: {}".format(config_path, e)) + # update the request with the new local base-script-file path + request["base_script_file"] = os.path.basename(config_path) + + vpcs_instance = VPCSDevice(vpcs_path, config_path, self._working_dir, host=self._host, name=name) + # find a console port if self._current_console_port > self._console_end_port_range: self._current_console_port = self._console_start_port_range @@ -351,6 +367,7 @@ class VPCS(IModule): Optional request parameters: - any setting to update + - base_script_file_base64 (script-file base64 encoded) Response parameters: - updated settings @@ -368,6 +385,26 @@ class VPCS(IModule): return response = {} + try: + # a new base-script-file has been pushed + if "base_script_file_base64" in request: + config = base64.decodestring(request["base_script_file_base64"].encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + config = config.replace('%h', vpcs_instance.name) + config_path = os.path.join(vpcs_instance.working_dir, "base-script-file") + try: + with open(config_path, "w") as f: + log.info("saving base-script-file to {}".format(config_path)) + f.write(config) + except OSError as e: + raise VPCSError("Could not save the configuration {}: {}".format(config_path, e)) + # update the request with the new local base-script-file path + request["base_script_file"] = os.path.basename(config_path) + + except VPCSError as e: + self.send_custom_error(str(e)) + return + # update the VPCS settings for name, value in request.items(): if hasattr(vpcs_instance, name) and getattr(vpcs_instance, name) != value: diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index fbfd13f2a..2675b13b9 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -36,6 +36,10 @@ VPCS_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "base_script_file_base64": { + "description": "startup script file base64 encoded", + "type": "string" + }, }, "required": ["path"] } @@ -73,10 +77,14 @@ VPCS_UPDATE_SCHEMA = { "minLength": 1, }, "base_script_file": { - "description": "path to the VPCS startup configuration file", + "description": "path to the VPCS startup script file file", "type": "string", "minLength": 1, }, + "base_script_file_base64": { + "description": "startup script file base64 encoded", + "type": "string" + }, }, "required": ["id"] } diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 94282fde7..155445b69 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -48,7 +48,7 @@ class VPCSDevice(object): def __init__(self, path, base_script_file, working_dir, host="127.0.0.1", name=None): # find an instance identifier (1 <= id <= 512) - # This 255 limit is due to a restriction on the number of possible + # This 512 limit is due to a restriction on the number of possible # mac addresses given in VPCS using the -m option self._id = 0 for identifier in range(1, 513): From 6a839c4b7ba3ab10dd3ac87f7577f1c4702ae0d9 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 16 May 2014 12:35:48 -0600 Subject: [PATCH 09/46] Wait more time for ioucon thread to be completed. Prevent IOS to be started or stopped if the status isn't right. --- gns3server/modules/attic.py | 38 +++++++++++++++++++ .../modules/dynamips/hypervisor_manager.py | 23 ++--------- gns3server/modules/dynamips/nodes/router.py | 10 +++-- gns3server/modules/iou/iou_device.py | 30 +++++++-------- gns3server/modules/iou/ioucon.py | 5 ++- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/gns3server/modules/attic.py b/gns3server/modules/attic.py index b928eb3dc..af87e7714 100644 --- a/gns3server/modules/attic.py +++ b/gns3server/modules/attic.py @@ -21,6 +21,7 @@ Useful functions... in the attic ;) import socket import errno +import time def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP", ignore_ports=[]): @@ -64,3 +65,40 @@ def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP", raise Exception("Could not find an unused port: {}".format(e)) raise Exception("Could not find a free port between {0} and {1}".format(start_port, end_port)) + + +def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10): + """ + Waits for a socket to be ready for wait time. + + :param host: host/address to connect to + :param port: port to connect to + :param wait: maximum wait time + :param socket_timeout: timeout for the socket + + :returns: tuple with boolean indicating if the socket is ready and the last exception + that occurred when connecting to the socket + """ + + # connect to a local address by default + # if listening to all addresses (IPv4 or IPv6) + if host == "0.0.0.0": + host = "127.0.0.1" + elif host == "::": + host = "::1" + + connection_success = False + begin = time.time() + last_exception = None + while (time.time() - begin < wait): + time.sleep(0.01) + try: + with socket.create_connection((host, port), socket_timeout): + pass + except OSError as e: + last_exception = e + continue + connection_success = True + break + + return (connection_success, last_exception) diff --git a/gns3server/modules/dynamips/hypervisor_manager.py b/gns3server/modules/dynamips/hypervisor_manager.py index 1db9f285b..4a65b2f47 100644 --- a/gns3server/modules/dynamips/hypervisor_manager.py +++ b/gns3server/modules/dynamips/hypervisor_manager.py @@ -22,10 +22,10 @@ Manages Dynamips hypervisors (load-balancing etc.) from .hypervisor import Hypervisor from .dynamips_error import DynamipsError from ..attic import find_unused_port +from ..attic import wait_socket_is_ready from pkg_resources import parse_version import os -import socket import time import logging @@ -513,26 +513,9 @@ class HypervisorManager(object): :param timeout: timeout value (default is 10 seconds) """ - # connect to a local address by default - # if listening to all addresses (IPv4 or IPv6) - if host == "0.0.0.0": - host = "127.0.0.1" - elif host == "::": - host = "::1" - - connection_success = False begin = time.time() - # try to connect for 10 seconds - while(time.time() - begin < 10.0): - time.sleep(0.01) - try: - with socket.create_connection((host, port), timeout): - pass - except OSError as e: - last_exception = e - continue - connection_success = True - break + # wait for the socket for a maximum of 10 seconds. + connection_success, last_exception = wait_socket_is_ready(host, port, wait=10.0) if not connection_success: # FIXME: throw exception here diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 0534d926b..e2ce440d1 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -313,9 +313,10 @@ class Router(object): At least the IOS image must be set before starting it. """ - if self.get_status() == "suspended": + status = self.get_status() + if status == "suspended": self.resume() - else: + elif status == "inactive": if not os.path.isfile(self._image): raise DynamipsError("IOS image '{}' is not accessible".format(self._image)) @@ -340,8 +341,9 @@ class Router(object): The settings are kept. """ - self._hypervisor.send("vm stop {}".format(self._name)) - log.info("router {name} [id={id}] has been stopped".format(name=self._name, id=self._id)) + if self.get_status() != "inactive": + self._hypervisor.send("vm stop {}".format(self._name)) + log.info("router {name} [id={id}] has been stopped".format(name=self._name, id=self._id)) def suspend(self): """ diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 5f282d150..66e33f3d5 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -523,25 +523,11 @@ class IOUDevice(object): Stops the IOU process. """ - # stop the IOU process - if self.is_running(): - log.info("stopping IOU instance {} PID={}".format(self._id, self._process.pid)) - try: - self._process.terminate() - self._process.wait(1) - except subprocess.TimeoutExpired: - self._process.kill() - if self._process.poll() == None: - log.warn("IOU instance {} PID={} is still running".format(self._id, - self._process.pid)) - self._process = None - self._started = False - # stop console support if self._ioucon_thead: self._ioucon_thread_stop_event.set() if self._ioucon_thead.is_alive(): - self._ioucon_thead.join(timeout=0.10) + self._ioucon_thead.join(timeout=3.0) # wait for the thread to free the console port self._ioucon_thead = None # stop iouyap @@ -557,6 +543,20 @@ class IOUDevice(object): self._id)) self._iouyap_process = None + # stop the IOU process + if self.is_running(): + log.info("stopping IOU instance {} PID={}".format(self._id, self._process.pid)) + try: + self._process.terminate() + self._process.wait(1) + except subprocess.TimeoutExpired: + self._process.kill() + if self._process.poll() == None: + log.warn("IOU instance {} PID={} is still running".format(self._id, + self._process.pid)) + self._process = None + self._started = False + def read_iou_stdout(self): """ Reads the standard output of the IOU process. diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index 77ed0fd90..a30c66e9c 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -139,7 +139,10 @@ class FileLock: def unlock(self): if self.fd: # Deleting first prevents a race condition - os.unlink(self.fd.name) + try: + os.unlink(self.fd.name) + except FileNotFoundError as e: + log.debug("{}".format(e)) self.fd.close() def __enter__(self): From f4ab8e2dd0db5acd163906d9068da1c5fa17f641 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 May 2014 18:07:16 -0600 Subject: [PATCH 10/46] UDP connection checks. --- gns3server/modules/dynamips/__init__.py | 7 +++++++ gns3server/modules/iou/__init__.py | 6 ++++++ gns3server/modules/vpcs/__init__.py | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 0b4bd4d78..741928816 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -25,6 +25,7 @@ import base64 import tempfile import shutil import glob +import socket from gns3server.modules import IModule import gns3server.jsonrpc as jsonrpc @@ -363,6 +364,12 @@ class Dynamips(IModule): lport = request["nio"]["lport"] rhost = request["nio"]["rhost"] rport = request["nio"]["rport"] + try: + #TODO: handle IPv6 + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.connect((rhost, rport)) + except OSError as e: + raise DynamipsError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) # check if we have an allocated NIO UDP auto nio = node.hypervisor.get_nio_udp_auto(lport) if not nio: diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 3d6da02c6..4834c145c 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -658,6 +658,12 @@ class IOU(IModule): lport = request["nio"]["lport"] rhost = request["nio"]["rhost"] rport = request["nio"]["rport"] + try: + #TODO: handle IPv6 + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.connect((rhost, rport)) + except OSError as e: + raise IOUError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) nio = NIO_UDP(lport, rhost, rport) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index dd8d28274..423b614b9 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -625,6 +625,12 @@ class VPCS(IModule): lport = request["nio"]["lport"] rhost = request["nio"]["rhost"] rport = request["nio"]["rport"] + try: + #TODO: handle IPv6 + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.connect((rhost, rport)) + except OSError as e: + raise VPCSError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e)) nio = NIO_UDP(lport, rhost, rport) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] From 85ef421d728faaec177e0aca7f926948d192418e Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 May 2014 18:39:37 -0600 Subject: [PATCH 11/46] Catch exceptions in file upload handler. --- gns3server/handlers/file_upload_handler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gns3server/handlers/file_upload_handler.py b/gns3server/handlers/file_upload_handler.py index 41ac56825..d73f12d69 100644 --- a/gns3server/handlers/file_upload_handler.py +++ b/gns3server/handlers/file_upload_handler.py @@ -81,8 +81,12 @@ class FileUploadHandler(tornado.web.RequestHandler): if "file" in self.request.files: fileinfo = self.request.files["file"][0] destination_path = os.path.join(self._upload_dir, fileinfo['filename']) - with open(destination_path, 'wb') as f: - f.write(fileinfo['body']) + try: + with open(destination_path, 'wb') as f: + f.write(fileinfo['body']) + except OSError as e: + self.write("Could not upload {}: {}".format(fileinfo['filename'], e)) + return st = os.stat(destination_path) os.chmod(destination_path, st.st_mode | stat.S_IXUSR) self.redirect("/upload") From 0af4ea81ff7bfdb5c4983f75f30fa9fb66a8fa96 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 18 May 2014 19:12:46 -0600 Subject: [PATCH 12/46] Working VPCS implementation. --- gns3server/modules/attic.py | 34 ++++ gns3server/modules/iou/__init__.py | 37 +---- gns3server/modules/vpcs/__init__.py | 216 ++++++++---------------- gns3server/modules/vpcs/schemas.py | 65 ++++---- gns3server/modules/vpcs/vpcs_device.py | 221 +++++++++++++------------ gns3server/version.py | 2 +- 6 files changed, 260 insertions(+), 315 deletions(-) diff --git a/gns3server/modules/attic.py b/gns3server/modules/attic.py index af87e7714..d0ebf1ed4 100644 --- a/gns3server/modules/attic.py +++ b/gns3server/modules/attic.py @@ -19,10 +19,16 @@ Useful functions... in the attic ;) """ +import sys +import os +import struct import socket import errno import time +import logging +log = logging.getLogger(__name__) + def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP", ignore_ports=[]): """ @@ -102,3 +108,31 @@ def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10): break return (connection_success, last_exception) + + +def has_privileged_access(executable, device): + """ + Check if an executable can access Ethernet and TAP devices in + RAW mode. + + :param executable: executable path + :param device: device name + + :returns: True or False + """ + + # we are root, so we should have privileged access too + if os.geteuid() == 0: + return True + + # test if the executable has the CAP_NET_RAW capability (Linux only) + if sys.platform.startswith("linux") and "security.capability" in os.listxattr(executable): + try: + caps = os.getxattr(executable, "security.capability") + # test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set + if struct.unpack(" self._console_end_port_range: - self._current_console_port = self._console_start_port_range - try: - vpcs_instance.console = find_unused_port(self._current_console_port, self._console_end_port_range, self._host) - except Exception as e: - raise VPCSError(e) - self._current_console_port += 1 + vpcs_instance = VPCSDevice(self._vpcs, + self._working_dir, + self._host, + name, + self._console_start_port_range, + self._console_end_port_range) + except VPCSError as e: self.send_custom_error(str(e)) return @@ -367,7 +302,7 @@ class VPCS(IModule): Optional request parameters: - any setting to update - - base_script_file_base64 (script-file base64 encoded) + - script_file_base64 (base64 encoded) Response parameters: - updated settings @@ -384,28 +319,42 @@ class VPCS(IModule): if not vpcs_instance: return - response = {} + config_path = os.path.join(vpcs_instance.working_dir, "startup.vpc") try: - # a new base-script-file has been pushed - if "base_script_file_base64" in request: - config = base64.decodestring(request["base_script_file_base64"].encode("utf-8")).decode("utf-8") - config = "!\n" + config.replace("\r", "") + if "script_file_base64" in request: + # a new startup-config has been pushed + config = base64.decodestring(request["script_file_base64"].encode("utf-8")).decode("utf-8") + config = config.replace("\r", "") config = config.replace('%h', vpcs_instance.name) - config_path = os.path.join(vpcs_instance.working_dir, "base-script-file") try: with open(config_path, "w") as f: - log.info("saving base-script-file to {}".format(config_path)) + log.info("saving script file to {}".format(config_path)) f.write(config) except OSError as e: raise VPCSError("Could not save the configuration {}: {}".format(config_path, e)) - # update the request with the new local base-script-file path - request["base_script_file"] = os.path.basename(config_path) - + # update the request with the new local startup-config path + request["script_file"] = os.path.basename(config_path) + elif "script_file" in request: + if os.path.isfile(request["script_file"]) and request["script_file"] != config_path: + # this is a local file set in the GUI + try: + with open(request["script_file"], "r") as f: + config = f.read() + with open(config_path, "w") as f: + config = config.replace("\r", "") + config = config.replace('%h', vpcs_instance.name) + f.write(config) + request["script_file"] = os.path.basename(config_path) + except OSError as e: + raise VPCSError("Could not save the configuration from {} to {}: {}".format(request["script_file"], config_path, e)) + elif not os.path.isfile(config_path): + raise VPCSError("Startup-config {} could not be found on this server".format(config_path)) except VPCSError as e: self.send_custom_error(str(e)) return - + # update the VPCS settings + response = {} for name, value in request.items(): if hasattr(vpcs_instance, name) and getattr(vpcs_instance, name) != value: try: @@ -442,7 +391,6 @@ class VPCS(IModule): try: log.debug("starting VPCS with command: {}".format(vpcs_instance.command())) - vpcs_instance.vpcs = self._vpcs vpcs_instance.start() except VPCSError as e: self.send_custom_error(str(e)) @@ -537,53 +485,24 @@ class VPCS(IModule): return try: - - # find a UDP port - if self._current_udp_port >= self._udp_end_port_range: - self._current_udp_port = self._udp_start_port_range - try: - port = find_unused_port(self._current_udp_port, self._udp_end_port_range, host=self._host, socket_type="UDP") - except Exception as e: - raise VPCSError(e) - self._current_udp_port += 1 - - log.info("{} [id={}] has allocated UDP port {} with host {}".format(vpcs_instance.name, - vpcs_instance.id, - port, - self._host)) - response = {"lport": port} - - except VPCSError as e: + port = find_unused_port(self._udp_start_port_range, + self._udp_end_port_range, + host=self._host, + socket_type="UDP", + ignore_ports=self._allocated_udp_ports) + except Exception as e: self.send_custom_error(str(e)) - return + self._allocated_udp_ports.append(port) + log.info("{} [id={}] has allocated UDP port {} with host {}".format(vpcs_instance.name, + vpcs_instance.id, + port, + self._host)) + + response = {"lport": port} response["port_id"] = request["port_id"] self.send_response(response) - def _check_for_privileged_access(self, device): - """ - Check if VPCS can access Ethernet and TAP devices. - - :param device: device name - """ - - # we are root, so vpcs should have privileged access too - if os.geteuid() == 0: - return - - # test if VPCS has the CAP_NET_RAW capability - if "security.capability" in os.listxattr(self._vpcs): - try: - caps = os.getxattr(self._vpcs, "security.capability") - # test the 2nd byte and check if the 13th bit (CAP_NET_RAW) is set - if struct.unpack(" Date: Sun, 18 May 2014 19:20:05 -0600 Subject: [PATCH 13/46] Fix issue with VPCS create request. --- gns3server/modules/vpcs/schemas.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 52a6bef35..229e2accc 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -27,7 +27,6 @@ VPCS_CREATE_SCHEMA = { "minLength": 1, }, }, - "required": ["path"] } VPCS_DELETE_SCHEMA = { From 6c0918312cd07e4fb964850aec0a0e8b03689709 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 18 May 2014 22:29:41 -0600 Subject: [PATCH 14/46] Use SIGTERM instead of SIGUSR1 to stop vpcs. --- gns3server/modules/vpcs/vpcs_device.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index ea16f8b78..ad9eafbfa 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -251,7 +251,6 @@ class VPCSDevice(object): :returns: VPCS command line (string) """ - print(self._build_command()) return " ".join(self._build_command()) def delete(self): @@ -319,7 +318,7 @@ class VPCSDevice(object): # stop the VPCS process if self.is_running(): log.info("stopping VPCS instance {} PID={}".format(self._id, self._process.pid)) - self._process.send_signal(signal.SIGUSR1) # send SIGUSR1 will stop VPCS + self._process.send_signal(signal.SIGTERM) # send SIGTERM will stop VPCS self._process.wait() self._process = None From 7182e59892966e6b366e29ce8df5f3ac3d0632c9 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 May 2014 12:05:30 -0600 Subject: [PATCH 15/46] Clean delete for IOU & VPCS devices. --- gns3server/modules/iou/__init__.py | 2 +- gns3server/modules/iou/iou_device.py | 24 ++++++++++++++++++++++++ gns3server/modules/vpcs/__init__.py | 2 +- gns3server/modules/vpcs/vpcs_device.py | 24 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 37da5200d..b945c4ba9 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -373,7 +373,7 @@ class IOU(IModule): return try: - iou_instance.delete() + iou_instance.clean_delete() del self._iou_instances[request["id"]] except IOUError as e: self.send_custom_error(str(e)) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 66e33f3d5..bb1265356 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -27,6 +27,8 @@ import subprocess import argparse import threading import configparser +import shutil + from .ioucon import start_ioucon from .iou_error import IOUError from .adapters.ethernet_adapter import EthernetAdapter @@ -332,6 +334,28 @@ class IOUDevice(object): log.info("IOU device {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) + def clean_delete(self): + """ + Deletes this IOU device & all files (nvram, startup-config etc.) + """ + + self.stop() + self._instances.remove(self._id) + + if self.console: + self._allocated_console_ports.remove(self.console) + + try: + shutil.rmtree(self._working_dir) + except OSError as e: + log.error("could not delete IOU device {name} [id={id}]: {error}".format(name=self._name, + id=self._id, + error=e)) + return + + log.info("IOU device {name} [id={id}] has been deleted (including associated files)".format(name=self._name, + id=self._id)) + @property def started(self): """ diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 22487821b..294fe2ec8 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -284,7 +284,7 @@ class VPCS(IModule): return try: - vpcs_instance.delete() + vpcs_instance.clean_delete() del self._vpcs_instances[request["id"]] except VPCSError as e: self.send_custom_error(str(e)) diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index ad9eafbfa..f95cb9d02 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -23,6 +23,8 @@ order to run an VPCS instance. import os import subprocess import signal +import shutil + from .vpcs_error import VPCSError from .adapters.ethernet_adapter import EthernetAdapter from .nios.nio_udp import NIO_UDP @@ -267,6 +269,28 @@ class VPCSDevice(object): log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) + def clean_delete(self): + """ + Deletes this VPCS device & all files (configs, logs etc.) + """ + + self.stop() + self._instances.remove(self._id) + + if self.console: + self._allocated_console_ports.remove(self.console) + + try: + shutil.rmtree(self._working_dir) + except OSError as e: + log.error("could not delete VPCS device {name} [id={id}]: {error}".format(name=self._name, + id=self._id, + error=e)) + return + + log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name, + id=self._id)) + @property def started(self): """ From babdfd5086461312cfebc960775a9bf9812806b1 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 May 2014 13:14:57 -0600 Subject: [PATCH 16/46] Amend device configs when renaming. --- gns3server/modules/dynamips/nodes/router.py | 44 +++++++++++++++++++++ gns3server/modules/iou/__init__.py | 2 +- gns3server/modules/iou/iou_device.py | 15 ++++++- gns3server/modules/vpcs/__init__.py | 2 +- gns3server/modules/vpcs/vpcs_device.py | 19 +++++++-- 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index e2ce440d1..554cd7297 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -225,6 +225,38 @@ class Router(object): if new_name in self._allocated_names: raise DynamipsError('Name "{}" is already used by another router'.format(new_name)) + if self._startup_config: + # change the hostname in the startup-config + startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) + if os.path.isfile(startup_config_path): + try: + with open(startup_config_path, "r+") as f: + old_config = f.read() + new_config = old_config.replace(self.name, new_name) + f.seek(0) + f.write(new_config) + new_startup_config_path = os.path.join(os.path.dirname(startup_config_path), "{}.cfg".format(new_name)) + os.rename(startup_config_path, new_startup_config_path) + except OSError as e: + raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e)) + self.set_config(new_startup_config_path) + + if self._private_config: + # change the hostname in the startup-config + private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name)) + if os.path.isfile(private_config_path): + try: + with open(private_config_path, "r+") as f: + old_config = f.read() + new_config = old_config.replace(self.name, new_name) + f.seek(0) + f.write(new_config) + new_private_config_path = os.path.join(os.path.dirname(private_config_path), "{}-private.cfg".format(new_name)) + os.rename(private_config_path, new_private_config_path) + except OSError as e: + raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e)) + self.set_config(self.startup_config, new_private_config_path) + new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("vm rename {name} {new_name}".format(name=self._name, @@ -300,6 +332,18 @@ class Router(object): self._hypervisor.send("vm clean_delete {}".format(self._name)) self._hypervisor.devices.remove(self) + if self._startup_config: + # delete the startup-config + startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) + if os.path.isfile(startup_config_path): + os.remove(startup_config_path) + + if self._private_config: + # delete the private-config + private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name)) + if os.path.isfile(private_config_path): + os.remove(private_config_path) + log.info("router {name} [id={id}] has been deleted (including associated files)".format(name=self._name, id=self._id)) self._allocated_names.remove(self.name) if self.console: diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index b945c4ba9..4a3a295ad 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -437,7 +437,7 @@ class IOU(IModule): except OSError as e: raise IOUError("Could not save the configuration from {} to {}: {}".format(request["startup_config"], config_path, e)) elif not os.path.isfile(config_path): - raise IOUError("Startup-config {} could not be found on this server".format(config_path)) + raise IOUError("Startup-config {} could not be found on this server".format(request["startup_config"])) except IOUError as e: self.send_custom_error(str(e)) return diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index bb1265356..fc7ec837d 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -177,10 +177,23 @@ class IOUDevice(object): :param new_name: name """ - self._name = new_name + if self._startup_config: + # update the startup-config + config_path = os.path.join(self.working_dir, "startup-config") + if os.path.isfile(config_path): + try: + with open(config_path, "r+") as f: + old_config = f.read() + new_config = old_config.replace(self._name, new_name) + f.seek(0) + f.write(new_config) + except OSError as e: + raise IOUError("Could not amend the configuration {}: {}".format(config_path, e)) + log.info("IOU {name} [id={id}]: renamed to {new_name}".format(name=self._name, id=self._id, new_name=new_name)) + self._name = new_name @property def path(self): diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 294fe2ec8..585f9abd3 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -348,7 +348,7 @@ class VPCS(IModule): except OSError as e: raise VPCSError("Could not save the configuration from {} to {}: {}".format(request["script_file"], config_path, e)) elif not os.path.isfile(config_path): - raise VPCSError("Startup-config {} could not be found on this server".format(config_path)) + raise VPCSError("Startup-config {} could not be found on this server".format(request["script_file"])) except VPCSError as e: self.send_custom_error(str(e)) return diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index f95cb9d02..5e3263bbf 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -158,10 +158,23 @@ class VPCSDevice(object): :param new_name: name """ - self._name = new_name + if self._script_file: + # update the startup.vpc + config_path = os.path.join(self.working_dir, "startup.vpc") + if os.path.isfile(config_path): + try: + with open(config_path, "r+") as f: + old_config = f.read() + new_config = old_config.replace(self._name, new_name) + f.seek(0) + f.write(new_config) + except OSError as e: + raise VPCSError("Could not amend the configuration {}: {}".format(config_path, e)) + log.info("VPCS {name} [id={id}]: renamed to {new_name}".format(name=self._name, id=self._id, new_name=new_name)) + self._name = new_name @property def path(self): @@ -288,8 +301,8 @@ class VPCSDevice(object): error=e)) return - log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name, - id=self._id)) + log.info("VPCS device {name} [id={id}] has been deleted (including associated files)".format(name=self._name, + id=self._id)) @property def started(self): From e41afbb5c64ef9b1ae75c2efa6c72b573c8a4cb7 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 May 2014 15:50:27 -0600 Subject: [PATCH 17/46] IOU layer 1 keepalive messages support. --- gns3server/modules/iou/__init__.py | 1 - gns3server/modules/iou/iou_device.py | 50 +++++++++++++++++++++++++++- gns3server/modules/iou/schemas.py | 4 +++ gns3server/modules/vpcs/__init__.py | 1 - 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 4a3a295ad..5ae65f98d 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -479,7 +479,6 @@ class IOU(IModule): return try: - log.debug("starting IOU with command: {}".format(iou_instance.command())) iou_instance.iouyap = self._iouyap iou_instance.iourc = self._iourc iou_instance.start() diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index fc7ec837d..90e00e1aa 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -104,6 +104,7 @@ class IOUDevice(object): self._nvram = 128 # Kilobytes self._startup_config = "" self._ram = 256 # Megabytes + self._l1_keepalives = True # update the working directory self.working_dir = working_dir @@ -136,7 +137,8 @@ class IOUDevice(object): "nvram": self._nvram, "ethernet_adapters": len(self._ethernet_adapters), "serial_adapters": len(self._serial_adapters), - "console": self._console} + "console": self._console, + "l1_keepalives": self._l1_keepalives} return iou_defaults @@ -708,6 +710,26 @@ class IOUDevice(object): return nio + def _enable_l1_keepalives(self, command): + """ + Enables L1 keepalive messages if supported. + + :param command: command line + """ + + env = os.environ.copy() + env["IOURC"] = self._iourc + output = b"" + try: + output = subprocess.check_output([self._path, "-h"], stderr=subprocess.STDOUT, cwd=self._working_dir, env=env) + except OSError as e: + log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e)) + else: + if re.search("-l\s+Enable Layer 1 keepalive messages", output.decode("utf-8")): + command.extend(["-l"]) + else: + log.warn("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) + def _build_command(self): """ Command to start the IOU process. @@ -750,6 +772,8 @@ class IOUDevice(object): command.extend(["-L"]) # disable local console, use remote console if self._startup_config: command.extend(["-c", self._startup_config]) + if self._l1_keepalives: + self._enable_l1_keepalives(command) command.extend([str(self._id)]) return command @@ -777,6 +801,30 @@ class IOUDevice(object): else: log.info("IOU {name} [id={id}]: does not use the default IOU image values".format(name=self._name, id=self._id)) + @property + def l1_keepalives(self): + """ + Returns either layer 1 keepalive messages option is enabled or disabled. + + :returns: boolean + """ + + return self._l1_keepalives + + @l1_keepalives.setter + def l1_keepalives(self, state): + """ + Enables or disables layer 1 keepalive messages. + + :param state: boolean + """ + + self._l1_keepalives = state + if state: + log.info("IOU {name} [id={id}]: has activated layer 1 keepalive messages".format(name=self._name, id=self._id)) + else: + log.info("IOU {name} [id={id}]: has deactivated layer 1 keepalive messages".format(name=self._name, id=self._id)) + @property def ram(self): """ diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 5d37ffedf..ad232a4cf 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -102,6 +102,10 @@ IOU_UPDATE_SCHEMA = { "description": "use the default IOU RAM & NVRAM values", "type": "boolean" }, + "l1_keepalives": { + "description": "enable or disable layer 1 keepalive messages", + "type": "boolean" + }, "startup_config_base64": { "description": "startup configuration base64 encoded", "type": "string" diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 585f9abd3..3ef9347c4 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -390,7 +390,6 @@ class VPCS(IModule): return try: - log.debug("starting VPCS with command: {}".format(vpcs_instance.command())) vpcs_instance.start() except VPCSError as e: self.send_custom_error(str(e)) From 119eb635cf0fe646c9198cab38b6b1454ba2a649 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 May 2014 18:52:59 -0600 Subject: [PATCH 18/46] Changes how to look for vpcs and iouyap locations. --- gns3server/modules/iou/__init__.py | 21 +++++++++------------ gns3server/modules/vpcs/__init__.py | 24 ++++++++++++------------ gns3server/modules/vpcs/vpcs_device.py | 3 +++ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 5ae65f98d..e7d2c0c64 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -69,18 +69,15 @@ class IOU(IModule): iou_config = config.get_section_config(name.upper()) self._iouyap = iou_config.get("iouyap") if not self._iouyap or not os.path.isfile(self._iouyap): - iouyap_in_cwd = os.path.join(os.getcwd(), "iouyap") - if os.path.isfile(iouyap_in_cwd): - self._iouyap = iouyap_in_cwd - else: - # look for iouyap if none is defined or accessible - for path in os.environ["PATH"].split(":"): - try: - if "iouyap" in os.listdir(path) and os.access(os.path.join(path, "iouyap"), os.X_OK): - self._iouyap = os.path.join(path, "iouyap") - break - except OSError: - continue + paths = [os.getcwd()] + os.environ["PATH"].split(":") + # look for iouyap in the current working directory and $PATH + for path in paths: + try: + if "iouyap" in os.listdir(path) and os.access(os.path.join(path, "iouyap"), os.X_OK): + self._iouyap = os.path.join(path, "iouyap") + break + except OSError: + continue if not self._iouyap: log.warning("iouyap binary couldn't be found!") diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 3ef9347c4..5c31310ac 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -66,18 +66,15 @@ class VPCS(IModule): vpcs_config = config.get_section_config(name.upper()) self._vpcs = vpcs_config.get("vpcs") if not self._vpcs or not os.path.isfile(self._vpcs): - vpcs_in_cwd = os.path.join(os.getcwd(), "vpcs") - if os.path.isfile(vpcs_in_cwd): - self._vpcs = vpcs_in_cwd - else: - # look for vpcs if none is defined or accessible - for path in os.environ["PATH"].split(":"): - try: - if "vpcs" in os.listdir(path) and os.access(os.path.join(path, "vpcs"), os.X_OK): - self._vpcs = os.path.join(path, "vpcs") - break - except OSError: - continue + paths = [os.getcwd()] + os.environ["PATH"].split(":") + # look for VPCS in the current working directory and $PATH + for path in paths: + try: + if "vpcs" in os.listdir(path) and os.access(os.path.join(path, "vpcs"), os.X_OK): + self._vpcs = os.path.join(path, "vpcs") + break + except OSError: + continue if not self._vpcs: log.warning("VPCS binary couldn't be found!") @@ -241,6 +238,9 @@ class VPCS(IModule): except OSError as e: raise VPCSError("Could not create working directory {}".format(e)) + if not self._vpcs: + raise VPCSError("No path to a VPCS executable has been set") + vpcs_instance = VPCSDevice(self._vpcs, self._working_dir, self._host, diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 5e3263bbf..23bd69705 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -321,6 +321,9 @@ class VPCSDevice(object): if not self.is_running(): + if not self._path: + raise VPCSError("No path to a VPCS executable has been set") + if not os.path.isfile(self._path): raise VPCSError("VPCS '{}' is not accessible".format(self._path)) From 08cb3de6837c11074dfdc11a8cba74410be48fe5 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 May 2014 22:21:15 -0600 Subject: [PATCH 19/46] Fix a potential issue in ioucon. --- gns3server/modules/iou/ioucon.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index a30c66e9c..9fda17176 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -411,8 +411,11 @@ class IOU(Router): log.debug("Waiting to connect to {}".format(self.ttyS)) time.sleep(RETRY_DELAY) except Exception as e: - raise NetioError("Couldn't connect to socket {}: {}" - .format(self.ttyS, e)) + if e.errno == 111: # connection refused + log.debug("Waiting to connect to {}".format(self.ttyS)) + time.sleep(RETRY_DELAY) + else: + raise NetioError("Couldn't connect to socket {}: {}".format(self.ttyS, e)) else: break From 77b845a17f2dbfc54858820ed8d17695a545c66a Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 May 2014 10:28:59 -0600 Subject: [PATCH 20/46] Revert "Fix a potential issue in ioucon." This reverts commit 08cb3de6837c11074dfdc11a8cba74410be48fe5. --- gns3server/modules/iou/ioucon.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index 9fda17176..a30c66e9c 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -411,11 +411,8 @@ class IOU(Router): log.debug("Waiting to connect to {}".format(self.ttyS)) time.sleep(RETRY_DELAY) except Exception as e: - if e.errno == 111: # connection refused - log.debug("Waiting to connect to {}".format(self.ttyS)) - time.sleep(RETRY_DELAY) - else: - raise NetioError("Couldn't connect to socket {}: {}".format(self.ttyS, e)) + raise NetioError("Couldn't connect to socket {}: {}" + .format(self.ttyS, e)) else: break From 566c48ffedf6be38ef47fe0ae93e010ce71b9599 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 May 2014 10:37:11 -0600 Subject: [PATCH 21/46] Send error if L1 keepalive messages are not supported. --- gns3server/modules/iou/iou_device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 90e00e1aa..527e7d14d 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -104,7 +104,7 @@ class IOUDevice(object): self._nvram = 128 # Kilobytes self._startup_config = "" self._ram = 256 # Megabytes - self._l1_keepalives = True + self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). # update the working directory self.working_dir = working_dir @@ -728,7 +728,7 @@ class IOUDevice(object): if re.search("-l\s+Enable Layer 1 keepalive messages", output.decode("utf-8")): command.extend(["-l"]) else: - log.warn("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) + raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) def _build_command(self): """ From b42d751e89f0b47323913a9170c87341f711467f Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 May 2014 17:21:45 -0600 Subject: [PATCH 22/46] Fix console port restoration for IOU and VPCS (when loading a project). --- gns3server/main.py | 4 ++-- gns3server/modules/iou/__init__.py | 7 ++++--- gns3server/modules/iou/iou_device.py | 26 ++++++++++++++++---------- gns3server/modules/iou/ioucon.py | 14 +++++--------- gns3server/modules/iou/schemas.py | 8 +++++++- gns3server/modules/vpcs/__init__.py | 9 ++++++--- gns3server/modules/vpcs/schemas.py | 6 ++++++ gns3server/modules/vpcs/vpcs_device.py | 25 +++++++++++++++---------- 8 files changed, 61 insertions(+), 38 deletions(-) diff --git a/gns3server/main.py b/gns3server/main.py index 71514fcee..f51730fff 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -48,8 +48,8 @@ def locale_check(): or there: http://robjwells.com/post/61198832297/get-your-us-ascii-out-of-my-face """ - # no need to check on Windows - if sys.platform.startswith("win"): + # no need to check on Windows or when frozen + if sys.platform.startswith("win") or hasattr(sys, "frozen"): return language = encoding = None diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index e7d2c0c64..9b71e2bb4 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -301,6 +301,7 @@ class IOU(IModule): Optional request parameters: - name (IOU name) + - console (IOU console port) Response parameters: - id (IOU instance identifier) @@ -314,9 +315,8 @@ class IOU(IModule): if not self.validate_request(request, IOU_CREATE_SCHEMA): return - name = None - if "name" in request: - name = request["name"] + name = request.get("name") + console = request.get("console") iou_path = request["path"] try: @@ -331,6 +331,7 @@ class IOU(IModule): self._working_dir, self._host, name, + console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 527e7d14d..3598e4ad6 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -50,6 +50,7 @@ class IOUDevice(object): :param working_dir: path to a working directory :param host: host/address to bind for console and UDP connections :param name: name of this IOU device + :param console: TCP console port :param console_start_port_range: TCP console port range start :param console_end_port_range: TCP console port range end """ @@ -61,6 +62,7 @@ class IOUDevice(object): working_dir, host="127.0.0.1", name=None, + console=None, console_start_port_range=4001, console_end_port_range=4512): @@ -82,7 +84,7 @@ class IOUDevice(object): self._path = path self._iourc = "" self._iouyap = "" - self._console = None + self._console = console self._working_dir = None self._command = [] self._process = None @@ -109,16 +111,20 @@ class IOUDevice(object): # update the working directory self.working_dir = working_dir - # allocate a console port - try: - self._console = find_unused_port(self._console_start_port_range, - self._console_end_port_range, - self._host, - ignore_ports=self._allocated_console_ports) - except Exception as e: - raise IOUError(e) + if not self._console: + # allocate a console port + try: + self._console = find_unused_port(self._console_start_port_range, + self._console_end_port_range, + self._host, + ignore_ports=self._allocated_console_ports) + except Exception as e: + raise IOUError(e) + if self._console in self._allocated_console_ports: + raise IOUError("Console port {} is already in used another IOU device".format(console)) self._allocated_console_ports.append(self._console) + log.info("IOU device {name} [id={id}] has been created".format(name=self._name, id=self._id)) @@ -317,7 +323,7 @@ class IOUDevice(object): """ if console in self._allocated_console_ports: - raise IOUError("Console port {} is already in used by another IOU device".format(console)) + raise IOUError("Console port {} is already used by another IOU device".format(console)) self._allocated_console_ports.remove(self._console) self._console = console diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index a30c66e9c..7578c0a3a 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -393,14 +393,12 @@ class IOU(Router): except FileNotFoundError: pass except Exception as e: - raise NetioError("Couldn't unlink socket {}: {}" - .format(self.ttyC, e)) + raise NetioError("Couldn't unlink socket {}: {}".format(self.ttyC, e)) try: self.fd.bind(self.ttyC) except Exception as e: - raise NetioError("Couldn't create socket {}: {}" - .format(self.ttyC, e)) + raise NetioError("Couldn't create socket {}: {}".format(self.ttyC, e)) def _connect(self): # Keep trying until we connect or die trying @@ -411,8 +409,7 @@ class IOU(Router): log.debug("Waiting to connect to {}".format(self.ttyS)) time.sleep(RETRY_DELAY) except Exception as e: - raise NetioError("Couldn't connect to socket {}: {}" - .format(self.ttyS, e)) + raise NetioError("Couldn't connect to socket {}: {}".format(self.ttyS, e)) else: break @@ -465,8 +462,7 @@ def mkdir_netio(netio_dir): except FileExistsError: pass except Exception as e: - raise NetioError("Couldn't create directory {}: {}" - .format(netio_dir, e)) + raise NetioError("Couldn't create directory {}: {}".format(netio_dir, e)) def send_recv_loop(console, router, esc_char, stop_event): @@ -631,7 +627,7 @@ def start_ioucon(cmdline_args, stop_event): if args.debug: traceback.print_exc(file=sys.stderr) else: - print(e, file=sys.stderr) + log.error("ioucon: {}".format(e)) sys.exit(EXIT_FAILURE) log.info("exiting...") diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index ad232a4cf..657590aa9 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -26,13 +26,19 @@ IOU_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "console": { + "description": "console TCP port", + "minimum": 1, + "maximum": 65535, + "type": "integer" + }, "path": { "description": "path to the IOU executable", "type": "string", "minLength": 1, } }, - "required": ["path"] + "required": ["path"], } IOU_DELETE_SCHEMA = { diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 5c31310ac..ea270e0c6 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -213,6 +213,7 @@ class VPCS(IModule): Optional request parameters: - name (VPCS name) + - console (VPCS console port) Response parameters: - id (VPCS instance identifier) @@ -226,9 +227,10 @@ class VPCS(IModule): if request and not self.validate_request(request, VPCS_CREATE_SCHEMA): return - name = None - if request and "name" in request: - name = request["name"] + name = console = None + if request: + name = request.get("name") + console = request.get("console") try: try: @@ -245,6 +247,7 @@ class VPCS(IModule): self._working_dir, self._host, name, + console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 229e2accc..681b48f6c 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -26,6 +26,12 @@ VPCS_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "console": { + "description": "console TCP port", + "minimum": 1, + "maximum": 65535, + "type": "integer" + }, }, } diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 23bd69705..d9bdb33ec 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -43,6 +43,7 @@ class VPCSDevice(object): :param working_dir: path to a working directory :param host: host/address to bind for console and UDP connections :param name: name of this VPCS device + :param console: TCP console port :param console_start_port_range: TCP console port range start :param console_end_port_range: TCP console port range end """ @@ -55,6 +56,7 @@ class VPCSDevice(object): working_dir, host="127.0.0.1", name=None, + console=None, console_start_port_range=4512, console_end_port_range=5000): @@ -77,7 +79,7 @@ class VPCSDevice(object): self._name = "VPCS{}".format(self._id) self._path = path - self._console = None + self._console = console self._working_dir = None self._command = [] self._process = None @@ -94,15 +96,18 @@ class VPCSDevice(object): # update the working directory self.working_dir = working_dir - # allocate a console port - try: - self._console = find_unused_port(self._console_start_port_range, - self._console_end_port_range, - self._host, - ignore_ports=self._allocated_console_ports) - except Exception as e: - raise VPCSError(e) + if not self._console: + # allocate a console port + try: + self._console = find_unused_port(self._console_start_port_range, + self._console_end_port_range, + self._host, + ignore_ports=self._allocated_console_ports) + except Exception as e: + raise VPCSError(e) + if self._console in self._allocated_console_ports: + raise VPCSError("Console port {} is already used by another VPCS device".format(console)) self._allocated_console_ports.append(self._console) log.info("VPCS device {name} [id={id}] has been created".format(name=self._name, @@ -250,7 +255,7 @@ class VPCSDevice(object): """ if console in self._allocated_console_ports: - raise VPCSError("Console port {} is already in used by another VPCS device".format(console)) + raise VPCSError("Console port {} is already used by another VPCS device".format(console)) self._allocated_console_ports.remove(self._console) self._console = console From 3d6ec140b7af0a7b8b3a0af3fc9fe42ad7d984c3 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 May 2014 18:06:28 -0600 Subject: [PATCH 23/46] Forbid additional properties in schemas, add missing ones. --- gns3server/modules/dynamips/backends/vm.py | 6 ++- gns3server/modules/dynamips/schemas/atmsw.py | 10 +++- gns3server/modules/dynamips/schemas/ethhub.py | 10 +++- gns3server/modules/dynamips/schemas/ethsw.py | 10 +++- gns3server/modules/dynamips/schemas/frsw.py | 10 +++- gns3server/modules/dynamips/schemas/vm.py | 49 +++++++++++++++++-- gns3server/modules/iou/schemas.py | 9 ++++ gns3server/modules/vpcs/schemas.py | 9 ++++ gns3server/version.py | 2 +- 9 files changed, 102 insertions(+), 13 deletions(-) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 9b6055ee5..99fa0f87e 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -479,7 +479,11 @@ class VM(object): # Update the ghost IOS file in case the RAM size has changed if self._hypervisor_manager.ghost_ios_support: - self.set_ghost_ios(router) + try: + self.set_ghost_ios(router) + except DynamipsError as e: + self.send_custom_error(str(e)) + return self.send_response(response) diff --git a/gns3server/modules/dynamips/schemas/atmsw.py b/gns3server/modules/dynamips/schemas/atmsw.py index 5d96e8c5b..b545880b6 100644 --- a/gns3server/modules/dynamips/schemas/atmsw.py +++ b/gns3server/modules/dynamips/schemas/atmsw.py @@ -24,8 +24,9 @@ ATMSW_CREATE_SCHEMA = { "description": "ATM switch name", "type": "string", "minLength": 1, - } - } + }, + }, + "additionalProperties": False, } ATMSW_DELETE_SCHEMA = { @@ -38,6 +39,7 @@ ATMSW_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -56,6 +58,7 @@ ATMSW_UPDATE_SCHEMA = { "minLength": 1, }, }, + "additionalProperties": False, "required": ["id"] } @@ -73,6 +76,7 @@ ATMSW_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -237,6 +241,7 @@ ATMSW_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port", "port_id", "mappings", "nio"], } @@ -255,5 +260,6 @@ ATMSW_DELETE_NIO_SCHEMA = { "minimum": 1, }, }, + "additionalProperties": False, "required": ["id", "port"] } diff --git a/gns3server/modules/dynamips/schemas/ethhub.py b/gns3server/modules/dynamips/schemas/ethhub.py index db1b1a299..efea271cb 100644 --- a/gns3server/modules/dynamips/schemas/ethhub.py +++ b/gns3server/modules/dynamips/schemas/ethhub.py @@ -24,8 +24,9 @@ ETHHUB_CREATE_SCHEMA = { "description": "Ethernet hub name", "type": "string", "minLength": 1, - } - } + }, + }, + "additionalProperties": False, } ETHHUB_DELETE_SCHEMA = { @@ -38,6 +39,7 @@ ETHHUB_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -56,6 +58,7 @@ ETHHUB_UPDATE_SCHEMA = { "minLength": 1, }, }, + "additionalProperties": False, "required": ["id"] } @@ -73,6 +76,7 @@ ETHHUB_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -234,6 +238,7 @@ ETHHUB_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port_id", "port", "nio"] } @@ -252,5 +257,6 @@ ETHHUB_DELETE_NIO_SCHEMA = { "minimum": 1, }, }, + "additionalProperties": False, "required": ["id", "port"] } diff --git a/gns3server/modules/dynamips/schemas/ethsw.py b/gns3server/modules/dynamips/schemas/ethsw.py index c68a35651..0c8b80746 100644 --- a/gns3server/modules/dynamips/schemas/ethsw.py +++ b/gns3server/modules/dynamips/schemas/ethsw.py @@ -24,8 +24,9 @@ ETHSW_CREATE_SCHEMA = { "description": "Ethernet switch name", "type": "string", "minLength": 1, - } - } + }, + }, + "additionalProperties": False, } ETHSW_DELETE_SCHEMA = { @@ -38,6 +39,7 @@ ETHSW_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -71,6 +73,7 @@ ETHSW_UPDATE_SCHEMA = { # }, # }, }, + #"additionalProperties": False, "required": ["id"] } @@ -88,6 +91,7 @@ ETHSW_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -258,6 +262,7 @@ ETHSW_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port_id", "port", "port_type", "vlan", "nio"], "dependencies": { @@ -281,5 +286,6 @@ ETHSW_DELETE_NIO_SCHEMA = { "minimum": 1, }, }, + "additionalProperties": False, "required": ["id", "port"] } diff --git a/gns3server/modules/dynamips/schemas/frsw.py b/gns3server/modules/dynamips/schemas/frsw.py index 984d2c247..72272782d 100644 --- a/gns3server/modules/dynamips/schemas/frsw.py +++ b/gns3server/modules/dynamips/schemas/frsw.py @@ -24,8 +24,9 @@ FRSW_CREATE_SCHEMA = { "description": "Frame relay switch name", "type": "string", "minLength": 1, - } - } + }, + }, + "additionalProperties": False, } FRSW_DELETE_SCHEMA = { @@ -38,6 +39,7 @@ FRSW_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -56,6 +58,7 @@ FRSW_UPDATE_SCHEMA = { "minLength": 1, }, }, + "additionalProperties": False, "required": ["id"] } @@ -73,6 +76,7 @@ FRSW_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -237,6 +241,7 @@ FRSW_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port", "port_id", "mappings", "nio"], } @@ -255,5 +260,6 @@ FRSW_DELETE_NIO_SCHEMA = { "minimum": 1, }, }, + "additionalProperties": False, "required": ["id", "port"] } diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 7ab8e3fe5..99b9096c8 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -58,13 +58,14 @@ VM_CREATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, - "mac_address": { + "mac_addr": { "description": "base MAC address", "type": "string", "minLength": 1, "pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$" } }, + "additionalProperties": False, "required": ["platform", "image", "ram"] } @@ -78,6 +79,7 @@ VM_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -91,6 +93,7 @@ VM_START_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -104,6 +107,7 @@ VM_STOP_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -117,6 +121,7 @@ VM_SUSPEND_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -130,10 +135,11 @@ VM_RELOAD_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } -#TODO: platform specific properties? +#TODO: improve platform specific properties (dependencies?) VM_UPDATE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to update a VM instance", @@ -237,7 +243,7 @@ VM_UPDATE_SCHEMA = { "minimum": 1, "maximum": 65535 }, - "mac_address": { + "mac_addr": { "description": "base MAC address", "type": "string", "minLength": 1, @@ -326,7 +332,39 @@ VM_UPDATE_SCHEMA = { "description": "private configuration base64 encoded", "type": "string" }, + # C7200 properties + "npe": { + "description": "NPE model", + "enum": ["npe-100", + "npe-150", + "npe-175", + "npe-200", + "npe-225", + "npe-300", + "npe-400", + "npe-g2"] + }, + "midplane": { + "description": "Midplane model", + "enum": ["std", "vxr"] + }, + "sensors": { + "description": "Temperature sensors", + "type": "array" + }, + "power_supplies": { + "description": "Power supplies status", + "type": "array" + }, + # I/O memory property for all platforms but C7200 + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100 + }, }, + "additionalProperties": False, "required": ["id"] } @@ -340,6 +378,7 @@ VM_SAVE_CONFIG_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -357,6 +396,7 @@ VM_IDLEPCS_SCHEMA = { "type": "boolean" }, }, + "additionalProperties": False, "required": ["id"] } @@ -374,6 +414,7 @@ VM_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -542,6 +583,7 @@ VM_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port_id", "slot", "port", "nio"] } @@ -567,5 +609,6 @@ VM_DELETE_NIO_SCHEMA = { "maximum": 49 # maximum is 16 for regular port numbers, WICs port numbers start at 16, 32 or 48 }, }, + "additionalProperties": False, "required": ["id", "slot", "port"] } diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 657590aa9..1723f4a8a 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -38,6 +38,7 @@ IOU_CREATE_SCHEMA = { "minLength": 1, } }, + "additionalProperties": False, "required": ["path"], } @@ -51,6 +52,7 @@ IOU_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -117,6 +119,7 @@ IOU_UPDATE_SCHEMA = { "type": "string" }, }, + "additionalProperties": False, "required": ["id"] } @@ -130,6 +133,7 @@ IOU_START_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -143,6 +147,7 @@ IOU_STOP_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -156,6 +161,7 @@ IOU_RELOAD_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -173,6 +179,7 @@ IOU_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -341,6 +348,7 @@ IOU_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port_id", "slot", "port", "nio"] } @@ -367,5 +375,6 @@ IOU_DELETE_NIO_SCHEMA = { "maximum": 3 }, }, + "additionalProperties": False, "required": ["id", "slot", "port"] } diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 681b48f6c..015b23131 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -33,6 +33,7 @@ VPCS_CREATE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, } VPCS_DELETE_SCHEMA = { @@ -45,6 +46,7 @@ VPCS_DELETE_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -78,6 +80,7 @@ VPCS_UPDATE_SCHEMA = { "type": "string" }, }, + "additionalProperties": False, "required": ["id"] } @@ -91,6 +94,7 @@ VPCS_START_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -104,6 +108,7 @@ VPCS_STOP_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -117,6 +122,7 @@ VPCS_RELOAD_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id"] } @@ -134,6 +140,7 @@ VPCS_ALLOCATE_UDP_PORT_SCHEMA = { "type": "integer" }, }, + "additionalProperties": False, "required": ["id", "port_id"] } @@ -296,6 +303,7 @@ VPCS_ADD_NIO_SCHEMA = { ] }, }, + "additionalProperties": False, "required": ["id", "port_id", "port", "nio"] } @@ -315,5 +323,6 @@ VPCS_DELETE_NIO_SCHEMA = { "maximum": 0 }, }, + "additionalProperties": False, "required": ["id", "port"] } diff --git a/gns3server/version.py b/gns3server/version.py index 1dafced72..f2a2a65b5 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a5.dev2" +__version__ = "1.0a5.dev3" __version_info__ = (1, 0, 0, -99) From 72b204dfe659241a8270378247c8baa30dad9e0b Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 21 May 2014 19:11:28 -0600 Subject: [PATCH 24/46] Use SIGBREAK to stop VPCS on Windows. --- gns3server/modules/iou/__init__.py | 1 + gns3server/modules/vpcs/__init__.py | 1 + gns3server/modules/vpcs/vpcs_device.py | 12 ++++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 9b71e2bb4..ac4129132 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -580,6 +580,7 @@ class IOU(IModule): ignore_ports=self._allocated_udp_ports) except Exception as e: self.send_custom_error(str(e)) + return self._allocated_udp_ports.append(port) log.info("{} [id={}] has allocated UDP port {} with host {}".format(iou_instance.name, diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index ea270e0c6..7e539e4b8 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -494,6 +494,7 @@ class VPCS(IModule): ignore_ports=self._allocated_udp_ports) except Exception as e: self.send_custom_error(str(e)) + return self._allocated_udp_ports.append(port) log.info("{} [id={}] has allocated UDP port {} with host {}".format(vpcs_instance.name, diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index d9bdb33ec..6a5a6397c 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -21,6 +21,7 @@ order to run an VPCS instance. """ import os +import sys import subprocess import signal import shutil @@ -343,11 +344,14 @@ class VPCSDevice(object): log.info("starting VPCS: {}".format(self._command)) self._vpcs_stdout_file = os.path.join(self._working_dir, "vpcs.log") log.info("logging to {}".format(self._vpcs_stdout_file)) + if sys.platform.startswith("win32"): + flags = subprocess.CREATE_NEW_PROCESS_GROUP with open(self._vpcs_stdout_file, "w") as fd: self._process = subprocess.Popen(self._command, stdout=fd, stderr=subprocess.STDOUT, - cwd=self._working_dir) + cwd=self._working_dir, + creationflags=flags) log.info("VPCS instance {} started PID={}".format(self._id, self._process.pid)) self._started = True except OSError as e: @@ -363,7 +367,11 @@ class VPCSDevice(object): # stop the VPCS process if self.is_running(): log.info("stopping VPCS instance {} PID={}".format(self._id, self._process.pid)) - self._process.send_signal(signal.SIGTERM) # send SIGTERM will stop VPCS + if sys.platform.startswith("win32"): + self._process.send_signal(signal.CTRL_BREAK_EVENT) + else: + self._process.terminate() + self._process.wait() self._process = None From 909915ceebc361cad5ad3eb304b824a8a2de4555 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 21 May 2014 19:13:32 -0600 Subject: [PATCH 25/46] Bump version to alpha5. --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index f2a2a65b5..4a8ed7e54 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a5.dev3" +__version__ = "1.0a5" __version_info__ = (1, 0, 0, -99) From f1d346f9585b075fa6a6e1eab771f2291ac1c979 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 21 May 2014 23:34:19 -0600 Subject: [PATCH 26/46] Bump to version 1.0a6.dev1 --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index 4a8ed7e54..e3ec80857 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a5" +__version__ = "1.0a6.dev1" __version_info__ = (1, 0, 0, -99) From 0f5d2927dfae48e4bdd875134638c0f578259895 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 22 May 2014 10:14:09 -0600 Subject: [PATCH 27/46] Fixes VPCS start on Linux/UNIX. Fixes #15. --- gns3server/modules/vpcs/vpcs_device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 6a5a6397c..d84d2a7e0 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -344,6 +344,7 @@ class VPCSDevice(object): log.info("starting VPCS: {}".format(self._command)) self._vpcs_stdout_file = os.path.join(self._working_dir, "vpcs.log") log.info("logging to {}".format(self._vpcs_stdout_file)) + flags = 0 if sys.platform.startswith("win32"): flags = subprocess.CREATE_NEW_PROCESS_GROUP with open(self._vpcs_stdout_file, "w") as fd: From 9da5aa110743eb8c72ccf3992282a1c462315616 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 22 May 2014 10:54:34 -0600 Subject: [PATCH 28/46] Fixes validation issue with c2600 XM chassis. --- gns3server/modules/dynamips/schemas/vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 99b9096c8..581a4d655 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -35,7 +35,7 @@ VM_CREATE_SCHEMA = { "description": "router chassis model", "type": "string", "minLength": 1, - "pattern": "^[0-9]{4}$" + "pattern": "^[0-9]{4}(XM)?$" }, "image": { "description": "path to the IOS image file", From f2fbdf618f29bdffe453a334aec2aa6ff0b0c6ba Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 22 May 2014 13:06:29 -0600 Subject: [PATCH 29/46] Fixes privileged access checks for IOU. --- gns3server/modules/iou/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index ac4129132..0b04574ac 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -643,14 +643,13 @@ class IOU(IModule): nio = NIO_UDP(lport, rhost, rport) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] - if not self.has_privileged_access(self._iouyap, tap_device): + if not has_privileged_access(self._iouyap, tap_device): raise IOUError("{} has no privileged access to {}.".format(self._iouyap, tap_device)) nio = NIO_TAP(tap_device) elif request["nio"]["type"] == "nio_generic_ethernet": ethernet_device = request["nio"]["ethernet_device"] - if not self.has_privileged_access(self._iouyap, ethernet_device): + if not has_privileged_access(self._iouyap, ethernet_device): raise IOUError("{} has no privileged access to {}.".format(self._iouyap, ethernet_device)) - self._check_for_privileged_access(ethernet_device) nio = NIO_GenericEthernet(ethernet_device) if not nio: raise IOUError("Requested NIO does not exist or is not supported: {}".format(request["nio"]["type"])) From d7b9ed33f80c7a3675d1d2d1d2311afcac302081 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 25 May 2014 01:51:57 -0600 Subject: [PATCH 30/46] Bump to version 1.0a6.dev2 --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index e3ec80857..697d30737 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a6.dev1" +__version__ = "1.0a6.dev2" __version_info__ = (1, 0, 0, -99) From 3a0439c9aec86ca5c3ea52912ac1056f57597d92 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 27 May 2014 03:59:22 -0600 Subject: [PATCH 31/46] New hostnames management for the Dynamips module. --- gns3server/modules/dynamips/backends/atmsw.py | 6 ++--- .../modules/dynamips/backends/ethhub.py | 7 ++---- gns3server/modules/dynamips/backends/ethsw.py | 7 ++---- gns3server/modules/dynamips/backends/frsw.py | 7 ++---- gns3server/modules/dynamips/backends/vm.py | 6 ++--- .../modules/dynamips/nodes/atm_switch.py | 20 ++++++++--------- gns3server/modules/dynamips/nodes/bridge.py | 6 +++++ gns3server/modules/dynamips/nodes/c1700.py | 2 +- gns3server/modules/dynamips/nodes/c2600.py | 2 +- gns3server/modules/dynamips/nodes/c2691.py | 2 +- gns3server/modules/dynamips/nodes/c3600.py | 2 +- gns3server/modules/dynamips/nodes/c3725.py | 2 +- gns3server/modules/dynamips/nodes/c3745.py | 2 +- gns3server/modules/dynamips/nodes/c7200.py | 2 +- .../modules/dynamips/nodes/ethernet_switch.py | 19 +++++++--------- .../dynamips/nodes/frame_relay_switch.py | 19 +++++++--------- gns3server/modules/dynamips/nodes/hub.py | 14 ++++-------- gns3server/modules/dynamips/nodes/router.py | 22 ++++++++----------- gns3server/modules/dynamips/schemas/atmsw.py | 1 + gns3server/modules/dynamips/schemas/ethhub.py | 1 + gns3server/modules/dynamips/schemas/ethsw.py | 1 + gns3server/modules/dynamips/schemas/frsw.py | 1 + gns3server/modules/dynamips/schemas/vm.py | 2 +- 23 files changed, 66 insertions(+), 87 deletions(-) diff --git a/gns3server/modules/dynamips/backends/atmsw.py b/gns3server/modules/dynamips/backends/atmsw.py index 4c5c26445..b775c191f 100644 --- a/gns3server/modules/dynamips/backends/atmsw.py +++ b/gns3server/modules/dynamips/backends/atmsw.py @@ -38,7 +38,7 @@ class ATMSW(object): """ Creates a new ATM switch. - Optional request parameters: + Mandatory request parameters: - name (switch name) Response parameters: @@ -52,10 +52,8 @@ class ATMSW(object): if request and not self.validate_request(request, ATMSW_CREATE_SCHEMA): return - name = None - if request and "name" in request: - name = request["name"] + name = request["name"] try: if not self._hypervisor_manager: self.start_hypervisor_manager() diff --git a/gns3server/modules/dynamips/backends/ethhub.py b/gns3server/modules/dynamips/backends/ethhub.py index 28678bf0d..9939cb71a 100644 --- a/gns3server/modules/dynamips/backends/ethhub.py +++ b/gns3server/modules/dynamips/backends/ethhub.py @@ -37,7 +37,7 @@ class ETHHUB(object): """ Creates a new Ethernet hub. - Optional request parameters: + Mandatory request parameters: - name (hub name) Response parameters: @@ -51,10 +51,7 @@ class ETHHUB(object): if request and not self.validate_request(request, ETHHUB_CREATE_SCHEMA): return - name = None - if request and "name" in request: - name = request["name"] - + name = request["name"] try: if not self._hypervisor_manager: self.start_hypervisor_manager() diff --git a/gns3server/modules/dynamips/backends/ethsw.py b/gns3server/modules/dynamips/backends/ethsw.py index cf8369f49..cb06f9276 100644 --- a/gns3server/modules/dynamips/backends/ethsw.py +++ b/gns3server/modules/dynamips/backends/ethsw.py @@ -37,7 +37,7 @@ class ETHSW(object): """ Creates a new Ethernet switch. - Optional request parameters: + Mandatory request parameters: - name (switch name) Response parameters: @@ -51,10 +51,7 @@ class ETHSW(object): if request and not self.validate_request(request, ETHSW_CREATE_SCHEMA): return - name = None - if request and "name" in request: - name = request["name"] - + name = request["name"] try: if not self._hypervisor_manager: self.start_hypervisor_manager() diff --git a/gns3server/modules/dynamips/backends/frsw.py b/gns3server/modules/dynamips/backends/frsw.py index 5bbf72a21..34042332f 100644 --- a/gns3server/modules/dynamips/backends/frsw.py +++ b/gns3server/modules/dynamips/backends/frsw.py @@ -37,7 +37,7 @@ class FRSW(object): """ Creates a new Frame-Relay switch. - Optional request parameters: + Mandatory request parameters: - name (switch name) Response parameters: @@ -51,10 +51,7 @@ class FRSW(object): if request and not self.validate_request(request, FRSW_CREATE_SCHEMA): return - name = None - if request and "name" in request: - name = request["name"] - + name = request["name"] try: if not self._hypervisor_manager: self.start_hypervisor_manager() diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 99fa0f87e..56274d9f0 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -105,12 +105,12 @@ class VM(object): Creates a new VM (router). Mandatory request parameters: + - name (vm name) - platform (platform name e.g. c7200) - image (path to IOS image) - ram (amount of RAM in MB) Optional request parameters: - - name (vm name) - console (console port number) - aux (auxiliary console port number) - mac_addr (MAC address) @@ -127,9 +127,7 @@ class VM(object): if not self.validate_request(request, VM_CREATE_SCHEMA): return - name = None - if "name" in request: - name = request["name"] + name = request["name"] platform = request["platform"] image = request["image"] ram = request["ram"] diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index eecd9544e..7dfcbbbe5 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -37,22 +37,16 @@ class ATMSwitch(object): _allocated_names = [] _instance_count = 1 - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): + + # check if the name is already taken + if name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another ATM switch'.format(name)) # create an unique ID self._id = ATMSwitch._instance_count ATMSwitch._instance_count += 1 - # let's create a unique name if none has been chosen - if not name: - name_id = self._id - while True: - name = "ATM" + str(name_id) - # check if the name has already been allocated to another switch - if name not in self._allocated_names: - break - name_id += 1 - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces @@ -102,6 +96,10 @@ class ATMSwitch(object): :param new_name: New name for this switch """ + # check if the name is already taken + if new_name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another ATM switch'.format(new_name)) + new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("atmsw rename {name} {new_name}".format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index a47bf6548..d0f194d63 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -20,6 +20,8 @@ Interface for Dynamips NIO bridge module ("nio_bridge"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L538 """ +from ..dynamips_error import DynamipsError + class Bridge(object): """ @@ -58,6 +60,10 @@ class Bridge(object): :param new_name: New name for this bridge """ + # check if the name is already taken + if new_name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another bridge'.format(new_name)) + new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("nio_bridge rename {name} {new_name}".format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/c1700.py b/gns3server/modules/dynamips/nodes/c1700.py index 51318790f..09d75d316 100644 --- a/gns3server/modules/dynamips/nodes/c1700.py +++ b/gns3server/modules/dynamips/nodes/c1700.py @@ -39,7 +39,7 @@ class C1700(Router): 1710 is not supported. """ - def __init__(self, hypervisor, name=None, chassis="1720"): + def __init__(self, hypervisor, name, chassis="1720"): Router.__init__(self, hypervisor, name, platform="c1700") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c2600.py b/gns3server/modules/dynamips/nodes/c2600.py index e7f5b61a3..ff0fb0c02 100644 --- a/gns3server/modules/dynamips/nodes/c2600.py +++ b/gns3server/modules/dynamips/nodes/c2600.py @@ -54,7 +54,7 @@ class C2600(Router): "2650XM": C2600_MB_1FE, "2651XM": C2600_MB_2FE} - def __init__(self, hypervisor, name=None, chassis="2610"): + def __init__(self, hypervisor, name, chassis="2610"): Router.__init__(self, hypervisor, name, platform="c2600") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c2691.py b/gns3server/modules/dynamips/nodes/c2691.py index 9ba7e396d..baec82dee 100644 --- a/gns3server/modules/dynamips/nodes/c2691.py +++ b/gns3server/modules/dynamips/nodes/c2691.py @@ -35,7 +35,7 @@ class C2691(Router): :param name: name for this router """ - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): Router.__init__(self, hypervisor, name, platform="c2691") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c3600.py b/gns3server/modules/dynamips/nodes/c3600.py index ccbd565c6..fd9790d4a 100644 --- a/gns3server/modules/dynamips/nodes/c3600.py +++ b/gns3server/modules/dynamips/nodes/c3600.py @@ -37,7 +37,7 @@ class C3600(Router): 3620, 3640 or 3660 (default = 3640). """ - def __init__(self, hypervisor, name=None, chassis="3640"): + def __init__(self, hypervisor, name, chassis="3640"): Router.__init__(self, hypervisor, name, platform="c3600") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c3725.py b/gns3server/modules/dynamips/nodes/c3725.py index d32fab1b5..455575ce4 100644 --- a/gns3server/modules/dynamips/nodes/c3725.py +++ b/gns3server/modules/dynamips/nodes/c3725.py @@ -35,7 +35,7 @@ class C3725(Router): :param name: name for this router """ - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): Router.__init__(self, hypervisor, name, platform="c3725") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c3745.py b/gns3server/modules/dynamips/nodes/c3745.py index 5c0ea13cb..5c914fee7 100644 --- a/gns3server/modules/dynamips/nodes/c3745.py +++ b/gns3server/modules/dynamips/nodes/c3745.py @@ -35,7 +35,7 @@ class C3745(Router): :param name: name for this router """ - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): Router.__init__(self, hypervisor, name, platform="c3745") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index 0307eedc8..ce63e7261 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -38,7 +38,7 @@ class C7200(Router): :param npe: default NPE """ - def __init__(self, hypervisor, name=None, npe="npe-400"): + def __init__(self, hypervisor, name, npe="npe-400"): Router.__init__(self, hypervisor, name, platform="c7200") # Set default values for this platform diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index 83ddf0800..99794f4a5 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -37,22 +37,16 @@ class EthernetSwitch(object): _allocated_names = [] _instance_count = 1 - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): + + # check if the name is already taken + if name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another Ethernet switch'.format(name)) # create an unique ID self._id = EthernetSwitch._instance_count EthernetSwitch._instance_count += 1 - # let's create a unique name if none has been chosen - if not name: - name_id = self._id - while True: - name = "SW" + str(name_id) - # check if the name has already been allocated to another switch - if name not in self._allocated_names: - break - name_id += 1 - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces @@ -102,6 +96,9 @@ class EthernetSwitch(object): :param new_name: New name for this switch """ + if new_name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another Ethernet switch'.format(new_name)) + new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("ethsw rename {name} {new_name}".format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index 8a1eec31f..1cd5f3a02 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -37,22 +37,16 @@ class FrameRelaySwitch(object): _allocated_names = [] _instance_count = 1 - def __init__(self, hypervisor, name=None): + def __init__(self, hypervisor, name): + + # check if the name is already taken + if name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another Frame Relay switch'.format(name)) # create an unique ID self._id = FrameRelaySwitch._instance_count FrameRelaySwitch._instance_count += 1 - # let's create a unique name if none has been chosen - if not name: - name_id = self._id - while True: - name = "FR" + str(name_id) - # check if the name has already been allocated to another switch - if name not in self._allocated_names: - break - name_id += 1 - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces @@ -102,6 +96,9 @@ class FrameRelaySwitch(object): :param new_name: New name for this switch """ + if new_name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another Frame Relay switch'.format(new_name)) + new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("frsw rename {name} {new_name}".format(name=self._name, diff --git a/gns3server/modules/dynamips/nodes/hub.py b/gns3server/modules/dynamips/nodes/hub.py index b66a54889..2ee8274ba 100644 --- a/gns3server/modules/dynamips/nodes/hub.py +++ b/gns3server/modules/dynamips/nodes/hub.py @@ -38,20 +38,14 @@ class Hub(Bridge): def __init__(self, hypervisor, name): + # check if the name is already taken + if name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another Ethernet hub'.format(name)) + # create an unique ID self._id = Hub._instance_count Hub._instance_count += 1 - # let's create a unique name if none has been chosen - if not name: - name_id = self._id - while True: - name = "Hub" + str(name_id) - # check if the name has already been allocated to another switch - if name not in self._allocated_names: - break - name_id += 1 - self._mapping = {} Bridge.__init__(self, hypervisor, name) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 554cd7297..931573f3c 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -50,22 +50,18 @@ class Router(object): 2: "running", 3: "suspended"} - def __init__(self, hypervisor, name=None, platform="c7200", ghost_flag=False): + def __init__(self, hypervisor, name, platform="c7200", ghost_flag=False): if not ghost_flag: + + # check if the name is already taken + if name in self._allocated_names: + raise DynamipsError('Name "{}" is already used by another router'.format(name)) + # create an unique ID self._id = Router._instance_count Router._instance_count += 1 - # let's create a unique name if none has been chosen - if not name: - name_id = self._id - while True: - name = "R" + str(name_id) - # check if the name has already been allocated to another router - if name not in self._allocated_names: - break - name_id += 1 else: log.info("creating a new ghost IOS file") self._id = 0 @@ -581,10 +577,10 @@ class Router(object): reply = self._hypervisor.send("vm extract_config {}".format(self._name))[0].rsplit(' ', 2)[-2:] except IOError: #for some reason Dynamips gets frozen when it does not find the magic number in the NVRAM file. - return (None, None) + return None, None startup_config = reply[0][1:-1] # get statup-config and remove single quotes private_config = reply[1][1:-1] # get private-config and remove single quotes - return (startup_config, private_config) + return startup_config, private_config def push_config(self, startup_config, private_config='(keep)'): """ @@ -727,7 +723,7 @@ class Router(object): else: flag = 0 self._hypervisor.send("vm set_sparse_mem {name} {sparsemem}".format(name=self._name, - sparsemem=flag)) + sparsemem=flag)) if sparsemem: log.info("router {name} [id={id}]: sparse memory enabled".format(name=self._name, diff --git a/gns3server/modules/dynamips/schemas/atmsw.py b/gns3server/modules/dynamips/schemas/atmsw.py index b545880b6..2041299b6 100644 --- a/gns3server/modules/dynamips/schemas/atmsw.py +++ b/gns3server/modules/dynamips/schemas/atmsw.py @@ -27,6 +27,7 @@ ATMSW_CREATE_SCHEMA = { }, }, "additionalProperties": False, + "required": ["name"] } ATMSW_DELETE_SCHEMA = { diff --git a/gns3server/modules/dynamips/schemas/ethhub.py b/gns3server/modules/dynamips/schemas/ethhub.py index efea271cb..6db1b796c 100644 --- a/gns3server/modules/dynamips/schemas/ethhub.py +++ b/gns3server/modules/dynamips/schemas/ethhub.py @@ -27,6 +27,7 @@ ETHHUB_CREATE_SCHEMA = { }, }, "additionalProperties": False, + "required": ["name"] } ETHHUB_DELETE_SCHEMA = { diff --git a/gns3server/modules/dynamips/schemas/ethsw.py b/gns3server/modules/dynamips/schemas/ethsw.py index 0c8b80746..a33a98b88 100644 --- a/gns3server/modules/dynamips/schemas/ethsw.py +++ b/gns3server/modules/dynamips/schemas/ethsw.py @@ -27,6 +27,7 @@ ETHSW_CREATE_SCHEMA = { }, }, "additionalProperties": False, + "required": ["name"] } ETHSW_DELETE_SCHEMA = { diff --git a/gns3server/modules/dynamips/schemas/frsw.py b/gns3server/modules/dynamips/schemas/frsw.py index 72272782d..5dd5e5bb3 100644 --- a/gns3server/modules/dynamips/schemas/frsw.py +++ b/gns3server/modules/dynamips/schemas/frsw.py @@ -27,6 +27,7 @@ FRSW_CREATE_SCHEMA = { }, }, "additionalProperties": False, + "required": ["name"] } FRSW_DELETE_SCHEMA = { diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 581a4d655..47bcb75be 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -66,7 +66,7 @@ VM_CREATE_SCHEMA = { } }, "additionalProperties": False, - "required": ["platform", "image", "ram"] + "required": ["name", "platform", "image", "ram"] } VM_DELETE_SCHEMA = { From a39a693cdacd0262f431d13b3ac6738e0a89579a Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 27 May 2014 11:23:06 -0600 Subject: [PATCH 32/46] Hostname management refactoring. --- gns3server/modules/dynamips/backends/atmsw.py | 3 +- .../modules/dynamips/backends/ethhub.py | 2 +- gns3server/modules/dynamips/backends/ethsw.py | 2 +- gns3server/modules/dynamips/backends/frsw.py | 2 +- .../modules/dynamips/nodes/atm_bridge.py | 1 + .../modules/dynamips/nodes/atm_switch.py | 33 +++++++---------- gns3server/modules/dynamips/nodes/bridge.py | 11 ------ .../modules/dynamips/nodes/ethernet_switch.py | 32 +++++++--------- .../dynamips/nodes/frame_relay_switch.py | 32 +++++++--------- gns3server/modules/dynamips/nodes/hub.py | 23 +++++++----- gns3server/modules/dynamips/nodes/router.py | 37 ++++++++----------- gns3server/modules/iou/__init__.py | 6 +-- gns3server/modules/iou/iou_device.py | 10 ++--- gns3server/modules/iou/schemas.py | 2 +- gns3server/modules/vpcs/__init__.py | 16 ++++---- gns3server/modules/vpcs/schemas.py | 1 + gns3server/modules/vpcs/vpcs_device.py | 2 +- gns3server/version.py | 2 +- 18 files changed, 92 insertions(+), 125 deletions(-) diff --git a/gns3server/modules/dynamips/backends/atmsw.py b/gns3server/modules/dynamips/backends/atmsw.py index b775c191f..5f4ab494c 100644 --- a/gns3server/modules/dynamips/backends/atmsw.py +++ b/gns3server/modules/dynamips/backends/atmsw.py @@ -49,10 +49,9 @@ class ATMSW(object): """ # validate the request - if request and not self.validate_request(request, ATMSW_CREATE_SCHEMA): + if not self.validate_request(request, ATMSW_CREATE_SCHEMA): return - name = request["name"] try: if not self._hypervisor_manager: diff --git a/gns3server/modules/dynamips/backends/ethhub.py b/gns3server/modules/dynamips/backends/ethhub.py index 9939cb71a..c09703c2e 100644 --- a/gns3server/modules/dynamips/backends/ethhub.py +++ b/gns3server/modules/dynamips/backends/ethhub.py @@ -48,7 +48,7 @@ class ETHHUB(object): """ # validate the request - if request and not self.validate_request(request, ETHHUB_CREATE_SCHEMA): + if not self.validate_request(request, ETHHUB_CREATE_SCHEMA): return name = request["name"] diff --git a/gns3server/modules/dynamips/backends/ethsw.py b/gns3server/modules/dynamips/backends/ethsw.py index cb06f9276..a59ec4b7e 100644 --- a/gns3server/modules/dynamips/backends/ethsw.py +++ b/gns3server/modules/dynamips/backends/ethsw.py @@ -48,7 +48,7 @@ class ETHSW(object): """ # validate the request - if request and not self.validate_request(request, ETHSW_CREATE_SCHEMA): + if not self.validate_request(request, ETHSW_CREATE_SCHEMA): return name = request["name"] diff --git a/gns3server/modules/dynamips/backends/frsw.py b/gns3server/modules/dynamips/backends/frsw.py index 34042332f..cae6923f4 100644 --- a/gns3server/modules/dynamips/backends/frsw.py +++ b/gns3server/modules/dynamips/backends/frsw.py @@ -48,7 +48,7 @@ class FRSW(object): """ # validate the request - if request and not self.validate_request(request, FRSW_CREATE_SCHEMA): + if not self.validate_request(request, FRSW_CREATE_SCHEMA): return name = request["name"] diff --git a/gns3server/modules/dynamips/nodes/atm_bridge.py b/gns3server/modules/dynamips/nodes/atm_bridge.py index 036cfb5d1..10abe1b2e 100644 --- a/gns3server/modules/dynamips/nodes/atm_bridge.py +++ b/gns3server/modules/dynamips/nodes/atm_bridge.py @@ -33,6 +33,7 @@ class ATMBridge(object): def __init__(self, hypervisor, name): + #FIXME: instance tracking self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("atm_bridge create {}".format(self._name)) diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 7dfcbbbe5..00fb967ce 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -34,20 +34,21 @@ class ATMSwitch(object): :param name: name for this switch """ - _allocated_names = [] - _instance_count = 1 + _instances = [] def __init__(self, hypervisor, name): - # check if the name is already taken - if name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another ATM switch'.format(name)) + # find an instance identifier (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - # create an unique ID - self._id = ATMSwitch._instance_count - ATMSwitch._instance_count += 1 + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("atmsw create {}".format(self._name)) @@ -62,11 +63,10 @@ class ATMSwitch(object): @classmethod def reset(cls): """ - Resets the instance count and the allocated names list. + Resets the instance count and the allocated instances list. """ - cls._instance_count = 1 - cls._allocated_names.clear() + cls._instances.clear() @property def id(self): @@ -96,11 +96,6 @@ class ATMSwitch(object): :param new_name: New name for this switch """ - # check if the name is already taken - if new_name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another ATM switch'.format(new_name)) - - new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("atmsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) @@ -109,9 +104,7 @@ class ATMSwitch(object): id=self._id, new_name=new_name)) - self._allocated_names.remove(self.name) self._name = new_name - self._allocated_names.append(new_name_no_quotes) @property def hypervisor(self): @@ -162,7 +155,7 @@ class ATMSwitch(object): log.info("ATM switch {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) self._hypervisor.devices.remove(self) - self._allocated_names.remove(self.name) + self._instances.remove(self._id) def has_port(self, port): """ diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index d0f194d63..84e7255ae 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -31,12 +31,9 @@ class Bridge(object): :param name: name for this bridge """ - _allocated_names = [] - def __init__(self, hypervisor, name): self._hypervisor = hypervisor - self._allocated_names.append(name) self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("nio_bridge create {}".format(self._name)) self._hypervisor.devices.append(self) @@ -60,18 +57,11 @@ class Bridge(object): :param new_name: New name for this bridge """ - # check if the name is already taken - if new_name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another bridge'.format(new_name)) - - new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("nio_bridge rename {name} {new_name}".format(name=self._name, new_name=new_name)) - self._allocated_names.remove(self.name) self._name = new_name - self._allocated_names.append(new_name_no_quotes) @property def hypervisor(self): @@ -109,7 +99,6 @@ class Bridge(object): self._hypervisor.send("nio_bridge delete {}".format(self._name)) self._hypervisor.devices.remove(self) - self._allocated_names.remove(self.name) def add_nio(self, nio): """ diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index 99794f4a5..9363bafbb 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -34,20 +34,21 @@ class EthernetSwitch(object): :param name: name for this switch """ - _allocated_names = [] - _instance_count = 1 + _instances = [] def __init__(self, hypervisor, name): - # check if the name is already taken - if name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another Ethernet switch'.format(name)) + # find an instance identifier (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - # create an unique ID - self._id = EthernetSwitch._instance_count - EthernetSwitch._instance_count += 1 + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("ethsw create {}".format(self._name)) @@ -62,11 +63,10 @@ class EthernetSwitch(object): @classmethod def reset(cls): """ - Resets the instance count and the allocated names list. + Resets the instance count and the allocated instances list. """ - cls._instance_count = 1 - cls._allocated_names.clear() + cls._instances.clear() @property def id(self): @@ -96,10 +96,6 @@ class EthernetSwitch(object): :param new_name: New name for this switch """ - if new_name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another Ethernet switch'.format(new_name)) - - new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("ethsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) @@ -108,9 +104,7 @@ class EthernetSwitch(object): id=self._id, new_name=new_name)) - self._allocated_names.remove(self.name) self._name = new_name - self._allocated_names.append(new_name_no_quotes) @property def hypervisor(self): @@ -161,7 +155,7 @@ class EthernetSwitch(object): log.info("Ethernet switch {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) self._hypervisor.devices.remove(self) - self._allocated_names.remove(self.name) + self._instances.remove(self._id) def add_nio(self, nio, port): """ diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index 1cd5f3a02..e096c1376 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -34,20 +34,21 @@ class FrameRelaySwitch(object): :param name: name for this switch """ - _allocated_names = [] - _instance_count = 1 + _instances = [] def __init__(self, hypervisor, name): - # check if the name is already taken - if name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another Frame Relay switch'.format(name)) + # find an instance identifier (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - # create an unique ID - self._id = FrameRelaySwitch._instance_count - FrameRelaySwitch._instance_count += 1 + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._hypervisor.send("frsw create {}".format(self._name)) @@ -62,11 +63,10 @@ class FrameRelaySwitch(object): @classmethod def reset(cls): """ - Resets the instance count and the allocated names list. + Resets the instance count and the allocated instances list. """ - cls._instance_count = 1 - cls._allocated_names.clear() + cls._instances.clear() @property def id(self): @@ -96,10 +96,6 @@ class FrameRelaySwitch(object): :param new_name: New name for this switch """ - if new_name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another Frame Relay switch'.format(new_name)) - - new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("frsw rename {name} {new_name}".format(name=self._name, new_name=new_name)) @@ -108,9 +104,7 @@ class FrameRelaySwitch(object): id=self._id, new_name=new_name)) - self._allocated_names.remove(self.name) self._name = new_name - self._allocated_names.append(new_name_no_quotes) @property def hypervisor(self): @@ -161,7 +155,7 @@ class FrameRelaySwitch(object): log.info("Frame Relay switch {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) self._hypervisor.devices.remove(self) - self._allocated_names.remove(self.name) + self._instances.remove(self._id) def has_port(self, port): """ diff --git a/gns3server/modules/dynamips/nodes/hub.py b/gns3server/modules/dynamips/nodes/hub.py index 2ee8274ba..b32ff9443 100644 --- a/gns3server/modules/dynamips/nodes/hub.py +++ b/gns3server/modules/dynamips/nodes/hub.py @@ -34,17 +34,20 @@ class Hub(Bridge): :param name: name for this hub """ - _instance_count = 1 + _instances = [] def __init__(self, hypervisor, name): - # check if the name is already taken - if name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another Ethernet hub'.format(name)) + # find an instance identifier (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - # create an unique ID - self._id = Hub._instance_count - Hub._instance_count += 1 + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") self._mapping = {} Bridge.__init__(self, hypervisor, name) @@ -55,11 +58,10 @@ class Hub(Bridge): @classmethod def reset(cls): """ - Resets the instance count and the allocated names list. + Resets the instance count and the allocated instances list. """ - cls._instance_count = 1 - cls._allocated_names.clear() + cls._instances.clear() @property def id(self): @@ -89,6 +91,7 @@ class Hub(Bridge): Bridge.delete(self) log.info("Ethernet hub {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) + self._instances.remove(self._id) def add_nio(self, nio, port): """ diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 931573f3c..343219866 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -41,10 +41,9 @@ class Router(object): :param ghost_flag: used when creating a ghost IOS. """ - _allocated_names = [] + _instances = [] _allocated_console_ports = [] _allocated_aux_ports = [] - _instance_count = 1 _status = {0: "inactive", 1: "shutting down", 2: "running", @@ -54,20 +53,22 @@ class Router(object): if not ghost_flag: - # check if the name is already taken - if name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another router'.format(name)) + # find an instance identifier (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - # create an unique ID - self._id = Router._instance_count - Router._instance_count += 1 + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") else: log.info("creating a new ghost IOS file") self._id = 0 name = "Ghost" - self._allocated_names.append(name) self._hypervisor = hypervisor self._name = '"' + name + '"' # put name into quotes to protect spaces self._platform = platform @@ -140,11 +141,10 @@ class Router(object): @classmethod def reset(cls): """ - Resets the instance count and the allocated names list. + Resets the instance count and the allocated instances list. """ - cls._instance_count = 1 - cls._allocated_names.clear() + cls._instances.clear() cls._allocated_console_ports.clear() cls._allocated_aux_ports.clear() @@ -218,9 +218,6 @@ class Router(object): :param new_name: new name string """ - if new_name in self._allocated_names: - raise DynamipsError('Name "{}" is already used by another router'.format(new_name)) - if self._startup_config: # change the hostname in the startup-config startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) @@ -261,10 +258,7 @@ class Router(object): log.info("router {name} [id={id}]: renamed to {new_name}".format(name=self._name, id=self._id, new_name=new_name)) - - self._allocated_names.remove(self.name) self._name = new_name - self._allocated_names.append(new_name_no_quotes) @property def platform(self): @@ -312,9 +306,9 @@ class Router(object): self._hypervisor.send("vm delete {}".format(self._name)) self._hypervisor.devices.remove(self) - log.info("router {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) - self._allocated_names.remove(self.name) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) if self.aux: @@ -341,7 +335,8 @@ class Router(object): os.remove(private_config_path) log.info("router {name} [id={id}] has been deleted (including associated files)".format(name=self._name, id=self._id)) - self._allocated_names.remove(self.name) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) if self.aux: diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 0b04574ac..aa7a7dbf2 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -315,7 +315,7 @@ class IOU(IModule): if not self.validate_request(request, IOU_CREATE_SCHEMA): return - name = request.get("name") + name = request["name"] console = request.get("console") iou_path = request["path"] @@ -327,10 +327,10 @@ class IOU(IModule): except OSError as e: raise IOUError("Could not create working directory {}".format(e)) - iou_instance = IOUDevice(iou_path, + iou_instance = IOUDevice(name, + iou_path, self._working_dir, self._host, - name, console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 3598e4ad6..4c3aee38c 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -58,10 +58,11 @@ class IOUDevice(object): _instances = [] _allocated_console_ports = [] - def __init__(self, path, + def __init__(self, + name, + path, working_dir, host="127.0.0.1", - name=None, console=None, console_start_port_range=4001, console_end_port_range=4512): @@ -77,10 +78,7 @@ class IOUDevice(object): if self._id == 0: raise IOUError("Maximum number of IOU instances reached") - if name: - self._name = name - else: - self._name = "IOU{}".format(self._id) + self._name = name self._path = path self._iourc = "" self._iouyap = "" diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 1723f4a8a..355206590 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -39,7 +39,7 @@ IOU_CREATE_SCHEMA = { } }, "additionalProperties": False, - "required": ["path"], + "required": ["name", "path"], } IOU_DELETE_SCHEMA = { diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 7e539e4b8..291aadeed 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -211,8 +211,10 @@ class VPCS(IModule): """ Creates a new VPCS instance. - Optional request parameters: + Mandatory request parameters: - name (VPCS name) + + Optional request parameters: - console (VPCS console port) Response parameters: @@ -224,13 +226,11 @@ class VPCS(IModule): """ # validate the request - if request and not self.validate_request(request, VPCS_CREATE_SCHEMA): + if not self.validate_request(request, VPCS_CREATE_SCHEMA): return - name = console = None - if request: - name = request.get("name") - console = request.get("console") + name = request["name"] + console = request.get("console") try: try: @@ -243,10 +243,10 @@ class VPCS(IModule): if not self._vpcs: raise VPCSError("No path to a VPCS executable has been set") - vpcs_instance = VPCSDevice(self._vpcs, + vpcs_instance = VPCSDevice(name, + self._vpcs, self._working_dir, self._host, - name, console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 015b23131..868f9b310 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -34,6 +34,7 @@ VPCS_CREATE_SCHEMA = { }, }, "additionalProperties": False, + "required": ["name"] } VPCS_DELETE_SCHEMA = { diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index d84d2a7e0..f3fbf3e3e 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -53,10 +53,10 @@ class VPCSDevice(object): _allocated_console_ports = [] def __init__(self, + name, path, working_dir, host="127.0.0.1", - name=None, console=None, console_start_port_range=4512, console_end_port_range=5000): diff --git a/gns3server/version.py b/gns3server/version.py index 697d30737..68bdcc6bd 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a6.dev2" +__version__ = "1.0a6.dev3" __version_info__ = (1, 0, 0, -99) From 7b58f146812b94411fc7de327e6bb102185ace0c Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 28 May 2014 06:26:20 -0600 Subject: [PATCH 33/46] Some PEP8 style fixes. --- gns3server/builtins/server_version.py | 1 + gns3server/handlers/file_upload_handler.py | 11 +- gns3server/handlers/jsonrpc_websocket.py | 3 +- gns3server/jsonrpc.py | 2 +- gns3server/module_manager.py | 1 - gns3server/modules/attic.py | 7 +- gns3server/modules/base.py | 2 +- gns3server/modules/dynamips/__init__.py | 10 +- .../modules/dynamips/adapters/adapter.py | 2 +- .../modules/dynamips/dynamips_hypervisor.py | 10 +- gns3server/modules/dynamips/hypervisor.py | 12 +- .../modules/dynamips/hypervisor_manager.py | 7 +- gns3server/modules/dynamips/nios/nio.py | 4 +- gns3server/modules/dynamips/nodes/bridge.py | 2 - gns3server/modules/dynamips/nodes/router.py | 27 +- gns3server/modules/dynamips/schemas/atmsw.py | 242 ++++++++--------- gns3server/modules/dynamips/schemas/ethhub.py | 242 ++++++++--------- gns3server/modules/dynamips/schemas/ethsw.py | 244 +++++++++--------- gns3server/modules/dynamips/schemas/frsw.py | 242 ++++++++--------- gns3server/modules/dynamips/schemas/vm.py | 242 ++++++++--------- gns3server/modules/iou/__init__.py | 22 +- gns3server/modules/iou/iou_device.py | 54 ++-- gns3server/modules/iou/ioucon.py | 56 ++-- gns3server/modules/iou/schemas.py | 242 ++++++++--------- gns3server/modules/vpcs/__init__.py | 17 +- gns3server/modules/vpcs/schemas.py | 242 ++++++++--------- gns3server/modules/vpcs/vpcs_device.py | 37 +-- requirements.txt | 1 + setup.py | 8 +- tests/test_jsonrpc.py | 6 +- tests/test_version_handler.py | 4 +- 31 files changed, 988 insertions(+), 1014 deletions(-) diff --git a/gns3server/builtins/server_version.py b/gns3server/builtins/server_version.py index b637bbf33..aaf294fb1 100644 --- a/gns3server/builtins/server_version.py +++ b/gns3server/builtins/server_version.py @@ -23,6 +23,7 @@ Sends version to requesting clients in JSON-RPC Websocket handler. from ..version import __version__ from ..jsonrpc import JSONRPCResponse + def server_version(handler, request_id, params): """ Builtin destination to return the server version. diff --git a/gns3server/handlers/file_upload_handler.py b/gns3server/handlers/file_upload_handler.py index d73f12d69..93e9158ce 100644 --- a/gns3server/handlers/file_upload_handler.py +++ b/gns3server/handlers/file_upload_handler.py @@ -38,15 +38,14 @@ class FileUploadHandler(tornado.web.RequestHandler): :param request: Tornado Request instance """ - def __init__(self, application, request): + def __init__(self, application, request, **kwargs): - # get the upload directory from the configuration file + super().__init__(application, request, **kwargs) config = Config.instance() server_config = config.get_default_section() - # default projects directory is "~/Documents/GNS3/images" - self._upload_dir = os.path.expandvars(os.path.expanduser(server_config.get("upload_directory", "~/Documents/GNS3/images"))) + self._upload_dir = os.path.expandvars( + os.path.expanduser(server_config.get("upload_directory", "~/Documents/GNS3/images"))) self._host = request.host - try: os.makedirs(self._upload_dir) log.info("upload directory '{}' created".format(self._upload_dir)) @@ -55,8 +54,6 @@ class FileUploadHandler(tornado.web.RequestHandler): except OSError as e: log.error("could not create the upload directory {}: {}".format(self._upload_dir, e)) - tornado.websocket.WebSocketHandler.__init__(self, application, request) - def get(self): """ Invoked on GET request. diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py index f09d0040d..fdab3cadc 100644 --- a/gns3server/handlers/jsonrpc_websocket.py +++ b/gns3server/handlers/jsonrpc_websocket.py @@ -106,8 +106,7 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): if destination.startswith("builtin"): log.debug("registering {} as a built-in destination".format(destination)) else: - log.debug("registering {} as a destination for the {} module".format(destination, - module)) + log.debug("registering {} as a destination for the {} module".format(destination, module)) cls.destinations[destination] = module def open(self): diff --git a/gns3server/jsonrpc.py b/gns3server/jsonrpc.py index ce9813b84..c4251aad8 100644 --- a/gns3server/jsonrpc.py +++ b/gns3server/jsonrpc.py @@ -161,7 +161,7 @@ class JSONRPCRequest(JSONRPCObject): def __init__(self, method, params=None, request_id=None): JSONRPCObject.__init__(self) - if request_id == None: + if request_id is None: request_id = str(uuid.uuid4()) self.id = request_id self.method = method diff --git a/gns3server/module_manager.py b/gns3server/module_manager.py index cf3814bec..878f0852c 100644 --- a/gns3server/module_manager.py +++ b/gns3server/module_manager.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import imp import inspect import pkgutil from .modules import IModule diff --git a/gns3server/modules/attic.py b/gns3server/modules/attic.py index d0ebf1ed4..8b4a0714d 100644 --- a/gns3server/modules/attic.py +++ b/gns3server/modules/attic.py @@ -96,7 +96,7 @@ def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10): connection_success = False begin = time.time() last_exception = None - while (time.time() - begin < wait): + while time.time() - begin < wait: time.sleep(0.01) try: with socket.create_connection((host, port), socket_timeout): @@ -107,16 +107,15 @@ def wait_socket_is_ready(host, port, wait=2.0, socket_timeout=10): connection_success = True break - return (connection_success, last_exception) + return connection_success, last_exception -def has_privileged_access(executable, device): +def has_privileged_access(executable): """ Check if an executable can access Ethernet and TAP devices in RAW mode. :param executable: executable path - :param device: device name :returns: True or False """ diff --git a/gns3server/modules/base.py b/gns3server/modules/base.py index f9c19ca03..620737c03 100644 --- a/gns3server/modules/base.py +++ b/gns3server/modules/base.py @@ -288,7 +288,7 @@ class IModule(multiprocessing.Process): """ # check if we have a request - if request == None: + if request is None: self.send_param_error() return False log.debug("received request {}".format(request)) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 741928816..3e278c252 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -27,7 +27,6 @@ import shutil import glob import socket from gns3server.modules import IModule -import gns3server.jsonrpc as jsonrpc from .hypervisor import Hypervisor from .hypervisor_manager import HypervisorManager @@ -249,8 +248,8 @@ class Dynamips(IModule): if not os.access(self._dynamips, os.X_OK): raise DynamipsError("Dynamips {} is not executable".format(self._dynamips)) + workdir = os.path.join(self._working_dir, "dynamips") try: - workdir = os.path.join(self._working_dir, "dynamips") os.makedirs(workdir) except FileExistsError: pass @@ -282,7 +281,7 @@ class Dynamips(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() return @@ -342,7 +341,7 @@ class Dynamips(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() else: log.debug("received request {}".format(request)) @@ -415,7 +414,6 @@ class Dynamips(IModule): port, host)) response = {"lport": port} - return response def set_ghost_ios(self, router): @@ -498,7 +496,7 @@ class Dynamips(IModule): """ log.info("creating config file {} from base64".format(destination_config_path)) - config = base64.decodestring(config_base64.encode("utf-8")).decode("utf-8") + config = base64.decodebytes(config_base64.encode("utf-8")).decode("utf-8") config = "!\n" + config.replace("\r", "") config = config.replace('%h', router.name) config_dir = os.path.dirname(destination_config_path) diff --git a/gns3server/modules/dynamips/adapters/adapter.py b/gns3server/modules/dynamips/adapters/adapter.py index b963e3345..d963933e9 100644 --- a/gns3server/modules/dynamips/adapters/adapter.py +++ b/gns3server/modules/dynamips/adapters/adapter.py @@ -63,7 +63,7 @@ class Adapter(object): False otherwise. """ - if self._wics[wic_slot_id] == None: + if self._wics[wic_slot_id] is None: return True return False diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 0a53b64b0..0770ff631 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -61,7 +61,7 @@ class DynamipsHypervisor(object): self._udp_end_port_range = 20000 self._nio_udp_auto_instances = {} self._version = "N/A" - self._timeout = 30 + self._timeout = timeout self._socket = None self._uuid = None @@ -80,9 +80,7 @@ class DynamipsHypervisor(object): host = self._host try: - self._socket = socket.create_connection((host, - self._port), - self._timeout) + self._socket = socket.create_connection((host, self._port), self._timeout) except OSError as e: raise DynamipsError("Could not connect to server: {}".format(e)) @@ -477,7 +475,7 @@ class DynamipsHypervisor(object): self.socket.sendall(command.encode('utf-8')) except OSError as e: raise DynamipsError("Lost communication with {host}:{port} :{error}" - .format(host=self._host, port=self._port, error=e)) + .format(host=self._host, port=self._port, error=e)) # Now retrieve the result data = [] @@ -488,7 +486,7 @@ class DynamipsHypervisor(object): buf += chunk.decode("utf-8") except OSError as e: raise DynamipsError("Communication timed out with {host}:{port} :{error}" - .format(host=self._host, port=self._port, error=e)) + .format(host=self._host, port=self._port, error=e)) # If the buffer doesn't end in '\n' then we can't be done try: diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index 911874e03..e1cc2e291 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -70,7 +70,7 @@ class Hypervisor(DynamipsHypervisor): :returns: id (integer) """ - return(self._id) + return self._id @property def started(self): @@ -90,7 +90,7 @@ class Hypervisor(DynamipsHypervisor): :returns: path to Dynamips """ - return(self._path) + return self._path @path.setter def path(self, path): @@ -110,7 +110,7 @@ class Hypervisor(DynamipsHypervisor): :returns: port number (integer) """ - return(self._port) + return self._port @port.setter def port(self, port): @@ -130,7 +130,7 @@ class Hypervisor(DynamipsHypervisor): :returns: host/address (string) """ - return(self._host) + return self._host @host.setter def host(self, host): @@ -232,7 +232,7 @@ class Hypervisor(DynamipsHypervisor): self._process.wait(1) except subprocess.TimeoutExpired: self._process.kill() - if self._process.poll() == None: + if self._process.poll() is None: log.warn("Dynamips process {} is still running".format(self._process.pid)) if self._stdout_file and os.access(self._stdout_file, os.W_OK): @@ -264,7 +264,7 @@ class Hypervisor(DynamipsHypervisor): :returns: True or False """ - if self._process and self._process.poll() == None: + if self._process and self._process.poll() is None: return True return False diff --git a/gns3server/modules/dynamips/hypervisor_manager.py b/gns3server/modules/dynamips/hypervisor_manager.py index 4a65b2f47..7c8153c43 100644 --- a/gns3server/modules/dynamips/hypervisor_manager.py +++ b/gns3server/modules/dynamips/hypervisor_manager.py @@ -39,10 +39,6 @@ class HypervisorManager(object): :param path: path to the Dynamips executable :param working_dir: path to a working directory :param host: host/address for hypervisors to listen to - :param base_port: base TCP port for hypervisors - :param base_console: base TCP port for consoles - :param base_aux: base TCP port for auxiliary consoles - :param base_udp: base UDP port for UDP tunnels """ def __init__(self, path, working_dir, host='127.0.0.1'): @@ -504,13 +500,12 @@ class HypervisorManager(object): else: log.info("allocating an hypervisor per IOS image disabled") - def wait_for_hypervisor(self, host, port, timeout=10): + def wait_for_hypervisor(self, host, port): """ Waits for an hypervisor to be started (accepting a socket connection) :param host: host/address to connect to the hypervisor :param port: port to connect to the hypervisor - :param timeout: timeout value (default is 10 seconds) """ begin = time.time() diff --git a/gns3server/modules/dynamips/nios/nio.py b/gns3server/modules/dynamips/nios/nio.py index f27b7e735..04af1380b 100644 --- a/gns3server/modules/dynamips/nios/nio.py +++ b/gns3server/modules/dynamips/nios/nio.py @@ -174,7 +174,7 @@ class NIO(object): :returns: tuple (filter name, filter options) """ - return (self._input_filter, self._input_filter_options) + return self._input_filter, self._input_filter_options @property def output_filter(self): @@ -184,7 +184,7 @@ class NIO(object): :returns: tuple (filter name, filter options) """ - return (self._output_filter, self._output_filter_options) + return self._output_filter, self._output_filter_options def get_stats(self): """ diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index 84e7255ae..a87ba029d 100644 --- a/gns3server/modules/dynamips/nodes/bridge.py +++ b/gns3server/modules/dynamips/nodes/bridge.py @@ -20,8 +20,6 @@ Interface for Dynamips NIO bridge module ("nio_bridge"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L538 """ -from ..dynamips_error import DynamipsError - class Bridge(object): """ diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 343219866..7bfe7e52c 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -134,7 +134,7 @@ class Router(object): # get the default base MAC address self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform, - name=self._name))[0] + name=self._name))[0] self._hypervisor.devices.append(self) @@ -250,7 +250,6 @@ class Router(object): raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e)) self.set_config(self.startup_config, new_private_config_path) - new_name_no_quotes = new_name new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("vm rename {name} {new_name}".format(name=self._name, new_name=new_name)) @@ -978,7 +977,7 @@ class Router(object): translated by the JIT (they contain the native code corresponding to MIPS code pages). - :param excec_area: exec area value (integer) + :param exec_area: exec area value (integer) """ self._hypervisor.send("vm set_exec_area {name} {exec_area}".format(name=self._name, @@ -1259,7 +1258,7 @@ class Router(object): :returns: slot bindings (adapter names) list """ - return (self._hypervisor.send("vm slot_bindings {}".format(self._name))) + return self._hypervisor.send("vm slot_bindings {}".format(self._name)) def slot_add_binding(self, slot_id, adapter): """ @@ -1275,16 +1274,16 @@ class Router(object): raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, slot_id=slot_id)) - if slot != None: + if slot is not None: current_adapter = slot raise DynamipsError("Slot {slot_id} is already occupied by adapter {adapter} on router {name}".format(name=self._name, slot_id=slot_id, adapter=current_adapter)) # Only c7200, c3600 and c3745 (NM-4T only) support new adapter while running - if self.is_running() and not (self._platform == 'c7200' \ - and not (self._platform == 'c3600' and self.chassis == '3660') \ - and not (self._platform == 'c3745' and adapter == 'NM-4T')): + if self.is_running() and not (self._platform == 'c7200' + and not (self._platform == 'c3600' and self.chassis == '3660') + and not (self._platform == 'c3745' and adapter == 'NM-4T')): raise DynamipsError("Adapter {adapter} cannot be added while router {name} is running".format(adapter=adapter, name=self._name)) @@ -1322,14 +1321,14 @@ class Router(object): raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, slot_id=slot_id)) - if adapter == None: + if adapter is None: raise DynamipsError("No adapter in slot {slot_id} on router {name}".format(name=self._name, slot_id=slot_id)) # Only c7200, c3600 and c3745 (NM-4T only) support to remove adapter while running - if self.is_running() and not (self._platform == 'c7200' \ - and not (self._platform == 'c3600' and self.chassis == '3660') \ - and not (self._platform == 'c3745' and adapter == 'NM-4T')): + if self.is_running() and not (self._platform == 'c7200' + and not (self._platform == 'c3600' and self.chassis == '3660') + and not (self._platform == 'c3745' and adapter == 'NM-4T')): raise DynamipsError("Adapter {adapter} cannot be removed while router {name} is running".format(adapter=adapter, name=self._name)) @@ -1415,8 +1414,8 @@ class Router(object): # WIC1 = 16, WIC2 = 32 and WIC3 = 48 internal_wic_slot_id = 16 * (wic_slot_id + 1) self._hypervisor.send("vm slot_remove_binding {name} {slot_id} {wic_slot_id}".format(name=self._name, - slot_id=slot_id, - wic_slot_id=internal_wic_slot_id)) + slot_id=slot_id, + wic_slot_id=internal_wic_slot_id)) log.info("router {name} [id={id}]: {wic} removed from WIC slot {wic_slot_id}".format(name=self._name, id=self._id, diff --git a/gns3server/modules/dynamips/schemas/atmsw.py b/gns3server/modules/dynamips/schemas/atmsw.py index 2041299b6..cddea592d 100644 --- a/gns3server/modules/dynamips/schemas/atmsw.py +++ b/gns3server/modules/dynamips/schemas/atmsw.py @@ -87,129 +87,129 @@ ATMSW_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/dynamips/schemas/ethhub.py b/gns3server/modules/dynamips/schemas/ethhub.py index 6db1b796c..50470bccc 100644 --- a/gns3server/modules/dynamips/schemas/ethhub.py +++ b/gns3server/modules/dynamips/schemas/ethhub.py @@ -87,129 +87,129 @@ ETHHUB_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/dynamips/schemas/ethsw.py b/gns3server/modules/dynamips/schemas/ethsw.py index a33a98b88..92f47b80a 100644 --- a/gns3server/modules/dynamips/schemas/ethsw.py +++ b/gns3server/modules/dynamips/schemas/ethsw.py @@ -102,129 +102,129 @@ ETHSW_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { @@ -269,7 +269,7 @@ ETHSW_ADD_NIO_SCHEMA = { "dependencies": { "port_type": ["vlan"], "vlan": ["port_type"] - } + } } ETHSW_DELETE_NIO_SCHEMA = { diff --git a/gns3server/modules/dynamips/schemas/frsw.py b/gns3server/modules/dynamips/schemas/frsw.py index 5dd5e5bb3..b5b6ebdbd 100644 --- a/gns3server/modules/dynamips/schemas/frsw.py +++ b/gns3server/modules/dynamips/schemas/frsw.py @@ -87,129 +87,129 @@ FRSW_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 47bcb75be..3a7d9af58 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -424,129 +424,129 @@ VM_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index aa7a7dbf2..86a51d604 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -20,17 +20,13 @@ IOU server module. """ import os -import sys import base64 import tempfile -import fcntl -import struct import socket import shutil from gns3server.modules import IModule from gns3server.config import Config -import gns3server.jsonrpc as jsonrpc from .iou_device import IOUDevice from .iou_error import IOUError from .nios.nio_udp import NIO_UDP @@ -215,12 +211,12 @@ class IOU(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() return if "iourc" in request: - iourc_content = base64.decodestring(request["iourc"].encode("utf-8")).decode("utf-8") + iourc_content = base64.decodebytes(request["iourc"].encode("utf-8")).decode("utf-8") iourc_content = iourc_content.replace("\r\n", "\n") # dos2unix try: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: @@ -228,7 +224,7 @@ class IOU(IModule): f.write(iourc_content) self._iourc = f.name except OSError as e: - raise IOUError("Could not save iourc file to {}: {}".format(f.name, e)) + raise IOUError("Could not create the iourc file: {}".format(e)) if "iouyap" in request and request["iouyap"]: self._iouyap = request["iouyap"] @@ -410,7 +406,7 @@ class IOU(IModule): try: if "startup_config_base64" in request: # a new startup-config has been pushed - config = base64.decodestring(request["startup_config_base64"].encode("utf-8")).decode("utf-8") + config = base64.decodebytes(request["startup_config_base64"].encode("utf-8")).decode("utf-8") config = "!\n" + config.replace("\r", "") config = config.replace('%h', iou_instance.name) try: @@ -587,8 +583,8 @@ class IOU(IModule): iou_instance.id, port, self._host)) - response = {"lport": port} - response["port_id"] = request["port_id"] + response = {"lport": port, + "port_id": request["port_id"]} self.send_response(response) @IModule.route("iou.add_nio") @@ -643,12 +639,12 @@ class IOU(IModule): nio = NIO_UDP(lport, rhost, rport) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] - if not has_privileged_access(self._iouyap, tap_device): + if not has_privileged_access(self._iouyap): raise IOUError("{} has no privileged access to {}.".format(self._iouyap, tap_device)) nio = NIO_TAP(tap_device) elif request["nio"]["type"] == "nio_generic_ethernet": ethernet_device = request["nio"]["ethernet_device"] - if not has_privileged_access(self._iouyap, ethernet_device): + if not has_privileged_access(self._iouyap): raise IOUError("{} has no privileged access to {}.".format(self._iouyap, ethernet_device)) nio = NIO_GenericEthernet(ethernet_device) if not nio: @@ -710,7 +706,7 @@ class IOU(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() else: log.debug("received request {}".format(request)) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 4c3aee38c..8233240a6 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -83,7 +83,7 @@ class IOUDevice(object): self._iourc = "" self._iouyap = "" self._console = console - self._working_dir = None + self._working_dir = working_dir self._command = [] self._process = None self._iouyap_process = None @@ -154,7 +154,7 @@ class IOUDevice(object): :returns: id (integer) """ - return(self._id) + return self._id @classmethod def reset(cls): @@ -185,7 +185,7 @@ class IOUDevice(object): if self._startup_config: # update the startup-config - config_path = os.path.join(self.working_dir, "startup-config") + config_path = os.path.join(self._working_dir, "startup-config") if os.path.isfile(config_path): try: with open(config_path, "r+") as f: @@ -209,7 +209,7 @@ class IOUDevice(object): :returns: path to IOU """ - return(self._path) + return self._path @path.setter def path(self, path): @@ -221,8 +221,8 @@ class IOUDevice(object): self._path = path log.info("IOU {name} [id={id}]: path changed to {path}".format(name=self._name, - id=self._id, - path=path)) + id=self._id, + path=path)) @property def iourc(self): @@ -232,14 +232,14 @@ class IOUDevice(object): :returns: path to the iourc file """ - return(self._iourc) + return self._iourc @iourc.setter def iourc(self, iourc): """ Sets the path to the iourc file. - :param path: path to the iourc file. + :param iourc: path to the iourc file. """ self._iourc = iourc @@ -255,14 +255,14 @@ class IOUDevice(object): :returns: path to iouyap """ - return(self._iouyap) + return self._iouyap @iouyap.setter def iouyap(self, iouyap): """ Sets the path to iouyap. - :param path: path to iouyap + :param iouyap: path to iouyap """ self._iouyap = iouyap @@ -299,8 +299,8 @@ class IOUDevice(object): self._working_dir = working_dir log.info("IOU {name} [id={id}]: working directory changed to {wd}".format(name=self._name, - id=self._id, - wd=self._working_dir)) + id=self._id, + wd=self._working_dir)) @property def console(self): @@ -327,8 +327,8 @@ class IOUDevice(object): self._console = console self._allocated_console_ports.append(self._console) log.info("IOU {name} [id={id}]: console port set to {port}".format(name=self._name, - id=self._id, - port=console)) + id=self._id, + port=console)) def command(self): """ @@ -368,8 +368,8 @@ class IOUDevice(object): shutil.rmtree(self._working_dir) except OSError as e: log.error("could not delete IOU device {name} [id={id}]: {error}".format(name=self._name, - id=self._id, - error=e)) + id=self._id, + error=e)) return log.info("IOU device {name} [id={id}] has been deleted (including associated files)".format(name=self._name, @@ -402,6 +402,7 @@ class IOUDevice(object): for unit in adapter.ports.keys(): nio = adapter.get_nio(unit) if nio: + connection = None if isinstance(nio, NIO_UDP): # UDP tunnel connection = {"tunnel_udp": "{lport}:{rhost}:{rport}".format(lport=nio.lport, @@ -415,7 +416,8 @@ class IOUDevice(object): # Ethernet interface connection = {"eth_dev": "{ethernet_device}".format(ethernet_device=nio.ethernet_device)} - config["{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id)] = connection + if connection: + config["{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id)] = connection unit_id += 1 bay_id += 1 @@ -581,7 +583,7 @@ class IOUDevice(object): self._iouyap_process.wait(1) except subprocess.TimeoutExpired: self._iouyap_process.kill() - if self._iouyap_process.poll() == None: + if self._iouyap_process.poll() is None: log.warn("iouyap PID={} for IOU instance {} is still running".format(self._iouyap_process.pid, self._id)) self._iouyap_process = None @@ -594,7 +596,7 @@ class IOUDevice(object): self._process.wait(1) except subprocess.TimeoutExpired: self._process.kill() - if self._process.poll() == None: + if self._process.poll() is None: log.warn("IOU instance {} PID={} is still running".format(self._id, self._process.pid)) self._process = None @@ -637,7 +639,7 @@ class IOUDevice(object): :returns: True or False """ - if self._process and self._process.poll() == None: + if self._process and self._process.poll() is None: return True return False @@ -648,7 +650,7 @@ class IOUDevice(object): :returns: True or False """ - if self._iouyap_process and self._iouyap_process.poll() == None: + if self._iouyap_process and self._iouyap_process.poll() is None: return True return False @@ -723,16 +725,14 @@ class IOUDevice(object): env = os.environ.copy() env["IOURC"] = self._iourc - output = b"" try: output = subprocess.check_output([self._path, "-h"], stderr=subprocess.STDOUT, cwd=self._working_dir, env=env) - except OSError as e: - log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e)) - else: if re.search("-l\s+Enable Layer 1 keepalive messages", output.decode("utf-8")): command.extend(["-l"]) else: raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) + except OSError as e: + log.warn("could not determine if layer 1 keepalive messages are supported by {}: {}".format(os.path.basename(self._path), e)) def _build_command(self): """ @@ -904,8 +904,8 @@ class IOUDevice(object): self._startup_config = startup_config log.info("IOU {name} [id={id}]: startup_config set to {config}".format(name=self._name, - id=self._id, - config=self._startup_config)) + id=self._id, + config=self._startup_config)) @property def ethernet_adapters(self): diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index 7578c0a3a..c3d046361 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -56,32 +56,32 @@ EXIT_ABORT = 2 # Mostly from: # https://code.google.com/p/miniboa/source/browse/trunk/miniboa/telnet.py #--[ Telnet Commands ]--------------------------------------------------------- -SE = 240 # End of subnegotiation parameters -NOP = 241 # No operation -DATMK = 242 # Data stream portion of a sync. -BREAK = 243 # NVT Character BRK -IP = 244 # Interrupt Process -AO = 245 # Abort Output -AYT = 246 # Are you there -EC = 247 # Erase Character -EL = 248 # Erase Line -GA = 249 # The Go Ahead Signal -SB = 250 # Sub-option to follow -WILL = 251 # Will; request or confirm option begin -WONT = 252 # Wont; deny option request -DO = 253 # Do = Request or confirm remote option -DONT = 254 # Don't = Demand or confirm option halt -IAC = 255 # Interpret as Command -SEND = 1 # Sub-process negotiation SEND command -IS = 0 # Sub-process negotiation IS command +SE = 240 # End of sub-negotiation parameters +NOP = 241 # No operation +DATMK = 242 # Data stream portion of a sync. +BREAK = 243 # NVT Character BRK +IP = 244 # Interrupt Process +AO = 245 # Abort Output +AYT = 246 # Are you there +EC = 247 # Erase Character +EL = 248 # Erase Line +GA = 249 # The Go Ahead Signal +SB = 250 # Sub-option to follow +WILL = 251 # Will; request or confirm option begin +WONT = 252 # Wont; deny option request +DO = 253 # Do = Request or confirm remote option +DONT = 254 # Don't = Demand or confirm option halt +IAC = 255 # Interpret as Command +SEND = 1 # Sub-process negotiation SEND command +IS = 0 # Sub-process negotiation IS command #--[ Telnet Options ]---------------------------------------------------------- BINARY = 0 # Transmit Binary -ECHO = 1 # Echo characters back to sender -RECON = 2 # Reconnection -SGA = 3 # Suppress Go-Ahead -TMARK = 6 # Timing Mark -TTYPE = 24 # Terminal Type -NAWS = 31 # Negotiate About Window Size +ECHO = 1 # Echo characters back to sender +RECON = 2 # Reconnection +SGA = 3 # Suppress Go-Ahead +TMARK = 6 # Timing Mark +TTYPE = 24 # Terminal Type +NAWS = 31 # Negotiate About Window Size LINEMO = 34 # Line Mode @@ -299,9 +299,7 @@ class TelnetServer(Console): buf.extend(self._read_block(1)) iac_cmd.append(buf[iac_loc + 2]) # We do ECHO, SGA, and BINARY. Period. - if (iac_cmd[1] == DO - and iac_cmd[2] not in [ECHO, SGA, BINARY]): - + if iac_cmd[1] == DO and iac_cmd[2] not in [ECHO, SGA, BINARY]: self._write_cur(bytes([IAC, WONT, iac_cmd[2]])) log.debug("Telnet WON'T {:#x}".format(iac_cmd[2])) else: @@ -326,7 +324,7 @@ class TelnetServer(Console): fd.send(bytes([IAC, WILL, ECHO, IAC, WILL, SGA, IAC, WILL, BINARY, - IAC, DO, BINARY])) + IAC, DO, BINARY])) if args.telnet_limit and len(self.fd_dict) > args.telnet_limit: fd.send(b'\r\nToo many connections\r\n') @@ -601,7 +599,7 @@ def start_ioucon(cmdline_args, stop_event): nport = int(port) except ValueError: pass - if (addr == '' or nport == 0): + if addr == '' or nport == 0: raise ConfigError('format for --telnet-server must be ' 'ADDR:PORT (like 127.0.0.1:20000)') diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 355206590..1d7ed5544 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -189,129 +189,129 @@ IOU_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 291aadeed..9c566ea16 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -20,16 +20,12 @@ VPCS server module. """ import os -import sys import base64 -import tempfile -import struct import socket import shutil from gns3server.modules import IModule from gns3server.config import Config -import gns3server.jsonrpc as jsonrpc from .vpcs_device import VPCSDevice from .vpcs_error import VPCSError from .nios.nio_udp import NIO_UDP @@ -101,7 +97,6 @@ class VPCS(IModule): :param signum: signal number (if called by the signal handler) """ - # self._vpcs_callback.stop() # delete all VPCS instances for vpcs_id in self._vpcs_instances: vpcs_instance = self._vpcs_instances[vpcs_id] @@ -162,7 +157,7 @@ class VPCS(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() return @@ -326,7 +321,7 @@ class VPCS(IModule): try: if "script_file_base64" in request: # a new startup-config has been pushed - config = base64.decodestring(request["script_file_base64"].encode("utf-8")).decode("utf-8") + config = base64.decodebytes(request["script_file_base64"].encode("utf-8")).decode("utf-8") config = config.replace("\r", "") config = config.replace('%h', vpcs_instance.name) try: @@ -502,8 +497,8 @@ class VPCS(IModule): port, self._host)) - response = {"lport": port} - response["port_id"] = request["port_id"] + response = {"lport": port, + "port_id": request["port_id"]} self.send_response(response) @IModule.route("vpcs.add_nio") @@ -554,7 +549,7 @@ class VPCS(IModule): nio = NIO_UDP(lport, rhost, rport) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] - if not self.has_privileged_access(self._vpcs, tap_device): + if not self.has_privileged_access(self._vpcs): raise VPCSError("{} has no privileged access to {}.".format(self._vpcs, tap_device)) nio = NIO_TAP(tap_device) if not nio: @@ -614,7 +609,7 @@ class VPCS(IModule): :param request: JSON request """ - if request == None: + if request is None: self.send_param_error() else: log.debug("received request {}".format(request)) diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 868f9b310..7258bda2c 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -151,129 +151,129 @@ VPCS_ADD_NIO_SCHEMA = { "type": "object", "definitions": { - "UDP": { - "description": "UDP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_udp"] - }, - "lport": { - "description": "Local port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "rhost": { - "description": "Remote host", - "type": "string", - "minLength": 1 - }, - "rport": { - "description": "Remote port", - "type": "integer", - "minimum": 1, - "maximum": 65535 - } - }, - "required": ["type", "lport", "rhost", "rport"], - "additionalProperties": False - }, - "Ethernet": { - "description": "Generic Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_generic_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "LinuxEthernet": { - "description": "Linux Ethernet Network Input/Output", - "properties": { - "type": { - "enum": ["nio_linux_ethernet"] - }, - "ethernet_device": { - "description": "Ethernet device name e.g. eth0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "ethernet_device"], - "additionalProperties": False - }, - "TAP": { - "description": "TAP Network Input/Output", - "properties": { - "type": { - "enum": ["nio_tap"] - }, - "tap_device": { - "description": "TAP device name e.g. tap0", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "tap_device"], - "additionalProperties": False - }, - "UNIX": { - "description": "UNIX Network Input/Output", - "properties": { - "type": { - "enum": ["nio_unix"] - }, - "local_file": { - "description": "path to the UNIX socket file (local)", - "type": "string", - "minLength": 1 - }, - "remote_file": { - "description": "path to the UNIX socket file (remote)", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "local_file", "remote_file"], - "additionalProperties": False - }, - "VDE": { - "description": "VDE Network Input/Output", - "properties": { - "type": { - "enum": ["nio_vde"] - }, - "control_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - "local_file": { - "description": "path to the VDE control file", - "type": "string", - "minLength": 1 - }, - }, - "required": ["type", "control_file", "local_file"], - "additionalProperties": False - }, - "NULL": { - "description": "NULL Network Input/Output", - "properties": { - "type": { - "enum": ["nio_null"] - }, - }, - "required": ["type"], - "additionalProperties": False + "UDP": { + "description": "UDP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_udp"] + }, + "lport": { + "description": "Local port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "rhost": { + "description": "Remote host", + "type": "string", + "minLength": 1 + }, + "rport": { + "description": "Remote port", + "type": "integer", + "minimum": 1, + "maximum": 65535 + } }, + "required": ["type", "lport", "rhost", "rport"], + "additionalProperties": False }, + "Ethernet": { + "description": "Generic Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_generic_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "LinuxEthernet": { + "description": "Linux Ethernet Network Input/Output", + "properties": { + "type": { + "enum": ["nio_linux_ethernet"] + }, + "ethernet_device": { + "description": "Ethernet device name e.g. eth0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "ethernet_device"], + "additionalProperties": False + }, + "TAP": { + "description": "TAP Network Input/Output", + "properties": { + "type": { + "enum": ["nio_tap"] + }, + "tap_device": { + "description": "TAP device name e.g. tap0", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "tap_device"], + "additionalProperties": False + }, + "UNIX": { + "description": "UNIX Network Input/Output", + "properties": { + "type": { + "enum": ["nio_unix"] + }, + "local_file": { + "description": "path to the UNIX socket file (local)", + "type": "string", + "minLength": 1 + }, + "remote_file": { + "description": "path to the UNIX socket file (remote)", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "local_file", "remote_file"], + "additionalProperties": False + }, + "VDE": { + "description": "VDE Network Input/Output", + "properties": { + "type": { + "enum": ["nio_vde"] + }, + "control_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + "local_file": { + "description": "path to the VDE control file", + "type": "string", + "minLength": 1 + }, + }, + "required": ["type", "control_file", "local_file"], + "additionalProperties": False + }, + "NULL": { + "description": "NULL Network Input/Output", + "properties": { + "type": { + "enum": ["nio_null"] + }, + }, + "required": ["type"], + "additionalProperties": False + }, + }, "properties": { "id": { diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index f3fbf3e3e..eb639954a 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -81,7 +81,8 @@ class VPCSDevice(object): self._path = path self._console = console - self._working_dir = None + self._working_dir = working_dir + self._host = host self._command = [] self._process = None self._vpcs_stdout_file = "" @@ -135,7 +136,7 @@ class VPCSDevice(object): :returns: id (integer) """ - return(self._id) + return self._id @classmethod def reset(cls): @@ -166,7 +167,7 @@ class VPCSDevice(object): if self._script_file: # update the startup.vpc - config_path = os.path.join(self.working_dir, "startup.vpc") + config_path = os.path.join(self._working_dir, "startup.vpc") if os.path.isfile(config_path): try: with open(config_path, "r+") as f: @@ -178,8 +179,8 @@ class VPCSDevice(object): raise VPCSError("Could not amend the configuration {}: {}".format(config_path, e)) log.info("VPCS {name} [id={id}]: renamed to {new_name}".format(name=self._name, - id=self._id, - new_name=new_name)) + id=self._id, + new_name=new_name)) self._name = new_name @property @@ -190,7 +191,7 @@ class VPCSDevice(object): :returns: path to VPCS """ - return(self._path) + return self._path @path.setter def path(self, path): @@ -202,8 +203,8 @@ class VPCSDevice(object): self._path = path log.info("VPCS {name} [id={id}]: path changed to {path}".format(name=self._name, - id=self._id, - path=path)) + id=self._id, + path=path)) @property def working_dir(self): @@ -234,8 +235,8 @@ class VPCSDevice(object): self._working_dir = working_dir log.info("VPCS {name} [id={id}]: working directory changed to {wd}".format(name=self._name, - id=self._id, - wd=self._working_dir)) + id=self._id, + wd=self._working_dir)) @property def console(self): @@ -262,8 +263,8 @@ class VPCSDevice(object): self._console = console self._allocated_console_ports.append(self._console) log.info("VPCS {name} [id={id}]: console port set to {port}".format(name=self._name, - id=self._id, - port=console)) + id=self._id, + port=console)) def command(self): """ @@ -286,7 +287,7 @@ class VPCSDevice(object): self._allocated_console_ports.remove(self.console) log.info("VPCS device {name} [id={id}] has been deleted".format(name=self._name, - id=self._id)) + id=self._id)) def clean_delete(self): """ @@ -331,10 +332,10 @@ class VPCSDevice(object): raise VPCSError("No path to a VPCS executable has been set") if not os.path.isfile(self._path): - raise VPCSError("VPCS '{}' is not accessible".format(self._path)) + raise VPCSError("VPCS program '{}' is not accessible".format(self._path)) if not os.access(self._path, os.X_OK): - raise VPCSError("VPCS '{}' is not executable".format(self._path)) + raise VPCSError("VPCS program '{}' is not executable".format(self._path)) if not self._ethernet_adapter.get_nio(0): raise VPCSError("This VPCS instance must be connected in order to start") @@ -400,7 +401,7 @@ class VPCSDevice(object): :returns: True or False """ - if self._process and self._process.poll() == None: + if self._process and self._process.poll() is None: return True return False @@ -414,7 +415,7 @@ class VPCSDevice(object): if not self._ethernet_adapter.port_exists(port_id): raise VPCSError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter, - port_id=port_id)) + port_id=port_id)) self._ethernet_adapter.add_nio(port_id, nio) log.info("VPCS {name} [id={id}]: {nio} added to port {port_id}".format(name=self._name, @@ -517,7 +518,7 @@ class VPCSDevice(object): """ Sets the script-file for this VPCS instance. - :param base_script_file: path to base-script-file + :param script_file: path to base-script-file """ self._script_file = script_file diff --git a/requirements.txt b/requirements.txt index 6fb17eeaa..ae4f8b0a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +netifaces tornado pyzmq netifaces-py3 diff --git a/setup.py b/setup.py index ffba880a4..9008d47a0 100644 --- a/setup.py +++ b/setup.py @@ -48,12 +48,12 @@ setup( "tornado>=3.1", "pyzmq>=14.0.0", "jsonschema==2.3.0" - ], + ], entry_points={ "console_scripts": [ "gns3server = gns3server.main:main", - ] - }, + ] + }, packages=find_packages(), package_data={"gns3server": ["templates/upload.html"]}, include_package_data=True, @@ -71,5 +71,5 @@ setup( "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", - ], + ], ) diff --git a/tests/test_jsonrpc.py b/tests/test_jsonrpc.py index 67bea4a96..155502c56 100644 --- a/tests/test_jsonrpc.py +++ b/tests/test_jsonrpc.py @@ -40,7 +40,7 @@ class JSONRPC(AsyncTestCase): AsyncWSRequest(self.URL, self.io_loop, self.stop, json_encode(request)) response = self.wait() json_response = json_decode(response) - assert json_response["id"] == None + assert json_response["id"] is None assert json_response["error"].get("code") == -32600 def test_request_with_invalid_json(self): @@ -49,7 +49,7 @@ class JSONRPC(AsyncTestCase): AsyncWSRequest(self.URL, self.io_loop, self.stop, request) response = self.wait() json_response = json_decode(response) - assert json_response["id"] == None + assert json_response["id"] is None assert json_response["error"].get("code") == -32700 def test_request_with_invalid_jsonrpc_field(self): @@ -58,7 +58,7 @@ class JSONRPC(AsyncTestCase): AsyncWSRequest(self.URL, self.io_loop, self.stop, json_encode(request)) response = self.wait() json_response = json_decode(response) - assert json_response["id"] == None + assert json_response["id"] is None assert json_response["error"].get("code") == -32700 def test_request_with_no_params(self): diff --git a/tests/test_version_handler.py b/tests/test_version_handler.py index feeef3d37..0c8c75d96 100644 --- a/tests/test_version_handler.py +++ b/tests/test_version_handler.py @@ -34,7 +34,7 @@ class TestVersionHandler(AsyncHTTPTestCase): self.http_client.fetch(self.get_url(self.URL), self.stop) response = self.wait() - assert(response.headers['Content-Type'].startswith('application/json')) - assert(response.body) + assert response.headers['Content-Type'].startswith('application/json') + assert response.body body = json_decode(response.body) assert body['version'] == __version__ From a0a5705fd83a58b02411c458d6f9671a6e5b843a Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 29 May 2014 01:30:18 -0600 Subject: [PATCH 34/46] Fixes issue with Frozen server and templates directory. --- gns3server/server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gns3server/server.py b/gns3server/server.py index d468a931a..60d2b998e 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -32,6 +32,7 @@ import socket import tornado.ioloop import tornado.web import tornado.autoreload +import pkg_resources from pkg_resources import parse_version from .config import Config @@ -143,8 +144,12 @@ class Server(object): router = self._create_zmq_router() # Add our JSON-RPC Websocket handler to Tornado self.handlers.extend([(r"/", JSONRPCWebSocket, dict(zmq_router=router))]) + if hasattr(sys, "frozen"): + templates_dir = "templates" + else: + templates_dir = pkg_resources.resource_filename("gns3server", "templates") tornado_app = tornado.web.Application(self.handlers, - template_path=os.path.join(os.path.dirname(__file__), "templates"), + template_path=templates_dir, debug=True) # FIXME: debug mode! try: From e817c137381e5cb5cbaff2aae70e52c42ca9f14c Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 29 May 2014 03:10:45 -0600 Subject: [PATCH 35/46] Catch BlockingIOError in ioucon. --- gns3server/modules/iou/ioucon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gns3server/modules/iou/ioucon.py b/gns3server/modules/iou/ioucon.py index c3d046361..f00a60fba 100644 --- a/gns3server/modules/iou/ioucon.py +++ b/gns3server/modules/iou/ioucon.py @@ -379,7 +379,10 @@ class IOU(Router): return buf def write(self, buf): - self.fd.send(buf) + try: + self.fd.send(buf) + except BlockingIOError: + return def _open(self): self.fd = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) From 61ef750da38827baee6faef0d6f9c47c1755d4f4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 29 May 2014 12:59:13 -0600 Subject: [PATCH 36/46] Replace decode errors when reading device configs. --- gns3server/modules/dynamips/__init__.py | 2 +- gns3server/modules/dynamips/hypervisor.py | 2 +- gns3server/modules/dynamips/nodes/router.py | 4 ++-- gns3server/modules/iou/__init__.py | 2 +- gns3server/modules/iou/iou_device.py | 6 +++--- gns3server/modules/vpcs/__init__.py | 2 +- gns3server/modules/vpcs/vpcs_device.py | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 3e278c252..b2667f933 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -474,7 +474,7 @@ class Dynamips(IModule): raise DynamipsError("Could not create configs directory: {}".format(e)) try: - with open(local_base_config, "r") as f: + with open(local_base_config, "r", errors="replace") as f: config = f.read() with open(config_path, "w") as f: config = "!\n" + config.replace("\r", "") diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index e1cc2e291..20e49741a 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -251,7 +251,7 @@ class Hypervisor(DynamipsHypervisor): output = "" if self._stdout_file and os.access(self._stdout_file, os.R_OK): try: - with open(self._stdout_file) as file: + with open(self._stdout_file, errors="replace") as file: output = file.read() except OSError as e: log.warn("could not read {}: {}".format(self._stdout_file, e)) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 7bfe7e52c..bfdc9c28b 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -223,7 +223,7 @@ class Router(object): startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) if os.path.isfile(startup_config_path): try: - with open(startup_config_path, "r+") as f: + with open(startup_config_path, "r+", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self.name, new_name) f.seek(0) @@ -239,7 +239,7 @@ class Router(object): private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name)) if os.path.isfile(private_config_path): try: - with open(private_config_path, "r+") as f: + with open(private_config_path, "r+", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self.name, new_name) f.seek(0) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 86a51d604..74a3a4442 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -421,7 +421,7 @@ class IOU(IModule): if os.path.isfile(request["startup_config"]) and request["startup_config"] != config_path: # this is a local file set in the GUI try: - with open(request["startup_config"], "r") as f: + with open(request["startup_config"], "r", errors="replace") as f: config = f.read() with open(config_path, "w") as f: config = "!\n" + config.replace("\r", "") diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 8233240a6..42a5cd6ca 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -188,7 +188,7 @@ class IOUDevice(object): config_path = os.path.join(self._working_dir, "startup-config") if os.path.isfile(config_path): try: - with open(config_path, "r+") as f: + with open(config_path, "r+", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self._name, new_name) f.seek(0) @@ -611,7 +611,7 @@ class IOUDevice(object): output = "" if self._iou_stdout_file: try: - with open(self._iou_stdout_file) as file: + with open(self._iou_stdout_file, errors="replace") as file: output = file.read() except OSError as e: log.warn("could not read {}: {}".format(self._iou_stdout_file, e)) @@ -626,7 +626,7 @@ class IOUDevice(object): output = "" if self._iouyap_stdout_file: try: - with open(self._iouyap_stdout_file) as file: + with open(self._iouyap_stdout_file, errors="replace") as file: output = file.read() except OSError as e: log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e)) diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 9c566ea16..7140d5928 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -336,7 +336,7 @@ class VPCS(IModule): if os.path.isfile(request["script_file"]) and request["script_file"] != config_path: # this is a local file set in the GUI try: - with open(request["script_file"], "r") as f: + with open(request["script_file"], "r", errors="replace") as f: config = f.read() with open(config_path, "w") as f: config = config.replace("\r", "") diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index eb639954a..5cd09489c 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -170,7 +170,7 @@ class VPCSDevice(object): config_path = os.path.join(self._working_dir, "startup.vpc") if os.path.isfile(config_path): try: - with open(config_path, "r+") as f: + with open(config_path, "r+", errors="replace") as f: old_config = f.read() new_config = old_config.replace(self._name, new_name) f.seek(0) @@ -388,7 +388,7 @@ class VPCSDevice(object): output = "" if self._vpcs_stdout_file: try: - with open(self._vpcs_stdout_file) as file: + with open(self._vpcs_stdout_file, errors="replace") as file: output = file.read() except OSError as e: log.warn("could not read {}: {}".format(self._vpcs_stdout_file, e)) From e5f5228329c01b52f1edec1fd84b5e514ccbad7b Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 30 May 2014 13:49:52 -0600 Subject: [PATCH 37/46] Bump to version 1.0-alpha6. --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index 68bdcc6bd..c5727705e 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a6.dev3" +__version__ = "1.0a6" __version_info__ = (1, 0, 0, -99) From 3a57539f774be4f63510efcc95b4793b54426258 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 31 May 2014 10:51:19 -0600 Subject: [PATCH 38/46] Bump to version alpha7.dev1 --- gns3server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/version.py b/gns3server/version.py index c5727705e..a40a2cfaa 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a6" +__version__ = "1.0a7.dev1" __version_info__ = (1, 0, 0, -99) From 9ef715e3416e3df16753c857a37494816b2ba261 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 31 May 2014 14:43:07 -0600 Subject: [PATCH 39/46] Update README. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b99cc4f39..7dd682c41 100644 --- a/README.rst +++ b/README.rst @@ -34,4 +34,4 @@ Please use our all-in-one installer. Mac OS X -------- -DMG package is not available yet. +Please use our DMG package. From f9ee38dd5593da70c2dbc58a70b12a3588491cba Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 10 Jun 2014 09:33:27 -0600 Subject: [PATCH 40/46] Fixes issues to restore the correct working directories for IOU and VPCS devices when loading a project. Prevent multiple clients to use the same server (this is not supported yet). --- gns3server/handlers/jsonrpc_websocket.py | 8 +++++ gns3server/modules/dynamips/nodes/router.py | 2 +- gns3server/modules/iou/__init__.py | 33 +-------------------- gns3server/modules/iou/iou_device.py | 26 +++++++++++----- gns3server/modules/vpcs/__init__.py | 6 ---- gns3server/modules/vpcs/vpcs_device.py | 32 ++++++++++++-------- 6 files changed, 49 insertions(+), 58 deletions(-) diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py index fdab3cadc..d1db0e144 100644 --- a/gns3server/handlers/jsonrpc_websocket.py +++ b/gns3server/handlers/jsonrpc_websocket.py @@ -27,6 +27,7 @@ from ..jsonrpc import JSONRPCParseError from ..jsonrpc import JSONRPCInvalidRequest from ..jsonrpc import JSONRPCMethodNotFound from ..jsonrpc import JSONRPCNotification +from ..jsonrpc import JSONRPCCustomError import logging log = logging.getLogger(__name__) @@ -142,6 +143,13 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): if jsonrpc_version != self.version: return self.write_message(JSONRPCInvalidRequest()()) + if len(self.clients) > 1: + #TODO: multiple client support + log.warn("GNS3 server doesn't support multiple clients yet") + return self.write_message(JSONRPCCustomError(-3200, + "There are {} clients connected, the GNS3 server cannot handle multiple clients yet".format(len(self.clients)), + request_id)()) + if method not in self.destinations: if request_id: return self.write_message(JSONRPCMethodNotFound(request_id)()) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index bfdc9c28b..525c4e36c 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -364,7 +364,7 @@ class Router(object): # IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1 if elf_header_start != b'\x7fELF\x01\x02\x01': - raise DynamipsError("'{}' is not a valid IOU image".format(self._image)) + raise DynamipsError("'{}' is not a valid IOS image".format(self._image)) self._hypervisor.send("vm start {}".format(self._name)) log.info("router {name} [id={id}] has been started".format(name=self._name, id=self._id)) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 74a3a4442..13a3e2528 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -263,30 +263,6 @@ class IOU(IModule): log.debug("received request {}".format(request)) - def test_result(self, message, result="error"): - """ - """ - - return {"result": result, "message": message} - - @IModule.route("iou.test_settings") - def test_settings(self, request): - """ - """ - - response = [] - - # test iourc - if self._iourc == "": - response.append(self.test_result("No iourc file has been added")) - elif not os.path.isfile(self._iourc): - response.append(self.test_result("iourc file {} is not accessible".format(self._iourc))) - else: - #TODO: check hostname + license inside the file - pass - - self.send_response(response) - @IModule.route("iou.create") def iou_create(self, request): """ @@ -312,17 +288,10 @@ class IOU(IModule): return name = request["name"] - console = request.get("console") iou_path = request["path"] + console = request.get("console") try: - try: - os.makedirs(self._working_dir) - except FileExistsError: - pass - except OSError as e: - raise IOUError("Could not create working directory {}".format(e)) - iou_instance = IOUDevice(name, iou_path, self._working_dir, diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 42a5cd6ca..f465fdfad 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -83,7 +83,7 @@ class IOUDevice(object): self._iourc = "" self._iouyap = "" self._console = console - self._working_dir = working_dir + self._working_dir = None self._command = [] self._process = None self._iouyap_process = None @@ -106,8 +106,8 @@ class IOUDevice(object): self._ram = 256 # Megabytes self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). - # update the working directory - self.working_dir = working_dir + # create the device own working directory + self.working_dir = os.path.join(working_dir, "iou", "{}".format(self._name)) if not self._console: # allocate a console port @@ -183,6 +183,18 @@ class IOUDevice(object): :param new_name: name """ + if self._started: + raise IOUError("Cannot change the name to {} while the device is running".format(new_name)) + + new_working_dir = os.path.join(os.path.dirname(self._working_dir), new_name) + try: + shutil.move(self._working_dir, new_working_dir) + self._working_dir = new_working_dir + except OSError as e: + raise IOUError("Could not move working directory from {} to {}: {}".format(self._working_dir, + new_working_dir, + e)) + if self._startup_config: # update the startup-config config_path = os.path.join(self._working_dir, "startup-config") @@ -288,8 +300,6 @@ class IOUDevice(object): :param working_dir: path to the working directory """ - # create our own working directory - working_dir = os.path.join(working_dir, "iou", "device-{}".format(self._id)) try: os.makedirs(working_dir) except FileExistsError: @@ -345,7 +355,8 @@ class IOUDevice(object): """ self.stop() - self._instances.remove(self._id) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) @@ -359,7 +370,8 @@ class IOUDevice(object): """ self.stop() - self._instances.remove(self._id) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 7140d5928..4a0f1a4b2 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -228,12 +228,6 @@ class VPCS(IModule): console = request.get("console") try: - try: - os.makedirs(self._working_dir) - except FileExistsError: - pass - except OSError as e: - raise VPCSError("Could not create working directory {}".format(e)) if not self._vpcs: raise VPCSError("No path to a VPCS executable has been set") diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 5cd09489c..09152328a 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -74,14 +74,10 @@ class VPCSDevice(object): if self._id == 0: raise VPCSError("Maximum number of VPCS instances reached") - if name: - self._name = name - else: - self._name = "VPCS{}".format(self._id) - + self._name = name self._path = path self._console = console - self._working_dir = working_dir + self._working_dir = None self._host = host self._command = [] self._process = None @@ -95,8 +91,8 @@ class VPCSDevice(object): self._script_file = "" self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface - # update the working directory - self.working_dir = working_dir + # create the device own working directory + self.working_dir = os.path.join(working_dir, "vpcs", "{}".format(name)) if not self._console: # allocate a console port @@ -165,6 +161,18 @@ class VPCSDevice(object): :param new_name: name """ + if self._started: + raise VPCSError("Cannot change the name to {} while the device is running".format(new_name)) + + new_working_dir = os.path.join(os.path.dirname(self._working_dir), new_name) + try: + shutil.move(self._working_dir, new_working_dir) + self._working_dir = new_working_dir + except OSError as e: + raise VPCSError("Could not move working directory from {} to {}: {}".format(self._working_dir, + new_working_dir, + e)) + if self._script_file: # update the startup.vpc config_path = os.path.join(self._working_dir, "startup.vpc") @@ -224,8 +232,6 @@ class VPCSDevice(object): :param working_dir: path to the working directory """ - # create our own working directory - working_dir = os.path.join(working_dir, "vpcs", "pc-{}".format(self._id)) try: os.makedirs(working_dir) except FileExistsError: @@ -281,7 +287,8 @@ class VPCSDevice(object): """ self.stop() - self._instances.remove(self._id) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) @@ -295,7 +302,8 @@ class VPCSDevice(object): """ self.stop() - self._instances.remove(self._id) + if self._id in self._instances: + self._instances.remove(self._id) if self.console: self._allocated_console_ports.remove(self.console) From cb763e0926fc495cfc7ba6550a5f83cbb9756978 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 15 Jun 2014 05:18:33 -0600 Subject: [PATCH 41/46] Use Dynamips, IOU and VPCS identifiers to correctly load a topology. --- gns3server/handlers/jsonrpc_websocket.py | 6 +++- gns3server/modules/dynamips/backends/vm.py | 9 +++-- gns3server/modules/dynamips/nodes/c1700.py | 5 +-- gns3server/modules/dynamips/nodes/c2600.py | 5 +-- gns3server/modules/dynamips/nodes/c2691.py | 5 +-- gns3server/modules/dynamips/nodes/c3600.py | 5 +-- gns3server/modules/dynamips/nodes/c3725.py | 5 +-- gns3server/modules/dynamips/nodes/c3745.py | 5 +-- gns3server/modules/dynamips/nodes/c7200.py | 5 +-- gns3server/modules/dynamips/nodes/router.py | 27 ++++++++------ gns3server/modules/dynamips/schemas/vm.py | 4 +++ gns3server/modules/iou/__init__.py | 2 ++ gns3server/modules/iou/iou_device.py | 35 ++++++++++++------ gns3server/modules/iou/schemas.py | 4 +++ gns3server/modules/vpcs/__init__.py | 2 ++ gns3server/modules/vpcs/schemas.py | 4 +++ gns3server/modules/vpcs/vpcs_device.py | 40 ++++++++++++++------- gns3server/version.py | 2 +- 18 files changed, 115 insertions(+), 55 deletions(-) diff --git a/gns3server/handlers/jsonrpc_websocket.py b/gns3server/handlers/jsonrpc_websocket.py index d1db0e144..677ebe2d7 100644 --- a/gns3server/handlers/jsonrpc_websocket.py +++ b/gns3server/handlers/jsonrpc_websocket.py @@ -176,7 +176,11 @@ class JSONRPCWebSocket(tornado.websocket.WebSocketHandler): Invoked when the WebSocket is closed. """ - log.info("Websocket client {} disconnected".format(self.session_id)) + try: + log.info("Websocket client {} disconnected".format(self.session_id)) + except RuntimeError: + # to ignore logging exception: RuntimeError: reentrant call inside <_io.BufferedWriter name=''> + pass self.clients.remove(self) # Reset the modules if there are no clients anymore diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 56274d9f0..c757c7b4f 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -132,9 +132,8 @@ class VM(object): image = request["image"] ram = request["ram"] hypervisor = None - chassis = None - if "chassis" in request: - chassis = request["chassis"] + chassis = request.get("chassis") + router_id = request.get("router_id") try: @@ -147,9 +146,9 @@ class VM(object): hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram) if chassis: - router = PLATFORMS[platform](hypervisor, name, chassis=chassis) + router = PLATFORMS[platform](hypervisor, name, router_id, chassis=chassis) else: - router = PLATFORMS[platform](hypervisor, name) + router = PLATFORMS[platform](hypervisor, name, router_id) router.ram = ram router.image = image router.sparsemem = self._hypervisor_manager.sparse_memory_support diff --git a/gns3server/modules/dynamips/nodes/c1700.py b/gns3server/modules/dynamips/nodes/c1700.py index 09d75d316..0d59f616e 100644 --- a/gns3server/modules/dynamips/nodes/c1700.py +++ b/gns3server/modules/dynamips/nodes/c1700.py @@ -34,13 +34,14 @@ class C1700(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID :param chassis: chassis for this router: 1720, 1721, 1750, 1751 or 1760 (default = 1720). 1710 is not supported. """ - def __init__(self, hypervisor, name, chassis="1720"): - Router.__init__(self, hypervisor, name, platform="c1700") + def __init__(self, hypervisor, name, router_id=None, chassis="1720"): + Router.__init__(self, hypervisor, name, router_id, platform="c1700") # Set default values for this platform self._ram = 64 diff --git a/gns3server/modules/dynamips/nodes/c2600.py b/gns3server/modules/dynamips/nodes/c2600.py index ff0fb0c02..155fbf2f3 100644 --- a/gns3server/modules/dynamips/nodes/c2600.py +++ b/gns3server/modules/dynamips/nodes/c2600.py @@ -36,6 +36,7 @@ class C2600(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID :param chassis: chassis for this router: 2610, 2611, 2620, 2621, 2610XM, 2611XM 2620XM, 2621XM, 2650XM or 2651XM (default = 2610). @@ -54,8 +55,8 @@ class C2600(Router): "2650XM": C2600_MB_1FE, "2651XM": C2600_MB_2FE} - def __init__(self, hypervisor, name, chassis="2610"): - Router.__init__(self, hypervisor, name, platform="c2600") + def __init__(self, hypervisor, name, router_id=None, chassis="2610"): + Router.__init__(self, hypervisor, name, router_id, platform="c2600") # Set default values for this platform self._ram = 64 diff --git a/gns3server/modules/dynamips/nodes/c2691.py b/gns3server/modules/dynamips/nodes/c2691.py index baec82dee..339fada94 100644 --- a/gns3server/modules/dynamips/nodes/c2691.py +++ b/gns3server/modules/dynamips/nodes/c2691.py @@ -33,10 +33,11 @@ class C2691(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID """ - def __init__(self, hypervisor, name): - Router.__init__(self, hypervisor, name, platform="c2691") + def __init__(self, hypervisor, name, router_id=None): + Router.__init__(self, hypervisor, name, router_id, platform="c2691") # Set default values for this platform self._ram = 128 diff --git a/gns3server/modules/dynamips/nodes/c3600.py b/gns3server/modules/dynamips/nodes/c3600.py index fd9790d4a..b0117a163 100644 --- a/gns3server/modules/dynamips/nodes/c3600.py +++ b/gns3server/modules/dynamips/nodes/c3600.py @@ -33,12 +33,13 @@ class C3600(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID :param chassis: chassis for this router: 3620, 3640 or 3660 (default = 3640). """ - def __init__(self, hypervisor, name, chassis="3640"): - Router.__init__(self, hypervisor, name, platform="c3600") + def __init__(self, hypervisor, name, router_id=None, chassis="3640"): + Router.__init__(self, hypervisor, name, router_id, platform="c3600") # Set default values for this platform self._ram = 128 diff --git a/gns3server/modules/dynamips/nodes/c3725.py b/gns3server/modules/dynamips/nodes/c3725.py index 455575ce4..9317a393d 100644 --- a/gns3server/modules/dynamips/nodes/c3725.py +++ b/gns3server/modules/dynamips/nodes/c3725.py @@ -33,10 +33,11 @@ class C3725(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID """ - def __init__(self, hypervisor, name): - Router.__init__(self, hypervisor, name, platform="c3725") + def __init__(self, hypervisor, name, router_id=None): + Router.__init__(self, hypervisor, name, router_id, platform="c3725") # Set default values for this platform self._ram = 128 diff --git a/gns3server/modules/dynamips/nodes/c3745.py b/gns3server/modules/dynamips/nodes/c3745.py index 5c914fee7..f8392cc3b 100644 --- a/gns3server/modules/dynamips/nodes/c3745.py +++ b/gns3server/modules/dynamips/nodes/c3745.py @@ -33,10 +33,11 @@ class C3745(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID """ - def __init__(self, hypervisor, name): - Router.__init__(self, hypervisor, name, platform="c3745") + def __init__(self, hypervisor, name, router_id=None): + Router.__init__(self, hypervisor, name, router_id, platform="c3745") # Set default values for this platform self._ram = 128 diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index ce63e7261..0dd7127b6 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -35,11 +35,12 @@ class C7200(Router): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID :param npe: default NPE """ - def __init__(self, hypervisor, name, npe="npe-400"): - Router.__init__(self, hypervisor, name, platform="c7200") + def __init__(self, hypervisor, name, router_id=None, npe="npe-400"): + Router.__init__(self, hypervisor, name, router_id, platform="c7200") # Set default values for this platform self._ram = 256 diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 525c4e36c..174a7136b 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -37,6 +37,7 @@ class Router(object): :param hypervisor: Dynamips hypervisor instance :param name: name for this router + :param router_id: router instance ID :param platform: c7200, c3745, c3725, c3600, c2691, c2600 or c1700 :param ghost_flag: used when creating a ghost IOS. """ @@ -49,20 +50,26 @@ class Router(object): 2: "running", 3: "suspended"} - def __init__(self, hypervisor, name, platform="c7200", ghost_flag=False): + def __init__(self, hypervisor, name, router_id=None, platform="c7200", ghost_flag=False): if not ghost_flag: - # find an instance identifier (0 < id <= 4096) - self._id = 0 - for identifier in range(1, 4097): - if identifier not in self._instances: - self._id = identifier - self._instances.append(self._id) - break + if not router_id: + # find an instance identifier if none is provided (0 < id <= 4096) + self._id = 0 + for identifier in range(1, 4097): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - if self._id == 0: - raise DynamipsError("Maximum number of instances reached") + if self._id == 0: + raise DynamipsError("Maximum number of instances reached") + else: + if router_id in self._instances: + raise DynamipsError("Router identifier {} is already used by another router".format(router_id)) + self._id = router_id + self._instances.append(self._id) else: log.info("creating a new ghost IOS file") diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 3a7d9af58..3d4d10785 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -25,6 +25,10 @@ VM_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "router_id": { + "description": "VM/router instance ID", + "type": "integer" + }, "platform": { "description": "router platform", "type": "string", diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 13a3e2528..f579b92c6 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -290,12 +290,14 @@ class IOU(IModule): name = request["name"] iou_path = request["path"] console = request.get("console") + iou_id = request.get("iou_id") try: iou_instance = IOUDevice(name, iou_path, self._working_dir, self._host, + iou_id, console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index f465fdfad..96a5d207e 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -46,10 +46,11 @@ class IOUDevice(object): """ IOU device implementation. + :param name: name of this IOU device :param path: path to IOU executable :param working_dir: path to a working directory :param host: host/address to bind for console and UDP connections - :param name: name of this IOU device + :param iou_id: IOU instance ID :param console: TCP console port :param console_start_port_range: TCP console port range start :param console_end_port_range: TCP console port range end @@ -63,20 +64,27 @@ class IOUDevice(object): path, working_dir, host="127.0.0.1", + iou_id = None, console=None, console_start_port_range=4001, console_end_port_range=4512): - # find an instance identifier (0 < id <= 512) - self._id = 0 - for identifier in range(1, 513): - if identifier not in self._instances: - self._id = identifier - self._instances.append(self._id) - break + if not iou_id: + # find an instance identifier if none is provided (0 < id <= 512) + self._id = 0 + for identifier in range(1, 513): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break - if self._id == 0: - raise IOUError("Maximum number of IOU instances reached") + if self._id == 0: + raise IOUError("Maximum number of IOU instances reached") + else: + if iou_id in self._instances: + raise IOUError("IOU identifier {} is already used by another IOU device".format(iou_id)) + self._id = iou_id + self._instances.append(self._id) self._name = name self._path = path @@ -106,8 +114,13 @@ class IOUDevice(object): self._ram = 256 # Megabytes self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). + working_dir_path = os.path.join(working_dir, "iou", "device-{}".format(self._id)) + + if iou_id and not os.path.isdir(working_dir_path): + raise IOUError("Working directory {} doesn't exist".format(working_dir_path)) + # create the device own working directory - self.working_dir = os.path.join(working_dir, "iou", "{}".format(self._name)) + self.working_dir = working_dir_path if not self._console: # allocate a console port diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 1d7ed5544..1ee41107a 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -26,6 +26,10 @@ IOU_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "iou_id": { + "description": "IOU device instance ID", + "type": "integer" + }, "console": { "description": "console TCP port", "minimum": 1, diff --git a/gns3server/modules/vpcs/__init__.py b/gns3server/modules/vpcs/__init__.py index 4a0f1a4b2..c7493d4d8 100644 --- a/gns3server/modules/vpcs/__init__.py +++ b/gns3server/modules/vpcs/__init__.py @@ -226,6 +226,7 @@ class VPCS(IModule): name = request["name"] console = request.get("console") + vpcs_id = request.get("vpcs_id") try: @@ -236,6 +237,7 @@ class VPCS(IModule): self._vpcs, self._working_dir, self._host, + vpcs_id, console, self._console_start_port_range, self._console_end_port_range) diff --git a/gns3server/modules/vpcs/schemas.py b/gns3server/modules/vpcs/schemas.py index 7258bda2c..d7ca8b871 100644 --- a/gns3server/modules/vpcs/schemas.py +++ b/gns3server/modules/vpcs/schemas.py @@ -26,6 +26,10 @@ VPCS_CREATE_SCHEMA = { "type": "string", "minLength": 1, }, + "vpcs_id": { + "description": "VPCS device instance ID", + "type": "integer" + }, "console": { "description": "console TCP port", "minimum": 1, diff --git a/gns3server/modules/vpcs/vpcs_device.py b/gns3server/modules/vpcs/vpcs_device.py index 09152328a..cb3c4f061 100644 --- a/gns3server/modules/vpcs/vpcs_device.py +++ b/gns3server/modules/vpcs/vpcs_device.py @@ -40,10 +40,11 @@ class VPCSDevice(object): """ VPCS device implementation. + :param name: name of this VPCS device :param path: path to VPCS executable :param working_dir: path to a working directory :param host: host/address to bind for console and UDP connections - :param name: name of this VPCS device + :param vpcs_id: VPCS instance ID :param console: TCP console port :param console_start_port_range: TCP console port range start :param console_end_port_range: TCP console port range end @@ -57,22 +58,30 @@ class VPCSDevice(object): path, working_dir, host="127.0.0.1", + vpcs_id=None, console=None, console_start_port_range=4512, console_end_port_range=5000): - # find an instance identifier (1 <= id <= 255) - # This 255 limit is due to a restriction on the number of possible - # MAC addresses given in VPCS using the -m option - self._id = 0 - for identifier in range(1, 256): - if identifier not in self._instances: - self._id = identifier - self._instances.append(self._id) - break - if self._id == 0: - raise VPCSError("Maximum number of VPCS instances reached") + if not vpcs_id: + # find an instance identifier is none is provided (1 <= id <= 255) + # This 255 limit is due to a restriction on the number of possible + # MAC addresses given in VPCS using the -m option + self._id = 0 + for identifier in range(1, 256): + if identifier not in self._instances: + self._id = identifier + self._instances.append(self._id) + break + + if self._id == 0: + raise VPCSError("Maximum number of VPCS instances reached") + else: + if vpcs_id in self._instances: + raise VPCSError("VPCS identifier {} is already used by another VPCS device".format(vpcs_id)) + self._id = vpcs_id + self._instances.append(self._id) self._name = name self._path = path @@ -91,8 +100,13 @@ class VPCSDevice(object): self._script_file = "" self._ethernet_adapter = EthernetAdapter() # one adapter with 1 Ethernet interface + working_dir_path = os.path.join(working_dir, "vpcs", "pc-{}".format(self._id)) + + if vpcs_id and not os.path.isdir(working_dir_path): + raise VPCSError("Working directory {} doesn't exist".format(working_dir_path)) + # create the device own working directory - self.working_dir = os.path.join(working_dir, "vpcs", "{}".format(name)) + self.working_dir = working_dir_path if not self._console: # allocate a console port diff --git a/gns3server/version.py b/gns3server/version.py index a40a2cfaa..beca4a33f 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a7.dev1" +__version__ = "1.0a7.dev2" __version_info__ = (1, 0, 0, -99) From 587ddf764668e12e29df56b141438c20bef10344 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 18 Jun 2014 06:08:00 -0600 Subject: [PATCH 42/46] IOU: rename startup-config to initial-config because it makes more sense. --- gns3server/modules/iou/__init__.py | 28 ++++++++++---------- gns3server/modules/iou/iou_device.py | 38 ++++++++++++++-------------- gns3server/modules/iou/schemas.py | 8 +++--- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index f579b92c6..7bdc74943 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -356,7 +356,7 @@ class IOU(IModule): Optional request parameters: - any setting to update - - startup_config_base64 (startup-config base64 encoded) + - initial_config_base64 (initial-config base64 encoded) Response parameters: - updated settings @@ -373,36 +373,36 @@ class IOU(IModule): if not iou_instance: return - config_path = os.path.join(iou_instance.working_dir, "startup-config") + config_path = os.path.join(iou_instance.working_dir, "initial-config") try: - if "startup_config_base64" in request: - # a new startup-config has been pushed - config = base64.decodebytes(request["startup_config_base64"].encode("utf-8")).decode("utf-8") + if "initial_config_base64" in request: + # a new initial-config has been pushed + config = base64.decodebytes(request["initial_config_base64"].encode("utf-8")).decode("utf-8") config = "!\n" + config.replace("\r", "") config = config.replace('%h', iou_instance.name) try: with open(config_path, "w") as f: - log.info("saving startup-config to {}".format(config_path)) + log.info("saving initial-config to {}".format(config_path)) f.write(config) except OSError as e: raise IOUError("Could not save the configuration {}: {}".format(config_path, e)) - # update the request with the new local startup-config path - request["startup_config"] = os.path.basename(config_path) - elif "startup_config" in request: - if os.path.isfile(request["startup_config"]) and request["startup_config"] != config_path: + # update the request with the new local initial-config path + request["initial_config"] = os.path.basename(config_path) + elif "initial_config" in request: + if os.path.isfile(request["initial_config"]) and request["initial_config"] != config_path: # this is a local file set in the GUI try: - with open(request["startup_config"], "r", errors="replace") as f: + with open(request["initial_config"], "r", errors="replace") as f: config = f.read() with open(config_path, "w") as f: config = "!\n" + config.replace("\r", "") config = config.replace('%h', iou_instance.name) f.write(config) - request["startup_config"] = os.path.basename(config_path) + request["initial_config"] = os.path.basename(config_path) except OSError as e: - raise IOUError("Could not save the configuration from {} to {}: {}".format(request["startup_config"], config_path, e)) + raise IOUError("Could not save the configuration from {} to {}: {}".format(request["initial_config"], config_path, e)) elif not os.path.isfile(config_path): - raise IOUError("Startup-config {} could not be found on this server".format(request["startup_config"])) + raise IOUError("Startup-config {} could not be found on this server".format(request["initial_config"])) except IOUError as e: self.send_custom_error(str(e)) return diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index 96a5d207e..ca2f229f0 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -110,7 +110,7 @@ class IOUDevice(object): self._slots = self._ethernet_adapters + self._serial_adapters self._use_default_iou_values = True # for RAM & NVRAM values self._nvram = 128 # Kilobytes - self._startup_config = "" + self._initial_config = "" self._ram = 256 # Megabytes self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). @@ -148,7 +148,7 @@ class IOUDevice(object): iou_defaults = {"name": self._name, "path": self._path, - "startup_config": self._startup_config, + "intial_config": self._initial_config, "use_default_iou_values": self._use_default_iou_values, "ram": self._ram, "nvram": self._nvram, @@ -208,9 +208,9 @@ class IOUDevice(object): new_working_dir, e)) - if self._startup_config: - # update the startup-config - config_path = os.path.join(self._working_dir, "startup-config") + if self._intial_config: + # update the initial-config + config_path = os.path.join(self._working_dir, "initial-config") if os.path.isfile(config_path): try: with open(config_path, "r+", errors="replace") as f: @@ -379,7 +379,7 @@ class IOUDevice(object): def clean_delete(self): """ - Deletes this IOU device & all files (nvram, startup-config etc.) + Deletes this IOU device & all files (nvram, initial-config etc.) """ self.stop() @@ -799,8 +799,8 @@ class IOUDevice(object): command.extend(["-n", str(self._nvram)]) command.extend(["-m", str(self._ram)]) command.extend(["-L"]) # disable local console, use remote console - if self._startup_config: - command.extend(["-c", self._startup_config]) + if self._initial_config: + command.extend(["-c", self._initial_config]) if self._l1_keepalives: self._enable_l1_keepalives(command) command.extend([str(self._id)]) @@ -910,27 +910,27 @@ class IOUDevice(object): self._nvram = nvram @property - def startup_config(self): + def initial_config(self): """ - Returns the startup-config for this IOU instance. + Returns the initial-config for this IOU instance. - :returns: path to startup-config file + :returns: path to initial-config file """ - return self._startup_config + return self._initial_config - @startup_config.setter - def startup_config(self, startup_config): + @initial_config.setter + def initial_config(self, initial_config): """ - Sets the startup-config for this IOU instance. + Sets the initial-config for this IOU instance. - :param startup_config: path to startup-config file + :param initial_config: path to initial-config file """ - self._startup_config = startup_config - log.info("IOU {name} [id={id}]: startup_config set to {config}".format(name=self._name, + self._initial_config = initial_config + log.info("IOU {name} [id={id}]: initial_config set to {config}".format(name=self._name, id=self._id, - config=self._startup_config)) + config=self._initial_config)) @property def ethernet_adapters(self): diff --git a/gns3server/modules/iou/schemas.py b/gns3server/modules/iou/schemas.py index 1ee41107a..b6a33dc7c 100644 --- a/gns3server/modules/iou/schemas.py +++ b/gns3server/modules/iou/schemas.py @@ -79,8 +79,8 @@ IOU_UPDATE_SCHEMA = { "type": "string", "minLength": 1, }, - "startup_config": { - "description": "path to the IOU startup configuration file", + "initial_config": { + "description": "path to the IOU initial configuration file", "type": "string", "minLength": 1, }, @@ -118,8 +118,8 @@ IOU_UPDATE_SCHEMA = { "description": "enable or disable layer 1 keepalive messages", "type": "boolean" }, - "startup_config_base64": { - "description": "startup configuration base64 encoded", + "initial_config_base64": { + "description": "initial configuration base64 encoded", "type": "string" }, }, From 49506ada3fe71ef8ffb6e75ec19c03a11cf782d6 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 18 Jun 2014 07:22:57 -0600 Subject: [PATCH 43/46] Fixes inconsistencies with startup and private config paths when renaming an IOS router. --- gns3server/modules/dynamips/backends/vm.py | 4 ++-- gns3server/modules/dynamips/nodes/router.py | 12 +++--------- gns3server/modules/iou/__init__.py | 2 +- gns3server/modules/iou/iou_device.py | 4 ++-- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index c757c7b4f..57d12a99f 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -387,8 +387,8 @@ class VM(object): response = {} try: - startup_config_path = os.path.join(router.hypervisor.working_dir, "configs", "{}.cfg".format(router.name)) - private_config_path = os.path.join(router.hypervisor.working_dir, "configs", "{}-private.cfg".format(router.name)) + startup_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(router.id)) + private_config_path = os.path.join(router.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(router.id)) # a new startup-config has been pushed if "startup_config_base64" in request: diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 174a7136b..6b8f9228b 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -227,7 +227,7 @@ class Router(object): if self._startup_config: # change the hostname in the startup-config - startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}.cfg".format(self.name)) + startup_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_startup-config.cfg".format(self.id)) if os.path.isfile(startup_config_path): try: with open(startup_config_path, "r+", errors="replace") as f: @@ -235,15 +235,12 @@ class Router(object): new_config = old_config.replace(self.name, new_name) f.seek(0) f.write(new_config) - new_startup_config_path = os.path.join(os.path.dirname(startup_config_path), "{}.cfg".format(new_name)) - os.rename(startup_config_path, new_startup_config_path) except OSError as e: raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e)) - self.set_config(new_startup_config_path) if self._private_config: - # change the hostname in the startup-config - private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "{}-private.cfg".format(self.name)) + # change the hostname in the private-config + private_config_path = os.path.join(self.hypervisor.working_dir, "configs", "i{}_private-config.cfg".format(self.id)) if os.path.isfile(private_config_path): try: with open(private_config_path, "r+", errors="replace") as f: @@ -251,11 +248,8 @@ class Router(object): new_config = old_config.replace(self.name, new_name) f.seek(0) f.write(new_config) - new_private_config_path = os.path.join(os.path.dirname(private_config_path), "{}-private.cfg".format(new_name)) - os.rename(private_config_path, new_private_config_path) except OSError as e: raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e)) - self.set_config(self.startup_config, new_private_config_path) new_name = '"' + new_name + '"' # put the new name into quotes to protect spaces self._hypervisor.send("vm rename {name} {new_name}".format(name=self._name, diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 7bdc74943..b42c80306 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -373,7 +373,7 @@ class IOU(IModule): if not iou_instance: return - config_path = os.path.join(iou_instance.working_dir, "initial-config") + config_path = os.path.join(iou_instance.working_dir, "initial-config.cfg") try: if "initial_config_base64" in request: # a new initial-config has been pushed diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index ca2f229f0..44cf08314 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -208,9 +208,9 @@ class IOUDevice(object): new_working_dir, e)) - if self._intial_config: + if self._initial_config: # update the initial-config - config_path = os.path.join(self._working_dir, "initial-config") + config_path = os.path.join(self._working_dir, "initial-config.cfg") if os.path.isfile(config_path): try: with open(config_path, "r+", errors="replace") as f: From 14bb12d3fbb4fce1f0320354ca2c877c5b52c73a Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 21 Jun 2014 06:53:47 -0600 Subject: [PATCH 44/46] Check for sticky bit when checking for executable access. --- gns3server/main.py | 1 + gns3server/modules/attic.py | 7 ++++++- gns3server/modules/dynamips/__init__.py | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gns3server/main.py b/gns3server/main.py index f51730fff..77d416c5a 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -70,6 +70,7 @@ def locale_check(): locale.setlocale(locale.LC_ALL, (language, "UTF-8")) except locale.Error as e: log.error("could not set an UTF-8 encoding for the {} locale: {}".format(language, e)) + raise SystemExit else: log.info("current locale is {}.{}".format(language, encoding)) diff --git a/gns3server/modules/attic.py b/gns3server/modules/attic.py index 8b4a0714d..8c1fae638 100644 --- a/gns3server/modules/attic.py +++ b/gns3server/modules/attic.py @@ -23,6 +23,7 @@ import sys import os import struct import socket +import stat import errno import time @@ -120,8 +121,12 @@ def has_privileged_access(executable): :returns: True or False """ - # we are root, so we should have privileged access too if os.geteuid() == 0: + # we are root, so we should have privileged access. + return True + + if not sys.platform.startswith("win") and os.stat(executable).st_mode & stat.S_ISVTX == stat.S_ISVTX: + # the executable has a sticky bit. return True # test if the executable has the CAP_NET_RAW capability (Linux only) diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index b2667f933..d6a94d033 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -31,6 +31,7 @@ from gns3server.modules import IModule from .hypervisor import Hypervisor from .hypervisor_manager import HypervisorManager from .dynamips_error import DynamipsError +from ..attic import has_privileged_access # Nodes from .nodes.router import Router @@ -378,12 +379,18 @@ class Dynamips(IModule): nio.connect(rhost, rport) elif request["nio"]["type"] == "nio_generic_ethernet": ethernet_device = request["nio"]["ethernet_device"] + if not has_privileged_access(self._dynamips): + raise DynamipsError("{} has no privileged access to {}.".format(self._dynamips, ethernet_device)) nio = NIO_GenericEthernet(node.hypervisor, ethernet_device) elif request["nio"]["type"] == "nio_linux_ethernet": ethernet_device = request["nio"]["ethernet_device"] + if not has_privileged_access(self._dynamips): + raise DynamipsError("{} has no privileged access to {}.".format(self._dynamips, ethernet_device)) nio = NIO_LinuxEthernet(node.hypervisor, ethernet_device) elif request["nio"]["type"] == "nio_tap": tap_device = request["nio"]["tap_device"] + if not has_privileged_access(self._dynamips): + raise DynamipsError("{} has no privileged access to {}.".format(self._dynamips, tap_device)) nio = NIO_TAP(node.hypervisor, tap_device) elif request["nio"]["type"] == "nio_unix": local_file = request["nio"]["local_file"] From 606f773f3db344fc24093ae79f10562a4f56091e Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 26 Jun 2014 03:06:58 -0600 Subject: [PATCH 45/46] New feature: packet capture for IOS routers. --- gns3server/modules/dynamips/backends/vm.py | 86 +++++++++++++++++++++ gns3server/modules/dynamips/nios/nio.py | 4 +- gns3server/modules/dynamips/nodes/router.py | 65 ++++++++++++++++ gns3server/modules/dynamips/schemas/vm.py | 70 +++++++++++++++++ gns3server/version.py | 2 +- 5 files changed, 225 insertions(+), 2 deletions(-) diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 57d12a99f..04e5fb75a 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -56,6 +56,8 @@ from ..schemas.vm import VM_STOP_SCHEMA from ..schemas.vm import VM_SUSPEND_SCHEMA from ..schemas.vm import VM_RELOAD_SCHEMA from ..schemas.vm import VM_UPDATE_SCHEMA +from ..schemas.vm import VM_START_CAPTURE_SCHEMA +from ..schemas.vm import VM_STOP_CAPTURE_SCHEMA from ..schemas.vm import VM_SAVE_CONFIG_SCHEMA from ..schemas.vm import VM_IDLEPCS_SCHEMA from ..schemas.vm import VM_ALLOCATE_UDP_PORT_SCHEMA @@ -484,6 +486,90 @@ class VM(object): self.send_response(response) + @IModule.route("dynamips.vm.start_capture") + def vm_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - slot (slot number) + - port (port number) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, VM_START_CAPTURE_SCHEMA): + return + + # get the router instance + router = self.get_device_instance(request["id"], self._routers) + if not router: + return + + slot = request["slot"] + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(router.hypervisor.working_dir, "captures", capture_file_name) + router.start_capture(slot, port, capture_file_path, data_link_type) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("dynamips.vm.stop_capture") + def vm_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - slot (slot number) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, VM_STOP_CAPTURE_SCHEMA): + return + + # get the router instance + router = self.get_device_instance(request["id"], self._routers) + if not router: + return + + slot = request["slot"] + port = request["port"] + try: + router.stop_capture(slot, port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) + @IModule.route("dynamips.vm.save_config") def vm_save_config(self, request): """ diff --git a/gns3server/modules/dynamips/nios/nio.py b/gns3server/modules/dynamips/nios/nio.py index 04af1380b..1fd61bf9e 100644 --- a/gns3server/modules/dynamips/nios/nio.py +++ b/gns3server/modules/dynamips/nios/nio.py @@ -57,6 +57,8 @@ class NIO(object): Deletes this NIO. """ + if self._input_filter or self._output_filter: + self.unbind_filter("both") self._hypervisor.send("nio delete {}".format(self._name)) log.info("NIO {name} has been deleted".format(name=self._name)) @@ -134,7 +136,7 @@ class NIO(object): def setup_filter(self, direction, options): """ - Setups a packet filter binded with this NIO. + Setups a packet filter bound with this NIO. Filter "freq_drop" has 1 argument "". It will drop everything with a -1 frequency, drop every Nth packet with a diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 6b8f9228b..7fc15e5ab 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1539,6 +1539,71 @@ class Router(object): slot_id=slot_id, port_id=port_id)) + def start_capture(self, slot_id, port_id, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param slot_id: slot ID + :param port_id: port ID + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, + slot_id=slot_id)) + if not adapter.port_exists(port_id): + raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + nio = adapter.get_nio(port_id) + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {port_id} has already a filter applied on {adapter}".format(adapter=adapter, + port_id=port_id)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("router {name} [id={id}]: capturing on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) + + def stop_capture(self, slot_id, port_id): + """ + Stops a packet capture. + + :param slot_id: slot ID + :param port_id: port ID + """ + + try: + adapter = self._slots[slot_id] + except IndexError: + raise DynamipsError("Slot {slot_id} doesn't exist on router {name}".format(name=self._name, + slot_id=slot_id)) + if not adapter.port_exists(port_id): + raise DynamipsError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter, + port_id=port_id)) + + nio = adapter.get_nio(port_id) + nio.unbind_filter("both") + def _create_slots(self, numslots): """ Creates the appropriate number of slots for this router. diff --git a/gns3server/modules/dynamips/schemas/vm.py b/gns3server/modules/dynamips/schemas/vm.py index 3d4d10785..241e059d5 100644 --- a/gns3server/modules/dynamips/schemas/vm.py +++ b/gns3server/modules/dynamips/schemas/vm.py @@ -372,6 +372,76 @@ VM_UPDATE_SCHEMA = { "required": ["id"] } +VM_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on a VM instance port", + "type": "object", + "properties": { + "id": { + "description": "VM instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the VM instance", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 49 # maximum is 16 for regular port numbers, WICs port numbers start at 16, 32 or 48 + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "slot", "port", "capture_file_name"] +} + +VM_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on a VM instance port", + "type": "object", + "properties": { + "id": { + "description": "VM instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the VM instance", + "type": "integer" + }, + "slot": { + "description": "Slot number", + "type": "integer", + "minimum": 0, + "maximum": 6 + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 0, + "maximum": 49 # maximum is 16 for regular port numbers, WICs port numbers start at 16, 32 or 48 + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "slot", "port"] +} + VM_SAVE_CONFIG_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to save the configs for VM instance", diff --git a/gns3server/version.py b/gns3server/version.py index beca4a33f..c15d4ef7e 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -23,5 +23,5 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "1.0a7.dev2" +__version__ = "1.0a7.dev3" __version_info__ = (1, 0, 0, -99) From 33787d486ae532acfd941e87c00648ab3482e554 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 27 Jun 2014 07:26:47 -0600 Subject: [PATCH 46/46] New feature: packet capture for the Ethernet hub and Ethernet, ATM and Frame relay switches. --- gns3server/modules/dynamips/backends/atmsw.py | 83 ++++++++++++++++++ .../modules/dynamips/backends/ethhub.py | 85 ++++++++++++++++++- gns3server/modules/dynamips/backends/ethsw.py | 83 ++++++++++++++++++ gns3server/modules/dynamips/backends/frsw.py | 83 ++++++++++++++++++ .../modules/dynamips/nodes/atm_switch.py | 52 ++++++++++++ .../modules/dynamips/nodes/ethernet_switch.py | 52 ++++++++++++ .../dynamips/nodes/frame_relay_switch.py | 52 ++++++++++++ gns3server/modules/dynamips/nodes/hub.py | 52 ++++++++++++ gns3server/modules/dynamips/nodes/router.py | 16 ++-- gns3server/modules/dynamips/schemas/atmsw.py | 56 ++++++++++++ gns3server/modules/dynamips/schemas/ethhub.py | 56 ++++++++++++ gns3server/modules/dynamips/schemas/ethsw.py | 56 ++++++++++++ gns3server/modules/dynamips/schemas/frsw.py | 56 ++++++++++++ 13 files changed, 776 insertions(+), 6 deletions(-) diff --git a/gns3server/modules/dynamips/backends/atmsw.py b/gns3server/modules/dynamips/backends/atmsw.py index 5f4ab494c..2ce0410b2 100644 --- a/gns3server/modules/dynamips/backends/atmsw.py +++ b/gns3server/modules/dynamips/backends/atmsw.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import re +import os from gns3server.modules import IModule from ..nodes.atm_switch import ATMSwitch from ..dynamips_error import DynamipsError @@ -26,6 +27,8 @@ from ..schemas.atmsw import ATMSW_UPDATE_SCHEMA from ..schemas.atmsw import ATMSW_ALLOCATE_UDP_PORT_SCHEMA from ..schemas.atmsw import ATMSW_ADD_NIO_SCHEMA from ..schemas.atmsw import ATMSW_DELETE_NIO_SCHEMA +from ..schemas.atmsw import ATMSW_START_CAPTURE_SCHEMA +from ..schemas.atmsw import ATMSW_STOP_CAPTURE_SCHEMA import logging log = logging.getLogger(__name__) @@ -310,3 +313,83 @@ class ATMSW(object): return self.send_response(True) + + @IModule.route("dynamips.atmsw.start_capture") + def atmsw_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port (port identifier) + - port_id (port identifier) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ATMSW_START_CAPTURE_SCHEMA): + return + + # get the ATM switch instance + atmsw = self.get_device_instance(request["id"], self._atm_switches) + if not atmsw: + return + + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(atmsw.hypervisor.working_dir, "captures", capture_file_name) + atmsw.start_capture(port, capture_file_path, data_link_type) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("dynamips.atmsw.stop_capture") + def atmsw_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ATMSW_STOP_CAPTURE_SCHEMA): + return + + # get the ATM switch instance + atmsw = self.get_device_instance(request["id"], self._atm_switches) + if not atmsw: + return + + port = request["port"] + try: + atmsw.stop_capture(port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) diff --git a/gns3server/modules/dynamips/backends/ethhub.py b/gns3server/modules/dynamips/backends/ethhub.py index c09703c2e..97c9df7fa 100644 --- a/gns3server/modules/dynamips/backends/ethhub.py +++ b/gns3server/modules/dynamips/backends/ethhub.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os from gns3server.modules import IModule from ..nodes.hub import Hub from ..dynamips_error import DynamipsError @@ -25,6 +26,8 @@ from ..schemas.ethhub import ETHHUB_UPDATE_SCHEMA from ..schemas.ethhub import ETHHUB_ALLOCATE_UDP_PORT_SCHEMA from ..schemas.ethhub import ETHHUB_ADD_NIO_SCHEMA from ..schemas.ethhub import ETHHUB_DELETE_NIO_SCHEMA +from ..schemas.ethhub import ETHHUB_START_CAPTURE_SCHEMA +from ..schemas.ethhub import ETHHUB_STOP_CAPTURE_SCHEMA import logging log = logging.getLogger(__name__) @@ -236,7 +239,7 @@ class ETHHUB(object): self.send_response({"port_id": request["port_id"]}) @IModule.route("dynamips.ethhub.delete_nio") - def ethsw_delete_nio(self, request): + def ethhub_delete_nio(self, request): """ Deletes an NIO (Network Input/Output). @@ -268,3 +271,83 @@ class ETHHUB(object): return self.send_response(True) + + @IModule.route("dynamips.ethhub.start_capture") + def ethhub_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port (port identifier) + - port_id (port identifier) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ETHHUB_START_CAPTURE_SCHEMA): + return + + # get the Ethernet hub instance + ethhub = self.get_device_instance(request["id"], self._ethernet_hubs) + if not ethhub: + return + + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(ethhub.hypervisor.working_dir, "captures", capture_file_name) + ethhub.start_capture(port, capture_file_path, data_link_type) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("dynamips.ethhub.stop_capture") + def ethhub_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ETHHUB_STOP_CAPTURE_SCHEMA): + return + + # get the Ethernet hub instance + ethhub = self.get_device_instance(request["id"], self._ethernet_hubs) + if not ethhub: + return + + port = request["port"] + try: + ethhub.stop_capture(port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) diff --git a/gns3server/modules/dynamips/backends/ethsw.py b/gns3server/modules/dynamips/backends/ethsw.py index a59ec4b7e..e251e158b 100644 --- a/gns3server/modules/dynamips/backends/ethsw.py +++ b/gns3server/modules/dynamips/backends/ethsw.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os from gns3server.modules import IModule from ..nodes.ethernet_switch import EthernetSwitch from ..dynamips_error import DynamipsError @@ -25,6 +26,8 @@ from ..schemas.ethsw import ETHSW_UPDATE_SCHEMA from ..schemas.ethsw import ETHSW_ALLOCATE_UDP_PORT_SCHEMA from ..schemas.ethsw import ETHSW_ADD_NIO_SCHEMA from ..schemas.ethsw import ETHSW_DELETE_NIO_SCHEMA +from ..schemas.ethsw import ETHSW_START_CAPTURE_SCHEMA +from ..schemas.ethsw import ETHSW_STOP_CAPTURE_SCHEMA import logging log = logging.getLogger(__name__) @@ -297,3 +300,83 @@ class ETHSW(object): return self.send_response(True) + + @IModule.route("dynamips.ethsw.start_capture") + def ethsw_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port (port identifier) + - port_id (port identifier) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ETHSW_START_CAPTURE_SCHEMA): + return + + # get the Ethernet switch instance + ethsw = self.get_device_instance(request["id"], self._ethernet_switches) + if not ethsw: + return + + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(ethsw.hypervisor.working_dir, "captures", capture_file_name) + ethsw.start_capture(port, capture_file_path, data_link_type) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("dynamips.ethsw.stop_capture") + def ethsw_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, ETHSW_STOP_CAPTURE_SCHEMA): + return + + # get the Ethernet switch instance + ethsw = self.get_device_instance(request["id"], self._ethernet_switches) + if not ethsw: + return + + port = request["port"] + try: + ethsw.stop_capture(port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) diff --git a/gns3server/modules/dynamips/backends/frsw.py b/gns3server/modules/dynamips/backends/frsw.py index cae6923f4..ed63f5017 100644 --- a/gns3server/modules/dynamips/backends/frsw.py +++ b/gns3server/modules/dynamips/backends/frsw.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os from gns3server.modules import IModule from ..nodes.frame_relay_switch import FrameRelaySwitch from ..dynamips_error import DynamipsError @@ -25,6 +26,8 @@ from ..schemas.frsw import FRSW_UPDATE_SCHEMA from ..schemas.frsw import FRSW_ALLOCATE_UDP_PORT_SCHEMA from ..schemas.frsw import FRSW_ADD_NIO_SCHEMA from ..schemas.frsw import FRSW_DELETE_NIO_SCHEMA +from ..schemas.frsw import FRSW_START_CAPTURE_SCHEMA +from ..schemas.frsw import FRSW_STOP_CAPTURE_SCHEMA import logging log = logging.getLogger(__name__) @@ -289,3 +292,83 @@ class FRSW(object): return self.send_response(True) + + @IModule.route("dynamips.frsw.start_capture") + def frsw_start_capture(self, request): + """ + Starts a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port (port identifier) + - port_id (port identifier) + - capture_file_name + + Optional request parameters: + - data_link_type (PCAP DLT_* value) + + Response parameters: + - port_id (port identifier) + - capture_file_path (path to the capture file) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, FRSW_START_CAPTURE_SCHEMA): + return + + # get the Frame relay switch instance + frsw = self.get_device_instance(request["id"], self._frame_relay_switches) + if not frsw: + return + + port = request["port"] + capture_file_name = request["capture_file_name"] + data_link_type = request.get("data_link_type") + + try: + capture_file_path = os.path.join(frsw.hypervisor.working_dir, "captures", capture_file_name) + frsw.start_capture(port, capture_file_path, data_link_type) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"], + "capture_file_path": capture_file_path} + self.send_response(response) + + @IModule.route("dynamips.frsw.stop_capture") + def frsw_stop_capture(self, request): + """ + Stops a packet capture. + + Mandatory request parameters: + - id (vm identifier) + - port_id (port identifier) + - port (port number) + + Response parameters: + - port_id (port identifier) + + :param request: JSON request + """ + + # validate the request + if not self.validate_request(request, FRSW_STOP_CAPTURE_SCHEMA): + return + + # get the Frame relay switch instance + frsw = self.get_device_instance(request["id"], self._frame_relay_switches) + if not frsw: + return + + port = request["port"] + try: + frsw.stop_capture(port) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + + response = {"port_id": request["port_id"]} + self.send_response(response) diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index 00fb967ce..0c382c441 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -20,6 +20,7 @@ Interface for Dynamips virtual ATM switch module ("atmsw"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593 """ +import os from ..dynamips_error import DynamipsError import logging @@ -351,3 +352,54 @@ class ATMSwitch(object): vpi2=vpi2, vci2=vci2)) del self._mapping[(port1, vpi1, vci1)] + + def start_capture(self, port, output_file, data_link_type="DLT_ATM_RFC1483"): + """ + Starts a packet capture. + + :param port: allocated port + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_ATM_RFC1483 + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {} has already a filter applied".format(port)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("ATM switch {name} [id={id}]: starting packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) + + def stop_capture(self, port): + """ + Stops a packet capture. + + :param port: allocated port + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + nio.unbind_filter("both") + log.info("ATM switch {name} [id={id}]: stopping packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index 9363bafbb..45cc25c02 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -20,6 +20,7 @@ Interface for Dynamips virtual Ethernet switch module ("ethsw"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558 """ +import os from ..dynamips_error import DynamipsError import logging @@ -287,3 +288,54 @@ class EthernetSwitch(object): """ self._hypervisor.send("ethsw clear_mac_addr_table {}".format(self._name)) + + def start_capture(self, port, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param port: allocated port + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {} has already a filter applied".format(port)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("Ethernet switch {name} [id={id}]: starting packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) + + def stop_capture(self, port): + """ + Stops a packet capture. + + :param port: allocated port + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + nio.unbind_filter("both") + log.info("Ethernet switch {name} [id={id}]: stopping packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index e096c1376..0b44fbeaa 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -20,6 +20,7 @@ Interface for Dynamips virtual Frame-Relay switch module. http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642 """ +import os from ..dynamips_error import DynamipsError import logging @@ -273,3 +274,54 @@ class FrameRelaySwitch(object): port2=port2, dlci2=dlci2)) del self._mapping[(port1, dlci1)] + + def start_capture(self, port, output_file, data_link_type="DLT_FRELAY"): + """ + Starts a packet capture. + + :param port: allocated port + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_FRELAY + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {} has already a filter applied".format(port)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("Frame relay switch {name} [id={id}]: starting packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) + + def stop_capture(self, port): + """ + Stops a packet capture. + + :param port: allocated port + """ + + if port not in self._nios: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._nios[port] + nio.unbind_filter("both") + log.info("Frame relay switch {name} [id={id}]: stopping packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) diff --git a/gns3server/modules/dynamips/nodes/hub.py b/gns3server/modules/dynamips/nodes/hub.py index b32ff9443..6f7f0e592 100644 --- a/gns3server/modules/dynamips/nodes/hub.py +++ b/gns3server/modules/dynamips/nodes/hub.py @@ -19,6 +19,7 @@ Hub object that uses the Bridge interface to create a hub with ports. """ +import os from .bridge import Bridge from ..dynamips_error import DynamipsError @@ -134,3 +135,54 @@ class Hub(Bridge): del self._mapping[port] return nio + + def start_capture(self, port, output_file, data_link_type="DLT_EN10MB"): + """ + Starts a packet capture. + + :param port: allocated port + :param output_file: PCAP destination file for the capture + :param data_link_type: PCAP data link type (DLT_*), default is DLT_EN10MB + """ + + if port not in self._mapping: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._mapping[port] + + data_link_type = data_link_type.lower() + if data_link_type.startswith("dlt_"): + data_link_type = data_link_type[4:] + + if nio.input_filter[0] is not None and nio.output_filter[0] is not None: + raise DynamipsError("Port {} has already a filter applied".format(port)) + + try: + os.makedirs(os.path.dirname(output_file)) + except FileExistsError: + pass + except OSError as e: + raise DynamipsError("Could not create captures directory {}".format(e)) + + nio.bind_filter("both", "capture") + nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) + + log.info("Ethernet hub {name} [id={id}]: starting packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) + + def stop_capture(self, port): + """ + Stops a packet capture. + + :param port: allocated port + """ + + if port not in self._mapping: + raise DynamipsError("Port {} is not allocated".format(port)) + + nio = self._mapping[port] + nio.unbind_filter("both") + log.info("Ethernet hub {name} [id={id}]: stopping packet capture on {port}".format(name=self._name, + id=self._id, + port=port)) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 7fc15e5ab..9c08ec778 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1578,11 +1578,11 @@ class Router(object): nio.bind_filter("both", "capture") nio.setup_filter("both", "{} {}".format(data_link_type, output_file)) - log.info("router {name} [id={id}]: capturing on port {slot_id}/{port_id}".format(name=self._name, - id=self._id, - nio_name=nio.name, - slot_id=slot_id, - port_id=port_id)) + log.info("router {name} [id={id}]: starting packet capture on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) def stop_capture(self, slot_id, port_id): """ @@ -1604,6 +1604,12 @@ class Router(object): nio = adapter.get_nio(port_id) nio.unbind_filter("both") + log.info("router {name} [id={id}]: stopping packet capture on port {slot_id}/{port_id}".format(name=self._name, + id=self._id, + nio_name=nio.name, + slot_id=slot_id, + port_id=port_id)) + def _create_slots(self, numslots): """ Creates the appropriate number of slots for this router. diff --git a/gns3server/modules/dynamips/schemas/atmsw.py b/gns3server/modules/dynamips/schemas/atmsw.py index cddea592d..376694783 100644 --- a/gns3server/modules/dynamips/schemas/atmsw.py +++ b/gns3server/modules/dynamips/schemas/atmsw.py @@ -264,3 +264,59 @@ ATMSW_DELETE_NIO_SCHEMA = { "additionalProperties": False, "required": ["id", "port"] } + +ATMSW_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on an ATM switch instance port", + "type": "object", + "properties": { + "id": { + "description": "ATM switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the ATM switch instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port", "capture_file_name"] +} + +ATMSW_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on an ATM switch instance port", + "type": "object", + "properties": { + "id": { + "description": "ATM switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the ATM switch instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port"] +} diff --git a/gns3server/modules/dynamips/schemas/ethhub.py b/gns3server/modules/dynamips/schemas/ethhub.py index 50470bccc..1002a696d 100644 --- a/gns3server/modules/dynamips/schemas/ethhub.py +++ b/gns3server/modules/dynamips/schemas/ethhub.py @@ -261,3 +261,59 @@ ETHHUB_DELETE_NIO_SCHEMA = { "additionalProperties": False, "required": ["id", "port"] } + +ETHHUB_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on an Ethernet hub instance port", + "type": "object", + "properties": { + "id": { + "description": "Ethernet hub instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Ethernet hub instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port", "capture_file_name"] +} + +ETHHUB_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on an Ethernet hub instance port", + "type": "object", + "properties": { + "id": { + "description": "Ethernet hub instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Ethernet hub instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port"] +} diff --git a/gns3server/modules/dynamips/schemas/ethsw.py b/gns3server/modules/dynamips/schemas/ethsw.py index 92f47b80a..aeac7023d 100644 --- a/gns3server/modules/dynamips/schemas/ethsw.py +++ b/gns3server/modules/dynamips/schemas/ethsw.py @@ -290,3 +290,59 @@ ETHSW_DELETE_NIO_SCHEMA = { "additionalProperties": False, "required": ["id", "port"] } + +ETHSW_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on an Ethernet switch instance port", + "type": "object", + "properties": { + "id": { + "description": "Ethernet switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Ethernet switch instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port", "capture_file_name"] +} + +ETHSW_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on an Ethernet switch instance port", + "type": "object", + "properties": { + "id": { + "description": "Ethernet switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Ethernet switch instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port"] +} diff --git a/gns3server/modules/dynamips/schemas/frsw.py b/gns3server/modules/dynamips/schemas/frsw.py index b5b6ebdbd..835e47a7b 100644 --- a/gns3server/modules/dynamips/schemas/frsw.py +++ b/gns3server/modules/dynamips/schemas/frsw.py @@ -264,3 +264,59 @@ FRSW_DELETE_NIO_SCHEMA = { "additionalProperties": False, "required": ["id", "port"] } + +FRSW_START_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to start a packet capture on a Frame relay switch instance port", + "type": "object", + "properties": { + "id": { + "description": "Frame relay switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Frame relay instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + "capture_file_name": { + "description": "Capture file name", + "type": "string", + "minLength": 1, + }, + "data_link_type": { + "description": "PCAP data link type", + "type": "string", + "minLength": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port", "capture_file_name"] +} + +FRSW_STOP_CAPTURE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to stop a packet capture on a Frame relay switch instance port", + "type": "object", + "properties": { + "id": { + "description": "Frame relay switch instance ID", + "type": "integer" + }, + "port_id": { + "description": "Unique port identifier for the Frame relay instance", + "type": "integer" + }, + "port": { + "description": "Port number", + "type": "integer", + "minimum": 1, + }, + }, + "additionalProperties": False, + "required": ["id", "port_id", "port"] +}