diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 1518f597..bd10c27a 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -200,14 +200,7 @@ class Controller: 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) + yield from project.load() @property def projects(self): diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index 1eb4ef40..4ef43257 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -30,9 +30,12 @@ class Link: Base class for links. """ - def __init__(self, project): + def __init__(self, project, link_id=None): - self._id = str(uuid.uuid4()) + if link_id: + self._id = link_id + else: + self._id = str(uuid.uuid4()) self._nodes = [] self._project = project self._capturing = False @@ -134,6 +137,10 @@ class Link: def id(self): return self._id + @property + def nodes(self): + return self._nodes + @property def capturing(self): return self._capturing diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 379b109b..696b8603 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -24,11 +24,12 @@ import shutil from uuid import UUID, uuid4 from .node import Node -from .topology import project_to_topology +from .topology import project_to_topology, load_topology from .udp_link import UDPLink from ..config import Config from ..utils.path import check_path_allowed, get_default_project_directory + import logging log = logging.getLogger(__name__) @@ -245,11 +246,13 @@ class Project: return self._nodes @asyncio.coroutine - def add_link(self): + def add_link(self, link_id=None): """ Create a link. By default the link is empty """ - link = UDPLink(self) + if link_id and link_id in self._links: + return self._links[link.id] + link = UDPLink(self, link_id=link_id) self._links[link.id] = link self.dump() return link @@ -307,18 +310,42 @@ class Project: raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e)) return path + def _topology_file(self): + if self.name is None: + filename = "untitled.gns3" + else: + filename = self.name + ".gns3" + return os.path.join(self.path, filename) + + @asyncio.coroutine + def load(self): + """ + Load topology elements + """ + path = self._topology_file() + topology = load_topology(path)["topology"] + for compute in topology["computes"]: + yield from self.controller.add_compute(**compute) + for node in topology["nodes"]: + compute = self.controller.get_compute(node.pop("compute_id")) + name = node.pop("name") + node_id = node.pop("node_id") + yield from self.add_node(compute, name, node_id, **node) + for link_data in topology["links"]: + link = yield from self.add_link(link_id=link_data["link_id"]) + for node_link in link_data["nodes"]: + node = self.get_node(node_link["node_id"]) + yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"]) + def dump(self): """ Dump topology to disk """ try: - if self.name is None: - filename = "untitled.gns3" - else: - filename = self.name + ".gns3" topo = project_to_topology(self) - log.debug("Write %s", filename) - with open(os.path.join(self.path, filename), "w+") as f: + path = self._topology_file() + log.debug("Write %s", path) + with open(path, "w+") as f: json.dump(topo, f, indent=4, sort_keys=True) except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not write topology: {}".format(e)) diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 86cb0552..0fdb5be7 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -22,6 +22,7 @@ from ..version import __version__ GNS3_FILE_FORMAT_REVISION = 5 + def project_to_topology(project): """ :return: A dictionnary with the topology ready to dump to a .gns3 diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index e5a2d4c0..60f02db7 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -24,8 +24,8 @@ from .link import Link class UDPLink(Link): - def __init__(self, project): - super().__init__(project) + def __init__(self, project, link_id=None): + super().__init__(project, link_id=link_id) self._capture_node = None @asyncio.coroutine diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index af1c0926..dd48bca1 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 tests.utils import AsyncioMagicMock, asyncio_patch from gns3server.controller import Controller from gns3server.controller.compute import Compute @@ -204,8 +204,7 @@ def test_load_project(controller, async_run, tmpdir): } ], "links": [ - { - "capturing": True, + { "link_id": "c44331d2-2da4-490d-9aad-7f5c126ae271", "nodes": [ {"node_id": "c067b922-7f77-4680-ac00-0226c6583598", "adapter_number": 0, "port_number": 0}, @@ -240,15 +239,17 @@ def test_load_project(controller, async_run, tmpdir): 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"))) + with asyncio_patch("gns3server.controller.node.Node.create") as mock_node_create: + 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'}) - + project = controller.get_project('c8d07a5a-134f-4c3f-8599-e35eac85eb17') + assert project.name == "Test" + assert project.path == str(tmpdir) + link = project.get_link("c44331d2-2da4-490d-9aad-7f5c126ae271") + assert len(link.nodes) == 2 + + node1 = project.get_node("50d66d7b-0dd7-4e9f-b720-6eb621ae6543") + assert node1.name == "PC1" diff --git a/tests/controller/test_topology.py b/tests/controller/test_topology.py index 5a6bc8ac..0aad6654 100644 --- a/tests/controller/test_topology.py +++ b/tests/controller/test_topology.py @@ -64,7 +64,6 @@ def test_basic_topology(tmpdir, async_run, controller): assert topo["topology"]["computes"][0] == compute.__json__() - def test_load_topology(tmpdir): data = { "project_id": "69f26504-7aa3-48aa-9f29-798d44841211", @@ -84,6 +83,7 @@ def test_load_topology(tmpdir): 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):