From 4bbd8938ab6ad60404cc495021b803f32abe2c92 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 1 Sep 2016 15:36:41 +0200 Subject: [PATCH] Manage error when GNS3VM failed to start Fix https://github.com/GNS3/gns3-gui/issues/1446 --- gns3server/controller/compute.py | 3 ++ gns3server/controller/gns3vm/__init__.py | 20 +++++--- gns3server/controller/gns3vm/gns3_vm_error.py | 2 +- .../controller/gns3vm/vmware_gns3_vm.py | 2 +- tests/controller/test_compute.py | 27 +++++++++++ tests/controller/test_controller.py | 12 +++-- tests/controller/test_gns3vm.py | 47 ++++++++++++++++++- 7 files changed, 101 insertions(+), 12 deletions(-) diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index c49e8d3b..13594871 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -334,6 +334,9 @@ class Compute: @asyncio.coroutine def http_query(self, method, path, data=None, **kwargs): if not self._connected: + if self._id == "vm" and not self._controller.gns3vm.running: + yield from self._controller.gns3vm.start() + yield from self.connect() if not self._connected: raise aiohttp.web.HTTPConflict(text="Can't connect to {}".format(self._name)) diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py index 8764a735..7ced6df7 100644 --- a/gns3server/controller/gns3vm/__init__.py +++ b/gns3server/controller/gns3vm/__init__.py @@ -19,9 +19,11 @@ import sys import copy import asyncio +from ...utils.asyncio import locked_coroutine from .vmware_gns3_vm import VMwareGNS3VM from .virtualbox_gns3_vm import VirtualBoxGNS3VM from .remote_gns3_vm import RemoteGNS3VM +from .gns3_vm_error import GNS3VMError import logging log = logging.getLogger(__name__) @@ -212,8 +214,14 @@ class GNS3VM: Auto start the GNS3 VM if require """ if self.enable: - yield from self._start() - + try: + yield from self.start() + except GNS3VMError as e: + # User will receive the error later when they will try to use the node + yield from self._controller.add_compute(compute_id="vm", + name="GNS3 VM ({})".format(self._current_engine().vmname), + host=None, + force=True) @asyncio.coroutine def auto_stop_vm(self): if self.enable and self.auto_stop: @@ -222,8 +230,8 @@ class GNS3VM: except GNS3VMError as e: log.warn(str(e)) - @asyncio.coroutine - def _start(self): + @locked_coroutine + def start(self): """ Start the GNS3 VM """ @@ -232,7 +240,7 @@ class GNS3VM: log.info("Start the GNS3 VM") engine.vmname = self._settings["vmname"] yield from engine.start() - yield from self._controller.add_compute(compute_id="vm", + yield from self._controller.add_compute(compute_id="vm", name="GNS3 VM ({})".format(engine.vmname), protocol=self.protocol, host=self.ip_address, @@ -241,7 +249,7 @@ class GNS3VM: password=self.password, force=True) - @asyncio.coroutine + @locked_coroutine def _stop(self): """ Stop the GNS3 VM diff --git a/gns3server/controller/gns3vm/gns3_vm_error.py b/gns3server/controller/gns3vm/gns3_vm_error.py index 29e4f85b..0a53cb3f 100644 --- a/gns3server/controller/gns3vm/gns3_vm_error.py +++ b/gns3server/controller/gns3vm/gns3_vm_error.py @@ -26,4 +26,4 @@ class GNS3VMError(Exception): return self._message def __str__(self): - return self._message + return "GNS3VM: {}".format(self._message) diff --git a/gns3server/controller/gns3vm/vmware_gns3_vm.py b/gns3server/controller/gns3vm/vmware_gns3_vm.py index 657ec79f..ad3e692b 100644 --- a/gns3server/controller/gns3vm/vmware_gns3_vm.py +++ b/gns3server/controller/gns3vm/vmware_gns3_vm.py @@ -114,7 +114,6 @@ class VMwareGNS3VM(BaseGNS3VM): args.extend(["nogui"]) yield from self._execute("start", args) log.info("GNS3 VM has been started") - self.running = True # check if the VMware guest tools are installed vmware_tools_state = yield from self._execute("checkToolsState", [self._vmx_path]) @@ -125,6 +124,7 @@ class VMwareGNS3VM(BaseGNS3VM): guest_ip_address = yield from self._execute("getGuestIPAddress", [self._vmx_path, "-wait"], timeout=120) self.ip_address = guest_ip_address log.info("GNS3 VM IP address set to {}".format(guest_ip_address)) + self.running = True @asyncio.coroutine def stop(self): diff --git a/tests/controller/test_compute.py b/tests/controller/test_compute.py index 7177150e..7f3fe791 100644 --- a/tests/controller/test_compute.py +++ b/tests/controller/test_compute.py @@ -92,6 +92,33 @@ def test_compute_httpQueryNotConnected(compute, controller, async_run): controller.notification.emit.assert_called_with("compute.updated", compute.__json__()) + +def test_compute_httpQueryNotConnectedGNS3vmNotRunning(compute, controller, async_run): + """ + We are not connected to the remote and it's a GNS3 VM. So we need to start it + """ + controller._notification = MagicMock() + controller.gns3vm = AsyncioMagicMock() + controller.gns3vm.running = False + + compute._id = "vm" + compute._connected = False + response = AsyncioMagicMock() + response.read = AsyncioMagicMock(return_value=json.dumps({"version": __version__}).encode()) + response.status = 200 + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + async_run(compute.post("/projects", {"a": "b"})) + mock.assert_any_call("GET", "https://example.com:84/v2/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, chunked=False) + mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=False) + + assert controller.gns3vm.start.called + assert compute._connected + assert compute._capabilities["version"] == __version__ + controller.notification.emit.assert_called_with("compute.updated", compute.__json__()) + + + + def test_compute_httpQueryNotConnectedInvalidVersion(compute, async_run): compute._connected = False response = AsyncioMagicMock() diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index 3d7a96c6..483b92bd 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -289,7 +289,12 @@ def test_getProject(controller, async_run): def test_start(controller, async_run): - async_run(controller.start()) + controller.gns3vm.settings = { + "enable": False, + "engine": "vmware" + } + with asyncio_patch("gns3server.controller.compute.Compute.connect") as mock: + async_run(controller.start()) assert len(controller.computes) == 1 # Local compute is created assert controller.computes["local"].name == socket.gethostname() @@ -303,8 +308,9 @@ def test_start_vm(controller, async_run): "engine": "vmware" } with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.start") as mock: - async_run(controller.start()) - assert mock.called + with asyncio_patch("gns3server.controller.compute.Compute.connect") as mock_connect: + async_run(controller.start()) + assert mock.called assert "local" in controller.computes assert "vm" in controller.computes assert len(controller.computes) == 2 # Local compute and vm are created diff --git a/tests/controller/test_gns3vm.py b/tests/controller/test_gns3vm.py index 1cb241be..cbeeded9 100644 --- a/tests/controller/test_gns3vm.py +++ b/tests/controller/test_gns3vm.py @@ -16,9 +16,31 @@ # along with this program. If not, see . import pytest -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, AsyncioMagicMock from gns3server.controller.gns3vm import GNS3VM +from gns3server.controller.gns3vm.gns3_vm_error import GNS3VMError + +@pytest.fixture +def dummy_engine(): + engine = AsyncioMagicMock() + engine.running = False + engine.ip_address = "vm.local" + engine.protocol = "https" + engine.port = 8442 + engine.user = "hello" + engine.password = "world" + return engine + + +@pytest.fixture +def dummy_gns3vm(controller, dummy_engine): + vm = GNS3VM(controller) + vm._settings["engine"] = "dummy" + vm._settings["vmname"] = "Test VM" + vm._settings["enable"] = True + vm._engines["dummy"] = dummy_engine + return vm def test_list(async_run, controller): @@ -50,3 +72,26 @@ def test_update_settings(controller, async_run): assert "vm" in controller.computes async_run(vm.update_settings({"enable": False})) assert "vm" not in controller.computes + + +def test_auto_start(async_run, controller, dummy_gns3vm, dummy_engine): + """ + When start the compute should be add to the controller + """ + async_run(dummy_gns3vm.auto_start_vm()) + assert dummy_engine.start.called + assert controller.computes["vm"].name == "GNS3 VM (Test VM)" + assert controller.computes["vm"].host == "vm.local" + assert controller.computes["vm"].port == 8442 + assert controller.computes["vm"].protocol == "https" + assert controller.computes["vm"].user == "hello" + assert controller.computes["vm"].password == "world" + + +def test_auto_start_with_error(async_run, controller, dummy_gns3vm, dummy_engine): + dummy_engine.start.side_effect = GNS3VMError("Dummy error") + + async_run(dummy_gns3vm.auto_start_vm()) + assert dummy_engine.start.called + assert controller.computes["vm"].name == "GNS3 VM (Test VM)" +