mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 09:18:08 +00:00
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:
parent
3df5cdb76f
commit
41a1d16e92
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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,27 +215,19 @@ 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))
|
||||
|
||||
#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)
|
||||
self._hypervisor_manager_settings = request
|
||||
|
||||
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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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!
|
||||
|
||||
|
@ -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())
|
||||
@ -80,8 +77,8 @@ class IOU(IModule):
|
||||
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):
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
#=======================================================================
|
||||
# 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)
|
||||
if not instance:
|
||||
continue
|
||||
|
||||
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:
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user