diff --git a/gns3server/module_manager.py b/gns3server/module_manager.py index 6c7b28a4..876b09a9 100644 --- a/gns3server/module_manager.py +++ b/gns3server/module_manager.py @@ -82,8 +82,8 @@ class ModuleManager(object): log.info("loading {} module".format(module_class[0].lower())) info = Module(name=module_class[0].lower(), cls=module_class[1]) self._modules.append(info) - except: - log.warning("error while analyzing {} package directory".format(name)) + except Exception as e: + log.critical("error while analyzing {} package directory".format(name), exc_info=1) finally: if file: file.close() diff --git a/gns3server/modules/base.py b/gns3server/modules/base.py index 7b4f3976..3f4d04f8 100644 --- a/gns3server/modules/base.py +++ b/gns3server/modules/base.py @@ -93,9 +93,16 @@ class IModule(multiprocessing.Process): stream.on_recv(callback) return stream -# def add_periodic_callback(self, callback, time): -# -# self.test = zmq.eventloop.ioloop.PeriodicCallback(callback, time, self._ioloop).start() + def add_periodic_callback(self, callback, time): + """ + Adds a periodic callback to the ioloop. + + :param callback: callback to be called + :param time: frequency when the callback is executed + """ + + periodic_callback = zmq.eventloop.ioloop.PeriodicCallback(callback, time, self._ioloop) + return periodic_callback def run(self): """ diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index e6c8e8e8..b5599d03 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -19,6 +19,8 @@ Dynamips server module. """ +import os +import tempfile from gns3server.modules import IModule import gns3server.jsonrpc as jsonrpc @@ -99,12 +101,16 @@ class Dynamips(IModule): IModule.__init__(self, name=name, args=args, kwargs=kwargs) self._hypervisor_manager = None + self._remote_server = False self._routers = {} self._ethernet_switches = {} self._frame_relay_switches = {} self._atm_switches = {} self._ethernet_hubs = {} + #self._callback = self.add_periodic_callback(self.test, 1000) + #self._callback.start() + def stop(self): """ Properly stops the module. @@ -148,6 +154,8 @@ class Dynamips(IModule): self._frame_relay_switches.clear() self._atm_switches.clear() + self._hypervisor_manager = None + self._remote_server = False log.info("dynamips module has been reset") @IModule.route("dynamips.settings") @@ -155,6 +163,12 @@ class Dynamips(IModule): """ Set or update settings. + Mandatory request parameters: + - path (path to the Dynamips executable) + + Optional request parameters: + - working_dir (path to a working directory) + :param request: JSON request """ @@ -167,9 +181,32 @@ class Dynamips(IModule): #TODO: JSON schema validation # starts the hypervisor manager if it hasn't been started yet if not self._hypervisor_manager: - #TODO: working dir support - log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format("/tmp")) - self._hypervisor_manager = HypervisorManager(request["path"], "/tmp") + dynamips_path = request["path"] + + if "working_dir" in request: + working_dir = request["working_dir"] + log.info("this server is local") + else: + self._remote_server = True + log.info("this server is remote") + try: + working_dir = tempfile.mkdtemp(prefix="gns3-remote-server-") + working_dir = os.path.join(working_dir, "dynamips") + os.makedirs(working_dir) + log.info("temporary working directory created: {}".format(working_dir)) + except EnvironmentError as e: + raise DynamipsError("Could not create temporary working directory: {}".format(e)) + + #TODO: check if executable + if not os.path.exists(dynamips_path): + raise DynamipsError("Dynamips executable {} doesn't exist".format(working_dir)) + + #TODO: check if writable + if not os.path.exists(working_dir): + raise DynamipsError("Working directory {} doesn't exist".format(working_dir)) + + log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format(working_dir)) + self._hypervisor_manager = HypervisorManager(dynamips_path, working_dir) # apply settings to the hypervisor manager for name, value in request.items(): @@ -305,6 +342,28 @@ class Dynamips(IModule): router.ghost_status = 2 router.ghost_file = ghost_instance +# def get_base64_config(self, config_path, router): +# """ +# Get the base64 encoded config from a file. +# Replaces %h by the router name. +# +# :param config_path: path to the configuration file. +# :param router: Router instance. +# +# :returns: base64 encoded string +# """ +# +# try: +# with open(config_path, "r") as f: +# log.info("opening configuration file: {}".format(config_path)) +# config = f.read() +# config = '!\n' + config.replace('\r', "") +# config = config.replace('%h', router.name) +# encoded = ("").join(base64.encodestring(config.encode("utf-8")).decode("utf-8").split()) +# return encoded +# except EnvironmentError as e: +# raise DynamipsError("Cannot parse {}: {}".format(config_path, e)) + @IModule.route("dynamips.nio.get_interfaces") def nio_get_interfaces(self, request): """ diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 14cfa9fb..8486ea34 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import os +import base64 from gns3server.modules import IModule from ..dynamips_error import DynamipsError @@ -121,8 +122,13 @@ class VM(object): platform = request["platform"] image = request["image"] ram = request["ram"] + hypervisor = None try: + + if not self._hypervisor_manager: + raise DynamipsError("Dynamips manager is not started") + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram) router = PLATFORMS[platform](hypervisor, name) @@ -158,11 +164,14 @@ class VM(object): self.set_ghost_ios(router) except DynamipsError as e: - hypervisor.decrease_memory_load(ram) - if hypervisor.memory_load == 0 and not hypervisor.devices: - hypervisor.stop() - self._hypervisor_manager.hypervisors.remove(hypervisor) - self.send_custom_error(str(e)) + dynamips_stdout = "" + if hypervisor: + hypervisor.decrease_memory_load(ram) + if hypervisor.memory_load == 0 and not hypervisor.devices: + hypervisor.stop() + self._hypervisor_manager.hypervisors.remove(hypervisor) + dynamips_stdout = hypervisor.read_stdout() + self.send_custom_error(str(e) + dynamips_stdout) return response = {"name": router.name, @@ -330,6 +339,7 @@ class VM(object): Optional request parameters: - any setting to update + - startup_config_base64 (startup-config base64 encoded) Response parameters: - same as original request @@ -346,6 +356,32 @@ class VM(object): router_id = request["id"] router = self._routers[router_id] + try: + # a new startup-config has been pushed + if "startup_config_base64" in request: + config = base64.decodestring(request["startup_config_base64"].encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + config = config.replace('%h', router.name) + config_dir = os.path.join(router.hypervisor.working_dir, "configs") + if not os.path.exists(config_dir): + try: + os.makedirs(config_dir) + except EnvironmentError as e: + raise DynamipsError("Could not create configs directory: {}".format(e)) + config_path = os.path.join(config_dir, "{}.cfg".format(router.name)) + try: + with open(config_path, "w") as f: + log.info("saving startup-config to {}".format(config_path)) + f.write(config) + except EnvironmentError as e: + raise DynamipsError("Could not save the configuration {}: {}".format(config_path, e)) + request["startup_config"] = "configs" + os.sep + os.path.basename(config_path) + if "startup_config" in request: + router.set_config(request["startup_config"]) + except DynamipsError as e: + self.send_custom_error(str(e)) + return + # update the settings for name, value in request.items(): if hasattr(router, name) and getattr(router, name) != value: @@ -370,7 +406,7 @@ class VM(object): if router.slots[slot_id]: try: router.slot_remove_binding(slot_id) - except: + except DynamipsError as e: self.send_custom_error(str(e)) return elif name.startswith("wic") and value in WIC_MATRIX: @@ -387,7 +423,11 @@ class VM(object): elif name.startswith("wic") and value == None: wic_slot_id = int(name[-1]) if router.slots[0].wics and router.slots[0].wics[wic_slot_id]: - router.uninstall_wic(wic_slot_id) + try: + router.uninstall_wic(wic_slot_id) + except DynamipsError as e: + self.send_custom_error(str(e)) + return # Update the ghost IOS file in case the RAM size has changed if self._hypervisor_manager.ghost_ios_support: @@ -396,6 +436,40 @@ class VM(object): # for now send back the original request self.send_response(request) + @IModule.route("dynamips.vm.save_config") + def vm_save_config(self, request): + """ + Save the configs for a VM (router). + + Mandatory request parameters: + - id (vm identifier) + """ + + if request == None: + self.send_param_error() + return + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + router_id = request["id"] + router = self._routers[router_id] + try: + if router.startup_config: + #TODO: handle private-config + startup_config_base64, _ = router.extract_config() + if startup_config_base64: + try: + config = base64.decodestring(startup_config_base64.encode("utf-8")).decode("utf-8") + config = "!\n" + config.replace("\r", "") + config_path = os.path.join(router.hypervisor.working_dir, router.startup_config) + with open(config_path, "w") as f: + log.info("saving startup-config to {}".format(router.startup_config)) + f.write(config) + except EnvironmentError as e: + raise DynamipsError("Could not save the configuration {}: {}".format(config_path, e)) + except DynamipsError as e: + log.warn("could not save config to {}: {}".format(router.startup_config, e)) + @IModule.route("dynamips.vm.idlepcs") def vm_idlepcs(self, request): """ diff --git a/gns3server/modules/dynamips/dynamips_hypervisor.py b/gns3server/modules/dynamips/dynamips_hypervisor.py index 8ad17a90..c4d554bd 100644 --- a/gns3server/modules/dynamips/dynamips_hypervisor.py +++ b/gns3server/modules/dynamips/dynamips_hypervisor.py @@ -33,6 +33,7 @@ class DynamipsHypervisor(object): """ Creates a new connection to a Dynamips server (also called hypervisor) + :param working_dir: working directory :param host: the hostname or ip address string of the Dynamips server :param port: the tcp port integer (defaults to 7200) :param timeout: timeout integer for how long to wait for a response to commands sent to the @@ -43,7 +44,7 @@ class DynamipsHypervisor(object): error_re = re.compile(r"""^2[0-9]{2}-""") success_re = re.compile(r"""^1[0-9]{2}\s{1}""") - def __init__(self, host, port=7200, timeout=30.0): + def __init__(self, working_dir, host, port=7200, timeout=30.0): self._host = host self._port = port @@ -51,7 +52,7 @@ class DynamipsHypervisor(object): self._devices = [] self._ghosts = {} self._jitsharing_groups = {} - self._working_dir = "" + self._working_dir = working_dir self._baseconsole = 2000 self._baseaux = 2100 self._baseudp = 10000 @@ -155,6 +156,7 @@ class DynamipsHypervisor(object): # encase working_dir in quotes to protect spaces in the path self.send("hypervisor working_dir {}".format('"' + working_dir + '"')) self._working_dir = working_dir + log.debug("working directory set to {}".format(self._working_dir)) def save_config(self, filename): """ @@ -332,6 +334,40 @@ class DynamipsHypervisor(object): return self._port + @staticmethod + def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP"): + """ + Finds an unused port in a range. + + :param start_port: first port in the range + :param end_port: last port in the range + :param host: host/address for bind() + :param socket_type: TCP (default) or UDP + """ + + if socket_type == "UDP": + socket_type = socket.SOCK_DGRAM + else: + socket_type = socket.SOCK_STREAM + + for port in range(start_port, end_port): + if port > end_port: + raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, end_port)) + try: + if ":" in host: + # IPv6 address support + s = socket.socket(socket.AF_INET6, socket_type) + else: + s = socket.socket(socket.AF_INET, socket_type) + # the port is available if bind is a success + s.bind((host, port)) + return port + except socket.error as e: + if e.errno == errno.EADDRINUSE: # socket already in use + continue + else: + raise DynamipsError("Could not find an unused port: {}".format(e)) + def allocate_udp_port(self, max_port=100): """ Allocates a new UDP port for creating an UDP NIO. @@ -342,28 +378,14 @@ class DynamipsHypervisor(object): :returns: port number (integer) """ - #FIXME: better check for IPv6 start_port = self._current_udp_port end_port = start_port + max_port - for port in range(start_port, end_port): - if port > end_port: - raise DynamipsError("Could not find a free port between {0} and {1}".format(start_port, max_port)) - try: - if self.host.__contains__(':'): - # IPv6 address support - s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - else: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # the port is available if bind is a success - s.bind((self._host, port)) - #FIXME: increment? - self._current_udp_port += 1 - return port - except socket.error as e: - if e.errno == errno.EADDRINUSE: # socket already in use - continue - else: - raise DynamipsError("UDP port allocation: {}".format(e)) + allocated_port = DynamipsHypervisor.find_unused_port(start_port, end_port, self._host, socket_type="UDP") + if allocated_port - self._current_udp_port > 1: + self._current_udp_port += allocated_port - self._current_udp_port + else: + self._current_udp_port += 1 + return allocated_port def send_raw(self, string): """ diff --git a/gns3server/modules/dynamips/hypervisor.py b/gns3server/modules/dynamips/hypervisor.py index 31e86909..98082aa6 100644 --- a/gns3server/modules/dynamips/hypervisor.py +++ b/gns3server/modules/dynamips/hypervisor.py @@ -22,11 +22,12 @@ Represents a Dynamips hypervisor and starts/stops the associated Dynamips proces import os import time import subprocess -import logging from .dynamips_hypervisor import DynamipsHypervisor +from .dynamips_error import DynamipsError -logger = logging.getLogger(__name__) +import logging +log = logging.getLogger(__name__) class Hypervisor(DynamipsHypervisor): @@ -34,26 +35,25 @@ class Hypervisor(DynamipsHypervisor): Hypervisor. :param path: path to Dynamips executable - :param workingdir: working directory + :param working_dir: working directory :param port: port for this hypervisor :param host: host/address for this hypervisor """ _instance_count = 0 - def __init__(self, path, workingdir, host, port): + def __init__(self, path, working_dir, host, port): - DynamipsHypervisor.__init__(self, host, port) + DynamipsHypervisor.__init__(self, working_dir, host, port) # create an unique ID self._id = Hypervisor._instance_count Hypervisor._instance_count += 1 self._path = path - self._workingdir = workingdir self._command = [] self._process = None - self._stdout = None + self._stdout_file = "" # settings used the load-balance hypervisors # (for the hypervisor manager) @@ -130,26 +130,6 @@ class Hypervisor(DynamipsHypervisor): self._host = host - @property - def workingdir(self): - """ - Returns the working directory used to start the Dynamips hypervisor. - - :returns: path to a working directory - """ - - return(self._workingdir) - - @workingdir.setter - def workingdir(self, workingdir): - """ - Sets the working directory used to start the Dynamips hypervisor. - - :param workingdir: path to a working directory - """ - - self._workingdir = workingdir - @property def image_ref(self): """ @@ -210,20 +190,18 @@ class Hypervisor(DynamipsHypervisor): self._command = self._build_command() try: - logger.info("Starting Dynamips: {}".format(self._command)) - # TODO: create unique filename for stdout - self.stdout_file = os.path.join(self._workingdir, "dynamips.log") - fd = open(self.stdout_file, "w") - # TODO: check for exceptions and if process has already been started - self._process = subprocess.Popen(self._command, - stdout=fd, - stderr=subprocess.STDOUT, - cwd=self._workingdir) - logger.info("Dynamips started PID={}".format(self._process.pid)) - except OSError as e: - logger.error("Could not start Dynamips: {}".format(e)) - finally: - fd.close() + log.info("starting Dynamips: {}".format(self._command)) + self._stdout_file = os.path.join(self._working_dir, "dynamips-{}.log".format(self._port)) + log.info("logging to {}".format(self._stdout_file)) + with open(self._stdout_file, "w") as fd: + self._process = subprocess.Popen(self._command, + stdout=fd, + stderr=subprocess.STDOUT, + cwd=self._working_dir) + log.info("Dynamips started PID={}".format(self._process.pid)) + except EnvironmentError as e: + log.error("could not start Dynamips: {}".format(e)) + raise DynamipsError("could not start Dynamips: {}".format(e)) def stop(self): """ @@ -232,7 +210,7 @@ class Hypervisor(DynamipsHypervisor): if self.is_running(): DynamipsHypervisor.stop(self) - logger.info("Stopping Dynamips PID={}".format(self._process.pid)) + log.info("stopping Dynamips PID={}".format(self._process.pid)) # give some time for the hypervisor to properly stop. # time to delete UNIX NIOs for instance. time.sleep(0.01) @@ -245,9 +223,13 @@ class Hypervisor(DynamipsHypervisor): Only use when the process has been stopped or has crashed. """ - # TODO: check for exceptions - with open(self.stdout_file) as file: - output = file.read() + output = "" + if self._stdout_file: + try: + with open(self._stdout_file) as file: + output = file.read() + except EnvironmentError as e: + log.warn("could not read {}: {}".format(self._stdout_file, e)) return output def is_running(self): diff --git a/gns3server/modules/dynamips/hypervisor_manager.py b/gns3server/modules/dynamips/hypervisor_manager.py index 7ed84f68..1a5a7c01 100644 --- a/gns3server/modules/dynamips/hypervisor_manager.py +++ b/gns3server/modules/dynamips/hypervisor_manager.py @@ -19,8 +19,8 @@ Manages Dynamips hypervisors (load-balancing etc.) """ -from __future__ import unicode_literals from .hypervisor import Hypervisor +from .dynamips_error import DynamipsError import socket import time import logging @@ -33,7 +33,7 @@ class HypervisorManager(object): Manages Dynamips hypervisors. :param path: path to the Dynamips executable - :param workingdir: path to a working directory + :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 @@ -43,7 +43,7 @@ class HypervisorManager(object): def __init__(self, path, - workingdir, + working_dir, host='127.0.0.1', base_hypervisor_port=7200, base_console_port=2000, @@ -52,7 +52,7 @@ class HypervisorManager(object): self._hypervisors = [] self._path = path - self._workingdir = workingdir + self._working_dir = working_dir self._host = host self._base_hypervisor_port = base_hypervisor_port self._current_port = self._base_hypervisor_port @@ -108,25 +108,29 @@ class HypervisorManager(object): log.info("Dynamips path set to {}".format(self._path)) @property - def workingdir(self): + def working_dir(self): """ Returns the Dynamips working directory path. :returns: path to Dynamips working directory """ - return self._workingdir + return self._working_dir - @workingdir.setter - def workingdir(self, workingdir): + @working_dir.setter + def working_dir(self, working_dir): """ Sets a new path to the Dynamips working directory. - :param workingdir: path to Dynamips working directory + :param working_dir: path to Dynamips working directory """ - self._workingdir = workingdir - log.info("working directory set to {}".format(self._workingdir)) + self._working_dir = working_dir + log.info("working directory set to {}".format(self._working_dir)) + + # update all existing hypervisors with the new working directory + for hypervisor in self._hypervisors: + hypervisor.working_dir = working_dir @property def base_hypervisor_port(self): @@ -406,16 +410,12 @@ class HypervisorManager(object): # try to connect for 10 seconds while(time.time() - begin < 10.0): time.sleep(0.01) - sock = None try: - sock = socket.create_connection((host, port), timeout) + with socket.create_connection((host, port), timeout): + pass except socket.error as e: last_exception = e - #time.sleep(0.01) continue - finally: - if sock: - sock.close() connection_success = True break @@ -426,6 +426,25 @@ class HypervisorManager(object): else: log.info("Dynamips server ready after {:.4f} seconds".format(time.time() - begin)) + def allocate_tcp_port(self, max_port=100): + """ + Allocates a new TCP port for a Dynamips hypervisor. + + :param max_port: maximum number of port to scan in + order to find one available for use. + + :returns: port number (integer) + """ + + start_port = self._current_port + end_port = start_port + max_port + allocated_port = Hypervisor.find_unused_port(start_port, end_port, self._host) + if allocated_port - self._current_port > 1: + self._current_port += allocated_port - self._current_port + else: + self._current_port += 1 + return allocated_port + def start_new_hypervisor(self): """ Creates a new Dynamips process and start it. @@ -433,15 +452,23 @@ class HypervisorManager(object): :returns: the new hypervisor instance """ + port = self.allocate_tcp_port() +# working_dir = os.path.join(self._working_dir, "instance-{}".format(port)) +# if not os.path.exists(working_dir): +# try: +# os.makedirs(working_dir) +# except EnvironmentError as e: +# raise DynamipsError("{}".format(e)) + hypervisor = Hypervisor(self._path, - self._workingdir, + self._working_dir, self._host, - self._current_port) + port) log.info("creating new hypervisor {}:{}".format(hypervisor.host, hypervisor.port)) hypervisor.start() - self.wait_for_hypervisor(self._host, self._current_port) + self.wait_for_hypervisor(self._host, port) log.info("hypervisor {}:{} has successfully started".format(hypervisor.host, hypervisor.port)) hypervisor.connect() @@ -450,7 +477,6 @@ class HypervisorManager(object): hypervisor.baseudp = self._current_base_udp_port self._current_base_udp_port += self._udp_incrementation_per_hypervisor self._hypervisors.append(hypervisor) - self._current_port += 1 return hypervisor def allocate_hypervisor_for_router(self, router_ios_image, router_ram): diff --git a/gns3server/modules/dynamips/nodes/atm_bridge.py b/gns3server/modules/dynamips/nodes/atm_bridge.py index c98975f8..036cfb5d 100644 --- a/gns3server/modules/dynamips/nodes/atm_bridge.py +++ b/gns3server/modules/dynamips/nodes/atm_bridge.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM bridge module ("atm_bridge"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L622 """ -from __future__ import unicode_literals from ..dynamips_error import DynamipsError diff --git a/gns3server/modules/dynamips/nodes/atm_switch.py b/gns3server/modules/dynamips/nodes/atm_switch.py index d4431a46..eecd9544 100644 --- a/gns3server/modules/dynamips/nodes/atm_switch.py +++ b/gns3server/modules/dynamips/nodes/atm_switch.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual ATM switch module ("atmsw"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L593 """ -from __future__ import unicode_literals from ..dynamips_error import DynamipsError import logging diff --git a/gns3server/modules/dynamips/nodes/bridge.py b/gns3server/modules/dynamips/nodes/bridge.py index 1a4ae08d..a47bf654 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 __future__ import unicode_literals - class Bridge(object): """ @@ -36,6 +34,7 @@ class Bridge(object): 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) diff --git a/gns3server/modules/dynamips/nodes/c1700.py b/gns3server/modules/dynamips/nodes/c1700.py index 0943a9c2..51318790 100644 --- a/gns3server/modules/dynamips/nodes/c1700.py +++ b/gns3server/modules/dynamips/nodes/c1700.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 1700 instances module ("c1700") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L428 """ -from __future__ import unicode_literals from .router import Router from ..adapters.c1700_mb_1fe import C1700_MB_1FE from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1 diff --git a/gns3server/modules/dynamips/nodes/c2600.py b/gns3server/modules/dynamips/nodes/c2600.py index bc961d2c..e7f5b61a 100644 --- a/gns3server/modules/dynamips/nodes/c2600.py +++ b/gns3server/modules/dynamips/nodes/c2600.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2600 instances module ("c2600") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L404 """ -from __future__ import unicode_literals from .router import Router from ..adapters.c2600_mb_1e import C2600_MB_1E from ..adapters.c2600_mb_2e import C2600_MB_2E diff --git a/gns3server/modules/dynamips/nodes/c2691.py b/gns3server/modules/dynamips/nodes/c2691.py index f846f1ac..9ba7e396 100644 --- a/gns3server/modules/dynamips/nodes/c2691.py +++ b/gns3server/modules/dynamips/nodes/c2691.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 2691 instances module ("c2691") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L387 """ -from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE diff --git a/gns3server/modules/dynamips/nodes/c3600.py b/gns3server/modules/dynamips/nodes/c3600.py index 62230829..ccbd565c 100644 --- a/gns3server/modules/dynamips/nodes/c3600.py +++ b/gns3server/modules/dynamips/nodes/c3600.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3600 instances module ("c3600") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L366 """ -from __future__ import unicode_literals from .router import Router from ..adapters.leopard_2fe import Leopard_2FE diff --git a/gns3server/modules/dynamips/nodes/c3725.py b/gns3server/modules/dynamips/nodes/c3725.py index d1bb39dc..d32fab1b 100644 --- a/gns3server/modules/dynamips/nodes/c3725.py +++ b/gns3server/modules/dynamips/nodes/c3725.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3725 instances module ("c3725") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L346 """ -from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE diff --git a/gns3server/modules/dynamips/nodes/c3745.py b/gns3server/modules/dynamips/nodes/c3745.py index db6d69bb..5c0ea13c 100644 --- a/gns3server/modules/dynamips/nodes/c3745.py +++ b/gns3server/modules/dynamips/nodes/c3745.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 3745 instances module ("c3745") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L326 """ -from __future__ import unicode_literals from .router import Router from ..adapters.gt96100_fe import GT96100_FE diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index 7164e3b5..0d73d3fc 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Cisco 7200 instances module ("c7200") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L294 """ -from __future__ import unicode_literals from ..dynamips_error import DynamipsError from .router import Router from ..adapters.c7200_io_2fe import C7200_IO_2FE diff --git a/gns3server/modules/dynamips/nodes/ethernet_switch.py b/gns3server/modules/dynamips/nodes/ethernet_switch.py index 3b7379bf..83ddf080 100644 --- a/gns3server/modules/dynamips/nodes/ethernet_switch.py +++ b/gns3server/modules/dynamips/nodes/ethernet_switch.py @@ -20,8 +20,6 @@ Interface for Dynamips virtual Ethernet switch module ("ethsw"). http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558 """ - -from __future__ import unicode_literals from ..dynamips_error import DynamipsError import logging diff --git a/gns3server/modules/dynamips/nodes/frame_relay_switch.py b/gns3server/modules/dynamips/nodes/frame_relay_switch.py index 0c25c060..8a1eec31 100644 --- a/gns3server/modules/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/modules/dynamips/nodes/frame_relay_switch.py @@ -20,7 +20,6 @@ Interface for Dynamips virtual Frame-Relay switch module. http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L642 """ -from __future__ import unicode_literals from ..dynamips_error import DynamipsError import logging diff --git a/gns3server/modules/dynamips/nodes/hub.py b/gns3server/modules/dynamips/nodes/hub.py index 2d52fa7f..b66a5488 100644 --- a/gns3server/modules/dynamips/nodes/hub.py +++ b/gns3server/modules/dynamips/nodes/hub.py @@ -19,7 +19,6 @@ Hub object that uses the Bridge interface to create a hub with ports. """ -from __future__ import unicode_literals from .bridge import Bridge from ..dynamips_error import DynamipsError @@ -53,7 +52,6 @@ class Hub(Bridge): break name_id += 1 - self._allocated_names.append(name) self._mapping = {} Bridge.__init__(self, hypervisor, name) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 7c5216ac..586144a4 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -20,7 +20,7 @@ Interface for Dynamips virtual Machine module ("vm") http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L77 """ -from __future__ import unicode_literals +from ..dynamips_hypervisor import DynamipsHypervisor from ..dynamips_error import DynamipsError import time import sys @@ -68,6 +68,8 @@ class Router(object): self._name = '"' + name + '"' # put name into quotes to protect spaces self._platform = platform self._image = "" + self._startup_config = "" + self._private_config = "" self._ram = 128 # Megabytes self._nvram = 128 # Kilobytes self._mmap = True @@ -100,8 +102,12 @@ class Router(object): log.info("router {platform} {name} [id={id}] has been created".format(name=self._name, platform=platform, id=self._id)) - self.console = self._hypervisor.baseconsole + self._id - self.aux = self._hypervisor.baseaux + self._id + + # allocate and check that console and aux ports are unused + console_port = (self._hypervisor.baseconsole - 1) + self._id + self.console = DynamipsHypervisor.find_unused_port(console_port, console_port + 1, self._hypervisor.host) + aux_port = (self._hypervisor.baseaux - 1) + self._id + self.aux = DynamipsHypervisor.find_unused_port(aux_port, aux_port + 1, self._hypervisor.host) # get the default base MAC address self._mac_addr = self._hypervisor.send("{platform} get_mac_addr {name}".format(platform=self._platform, @@ -130,6 +136,8 @@ class Router(object): router_defaults = {"platform": self._platform, "image": self._image, + "startup_config": self._startup_config, + "private_config": self._private_config, "ram": self._ram, "nvram": self._nvram, "mmap": self._mmap, @@ -249,7 +257,7 @@ class Router(object): Deletes this router. """ - self._hypervisor.send("vm delete {}".format(self._name)) + self._hypervisor.send("vm clean_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)) @@ -384,6 +392,46 @@ class Router(object): self._image = image + @property + def startup_config(self): + """ + Returns the startup-config for this router. + + :returns: path to startup-config file + """ + + return self._startup_config + + @startup_config.setter + def startup_config(self, startup_config): + """ + Sets the startup-config for this router. + + :param startup_config: path to startup-config file + """ + + self._startup_config = startup_config + + @property + def private_config(self): + """ + Returns the private-config for this router. + + :returns: path to private-config file + """ + + return self._private_config + + @private_config.setter + def private_config(self, private_config): + """ + Sets the private-config for this router. + + :param private_config: path to private-config file + """ + + self._private_config = private_config + def set_config(self, startup_config, private_config=''): """ Sets the config files that are pushed to startup-config and @@ -394,18 +442,20 @@ class Router(object): (keep existing data when if an empty string) """ - self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name, - startup='"' + startup_config + '"', - private='"' + private_config + '"')) + if self._startup_config != startup_config or self._private_config != private_config: - log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name, - id=self._id, - startup='"' + startup_config + '"')) + self._hypervisor.send("vm set_config {name} {startup} {private}".format(name=self._name, + startup='"' + startup_config + '"', + private='"' + private_config + '"')) - if private_config: - log.info("router {name} [id={id}]: has a private-config set: {private}".format(name=self._name, + log.info("router {name} [id={id}]: has a startup-config set: {startup}".format(name=self._name, id=self._id, - private='"' + private_config + '"')) + startup='"' + startup_config + '"')) + + if private_config: + log.info("router {name} [id={id}]: has a private-config set: {private}".format(name=self._name, + id=self._id, + private='"' + private_config + '"')) def extract_config(self): """ diff --git a/gns3server/server.py b/gns3server/server.py index 28e99882..219d671c 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -49,9 +49,16 @@ class Server(object): self._host = host self._port = port if ipc: - self._zmq_port = 0 # this forces module to use IPC for communications + self._zmq_port = 0 # this forces to use IPC for communications with the ZeroMQ server else: - self._zmq_port = port + 1 # this server port + 1 + try: + # let the OS find an unused port for the ZeroMQ server + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(('127.0.0.1', 0)) + self._zmq_port = sock.getsockname()[1] + except socket.error as e: + log.warn("could not pick up a random port for the ZeroMQ server: {}".format(e)) + self._zmq_port = port + 1 # let's try this server port + 1 self._ipc = ipc self._modules = [] diff --git a/tests/dynamips/dynamips.stable b/tests/dynamips/dynamips.stable index 72a01464..0af011ac 100755 Binary files a/tests/dynamips/dynamips.stable and b/tests/dynamips/dynamips.stable differ diff --git a/tests/dynamips/test_hypervisor.py b/tests/dynamips/test_hypervisor.py index d89da84e..ed0ee2ab 100644 --- a/tests/dynamips/test_hypervisor.py +++ b/tests/dynamips/test_hypervisor.py @@ -18,9 +18,9 @@ def test_host(hypervisor): assert hypervisor.host == "127.0.0.1" -def test_workingdir(hypervisor): +def test_working_dir(hypervisor): - assert hypervisor.workingdir == "/tmp" + assert hypervisor.working_dir == "/tmp" def test_path(hypervisor): diff --git a/tox.ini b/tox.ini index 8bfea5a7..84900ea9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py33 +envlist = py33 [testenv] commands = py.test [] -s tests