diff --git a/gns3server/main.py b/gns3server/main.py index 9530bd07..f51f2f82 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -18,6 +18,7 @@ import datetime import sys +import multiprocessing import logging import tornado.options import gns3server @@ -34,6 +35,10 @@ def main(): Entry point for GNS3 server """ + if sys.platform.startswith("win"): + # necessary on Windows to use freezing software + multiprocessing.freeze_support() + current_year = datetime.date.today().year print("GNS3 server version {}".format(gns3server.__version__)) print("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year)) diff --git a/gns3server/modules/__init__.py b/gns3server/modules/__init__.py index 6447293d..5ff0caac 100644 --- a/gns3server/modules/__init__.py +++ b/gns3server/modules/__init__.py @@ -15,4 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import sys from .base import IModule +from .dynamips import Dynamips +from .iou import IOU + +MODULES = [Dynamips] + +if sys.platform.startswith("linux"): + # IOU runs only on Linux + MODULES.append(IOU) diff --git a/gns3server/modules/base.py b/gns3server/modules/base.py index d1bca93c..c77c394d 100644 --- a/gns3server/modules/base.py +++ b/gns3server/modules/base.py @@ -19,6 +19,7 @@ Base class (interface) for modules """ +import sys import gns3server.jsonrpc as jsonrpc import multiprocessing import zmq @@ -109,7 +110,10 @@ class IModule(multiprocessing.Process): log.warning("Module {} got signal {}, exiting...".format(self.name, signum)) self.stop() - for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]: + signals = [signal.SIGTERM, signal.SIGINT] + if not sys.platform.startswith("win"): + signals.extend([signal.SIGHUP, signal.SIGQUIT]) + for sig in signals: signal.signal(sig, signal_handler) log.info("{} module running with PID {}".format(self.name, self.pid)) @@ -179,6 +183,20 @@ class IModule(multiprocessing.Process): self._current_call_id)) self._stream.send_json(response) + def send_notification(self, results): + """ + Sends a notification + + :param results: JSON results to the ZeroMQ server + """ + + jsonrpc_response = jsonrpc.JSONRPCNotification(results)() + + # add session to the response + response = [self._current_session, jsonrpc_response] + log.debug("ZeroMQ client ({}) sending: {}".format(self.name, response)) + self._stream.send_json(response) + def _decode_request(self, request): """ Decodes the request to JSON. diff --git a/gns3server/modules/dynamips/__init__.py b/gns3server/modules/dynamips/__init__.py index 160b5f3a..324be5b2 100644 --- a/gns3server/modules/dynamips/__init__.py +++ b/gns3server/modules/dynamips/__init__.py @@ -102,6 +102,7 @@ class Dynamips(IModule): IModule.__init__(self, name, *args, **kwargs) self._hypervisor_manager = None + self._hypervisor_manager_settings = {} self._remote_server = False self._routers = {} self._ethernet_switches = {} @@ -110,6 +111,8 @@ class Dynamips(IModule): self._ethernet_hubs = {} self._projects_dir = kwargs["projects_dir"] self._tempdir = kwargs["temp_dir"] + self._working_dir = self._projects_dir + self._dynamips = "" #self._callback = self.add_periodic_callback(self.test, 1000) #self._callback.start() @@ -161,6 +164,34 @@ class Dynamips(IModule): self._remote_server = False log.info("dynamips module has been reset") + def start_hypervisor_manager(self): + """ + Starts the hypervisor manager. + """ + + # check if Dynamips path exists + if not os.path.exists(self._dynamips): + raise DynamipsError("Dynamips executable {} doesn't exist".format(self._dynamips)) + + # check if Dynamips is executable + if not os.access(self._dynamips, os.X_OK): + raise DynamipsError("Dynamips {} is not executable".format(self._dynamips)) + + # check if the working directory exists + if not os.path.exists(self._working_dir): + raise DynamipsError("Working directory {} doesn't exist".format(self._working_dir)) + + # check if the working directory is writable + if not os.access(self._working_dir, os.W_OK): + raise DynamipsError("Cannot write to working directory {}".format(self._working_dir)) + + log.info("starting the hypervisor manager with Dynamips working directory set to '{}'".format(self._working_dir)) + self._hypervisor_manager = HypervisorManager(self._dynamips, self._working_dir) + + for name, value in self._hypervisor_manager_settings.items(): + if hasattr(self._hypervisor_manager, name) and getattr(self._hypervisor_manager, name) != value: + setattr(self._hypervisor_manager, name, value) + @IModule.route("dynamips.settings") def settings(self, request): """ @@ -184,31 +215,23 @@ class Dynamips(IModule): #TODO: JSON schema validation # starts the hypervisor manager if it hasn't been started yet if not self._hypervisor_manager: - dynamips_path = request["path"] + self._dynamips = request.pop("path") if "working_dir" in request: - working_dir = request["working_dir"] + self._working_dir = request.pop("working_dir") log.info("this server is local") else: self._remote_server = True log.info("this server is remote") - working_dir = self._projects_dir - - #TODO: check if executable - if not os.path.exists(dynamips_path): - raise DynamipsError("Dynamips executable {} doesn't exist".format(working_dir)) + self._working_dir = self._projects_dir - #TODO: check if writable - if not os.path.exists(working_dir): - raise DynamipsError("Working directory {} doesn't exist".format(working_dir)) + self._hypervisor_manager_settings = request - 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(): - if hasattr(self._hypervisor_manager, name) and getattr(self._hypervisor_manager, name) != value: - setattr(self._hypervisor_manager, name, value) + else: + # apply settings to the hypervisor manager + for name, value in request.items(): + if hasattr(self._hypervisor_manager, name) and getattr(self._hypervisor_manager, name) != value: + setattr(self._hypervisor_manager, name, value) @IModule.route("dynamips.echo") def echo(self, request): diff --git a/gns3server/modules/dynamips/backends/atmsw.py b/gns3server/modules/dynamips/backends/atmsw.py index 9bc4b037..b25e3d3d 100644 --- a/gns3server/modules/dynamips/backends/atmsw.py +++ b/gns3server/modules/dynamips/backends/atmsw.py @@ -47,6 +47,9 @@ class ATMSW(object): name = request["name"] try: + if not self._hypervisor_manager: + self.start_hypervisor_manager() + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_simulated_device() atmsw = ATMSwitch(hypervisor, name) except DynamipsError as e: diff --git a/gns3server/modules/dynamips/backends/ethhub.py b/gns3server/modules/dynamips/backends/ethhub.py index f8419c1b..4d84d97b 100644 --- a/gns3server/modules/dynamips/backends/ethhub.py +++ b/gns3server/modules/dynamips/backends/ethhub.py @@ -46,6 +46,9 @@ class ETHHUB(object): name = request["name"] try: + if not self._hypervisor_manager: + self.start_hypervisor_manager() + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_simulated_device() ethhub = Hub(hypervisor, name) except DynamipsError as e: diff --git a/gns3server/modules/dynamips/backends/ethsw.py b/gns3server/modules/dynamips/backends/ethsw.py index 5d17b711..891a8a1e 100644 --- a/gns3server/modules/dynamips/backends/ethsw.py +++ b/gns3server/modules/dynamips/backends/ethsw.py @@ -46,6 +46,9 @@ class ETHSW(object): name = request["name"] try: + if not self._hypervisor_manager: + self.start_hypervisor_manager() + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_simulated_device() ethsw = EthernetSwitch(hypervisor, name) except DynamipsError as e: diff --git a/gns3server/modules/dynamips/backends/frsw.py b/gns3server/modules/dynamips/backends/frsw.py index a45ddc85..896bc11d 100644 --- a/gns3server/modules/dynamips/backends/frsw.py +++ b/gns3server/modules/dynamips/backends/frsw.py @@ -46,6 +46,9 @@ class FRSW(object): name = request["name"] try: + if not self._hypervisor_manager: + self.start_hypervisor_manager() + hypervisor = self._hypervisor_manager.allocate_hypervisor_for_simulated_device() frsw = FrameRelaySwitch(hypervisor, name) except DynamipsError as e: diff --git a/gns3server/modules/dynamips/backends/vm.py b/gns3server/modules/dynamips/backends/vm.py index 2f8592d7..2b237cf0 100644 --- a/gns3server/modules/dynamips/backends/vm.py +++ b/gns3server/modules/dynamips/backends/vm.py @@ -127,7 +127,7 @@ class VM(object): try: if not self._hypervisor_manager: - raise DynamipsError("Dynamips manager is not started") + self.start_hypervisor_manager() hypervisor = self._hypervisor_manager.allocate_hypervisor_for_router(image, ram) diff --git a/gns3server/modules/dynamips/nodes/c7200.py b/gns3server/modules/dynamips/nodes/c7200.py index 0d73d3fc..e83a0b48 100644 --- a/gns3server/modules/dynamips/nodes/c7200.py +++ b/gns3server/modules/dynamips/nodes/c7200.py @@ -67,9 +67,9 @@ class C7200(Router): # first slot is a mandatory Input/Output controller (based on NPE type) if npe == "npe-g2": - self._slots[0] = C7200_IO_GE_E() + self.slot_add_binding(0, C7200_IO_GE_E()) else: - self._slots[0] = C7200_IO_2FE() + self.slot_add_binding(0, C7200_IO_2FE()) def defaults(self): """ diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 586144a4..aac9a04d 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -1205,7 +1205,8 @@ class Router(object): slot_id=slot_id)) if adapter == None: - return + raise DynamipsError("No adapter in slot {slot_id} on router {name}".format(name=self._name, + slot_id=slot_id)) #FIXME: check if adapter can be removed! diff --git a/gns3server/modules/iou/__init__.py b/gns3server/modules/iou/__init__.py index 6de545ff..a51f5df1 100644 --- a/gns3server/modules/iou/__init__.py +++ b/gns3server/modules/iou/__init__.py @@ -45,9 +45,6 @@ class IOU(IModule): def __init__(self, name, *args, **kwargs): - if not sys.platform.startswith("linux"): - raise IOUError("Sorry the IOU module only works on Linux") - # get the iouyap location config = Config.instance() iou_config = config.get_section_config(name.upper()) @@ -74,14 +71,14 @@ class IOU(IModule): self._udp_start_port_range = 30001 self._udp_end_port_range = 40001 self._current_udp_port = self._udp_start_port_range - self._host = "127.0.0.1" #FIXME: used by ZeroMQ... + self._host = "127.0.0.1" # FIXME: used by ZeroMQ... self._projects_dir = kwargs["projects_dir"] self._tempdir = kwargs["temp_dir"] self._working_dir = self._projects_dir self._iourc = "" - #self._callback = self.add_periodic_callback(self.test, 1000) - #self._callback.start() + self._iou_callback = self.add_periodic_callback(self._check_iou, 5000) + self._iou_callback.start() def stop(self): """ @@ -95,6 +92,17 @@ class IOU(IModule): IModule.stop(self) # this will stop the I/O loop + def _check_iou(self): + + for iou_id in self._iou_instances: + iou_instance = self._iou_instances[iou_id] + if iou_instance.started and not iou_instance.is_running(): + self.send_notification({"module": self.name, + "id": iou_id, + "name": iou_instance.name, + "message": "IOU is not running"}) + iou_instance.stop() + @IModule.route("iou.reset") def reset(self, request): """ @@ -115,6 +123,13 @@ class IOU(IModule): self._remote_server = False self._current_console_port = self._console_start_port_range self._current_udp_port = self._udp_start_port_range + + if self._iourc and os.path.exists(self._iourc): + try: + os.remove(self._iourc) + except EnvironmentError as e: + log.warn("could not delete iourc file {}: {}".format(self._iourc, e)) + log.info("IOU module has been reset") @IModule.route("iou.settings") @@ -360,6 +375,37 @@ class IOU(IModule): return self.send_response(request) + @IModule.route("iou.reload") + def vm_reload(self, request): + """ + Reloads an IOU instance. + + Mandatory request parameters: + - id (IOU identifier) + + Response parameters: + - same as original request + + :param request: JSON request + """ + + if request == None: + self.send_param_error() + return + + #TODO: JSON schema validation for the request + log.debug("received request {}".format(request)) + iou_id = request["id"] + iou_instance = self._iou_instances[iou_id] + try: + if iou_instance.is_running(): + iou_instance.stop() + iou_instance.start() + except IOUError as e: + self.send_custom_error(str(e)) + return + self.send_response(request) + @IModule.route("iou.allocate_udp_port") def allocate_udp_port(self, request): """ diff --git a/gns3server/modules/iou/iou_device.py b/gns3server/modules/iou/iou_device.py index f64f0cb5..912ee471 100644 --- a/gns3server/modules/iou/iou_device.py +++ b/gns3server/modules/iou/iou_device.py @@ -79,6 +79,7 @@ class IOUDevice(object): self._ioucon_thead = None self._ioucon_thread_stop_event = None self._host = host + self._started = False # IOU settings self._ethernet_adapters = [EthernetAdapter(), EthernetAdapter()] # one adapter = 4 interfaces @@ -295,6 +296,16 @@ class IOUDevice(object): log.info("IOU device {name} [id={id}] has been deleted".format(name=self._name, id=self._id)) + @property + def started(self): + """ + Returns either this IOU device has been started or not. + + :returns: boolean + """ + + return self._started + def _update_iouyap_config(self): """ Updates the iouyap.ini file. @@ -411,6 +422,7 @@ class IOUDevice(object): cwd=self._working_dir, env=env) log.info("IOU instance {} started PID={}".format(self._id, self._process.pid)) + self._started = True except EnvironmentError as e: log.error("could not start IOU: {}".format(e)) raise IOUError("could not start IOU: {}".format(e)) @@ -437,6 +449,7 @@ class IOUDevice(object): 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: diff --git a/gns3server/server.py b/gns3server/server.py index 0b82d25c..73434b02 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -23,6 +23,7 @@ import zmq from zmq.eventloop import ioloop, zmqstream ioloop.install() +import sys import os import tempfile import signal @@ -37,6 +38,7 @@ from .handlers.jsonrpc_websocket import JSONRPCWebSocket from .handlers.version_handler import VersionHandler from .handlers.file_upload_handler import FileUploadHandler from .module_manager import ModuleManager +from .modules import MODULES import logging log = logging.getLogger(__name__) @@ -85,23 +87,38 @@ class Server(object): Loads the modules. """ - cwd = os.path.dirname(os.path.abspath(__file__)) - module_path = os.path.join(cwd, 'modules') - log.info("loading modules from {}".format(module_path)) - module_manager = ModuleManager([module_path]) - module_manager.load_modules() - for module in module_manager.get_all_modules(): - instance = module_manager.activate_module(module, - "127.0.0.1", # ZeroMQ server address - self._zmq_port, # ZeroMQ server port - projects_dir=self._projects_dir, - temp_dir=self._temp_dir) - if not instance: - continue + #======================================================================= + # cwd = os.path.dirname(os.path.abspath(__file__)) + # module_path = os.path.join(cwd, 'modules') + # log.info("loading modules from {}".format(module_path)) + # module_manager = ModuleManager([module_path]) + # module_manager.load_modules() + # for module in module_manager.get_all_modules(): + # instance = module_manager.activate_module(module, + # "127.0.0.1", # ZeroMQ server address + # self._zmq_port, # ZeroMQ server port + # projects_dir=self._projects_dir, + # temp_dir=self._temp_dir) + # if not instance: + # continue + # self._modules.append(instance) + # destinations = instance.destinations() + # for destination in destinations: + # JSONRPCWebSocket.register_destination(destination, module.name) + # instance.start() # starts the new process + #======================================================================= + + for module in MODULES: + instance = module(module.__name__.lower(), + "127.0.0.1", # ZeroMQ server address + self._zmq_port, # ZeroMQ server port + projects_dir=self._projects_dir, + temp_dir=self._temp_dir) + self._modules.append(instance) destinations = instance.destinations() for destination in destinations: - JSONRPCWebSocket.register_destination(destination, module.name) + JSONRPCWebSocket.register_destination(destination, instance.name) instance.start() # starts the new process def run(self): @@ -130,7 +147,10 @@ class Server(object): log.warning("Server got signal {}, exiting...".format(signum)) self._cleanup() - for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT]: + signals = [signal.SIGTERM, signal.SIGINT] + if not sys.platform.startswith("win"): + signals.extend([signal.SIGHUP, signal.SIGQUIT]) + for sig in signals: signal.signal(sig, signal_handler) try: diff --git a/gns3server/version.py b/gns3server/version.py index cd882e72..80b1ab0d 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__ = "0.1.dev" -__version_info__ = (0, 1, 0, -99) +__version__ = "0.9.dev" +__version_info__ = (0, 9, 0, -99)