From 6e2752648ab1ca03add3b95885d1856e62411b4e Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 27 Nov 2017 15:16:46 +0700 Subject: [PATCH] Implement #1153 into 2.2 branch. --- gns3server/controller/project.py | 37 ++++++++++++++++++++++++- tests/controller/test_project.py | 47 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 6c99431b..909d7cd8 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -112,8 +112,9 @@ class Project: self.reset() - # At project creation we write an empty .gns3 + # At project creation we write an empty .gns3 with the meta if not os.path.exists(self._topology_file()): + assert self._status != "closed" self.dump() @asyncio.coroutine @@ -497,11 +498,37 @@ class Project: except KeyError: raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id)) + def _get_closed_data(self, section, id_key): + """ + Get the data for a project from the .gns3 when + the project is close + + :param section: The section name in the .gns3 + :param id_key: The key for the element unique id + """ + + try: + path = self._topology_file() + with open(path, "r") as f: + topology = json.load(f) + except OSError as e: + raise aiohttp.web.HTTPInternalServerError(text="Could not load topology: {}".format(e)) + + try: + data = {} + for elem in topology["topology"][section]: + data[elem[id_key]] = elem + return data + except KeyError: + raise aiohttp.web.HTTPNotFound(text="Section {} not found in the topology".format(section)) + @property def nodes(self): """ :returns: Dictionary of the nodes """ + if self._status == "closed": + return self._get_closed_data("nodes", "node_id") return self._nodes @property @@ -509,6 +536,8 @@ class Project: """ :returns: Dictionary of the drawings """ + if self._status == "closed": + return self._get_closed_data("drawings", "drawing_id") return self._drawings @open_required @@ -591,6 +620,8 @@ class Project: """ :returns: Dictionary of the Links """ + if self._status == "closed": + return self._get_closed_data("links", "link_id") return self._links @property @@ -649,6 +680,8 @@ class Project: @asyncio.coroutine def close(self, ignore_notification=False): + if self._status == "closed": + return yield from self.stop_all() for compute in list(self._project_created_on_compute): try: @@ -660,6 +693,7 @@ class Project: self._status = "closed" if not ignore_notification: self.controller.notification.emit("project.closed", self.__json__()) + self.reset() def _cleanPictures(self): """ @@ -856,6 +890,7 @@ class Project: yield from self.open() self.dump() + assert self._status != "closed" try: with tempfile.TemporaryDirectory() as tmpdir: zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True) diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 67a16971..7571a71f 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -302,6 +302,22 @@ def test_get_node(async_run, controller): with pytest.raises(aiohttp.web.HTTPForbidden): project.get_node(vm.id) +def test_list_nodes(async_run, controller): + compute = MagicMock() + project = Project(controller=controller, name="Test") + + response = MagicMock() + response.json = {"console": 2048} + compute.post = AsyncioMagicMock(return_value=response) + + vm = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"})) + assert len(project.nodes) == 1 + assert isinstance(project.nodes, dict) + + async_run(project.close()) + assert len(project.nodes) == 1 + assert isinstance(project.nodes, dict) + def test_add_link(async_run, project, controller): compute = MagicMock() @@ -324,6 +340,20 @@ def test_add_link(async_run, project, controller): controller.notification.emit.assert_any_call("link.created", link.__json__()) +def test_list_links(async_run, project): + compute = MagicMock() + + response = MagicMock() + response.json = {"console": 2048} + compute.post = AsyncioMagicMock(return_value=response) + + link = async_run(project.add_link()) + assert len(project.links) == 1 + + async_run(project.close()) + assert len(project.links) == 1 + + def test_get_link(async_run, project): compute = MagicMock() @@ -370,6 +400,14 @@ def test_get_drawing(async_run, project): project.get_drawing("test") +def test_list_drawing(async_run, project): + drawing = async_run(project.add_drawing(None)) + assert len(project.drawings) == 1 + + async_run(project.close()) + assert len(project.drawings) == 1 + + def test_delete_drawing(async_run, project, controller): assert len(project._drawings) == 0 drawing = async_run(project.add_drawing()) @@ -412,8 +450,9 @@ def test_dump(): def test_open_close(async_run, controller): - project = Project(controller=controller, status="closed", name="Test") - assert project.status == "closed" + project = Project(controller=controller, name="Test") + assert project.status == "opened" + async_run(project.close()) project.start_all = AsyncioMagicMock() async_run(project.open()) assert not project.start_all.called @@ -425,7 +464,9 @@ def test_open_close(async_run, controller): def test_open_auto_start(async_run, controller): - project = Project(controller=controller, status="closed", name="Test", auto_start=True) + project = Project(controller=controller, name="Test") + assert project.status == "opened" + async_run(project.close()) project.start_all = AsyncioMagicMock() async_run(project.open()) assert project.start_all.called