Base server & plugin system & first unit tests

This commit is contained in:
grossmj 2013-10-30 15:58:17 -06:00
parent c6152c9503
commit 58f93edaf7
15 changed files with 411 additions and 96 deletions

View File

@ -23,6 +23,4 @@ notifications:
- "chat.freenode.net#gns3"
on_success: change
on_failure: always
use_notice: true
skip_join: true

View File

@ -6,6 +6,6 @@ include MANIFEST.in
include tox.ini
recursive-include tests *
recursive-include docs *
recursive-include gns3_server *
recursive-include gns3server *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

View File

@ -1,51 +0,0 @@
#!/usr/bin/env python
# -*- 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/>.
# Python 2.6 and 2.7 compatibility
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import sys
import tornado.ioloop
import tornado.web
import gns3server
from datetime import date
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Ready to serve")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
print("GNS3 server version {0}".format(gns3server.__version__))
print("Copyright (c) 2007-{0} GNS3 Technologies Inc.".format(date.today().year))
if sys.version_info < (2, 6):
raise RuntimeError("Python 2.6 or higher is required")
elif sys.version_info[0] == 3 and sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or higher is required")
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

View File

@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 GNS3 Technologies Inc.
#
@ -23,5 +23,8 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
from gns3server.plugin_manager import PluginManager
from gns3server.server import Server
__version__ = "0.1.dev"
__version_info__ = (0, 1, 0, -99)

View File

@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 GNS3 Technologies Inc.
#
@ -26,6 +26,11 @@ if not PY2:
string_types = (str,)
else:
unichr = unichr
text_type = unicode
range_type = xrange
string_types = (str, unicode)
text_type = unicode # @UndefinedVariable
range_type = xrange # @UndefinedVariable
string_types = (str, unicode) # @UndefinedVariable
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode

