diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py
index 2c81fd95..c7b6dfcc 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -22,6 +22,9 @@ from ..config import Config
class Controller:
"""The controller manage multiple gns3 servers"""
+ def __init__(self):
+ self._servers = {}
+
def isEnabled(self):
"""
:returns: True if current instance is the controller
@@ -29,6 +32,19 @@ class Controller:
"""
return Config.instance().get_section_config("Server").getboolean("controller")
+ def addServer(self, server):
+ """
+ Add a server to the dictionnary of servers controlled by GNS3
+ """
+ self._servers[server.id] = server
+
+ @property
+ def servers(self):
+ """
+ :returns: The dictionnary of servers managed by GNS3
+ """
+ return self._servers
+
@staticmethod
def instance():
"""
diff --git a/gns3server/controller/server.py b/gns3server/controller/server.py
new file mode 100644
index 00000000..fef33c3d
--- /dev/null
+++ b/gns3server/controller/server.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 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 Server:
+ """
+ A GNS3 server.
+ """
+
+ def __init__(self, server_id, protocol="http", host="localhost", port=8000, user=None, password=None):
+ self._id = server_id
+ self._protocol = protocol
+ self._host = host
+ self._port = port
+ self._user = user
+ self._password = password
+ self._connected = False
+ # The remote server version
+ self._version = None
+
+ @property
+ def id(self):
+ """
+ :returns: Server identifier (string)
+ """
+ return self._id
+
+ @property
+ def host(self):
+ """
+ :returns: Server host (string)
+ """
+ return self._host
+
+ def __json__(self):
+ return {
+ "server_id": self._id,
+ "protocol": self._protocol,
+ "host": self._host,
+ "port": self._port,
+ "user": self._user,
+ "connected": self._connected,
+ "version": self._version
+ }
+
+ def __eq__(self, other):
+ if not isinstance(other, Server):
+ return False
+ return other._id == self._id
diff --git a/gns3server/handlers/api/server_handler.py b/gns3server/handlers/api/server_handler.py
index 105129f2..019a841e 100644
--- a/gns3server/handlers/api/server_handler.py
+++ b/gns3server/handlers/api/server_handler.py
@@ -15,18 +15,41 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import asyncio
+from aiohttp.web import HTTPForbidden
+
from ...web.route import Route
from ...config import Config
from ...modules.project_manager import ProjectManager
-from aiohttp.web import HTTPForbidden
+from ...schemas.server import SERVER_CREATE_SCHEMA, SERVER_OBJECT_SCHEMA
+from ...controller import Controller
+from ...controller.server import Server
+
-import asyncio
import logging
-
log = logging.getLogger(__name__)
class ServerHandler:
+ """API entry points for server management."""
+
+ @classmethod
+ @Route.post(
+ r"/servers",
+ description="Register a server",
+ status_codes={
+ 201: "Server added"
+ },
+ controller=True,
+ input=SERVER_CREATE_SCHEMA,
+ output=SERVER_OBJECT_SCHEMA)
+ def create(request, response):
+
+ server = Server(request.json.pop("server_id"), **request.json)
+ Controller.instance().addServer(server)
+
+ response.set_status(201)
+ response.json(server)
@classmethod
@Route.post(
@@ -60,7 +83,7 @@ class ServerHandler:
continue
# then shutdown the server itself
- from gns3server.server import Server
- server = Server.instance()
+ from gns3server.web.web_server import WebServer
+ server = WebServer.instance()
asyncio.async(server.shutdown_server())
response.set_status(201)
diff --git a/gns3server/run.py b/gns3server/run.py
index 4e86e356..1c140b01 100644
--- a/gns3server/run.py
+++ b/gns3server/run.py
@@ -28,7 +28,7 @@ import locale
import argparse
import asyncio
-from gns3server.server import Server
+from gns3server.web.web_server import WebServer
from gns3server.web.logger import init_logger
from gns3server.version import __version__
from gns3server.config import Config
@@ -233,7 +233,7 @@ def run():
host = server_config["host"]
port = int(server_config["port"])
- server = Server.instance(host, port)
+ server = WebServer.instance(host, port)
try:
server.run()
except OSError as e:
diff --git a/gns3server/schemas/server.py b/gns3server/schemas/server.py
new file mode 100644
index 00000000..8d3c88be
--- /dev/null
+++ b/gns3server/schemas/server.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 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 .
+
+
+SERVER_CREATE_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to register a GNS3 server instance",
+ "type": "object",
+ "properties": {
+ "server_id": {
+ "description": "Server identifier",
+ "type": "string"
+ },
+ "protocol": {
+ "description": "Server protocol",
+ "enum": ["http", "https"]
+ },
+ "host": {
+ "description": "Server host",
+ "type": "string"
+ },
+ "port": {
+ "description": "Server port",
+ "type": "integer"
+ },
+ "user": {
+ "description": "User for auth",
+ "type": "string"
+ },
+ "password": {
+ "description": "Password for auth",
+ "type": "string"
+ }
+ },
+ "additionalProperties": False,
+ "required": ["server_id", "protocol", "host", "port"]
+}
+
+SERVER_OBJECT_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Request validation to a GNS3 server object instance",
+ "type": "object",
+ "properties": {
+ "server_id": {
+ "description": "Server identifier",
+ "type": "string"
+ },
+ "protocol": {
+ "description": "Server protocol",
+ "enum": ["http", "https"]
+ },
+ "host": {
+ "description": "Server host",
+ "type": "string"
+ },
+ "port": {
+ "description": "Server port",
+ "type": "integer"
+ },
+ "user": {
+ "description": "User for auth",
+ "type": "string"
+ },
+ "connected": {
+ "description": "True if controller is connected to the server",
+ "type": "boolean"
+ },
+ "version": {
+ "description": "Version of the GNS3 remote server",
+ "type": ["string", "null"]
+ }
+ },
+ "additionalProperties": False,
+ "required": ["server_id", "protocol", "host", "port"]
+}
diff --git a/gns3server/server.py b/gns3server/web/web_server.py
similarity index 96%
rename from gns3server/server.py
rename to gns3server/web/web_server.py
index c7474c81..88188f1c 100644
--- a/gns3server/server.py
+++ b/gns3server/web/web_server.py
@@ -29,11 +29,11 @@ import types
import time
import atexit
-from .web.route import Route
-from .web.request_handler import RequestHandler
-from .config import Config
-from .modules import MODULES
-from .modules.port_manager import PortManager
+from .route import Route
+from .request_handler import RequestHandler
+from ..config import Config
+from ..modules import MODULES
+from ..modules.port_manager import PortManager
# do not delete this import
import gns3server.handlers
@@ -42,7 +42,7 @@ import logging
log = logging.getLogger(__name__)
-class Server:
+class WebServer:
def __init__(self, host, port):
@@ -61,11 +61,11 @@ class Server:
:returns: instance of Server
"""
- if not hasattr(Server, "_instance") or Server._instance is None:
+ if not hasattr(WebServer, "_instance") or WebServer._instance is None:
assert host is not None
assert port is not None
- Server._instance = Server(host, port)
- return Server._instance
+ WebServer._instance = WebServer(host, port)
+ return WebServer._instance
@asyncio.coroutine
def _run_application(self, handler, ssl_context=None):
diff --git a/tests/conftest.py b/tests/conftest.py
index c8bf4c04..ac8d1ec6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -40,6 +40,7 @@ from gns3server.handlers import *
from gns3server.modules import MODULES
from gns3server.modules.port_manager import PortManager
from gns3server.modules.project_manager import ProjectManager
+from gns3server.controller import Controller
from tests.handlers.api.base import Query
@@ -136,8 +137,14 @@ def ethernet_device():
return sorted(psutil.net_if_addrs().keys())[0]
+@pytest.fixture
+def controller():
+ Controller._instance = None
+ return Controller.instance()
+
+
@pytest.yield_fixture(autouse=True)
-def run_around_tests(monkeypatch, port_manager):
+def run_around_tests(monkeypatch, port_manager, controller):
"""
This setup a temporay project file environnement around tests
"""
@@ -151,7 +158,7 @@ def run_around_tests(monkeypatch, port_manager):
config.set("Server", "project_directory", os.path.join(tmppath, 'projects'))
config.set("Server", "images_path", os.path.join(tmppath, 'images'))
config.set("Server", "auth", False)
- config.set("Server", "controller", False)
+ config.set("Server", "controller", True)
# Prevent executions of the VM if we forgot to mock something
config.set("VirtualBox", "vboxmanage_path", tmppath)
diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py
index 5ef9e375..76accce1 100644
--- a/tests/controller/test_controller.py
+++ b/tests/controller/test_controller.py
@@ -18,19 +18,23 @@
import pytest
from gns3server.controller import Controller
+from gns3server.controller.server import Server
from gns3server.config import Config
-@pytest.fixture
-def controller():
- Controller._instance = None
- return Controller.instance()
-
-
-
def test_isEnabled(controller):
Config.instance().set("Server", "controller", False)
assert not controller.isEnabled()
Config.instance().set("Server", "controller", True)
assert controller.isEnabled()
+
+def test_addServer(controller):
+ server1 = Server("test1")
+
+ controller.addServer(server1)
+ assert len(controller.servers) == 1
+ controller.addServer(Server("test1"))
+ assert len(controller.servers) == 1
+ controller.addServer(Server("test2"))
+ assert len(controller.servers) == 2
diff --git a/tests/controller/test_server.py b/tests/controller/test_server.py
new file mode 100644
index 00000000..ad2093f8
--- /dev/null
+++ b/tests/controller/test_server.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 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 pytest
+
+from gns3server.controller.server import Server
+
+
+@pytest.fixture
+def server():
+ return Server("my_server_id", protocol="https", host="example.com", port=84, user="test", password="secure")
+
+
+def test_init(server):
+ assert server.id == "my_server_id"
+
+
+def test_json(server):
+ assert server.__json__() == {
+ "server_id": "my_server_id",
+ "protocol": "https",
+ "host": "example.com",
+ "port": 84,
+ "user": "test",
+ "connected": False,
+ "version": None
+ }
+
+
+def test__eq__(server):
+ assert server != 1
+ assert server == server
+ assert server == Server("my_server_id")
+ assert server != Server("test")
diff --git a/tests/handlers/api/test_server.py b/tests/handlers/api/test_server.py
new file mode 100644
index 00000000..845920e7
--- /dev/null
+++ b/tests/handlers/api/test_server.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 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 .
+
+
+def test_server_create(server, controller):
+
+ params = {
+ "server_id": "my_server_id",
+ "protocol": "http",
+ "host": "example.com",
+ "port": 84,
+ "user": "julien",
+ "password": "secure"
+ }
+ response = server.post("/servers", params)
+ assert response.status == 201
+ assert response.route == "/servers"
+ assert response.json["user"] == "julien"
+ assert "password" not in response.json
+
+ assert len(controller.servers) == 1
+ assert controller.servers["my_server_id"].host == "example.com"