Windows support (freezing).

Client notification support.
Hypervisor manager changes.
IOU reload support.
Switch to non-dynamic module loading because of a multiprocessing
problem on Windows.
This commit is contained in:
grossmj 2014-03-15 21:41:04 -06:00
parent 3df5cdb76f
commit 41a1d16e92
15 changed files with 192 additions and 45 deletions

View File

@ -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))

View File

@ -15,4 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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)

View File

@ -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.

View File

@ -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
self._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._hypervisor_manager_settings = request
#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():
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):

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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)

View File

@ -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):
"""

View File

@ -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!

View File

@ -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):
"""

View File

@ -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:

View File

@ -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:

View File

@ -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)