From 5ae456d6e22fc5d70fff8c665bc8b367cbd2b7c0 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 5 Sep 2016 17:28:49 +0200 Subject: [PATCH] Avoid to corrupt project in case of error during loading Fix #651 --- gns3server/controller/project.py | 25 ++++++++++++++++++++----- tests/controller/test_project_open.py | 24 +++++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 0b98d8d4..e3d6c94f 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -570,7 +570,10 @@ class Project: self._status = "opened" path = self._topology_file() - if os.path.exists(path): + if not os.path.exists(path): + return + shutil.copy(path, path + ".backup") + try: topology = load_topology(path)["topology"] for compute in topology.get("computes", []): yield from self.controller.add_compute(**compute) @@ -584,13 +587,25 @@ class Project: 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"], label=node_link.get("label")) - for drawing_data in topology.get("drawings", []): drawing = yield from self.add_drawing(**drawing_data) - # Should we start the nodes when project is open - if self._auto_start: - yield from self.start_all() + # We catch all error to be able to rollback the .gns3 to the previous state + except Exception as e: + for compute in self._project_created_on_compute: + try: + yield from compute.post("/projects/{}/close".format(self._id)) + # We don't care if a compute is down at this step + except (aiohttp.errors.ClientOSError, aiohttp.web.HTTPNotFound, aiohttp.web.HTTPConflict): + pass + shutil.copy(path + ".backup", path) + self._status = "closed" + raise e + os.remove(path + ".backup") + + # Should we start the nodes when project is open + if self._auto_start: + yield from self.start_all() @open_required @asyncio.coroutine diff --git a/tests/controller/test_project_open.py b/tests/controller/test_project_open.py index babab288..252692dd 100644 --- a/tests/controller/test_project_open.py +++ b/tests/controller/test_project_open.py @@ -15,13 +15,16 @@ # 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 tests.utils import asyncio_patch, AsyncioMagicMock from gns3server.controller.compute import Compute + @pytest.fixture def demo_topology(): """ @@ -112,7 +115,7 @@ def demo_topology(): "z": 1 }, { - "compute_id": "local", + "compute_id": "vm", "console": 5001, "console_type": "telnet", "height": 59, @@ -148,6 +151,7 @@ def test_open(controller, tmpdir, demo_topology, async_run, http_server): json.dump(demo_topology, f) controller._computes["local"] = Compute("local", controller=controller, host=http_server[0], port=http_server[1]) + controller._computes["vm"] = controller._computes["local"] project = async_run(controller.load_project(str(tmpdir / "demo.gns3"))) assert project.status == "opened" @@ -159,3 +163,21 @@ def test_open(controller, tmpdir, demo_topology, async_run, http_server): assert len(project.drawings) == 1 assert project.name == "demo" + + +def test_open_missing_compute(controller, tmpdir, demo_topology, async_run, http_server): + """ + If a compute is missing the project should not be open and the .gns3 should + be the one before opening the project + """ + with open(str(tmpdir / "demo.gns3"), "w+") as f: + json.dump(demo_topology, f) + + controller._computes["local"] = Compute("local", controller=controller, host=http_server[0], port=http_server[1]) + + with pytest.raises(aiohttp.web_exceptions.HTTPNotFound): + project = async_run(controller.load_project(str(tmpdir / "demo.gns3"))) + assert controller.get_project("3c1be6f9-b4ba-4737-b209-63c47c23359f").status == "closed" + with open(str(tmpdir / "demo.gns3"), "r") as f: + topo = json.load(f) + assert len(topo["topology"]["nodes"]) == 2