From 4021a13651ecf02fe02a9a2e9181c6d8e3eb32f7 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 6 Sep 2018 09:49:12 +0200 Subject: [PATCH] Catch exceptions in various locations to fix small issues reported by Sentry. --- gns3server/compute/docker/docker_vm.py | 70 +++++++++++++---------- gns3server/compute/qemu/qemu_vm.py | 2 +- gns3server/controller/__init__.py | 4 +- gns3server/controller/node.py | 2 +- gns3server/controller/project.py | 2 +- gns3server/utils/asyncio/telnet_server.py | 6 +- gns3server/utils/ping_stats.py | 12 +++- gns3server/web/route.py | 5 ++ 8 files changed, 63 insertions(+), 40 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 852483d6..7c07bcaa 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -228,11 +228,13 @@ class DockerVM(BaseNode): binds = ["{}:/gns3:ro".format(ressources)] # We mount our own etc/network - network_config = self._create_network_config() + try: + network_config = self._create_network_config() + except OSError as e: + raise DockerError("Could not create network config in the container: {}".format(e)) binds.append("{}:/gns3volumes/etc/network:rw".format(network_config)) self._volumes = ["/etc/network"] - volumes = image_infos.get("Config", {}).get("Volumes") if volumes is None: return binds @@ -285,11 +287,12 @@ class DockerVM(BaseNode): try: image_infos = yield from self._get_image_information() except DockerHttp404Error: - log.info("Image %s is missing pulling it from docker hub", self._image) + log.info("Image '{}' is missing, pulling it from Docker hub...".format(self._image)) yield from self.pull_image(self._image) image_infos = yield from self._get_image_information() - if image_infos is None: - raise DockerError("Can't get image informations, please try again.") + + if image_infos is None: + raise DockerError("Cannot get information for image '{}', please try again.".format(self._image)) params = { "Hostname": self._name, @@ -313,7 +316,10 @@ class DockerVM(BaseNode): if params["Entrypoint"] is None: params["Entrypoint"] = [] if self._start_command: - params["Cmd"] = shlex.split(self._start_command) + try: + params["Cmd"] = shlex.split(self._start_command) + except ValueError as e: + raise DockerError("Invalid start command '{}': {}".format(self._start_command, e)) if len(params["Cmd"]) == 0: params["Cmd"] = image_infos.get("Config", {"Cmd": []})["Cmd"] if params["Cmd"] is None: @@ -355,8 +361,7 @@ class DockerVM(BaseNode): result = yield from self.manager.query("POST", "containers/create", data=params) self._cid = result['Id'] - log.info("Docker container '{name}' [{id}] created".format( - name=self._name, id=self._id)) + log.info("Docker container '{name}' [{id}] created".format(name=self._name, id=self._id)) return True def _format_env(self, variables, env): @@ -449,16 +454,19 @@ class DockerVM(BaseNode): @asyncio.coroutine def _start_aux(self): """ - Start an auxilary console + Start an auxiliary console """ # We can not use the API because docker doesn't expose a websocket api for exec # https://github.com/GNS3/gns3-gui/issues/1039 - process = yield from asyncio.subprocess.create_subprocess_exec( - "docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.STDOUT, - stdin=asyncio.subprocess.PIPE) + try: + process = yield from asyncio.subprocess.create_subprocess_exec( + "docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + stdin=asyncio.subprocess.PIPE) + except OSError as e: + raise DockerError("Could not start auxiliary console process: {}".format(e)) server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=True, echo=True) try: self._telnet_servers.append((yield from asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux))) @@ -481,21 +489,25 @@ class DockerVM(BaseNode): for volume in self._volumes: log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format( name=self._name, image=self._image, path=volume)) - process = yield from asyncio.subprocess.create_subprocess_exec( - "docker", - "exec", - self._cid, - "/gns3/bin/busybox", - "sh", - "-c", - "(" - "/gns3/bin/busybox find \"{path}\" -depth -print0" - " | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\"" - ")" - " && /gns3/bin/busybox chmod -R u+rX \"{path}\"" - " && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"" - .format(uid=os.getuid(), gid=os.getgid(), path=volume), - ) + + try: + process = yield from asyncio.subprocess.create_subprocess_exec( + "docker", + "exec", + self._cid, + "/gns3/bin/busybox", + "sh", + "-c", + "(" + "/gns3/bin/busybox find \"{path}\" -depth -print0" + " | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\"" + ")" + " && /gns3/bin/busybox chmod -R u+rX \"{path}\"" + " && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\"" + .format(uid=os.getuid(), gid=os.getgid(), path=volume), + ) + except OSError as e: + raise DockerError("Could not fix permissions for {}: {}".format(volume, e)) yield from process.wait() @asyncio.coroutine diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 894cad8c..16c9ebdf 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1028,7 +1028,7 @@ class QemuVM(BaseNode): if expect in line: result = line.decode("utf-8").strip() break - except EOFError as e: + except (ConnectionError, EOFError) as e: log.warn("Could not read from QEMU monitor: {}".format(e)) writer.close() return result diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 5467c4ba..e2e75370 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -69,7 +69,7 @@ class Controller: for directory, builtin in ( (get_resource('appliances'), True,), (self.appliances_path(), False,) ): - if os.path.isdir(directory): + if directory and os.path.isdir(directory): for file in os.listdir(directory): if not file.endswith('.gns3a') and not file.endswith('.gns3appliance'): continue @@ -509,7 +509,7 @@ class Controller: return self._computes[compute_id] except KeyError: if compute_id == "vm": - raise aiohttp.web.HTTPNotFound(text="You try to use a node on the GNS3 VM server but the GNS3 VM is not configured") + raise aiohttp.web.HTTPNotFound(text="A node is set to use the GNS3 VM server but the GNS3 VM is not configured") raise aiohttp.web.HTTPNotFound(text="Compute ID {} doesn't exist".format(compute_id)) def has_compute(self, compute_id): diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index f496e3d9..2226484a 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -177,7 +177,7 @@ class Node: try: with open(path) as f: return f.read() - except (PermissionError, OSError): + except OSError: return None @property diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index b067806f..6a6446ba 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -871,7 +871,7 @@ class Project: try: if os.path.exists(path + ".backup"): shutil.copy(path + ".backup", path) - except (PermissionError, OSError): + except OSError: pass self._status = "closed" self._loading = False diff --git a/gns3server/utils/asyncio/telnet_server.py b/gns3server/utils/asyncio/telnet_server.py index 604ab914..abed2e06 100644 --- a/gns3server/utils/asyncio/telnet_server.py +++ b/gns3server/utils/asyncio/telnet_server.py @@ -197,11 +197,9 @@ class AsyncioTelnetServer: yield from self._write_intro(network_writer, echo=self._echo, binary=self._binary, naws=self._naws) yield from connection.connected() yield from self._process(network_reader, network_writer, connection) - except ConnectionResetError: + except ConnectionError: with (yield from self._lock): - network_writer.close() - if self._reader_process == network_reader: self._reader_process = None # Cancel current read from this reader @@ -217,7 +215,7 @@ class AsyncioTelnetServer: try: writer.write_eof() yield from writer.drain() - except ConnectionResetError: + except ConnectionError: continue @asyncio.coroutine diff --git a/gns3server/utils/ping_stats.py b/gns3server/utils/ping_stats.py index 3252f9c2..88734793 100644 --- a/gns3server/utils/ping_stats.py +++ b/gns3server/utils/ping_stats.py @@ -43,8 +43,16 @@ class PingStats: cur_time > cls._last_measurement + 1.9: cls._last_measurement = cur_time # Non blocking call to get cpu usage. First call will return 0 - cls._last_cpu_percent = psutil.cpu_percent(interval=None) - cls._last_mem_percent = psutil.virtual_memory().percent + try: + cls._last_cpu_percent = psutil.cpu_percent(interval=None) + cls._last_mem_percent = psutil.virtual_memory().percent + except RuntimeError: + # ignore the following error: + # RuntimeError: host_statistics(HOST_CPU_LOAD_INFO) syscall failed: (ipc/send) invalid reply port + pass + except PermissionError: + # [Errno 13] Permission denied: '/proc/stat' + pass stats["cpu_usage_percent"] = cls._last_cpu_percent stats["memory_usage_percent"] = cls._last_mem_percent return stats diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 97adbf4a..9ceda349 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -225,6 +225,11 @@ class Route(object): response = Response(request=request, route=route) response.set_status(408) response.json({"message": "Client error", "status": 408}) + except MemoryError: + log.error("Memory error detected, server has run out of memory!", exc_info=1) + response = Response(request=request, route=route) + response.set_status(500) + response.json({"message": "Memory error", "status": 500}) except Exception as e: log.error("Uncaught exception detected: {type}".format(type=type(e)), exc_info=1) response = Response(request=request, route=route)