From f9c7c15f95a42b85e0a7f91ff2f38097ecf13226 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 10 Jan 2018 16:22:55 +0700 Subject: [PATCH] Fixing race condition when starting the GNS3 VM. --- gns3server/controller/__init__.py | 1 + gns3server/controller/compute.py | 12 ++-- gns3server/controller/gns3vm/__init__.py | 57 +++++++++++-------- .../controller/gns3vm/vmware_gns3_vm.py | 2 +- 4 files changed, 41 insertions(+), 31 deletions(-) diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 44441763..24674e27 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -405,6 +405,7 @@ class Controller: :param connect: True connect to the compute immediately :param kwargs: See the documentation of Compute """ + if compute_id not in self._computes: # We disallow to create from the outside the local and VM server diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 9eb9bd6b..e1948a35 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -377,13 +377,13 @@ class Compute: """ :param dont_connect: If true do not reconnect if not connected """ + if not self._connected and not dont_connect: if self._id == "vm" and not self._controller.gns3vm.running: yield from self._controller.gns3vm.start() - yield from self.connect() if not self._connected and not dont_connect: - raise ComputeError("Can't connect to {}".format(self._name)) + raise ComputeError("Cannot connect to compute '{}' with request {} {}".format(self._name, method, path)) response = yield from self._run_http_query(method, path, data=data, **kwargs) return response @@ -402,20 +402,20 @@ class Compute: """ Check if remote server is accessible """ + if not self._connected and not self._closed: try: + log.info("Connecting to compute '{}'".format(self._id)) response = yield from self._run_http_query("GET", "/capabilities") - except ComputeError: + except ComputeError as e: # Try to reconnect after 2 seconds if server unavailable only if not during tests (otherwise we create a ressources usage bomb) if not hasattr(sys, "_called_from_test") or not sys._called_from_test: self._connection_failure += 1 # After 5 failure we close the project using the compute to avoid sync issues if self._connection_failure == 5: - log.warning("Can't connect to compute %s", self._id) + log.warning("Cannot connect to compute '{}': {}".format(self._id, e)) yield from self._controller.close_compute_projects(self) - asyncio.get_event_loop().call_later(2, lambda: asyncio.async(self._try_reconnect())) - return except aiohttp.web.HTTPNotFound: raise aiohttp.web.HTTPConflict(text="The server {} is not a GNS3 server or it's a 1.X server".format(self._id)) diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py index 0ba8b30e..acd9f7c3 100644 --- a/gns3server/controller/gns3vm/__init__.py +++ b/gns3server/controller/gns3vm/__init__.py @@ -27,6 +27,7 @@ from .virtualbox_gns3_vm import VirtualBoxGNS3VM from .remote_gns3_vm import RemoteGNS3VM from .gns3_vm_error import GNS3VMError from ...version import __version__ +from ..compute import ComputeError import logging log = logging.getLogger(__name__) @@ -281,7 +282,8 @@ class GNS3VM: compute = yield from self._controller.add_compute(compute_id="vm", name="GNS3 VM is starting ({})".format(engine.vmname), host=None, - force=True) + force=True, + connect=False) try: yield from engine.start() @@ -290,6 +292,7 @@ class GNS3VM: log.error("Can't start the GNS3 VM: {}".format(str(e))) yield from compute.update(name="GNS3 VM ({})".format(engine.vmname)) raise e + yield from compute.connect() # we can connect now that the VM has started yield from compute.update(name="GNS3 VM ({})".format(engine.vmname), protocol=self.protocol, host=self.ip_address, @@ -297,7 +300,9 @@ class GNS3VM: user=self.user, password=self.password) - yield from self._check_network(compute) + # check if the VM is in the same subnet as the local server, start 10 seconds later to give + # some time for the compute in the VM to be ready for requests + asyncio.get_event_loop().call_later(10, lambda: asyncio.async(self._check_network(compute))) @asyncio.coroutine def _check_network(self, compute): @@ -305,28 +310,32 @@ class GNS3VM: Check that the VM is in the same subnet as the local server """ - vm_interfaces = yield from compute.interfaces() - vm_interface_netmask = None - for interface in vm_interfaces: - if interface["ip_address"] == self.ip_address: - vm_interface_netmask = interface["netmask"] - break - if vm_interface_netmask: - vm_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, vm_interface_netmask)).network - for compute_id in self._controller.computes: - if compute_id == "local": - compute = self._controller.get_compute(compute_id) - interfaces = yield from compute.interfaces() - netmask = None - for interface in interfaces: - if interface["ip_address"] == compute.host_ip: - netmask = interface["netmask"] - break - if netmask: - compute_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, netmask)).network - if vm_network.compare_networks(compute_network) != 0: - msg = "The GNS3 VM ({}) is not on the same network as the {} server ({}), please make sure the local server binding is in the same network as the GNS3 VM".format(vm_network, compute_id, compute_network) - self._controller.notification.emit("log.warning", {"message": msg}) + try: + vm_interfaces = yield from compute.interfaces() + vm_interface_netmask = None + for interface in vm_interfaces: + if interface["ip_address"] == self.ip_address: + vm_interface_netmask = interface["netmask"] + break + if vm_interface_netmask: + vm_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, vm_interface_netmask)).network + for compute_id in self._controller.computes: + if compute_id == "local": + compute = self._controller.get_compute(compute_id) + interfaces = yield from compute.interfaces() + netmask = None + for interface in interfaces: + if interface["ip_address"] == compute.host_ip: + netmask = interface["netmask"] + break + if netmask: + compute_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, netmask)).network + if vm_network.compare_networks(compute_network) != 0: + msg = "The GNS3 VM ({}) is not on the same network as the {} server ({}), please make sure the local server binding is in the same network as the GNS3 VM".format( + vm_network, compute_id, compute_network) + self._controller.notification.emit("log.warning", {"message": msg}) + except ComputeError as e: + log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e)) @locked_coroutine def _suspend(self): diff --git a/gns3server/controller/gns3vm/vmware_gns3_vm.py b/gns3server/controller/gns3vm/vmware_gns3_vm.py index d3f0c6d2..c14f0732 100644 --- a/gns3server/controller/gns3vm/vmware_gns3_vm.py +++ b/gns3server/controller/gns3vm/vmware_gns3_vm.py @@ -171,7 +171,7 @@ class VMwareGNS3VM(BaseGNS3VM): trial -= 1 # If ip not found fallback on old method if trial == 0: - log.warn("No IP found for the VM via readVariable fallback to getGuestIPAddress") + log.warning("No IP found for the VM via readVariable fallback to getGuestIPAddress") guest_ip_address = yield from self._execute("getGuestIPAddress", [self._vmx_path, "-wait"], timeout=120) break yield from asyncio.sleep(1)