mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 09:18:08 +00:00
IOU integration.
Improvements on module management. File upload support. Config file for the server.
This commit is contained in:
parent
89888ae7bf
commit
0f75dbc68a
115
gns3server/config.py
Normal file
115
gns3server/config.py
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Reads the configuration file and store the settings for the server & modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
"""
|
||||||
|
Configuration file management using configparser.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
appname = "GNS3"
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
|
||||||
|
# On windows, the configuration file location can be one of the following:
|
||||||
|
# 1: %APPDATA%/GNS3/server.ini
|
||||||
|
# 2: %APPDATA%/GNS3.ini
|
||||||
|
# 3: %COMMON_APPDATA%/GNS3/server.ini
|
||||||
|
# 4: %COMMON_APPDATA%/GNS3.ini
|
||||||
|
# 5: server.ini in the current working directory
|
||||||
|
|
||||||
|
appdata = os.path.expandvars("%APPDATA%")
|
||||||
|
common_appdata = os.path.expandvars("%COMMON_APPDATA%")
|
||||||
|
filename = "server.ini"
|
||||||
|
self._files = [os.path.join(appdata, appname, filename),
|
||||||
|
os.path.join(appdata, appname + ".ini"),
|
||||||
|
os.path.join(common_appdata, appname, filename),
|
||||||
|
os.path.join(common_appdata, appname + ".ini"),
|
||||||
|
filename]
|
||||||
|
else:
|
||||||
|
|
||||||
|
# On UNIX-like platforms, the configuration file location can be one of the following:
|
||||||
|
# 1: $HOME/.config/GNS3/server.conf
|
||||||
|
# 2: $HOME/.config/GNS3.conf
|
||||||
|
# 3: /etc/xdg/GNS3/server.conf
|
||||||
|
# 4: /etc/xdg/GNS3.conf
|
||||||
|
# 5: server.conf in the current working directory
|
||||||
|
|
||||||
|
home = os.path.expanduser("~")
|
||||||
|
filename = "server.conf"
|
||||||
|
self._files = [os.path.join(home, ".config", appname, filename),
|
||||||
|
os.path.join(home, ".config", appname + ".conf"),
|
||||||
|
os.path.join("/etc/xdg", appname, filename),
|
||||||
|
os.path.join("/etc/xdg", appname + ".conf"),
|
||||||
|
filename]
|
||||||
|
|
||||||
|
self._config = configparser.ConfigParser()
|
||||||
|
self.read_config()
|
||||||
|
|
||||||
|
def read_config(self):
|
||||||
|
"""
|
||||||
|
Read the configuration files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parsed_files = self._config.read(self._files)
|
||||||
|
if not parsed_files:
|
||||||
|
log.warning("no configuration file could be found or read")
|
||||||
|
|
||||||
|
def get_default_section(self):
|
||||||
|
"""
|
||||||
|
Get the default configuration section.
|
||||||
|
|
||||||
|
:returns: configparser section
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._config["DEFAULT"]
|
||||||
|
|
||||||
|
def get_section_config(self, section):
|
||||||
|
"""
|
||||||
|
Get a specific configuration section.
|
||||||
|
Returns the default section if none can be found.
|
||||||
|
|
||||||
|
:returns: configparser section
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not section in self._config:
|
||||||
|
return self._config["DEFAULT"]
|
||||||
|
return self._config[section]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def instance():
|
||||||
|
"""
|
||||||
|
Singleton to return only on instance of Config.
|
||||||
|
|
||||||
|
:returns: instance of Config
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not hasattr(Config, "_instance"):
|
||||||
|
Config._instance = Config()
|
||||||
|
return Config._instance
|
77
gns3server/handlers/file_upload_handler.py
Normal file
77
gns3server/handlers/file_upload_handler.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Simple file upload & listing handler.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tornado.web
|
||||||
|
from ..config import Config
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FileUploadHandler(tornado.web.RequestHandler):
|
||||||
|
"""
|
||||||
|
File upload handler.
|
||||||
|
|
||||||
|
:param application: Tornado Application instance
|
||||||
|
:param request: Tornado Request instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, application, request):
|
||||||
|
|
||||||
|
# get the upload directory from the configuration file
|
||||||
|
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")))
|
||||||
|
|
||||||
|
if not os.path.exists(self._upload_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(self._upload_dir)
|
||||||
|
log.info("upload directory '{}' created".format(self._upload_dir))
|
||||||
|
except EnvironmentError 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
items = []
|
||||||
|
path = self._upload_dir
|
||||||
|
for filename in os.listdir(path):
|
||||||
|
items.append(filename)
|
||||||
|
|
||||||
|
self.render("upload.html", path=path, items=items)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
"""
|
||||||
|
Invoked on POST request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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'])
|
||||||
|
self.redirect("/upload")
|
22
gns3server/handlers/upload.html
Normal file
22
gns3server/handlers/upload.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<title>Upload Form</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p><h1>Select & Upload</h1></p>
|
||||||
|
<form enctype="multipart/form-data" action="/upload" method="post">
|
||||||
|
File: <input type="file" name="file" />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<input type="submit" value="upload" />
|
||||||
|
</form>
|
||||||
|
{%if items%}
|
||||||
|
<h3>Files</h3>
|
||||||
|
<ul>
|
||||||
|
{%for item in items%}
|
||||||
|
<li>{{path}}{{item}}</a></li>
|
||||||
|
{%end%}
|
||||||
|
{%end%}
|
||||||
|
</ul>
|
||||||
|
</body>
|
@ -82,7 +82,7 @@ class ModuleManager(object):
|
|||||||
log.info("loading {} module".format(module_class[0].lower()))
|
log.info("loading {} module".format(module_class[0].lower()))
|
||||||
info = Module(name=module_class[0].lower(), cls=module_class[1])
|
info = Module(name=module_class[0].lower(), cls=module_class[1])
|
||||||
self._modules.append(info)
|
self._modules.append(info)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
log.critical("error while analyzing {} package directory".format(name), exc_info=1)
|
log.critical("error while analyzing {} package directory".format(name), exc_info=1)
|
||||||
finally:
|
finally:
|
||||||
if file:
|
if file:
|
||||||
@ -97,7 +97,7 @@ class ModuleManager(object):
|
|||||||
|
|
||||||
return self._modules
|
return self._modules
|
||||||
|
|
||||||
def activate_module(self, module, args=(), kwargs={}):
|
def activate_module(self, module, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Activates a given module.
|
Activates a given module.
|
||||||
|
|
||||||
@ -109,6 +109,10 @@ class ModuleManager(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
module_class = module.cls()
|
module_class = module.cls()
|
||||||
module_instance = module_class(name=module.name, args=args, kwargs={})
|
try:
|
||||||
log.info("activating {} module".format(module.name))
|
module_instance = module_class(module.name, *args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
log.critical("error while activating the {} module".format(module.name), exc_info=1)
|
||||||
|
return None
|
||||||
|
log.info("activating the {} module".format(module.name))
|
||||||
return module_instance
|
return module_instance
|
||||||
|
@ -37,15 +37,11 @@ class IModule(multiprocessing.Process):
|
|||||||
:param kwargs: named arguments for the module
|
:param kwargs: named arguments for the module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
destination = {}
|
modules = {}
|
||||||
|
|
||||||
def __init__(self, name=None, args=(), kwargs={}):
|
def __init__(self, name, *args, **kwargs):
|
||||||
|
|
||||||
multiprocessing.Process.__init__(self,
|
|
||||||
name=name,
|
|
||||||
args=args,
|
|
||||||
kwargs=kwargs)
|
|
||||||
|
|
||||||
|
multiprocessing.Process.__init__(self, name=name)
|
||||||
self._context = None
|
self._context = None
|
||||||
self._ioloop = None
|
self._ioloop = None
|
||||||
self._stream = None
|
self._stream = None
|
||||||
@ -203,14 +199,14 @@ class IModule(multiprocessing.Process):
|
|||||||
destination = request[1].get("method")
|
destination = request[1].get("method")
|
||||||
params = request[1].get("params")
|
params = request[1].get("params")
|
||||||
|
|
||||||
if destination not in self.destination:
|
if destination not in self.modules[self.name]:
|
||||||
self.send_internal_error()
|
self.send_internal_error()
|
||||||
return
|
return
|
||||||
|
|
||||||
log.debug("Routing request to {}: {}".format(destination, request[1]))
|
log.debug("Routing request to {}: {}".format(destination, request[1]))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.destination[destination](self, params)
|
self.modules[self.name][destination](self, params)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("uncaught exception {type}".format(type=type(e)), exc_info=1)
|
log.error("uncaught exception {type}".format(type=type(e)), exc_info=1)
|
||||||
self.send_custom_error("uncaught exception {type}: {string}".format(type=type(e), string=str(e)))
|
self.send_custom_error("uncaught exception {type}: {string}".format(type=type(e), string=str(e)))
|
||||||
@ -222,7 +218,10 @@ class IModule(multiprocessing.Process):
|
|||||||
:returns: list of destinations
|
:returns: list of destinations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.destination.keys()
|
if not self.name in self.modules:
|
||||||
|
log.warn("no destinations found for module {}".format(self.name))
|
||||||
|
return []
|
||||||
|
return self.modules[self.name].keys()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def route(cls, destination):
|
def route(cls, destination):
|
||||||
@ -233,6 +232,9 @@ class IModule(multiprocessing.Process):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(method):
|
def wrapper(method):
|
||||||
cls.destination[destination] = method
|
module = destination.split(".")[0]
|
||||||
|
if not module in cls.modules:
|
||||||
|
cls.modules[module] = {}
|
||||||
|
cls.modules[module][destination] = method
|
||||||
return method
|
return method
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -97,8 +97,9 @@ class Dynamips(IModule):
|
|||||||
:param kwargs: named arguments for the module
|
:param kwargs: named arguments for the module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, args=(), kwargs={}):
|
def __init__(self, name, *args, **kwargs):
|
||||||
IModule.__init__(self, name=name, args=args, kwargs=kwargs)
|
|
||||||
|
IModule.__init__(self, name, *args, **kwargs)
|
||||||
|
|
||||||
self._hypervisor_manager = None
|
self._hypervisor_manager = None
|
||||||
self._remote_server = False
|
self._remote_server = False
|
||||||
@ -107,6 +108,8 @@ class Dynamips(IModule):
|
|||||||
self._frame_relay_switches = {}
|
self._frame_relay_switches = {}
|
||||||
self._atm_switches = {}
|
self._atm_switches = {}
|
||||||
self._ethernet_hubs = {}
|
self._ethernet_hubs = {}
|
||||||
|
self._projects_dir = kwargs["projects_dir"]
|
||||||
|
self._tempdir = kwargs["temp_dir"]
|
||||||
|
|
||||||
#self._callback = self.add_periodic_callback(self.test, 1000)
|
#self._callback = self.add_periodic_callback(self.test, 1000)
|
||||||
#self._callback.start()
|
#self._callback.start()
|
||||||
@ -189,13 +192,7 @@ class Dynamips(IModule):
|
|||||||
else:
|
else:
|
||||||
self._remote_server = True
|
self._remote_server = True
|
||||||
log.info("this server is remote")
|
log.info("this server is remote")
|
||||||
try:
|
working_dir = self._projects_dir
|
||||||
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
|
#TODO: check if executable
|
||||||
if not os.path.exists(dynamips_path):
|
if not os.path.exists(dynamips_path):
|
||||||
@ -342,28 +339,6 @@ class Dynamips(IModule):
|
|||||||
router.ghost_status = 2
|
router.ghost_status = 2
|
||||||
router.ghost_file = ghost_instance
|
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")
|
@IModule.route("dynamips.nio.get_interfaces")
|
||||||
def nio_get_interfaces(self, request):
|
def nio_get_interfaces(self, request):
|
||||||
"""
|
"""
|
||||||
|
@ -84,6 +84,7 @@ class ATMSW(object):
|
|||||||
try:
|
try:
|
||||||
atmsw.delete()
|
atmsw.delete()
|
||||||
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(atmsw)
|
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(atmsw)
|
||||||
|
del self._atm_switches[atmsw_id]
|
||||||
except DynamipsError as e:
|
except DynamipsError as e:
|
||||||
self.send_custom_error(str(e))
|
self.send_custom_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -83,6 +83,7 @@ class ETHHUB(object):
|
|||||||
try:
|
try:
|
||||||
ethhub.delete()
|
ethhub.delete()
|
||||||
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(ethhub)
|
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(ethhub)
|
||||||
|
del self._ethernet_hubs[ethhub_id]
|
||||||
except DynamipsError as e:
|
except DynamipsError as e:
|
||||||
self.send_custom_error(str(e))
|
self.send_custom_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -83,6 +83,7 @@ class ETHSW(object):
|
|||||||
try:
|
try:
|
||||||
ethsw.delete()
|
ethsw.delete()
|
||||||
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(ethsw)
|
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(ethsw)
|
||||||
|
del self._ethernet_switches[ethsw_id]
|
||||||
except DynamipsError as e:
|
except DynamipsError as e:
|
||||||
self.send_custom_error(str(e))
|
self.send_custom_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -83,6 +83,7 @@ class FRSW(object):
|
|||||||
try:
|
try:
|
||||||
frsw.delete()
|
frsw.delete()
|
||||||
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(frsw)
|
self._hypervisor_manager.unallocate_hypervisor_for_simulated_device(frsw)
|
||||||
|
del self._frame_relay_switches[frsw_id]
|
||||||
except DynamipsError as e:
|
except DynamipsError as e:
|
||||||
self.send_custom_error(str(e))
|
self.send_custom_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -206,6 +206,7 @@ class VM(object):
|
|||||||
try:
|
try:
|
||||||
router.delete()
|
router.delete()
|
||||||
self._hypervisor_manager.unallocate_hypervisor_for_router(router)
|
self._hypervisor_manager.unallocate_hypervisor_for_router(router)
|
||||||
|
del self._routers[router_id]
|
||||||
except DynamipsError as e:
|
except DynamipsError as e:
|
||||||
self.send_custom_error(str(e))
|
self.send_custom_error(str(e))
|
||||||
return
|
return
|
||||||
|
@ -211,11 +211,16 @@ class Hypervisor(DynamipsHypervisor):
|
|||||||
if self.is_running():
|
if self.is_running():
|
||||||
DynamipsHypervisor.stop(self)
|
DynamipsHypervisor.stop(self)
|
||||||
log.info("stopping Dynamips PID={}".format(self._process.pid))
|
log.info("stopping Dynamips PID={}".format(self._process.pid))
|
||||||
|
try:
|
||||||
# give some time for the hypervisor to properly stop.
|
# give some time for the hypervisor to properly stop.
|
||||||
# time to delete UNIX NIOs for instance.
|
# time to delete UNIX NIOs for instance.
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
self._process.terminate()
|
||||||
|
self._process.wait(1)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
self._process.kill()
|
self._process.kill()
|
||||||
self._process.wait()
|
if self._process.poll() == None:
|
||||||
|
log.warn("Dynamips process {} is still running".format(self._process.pid))
|
||||||
|
|
||||||
def read_stdout(self):
|
def read_stdout(self):
|
||||||
"""
|
"""
|
||||||
|
@ -20,7 +20,6 @@ Manages Dynamips hypervisors (load-balancing etc.)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .hypervisor import Hypervisor
|
from .hypervisor import Hypervisor
|
||||||
from .dynamips_error import DynamipsError
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
506
gns3server/modules/iou/__init__.py
Normal file
506
gns3server/modules/iou/__init__.py
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
IOU server module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import base64
|
||||||
|
import tempfile
|
||||||
|
from gns3server.modules import IModule
|
||||||
|
from gns3server.config import Config
|
||||||
|
from .iou_device import IOUDevice
|
||||||
|
from .iou_error import IOUError
|
||||||
|
from .nios.nio_udp import NIO_UDP
|
||||||
|
import gns3server.jsonrpc as jsonrpc
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IOU(IModule):
|
||||||
|
"""
|
||||||
|
IOU module.
|
||||||
|
|
||||||
|
:param name: module name
|
||||||
|
:param args: arguments for the module
|
||||||
|
:param kwargs: named arguments for the module
|
||||||
|
"""
|
||||||
|
|
||||||
|
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())
|
||||||
|
self._iouyap = iou_config.get("iouyap")
|
||||||
|
if not self._iouyap:
|
||||||
|
for path in os.environ["PATH"].split(":"):
|
||||||
|
if "iouyap" in os.listdir(path) and os.access("iouyap", os.X_OK):
|
||||||
|
self._iouyap = os.path.join(path, "iouyap")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not self._iouyap or not os.path.exists(self._iouyap):
|
||||||
|
raise IOUError("iouyap binary couldn't be found!")
|
||||||
|
|
||||||
|
if not os.access(self._iouyap, os.X_OK):
|
||||||
|
raise IOUError("iouyap is not executable")
|
||||||
|
|
||||||
|
# a new process start when calling IModule
|
||||||
|
IModule.__init__(self, name, *args, **kwargs)
|
||||||
|
self._remote_server = False
|
||||||
|
self._iou_instances = {}
|
||||||
|
self._console_start_port_range = 4001
|
||||||
|
self._console_end_port_range = 4512
|
||||||
|
self._current_console_port = self._console_start_port_range
|
||||||
|
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"
|
||||||
|
self._projects_dir = kwargs["projects_dir"]
|
||||||
|
self._tempdir = kwargs["temp_dir"]
|
||||||
|
self._working_dir = self._projects_dir
|
||||||
|
|
||||||
|
#self._callback = self.add_periodic_callback(self.test, 1000)
|
||||||
|
#self._callback.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Properly stops the module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# delete all IOU instances
|
||||||
|
for iou_id in self._iou_instances:
|
||||||
|
iou_instance = self._iou_instances[iou_id]
|
||||||
|
iou_instance.delete()
|
||||||
|
|
||||||
|
IModule.stop(self) # this will stop the I/O loop
|
||||||
|
|
||||||
|
@IModule.route("iou.reset")
|
||||||
|
def reset(self, request):
|
||||||
|
"""
|
||||||
|
Resets the module.
|
||||||
|
|
||||||
|
:param request: JSON request
|
||||||
|
"""
|
||||||
|
|
||||||
|
# delete all IOU instances
|
||||||
|
for iou_id in self._iou_instances:
|
||||||
|
iou_instance = self._iou_instances[iou_id]
|
||||||
|
iou_instance.delete()
|
||||||
|
|
||||||
|
# resets the instance IDs
|
||||||
|
IOUDevice.reset()
|
||||||
|
|
||||||
|
self._iou_instances.clear()
|
||||||
|
self._remote_server = False
|
||||||
|
self._current_console_port = self._console_start_port_range
|
||||||
|
self._current_udp_port = self._udp_start_port_range
|
||||||
|
log.info("IOU module has been reset")
|
||||||
|
|
||||||
|
@IModule.route("iou.settings")
|
||||||
|
def settings(self, request):
|
||||||
|
"""
|
||||||
|
Set or update settings.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- path (path to the IOU executable)
|
||||||
|
|
||||||
|
Optional request parameters:
|
||||||
|
- working_dir (path to a working directory)
|
||||||
|
- console_start_port_range
|
||||||
|
- console_end_port_range
|
||||||
|
- udp_start_port_range
|
||||||
|
- udp_end_port_range
|
||||||
|
|
||||||
|
:param request: JSON request
|
||||||
|
"""
|
||||||
|
|
||||||
|
if request == None:
|
||||||
|
self.send_param_error()
|
||||||
|
return
|
||||||
|
|
||||||
|
print(request)
|
||||||
|
|
||||||
|
if "working_dir" in request and self._working_dir != request["working_dir"]:
|
||||||
|
self._working_dir = request["working_dir"]
|
||||||
|
log.info("this server is local with working directory path to {}".format(self._working_dir))
|
||||||
|
for iou_id in self._iou_instances:
|
||||||
|
iou_instance = self._iou_instances[iou_id]
|
||||||
|
iou_instance.working_dir = self._working_dir
|
||||||
|
else:
|
||||||
|
self._remote_server = True
|
||||||
|
log.info("this server is remote")
|
||||||
|
self._working_dir = self._projects_dir
|
||||||
|
|
||||||
|
if "console_start_port_range" in request and "console_end_port_range" in request:
|
||||||
|
self._console_start_port_range = request["console_start_port_range"]
|
||||||
|
self._console_end_port_range = request["console_end_port_range"]
|
||||||
|
|
||||||
|
if "udp_start_port_range" in request and "udp_end_port_range" in request:
|
||||||
|
self._udp_start_port_range = request["udp_start_port_range"]
|
||||||
|
self._udp_end_port_range = request["udp_end_port_range"]
|
||||||
|
|
||||||
|
log.debug("received request {}".format(request))
|
||||||
|
|
||||||
|
@IModule.route("iou.create")
|
||||||
|
def iou_create(self, request):
|
||||||
|
"""
|
||||||
|
Creates a new IOU instance.
|
||||||
|
|
||||||
|
Optional request parameters:
|
||||||
|
- name (IOU name)
|
||||||
|
- path (path to IOU)
|
||||||
|
|
||||||
|
Response parameters:
|
||||||
|
- id (IOU instance identifier)
|
||||||
|
- name (IOU name)
|
||||||
|
|
||||||
|
:param request: JSON request
|
||||||
|
"""
|
||||||
|
|
||||||
|
#TODO: JSON schema validation for the request
|
||||||
|
name = None
|
||||||
|
if request and "name" in request:
|
||||||
|
name = request["name"]
|
||||||
|
|
||||||
|
iou_path = request["path"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
iou_instance = IOUDevice(iou_path, self._working_dir, 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
|
||||||
|
iou_instance.console = IOUDevice.find_unused_port(self._current_console_port, self._console_end_port_range, self._host)
|
||||||
|
self._current_console_port += 1
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
response = {"name": iou_instance.name,
|
||||||
|
"id": iou_instance.id}
|
||||||
|
|
||||||
|
defaults = iou_instance.defaults()
|
||||||
|
response.update(defaults)
|
||||||
|
self._iou_instances[iou_instance.id] = iou_instance
|
||||||
|
self.send_response(response)
|
||||||
|
|
||||||
|
@IModule.route("iou.delete")
|
||||||
|
def iou_delete(self, request):
|
||||||
|
"""
|
||||||
|
Deletes an IOU instance.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance 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:
|
||||||
|
iou_instance.delete()
|
||||||
|
del self._iou_instances[iou_id]
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
self.send_response(request)
|
||||||
|
|
||||||
|
@IModule.route("iou.update")
|
||||||
|
def iou_update(self, request):
|
||||||
|
"""
|
||||||
|
Updates an IOU instance
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance identifier)
|
||||||
|
|
||||||
|
Optional request parameters:
|
||||||
|
- any setting to update
|
||||||
|
- startup_config_base64 (startup-config base64 encoded)
|
||||||
|
|
||||||
|
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:
|
||||||
|
# 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', iou_instance.name)
|
||||||
|
config_path = os.path.join(iou_instance.working_dir, "startup-config")
|
||||||
|
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 IOUError("Could not save the configuration {}: {}".format(config_path, e))
|
||||||
|
request["startup_config"] = os.path.basename(config_path)
|
||||||
|
if "startup_config" in request:
|
||||||
|
iou_instance.startup_config = request["startup_config"]
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
for name, value in request.items():
|
||||||
|
if hasattr(iou_instance, name) and getattr(iou_instance, name) != value:
|
||||||
|
try:
|
||||||
|
setattr(iou_instance, name, value)
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_response(request)
|
||||||
|
|
||||||
|
@IModule.route("iou.start")
|
||||||
|
def vm_start(self, request):
|
||||||
|
"""
|
||||||
|
Starts an IOU instance.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance 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:
|
||||||
|
log.debug("starting IOU with command: {}".format(iou_instance.command()))
|
||||||
|
iou_instance.iouyap = self._iouyap
|
||||||
|
iou_instance.start()
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
self.send_response(request)
|
||||||
|
|
||||||
|
@IModule.route("iou.stop")
|
||||||
|
def vm_stop(self, request):
|
||||||
|
"""
|
||||||
|
Stops an IOU instance.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance 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:
|
||||||
|
iou_instance.stop()
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Allocates a UDP port in order to create an UDP NIO.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU identifier)
|
||||||
|
- port_id (unique port identifier)
|
||||||
|
|
||||||
|
Response parameters:
|
||||||
|
- port_id (unique port identifier)
|
||||||
|
- lhost (local host address)
|
||||||
|
- lport (allocated local port)
|
||||||
|
|
||||||
|
: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:
|
||||||
|
|
||||||
|
# find a UDP port
|
||||||
|
if self._current_udp_port >= self._udp_end_port_range:
|
||||||
|
self._current_udp_port = self._udp_start_port_range
|
||||||
|
port = IOUDevice.find_unused_port(self._current_udp_port, self._udp_end_port_range, host=self._host, socket_type="UDP")
|
||||||
|
self._current_udp_port += 1
|
||||||
|
|
||||||
|
log.info("{} [id={}] has allocated UDP port {} with host {}".format(iou_instance .name,
|
||||||
|
iou_instance .id,
|
||||||
|
port,
|
||||||
|
self._host))
|
||||||
|
response = {"lport": port,
|
||||||
|
"lhost": self._host}
|
||||||
|
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
response["port_id"] = request["port_id"]
|
||||||
|
self.send_response(response)
|
||||||
|
|
||||||
|
@IModule.route("iou.add_nio")
|
||||||
|
def add_nio(self, request):
|
||||||
|
"""
|
||||||
|
Adds an NIO (Network Input/Output) for an IOU instance.
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance identifier)
|
||||||
|
- slot (slot number)
|
||||||
|
- port (port number)
|
||||||
|
- port_id (unique port identifier)
|
||||||
|
- nio (nio type, one of the following)
|
||||||
|
- "NIO_UDP"
|
||||||
|
- lport (local port)
|
||||||
|
- rhost (remote host)
|
||||||
|
- rport (remote port)
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
slot = request["slot"]
|
||||||
|
port = request["port"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
nio = None
|
||||||
|
#TODO: support for TAP and Ethernet NIOs
|
||||||
|
if request["nio"] == "NIO_UDP":
|
||||||
|
lport = request["lport"]
|
||||||
|
rhost = request["rhost"]
|
||||||
|
rport = request["rport"]
|
||||||
|
nio = NIO_UDP(lport, rhost, rport)
|
||||||
|
if not nio:
|
||||||
|
raise IOUError("Requested NIO doesn't exist or is not supported: {}".format(request["nio"]))
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
iou_instance.slot_add_nio_binding(slot, port, nio)
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
# for now send back the original request
|
||||||
|
self.send_response(request)
|
||||||
|
|
||||||
|
@IModule.route("iou.delete_nio")
|
||||||
|
def delete_nio(self, request):
|
||||||
|
"""
|
||||||
|
Deletes an NIO (Network Input/Output).
|
||||||
|
|
||||||
|
Mandatory request parameters:
|
||||||
|
- id (IOU instance identifier)
|
||||||
|
- slot (slot identifier)
|
||||||
|
- port (port 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]
|
||||||
|
slot = request["slot"]
|
||||||
|
port = request["port"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
iou_instance.slot_remove_nio_binding(slot, port)
|
||||||
|
except IOUError as e:
|
||||||
|
self.send_custom_error(str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
# for now send back the original request
|
||||||
|
self.send_response(request)
|
||||||
|
|
||||||
|
@IModule.route("iou.echo")
|
||||||
|
def echo(self, request):
|
||||||
|
"""
|
||||||
|
Echo end point for testing purposes.
|
||||||
|
|
||||||
|
:param request: JSON request
|
||||||
|
"""
|
||||||
|
|
||||||
|
if request == None:
|
||||||
|
self.send_param_error()
|
||||||
|
else:
|
||||||
|
log.debug("received request {}".format(request))
|
||||||
|
self.send_response(request)
|
0
gns3server/modules/iou/adapters/__init__.py
Normal file
0
gns3server/modules/iou/adapters/__init__.py
Normal file
104
gns3server/modules/iou/adapters/adapter.py
Normal file
104
gns3server/modules/iou/adapters/adapter.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class Adapter(object):
|
||||||
|
"""
|
||||||
|
Base class for adapters.
|
||||||
|
|
||||||
|
:param interfaces: number of interfaces supported by this adapter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, interfaces=4):
|
||||||
|
|
||||||
|
self._interfaces = interfaces
|
||||||
|
|
||||||
|
self._ports = {}
|
||||||
|
for port_id in range(0, interfaces):
|
||||||
|
self._ports[port_id] = None
|
||||||
|
|
||||||
|
def removable(self):
|
||||||
|
"""
|
||||||
|
Returns True if the adapter can be removed from a slot
|
||||||
|
and False if not.
|
||||||
|
|
||||||
|
:returns: boolean
|
||||||
|
"""
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def port_exists(self, port_id):
|
||||||
|
"""
|
||||||
|
Checks if a port exists on this adapter.
|
||||||
|
|
||||||
|
:returns: True is the port exists,
|
||||||
|
False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if port_id in self._ports:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_nio(self, port_id, nio):
|
||||||
|
"""
|
||||||
|
Adds a NIO to a port on this adapter.
|
||||||
|
|
||||||
|
:param port_id: port ID (integer)
|
||||||
|
:param nio: NIO instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._ports[port_id] = nio
|
||||||
|
|
||||||
|
def remove_nio(self, port_id):
|
||||||
|
"""
|
||||||
|
Removes a NIO from a port on this adapter.
|
||||||
|
|
||||||
|
:param port_id: port ID (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._ports[port_id] = None
|
||||||
|
|
||||||
|
def get_nio(self, port_id):
|
||||||
|
"""
|
||||||
|
Returns the NIO assigned to a port.
|
||||||
|
|
||||||
|
:params port_id: port ID (integer)
|
||||||
|
|
||||||
|
:returns: NIO instance
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._ports[port_id]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ports(self):
|
||||||
|
"""
|
||||||
|
Returns port to NIO mapping
|
||||||
|
|
||||||
|
:returns: dictionary port -> NIO
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._ports
|
||||||
|
|
||||||
|
@property
|
||||||
|
def interfaces(self):
|
||||||
|
"""
|
||||||
|
Returns the number of interfaces supported by this adapter.
|
||||||
|
|
||||||
|
:returns: number of interfaces
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._interfaces
|
31
gns3server/modules/iou/adapters/ethernet_adapter.py
Normal file
31
gns3server/modules/iou/adapters/ethernet_adapter.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .adapter import Adapter
|
||||||
|
|
||||||
|
|
||||||
|
class EthernetAdapter(Adapter):
|
||||||
|
"""
|
||||||
|
IOU Ethernet adapter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Adapter.__init__(self, interfaces=4)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return "IOU Ethernet adapter"
|
31
gns3server/modules/iou/adapters/serial_adapter.py
Normal file
31
gns3server/modules/iou/adapters/serial_adapter.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .adapter import Adapter
|
||||||
|
|
||||||
|
|
||||||
|
class SerialAdapter(Adapter):
|
||||||
|
"""
|
||||||
|
IOU Serial adapter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Adapter.__init__(self, interfaces=4)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return "IOU Serial adapter"
|
767
gns3server/modules/iou/iou_device.py
Normal file
767
gns3server/modules/iou/iou_device.py
Normal file
@ -0,0 +1,767 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
IOU device management (creates command line, processes, files etc.) in
|
||||||
|
order to run an IOU instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
import threading
|
||||||
|
import configparser
|
||||||
|
from .ioucon import start_ioucon
|
||||||
|
from .iou_error import IOUError
|
||||||
|
from .adapters.ethernet_adapter import EthernetAdapter
|
||||||
|
from .adapters.serial_adapter import SerialAdapter
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IOUDevice(object):
|
||||||
|
"""
|
||||||
|
IOU device implementation.
|
||||||
|
|
||||||
|
: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
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instances = []
|
||||||
|
|
||||||
|
def __init__(self, path, working_dir, host="127.0.0.1", name=None):
|
||||||
|
|
||||||
|
# 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 self._id == 0:
|
||||||
|
raise IOUError("Maximum number of IOU instances reached")
|
||||||
|
|
||||||
|
if name:
|
||||||
|
self._name = name
|
||||||
|
else:
|
||||||
|
self._name = "IOU{}".format(self._id)
|
||||||
|
self._path = path
|
||||||
|
self._iourc = ""
|
||||||
|
self._iouyap = ""
|
||||||
|
self._console = None
|
||||||
|
self._working_dir = None
|
||||||
|
self._command = []
|
||||||
|
self._process = None
|
||||||
|
self._iouyap_process = None
|
||||||
|
self._stdout_file = ""
|
||||||
|
self._ioucon_thead = None
|
||||||
|
self._ioucon_thread_stop_event = None
|
||||||
|
self._host = host
|
||||||
|
|
||||||
|
# IOU settings
|
||||||
|
self._ethernet_adapters = [EthernetAdapter(), EthernetAdapter()] # one adapter = 4 interfaces
|
||||||
|
self._serial_adapters = [SerialAdapter(), SerialAdapter()] # one adapter = 4 interfaces
|
||||||
|
self._slots = self._ethernet_adapters + self._serial_adapters
|
||||||
|
self._nvram = 128 # Kilobytes
|
||||||
|
self._startup_config = ""
|
||||||
|
self._ram = 256 # Megabytes
|
||||||
|
|
||||||
|
# update the working directory
|
||||||
|
self.working_dir = working_dir
|
||||||
|
|
||||||
|
log.info("IOU device {name} [id={id}] has been created".format(name=self._name,
|
||||||
|
id=self._id))
|
||||||
|
|
||||||
|
def defaults(self):
|
||||||
|
"""
|
||||||
|
Returns all the default attribute values for IOU.
|
||||||
|
|
||||||
|
:returns: default values (dictionary)
|
||||||
|
"""
|
||||||
|
|
||||||
|
iou_defaults = {"name": self._name,
|
||||||
|
"path": self._path,
|
||||||
|
"iourc": self._iourc,
|
||||||
|
"startup_config": self._startup_config,
|
||||||
|
"ram": self._ram,
|
||||||
|
"nvram": self._nvram,
|
||||||
|
"ethernet_adapters": len(self._ethernet_adapters),
|
||||||
|
"serial_adapters": len(self._serial_adapters),
|
||||||
|
"console": self._console}
|
||||||
|
|
||||||
|
return iou_defaults
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
"""
|
||||||
|
Returns the unique ID for this IOU device.
|
||||||
|
|
||||||
|
:returns: id (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return(self._id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset(cls):
|
||||||
|
"""
|
||||||
|
Resets allocated instance list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cls._instances.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""
|
||||||
|
Returns the name of this IOU device.
|
||||||
|
|
||||||
|
:returns: name
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, new_name):
|
||||||
|
"""
|
||||||
|
Sets the name of this IOU device.
|
||||||
|
|
||||||
|
:param new_name: name
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._name = new_name
|
||||||
|
log.info("IOU {name} [id={id}]: renamed to {new_name}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
new_name=new_name))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
"""
|
||||||
|
Returns the path to the IOU executable.
|
||||||
|
|
||||||
|
:returns: path to IOU
|
||||||
|
"""
|
||||||
|
|
||||||
|
return(self._path)
|
||||||
|
|
||||||
|
@path.setter
|
||||||
|
def path(self, path):
|
||||||
|
"""
|
||||||
|
Sets the path to the IOU executable.
|
||||||
|
|
||||||
|
:param path: path to IOU
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._path = path
|
||||||
|
log.info("IOU {name} [id={id}]: path changed to {path}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
path=path))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def iourc(self):
|
||||||
|
"""
|
||||||
|
Returns the path to the iourc file.
|
||||||
|
|
||||||
|
:returns: path to the iourc file
|
||||||
|
"""
|
||||||
|
|
||||||
|
return(self._iourc)
|
||||||
|
|
||||||
|
@iourc.setter
|
||||||
|
def iourc(self, iourc):
|
||||||
|
"""
|
||||||
|
Sets the path to the iourc file.
|
||||||
|
|
||||||
|
:param path: path to the iourc file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._iourc = iourc
|
||||||
|
log.info("IOU {name} [id={id}]: iourc file path set to {path}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
path=self._iourc))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def iouyap(self):
|
||||||
|
"""
|
||||||
|
Returns the path to iouyap
|
||||||
|
|
||||||
|
:returns: path to iouyap
|
||||||
|
"""
|
||||||
|
|
||||||
|
return(self._iouyap)
|
||||||
|
|
||||||
|
@iouyap.setter
|
||||||
|
def iouyap(self, iouyap):
|
||||||
|
"""
|
||||||
|
Sets the path to iouyap.
|
||||||
|
|
||||||
|
:param path: path to iouyap
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._iouyap = iouyap
|
||||||
|
log.info("IOU {name} [id={id}]: iouyap path set to {path}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
path=self._iouyap))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def working_dir(self):
|
||||||
|
"""
|
||||||
|
Returns current working directory
|
||||||
|
|
||||||
|
:returns: path to the working directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._working_dir
|
||||||
|
|
||||||
|
@working_dir.setter
|
||||||
|
def working_dir(self, working_dir):
|
||||||
|
"""
|
||||||
|
Sets the working directory for IOU.
|
||||||
|
|
||||||
|
:param working_dir: path to the working directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
# create our own working directory
|
||||||
|
working_dir = os.path.join(working_dir, "device-{}".format(self._id))
|
||||||
|
if not os.path.exists(working_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(working_dir)
|
||||||
|
except EnvironmentError as e:
|
||||||
|
raise IOUError("Could not create working directory {}: {}".format(working_dir, e))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def console(self):
|
||||||
|
"""
|
||||||
|
Returns the TCP console port.
|
||||||
|
|
||||||
|
:returns: console port (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._console
|
||||||
|
|
||||||
|
@console.setter
|
||||||
|
def console(self, console):
|
||||||
|
"""
|
||||||
|
Sets the TCP console port.
|
||||||
|
|
||||||
|
:param console: console port (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._console = console
|
||||||
|
log.info("IOU {name} [id={id}]: console port set to {port}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
port=console))
|
||||||
|
|
||||||
|
def command(self):
|
||||||
|
"""
|
||||||
|
Returns the IOU command line.
|
||||||
|
|
||||||
|
:returns: IOU command line (string)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return " ".join(self._build_command())
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""
|
||||||
|
Deletes this IOU device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.stop()
|
||||||
|
self._instances.remove(self._id)
|
||||||
|
log.info("IOU device {name} [id={id}] has been deleted".format(name=self._name,
|
||||||
|
id=self._id))
|
||||||
|
|
||||||
|
def _update_iouyap_config(self):
|
||||||
|
"""
|
||||||
|
Updates the iouyap.ini file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
iouyap_ini = os.path.join(self._working_dir, "iouyap.ini")
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config["default"] = {"netmap": "NETMAP",
|
||||||
|
"base_port": "49000"}
|
||||||
|
|
||||||
|
bay_id = 0
|
||||||
|
for adapter in self._slots:
|
||||||
|
unit_id = 0
|
||||||
|
for unit in adapter.ports.keys():
|
||||||
|
nio = adapter.get_nio(unit)
|
||||||
|
if nio:
|
||||||
|
#TODO: handle TAP and Ethernet NIOs
|
||||||
|
tunnel = {"tunnel_udp": "{lport}:{rhost}:{rport}".format(lport=nio.lport,
|
||||||
|
rhost=nio.rhost,
|
||||||
|
rport=nio.rport)}
|
||||||
|
|
||||||
|
config["{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self._id + 512), bay=bay_id, unit=unit_id)] = tunnel
|
||||||
|
unit_id += 1
|
||||||
|
bay_id += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(iouyap_ini, "w") as config_file:
|
||||||
|
config.write(config_file)
|
||||||
|
log.info("IOU {name} [id={id}]: iouyap.ini updated".format(name=self._name,
|
||||||
|
id=self._id))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
raise IOUError("Could not create {}: {}".format(iouyap_ini, e))
|
||||||
|
|
||||||
|
def _create_netmap_config(self):
|
||||||
|
"""
|
||||||
|
Creates the NETMAP file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
netmap_path = os.path.join(self._working_dir, "NETMAP")
|
||||||
|
try:
|
||||||
|
with open(netmap_path, "w") as f:
|
||||||
|
for bay in range(0, 16):
|
||||||
|
for unit in range(0, 4):
|
||||||
|
f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self._id + 512),
|
||||||
|
bay=bay,
|
||||||
|
unit=unit,
|
||||||
|
iou_id=self._id))
|
||||||
|
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
|
||||||
|
id=self._id))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
raise IOUError("Could not create {}: {}".format(netmap_path, e))
|
||||||
|
|
||||||
|
def _start_ioucon(self):
|
||||||
|
"""
|
||||||
|
Starts ioucon thread (for console connections).
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self._ioucon_thead:
|
||||||
|
telnet_server = "{}:{}".format(self._host, self._console)
|
||||||
|
log.info("starting ioucon for IOU instance {} to accept Telnet connections on {}".format(self._name, telnet_server))
|
||||||
|
args = argparse.Namespace(appl_id=str(self._id), debug=False, escape='^^', telnet_limit=0, telnet_server=telnet_server)
|
||||||
|
self._ioucon_thread_stop_event = threading.Event()
|
||||||
|
self._ioucon_thead = threading.Thread(target=start_ioucon, args=(args, self._ioucon_thread_stop_event))
|
||||||
|
self._ioucon_thead.start()
|
||||||
|
|
||||||
|
def _start_iouyap(self):
|
||||||
|
"""
|
||||||
|
Starts iouyap (handles connections to and from this IOU device).
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._update_iouyap_config()
|
||||||
|
command = [self._iouyap, str(self._id + 512)] # iouyap has always IOU ID + 512
|
||||||
|
log.info("starting iouyap: {}".format(command))
|
||||||
|
self._stdout_file = os.path.join(self._working_dir, "iouyap.log")
|
||||||
|
log.info("logging to {}".format(self._stdout_file))
|
||||||
|
with open(self._stdout_file, "w") as fd:
|
||||||
|
self._iouyap_process = subprocess.Popen(command,
|
||||||
|
stdout=fd,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
cwd=self._working_dir)
|
||||||
|
|
||||||
|
log.info("iouyap started PID={}".format(self._iouyap_process.pid))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
log.error("could not start iouyap: {}".format(e))
|
||||||
|
raise IOUError("Could not start iouyap: {}".format(e))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the IOU process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.is_running():
|
||||||
|
if not self._iourc or not os.path.exists(self._iourc):
|
||||||
|
raise IOUError("A iourc file is necessary to start IOU")
|
||||||
|
|
||||||
|
if not self._iouyap or not os.path.exists(self._iouyap):
|
||||||
|
raise IOUError("iouyap is necessary to start IOU")
|
||||||
|
|
||||||
|
self._create_netmap_config()
|
||||||
|
# created a environment variable pointing to the iourc file.
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["IOURC"] = self._iourc
|
||||||
|
self._command = self._build_command()
|
||||||
|
try:
|
||||||
|
log.info("starting IOU: {}".format(self._command))
|
||||||
|
self._stdout_file = os.path.join(self._working_dir, "iou.log")
|
||||||
|
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,
|
||||||
|
env=env)
|
||||||
|
log.info("IOU instance {} started PID={}".format(self._id, self._process.pid))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
log.error("could not start IOU: {}".format(e))
|
||||||
|
raise IOUError("could not start IOU: {}".format(e))
|
||||||
|
|
||||||
|
# start console support
|
||||||
|
self._start_ioucon()
|
||||||
|
# connections support
|
||||||
|
self._start_iouyap()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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 = None
|
||||||
|
|
||||||
|
# stop iouyap
|
||||||
|
if self.is_iouyap_running():
|
||||||
|
log.info("stopping iouyap PID={} for IOU instance {}".format(self._iouyap_process.pid, self._id))
|
||||||
|
try:
|
||||||
|
self._iouyap_process.terminate()
|
||||||
|
self._iouyap_process.wait(1)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self._iouyap_process.kill()
|
||||||
|
if self._iouyap_process.poll() == None:
|
||||||
|
log.warn("iouyap PID={} for IOU instance {} is still running".format(self._iouyap_process.pid,
|
||||||
|
self._id))
|
||||||
|
self._iouyap_process = None
|
||||||
|
|
||||||
|
def read_stdout(self):
|
||||||
|
"""
|
||||||
|
Reads the standard output of the IOU process.
|
||||||
|
Only use when the process has been stopped or has crashed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Checks if the IOU process is running
|
||||||
|
|
||||||
|
:returns: True or False
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._process and self._process.poll() == None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_iouyap_running(self):
|
||||||
|
"""
|
||||||
|
Checks if the iouyap process is running
|
||||||
|
|
||||||
|
:returns: True or False
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._iouyap_process and self._iouyap_process.poll() == None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def slot_add_nio_binding(self, slot_id, port_id, nio):
|
||||||
|
"""
|
||||||
|
Adds a slot NIO binding.
|
||||||
|
|
||||||
|
:param slot_id: slot ID
|
||||||
|
:param port_id: port ID
|
||||||
|
:param nio: NIO instance to add to the slot/port
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
adapter = self._slots[slot_id]
|
||||||
|
except IndexError:
|
||||||
|
raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
|
||||||
|
slot_id=slot_id))
|
||||||
|
|
||||||
|
if not adapter.port_exists(port_id):
|
||||||
|
raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
||||||
|
port_id=port_id))
|
||||||
|
|
||||||
|
adapter.add_nio(port_id, nio)
|
||||||
|
log.info("IOU {name} [id={id}]: {nio} added to {slot_id}/{port_id}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
nio=nio,
|
||||||
|
slot_id=slot_id,
|
||||||
|
port_id=port_id))
|
||||||
|
if self.is_iouyap_running():
|
||||||
|
self._update_iouyap_config()
|
||||||
|
os.kill(self._iouyap_process.pid, signal.SIGHUP)
|
||||||
|
|
||||||
|
def slot_remove_nio_binding(self, slot_id, port_id):
|
||||||
|
"""
|
||||||
|
Removes a slot NIO binding.
|
||||||
|
|
||||||
|
:param slot_id: slot ID
|
||||||
|
:param port_id: port ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
adapter = self._slots[slot_id]
|
||||||
|
except IndexError:
|
||||||
|
raise IOUError("Slot {slot_id} doesn't exist on IOU {name}".format(name=self._name,
|
||||||
|
slot_id=slot_id))
|
||||||
|
|
||||||
|
if not adapter.port_exists(port_id):
|
||||||
|
raise IOUError("Port {port_id} doesn't exist in adapter {adapter}".format(adapter=adapter,
|
||||||
|
port_id=port_id))
|
||||||
|
|
||||||
|
nio = adapter.get_nio(port_id)
|
||||||
|
adapter.remove_nio(port_id)
|
||||||
|
log.info("IOU {name} [id={id}]: {nio} removed from {slot_id}/{port_id}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
nio=nio,
|
||||||
|
slot_id=slot_id,
|
||||||
|
port_id=port_id))
|
||||||
|
if self.is_iouyap_running():
|
||||||
|
self._update_iouyap_config()
|
||||||
|
os.kill(self._iouyap_process.pid, signal.SIGHUP)
|
||||||
|
|
||||||
|
def _build_command(self):
|
||||||
|
"""
|
||||||
|
Command to start the IOU process.
|
||||||
|
(to be passed to subprocess.Popen())
|
||||||
|
|
||||||
|
IOU command line:
|
||||||
|
Usage: <image> [options] <application id>
|
||||||
|
<image>: unix-js-m | unix-is-m | unix-i-m | ...
|
||||||
|
<application id>: instance identifier (0 < id <= 1024)
|
||||||
|
Options:
|
||||||
|
-e <n> Number of Ethernet interfaces (default 2)
|
||||||
|
-s <n> Number of Serial interfaces (default 2)
|
||||||
|
-n <n> Size of nvram in Kb (default 64KB)
|
||||||
|
-b <string> IOS debug string
|
||||||
|
-c <name> Configuration file name
|
||||||
|
-d Generate debug information
|
||||||
|
-t Netio message trace
|
||||||
|
-q Suppress informational messages
|
||||||
|
-h Display this help
|
||||||
|
-C Turn off use of host clock
|
||||||
|
-m <n> Megabytes of router memory (default 256MB)
|
||||||
|
-L Disable local console, use remote console
|
||||||
|
-l Enable Layer 1 keepalive messages
|
||||||
|
-u <n> UDP port base for distributed networks
|
||||||
|
-R Ignore options from the IOURC file
|
||||||
|
-U Disable unix: file system location
|
||||||
|
-W Disable watchdog timer
|
||||||
|
-N Ignore the NETMAP file
|
||||||
|
"""
|
||||||
|
|
||||||
|
#TODO: add support for keepalive and watchdog
|
||||||
|
command = [self._path]
|
||||||
|
if len(self._ethernet_adapters) != 2:
|
||||||
|
command.extend(["-e", str(len(self._ethernet_adapters))])
|
||||||
|
if len(self._serial_adapters) != 2:
|
||||||
|
command.extend(["-s", str(len(self._serial_adapters))])
|
||||||
|
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])
|
||||||
|
command.extend([str(self._id)])
|
||||||
|
return command
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ram(self):
|
||||||
|
"""
|
||||||
|
Returns the amount of RAM allocated to this IOU instance.
|
||||||
|
|
||||||
|
:returns: amount of RAM in Mbytes (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._ram
|
||||||
|
|
||||||
|
@ram.setter
|
||||||
|
def ram(self, ram):
|
||||||
|
"""
|
||||||
|
Sets amount of RAM allocated to this IOU instance.
|
||||||
|
|
||||||
|
:param ram: amount of RAM in Mbytes (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._ram == ram:
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info("IOU {name} [id={id}]: RAM updated from {old_ram}MB to {new_ram}MB".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
old_ram=self._ram,
|
||||||
|
new_ram=ram))
|
||||||
|
|
||||||
|
self._ram = ram
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nvram(self):
|
||||||
|
"""
|
||||||
|
Returns the mount of NVRAM allocated to this IOU instance.
|
||||||
|
|
||||||
|
:returns: amount of NVRAM in Kbytes (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._nvram
|
||||||
|
|
||||||
|
@nvram.setter
|
||||||
|
def nvram(self, nvram):
|
||||||
|
"""
|
||||||
|
Sets amount of NVRAM allocated to this IOU instance.
|
||||||
|
|
||||||
|
:param nvram: amount of NVRAM in Kbytes (integer)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._nvram == nvram:
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info("IOU {name} [id={id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
old_nvram=self._nvram,
|
||||||
|
new_nvram=nvram))
|
||||||
|
self._nvram = nvram
|
||||||
|
|
||||||
|
@property
|
||||||
|
def startup_config(self):
|
||||||
|
"""
|
||||||
|
Returns the startup-config for this IOU instance.
|
||||||
|
|
||||||
|
: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 IOU instance.
|
||||||
|
|
||||||
|
:param startup_config: path to startup-config file
|
||||||
|
"""
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ethernet_adapters(self):
|
||||||
|
"""
|
||||||
|
Returns the number of Ethernet adapters for this IOU instance.
|
||||||
|
|
||||||
|
:returns: number of adapters
|
||||||
|
"""
|
||||||
|
|
||||||
|
return len(self._ethernet_adapters)
|
||||||
|
|
||||||
|
@ethernet_adapters.setter
|
||||||
|
def ethernet_adapters(self, ethernet_adapters):
|
||||||
|
"""
|
||||||
|
Sets the number of Ethernet adapters for this IOU instance.
|
||||||
|
|
||||||
|
:param ethernet_adapters: number of adapters
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._ethernet_adapters.clear()
|
||||||
|
for _ in range(0, ethernet_adapters):
|
||||||
|
self._ethernet_adapters.append(EthernetAdapter())
|
||||||
|
|
||||||
|
log.info("IOU {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
adapters=len(self._ethernet_adapters)))
|
||||||
|
|
||||||
|
self._slots = self._ethernet_adapters + self._serial_adapters
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serial_adapters(self):
|
||||||
|
"""
|
||||||
|
Returns the number of Serial adapters for this IOU instance.
|
||||||
|
|
||||||
|
:returns: number of adapters
|
||||||
|
"""
|
||||||
|
|
||||||
|
return len(self._serial_adapters)
|
||||||
|
|
||||||
|
@serial_adapters.setter
|
||||||
|
def serial_adapters(self, serial_adapters):
|
||||||
|
"""
|
||||||
|
Sets the number of Serial adapters for this IOU instance.
|
||||||
|
|
||||||
|
:param serial_adapters: number of adapters
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._serial_adapters.clear()
|
||||||
|
for _ in range(0, serial_adapters):
|
||||||
|
self._serial_adapters.append(SerialAdapter())
|
||||||
|
|
||||||
|
log.info("IOU {name} [id={id}]: number of Serial adapters changed to {adapters}".format(name=self._name,
|
||||||
|
id=self._id,
|
||||||
|
adapters=len(self._serial_adapters)))
|
||||||
|
|
||||||
|
self._slots = self._ethernet_adapters + self._serial_adapters
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_unused_port(start_port, end_port, host='127.0.0.1', socket_type="TCP"):
|
||||||
|
"""
|
||||||
|
Finds an unused port in the specified 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 IOUError("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 IOUError("Could not find an unused port: {}".format(e))
|
37
gns3server/modules/iou/iou_error.py
Normal file
37
gns3server/modules/iou/iou_error.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Custom exceptions for IOU module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IOUError(Exception):
|
||||||
|
|
||||||
|
def __init__(self, message, original_exception=None):
|
||||||
|
|
||||||
|
Exception.__init__(self, message)
|
||||||
|
self._message = message
|
||||||
|
self._original_exception = original_exception
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
|
||||||
|
return self._message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return self._message
|
645
gns3server/modules/iou/ioucon.py
Normal file
645
gns3server/modules/iou/ioucon.py
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Contribution from James Carpenter
|
||||||
|
#
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import select
|
||||||
|
import fcntl
|
||||||
|
import struct
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Escape characters
|
||||||
|
ESC_CHAR = '^^' # can be overriden from command line
|
||||||
|
ESC_QUIT = 'q'
|
||||||
|
|
||||||
|
# IOU seems to only send *1* byte at a time. If
|
||||||
|
# they ever fix that we'll be ready for it.
|
||||||
|
BUFFER_SIZE = 1024
|
||||||
|
|
||||||
|
# How long to wait before retrying a connection (seconds)
|
||||||
|
RETRY_DELAY = 3
|
||||||
|
|
||||||
|
# How often to test an idle connection (seconds)
|
||||||
|
POLL_TIMEOUT = 3
|
||||||
|
|
||||||
|
|
||||||
|
EXIT_SUCCESS = 0
|
||||||
|
EXIT_FAILURE = 1
|
||||||
|
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
|
||||||
|
#--[ 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
|
||||||
|
LINEMO = 34 # Line Mode
|
||||||
|
|
||||||
|
|
||||||
|
class FileLock:
|
||||||
|
|
||||||
|
# struct flock { /* from fcntl(2) */
|
||||||
|
# ...
|
||||||
|
# short l_type; /* Type of lock: F_RDLCK,
|
||||||
|
# F_WRLCK, F_UNLCK */
|
||||||
|
# short l_whence; /* How to interpret l_start:
|
||||||
|
# SEEK_SET, SEEK_CUR, SEEK_END */
|
||||||
|
# off_t l_start; /* Starting offset for lock */
|
||||||
|
# off_t l_len; /* Number of bytes to lock */
|
||||||
|
# pid_t l_pid; /* PID of process blocking our lock
|
||||||
|
# (F_GETLK only) */
|
||||||
|
# ...
|
||||||
|
# };
|
||||||
|
_flock = struct.Struct('hhqql')
|
||||||
|
|
||||||
|
def __init__(self, fname=None):
|
||||||
|
self.fd = None
|
||||||
|
self.fname = fname
|
||||||
|
|
||||||
|
def get_lock(self):
|
||||||
|
flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET,
|
||||||
|
0, 0, os.getpid())
|
||||||
|
flk = self._flock.unpack(
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_GETLK, flk))
|
||||||
|
|
||||||
|
# If it's not locked (or is locked by us) then return None,
|
||||||
|
# otherwise return the PID of the owner.
|
||||||
|
if flk[0] == fcntl.F_UNLCK:
|
||||||
|
return None
|
||||||
|
return flk[4]
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
try:
|
||||||
|
self.fd = open('{}.lck'.format(self.fname), 'a')
|
||||||
|
except Exception as e:
|
||||||
|
raise LockError("Couldn't get lock on {}: {}"
|
||||||
|
.format(self.fname, e))
|
||||||
|
|
||||||
|
flk = self._flock.pack(fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0)
|
||||||
|
try:
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETLK, flk)
|
||||||
|
except BlockingIOError:
|
||||||
|
raise LockError("Already connected. PID {} has lock on {}"
|
||||||
|
.format(self.get_lock(), self.fname))
|
||||||
|
|
||||||
|
# If we got here then we must have the lock. Store the PID.
|
||||||
|
self.fd.truncate(0)
|
||||||
|
self.fd.write('{}\n'.format(os.getpid()))
|
||||||
|
self.fd.flush()
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
if self.fd:
|
||||||
|
# Deleting first prevents a race condition
|
||||||
|
os.unlink(self.fd.name)
|
||||||
|
self.fd.close()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.lock()
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.unlock()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Console:
|
||||||
|
def fileno(self):
|
||||||
|
raise NotImplementedError("Only routers have fileno()")
|
||||||
|
|
||||||
|
|
||||||
|
class Router:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TTY(Console):
|
||||||
|
|
||||||
|
def read(self, fileno, bufsize):
|
||||||
|
return self.fd.read(bufsize)
|
||||||
|
|
||||||
|
def write(self, buf):
|
||||||
|
return self.fd.write(buf)
|
||||||
|
|
||||||
|
def register(self, epoll):
|
||||||
|
self.epoll = epoll
|
||||||
|
epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
try:
|
||||||
|
self.fd = open('/dev/tty', 'r+b', buffering=0)
|
||||||
|
except OSError as e:
|
||||||
|
raise TTYError("Couldn't open controlling TTY: {}".format(e))
|
||||||
|
|
||||||
|
# Save original flags
|
||||||
|
self.termios = termios.tcgetattr(self.fd)
|
||||||
|
self.fcntl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
|
||||||
|
|
||||||
|
# Update flags
|
||||||
|
tty.setraw(self.fd, termios.TCSANOW)
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
|
||||||
|
# Restore flags to original settings
|
||||||
|
termios.tcsetattr(self.fd, termios.TCSANOW, self.termios)
|
||||||
|
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.fcntl)
|
||||||
|
|
||||||
|
self.fd.close()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TelnetServer(Console):
|
||||||
|
|
||||||
|
def __init__(self, addr, port, stop_event):
|
||||||
|
self.addr = addr
|
||||||
|
self.port = port
|
||||||
|
self.fd_dict = {}
|
||||||
|
self.stop_event = stop_event
|
||||||
|
|
||||||
|
def read(self, fileno, bufsize):
|
||||||
|
# Someone wants to connect?
|
||||||
|
if fileno == self.sock_fd.fileno():
|
||||||
|
self._accept()
|
||||||
|
return None
|
||||||
|
|
||||||
|
self._cur_fileno = fileno
|
||||||
|
|
||||||
|
# Read a maximum of _bufsize_ bytes without blocking. When it
|
||||||
|
# would want to block it means there's no more data. An empty
|
||||||
|
# buffer normally means that we've been disconnected.
|
||||||
|
try:
|
||||||
|
buf = self._read_cur(bufsize, socket.MSG_DONTWAIT)
|
||||||
|
except BlockingIOError:
|
||||||
|
return None
|
||||||
|
if not buf:
|
||||||
|
self._disconnect(fileno)
|
||||||
|
|
||||||
|
# Process and remove any telnet commands from the buffer
|
||||||
|
if IAC in buf:
|
||||||
|
buf = self._IAC_parser(buf)
|
||||||
|
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def write(self, buf):
|
||||||
|
for fd in self.fd_dict.values():
|
||||||
|
fd.send(buf)
|
||||||
|
|
||||||
|
def register(self, epoll):
|
||||||
|
self.epoll = epoll
|
||||||
|
epoll.register(self.sock_fd, select.EPOLLIN)
|
||||||
|
|
||||||
|
def _read_block(self, bufsize):
|
||||||
|
buf = self._read_cur(bufsize, socket.MSG_WAITALL)
|
||||||
|
# If we don't get everything we were looking for then the
|
||||||
|
# client probably disconnected.
|
||||||
|
if len(buf) < bufsize:
|
||||||
|
self._disconnect(self._cur_fileno)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _read_cur(self, bufsize, flags):
|
||||||
|
return self.fd_dict[self._cur_fileno].recv(bufsize, flags)
|
||||||
|
|
||||||
|
def _write_cur(self, buf):
|
||||||
|
return self.fd_dict[self._cur_fileno].send(buf)
|
||||||
|
|
||||||
|
def _IAC_parser(self, buf):
|
||||||
|
skip_to = 0
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
# Locate an IAC to process
|
||||||
|
iac_loc = buf.find(IAC, skip_to)
|
||||||
|
if iac_loc < 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Get the TELNET command
|
||||||
|
iac_cmd = bytearray([IAC])
|
||||||
|
try:
|
||||||
|
iac_cmd.append(buf[iac_loc + 1])
|
||||||
|
except IndexError:
|
||||||
|
buf.extend(self._read_block(1))
|
||||||
|
iac_cmd.append(buf[iac_loc + 1])
|
||||||
|
|
||||||
|
# Is this just a 2-byte TELNET command?
|
||||||
|
if iac_cmd[1] not in [WILL, WONT, DO, DONT]:
|
||||||
|
if iac_cmd[1] == AYT:
|
||||||
|
log.debug("Telnet server received Are-You-There (AYT)")
|
||||||
|
self._write_cur(
|
||||||
|
b'\r\nYour Are-You-There received. I am here.\r\n'
|
||||||
|
)
|
||||||
|
elif iac_cmd[1] == IAC:
|
||||||
|
# It's data, not an IAC
|
||||||
|
iac_cmd.pop()
|
||||||
|
# This prevents the 0xff from being
|
||||||
|
# interputed as yet another IAC
|
||||||
|
skip_to = iac_loc + 1
|
||||||
|
log.debug("Received IAC IAC")
|
||||||
|
elif iac_cmd[1] == NOP:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
log.debug("Unhandled telnet command: "
|
||||||
|
"{0:#x} {1:#x}".format(*iac_cmd))
|
||||||
|
|
||||||
|
# This must be a 3-byte TELNET command
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
iac_cmd.append(buf[iac_loc + 2])
|
||||||
|
except IndexError:
|
||||||
|
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]):
|
||||||
|
|
||||||
|
self._write_cur(bytes([IAC, WONT, iac_cmd[2]]))
|
||||||
|
log.debug("Telnet WON'T {:#x}".format(iac_cmd[2]))
|
||||||
|
else:
|
||||||
|
log.debug("Unhandled telnet command: "
|
||||||
|
"{0:#x} {1:#x} {2:#x}".format(*iac_cmd))
|
||||||
|
|
||||||
|
# Remove the entire TELNET command from the buffer
|
||||||
|
buf = buf.replace(iac_cmd, b'', 1)
|
||||||
|
|
||||||
|
# Return the new copy of the buffer, minus telnet commands
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _accept(self):
|
||||||
|
fd, addr = self.sock_fd.accept()
|
||||||
|
self.fd_dict[fd.fileno()] = fd
|
||||||
|
self.epoll.register(fd, select.EPOLLIN | select.EPOLLET)
|
||||||
|
|
||||||
|
log.info("Telnet connection from {}:{}".format(addr[0], addr[1]))
|
||||||
|
|
||||||
|
# This is a one-way negotiation. This is very basic so there
|
||||||
|
# shouldn't be any problems with any decent client.
|
||||||
|
fd.send(bytes([IAC, WILL, ECHO,
|
||||||
|
IAC, WILL, SGA,
|
||||||
|
IAC, WILL, 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')
|
||||||
|
self._disconnect(fd.fileno())
|
||||||
|
log.warn("Client disconnected because of too many connections. "
|
||||||
|
"(limit currently {})".format(args.telnet_limit))
|
||||||
|
|
||||||
|
def _disconnect(self, fileno):
|
||||||
|
fd = self.fd_dict.pop(fileno)
|
||||||
|
log.info("Telnet client disconnected")
|
||||||
|
fd.shutdown(socket.SHUT_RDWR)
|
||||||
|
fd.close()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
# Open a socket and start listening
|
||||||
|
sock_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
try:
|
||||||
|
sock_fd.bind((self.addr, self.port))
|
||||||
|
except OSError:
|
||||||
|
raise TelnetServerError("Cannot bind to {}:{}"
|
||||||
|
.format(self.addr, self.port))
|
||||||
|
|
||||||
|
sock_fd.listen(socket.SOMAXCONN)
|
||||||
|
self.sock_fd = sock_fd
|
||||||
|
log.info("Telnet server ready for connections on {}:{}".format(self.addr, self.port))
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
for fileno in list(self.fd_dict.keys()):
|
||||||
|
self._disconnect(fileno)
|
||||||
|
self.sock_fd.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IOU(Router):
|
||||||
|
|
||||||
|
def __init__(self, ttyC, ttyS, stop_event):
|
||||||
|
self.ttyC = ttyC
|
||||||
|
self.ttyS = ttyS
|
||||||
|
self.stop_event = stop_event
|
||||||
|
|
||||||
|
def read(self, bufsize):
|
||||||
|
try:
|
||||||
|
buf = self.fd.recv(bufsize)
|
||||||
|
except BlockingIOError:
|
||||||
|
return None
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def write(self, buf):
|
||||||
|
self.fd.send(buf)
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
self.fd = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
self.fd.setblocking(False)
|
||||||
|
|
||||||
|
def _bind(self):
|
||||||
|
try:
|
||||||
|
os.unlink(self.ttyC)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as 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))
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# Keep trying until we connect or die trying
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
try:
|
||||||
|
self.fd.connect(self.ttyS)
|
||||||
|
except FileNotFoundError:
|
||||||
|
log.debug("Waiting to connect to {}".format(self.ttyS),
|
||||||
|
file=sys.stderr)
|
||||||
|
time.sleep(RETRY_DELAY)
|
||||||
|
except Exception as e:
|
||||||
|
raise NetioError("Couldn't connect to socket {}: {}"
|
||||||
|
.format(self.ttyS, e))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
def register(self, epoll):
|
||||||
|
self.epoll = epoll
|
||||||
|
epoll.register(self.fd, select.EPOLLIN | select.EPOLLET)
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
return self.fd.fileno()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._open()
|
||||||
|
self._bind()
|
||||||
|
self._connect()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
os.unlink(self.ttyC)
|
||||||
|
self.fd.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IOUConError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LockError(IOUConError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NetioError(IOUConError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TTYError(IOUConError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TelnetServerError(IOUConError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigError(IOUConError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def mkdir_netio(netio_dir):
|
||||||
|
try:
|
||||||
|
os.mkdir(netio_dir)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
raise NetioError("Couldn't create directory {}: {}"
|
||||||
|
.format(netio_dir, e))
|
||||||
|
|
||||||
|
|
||||||
|
def send_recv_loop(console, router, esc_char, stop_event):
|
||||||
|
|
||||||
|
epoll = select.epoll()
|
||||||
|
router.register(epoll)
|
||||||
|
console.register(epoll)
|
||||||
|
|
||||||
|
router_fileno = router.fileno()
|
||||||
|
esc_quit = bytes(ESC_QUIT.upper(), 'ascii')
|
||||||
|
esc_state = False
|
||||||
|
|
||||||
|
while not stop_event.is_set():
|
||||||
|
event_list = epoll.poll(timeout=POLL_TIMEOUT)
|
||||||
|
|
||||||
|
# When/if the poll times out we send an empty datagram. If IOU
|
||||||
|
# has gone away then this will toss a ConnectionRefusedError
|
||||||
|
# exception.
|
||||||
|
if not event_list:
|
||||||
|
router.write(b'')
|
||||||
|
continue
|
||||||
|
|
||||||
|
for fileno, event in event_list:
|
||||||
|
buf = bytearray()
|
||||||
|
|
||||||
|
# IOU --> tty(s)
|
||||||
|
if fileno == router_fileno:
|
||||||
|
while not stop_event.is_set():
|
||||||
|
data = router.read(BUFFER_SIZE)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
buf.extend(data)
|
||||||
|
console.write(buf)
|
||||||
|
|
||||||
|
# tty --> IOU
|
||||||
|
else:
|
||||||
|
while not stop_event.is_set():
|
||||||
|
data = console.read(fileno, BUFFER_SIZE)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
buf.extend(data)
|
||||||
|
|
||||||
|
# If we just received the escape character then
|
||||||
|
# enter the escape state.
|
||||||
|
#
|
||||||
|
# If we are in the escape state then check for a
|
||||||
|
# quit command. Or if it's the escape character then
|
||||||
|
# send the escape character. Else, send the escape
|
||||||
|
# character we ate earlier and whatever character we
|
||||||
|
# just got. Exit escape state.
|
||||||
|
#
|
||||||
|
# If we're not in the escape state and this isn't an
|
||||||
|
# escape character then just send it to IOU.
|
||||||
|
if esc_state:
|
||||||
|
if buf.upper() == esc_quit:
|
||||||
|
sys.exit(EXIT_SUCCESS)
|
||||||
|
elif buf == esc_char:
|
||||||
|
router.write(esc_char)
|
||||||
|
else:
|
||||||
|
router.write(esc_char)
|
||||||
|
router.write(buf)
|
||||||
|
esc_state = False
|
||||||
|
elif buf == esc_char:
|
||||||
|
esc_state = True
|
||||||
|
else:
|
||||||
|
router.write(buf)
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Connect to an IOU console port.')
|
||||||
|
parser.add_argument('-d', '--debug', action='store_true',
|
||||||
|
help='display some debugging information')
|
||||||
|
parser.add_argument('-e', '--escape',
|
||||||
|
help='set escape character (default: %(default)s)',
|
||||||
|
default=ESC_CHAR, metavar='CHAR')
|
||||||
|
parser.add_argument('-t', '--telnet-server',
|
||||||
|
help='start telnet server listening on ADDR:PORT',
|
||||||
|
metavar='ADDR:PORT', default=False)
|
||||||
|
parser.add_argument('-l', '--telnet-limit',
|
||||||
|
help='maximum number of simultaneous '
|
||||||
|
'telnet connections (default: %(default)s)',
|
||||||
|
metavar='LIMIT', type=int, default=1)
|
||||||
|
parser.add_argument('appl_id', help='IOU instance identifier')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def get_escape_character(escape):
|
||||||
|
|
||||||
|
# Figure out the escape character to use.
|
||||||
|
# Can be any ASCII character or a spelled out control
|
||||||
|
# character, like "^e". The string "none" disables it.
|
||||||
|
if escape.lower() == 'none':
|
||||||
|
esc_char = b''
|
||||||
|
elif len(escape) == 2 and escape[0] == '^':
|
||||||
|
c = ord(escape[1].upper()) - 0x40
|
||||||
|
if not 0 <= c <= 0x1f: # control code range
|
||||||
|
raise ConfigError("Invalid control code")
|
||||||
|
esc_char = bytes([c])
|
||||||
|
elif len(escape) == 1:
|
||||||
|
try:
|
||||||
|
esc_char = bytes(escape, 'ascii')
|
||||||
|
except ValueError as e:
|
||||||
|
raise ConfigError("Invalid escape character") from e
|
||||||
|
else:
|
||||||
|
raise ConfigError("Invalid length for escape character")
|
||||||
|
|
||||||
|
return esc_char
|
||||||
|
|
||||||
|
|
||||||
|
def start_ioucon(cmdline_args, stop_event):
|
||||||
|
|
||||||
|
global args
|
||||||
|
args = cmdline_args
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
else:
|
||||||
|
# default logging level
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
# Create paths for the Unix domain sockets
|
||||||
|
netio = '/tmp/netio{}'.format(os.getuid())
|
||||||
|
ttyC = '{}/ttyC{}'.format(netio, args.appl_id)
|
||||||
|
ttyS = '{}/ttyS{}'.format(netio, args.appl_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mkdir_netio(netio)
|
||||||
|
with FileLock(ttyC):
|
||||||
|
esc_char = get_escape_character(args.escape)
|
||||||
|
|
||||||
|
if args.telnet_server:
|
||||||
|
addr, _, port = args.telnet_server.partition(':')
|
||||||
|
nport = 0
|
||||||
|
try:
|
||||||
|
nport = int(port)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if (addr == '' or nport == 0):
|
||||||
|
raise ConfigError('format for --telnet-server must be '
|
||||||
|
'ADDR:PORT (like 127.0.0.1:20000)')
|
||||||
|
|
||||||
|
while not stop_event.is_set():
|
||||||
|
try:
|
||||||
|
if args.telnet_server:
|
||||||
|
with TelnetServer(addr, nport, stop_event) as console:
|
||||||
|
with IOU(ttyC, ttyS, stop_event) as router:
|
||||||
|
send_recv_loop(console, router, b'', stop_event)
|
||||||
|
else:
|
||||||
|
with TTY() as console, IOU(ttyC, ttyS, stop_event) as router:
|
||||||
|
send_recv_loop(console, router, esc_char, stop_event)
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(EXIT_ABORT)
|
||||||
|
finally:
|
||||||
|
# Put us at the beginning of a line
|
||||||
|
if not args.telnet_server:
|
||||||
|
print()
|
||||||
|
|
||||||
|
except IOUConError as e:
|
||||||
|
if args.debug:
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
sys.exit(EXIT_FAILURE)
|
||||||
|
|
||||||
|
log.info("exiting...")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
import threading
|
||||||
|
stop_event = threading.Event()
|
||||||
|
args = get_args()
|
||||||
|
start_ioucon(args, stop_event)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
gns3server/modules/iou/nios/__init__.py
Normal file
0
gns3server/modules/iou/nios/__init__.py
Normal file
75
gns3server/modules/iou/nios/nio_udp.py
Normal file
75
gns3server/modules/iou/nios/nio_udp.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface for UDP NIOs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NIO_UDP(object):
|
||||||
|
"""
|
||||||
|
IOU UDP NIO.
|
||||||
|
|
||||||
|
:param lport: local port number
|
||||||
|
:param rhost: remote address/host
|
||||||
|
:param rport: remote port number
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instance_count = 0
|
||||||
|
|
||||||
|
def __init__(self, lport, rhost, rport):
|
||||||
|
|
||||||
|
self._lport = lport
|
||||||
|
self._rhost = rhost
|
||||||
|
self._rport = rport
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lport(self):
|
||||||
|
"""
|
||||||
|
Returns the local port
|
||||||
|
|
||||||
|
:returns: local port number
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._lport
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rhost(self):
|
||||||
|
"""
|
||||||
|
Returns the remote host
|
||||||
|
|
||||||
|
:returns: remote address/host
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._rhost
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rport(self):
|
||||||
|
"""
|
||||||
|
Returns the remote port
|
||||||
|
|
||||||
|
:returns: remote port number
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self._rport
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
|
||||||
|
return "NIO UDP"
|
@ -16,7 +16,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Set up and run the server
|
Set up and run the server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import zmq
|
import zmq
|
||||||
@ -24,6 +24,7 @@ from zmq.eventloop import ioloop, zmqstream
|
|||||||
ioloop.install()
|
ioloop.install()
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import tempfile
|
||||||
import signal
|
import signal
|
||||||
import errno
|
import errno
|
||||||
import functools
|
import functools
|
||||||
@ -31,8 +32,10 @@ import socket
|
|||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.autoreload
|
import tornado.autoreload
|
||||||
|
from .config import Config
|
||||||
from .handlers.jsonrpc_websocket import JSONRPCWebSocket
|
from .handlers.jsonrpc_websocket import JSONRPCWebSocket
|
||||||
from .handlers.version_handler import VersionHandler
|
from .handlers.version_handler import VersionHandler
|
||||||
|
from .handlers.file_upload_handler import FileUploadHandler
|
||||||
from .module_manager import ModuleManager
|
from .module_manager import ModuleManager
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -42,7 +45,8 @@ log = logging.getLogger(__name__)
|
|||||||
class Server(object):
|
class Server(object):
|
||||||
|
|
||||||
# built-in handlers
|
# built-in handlers
|
||||||
handlers = [(r"/version", VersionHandler)]
|
handlers = [(r"/version", VersionHandler),
|
||||||
|
(r"/upload", FileUploadHandler)]
|
||||||
|
|
||||||
def __init__(self, host, port, ipc=False):
|
def __init__(self, host, port, ipc=False):
|
||||||
|
|
||||||
@ -62,6 +66,20 @@ class Server(object):
|
|||||||
self._ipc = ipc
|
self._ipc = ipc
|
||||||
self._modules = []
|
self._modules = []
|
||||||
|
|
||||||
|
# get the projects and temp directories from the configuration file (passed to the modules)
|
||||||
|
config = Config.instance()
|
||||||
|
server_config = config.get_default_section()
|
||||||
|
# default projects directory is "~/Documents/GNS3/projects"
|
||||||
|
self._projects_dir = os.path.expandvars(os.path.expanduser(server_config.get("projects_directory", "~/Documents/GNS3/projects")))
|
||||||
|
self._temp_dir = server_config.get("temporary_directory", tempfile.gettempdir())
|
||||||
|
|
||||||
|
if not os.path.exists(self._projects_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(self._projects_dir)
|
||||||
|
log.info("projects directory '{}' created".format(self._projects_dir))
|
||||||
|
except EnvironmentError as e:
|
||||||
|
log.error("could not create the projects directory {}: {}".format(self._projects_dir, e))
|
||||||
|
|
||||||
def load_modules(self):
|
def load_modules(self):
|
||||||
"""
|
"""
|
||||||
Loads the modules.
|
Loads the modules.
|
||||||
@ -73,7 +91,13 @@ class Server(object):
|
|||||||
module_manager = ModuleManager([module_path])
|
module_manager = ModuleManager([module_path])
|
||||||
module_manager.load_modules()
|
module_manager.load_modules()
|
||||||
for module in module_manager.get_all_modules():
|
for module in module_manager.get_all_modules():
|
||||||
instance = module_manager.activate_module(module, ("127.0.0.1", self._zmq_port))
|
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)
|
self._modules.append(instance)
|
||||||
destinations = instance.destinations()
|
destinations = instance.destinations()
|
||||||
for destination in destinations:
|
for destination in destinations:
|
||||||
|
3
setup.py
3
setup.py
@ -65,11 +65,8 @@ setup(
|
|||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 2",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.3",
|
"Programming Language :: Python :: 3.3",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
29
tests/iou/test_iou_device.py
Normal file
29
tests/iou/test_iou_device.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from gns3server.modules.iou import IOUDevice
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def iou(request):
|
||||||
|
|
||||||
|
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
iou_path = os.path.join(cwd, "i86bi_linux-ipbase-ms-12.4.bin")
|
||||||
|
iou_device = IOUDevice(iou_path, "/tmp")
|
||||||
|
iou_device.start()
|
||||||
|
request.addfinalizer(iou_device.delete)
|
||||||
|
return iou_device
|
||||||
|
|
||||||
|
|
||||||
|
def test_iou_is_started(iou):
|
||||||
|
|
||||||
|
print(iou.command())
|
||||||
|
assert iou.id == 1 # we should have only one IOU running!
|
||||||
|
assert iou.is_running()
|
||||||
|
|
||||||
|
|
||||||
|
def test_iou_restart(iou):
|
||||||
|
|
||||||
|
iou.stop()
|
||||||
|
assert not iou.is_running()
|
||||||
|
iou.start()
|
||||||
|
assert iou.is_running()
|
Loading…
Reference in New Issue
Block a user