diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 295e6762..581a9897 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -16,7 +16,6 @@ # along with this program. If not, see . import os -import sys import json import socket import asyncio @@ -318,7 +317,6 @@ class Controller: try: return self._computes[compute_id] except KeyError: - server_config = Config.instance().get_section_config("Server") 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="Compute ID {} doesn't exist".format(compute_id)) @@ -380,7 +378,7 @@ class Controller: :param load: Load the topology """ topo_data = load_topology(path) - topology = topo_data.pop("topology") + topo_data.pop("topology") topo_data.pop("version") topo_data.pop("revision") topo_data.pop("type") diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 47b780e8..413abba6 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -22,14 +22,12 @@ import socket import json import uuid import sys -import os import io from ..utils import parse_version -from ..utils.images import list_images, md5sum +from ..utils.images import list_images from ..utils.asyncio import locked_coroutine from ..controller.controller_error import ControllerError -from ..config import Config from ..version import __version__ @@ -216,7 +214,10 @@ class Compute: """ Return the IP associated to the host """ - return socket.gethostbyname(self._host) + try: + return socket.gethostbyname(self._host) + except socket.gaierror: + return '0.0.0.0' @host.setter def host(self, host): @@ -360,6 +361,16 @@ class Compute: response = yield from self._run_http_query(method, path, data=data, **kwargs) return response + @asyncio.coroutine + def _try_reconnect(self): + """ + We catch error during reconnect + """ + try: + yield from self.connect() + except aiohttp.web.HTTPConflict: + pass + @locked_coroutine def connect(self): """ @@ -374,14 +385,18 @@ class Compute: 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) yield from self._controller.close_compute_projects(self) - asyncio.get_event_loop().call_later(2, lambda: asyncio.async(self.connect())) + + 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)) except aiohttp.web.HTTPUnauthorized: - raise aiohttp.web.HTTPConflict(text="Invalid auth for server {} ".format(self._id)) + raise aiohttp.web.HTTPConflict(text="Invalid auth for server {}".format(self._id)) + except aiohttp.web.HTTPServiceUnavailable: + raise aiohttp.web.HTTPConflict(text="The server {} is unavailable".format(self._id)) if "version" not in response.json: self._http_session.close() diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index 5f8bf61e..03c5f12c 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -52,9 +52,11 @@ class Link: return self._created @asyncio.coroutine - def add_node(self, node, adapter_number, port_number, label=None): + def add_node(self, node, adapter_number, port_number, label=None, dump=True): """ Add a node to the link + + :param dump: Dump project on disk """ port = node.get_port(adapter_number, port_number) @@ -101,7 +103,8 @@ class Link: self._created = True self._project.controller.notification.emit("link.created", self.__json__()) - self._project.dump() + if dump: + self._project.dump() @asyncio.coroutine def update_nodes(self, nodes): diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 4debcb73..8796e028 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -289,7 +289,10 @@ class Project: if '{0}' in base_name or '{id}' in base_name: # base name is a template, replace {0} or {id} by an unique identifier for number in range(1, 1000000): - name = base_name.format(number, id=number) + try: + name = base_name.format(number, id=number, name="Node") + except KeyError as e: + raise aiohttp.web.HTTPConflict(text="{" + e.args[0] + "} is not a valid replacement string in the node name") if name not in self._allocated_node_names: self._allocated_node_names.add(name) return name @@ -314,10 +317,11 @@ class Project: @open_required @asyncio.coroutine - def add_node(self, compute, name, node_id, node_type=None, **kwargs): + def add_node(self, compute, name, node_id, dump=True, node_type=None, **kwargs): """ Create a node or return an existing node + :param dump: Dump topology to disk :param kwargs: See the documentation of node """ if node_id in self._nodes: @@ -349,7 +353,8 @@ class Project: yield from node.create() self._nodes[node.id] = node self.controller.notification.emit("node.created", node.__json__()) - self.dump() + if dump: + self.dump() return node @locked_coroutine @@ -401,17 +406,19 @@ class Project: @open_required @asyncio.coroutine - def add_drawing(self, drawing_id=None, **kwargs): + def add_drawing(self, drawing_id=None, dump=True, **kwargs): """ Create an drawing or return an existing drawing + :param dump: Dump the topology to disk :param kwargs: See the documentation of drawing """ if drawing_id not in self._drawings: drawing = Drawing(self, drawing_id=drawing_id, **kwargs) self._drawings[drawing.id] = drawing self.controller.notification.emit("drawing.created", drawing.__json__()) - self.dump() + if dump: + self.dump() return drawing return self._drawings[drawing_id] @@ -435,15 +442,18 @@ class Project: @open_required @asyncio.coroutine - def add_link(self, link_id=None): + def add_link(self, link_id=None, dump=True): """ Create a link. By default the link is empty + + :param dump: Dump topology to disk """ if link_id and link_id in self._links: - return self._links[link.id] + return self._links[link_id] link = UDPLink(self, link_id=link_id) self._links[link.id] = link - self.dump() + if dump: + self.dump() return link @open_required @@ -626,15 +636,16 @@ class Project: compute = self.controller.get_compute(node.pop("compute_id")) name = node.pop("name") node_id = node.pop("node_id") - yield from self.add_node(compute, name, node_id, **node) + yield from self.add_node(compute, name, node_id, dump=False, **node) for link_data in topology.get("links", []): link = yield from self.add_link(link_id=link_data["link_id"]) 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")) + yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False) for drawing_data in topology.get("drawings", []): - drawing = yield from self.add_drawing(**drawing_data) + yield from self.add_drawing(dump=False, **drawing_data) + self.dump() # 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: diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index b9400364..3f7a1540 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -163,11 +163,14 @@ def _convert_2_0_0_beta_2(topo, topo_path): dynamips_dir = os.path.join(topo_dir, "project-files", "dynamips") node_dir = os.path.join(dynamips_dir, node_id) - os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True) - for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))): - shutil.move(path, os.path.join(node_dir, os.path.basename(path))) - for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))): - shutil.move(path, os.path.join(node_dir, "configs", os.path.basename(path))) + try: + os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True) + for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))): + shutil.move(path, os.path.join(node_dir, os.path.basename(path))) + for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))): + shutil.move(path, os.path.join(node_dir, "configs", os.path.basename(path))) + except OSError as e: + raise aiohttp.web.HTTPConflict(text="Can't convert project {}: {}".format(topo_path, str(e))) return topo diff --git a/gns3server/handlers/api/controller/node_handler.py b/gns3server/handlers/api/controller/node_handler.py index 67857df7..d9a715f0 100644 --- a/gns3server/handlers/api/controller/node_handler.py +++ b/gns3server/handlers/api/controller/node_handler.py @@ -344,10 +344,7 @@ class NodeHandler: raise aiohttp.web.HTTPForbidden node_type = node.node_type - if node_type == "dynamips": - path = "/project-files/{}/{}".format(node_type, path) - else: - path = "/project-files/{}/{}/{}".format(node_type, node.id, path) + path = "/project-files/{}/{}/{}".format(node_type, node.id, path) res = yield from node.compute.http_query("GET", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), timeout=None, raw=True) response.set_status(200) @@ -384,12 +381,9 @@ class NodeHandler: raise aiohttp.web.HTTPForbidden node_type = node.node_type - if node_type == "dynamips": - path = "/project-files/{}/{}".format(node_type, path) - else: - path = "/project-files/{}/{}/{}".format(node_type, node.id, path) + path = "/project-files/{}/{}/{}".format(node_type, node.id, path) data = yield from request.content.read() - res = yield from node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True) + yield from node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True) response.set_status(201) diff --git a/gns3server/utils/asyncio/serial.py b/gns3server/utils/asyncio/serial.py index 6dc961dd..48d12bc0 100644 --- a/gns3server/utils/asyncio/serial.py +++ b/gns3server/utils/asyncio/serial.py @@ -122,7 +122,10 @@ def _asyncio_open_serial_unix(path): raise NodeError('Pipe file "{}" is missing'.format(path)) output = SerialReaderWriterProtocol() - con = yield from asyncio.get_event_loop().create_unix_connection(lambda: output, path) + try: + yield from asyncio.get_event_loop().create_unix_connection(lambda: output, path) + except ConnectionRefusedError: + raise NodeError('Can\'t open pipe file "{}"'.format(path)) return output