diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 0fb36ca6..1518f597 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -26,6 +26,7 @@ from .project import Project from .compute import Compute from .notification import Notification from ..version import __version__ +from .topology import load_topology import logging log = logging.getLogger(__name__) @@ -185,6 +186,29 @@ class Controller: def remove_project(self, project): del self._projects[project.id] + @asyncio.coroutine + def load_project(self, path): + """ + Load a project from a .gns3 + + :param path: Path of the .gns3 + """ + topo_data = load_topology(path) + topology = topo_data.pop("topology") + topo_data.pop("version") + topo_data.pop("revision") + topo_data.pop("type") + + project = yield from self.add_project(path=os.path.dirname(path), **topo_data) + + for compute in topology["computes"]: + yield from self.add_compute(**compute) + for node in topology["nodes"]: + compute = self.get_compute(node.pop("compute_id")) + name = node.pop("name") + node_id = node.pop("node_id") + yield from project.add_node(compute, name, node_id, **node) + @property def projects(self): """ diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 7f8bad83..86cb0552 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -15,8 +15,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import json +import aiohttp + from ..version import __version__ +GNS3_FILE_FORMAT_REVISION = 5 def project_to_topology(project): """ @@ -31,7 +35,7 @@ def project_to_topology(project): "computes": [] }, "type": "topology", - "revision": 5, + "revision": GNS3_FILE_FORMAT_REVISION, "version": __version__ } @@ -44,6 +48,20 @@ def project_to_topology(project): for compute in computes: if hasattr(compute, "__json__"): data["topology"]["computes"].append(compute.__json__()) - print(data) #TODO: check JSON schema return data + + +def load_topology(path): + """ + Open a topology file, patch it for last GNS3 release and return it + """ + try: + with open(path) as f: + topo = json.load(f) + except OSError as e: + raise aiohttp.web.HTTPConflict(text="Could not load topology {}: {}".format(path, str(e))) + #TODO: Check JSON schema + if topo["revision"] < GNS3_FILE_FORMAT_REVISION: + raise aiohttp.web.HTTPConflict(text="Old GNS3 project are not yet supported") + return topo diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index b775195e..af1c0926 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -21,7 +21,7 @@ import json import pytest import aiohttp from unittest.mock import MagicMock - +from tests.utils import AsyncioMagicMock from gns3server.controller import Controller from gns3server.controller.compute import Compute @@ -184,3 +184,71 @@ def test_close(controller, async_run): c._connected = True async_run(controller.close()) assert c.connected is False + + +def test_load_project(controller, async_run, tmpdir): + data = { + "name": "Test", + "project_id": "c8d07a5a-134f-4c3f-8599-e35eac85eb17", + "revision": 5, + "type": "topology", + "version": "2.0.0dev1", + "topology": { + "computes": [ + { + "compute_id": "my_remote", + "host": "127.0.0.1", + "name": "My remote", + "port": 3080, + "protocol": "http", + } + ], + "links": [ + { + "capturing": True, + "link_id": "c44331d2-2da4-490d-9aad-7f5c126ae271", + "nodes": [ + {"node_id": "c067b922-7f77-4680-ac00-0226c6583598", "adapter_number": 0, "port_number": 0}, + {"node_id": "50d66d7b-0dd7-4e9f-b720-6eb621ae6543", "adapter_number": 0, "port_number": 0}, + ], + } + ], + "nodes": [ + { + "compute_id": "my_remote", + "name": "PC2", + "node_id": "c067b922-7f77-4680-ac00-0226c6583598", + "node_type": "vpcs", + "properties": { + "startup_script": "set pcname PC2\n", + "startup_script_path": "startup.vpc" + }, + }, + { + "compute_id": "my_remote", + "name": "PC1", + "node_id": "50d66d7b-0dd7-4e9f-b720-6eb621ae6543", + "node_type": "vpcs", + "properties": { + "startup_script": "set pcname PC1\n", + "startup_script_path": "startup.vpc" + }, + } + ] + } + } + with open(str(tmpdir / "test.gns3"), "w+") as f: + json.dump(data, f) + controller.add_compute = AsyncioMagicMock() + mock_project = MagicMock() + controller.add_project = AsyncioMagicMock(return_value=mock_project) + controller._computes["my_remote"] = MagicMock() + + async_run(controller.load_project(str(tmpdir / "test.gns3"))) + + controller.add_compute.assert_called_with(compute_id='my_remote', host='127.0.0.1', name='My remote', port=3080, protocol='http') + controller.add_project.assert_called_with(name='Test', project_id='c8d07a5a-134f-4c3f-8599-e35eac85eb17', path=str(tmpdir)) + + mock_project.add_node.assert_any_call(controller._computes["my_remote"], 'PC1', '50d66d7b-0dd7-4e9f-b720-6eb621ae6543', node_type='vpcs', properties={'startup_script': 'set pcname PC1\n', 'startup_script_path': 'startup.vpc'}) + mock_project.add_node.assert_any_call(controller._computes["my_remote"], 'PC2', 'c067b922-7f77-4680-ac00-0226c6583598', node_type='vpcs', properties={'startup_script': 'set pcname PC2\n', 'startup_script_path': 'startup.vpc'}) + diff --git a/tests/controller/test_topology.py b/tests/controller/test_topology.py index 32db8448..5a6bc8ac 100644 --- a/tests/controller/test_topology.py +++ b/tests/controller/test_topology.py @@ -15,12 +15,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import json +import pytest +import aiohttp from unittest.mock import MagicMock from tests.utils import asyncio_patch from gns3server.controller.project import Project from gns3server.controller.compute import Compute -from gns3server.controller.topology import project_to_topology +from gns3server.controller.topology import project_to_topology, load_topology from gns3server.version import __version__ @@ -60,3 +63,45 @@ def test_basic_topology(tmpdir, async_run, controller): assert topo["topology"]["links"][0] == link.__json__() assert topo["topology"]["computes"][0] == compute.__json__() + + +def test_load_topology(tmpdir): + data = { + "project_id": "69f26504-7aa3-48aa-9f29-798d44841211", + "name": "Test", + "revision": 5, + "topology": { + "nodes": [], + "links": [], + "computes": [] + }, + "type": "topology", + "version": __version__} + + path = str(tmpdir / "test.gns3") + with open(path, "w+") as f: + json.dump(data, f) + topo = load_topology(path) + assert topo == data + +def test_load_topology_file_error(tmpdir): + path = str(tmpdir / "test.gns3") + with pytest.raises(aiohttp.web.HTTPConflict): + topo = load_topology(path) + + +def test_load_old_topology(tmpdir): + data = { + "project_id": "69f26504-7aa3-48aa-9f29-798d44841211", + "name": "Test", + "revision": 4, + "topology": { + }, + "type": "topology", + "version": __version__} + + path = str(tmpdir / "test.gns3") + with open(path, "w+") as f: + json.dump(data, f) + with pytest.raises(aiohttp.web.HTTPConflict): + topo = load_topology(path)