57
gns3server/main.py Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python
# -*- 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/>.
import datetime
import sys
import logging
import gns3server
import tornado.options
# command line options
from tornado.options import define
define("port", default=8000, help="run on the given port", type=int)
def main():
current_year = datetime.date.today().year
print("GNS3 server version {}".format(gns3server.__version__))
print("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
# we only support Python 2 version >= 2.7 and Python 3 version >= 3.3
if sys.version_info < (2, 7):
raise RuntimeError("Python 2.7 or higher is required")
elif sys.version_info[0] == 3 and sys.version_info < (3, 3):
raise RuntimeError("Python 3.3 or higher is required")
try:
tornado.options.parse_command_line()
except (tornado.options.Error, ValueError):
tornado.options.print_help()
raise SystemExit
#FIXME: log everything for now (excepting DEBUG)
logging.basicConfig(level=logging.INFO)
server = gns3server.Server()
server.load_plugins()
server.run()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,87 @@
# -*- 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/>.
import imp
import inspect
import pkgutil
import logging
from gns3server.plugins import IPlugin
logger = logging.getLogger(__name__)
class Plugin(object):
"""Plugin representation for the PluginManager
"""
def __init__(self, name, cls):
self._name = name
self._cls = cls
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
self._name = new_name
#@property
def cls(self):
return self._cls
class PluginManager(object):
"""Manages plugins
"""
def __init__(self, plugin_paths=['plugins']):
self._plugins = []
self._plugin_paths = plugin_paths
def load_plugins(self):
for _, name, ispkg in pkgutil.iter_modules(self._plugin_paths):
if (ispkg):
logger.info("analyzing '{}' package".format(name))
try:
file, pathname, description = imp.find_module(name, self._plugin_paths)
plugin_module = imp.load_module(name, file, pathname, description)
plugin_classes = inspect.getmembers(plugin_module, inspect.isclass)
for plugin_class in plugin_classes:
if issubclass(plugin_class[1], IPlugin):
# don't instantiate any parent plugins
if plugin_class[1].__module__ == name:
logger.info("loading '{}' plugin".format(plugin_class[0]))
info = Plugin(name=plugin_class[0], cls=plugin_class[1])
self._plugins.append(info)
finally:
if file:
file.close()
def get_all_plugins(self):
return self._plugins
def activate_plugin(self, plugin):
plugin_class = plugin.cls()
plugin_instance = plugin_class()
logger.info("'{}' plugin activated".format(plugin.name))
return plugin_instance

View File

@ -0,0 +1,18 @@
# -*- 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/>.
from gns3server.plugins.base import IPlugin

View File

@ -0,0 +1,30 @@
# -*- 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/>.
class IPlugin(object):
"""Plugin interface
"""
def __init__(self):
pass
def setup(self):
"""Called before the plugin is asked to do anything
"""
raise NotImplementedError()

View File

@ -0,0 +1,42 @@
# -*- 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/>.
import logging
import tornado.web
from gns3server.plugins import IPlugin
logger = logging.getLogger(__name__)
class TestHandler(tornado.web.RequestHandler):
def get(self):
self.write("This is my test handler")
class Dynamips(IPlugin):
def __init__(self):
IPlugin.__init__(self)
logger.info("Dynamips plugin is initializing")
def handlers(self):
"""Returns tornado web request handlers that the plugin manages
:returns: List of tornado.web.RequestHandler
"""
return [(r"/test", TestHandler)]

80
gns3server/server.py Normal file
View File

@ -0,0 +1,80 @@
# -*- 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/>.
import logging
import socket
import tornado.ioloop
import tornado.web
import gns3server
logger = logging.getLogger(__name__)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Welcome to the GNS3 server!")
class VersionHandler(tornado.web.RequestHandler):
def get(self):
response = {'version': gns3server.__version__}
self.write(response)
class Server(object):
# built-in handlers
handlers = [(r"/", MainHandler),
(r"/version", VersionHandler)]
def __init__(self):
self._plugins = []
def load_plugins(self):
"""Loads the plugins
"""
plugin_manager = gns3server.PluginManager()
plugin_manager.load_plugins()
for plugin in plugin_manager.get_all_plugins():
instance = plugin_manager.activate_plugin(plugin)
self._plugins.append(instance)
plugin_handlers = instance.handlers()
self.handlers.extend(plugin_handlers)
def run(self):
"""Starts the tornado web server
"""
from tornado.options import options
tornado_app = tornado.web.Application(self.handlers)
try:
port = options.port
print("Starting server on port {}".format(port))
tornado_app.listen(port)
except socket.error as e:
if e.errno is 48: # socket already in use
logging.critical("socket in use for port {}".format(port))
raise SystemExit
try:
tornado.ioloop.IOLoop.instance().start()
except (KeyboardInterrupt, SystemExit):
print("\nExiting...")
tornado.ioloop.IOLoop.instance().stop()

View File

@ -1,5 +1,3 @@
Yapsy==1.10.2-pythons2n3
astroid==1.0.0
logilab-common==0.60.0
networkx==1.8.1
tornado==3.1.1
tornado
jsonschema
networkx

View File

@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 GNS3 Technologies Inc.
#
@ -16,17 +16,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
import gns3server
class Tox(TestCommand):
def finalize_options(self):
TestCommand.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
#import here, cause outside the eggs aren't loaded
import tox
@ -34,32 +34,41 @@ class Tox(TestCommand):
sys.exit(errcode)
setup(
name = 'gns3-server',
scripts = ['gns3-server.py'],
version = gns3server.__version__,
url = 'http://github.com/GNS3/gns3-server',
license = 'GNU General Public License v3 (GPLv3)',
tests_require = ['tox'],
cmdclass = {'test': Tox},
install_requires = [],
author = 'Jeremy Grossmann',
author_email = 'package-maintainer@gns3.net',
description = 'GNS3 server with HTTP REST API to manage emulators',
long_description = open('README.rst', 'r').read(),
packages = find_packages(),
include_package_data = True,
platforms = 'any',
classifiers = [
'Development Status :: 1 - Planning',
'Environment :: Console',
'Intended Audience :: Information Technology',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
name="gns3-server",
version=__import__("gns3server").__version__,
url="http://github.com/GNS3/gns3-server",
license="GNU General Public License v3 (GPLv3)",
tests_require=["tox"],
cmdclass={"test": Tox},
author="Jeremy Grossmann",
author_email="package-maintainer@gns3.net",
description="GNS3 server with HTTP REST API to manage emulators",
long_description=open("README.rst", "r").read(),
install_requires=[
"tornado >= 2.0",
],
entry_points={
"console_scripts": [
"gns3server = gns3server.main:main",
]
},
packages=find_packages(),
include_package_data=True,
platforms="any",
classifiers=[
"Development Status :: 1 - Planning",
"Environment :: Console",
"Intended Audience :: Information Technology",
"Topic :: System :: Networking",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
'Natural Language :: English',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Topic :: System :: Networking'
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
)

View File

@ -0,0 +1,37 @@
from tornado.testing import AsyncHTTPTestCase
from gns3server.server import VersionHandler
from gns3server._compat import urlencode
import tornado.web
import json
# URL to test
URL = "/version"
class TestVersionHandler(AsyncHTTPTestCase):
def get_app(self):
return tornado.web.Application([(URL, VersionHandler)])
def test_endpoint(self):
self.http_client.fetch(self.get_url(URL), self.stop)
response = self.wait()
assert response.code == 200
# def test_post(self):
# data = urlencode({'test': 'works'})
# req = tornado.httpclient.HTTPRequest(self.get_url(URL),
# method='POST',
# body=data)
# self.http_client.fetch(req, self.stop)
# response = self.wait()
# assert response.code == 200
#
# def test_endpoint_differently(self):
# self.http_client.fetch(self.get_url(URL), self.stop)
# response = self.wait()
# assert(response.headers['Content-Type'].startswith('application/json'))
# assert(response.body != "")
# body = json.loads(response.body.decode('utf-8'))
# assert body['version'] == "0.1.dev"

View File

@ -1,6 +1,8 @@
[tox]
envlist = py26, py27, pypy, py33
envlist = py27, pypy, py33
[testenv]
deps = pytest
commands = py.test
commands = py.test [] -s tests
deps =
pytest
tornado