diff --git a/.travis.yml b/.travis.yml
index bb9aceb5..07eee96b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,6 +23,4 @@ notifications:
- "chat.freenode.net#gns3"
on_success: change
on_failure: always
- use_notice: true
- skip_join: true
diff --git a/MANIFEST.in b/MANIFEST.in
index 9ca568b7..75d25e8a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -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]
diff --git a/gns3-server.py b/gns3-server.py
deleted file mode 100644
index c321ee48..00000000
--- a/gns3-server.py
+++ /dev/null
@@ -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 .
-
-# 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()
-
diff --git a/gns3server/__init__.py b/gns3server/__init__.py
index d98d3e92..d57df556 100644
--- a/gns3server/__init__.py
+++ b/gns3server/__init__.py
@@ -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)
diff --git a/gns3server/_compat.py b/gns3server/_compat.py
index c866e3b1..78bd53f8 100644
--- a/gns3server/_compat.py
+++ b/gns3server/_compat.py
@@ -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
diff --git a/gns3server/main.py b/gns3server/main.py
new file mode 100644
index 00000000..bbd57346
--- /dev/null
+++ b/gns3server/main.py
@@ -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 .
+
+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()
diff --git a/gns3server/plugin_manager.py b/gns3server/plugin_manager.py
new file mode 100644
index 00000000..f0f582f4
--- /dev/null
+++ b/gns3server/plugin_manager.py
@@ -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 .
+
+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
diff --git a/gns3server/plugins/__init__.py b/gns3server/plugins/__init__.py
new file mode 100644
index 00000000..c76c9983
--- /dev/null
+++ b/gns3server/plugins/__init__.py
@@ -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 .
+
+from gns3server.plugins.base import IPlugin
diff --git a/gns3server/plugins/base.py b/gns3server/plugins/base.py
new file mode 100644
index 00000000..6160fd1a
--- /dev/null
+++ b/gns3server/plugins/base.py
@@ -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 .
+
+
+class IPlugin(object):
+ """Plugin interface
+ """
+
+ def __init__(self):
+ pass
+
+ def setup(self):
+ """Called before the plugin is asked to do anything
+ """
+
+ raise NotImplementedError()
diff --git a/gns3server/plugins/dynamips/__init__.py b/gns3server/plugins/dynamips/__init__.py
new file mode 100644
index 00000000..3d667e39
--- /dev/null
+++ b/gns3server/plugins/dynamips/__init__.py
@@ -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 .
+
+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)]
diff --git a/gns3server/server.py b/gns3server/server.py
new file mode 100644
index 00000000..c46417ee
--- /dev/null
+++ b/gns3server/server.py
@@ -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 .
+
+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()
diff --git a/requirements.txt b/requirements.txt
index c43f03c4..ed626c89 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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
diff --git a/setup.py b/setup.py
index cf12b787..edeef1c1 100644
--- a/setup.py
+++ b/setup.py
@@ -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 .
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",
],
)
diff --git a/tests/test_version_handler.py b/tests/test_version_handler.py
new file mode 100644
index 00000000..19b09ed2
--- /dev/null
+++ b/tests/test_version_handler.py
@@ -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"
+
diff --git a/tox.ini b/tox.ini
index fe9bd531..cc5a7779 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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