mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Drop Python 3.4 and switch to async / await syntax for asyncio. Fixes #1425
This commit is contained in:
1 parent
8baa480b79
commit
76af98404a
119 files changed
+2432
-3084
No files matched your search
@@ -65,8 +65,7 @@ class Controller:
|
||||
log.info("Load controller configuration file {}".format(self._config_file))
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def download_appliance_templates(self):
|
||||
async def download_appliance_templates(self):
|
||||
|
||||
session = aiohttp.ClientSession()
|
||||
try:
|
||||
@@ -74,7 +73,7 @@ class Controller:
|
||||
if self._appliance_templates_etag:
|
||||
log.info("Checking if appliance templates are up-to-date (ETag {})".format(self._appliance_templates_etag))
|
||||
headers["If-None-Match"] = self._appliance_templates_etag
|
||||
response = yield from session.get('https://api.github.com/repos/GNS3/gns3-registry/contents/appliances', headers=headers)
|
||||
response = await session.get('https://api.github.com/repos/GNS3/gns3-registry/contents/appliances', headers=headers)
|
||||
if response.status == 304:
|
||||
log.info("Appliance templates are already up-to-date (ETag {})".format(self._appliance_templates_etag))
|
||||
return
|
||||
@@ -84,19 +83,19 @@ class Controller:
|
||||
if etag:
|
||||
self._appliance_templates_etag = etag
|
||||
self.save()
|
||||
json_data = yield from response.json()
|
||||
json_data = await response.json()
|
||||
response.close()
|
||||
appliances_dir = get_resource('appliances')
|
||||
for appliance in json_data:
|
||||
if appliance["type"] == "file":
|
||||
appliance_name = appliance["name"]
|
||||
log.info("Download appliance template file from '{}'".format(appliance["download_url"]))
|
||||
response = yield from session.get(appliance["download_url"])
|
||||
response = await session.get(appliance["download_url"])
|
||||
if response.status != 200:
|
||||
log.warning("Could not download '{}' due to HTTP error code {}".format(appliance["download_url"], response.status))
|
||||
continue
|
||||
try:
|
||||
appliance_data = yield from response.read()
|
||||
appliance_data = await response.read()
|
||||
except asyncio.TimeoutError:
|
||||
log.warning("Timeout while downloading '{}'".format(appliance["download_url"]))
|
||||
continue
|
||||
@@ -215,8 +214,7 @@ class Controller:
|
||||
for b in builtins:
|
||||
self._appliances[b.id] = b
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
|
||||
log.info("Controller is starting")
|
||||
self.load_base_files()
|
||||
@@ -235,9 +233,9 @@ class Controller:
|
||||
if name == "gns3vm":
|
||||
name = "Main server"
|
||||
|
||||
computes = yield from self._load_controller_settings()
|
||||
computes = await self._load_controller_settings()
|
||||
try:
|
||||
self._local_server = yield from self.add_compute(compute_id="local",
|
||||
self._local_server = await self.add_compute(compute_id="local",
|
||||
name=name,
|
||||
protocol=server_config.get("protocol", "http"),
|
||||
host=host,
|
||||
@@ -251,15 +249,15 @@ class Controller:
|
||||
sys.exit(1)
|
||||
for c in computes:
|
||||
try:
|
||||
yield from self.add_compute(**c)
|
||||
await self.add_compute(**c)
|
||||
except (aiohttp.web.HTTPError, KeyError):
|
||||
pass # Skip not available servers at loading
|
||||
yield from self.load_projects()
|
||||
await self.load_projects()
|
||||
try:
|
||||
yield from self.gns3vm.auto_start_vm()
|
||||
await self.gns3vm.auto_start_vm()
|
||||
except GNS3VMError as e:
|
||||
log.warning(str(e))
|
||||
yield from self._project_auto_open()
|
||||
await self._project_auto_open()
|
||||
|
||||
def _update_config(self):
|
||||
"""
|
||||
@@ -271,19 +269,18 @@ class Controller:
|
||||
self._local_server.user = server_config.get("user")
|
||||
self._local_server.password = server_config.get("password")
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
|
||||
log.info("Controller is Stopping")
|
||||
for project in self._projects.values():
|
||||
yield from project.close()
|
||||
await project.close()
|
||||
for compute in self._computes.values():
|
||||
try:
|
||||
yield from compute.close()
|
||||
await compute.close()
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPError, OSError):
|
||||
pass
|
||||
yield from self.gns3vm.exit_vm()
|
||||
await self.gns3vm.exit_vm()
|
||||
self._computes = {}
|
||||
self._projects = {}
|
||||
|
||||
@@ -321,15 +318,14 @@ class Controller:
|
||||
except OSError as e:
|
||||
log.error("Cannnot write configuration file '{}': {}".format(self._config_file, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _load_controller_settings(self):
|
||||
async def _load_controller_settings(self):
|
||||
"""
|
||||
Reload the controller configuration from disk
|
||||
"""
|
||||
|
||||
try:
|
||||
if not os.path.exists(self._config_file):
|
||||
yield from self._import_gns3_gui_conf()
|
||||
await self._import_gns3_gui_conf()
|
||||
self.save()
|
||||
with open(self._config_file) as f:
|
||||
data = json.load(f)
|
||||
@@ -350,8 +346,7 @@ class Controller:
|
||||
self.load_appliances()
|
||||
return data.get("computes", [])
|
||||
|
||||
@asyncio.coroutine
|
||||
def load_projects(self):
|
||||
async def load_projects(self):
|
||||
"""
|
||||
Preload the list of projects from disk
|
||||
"""
|
||||
@@ -366,7 +361,7 @@ class Controller:
|
||||
for file in os.listdir(project_dir):
|
||||
if file.endswith(".gns3"):
|
||||
try:
|
||||
yield from self.load_project(os.path.join(project_dir, file), load=False)
|
||||
await self.load_project(os.path.join(project_dir, file), load=False)
|
||||
except (aiohttp.web.HTTPConflict, NotImplementedError):
|
||||
pass # Skip not compatible projects
|
||||
except OSError as e:
|
||||
@@ -417,8 +412,7 @@ class Controller:
|
||||
os.makedirs(appliances_path, exist_ok=True)
|
||||
return appliances_path
|
||||
|
||||
@asyncio.coroutine
|
||||
def _import_gns3_gui_conf(self):
|
||||
async def _import_gns3_gui_conf(self):
|
||||
"""
|
||||
Import old config from GNS3 GUI
|
||||
"""
|
||||
@@ -430,7 +424,7 @@ class Controller:
|
||||
server_settings = data.get("Servers", {})
|
||||
for remote in server_settings.get("remote_servers", []):
|
||||
try:
|
||||
yield from self.add_compute(
|
||||
await self.add_compute(
|
||||
host=remote.get("host", "localhost"),
|
||||
port=remote.get("port", 3080),
|
||||
protocol=remote.get("protocol", "http"),
|
||||
@@ -488,8 +482,7 @@ class Controller:
|
||||
self.load_appliances()
|
||||
self.notification.controller_emit("settings.updated", val)
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_compute(self, compute_id=None, name=None, force=False, connect=True, **kwargs):
|
||||
async def add_compute(self, compute_id=None, name=None, force=False, connect=True, **kwargs):
|
||||
"""
|
||||
Add a server to the dictionary of compute servers controlled by this controller
|
||||
|
||||
@@ -519,24 +512,23 @@ class Controller:
|
||||
self._computes[compute.id] = compute
|
||||
self.save()
|
||||
if connect:
|
||||
yield from compute.connect()
|
||||
await compute.connect()
|
||||
self.notification.controller_emit("compute.created", compute.__json__())
|
||||
return compute
|
||||
else:
|
||||
if connect:
|
||||
yield from self._computes[compute_id].connect()
|
||||
await self._computes[compute_id].connect()
|
||||
self.notification.controller_emit("compute.updated", self._computes[compute_id].__json__())
|
||||
return self._computes[compute_id]
|
||||
|
||||
@asyncio.coroutine
|
||||
def close_compute_projects(self, compute):
|
||||
async def close_compute_projects(self, compute):
|
||||
"""
|
||||
Close projects running on a compute
|
||||
"""
|
||||
|
||||
for project in self._projects.values():
|
||||
if compute in project.computes:
|
||||
yield from project.close()
|
||||
await project.close()
|
||||
|
||||
def compute_has_open_project(self, compute):
|
||||
"""
|
||||
@@ -550,8 +542,7 @@ class Controller:
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete_compute(self, compute_id):
|
||||
async def delete_compute(self, compute_id):
|
||||
"""
|
||||
Delete a compute node. Project using this compute will be close
|
||||
|
||||
@@ -562,8 +553,8 @@ class Controller:
|
||||
compute = self.get_compute(compute_id)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
return
|
||||
yield from self.close_compute_projects(compute)
|
||||
yield from compute.close()
|
||||
await self.close_compute_projects(compute)
|
||||
await compute.close()
|
||||
del self._computes[compute_id]
|
||||
self.save()
|
||||
self.notification.controller_emit("compute.deleted", compute.__json__())
|
||||
@@ -603,8 +594,7 @@ class Controller:
|
||||
|
||||
return compute_id in self._computes
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_project(self, project_id=None, name=None, path=None, **kwargs):
|
||||
async def add_project(self, project_id=None, name=None, path=None, **kwargs):
|
||||
"""
|
||||
Creates a project or returns an existing project
|
||||
|
||||
@@ -635,8 +625,7 @@ class Controller:
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id))
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_loaded_project(self, project_id):
|
||||
async def get_loaded_project(self, project_id):
|
||||
"""
|
||||
Returns a project or raise a 404 error.
|
||||
|
||||
@@ -644,7 +633,7 @@ class Controller:
|
||||
"""
|
||||
|
||||
project = self.get_project(project_id)
|
||||
yield from project.wait_loaded()
|
||||
await project.wait_loaded()
|
||||
return project
|
||||
|
||||
def remove_project(self, project):
|
||||
@@ -652,8 +641,7 @@ class Controller:
|
||||
if project.id in self._projects:
|
||||
del self._projects[project.id]
|
||||
|
||||
@asyncio.coroutine
|
||||
def load_project(self, path, load=True):
|
||||
async def load_project(self, path, load=True):
|
||||
"""
|
||||
Load a project from a .gns3
|
||||
|
||||
@@ -670,20 +658,19 @@ class Controller:
|
||||
if topo_data["project_id"] in self._projects:
|
||||
project = self._projects[topo_data["project_id"]]
|
||||
else:
|
||||
project = yield from self.add_project(path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data)
|
||||
project = await self.add_project(path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data)
|
||||
if load or project.auto_open:
|
||||
yield from project.open()
|
||||
await project.open()
|
||||
return project
|
||||
|
||||
@asyncio.coroutine
|
||||
def _project_auto_open(self):
|
||||
async def _project_auto_open(self):
|
||||
"""
|
||||
Auto open the project with auto open enable
|
||||
"""
|
||||
|
||||
for project in self._projects.values():
|
||||
if project.auto_open:
|
||||
yield from project.open()
|
||||
await project.open()
|
||||
|
||||
def get_free_project_name(self, base_name):
|
||||
"""
|
||||
@@ -747,8 +734,7 @@ class Controller:
|
||||
Controller._instance = Controller()
|
||||
return Controller._instance
|
||||
|
||||
@asyncio.coroutine
|
||||
def autoidlepc(self, compute_id, platform, image, ram):
|
||||
async def autoidlepc(self, compute_id, platform, image, ram):
|
||||
"""
|
||||
Compute and IDLE PC value for an image
|
||||
|
||||
@@ -761,17 +747,16 @@ class Controller:
|
||||
compute = self.get_compute(compute_id)
|
||||
for project in list(self._projects.values()):
|
||||
if project.name == "AUTOIDLEPC":
|
||||
yield from project.delete()
|
||||
await project.delete()
|
||||
self.remove_project(project)
|
||||
project = yield from self.add_project(name="AUTOIDLEPC")
|
||||
node = yield from project.add_node(compute, "AUTOIDLEPC", str(uuid.uuid4()), node_type="dynamips", platform=platform, image=image, ram=ram)
|
||||
res = yield from node.dynamips_auto_idlepc()
|
||||
yield from project.delete()
|
||||
project = await self.add_project(name="AUTOIDLEPC")
|
||||
node = await project.add_node(compute, "AUTOIDLEPC", str(uuid.uuid4()), node_type="dynamips", platform=platform, image=image, ram=ram)
|
||||
res = await node.dynamips_auto_idlepc()
|
||||
await project.delete()
|
||||
self.remove_project(project)
|
||||
return res
|
||||
|
||||
@asyncio.coroutine
|
||||
def compute_ports(self, compute_id):
|
||||
async def compute_ports(self, compute_id):
|
||||
"""
|
||||
Get the ports used by a compute.
|
||||
|
||||
@@ -779,5 +764,5 @@ class Controller:
|
||||
"""
|
||||
|
||||
compute = self.get_compute(compute_id)
|
||||
response = yield from compute.get("/network/ports")
|
||||
response = await compute.get("/network/ports")
|
||||
return response.json
|
||||
@@ -27,7 +27,7 @@ from operator import itemgetter
|
||||
|
||||
from ..utils import parse_version
|
||||
from ..utils.images import list_images
|
||||
from ..utils.asyncio import locking, asyncio_ensure_future
|
||||
from ..utils.asyncio import locking
|
||||
from ..controller.controller_error import ControllerError
|
||||
from ..version import __version__, __version_info__
|
||||
|
||||
@@ -146,18 +146,16 @@ class Compute:
|
||||
"""
|
||||
self._last_error = msg
|
||||
|
||||
@asyncio.coroutine
|
||||
def interfaces(self):
|
||||
async def interfaces(self):
|
||||
"""
|
||||
Get the list of network on compute
|
||||
"""
|
||||
if not self._interfaces_cache:
|
||||
response = yield from self.get("/network/interfaces")
|
||||
response = await self.get("/network/interfaces")
|
||||
self._interfaces_cache = response.json
|
||||
return self._interfaces_cache
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self, **kwargs):
|
||||
async def update(self, **kwargs):
|
||||
for kw in kwargs:
|
||||
if kw not in ("user", "password"):
|
||||
setattr(self, kw, kwargs[kw])
|
||||
@@ -170,13 +168,12 @@ class Compute:
|
||||
self._controller.notification.controller_emit("compute.updated", self.__json__())
|
||||
self._controller.save()
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
async def close(self):
|
||||
self._connected = False
|
||||
if self._http_session:
|
||||
self._http_session.close()
|
||||
if self._ws:
|
||||
yield from self._ws.close()
|
||||
await self._ws.close()
|
||||
self._ws = None
|
||||
self._closed = True
|
||||
|
||||
@@ -314,8 +311,7 @@ class Compute:
|
||||
"last_error": self._last_error
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def download_file(self, project, path):
|
||||
async def download_file(self, project, path):
|
||||
"""
|
||||
Read file of a project and download it
|
||||
|
||||
@@ -325,13 +321,12 @@ class Compute:
|
||||
"""
|
||||
|
||||
url = self._getUrl("/projects/{}/files/{}".format(project.id, path))
|
||||
response = yield from self._session().request("GET", url, auth=self._auth)
|
||||
response = await self._session().request("GET", url, auth=self._auth)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path))
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def download_image(self, image_type, image):
|
||||
async def download_image(self, image_type, image):
|
||||
"""
|
||||
Read file of a project and download it
|
||||
|
||||
@@ -341,13 +336,12 @@ class Compute:
|
||||
"""
|
||||
|
||||
url = self._getUrl("/{}/images/{}".format(image_type, image))
|
||||
response = yield from self._session().request("GET", url, auth=self._auth)
|
||||
response = await self._session().request("GET", url, auth=self._auth)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(image))
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def stream_file(self, project, path, timeout=None):
|
||||
async def stream_file(self, project, path, timeout=None):
|
||||
"""
|
||||
Read file of a project and stream it
|
||||
|
||||
@@ -372,7 +366,7 @@ class Compute:
|
||||
self._response.close()
|
||||
|
||||
url = self._getUrl("/projects/{}/stream/{}".format(project.id, path))
|
||||
response = yield from self._session().request("GET", url, auth=self._auth, timeout=timeout)
|
||||
response = await self._session().request("GET", url, auth=self._auth, timeout=timeout)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path))
|
||||
elif response.status == 403:
|
||||
@@ -383,34 +377,31 @@ class Compute:
|
||||
path))
|
||||
return StreamResponse(response)
|
||||
|
||||
@asyncio.coroutine
|
||||
def http_query(self, method, path, data=None, dont_connect=False, **kwargs):
|
||||
async def http_query(self, method, path, data=None, dont_connect=False, **kwargs):
|
||||
"""
|
||||
: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()
|
||||
await self._controller.gns3vm.start()
|
||||
await self.connect()
|
||||
if not self._connected and not dont_connect:
|
||||
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)
|
||||
response = await self._run_http_query(method, path, data=data, **kwargs)
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def _try_reconnect(self):
|
||||
async def _try_reconnect(self):
|
||||
"""
|
||||
We catch error during reconnect
|
||||
"""
|
||||
try:
|
||||
yield from self.connect()
|
||||
await self.connect()
|
||||
except aiohttp.web.HTTPConflict:
|
||||
pass
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def connect(self):
|
||||
async def connect(self):
|
||||
"""
|
||||
Check if remote server is accessible
|
||||
"""
|
||||
@@ -418,7 +409,7 @@ class Compute:
|
||||
if not self._connected and not self._closed and self.host:
|
||||
try:
|
||||
log.info("Connecting to compute '{}'".format(self._id))
|
||||
response = yield from self._run_http_query("GET", "/capabilities")
|
||||
response = await self._run_http_query("GET", "/capabilities")
|
||||
except ComputeError as e:
|
||||
log.warning("Cannot connect to compute '{}': {}".format(self._id, e))
|
||||
# Try to reconnect after 2 seconds if server unavailable only if not during tests (otherwise we create a ressource usage bomb)
|
||||
@@ -430,8 +421,8 @@ class Compute:
|
||||
# After 5 failure we close the project using the compute to avoid sync issues
|
||||
if self._connection_failure == 10:
|
||||
log.error("Could not connect to compute '{}' after multiple attempts: {}".format(self._id, e))
|
||||
yield from self._controller.close_compute_projects(self)
|
||||
asyncio.get_event_loop().call_later(2, lambda: asyncio_ensure_future(self._try_reconnect()))
|
||||
await self._controller.close_compute_projects(self)
|
||||
asyncio.get_event_loop().call_later(2, lambda: asyncio.ensure_future(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))
|
||||
@@ -479,18 +470,17 @@ class Compute:
|
||||
self._last_error = None
|
||||
self._controller.notification.controller_emit("compute.updated", self.__json__())
|
||||
|
||||
@asyncio.coroutine
|
||||
def _connect_notification(self):
|
||||
async def _connect_notification(self):
|
||||
"""
|
||||
Connect to the notification stream
|
||||
"""
|
||||
try:
|
||||
self._ws = yield from self._session().ws_connect(self._getUrl("/notifications/ws"), auth=self._auth)
|
||||
self._ws = await self._session().ws_connect(self._getUrl("/notifications/ws"), auth=self._auth)
|
||||
except (aiohttp.WSServerHandshakeError, aiohttp.ClientResponseError):
|
||||
self._ws = None
|
||||
while self._ws is not None:
|
||||
try:
|
||||
response = yield from self._ws.receive()
|
||||
response = await self._ws.receive()
|
||||
except aiohttp.WSServerHandshakeError:
|
||||
self._ws = None
|
||||
break
|
||||
@@ -505,13 +495,13 @@ class Compute:
|
||||
self._memory_usage_percent = event["memory_usage_percent"]
|
||||
self._controller.notification.controller_emit("compute.updated", self.__json__())
|
||||
else:
|
||||
yield from self._controller.notification.dispatch(action, event, compute_id=self.id)
|
||||
await self._controller.notification.dispatch(action, event, compute_id=self.id)
|
||||
if self._ws:
|
||||
yield from self._ws.close()
|
||||
await self._ws.close()
|
||||
|
||||
# Try to reconnect after 1 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:
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio_ensure_future(self.connect()))
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect()))
|
||||
self._ws = None
|
||||
self._cpu_usage_percent = None
|
||||
self._memory_usage_percent = None
|
||||
@@ -536,8 +526,7 @@ class Compute:
|
||||
""" Returns URL for specific path at Compute"""
|
||||
return self._getUrl(path)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _run_http_query(self, method, path, data=None, timeout=20, raw=False):
|
||||
async def _run_http_query(self, method, path, data=None, timeout=20, raw=False):
|
||||
with Timeout(timeout):
|
||||
url = self._getUrl(path)
|
||||
headers = {}
|
||||
@@ -562,13 +551,13 @@ class Compute:
|
||||
data = json.dumps(data).encode("utf-8")
|
||||
try:
|
||||
log.debug("Attempting request to compute: {method} {url} {headers}".format(method=method, url=url, headers=headers))
|
||||
response = yield from self._session().request(method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout)
|
||||
response = await self._session().request(method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise ComputeError("Timeout error for {} call to {} after {}s".format(method, url, timeout))
|
||||
except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, ValueError, KeyError, socket.gaierror) as e:
|
||||
# aiohttp 2.3.1 raises socket.gaierror when cannot find host
|
||||
raise ComputeError(str(e))
|
||||
body = yield from response.read()
|
||||
body = await response.read()
|
||||
if body and not raw:
|
||||
body = body.decode()
|
||||
|
||||
@@ -617,46 +606,40 @@ class Compute:
|
||||
response.body = b""
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, path, **kwargs):
|
||||
return (yield from self.http_query("GET", path, **kwargs))
|
||||
async def get(self, path, **kwargs):
|
||||
return (await self.http_query("GET", path, **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, path, data={}, **kwargs):
|
||||
response = yield from self.http_query("POST", path, data, **kwargs)
|
||||
async def post(self, path, data={}, **kwargs):
|
||||
response = await self.http_query("POST", path, data, **kwargs)
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def put(self, path, data={}, **kwargs):
|
||||
response = yield from self.http_query("PUT", path, data, **kwargs)
|
||||
async def put(self, path, data={}, **kwargs):
|
||||
response = await self.http_query("PUT", path, data, **kwargs)
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, path, **kwargs):
|
||||
return (yield from self.http_query("DELETE", path, **kwargs))
|
||||
async def delete(self, path, **kwargs):
|
||||
return (await self.http_query("DELETE", path, **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def forward(self, method, type, path, data=None):
|
||||
async def forward(self, method, type, path, data=None):
|
||||
"""
|
||||
Forward a call to the emulator on compute
|
||||
"""
|
||||
try:
|
||||
action = "/{}/{}".format(type, path)
|
||||
res = yield from self.http_query(method, action, data=data, timeout=None)
|
||||
res = await self.http_query(method, action, data=data, timeout=None)
|
||||
except aiohttp.ServerDisconnectedError:
|
||||
log.error("Connection lost to %s during %s %s", self._id, method, action)
|
||||
raise aiohttp.web.HTTPGatewayTimeout()
|
||||
return res.json
|
||||
|
||||
@asyncio.coroutine
|
||||
def images(self, type):
|
||||
async def images(self, type):
|
||||
"""
|
||||
Return the list of images available for this type on controller
|
||||
and on the compute node.
|
||||
"""
|
||||
images = []
|
||||
|
||||
res = yield from self.http_query("GET", "/{}/images".format(type), timeout=None)
|
||||
res = await self.http_query("GET", "/{}/images".format(type), timeout=None)
|
||||
images = res.json
|
||||
|
||||
try:
|
||||
@@ -671,17 +654,15 @@ class Compute:
|
||||
raise ComputeError("Cannot list images: {}".format(str(e)))
|
||||
return images
|
||||
|
||||
@asyncio.coroutine
|
||||
def list_files(self, project):
|
||||
async def list_files(self, project):
|
||||
"""
|
||||
List files in the project on computes
|
||||
"""
|
||||
path = "/projects/{}/files".format(project.id)
|
||||
res = yield from self.http_query("GET", path, timeout=None)
|
||||
res = await self.http_query("GET", path, timeout=None)
|
||||
return res.json
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_ip_on_same_subnet(self, other_compute):
|
||||
async def get_ip_on_same_subnet(self, other_compute):
|
||||
"""
|
||||
Try to find the best ip for communication from one compute
|
||||
to another
|
||||
@@ -695,8 +676,8 @@ class Compute:
|
||||
if (self.host_ip not in ('0.0.0.0', '127.0.0.1') and other_compute.host_ip not in ('0.0.0.0', '127.0.0.1')):
|
||||
return (self.host_ip, other_compute.host_ip)
|
||||
|
||||
this_compute_interfaces = yield from self.interfaces()
|
||||
other_compute_interfaces = yield from other_compute.interfaces()
|
||||
this_compute_interfaces = await self.interfaces()
|
||||
other_compute_interfaces = await other_compute.interfaces()
|
||||
|
||||
# Sort interface to put the compute host in first position
|
||||
# we guess that if user specified this host it could have a reason (VMware Nat / Host only interface)
|
||||
|
||||
@@ -165,8 +165,7 @@ class Drawing:
|
||||
def rotation(self, val):
|
||||
self._rotation = val
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self, **kwargs):
|
||||
async def update(self, **kwargs):
|
||||
"""
|
||||
Update the drawing
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def export_project(project, temporary_dir, include_images=False, keep_compute_id=False, allow_all_nodes=False):
|
||||
async def export_project(project, temporary_dir, include_images=False, keep_compute_id=False, allow_all_nodes=False):
|
||||
"""
|
||||
Export a project to a zip file.
|
||||
|
||||
@@ -61,7 +60,7 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
|
||||
# First we process the .gns3 in order to be sure we don't have an error
|
||||
for file in os.listdir(project._path):
|
||||
if file.endswith(".gns3"):
|
||||
yield from _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir)
|
||||
await _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir)
|
||||
|
||||
# Export the local files
|
||||
for root, dirs, files in os.walk(project._path, topdown=True):
|
||||
@@ -86,15 +85,15 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
|
||||
downloaded_files = set()
|
||||
for compute in project.computes:
|
||||
if compute.id != "local":
|
||||
compute_files = yield from compute.list_files(project)
|
||||
compute_files = await compute.list_files(project)
|
||||
for compute_file in compute_files:
|
||||
if _is_exportable(compute_file["path"]):
|
||||
(fd, temp_path) = tempfile.mkstemp(dir=temporary_dir)
|
||||
f = open(fd, "wb", closefd=True)
|
||||
response = yield from compute.download_file(project, compute_file["path"])
|
||||
response = await compute.download_file(project, compute_file["path"])
|
||||
while True:
|
||||
try:
|
||||
data = yield from response.content.read(1024)
|
||||
data = await response.content.read(1024)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading file '{}' from remote compute server {}:{}".format(compute_file["path"], compute.host, compute.port))
|
||||
if not data:
|
||||
@@ -154,8 +153,7 @@ def _is_exportable(path):
|
||||
return True
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir):
|
||||
async def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir):
|
||||
"""
|
||||
Patch a project file (.gns3) to export a project.
|
||||
The .gns3 file is renamed to project.gns3
|
||||
@@ -219,7 +217,7 @@ def _patch_project_file(project, path, zstream, include_images, keep_compute_id,
|
||||
for i in images if i['compute_id'] != 'local'])
|
||||
|
||||
for compute_id, image_type, image in remote_images:
|
||||
yield from _export_remote_images(project, compute_id, image_type, image, zstream, temporary_dir)
|
||||
await _export_remote_images(project, compute_id, image_type, image, zstream, temporary_dir)
|
||||
|
||||
zstream.writestr("project.gns3", json.dumps(topology).encode())
|
||||
return images
|
||||
@@ -253,8 +251,7 @@ def _export_local_image(image, zstream):
|
||||
return
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _export_remote_images(project, compute_id, image_type, image, project_zipfile, temporary_dir):
|
||||
async def _export_remote_images(project, compute_id, image_type, image, project_zipfile, temporary_dir):
|
||||
"""
|
||||
Export specific image from remote compute.
|
||||
"""
|
||||
@@ -268,14 +265,14 @@ def _export_remote_images(project, compute_id, image_type, image, project_zipfil
|
||||
|
||||
(fd, temp_path) = tempfile.mkstemp(dir=temporary_dir)
|
||||
f = open(fd, "wb", closefd=True)
|
||||
response = yield from compute.download_image(image_type, image)
|
||||
response = await compute.download_image(image_type, image)
|
||||
|
||||
if response.status != 200:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot export image from '{}' compute. Compute returned status code {}.".format(compute_id, response.status))
|
||||
|
||||
while True:
|
||||
try:
|
||||
data = yield from response.content.read(1024)
|
||||
data = await response.content.read(1024)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading image '{}' from remote compute server {}:{}".format(image, compute.host, compute.port))
|
||||
if not data:
|
||||
|
||||
@@ -21,7 +21,7 @@ import asyncio
|
||||
import aiohttp
|
||||
import ipaddress
|
||||
|
||||
from ...utils.asyncio import locking, asyncio_ensure_future
|
||||
from ...utils.asyncio import locking
|
||||
from .vmware_gns3_vm import VMwareGNS3VM
|
||||
from .virtualbox_gns3_vm import VirtualBoxGNS3VM
|
||||
from .hyperv_gns3_vm import HyperVGNS3VM
|
||||
@@ -204,8 +204,7 @@ class GNS3VM:
|
||||
|
||||
self._settings.update(val)
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_settings(self, settings):
|
||||
async def update_settings(self, settings):
|
||||
"""
|
||||
Update settings and will restart the VM if require
|
||||
"""
|
||||
@@ -213,15 +212,15 @@ class GNS3VM:
|
||||
new_settings = copy.copy(self._settings)
|
||||
new_settings.update(settings)
|
||||
if self.settings != new_settings:
|
||||
yield from self._stop()
|
||||
await self._stop()
|
||||
self._settings = settings
|
||||
self._controller.save()
|
||||
if self.enable:
|
||||
yield from self.start()
|
||||
await self.start()
|
||||
else:
|
||||
# When user fix something on his system and try again
|
||||
if self.enable and not self.current_engine().running:
|
||||
yield from self.start()
|
||||
await self.start()
|
||||
|
||||
def _get_engine(self, engine):
|
||||
"""
|
||||
@@ -248,8 +247,7 @@ class GNS3VM:
|
||||
def __json__(self):
|
||||
return self._settings
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self, engine):
|
||||
async def list(self, engine):
|
||||
"""
|
||||
List VMS for an engine
|
||||
"""
|
||||
@@ -257,7 +255,7 @@ class GNS3VM:
|
||||
engine = self._get_engine(engine)
|
||||
vms = []
|
||||
try:
|
||||
for vm in (yield from engine.list()):
|
||||
for vm in (await engine.list()):
|
||||
vms.append({"vmname": vm["vmname"]})
|
||||
except GNS3VMError as e:
|
||||
# We raise error only if user activated the GNS3 VM
|
||||
@@ -266,19 +264,18 @@ class GNS3VM:
|
||||
raise e
|
||||
return vms
|
||||
|
||||
@asyncio.coroutine
|
||||
def auto_start_vm(self):
|
||||
async def auto_start_vm(self):
|
||||
"""
|
||||
Auto start the GNS3 VM if require
|
||||
"""
|
||||
|
||||
if self.enable:
|
||||
try:
|
||||
yield from self.start()
|
||||
await self.start()
|
||||
except GNS3VMError as e:
|
||||
# User will receive the error later when they will try to use the node
|
||||
try:
|
||||
compute = yield from self._controller.add_compute(compute_id="vm",
|
||||
compute = await self._controller.add_compute(compute_id="vm",
|
||||
name="GNS3 VM ({})".format(self.current_engine().vmname),
|
||||
host=None,
|
||||
force=True)
|
||||
@@ -288,21 +285,19 @@ class GNS3VM:
|
||||
pass
|
||||
log.error("Cannot start the GNS3 VM: {}".format(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def exit_vm(self):
|
||||
async def exit_vm(self):
|
||||
|
||||
if self.enable:
|
||||
try:
|
||||
if self._settings["when_exit"] == "stop":
|
||||
yield from self._stop()
|
||||
await self._stop()
|
||||
elif self._settings["when_exit"] == "suspend":
|
||||
yield from self._suspend()
|
||||
await self._suspend()
|
||||
except GNS3VMError as e:
|
||||
log.warning(str(e))
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Start the GNS3 VM
|
||||
"""
|
||||
@@ -316,22 +311,22 @@ class GNS3VM:
|
||||
engine.ram = self._settings["ram"]
|
||||
engine.vcpus = self._settings["vcpus"]
|
||||
engine.headless = self._settings["headless"]
|
||||
compute = yield from self._controller.add_compute(compute_id="vm",
|
||||
compute = await self._controller.add_compute(compute_id="vm",
|
||||
name="GNS3 VM is starting ({})".format(engine.vmname),
|
||||
host=None,
|
||||
force=True,
|
||||
connect=False)
|
||||
|
||||
try:
|
||||
yield from engine.start()
|
||||
await engine.start()
|
||||
except Exception as e:
|
||||
yield from self._controller.delete_compute("vm")
|
||||
await self._controller.delete_compute("vm")
|
||||
log.error("Cannot start the GNS3 VM: {}".format(str(e)))
|
||||
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname))
|
||||
await compute.update(name="GNS3 VM ({})".format(engine.vmname))
|
||||
compute.set_last_error(str(e))
|
||||
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),
|
||||
await compute.connect() # we can connect now that the VM has started
|
||||
await compute.update(name="GNS3 VM ({})".format(engine.vmname),
|
||||
protocol=self.protocol,
|
||||
host=self.ip_address,
|
||||
port=self.port,
|
||||
@@ -340,16 +335,15 @@ class GNS3VM:
|
||||
|
||||
# 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_ensure_future(self._check_network(compute)))
|
||||
asyncio.get_event_loop().call_later(10, lambda: asyncio.ensure_future(self._check_network(compute)))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _check_network(self, compute):
|
||||
async def _check_network(self, compute):
|
||||
"""
|
||||
Check that the VM is in the same subnet as the local server
|
||||
"""
|
||||
|
||||
try:
|
||||
vm_interfaces = yield from compute.interfaces()
|
||||
vm_interfaces = await compute.interfaces()
|
||||
vm_interface_netmask = None
|
||||
for interface in vm_interfaces:
|
||||
if interface["ip_address"] == self.ip_address:
|
||||
@@ -360,7 +354,7 @@ class GNS3VM:
|
||||
for compute_id in self._controller.computes:
|
||||
if compute_id == "local":
|
||||
compute = self._controller.get_compute(compute_id)
|
||||
interfaces = yield from compute.interfaces()
|
||||
interfaces = await compute.interfaces()
|
||||
netmask = None
|
||||
for interface in interfaces:
|
||||
if interface["ip_address"] == compute.host_ip:
|
||||
@@ -378,27 +372,25 @@ class GNS3VM:
|
||||
log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e.text))
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def _suspend(self):
|
||||
async def _suspend(self):
|
||||
"""
|
||||
Suspend the GNS3 VM
|
||||
"""
|
||||
engine = self.current_engine()
|
||||
if "vm" in self._controller.computes:
|
||||
yield from self._controller.delete_compute("vm")
|
||||
await self._controller.delete_compute("vm")
|
||||
if engine.running:
|
||||
log.info("Suspend the GNS3 VM")
|
||||
yield from engine.suspend()
|
||||
await engine.suspend()
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def _stop(self):
|
||||
async def _stop(self):
|
||||
"""
|
||||
Stop the GNS3 VM
|
||||
"""
|
||||
engine = self.current_engine()
|
||||
if "vm" in self._controller.computes:
|
||||
yield from self._controller.delete_compute("vm")
|
||||
await self._controller.delete_compute("vm")
|
||||
if engine.running:
|
||||
log.info("Stop the GNS3 VM")
|
||||
yield from engine.stop()
|
||||
await engine.stop()
|
||||
@@ -254,32 +254,28 @@ class BaseGNS3VM:
|
||||
|
||||
return self._engine
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self):
|
||||
async def list(self):
|
||||
"""
|
||||
List all VMs
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Starts the GNS3 VM.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspend the GNS3 VM.
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stops the GNS3 VM.
|
||||
"""
|
||||
|
||||
@@ -164,8 +164,7 @@ class HyperVGNS3VM(BaseGNS3VM):
|
||||
except Exception as e:
|
||||
raise GNS3VMError("Could not set to {} and RAM amount set to {}: {}".format(vcpus, ram, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self):
|
||||
async def list(self):
|
||||
"""
|
||||
List all Hyper-V VMs
|
||||
"""
|
||||
@@ -189,8 +188,7 @@ class HyperVGNS3VM(BaseGNS3VM):
|
||||
|
||||
return wmi.WMI(moniker=path.replace('\\', '/'))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _set_state(self, state):
|
||||
async def _set_state(self, state):
|
||||
"""
|
||||
Set the desired state of the VM
|
||||
"""
|
||||
@@ -203,15 +201,14 @@ class HyperVGNS3VM(BaseGNS3VM):
|
||||
if ret == HyperVGNS3VM._WMI_JOB_STATUS_STARTED:
|
||||
job = self._get_wmi_obj(job_path)
|
||||
while job.JobState == HyperVGNS3VM._WMI_JOB_STATE_RUNNING:
|
||||
yield from asyncio.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
job = self._get_wmi_obj(job_path)
|
||||
if job.JobState != HyperVGNS3VM._WMI_JOB_STATE_COMPLETED:
|
||||
raise GNS3VMError("Error while changing state: {}".format(job.ErrorSummaryDescription))
|
||||
elif ret != 0 or ret != 32775:
|
||||
raise GNS3VMError("Failed to change state to {}".format(state))
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Starts the GNS3 VM.
|
||||
"""
|
||||
@@ -228,7 +225,7 @@ class HyperVGNS3VM(BaseGNS3VM):
|
||||
|
||||
# start the VM
|
||||
try:
|
||||
yield from self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_ENABLED)
|
||||
await self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_ENABLED)
|
||||
except GNS3VMError as e:
|
||||
raise GNS3VMError("Failed to start the GNS3 VM: {}".format(e))
|
||||
log.info("GNS3 VM has been started")
|
||||
@@ -259,32 +256,30 @@ class HyperVGNS3VM(BaseGNS3VM):
|
||||
break
|
||||
elif trial == 0:
|
||||
raise GNS3VMError("Could not find guest IP address for {}".format(self.vmname))
|
||||
yield from asyncio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
self.ip_address = guest_ip_address
|
||||
log.info("GNS3 VM IP address set to {}".format(guest_ip_address))
|
||||
self.running = True
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspends the GNS3 VM.
|
||||
"""
|
||||
|
||||
try:
|
||||
yield from self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_PAUSED)
|
||||
await self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_PAUSED)
|
||||
except GNS3VMError as e:
|
||||
raise GNS3VMError("Failed to suspend the GNS3 VM: {}".format(e))
|
||||
log.info("GNS3 VM has been suspended")
|
||||
self.running = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stops the GNS3 VM.
|
||||
"""
|
||||
|
||||
try:
|
||||
yield from self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_SHUTDOWN)
|
||||
await self._set_state(HyperVGNS3VM._HYPERV_VM_STATE_SHUTDOWN)
|
||||
except GNS3VMError as e:
|
||||
raise GNS3VMError("Failed to stop the GNS3 VM: {}".format(e))
|
||||
log.info("GNS3 VM has been stopped")
|
||||
|
||||
@@ -32,8 +32,7 @@ class RemoteGNS3VM(BaseGNS3VM):
|
||||
self._engine = "remote"
|
||||
super().__init__(controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self):
|
||||
async def list(self):
|
||||
"""
|
||||
List all VMs
|
||||
"""
|
||||
@@ -45,8 +44,7 @@ class RemoteGNS3VM(BaseGNS3VM):
|
||||
res.append({"vmname": compute.name})
|
||||
return res
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Starts the GNS3 VM.
|
||||
"""
|
||||
@@ -65,15 +63,13 @@ class RemoteGNS3VM(BaseGNS3VM):
|
||||
return
|
||||
raise GNS3VMError("Can't start the GNS3 VM remote VM {} not found".format(self.vmname))
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspend do nothing for remote server
|
||||
"""
|
||||
self.running = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stops the GNS3 VM.
|
||||
"""
|
||||
|
||||
@@ -40,24 +40,22 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
super().__init__(controller)
|
||||
self._virtualbox_manager = VirtualBox()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _execute(self, subcommand, args, timeout=60):
|
||||
async def _execute(self, subcommand, args, timeout=60):
|
||||
|
||||
try:
|
||||
result = yield from self._virtualbox_manager.execute(subcommand, args, timeout)
|
||||
result = await self._virtualbox_manager.execute(subcommand, args, timeout)
|
||||
return ("\n".join(result))
|
||||
except VirtualBoxError as e:
|
||||
raise GNS3VMError("Error while executing VBoxManage command: {}".format(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_state(self):
|
||||
async def _get_state(self):
|
||||
"""
|
||||
Returns the VM state (e.g. running, paused etc.)
|
||||
|
||||
:returns: state (string)
|
||||
"""
|
||||
|
||||
result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
result = await self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
for info in result.splitlines():
|
||||
if '=' in info:
|
||||
name, value = info.split('=', 1)
|
||||
@@ -65,15 +63,14 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return value.strip('"')
|
||||
return "unknown"
|
||||
|
||||
@asyncio.coroutine
|
||||
def _look_for_interface(self, network_backend):
|
||||
async def _look_for_interface(self, network_backend):
|
||||
"""
|
||||
Look for an interface with a specific network backend.
|
||||
|
||||
:returns: interface number or -1 if none is found
|
||||
"""
|
||||
|
||||
result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
result = await self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
interface = -1
|
||||
for info in result.splitlines():
|
||||
if '=' in info:
|
||||
@@ -86,15 +83,14 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
continue
|
||||
return interface
|
||||
|
||||
@asyncio.coroutine
|
||||
def _look_for_vboxnet(self, interface_number):
|
||||
async def _look_for_vboxnet(self, interface_number):
|
||||
"""
|
||||
Look for the VirtualBox network name associated with a host only interface.
|
||||
|
||||
:returns: None or vboxnet name
|
||||
"""
|
||||
|
||||
result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
result = await self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
for info in result.splitlines():
|
||||
if '=' in info:
|
||||
name, value = info.split('=', 1)
|
||||
@@ -102,8 +98,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return value.strip('"')
|
||||
return None
|
||||
|
||||
@asyncio.coroutine
|
||||
def _check_dhcp_server(self, vboxnet):
|
||||
async def _check_dhcp_server(self, vboxnet):
|
||||
"""
|
||||
Check if the DHCP server associated with a vboxnet is enabled.
|
||||
|
||||
@@ -111,7 +106,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
properties = yield from self._execute("list", ["dhcpservers"])
|
||||
properties = await self._execute("list", ["dhcpservers"])
|
||||
flag_dhcp_server_found = False
|
||||
for prop in properties.splitlines():
|
||||
try:
|
||||
@@ -125,8 +120,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def _check_vboxnet_exists(self, vboxnet):
|
||||
async def _check_vboxnet_exists(self, vboxnet):
|
||||
"""
|
||||
Check if the vboxnet interface exists
|
||||
|
||||
@@ -134,7 +128,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
properties = yield from self._execute("list", ["hostonlyifs"])
|
||||
properties = await self._execute("list", ["hostonlyifs"])
|
||||
for prop in properties.splitlines():
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
@@ -144,13 +138,12 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def _find_first_available_vboxnet(self):
|
||||
async def _find_first_available_vboxnet(self):
|
||||
"""
|
||||
Find the first available vboxnet.
|
||||
"""
|
||||
|
||||
properties = yield from self._execute("list", ["hostonlyifs"])
|
||||
properties = await self._execute("list", ["hostonlyifs"])
|
||||
for prop in properties.splitlines():
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
@@ -160,15 +153,14 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return value.strip()
|
||||
return None
|
||||
|
||||
@asyncio.coroutine
|
||||
def _check_vbox_port_forwarding(self):
|
||||
async def _check_vbox_port_forwarding(self):
|
||||
"""
|
||||
Checks if the NAT port forwarding rule exists.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
result = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
result = await self._execute("showvminfo", [self._vmname, "--machinereadable"])
|
||||
for info in result.splitlines():
|
||||
if '=' in info:
|
||||
name, value = info.split('=', 1)
|
||||
@@ -176,66 +168,64 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self):
|
||||
async def list(self):
|
||||
"""
|
||||
List all VirtualBox VMs
|
||||
"""
|
||||
|
||||
return (yield from self._virtualbox_manager.list_vms())
|
||||
return (await self._virtualbox_manager.list_vms())
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Start the GNS3 VM.
|
||||
"""
|
||||
|
||||
# get a NAT interface number
|
||||
nat_interface_number = yield from self._look_for_interface("nat")
|
||||
nat_interface_number = await self._look_for_interface("nat")
|
||||
if nat_interface_number < 0:
|
||||
raise GNS3VMError('VM "{}" must have a NAT interface configured in order to start'.format(self.vmname))
|
||||
|
||||
hostonly_interface_number = yield from self._look_for_interface("hostonly")
|
||||
hostonly_interface_number = await self._look_for_interface("hostonly")
|
||||
if hostonly_interface_number < 0:
|
||||
raise GNS3VMError('VM "{}" must have a host-only interface configured in order to start'.format(self.vmname))
|
||||
|
||||
vboxnet = yield from self._look_for_vboxnet(hostonly_interface_number)
|
||||
vboxnet = await self._look_for_vboxnet(hostonly_interface_number)
|
||||
if vboxnet is None:
|
||||
raise GNS3VMError('A VirtualBox host-only network could not be found on network adapter {} for "{}"'.format(hostonly_interface_number, self._vmname))
|
||||
|
||||
if not (yield from self._check_vboxnet_exists(vboxnet)):
|
||||
if not (await self._check_vboxnet_exists(vboxnet)):
|
||||
if sys.platform.startswith("win") and vboxnet == "vboxnet0":
|
||||
# The GNS3 VM is configured with vboxnet0 by default which is not available
|
||||
# on Windows. Try to patch this with the first available vboxnet we find.
|
||||
first_available_vboxnet = yield from self._find_first_available_vboxnet()
|
||||
first_available_vboxnet = await self._find_first_available_vboxnet()
|
||||
if first_available_vboxnet is None:
|
||||
raise GNS3VMError('Please add a VirtualBox host-only network with DHCP enabled and attached it to network adapter {} for "{}"'.format(hostonly_interface_number, self._vmname))
|
||||
yield from self.set_hostonly_network(hostonly_interface_number, first_available_vboxnet)
|
||||
await self.set_hostonly_network(hostonly_interface_number, first_available_vboxnet)
|
||||
vboxnet = first_available_vboxnet
|
||||
else:
|
||||
raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet,
|
||||
hostonly_interface_number,
|
||||
self._vmname))
|
||||
|
||||
if not (yield from self._check_dhcp_server(vboxnet)):
|
||||
if not (await self._check_dhcp_server(vboxnet)):
|
||||
raise GNS3VMError('DHCP must be enabled on VirtualBox host-only network "{}"'.format(vboxnet))
|
||||
|
||||
vm_state = yield from self._get_state()
|
||||
vm_state = await self._get_state()
|
||||
log.info('"{}" state is {}'.format(self._vmname, vm_state))
|
||||
|
||||
if vm_state == "poweroff":
|
||||
yield from self.set_vcpus(self.vcpus)
|
||||
yield from self.set_ram(self.ram)
|
||||
await self.set_vcpus(self.vcpus)
|
||||
await self.set_ram(self.ram)
|
||||
|
||||
if vm_state in ("poweroff", "saved"):
|
||||
# start the VM if it is not running
|
||||
args = [self._vmname]
|
||||
if self._headless:
|
||||
args.extend(["--type", "headless"])
|
||||
yield from self._execute("startvm", args)
|
||||
await self._execute("startvm", args)
|
||||
elif vm_state == "paused":
|
||||
args = [self._vmname, "resume"]
|
||||
yield from self._execute("controlvm", args)
|
||||
await self._execute("controlvm", args)
|
||||
ip_address = "127.0.0.1"
|
||||
try:
|
||||
# get a random port on localhost
|
||||
@@ -246,23 +236,22 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
except OSError as e:
|
||||
raise GNS3VMError("Error while getting random port: {}".format(e))
|
||||
|
||||
if (yield from self._check_vbox_port_forwarding()):
|
||||
if (await self._check_vbox_port_forwarding()):
|
||||
# delete the GNS3VM NAT port forwarding rule if it exists
|
||||
log.info("Removing GNS3VM NAT port forwarding rule from interface {}".format(nat_interface_number))
|
||||
yield from self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])
|
||||
await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number), "delete", "GNS3VM"])
|
||||
|
||||
# add a GNS3VM NAT port forwarding rule to redirect 127.0.0.1 with random port to port 3080 in the VM
|
||||
log.info("Adding GNS3VM NAT port forwarding rule with port {} to interface {}".format(api_port, nat_interface_number))
|
||||
yield from self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number),
|
||||
await self._execute("controlvm", [self._vmname, "natpf{}".format(nat_interface_number),
|
||||
"GNS3VM,tcp,{},{},,3080".format(ip_address, api_port)])
|
||||
|
||||
self.ip_address = yield from self._get_ip(hostonly_interface_number, api_port)
|
||||
self.ip_address = await self._get_ip(hostonly_interface_number, api_port)
|
||||
self.port = 3080
|
||||
log.info("GNS3 VM has been started with IP {}".format(self.ip_address))
|
||||
self.running = True
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_ip(self, hostonly_interface_number, api_port):
|
||||
async def _get_ip(self, hostonly_interface_number, api_port):
|
||||
"""
|
||||
Get the IP from VirtualBox.
|
||||
|
||||
@@ -276,14 +265,14 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
session = aiohttp.ClientSession()
|
||||
try:
|
||||
resp = None
|
||||
resp = yield from session.get('http://127.0.0.1:{}/v2/compute/network/interfaces'.format(api_port))
|
||||
resp = await session.get('http://127.0.0.1:{}/v2/compute/network/interfaces'.format(api_port))
|
||||
except (OSError, aiohttp.ClientError, TimeoutError, asyncio.TimeoutError):
|
||||
pass
|
||||
|
||||
if resp:
|
||||
if resp.status < 300:
|
||||
try:
|
||||
json_data = yield from resp.json()
|
||||
json_data = await resp.json()
|
||||
except ValueError:
|
||||
pass
|
||||
resp.close()
|
||||
@@ -296,35 +285,33 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
if "ip_address" in interface and len(interface["ip_address"]) > 0:
|
||||
return interface["ip_address"]
|
||||
remaining_try -= 1
|
||||
yield from asyncio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
raise GNS3VMError("Could not get the GNS3 VM ip make sure the VM receive an IP from VirtualBox")
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspend the GNS3 VM.
|
||||
"""
|
||||
|
||||
yield from self._execute("controlvm", [self._vmname, "savestate"], timeout=3)
|
||||
await self._execute("controlvm", [self._vmname, "savestate"], timeout=3)
|
||||
log.info("GNS3 VM has been suspend")
|
||||
self.running = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stops the GNS3 VM.
|
||||
"""
|
||||
|
||||
vm_state = yield from self._get_state()
|
||||
vm_state = await self._get_state()
|
||||
if vm_state == "poweroff":
|
||||
self.running = False
|
||||
return
|
||||
|
||||
yield from self._execute("controlvm", [self._vmname, "acpipowerbutton"], timeout=3)
|
||||
await self._execute("controlvm", [self._vmname, "acpipowerbutton"], timeout=3)
|
||||
trial = 120
|
||||
while True:
|
||||
try:
|
||||
vm_state = yield from self._get_state()
|
||||
vm_state = await self._get_state()
|
||||
# During a small amount of time the command will fail
|
||||
except GNS3VMError:
|
||||
vm_state = "running"
|
||||
@@ -332,37 +319,34 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
break
|
||||
trial -= 1
|
||||
if trial == 0:
|
||||
yield from self._execute("controlvm", [self._vmname, "poweroff"], timeout=3)
|
||||
await self._execute("controlvm", [self._vmname, "poweroff"], timeout=3)
|
||||
break
|
||||
yield from asyncio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
log.info("GNS3 VM has been stopped")
|
||||
self.running = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_vcpus(self, vcpus):
|
||||
async def set_vcpus(self, vcpus):
|
||||
"""
|
||||
Set the number of vCPU cores for the GNS3 VM.
|
||||
|
||||
:param vcpus: number of vCPU cores
|
||||
"""
|
||||
|
||||
yield from self._execute("modifyvm", [self._vmname, "--cpus", str(vcpus)], timeout=3)
|
||||
await self._execute("modifyvm", [self._vmname, "--cpus", str(vcpus)], timeout=3)
|
||||
log.info("GNS3 VM vCPU count set to {}".format(vcpus))
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_ram(self, ram):
|
||||
async def set_ram(self, ram):
|
||||
"""
|
||||
Set the RAM amount for the GNS3 VM.
|
||||
|
||||
:param ram: amount of memory
|
||||
"""
|
||||
|
||||
yield from self._execute("modifyvm", [self._vmname, "--memory", str(ram)], timeout=3)
|
||||
await self._execute("modifyvm", [self._vmname, "--memory", str(ram)], timeout=3)
|
||||
log.info("GNS3 VM RAM amount set to {}".format(ram))
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_hostonly_network(self, adapter_number, hostonly_network_name):
|
||||
async def set_hostonly_network(self, adapter_number, hostonly_network_name):
|
||||
"""
|
||||
Set a VirtualBox host-only network on a network adapter for the GNS3 VM.
|
||||
|
||||
@@ -370,7 +354,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
:param hostonly_network_name: name of the VirtualBox host-only network
|
||||
"""
|
||||
|
||||
yield from self._execute("modifyvm", [self._vmname, "--hostonlyadapter{}".format(adapter_number), hostonly_network_name], timeout=3)
|
||||
await self._execute("modifyvm", [self._vmname, "--hostonlyadapter{}".format(adapter_number), hostonly_network_name], timeout=3)
|
||||
log.info('VirtualBox host-only network "{}" set on network adapter {} for "{}"'.format(hostonly_network_name,
|
||||
adapter_number,
|
||||
self._vmname))
|
||||
@@ -43,24 +43,21 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
def vmx_path(self):
|
||||
return self._vmx_path
|
||||
|
||||
@asyncio.coroutine
|
||||
def _execute(self, subcommand, args, timeout=60, log_level=logging.INFO):
|
||||
async def _execute(self, subcommand, args, timeout=60, log_level=logging.INFO):
|
||||
|
||||
try:
|
||||
result = yield from self._vmware_manager.execute(subcommand, args, timeout, log_level=log_level)
|
||||
result = await self._vmware_manager.execute(subcommand, args, timeout, log_level=log_level)
|
||||
return (''.join(result))
|
||||
except VMwareError as e:
|
||||
raise GNS3VMError("Error while executing VMware command: {}".format(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _is_running(self):
|
||||
result = yield from self._vmware_manager.execute("list", [])
|
||||
async def _is_running(self):
|
||||
result = await self._vmware_manager.execute("list", [])
|
||||
if self._vmx_path in result:
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def _set_vcpus_ram(self, vcpus, ram):
|
||||
async def _set_vcpus_ram(self, vcpus, ram):
|
||||
"""
|
||||
Set the number of vCPU cores and amount of RAM for the GNS3 VM.
|
||||
|
||||
@@ -85,8 +82,7 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
except OSError as e:
|
||||
raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _set_extra_options(self):
|
||||
async def _set_extra_options(self):
|
||||
try:
|
||||
"""
|
||||
Due to bug/change in VMWare 14 we're not able to pass Hardware Virtualization in GNS3VM.
|
||||
@@ -109,23 +105,21 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
except OSError as e:
|
||||
raise GNS3VMError('Could not read/write VMware VMX file "{}": {}'.format(self._vmx_path, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def list(self):
|
||||
async def list(self):
|
||||
"""
|
||||
List all VMware VMs
|
||||
"""
|
||||
try:
|
||||
return (yield from self._vmware_manager.list_vms())
|
||||
return (await self._vmware_manager.list_vms())
|
||||
except VMwareError as e:
|
||||
raise GNS3VMError("Could not list VMware VMs: {}".format(str(e)))
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
async def start(self):
|
||||
"""
|
||||
Starts the GNS3 VM.
|
||||
"""
|
||||
|
||||
vms = yield from self.list()
|
||||
vms = await self.list()
|
||||
for vm in vms:
|
||||
if vm["vmname"] == self.vmname:
|
||||
self._vmx_path = vm["vmx_path"]
|
||||
@@ -138,25 +132,25 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
raise GNS3VMError("VMware VMX file {} doesn't exist".format(self._vmx_path))
|
||||
|
||||
# check if the VMware guest tools are installed
|
||||
vmware_tools_state = yield from self._execute("checkToolsState", [self._vmx_path])
|
||||
vmware_tools_state = await self._execute("checkToolsState", [self._vmx_path])
|
||||
if vmware_tools_state not in ("installed", "running"):
|
||||
raise GNS3VMError("VMware tools are not installed in {}".format(self.vmname))
|
||||
|
||||
try:
|
||||
running = yield from self._is_running()
|
||||
running = await self._is_running()
|
||||
except VMwareError as e:
|
||||
raise GNS3VMError("Could not list VMware VMs: {}".format(str(e)))
|
||||
if not running:
|
||||
log.info("Update GNS3 VM settings")
|
||||
# set the number of vCPUs and amount of RAM
|
||||
yield from self._set_vcpus_ram(self.vcpus, self.ram)
|
||||
yield from self._set_extra_options()
|
||||
await self._set_vcpus_ram(self.vcpus, self.ram)
|
||||
await self._set_extra_options()
|
||||
|
||||
# start the VM
|
||||
args = [self._vmx_path]
|
||||
if self._headless:
|
||||
args.extend(["nogui"])
|
||||
yield from self._execute("start", args)
|
||||
await self._execute("start", args)
|
||||
log.info("GNS3 VM has been started")
|
||||
|
||||
# get the guest IP address (first adapter only)
|
||||
@@ -164,7 +158,7 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
guest_ip_address = ""
|
||||
log.info("Waiting for GNS3 VM IP")
|
||||
while True:
|
||||
guest_ip_address = yield from self._execute("readVariable", [self._vmx_path, "guestVar", "gns3.eth0"], timeout=120, log_level=logging.DEBUG)
|
||||
guest_ip_address = await self._execute("readVariable", [self._vmx_path, "guestVar", "gns3.eth0"], timeout=120, log_level=logging.DEBUG)
|
||||
guest_ip_address = guest_ip_address.strip()
|
||||
if len(guest_ip_address) != 0:
|
||||
break
|
||||
@@ -172,15 +166,14 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
# If ip not found fallback on old method
|
||||
if trial == 0:
|
||||
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)
|
||||
guest_ip_address = await self._execute("getGuestIPAddress", [self._vmx_path, "-wait"], timeout=120)
|
||||
break
|
||||
yield from asyncio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
self.ip_address = guest_ip_address
|
||||
log.info("GNS3 VM IP address set to {}".format(guest_ip_address))
|
||||
self.running = True
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspend the GNS3 VM.
|
||||
"""
|
||||
@@ -188,14 +181,13 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
if self._vmx_path is None:
|
||||
raise GNS3VMError("No VMX path configured, can't suspend the VM")
|
||||
try:
|
||||
yield from self._execute("suspend", [self._vmx_path])
|
||||
await self._execute("suspend", [self._vmx_path])
|
||||
except GNS3VMError as e:
|
||||
log.warning("Error when suspending the VM: {}".format(str(e)))
|
||||
log.info("GNS3 VM has been suspended")
|
||||
self.running = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stops the GNS3 VM.
|
||||
"""
|
||||
@@ -203,7 +195,7 @@ class VMwareGNS3VM(BaseGNS3VM):
|
||||
if self._vmx_path is None:
|
||||
raise GNS3VMError("No VMX path configured, can't stop the VM")
|
||||
try:
|
||||
yield from self._execute("stop", [self._vmx_path, "soft"])
|
||||
await self._execute("stop", [self._vmx_path, "soft"])
|
||||
except GNS3VMError as e:
|
||||
log.warning("Error when stopping the VM: {}".format(str(e)))
|
||||
log.info("GNS3 VM has been stopped")
|
||||
|
||||
@@ -33,8 +33,7 @@ Handle the import of project from a .gns3project
|
||||
"""
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def import_project(controller, project_id, stream, location=None, name=None, keep_compute_id=False):
|
||||
async def import_project(controller, project_id, stream, location=None, name=None, keep_compute_id=False):
|
||||
"""
|
||||
Import a project contain in a zip file
|
||||
|
||||
@@ -87,7 +86,7 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(stream) as zip_file:
|
||||
yield from wait_run_in_executor(zip_file.extractall, path)
|
||||
await wait_run_in_executor(zip_file.extractall, path)
|
||||
except zipfile.BadZipFile:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot extract files from GNS3 project (invalid zip)")
|
||||
|
||||
@@ -138,9 +137,9 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
|
||||
# Project created on the remote GNS3 VM?
|
||||
if node["compute_id"] not in compute_created:
|
||||
compute = controller.get_compute(node["compute_id"])
|
||||
yield from compute.post("/projects", data={"name": project_name, "project_id": project_id,})
|
||||
await compute.post("/projects", data={"name": project_name, "project_id": project_id,})
|
||||
compute_created.add(node["compute_id"])
|
||||
yield from _move_files_to_compute(compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"]))
|
||||
await _move_files_to_compute(compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"]))
|
||||
|
||||
# And we dump the updated.gns3
|
||||
dot_gns3_path = os.path.join(path, project_name + ".gns3")
|
||||
@@ -153,7 +152,7 @@ def import_project(controller, project_id, stream, location=None, name=None, kee
|
||||
if os.path.exists(os.path.join(path, "images")):
|
||||
_import_images(controller, path)
|
||||
|
||||
project = yield from controller.load_project(dot_gns3_path, load=False)
|
||||
project = await controller.load_project(dot_gns3_path, load=False)
|
||||
return project
|
||||
|
||||
|
||||
@@ -176,8 +175,7 @@ def _move_node_file(path, old_id, new_id):
|
||||
shutil.move(node_dir, os.path.join(module_dir, new_id))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _move_files_to_compute(compute, project_id, directory, files_path):
|
||||
async def _move_files_to_compute(compute, project_id, directory, files_path):
|
||||
"""
|
||||
Move files to a remote compute
|
||||
"""
|
||||
@@ -188,12 +186,11 @@ def _move_files_to_compute(compute, project_id, directory, files_path):
|
||||
for filename in filenames:
|
||||
path = os.path.join(dirpath, filename)
|
||||
dst = os.path.relpath(path, directory)
|
||||
yield from _upload_file(compute, project_id, path, dst)
|
||||
yield from wait_run_in_executor(shutil.rmtree, os.path.join(directory, files_path))
|
||||
await _upload_file(compute, project_id, path, dst)
|
||||
await wait_run_in_executor(shutil.rmtree, os.path.join(directory, files_path))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _upload_file(compute, project_id, file_path, path):
|
||||
async def _upload_file(compute, project_id, file_path, path):
|
||||
"""
|
||||
Upload a file to a remote project
|
||||
|
||||
@@ -203,7 +200,7 @@ def _upload_file(compute, project_id, file_path, path):
|
||||
|
||||
path = "/projects/{}/files/{}".format(project_id, path.replace("\\", "/"))
|
||||
with open(file_path, "rb") as f:
|
||||
yield from compute.http_query("POST", path, f, timeout=None)
|
||||
await compute.http_query("POST", path, f, timeout=None)
|
||||
|
||||
|
||||
def _import_images(controller, path):
|
||||
|
||||
@@ -22,8 +22,6 @@ import html
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from gns3server.utils.asyncio import asyncio_ensure_future
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -151,8 +149,7 @@ class Link:
|
||||
return {"frequency_drop": [-1]}
|
||||
return self._filters
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_filters(self, filters):
|
||||
async def update_filters(self, filters):
|
||||
"""
|
||||
Modify the filters list.
|
||||
|
||||
@@ -173,15 +170,14 @@ class Link:
|
||||
if new_filters != self.filters:
|
||||
self._filters = new_filters
|
||||
if self._created:
|
||||
yield from self.update()
|
||||
await self.update()
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
self._project.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_suspend(self, value):
|
||||
async def update_suspend(self, value):
|
||||
if value != self._suspended:
|
||||
self._suspended = value
|
||||
yield from self.update()
|
||||
await self.update()
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
self._project.dump()
|
||||
|
||||
@@ -192,8 +188,7 @@ class Link:
|
||||
"""
|
||||
return self._created
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_node(self, node, adapter_number, port_number, label=None, dump=True):
|
||||
async def add_node(self, node, adapter_number, port_number, label=None, dump=True):
|
||||
"""
|
||||
Add a node to the link
|
||||
|
||||
@@ -241,7 +236,7 @@ class Link:
|
||||
})
|
||||
|
||||
if len(self._nodes) == 2:
|
||||
yield from self.create()
|
||||
await self.create()
|
||||
for n in self._nodes:
|
||||
n["node"].add_link(self)
|
||||
n["port"].link = self
|
||||
@@ -251,8 +246,7 @@ class Link:
|
||||
if dump:
|
||||
self._project.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_nodes(self, nodes):
|
||||
async def update_nodes(self, nodes):
|
||||
for node_data in nodes:
|
||||
node = self._project.get_node(node_data["node_id"])
|
||||
for port in self._nodes:
|
||||
@@ -263,23 +257,20 @@ class Link:
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
self._project.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
async def create(self):
|
||||
"""
|
||||
Create the link
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self):
|
||||
async def update(self):
|
||||
"""
|
||||
Update a link
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""
|
||||
Delete the link
|
||||
"""
|
||||
@@ -289,8 +280,7 @@ class Link:
|
||||
n["port"].link = None
|
||||
n["node"].remove_link(self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_capture(self, data_link_type="DLT_EN10MB", capture_file_name=None):
|
||||
async def start_capture(self, data_link_type="DLT_EN10MB", capture_file_name=None):
|
||||
"""
|
||||
Start capture on the link
|
||||
|
||||
@@ -299,11 +289,10 @@ class Link:
|
||||
|
||||
self._capturing = True
|
||||
self._capture_file_name = capture_file_name
|
||||
self._streaming_pcap = asyncio_ensure_future(self._start_streaming_pcap())
|
||||
self._streaming_pcap = asyncio.ensure_future(self._start_streaming_pcap())
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
@asyncio.coroutine
|
||||
def _start_streaming_pcap(self):
|
||||
async def _start_streaming_pcap(self):
|
||||
"""
|
||||
Dump a pcap file on disk
|
||||
"""
|
||||
@@ -315,7 +304,7 @@ class Link:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not delete old capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
try:
|
||||
stream_content = yield from self.read_pcap_from_source()
|
||||
stream_content = await self.read_pcap_from_source()
|
||||
except aiohttp.web.HTTPException as e:
|
||||
error_msg = "Could not stream PCAP file: error {}: {}".format(e.status, e.text)
|
||||
log.error(error_msg)
|
||||
@@ -328,7 +317,7 @@ class Link:
|
||||
with open(self.capture_file_path, "wb") as f:
|
||||
while self._capturing:
|
||||
# We read 1 bytes by 1 otherwise the remaining data is not read if the traffic stops
|
||||
data = yield from stream.read(1)
|
||||
data = await stream.read(1)
|
||||
if data:
|
||||
f.write(data)
|
||||
# Flush to disk otherwise the live is not really live
|
||||
@@ -338,8 +327,7 @@ class Link:
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write capture file '{}': {}".format(self.capture_file_path, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop_capture(self):
|
||||
async def stop_capture(self):
|
||||
"""
|
||||
Stop capture on the link
|
||||
"""
|
||||
@@ -347,16 +335,14 @@ class Link:
|
||||
self._capturing = False
|
||||
self._project.controller.notification.project_emit("link.updated", self.__json__())
|
||||
|
||||
@asyncio.coroutine
|
||||
def _read_pcap_from_source(self):
|
||||
async def _read_pcap_from_source(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute server
|
||||
"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
@asyncio.coroutine
|
||||
def node_updated(self, node):
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
Called when a node member of the link is updated
|
||||
"""
|
||||
|
||||
@@ -343,8 +343,7 @@ class Node:
|
||||
def links(self):
|
||||
return self._links
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
async def create(self):
|
||||
"""
|
||||
Create the node on the compute server
|
||||
"""
|
||||
@@ -358,21 +357,20 @@ class Node:
|
||||
trial = 0
|
||||
while trial != 6:
|
||||
try:
|
||||
response = yield from self._compute.post("/projects/{}/{}/nodes".format(self._project.id, self._node_type), data=data, timeout=timeout)
|
||||
response = await self._compute.post("/projects/{}/{}/nodes".format(self._project.id, self._node_type), data=data, timeout=timeout)
|
||||
except ComputeConflict as e:
|
||||
if e.response.get("exception") == "ImageMissingError":
|
||||
res = yield from self._upload_missing_image(self._node_type, e.response["image"])
|
||||
res = await self._upload_missing_image(self._node_type, e.response["image"])
|
||||
if not res:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
yield from self.parse_node_response(response.json)
|
||||
await self.parse_node_response(response.json)
|
||||
return True
|
||||
trial += 1
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self, **kwargs):
|
||||
async def update(self, **kwargs):
|
||||
"""
|
||||
Update the node on the compute server
|
||||
|
||||
@@ -403,15 +401,14 @@ class Node:
|
||||
self._list_ports()
|
||||
if update_compute:
|
||||
data = self._node_data(properties=compute_properties)
|
||||
response = yield from self.put(None, data=data)
|
||||
yield from self.parse_node_response(response.json)
|
||||
response = await self.put(None, data=data)
|
||||
await self.parse_node_response(response.json)
|
||||
elif old_json != self.__json__():
|
||||
# We send notif only if object has changed
|
||||
self.project.controller.notification.project_emit("node.updated", self.__json__())
|
||||
self.project.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def parse_node_response(self, response):
|
||||
async def parse_node_response(self, response):
|
||||
"""
|
||||
Update the object with the remote node object
|
||||
"""
|
||||
@@ -439,7 +436,7 @@ class Node:
|
||||
self._properties[key] = value
|
||||
self._list_ports()
|
||||
for link in self._links:
|
||||
yield from link.node_updated(self)
|
||||
await link.node_updated(self)
|
||||
|
||||
def _node_data(self, properties=None):
|
||||
"""
|
||||
@@ -477,12 +474,10 @@ class Node:
|
||||
del data[key]
|
||||
return data
|
||||
|
||||
@asyncio.coroutine
|
||||
def destroy(self):
|
||||
yield from self.delete()
|
||||
async def destroy(self):
|
||||
await self.delete()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, data=None):
|
||||
async def start(self, data=None):
|
||||
"""
|
||||
Start a node
|
||||
"""
|
||||
@@ -493,57 +488,52 @@ class Node:
|
||||
licence = self._project.controller.settings["IOU"]["iourc_content"]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPConflict(text="IOU licence is not configured")
|
||||
yield from self.post("/start", timeout=240, data={"iourc_content": licence})
|
||||
await self.post("/start", timeout=240, data={"iourc_content": licence})
|
||||
else:
|
||||
yield from self.post("/start", data=data, timeout=240)
|
||||
await self.post("/start", data=data, timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when starting {}".format(self._name))
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop(self):
|
||||
async def stop(self):
|
||||
"""
|
||||
Stop a node
|
||||
"""
|
||||
try:
|
||||
yield from self.post("/stop", timeout=240, dont_connect=True)
|
||||
await self.post("/stop", timeout=240, dont_connect=True)
|
||||
# We don't care if a node is down at this step
|
||||
except (ComputeError, aiohttp.ClientError, aiohttp.web.HTTPError):
|
||||
pass
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when stopping {}".format(self._name))
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend(self):
|
||||
async def suspend(self):
|
||||
"""
|
||||
Suspend a node
|
||||
"""
|
||||
try:
|
||||
yield from self.post("/suspend", timeout=240)
|
||||
await self.post("/suspend", timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when reloading {}".format(self._name))
|
||||
|
||||
@asyncio.coroutine
|
||||
def reload(self):
|
||||
async def reload(self):
|
||||
"""
|
||||
Suspend a node
|
||||
"""
|
||||
try:
|
||||
yield from self.post("/reload", timeout=240)
|
||||
await self.post("/reload", timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when reloading {}".format(self._name))
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, path, data=None, **kwargs):
|
||||
async def post(self, path, data=None, **kwargs):
|
||||
"""
|
||||
HTTP post on the node
|
||||
"""
|
||||
if data:
|
||||
return (yield from self._compute.post("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), data=data, **kwargs))
|
||||
return (await self._compute.post("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), data=data, **kwargs))
|
||||
else:
|
||||
return (yield from self._compute.post("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs))
|
||||
return (await self._compute.post("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def put(self, path, data=None, **kwargs):
|
||||
async def put(self, path, data=None, **kwargs):
|
||||
"""
|
||||
HTTP post on the node
|
||||
"""
|
||||
@@ -552,22 +542,20 @@ class Node:
|
||||
else:
|
||||
path = "/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path)
|
||||
if data:
|
||||
return (yield from self._compute.put(path, data=data, **kwargs))
|
||||
return (await self._compute.put(path, data=data, **kwargs))
|
||||
else:
|
||||
return (yield from self._compute.put(path, **kwargs))
|
||||
return (await self._compute.put(path, **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self, path=None, **kwargs):
|
||||
async def delete(self, path=None, **kwargs):
|
||||
"""
|
||||
HTTP post on the node
|
||||
"""
|
||||
if path is None:
|
||||
return (yield from self._compute.delete("/projects/{}/{}/nodes/{}".format(self._project.id, self._node_type, self._id), **kwargs))
|
||||
return (await self._compute.delete("/projects/{}/{}/nodes/{}".format(self._project.id, self._node_type, self._id), **kwargs))
|
||||
else:
|
||||
return (yield from self._compute.delete("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs))
|
||||
return (await self._compute.delete("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _upload_missing_image(self, type, img):
|
||||
async def _upload_missing_image(self, type, img):
|
||||
"""
|
||||
Search an image on local computer and upload it to remote compute
|
||||
if the image exists
|
||||
@@ -578,26 +566,24 @@ class Node:
|
||||
self.project.controller.notification.project_emit("log.info", {"message": "Uploading missing image {}".format(img)})
|
||||
try:
|
||||
with open(image, 'rb') as f:
|
||||
yield from self._compute.post("/{}/images/{}".format(self._node_type, os.path.basename(img)), data=f, timeout=None)
|
||||
await self._compute.post("/{}/images/{}".format(self._node_type, os.path.basename(img)), data=f, timeout=None)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't upload {}: {}".format(image, str(e)))
|
||||
self.project.controller.notification.project_emit("log.info", {"message": "Upload finished for {}".format(img)})
|
||||
return True
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def dynamips_auto_idlepc(self):
|
||||
async def dynamips_auto_idlepc(self):
|
||||
"""
|
||||
Compute the idle PC for a dynamips node
|
||||
"""
|
||||
return (yield from self._compute.get("/projects/{}/{}/nodes/{}/auto_idlepc".format(self._project.id, self._node_type, self._id), timeout=240)).json
|
||||
return (await self._compute.get("/projects/{}/{}/nodes/{}/auto_idlepc".format(self._project.id, self._node_type, self._id), timeout=240)).json
|
||||
|
||||
@asyncio.coroutine
|
||||
def dynamips_idlepc_proposals(self):
|
||||
async def dynamips_idlepc_proposals(self):
|
||||
"""
|
||||
Compute a list of potential idle PC
|
||||
"""
|
||||
return (yield from self._compute.get("/projects/{}/{}/nodes/{}/idlepc_proposals".format(self._project.id, self._node_type, self._id), timeout=240)).json
|
||||
return (await self._compute.get("/projects/{}/{}/nodes/{}/idlepc_proposals".format(self._project.id, self._node_type, self._id), timeout=240)).json
|
||||
|
||||
def get_port(self, adapter_number, port_number):
|
||||
"""
|
||||
|
||||
@@ -88,8 +88,7 @@ class Notification:
|
||||
"""
|
||||
return project.id in self._project_listeners and len(self._project_listeners[project.id]) > 0
|
||||
|
||||
@asyncio.coroutine
|
||||
def dispatch(self, action, event, compute_id):
|
||||
async def dispatch(self, action, event, compute_id):
|
||||
"""
|
||||
Notification received from compute node. Send it directly
|
||||
to clients or process it
|
||||
@@ -103,7 +102,7 @@ class Notification:
|
||||
# Update controller node data and send the event node.updated
|
||||
project = self._controller.get_project(event["project_id"])
|
||||
node = project.get_node(event["node_id"])
|
||||
yield from node.parse_node_response(event)
|
||||
await node.parse_node_response(event)
|
||||
|
||||
self.project_emit("node.updated", node.__json__())
|
||||
except (aiohttp.web.HTTPNotFound, aiohttp.web.HTTPForbidden): # Project closing
|
||||
|
||||
@@ -38,7 +38,6 @@ from ..utils.path import check_path_allowed, get_default_project_directory
|
||||
from ..utils.asyncio.pool import Pool
|
||||
from ..utils.asyncio import locking
|
||||
from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.asyncio import asyncio_ensure_future
|
||||
from .export_project import export_project
|
||||
from .import_project import import_project
|
||||
|
||||
@@ -122,8 +121,7 @@ class Project:
|
||||
assert self._status != "closed"
|
||||
self.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self, **kwargs):
|
||||
async def update(self, **kwargs):
|
||||
"""
|
||||
Update the project
|
||||
:param kwargs: Project properties
|
||||
@@ -141,7 +139,7 @@ class Project:
|
||||
|
||||
# update on computes
|
||||
for compute in list(self._project_created_on_compute):
|
||||
yield from compute.put(
|
||||
await compute.put(
|
||||
"/projects/{}".format(self._id), {
|
||||
"variables": self.variables
|
||||
}
|
||||
@@ -462,8 +460,7 @@ class Project:
|
||||
return new_name
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_node_from_appliance(self, appliance_id, x=0, y=0, compute_id=None):
|
||||
async def add_node_from_appliance(self, appliance_id, x=0, y=0, compute_id=None):
|
||||
"""
|
||||
Create a node from an appliance
|
||||
"""
|
||||
@@ -481,12 +478,11 @@ class Project:
|
||||
default_name_format = template.pop("default_name_format", "{name}-{0}")
|
||||
name = default_name_format.replace("{name}", name)
|
||||
node_id = str(uuid.uuid4())
|
||||
node = yield from self.add_node(compute, name, node_id, node_type=node_type, appliance_id=appliance_id, **template)
|
||||
node = await self.add_node(compute, name, node_id, node_type=node_type, appliance_id=appliance_id, **template)
|
||||
return node
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_node(self, compute, name, node_id, dump=True, node_type=None, **kwargs):
|
||||
async def add_node(self, compute, name, node_id, dump=True, node_type=None, **kwargs):
|
||||
"""
|
||||
Create a node or return an existing node
|
||||
|
||||
@@ -515,10 +511,10 @@ class Project:
|
||||
if self._variables:
|
||||
data["variables"] = self._variables
|
||||
|
||||
yield from compute.post("/projects", data=data)
|
||||
await compute.post("/projects", data=data)
|
||||
|
||||
self._project_created_on_compute.add(compute)
|
||||
yield from node.create()
|
||||
await node.create()
|
||||
self._nodes[node.id] = node
|
||||
self.controller.notification.project_emit("node.created", node.__json__())
|
||||
if dump:
|
||||
@@ -526,8 +522,7 @@ class Project:
|
||||
return node
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def __delete_node_links(self, node):
|
||||
async def __delete_node_links(self, node):
|
||||
"""
|
||||
Delete all link connected to this node.
|
||||
|
||||
@@ -536,16 +531,15 @@ class Project:
|
||||
"""
|
||||
for link in list(self._links.values()):
|
||||
if node in link.nodes:
|
||||
yield from self.delete_link(link.id, force_delete=True)
|
||||
await self.delete_link(link.id, force_delete=True)
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def delete_node(self, node_id):
|
||||
async def delete_node(self, node_id):
|
||||
node = self.get_node(node_id)
|
||||
yield from self.__delete_node_links(node)
|
||||
await self.__delete_node_links(node)
|
||||
self.remove_allocated_node_name(node.name)
|
||||
del self._nodes[node.id]
|
||||
yield from node.destroy()
|
||||
await node.destroy()
|
||||
self.dump()
|
||||
self.controller.notification.project_emit("node.deleted", node.__json__())
|
||||
|
||||
@@ -602,8 +596,7 @@ class Project:
|
||||
return self._drawings
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_drawing(self, drawing_id=None, dump=True, **kwargs):
|
||||
async def add_drawing(self, drawing_id=None, dump=True, **kwargs):
|
||||
"""
|
||||
Create an drawing or return an existing drawing
|
||||
|
||||
@@ -630,16 +623,14 @@ class Project:
|
||||
raise aiohttp.web.HTTPNotFound(text="Drawing ID {} doesn't exist".format(drawing_id))
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def delete_drawing(self, drawing_id):
|
||||
async def delete_drawing(self, drawing_id):
|
||||
drawing = self.get_drawing(drawing_id)
|
||||
del self._drawings[drawing.id]
|
||||
self.dump()
|
||||
self.controller.notification.project_emit("drawing.deleted", drawing.__json__())
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_link(self, link_id=None, dump=True):
|
||||
async def add_link(self, link_id=None, dump=True):
|
||||
"""
|
||||
Create a link. By default the link is empty
|
||||
|
||||
@@ -654,12 +645,11 @@ class Project:
|
||||
return link
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def delete_link(self, link_id, force_delete=False):
|
||||
async def delete_link(self, link_id, force_delete=False):
|
||||
link = self.get_link(link_id)
|
||||
del self._links[link.id]
|
||||
try:
|
||||
yield from link.delete()
|
||||
await link.delete()
|
||||
except Exception:
|
||||
if force_delete is False:
|
||||
raise
|
||||
@@ -703,8 +693,7 @@ class Project:
|
||||
raise aiohttp.web.HTTPNotFound(text="Snapshot ID {} doesn't exist".format(snapshot_id))
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def snapshot(self, name):
|
||||
async def snapshot(self, name):
|
||||
"""
|
||||
Snapshot the project
|
||||
|
||||
@@ -714,25 +703,23 @@ class Project:
|
||||
if name in [snap.name for snap in self._snapshots.values()]:
|
||||
raise aiohttp.web.HTTPConflict(text="The snapshot name {} already exists".format(name))
|
||||
snapshot = Snapshot(self, name=name)
|
||||
yield from snapshot.create()
|
||||
await snapshot.create()
|
||||
self._snapshots[snapshot.id] = snapshot
|
||||
return snapshot
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def delete_snapshot(self, snapshot_id):
|
||||
async def delete_snapshot(self, snapshot_id):
|
||||
snapshot = self.get_snapshot(snapshot_id)
|
||||
del self._snapshots[snapshot.id]
|
||||
os.remove(snapshot.path)
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self, ignore_notification=False):
|
||||
async def close(self, ignore_notification=False):
|
||||
if self._status == "closed":
|
||||
return
|
||||
yield from self.stop_all()
|
||||
await self.stop_all()
|
||||
for compute in list(self._project_created_on_compute):
|
||||
try:
|
||||
yield from compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
||||
await compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPError, aiohttp.ClientResponseError, TimeoutError):
|
||||
pass
|
||||
@@ -771,30 +758,28 @@ class Project:
|
||||
except OSError as e:
|
||||
log.warning(str(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
|
||||
if self._status != "opened":
|
||||
try:
|
||||
yield from self.open()
|
||||
await self.open()
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
# ignore missing images or other conflicts when deleting a project
|
||||
log.warning("Conflict while deleting project: {}".format(e.text))
|
||||
yield from self.delete_on_computes()
|
||||
yield from self.close()
|
||||
await self.delete_on_computes()
|
||||
await self.close()
|
||||
try:
|
||||
shutil.rmtree(self.path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can not delete project directory {}: {}".format(self.path, str(e)))
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete_on_computes(self):
|
||||
async def delete_on_computes(self):
|
||||
"""
|
||||
Delete the project on computes but not on controller
|
||||
"""
|
||||
for compute in list(self._project_created_on_compute):
|
||||
if compute.id != "local":
|
||||
yield from compute.delete("/projects/{}".format(self._id))
|
||||
await compute.delete("/projects/{}".format(self._id))
|
||||
self._project_created_on_compute.remove(compute)
|
||||
|
||||
@classmethod
|
||||
@@ -817,8 +802,7 @@ class Project:
|
||||
return os.path.join(self.path, self._filename)
|
||||
|
||||
@locking
|
||||
@asyncio.coroutine
|
||||
def open(self):
|
||||
async def open(self):
|
||||
"""
|
||||
Load topology elements
|
||||
"""
|
||||
@@ -862,19 +846,19 @@ class Project:
|
||||
|
||||
topology = project_data["topology"]
|
||||
for compute in topology.get("computes", []):
|
||||
yield from self.controller.add_compute(**compute)
|
||||
await self.controller.add_compute(**compute)
|
||||
for node in topology.get("nodes", []):
|
||||
compute = self.controller.get_compute(node.pop("compute_id"))
|
||||
name = node.pop("name")
|
||||
node_id = node.pop("node_id", str(uuid.uuid4()))
|
||||
yield from self.add_node(compute, name, node_id, dump=False, **node)
|
||||
await self.add_node(compute, name, node_id, dump=False, **node)
|
||||
for link_data in topology.get("links", []):
|
||||
if 'link_id' not in link_data.keys():
|
||||
# skip the link
|
||||
continue
|
||||
link = yield from self.add_link(link_id=link_data["link_id"])
|
||||
link = await self.add_link(link_id=link_data["link_id"])
|
||||
if "filters" in link_data:
|
||||
yield from link.update_filters(link_data["filters"])
|
||||
await link.update_filters(link_data["filters"])
|
||||
for node_link in link_data.get("nodes", []):
|
||||
node = self.get_node(node_link["node_id"])
|
||||
port = node.get_port(node_link["adapter_number"], node_link["port_number"])
|
||||
@@ -884,19 +868,19 @@ class Project:
|
||||
if port.link is not None:
|
||||
log.warning("Port {}/{} is already connected to link ID {}".format(node_link["adapter_number"], node_link["port_number"], port.link.id))
|
||||
continue
|
||||
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False)
|
||||
await link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False)
|
||||
if len(link.nodes) != 2:
|
||||
# a link should have 2 attached nodes, this can happen with corrupted projects
|
||||
yield from self.delete_link(link.id, force_delete=True)
|
||||
await self.delete_link(link.id, force_delete=True)
|
||||
for drawing_data in topology.get("drawings", []):
|
||||
yield from self.add_drawing(dump=False, **drawing_data)
|
||||
await 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 list(self._project_created_on_compute):
|
||||
try:
|
||||
yield from compute.post("/projects/{}/close".format(self._id))
|
||||
await compute.post("/projects/{}/close".format(self._id))
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPNotFound, aiohttp.web.HTTPConflict, aiohttp.ServerDisconnectedError):
|
||||
pass
|
||||
@@ -922,15 +906,14 @@ class Project:
|
||||
# Start all in the background without waiting for completion
|
||||
# we ignore errors because we want to let the user open
|
||||
# their project and fix it
|
||||
asyncio_ensure_future(self.start_all())
|
||||
asyncio.ensure_future(self.start_all())
|
||||
|
||||
@asyncio.coroutine
|
||||
def wait_loaded(self):
|
||||
async def wait_loaded(self):
|
||||
"""
|
||||
Wait until the project finish loading
|
||||
"""
|
||||
while self._loading:
|
||||
yield from asyncio.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
def _create_duplicate_project_file(self, path, zipstream):
|
||||
"""
|
||||
@@ -941,8 +924,7 @@ class Project:
|
||||
for data in zipstream:
|
||||
f.write(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def duplicate(self, name=None, location=None):
|
||||
async def duplicate(self, name=None, location=None):
|
||||
"""
|
||||
Duplicate a project
|
||||
|
||||
@@ -956,22 +938,22 @@ class Project:
|
||||
# If the project was not open we open it temporary
|
||||
previous_status = self._status
|
||||
if self._status == "closed":
|
||||
yield from self.open()
|
||||
await self.open()
|
||||
|
||||
self.dump()
|
||||
assert self._status != "closed"
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
zipstream = yield from export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True)
|
||||
zipstream = await export_project(self, tmpdir, keep_compute_id=True, allow_all_nodes=True)
|
||||
project_path = os.path.join(tmpdir, "project.gns3p")
|
||||
yield from wait_run_in_executor(self._create_duplicate_project_file, project_path, zipstream)
|
||||
await wait_run_in_executor(self._create_duplicate_project_file, project_path, zipstream)
|
||||
with open(project_path, "rb") as f:
|
||||
project = yield from import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
|
||||
project = await import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True)
|
||||
except (ValueError, OSError, UnicodeEncodeError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot duplicate project: {}".format(str(e)))
|
||||
|
||||
if previous_status == "closed":
|
||||
yield from self.close()
|
||||
await self.close()
|
||||
|
||||
return project
|
||||
|
||||
@@ -999,38 +981,34 @@ class Project:
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not write topology: {}".format(e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_all(self):
|
||||
async def start_all(self):
|
||||
"""
|
||||
Start all nodes
|
||||
"""
|
||||
pool = Pool(concurrency=3)
|
||||
for node in self.nodes.values():
|
||||
pool.append(node.start)
|
||||
yield from pool.join()
|
||||
await pool.join()
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop_all(self):
|
||||
async def stop_all(self):
|
||||
"""
|
||||
Stop all nodes
|
||||
"""
|
||||
pool = Pool(concurrency=3)
|
||||
for node in self.nodes.values():
|
||||
pool.append(node.stop)
|
||||
yield from pool.join()
|
||||
await pool.join()
|
||||
|
||||
@asyncio.coroutine
|
||||
def suspend_all(self):
|
||||
async def suspend_all(self):
|
||||
"""
|
||||
Suspend all nodes
|
||||
"""
|
||||
pool = Pool(concurrency=3)
|
||||
for node in self.nodes.values():
|
||||
pool.append(node.suspend)
|
||||
yield from pool.join()
|
||||
await pool.join()
|
||||
|
||||
@asyncio.coroutine
|
||||
def duplicate_node(self, node, x, y, z):
|
||||
async def duplicate_node(self, node, x, y, z):
|
||||
"""
|
||||
Duplicate a node
|
||||
|
||||
@@ -1061,21 +1039,21 @@ class Project:
|
||||
data['y'] = y
|
||||
data['z'] = z
|
||||
new_node_uuid = str(uuid.uuid4())
|
||||
new_node = yield from self.add_node(
|
||||
new_node = await self.add_node(
|
||||
node.compute,
|
||||
node.name,
|
||||
new_node_uuid,
|
||||
node_type=node_type,
|
||||
**data)
|
||||
try:
|
||||
yield from node.post("/duplicate", timeout=None, data={
|
||||
await node.post("/duplicate", timeout=None, data={
|
||||
"destination_node_id": new_node_uuid
|
||||
})
|
||||
except aiohttp.web.HTTPNotFound as e:
|
||||
yield from self.delete_node(new_node_uuid)
|
||||
await self.delete_node(new_node_uuid)
|
||||
raise aiohttp.web.HTTPConflict(text="This node type cannot be duplicated")
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
yield from self.delete_node(new_node_uuid)
|
||||
await self.delete_node(new_node_uuid)
|
||||
raise e
|
||||
return new_node
|
||||
|
||||
|
||||
@@ -82,8 +82,7 @@ class Snapshot:
|
||||
for data in zipstream:
|
||||
f.write(data)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
async def create(self):
|
||||
"""
|
||||
Create the snapshot
|
||||
"""
|
||||
@@ -99,31 +98,30 @@ class Snapshot:
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
zipstream = yield from export_project(self._project, tmpdir, keep_compute_id=True, allow_all_nodes=True)
|
||||
yield from wait_run_in_executor(self._create_snapshot_file, zipstream)
|
||||
zipstream = await export_project(self._project, tmpdir, keep_compute_id=True, allow_all_nodes=True)
|
||||
await wait_run_in_executor(self._create_snapshot_file, zipstream)
|
||||
except (ValueError, OSError, RuntimeError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not create snapshot file '{}': {}".format(self.path, e))
|
||||
|
||||
@asyncio.coroutine
|
||||
def restore(self):
|
||||
async def restore(self):
|
||||
"""
|
||||
Restore the snapshot
|
||||
"""
|
||||
|
||||
yield from self._project.delete_on_computes()
|
||||
await self._project.delete_on_computes()
|
||||
# We don't send close notification to clients because the close / open dance is purely internal
|
||||
yield from self._project.close(ignore_notification=True)
|
||||
await self._project.close(ignore_notification=True)
|
||||
|
||||
try:
|
||||
# delete the current project files
|
||||
project_files_path = os.path.join(self._project.path, "project-files")
|
||||
if os.path.exists(project_files_path):
|
||||
yield from wait_run_in_executor(shutil.rmtree, project_files_path)
|
||||
await wait_run_in_executor(shutil.rmtree, project_files_path)
|
||||
with open(self._path, "rb") as f:
|
||||
project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
||||
project = await import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
||||
except (OSError, PermissionError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
yield from project.open()
|
||||
await project.open()
|
||||
self._project.controller.notification.project_emit("snapshot.restored", self.__json__())
|
||||
return self._project
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ class UDPLink(Link):
|
||||
"""
|
||||
return self._link_data
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
async def create(self):
|
||||
"""
|
||||
Create the link on the nodes
|
||||
"""
|
||||
@@ -52,14 +51,14 @@ class UDPLink(Link):
|
||||
|
||||
# Get an IP allowing communication between both host
|
||||
try:
|
||||
(node1_host, node2_host) = yield from node1.compute.get_ip_on_same_subnet(node2.compute)
|
||||
(node1_host, node2_host) = await node1.compute.get_ip_on_same_subnet(node2.compute)
|
||||
except ValueError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot get an IP address on same subnet: {}".format(e))
|
||||
|
||||
# Reserve a UDP port on both side
|
||||
response = yield from node1.compute.post("/projects/{}/ports/udp".format(self._project.id))
|
||||
response = await node1.compute.post("/projects/{}/ports/udp".format(self._project.id))
|
||||
self._node1_port = response.json["udp_port"]
|
||||
response = yield from node2.compute.post("/projects/{}/ports/udp".format(self._project.id))
|
||||
response = await node2.compute.post("/projects/{}/ports/udp".format(self._project.id))
|
||||
self._node2_port = response.json["udp_port"]
|
||||
|
||||
node1_filters = {}
|
||||
@@ -79,7 +78,7 @@ class UDPLink(Link):
|
||||
"filters": node1_filters,
|
||||
"suspend": self._suspended
|
||||
})
|
||||
yield from node1.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
await node1.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
|
||||
self._link_data.append({
|
||||
"lport": self._node2_port,
|
||||
@@ -90,15 +89,14 @@ class UDPLink(Link):
|
||||
"suspend": self._suspended
|
||||
})
|
||||
try:
|
||||
yield from node2.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=120)
|
||||
await node2.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=120)
|
||||
except Exception as e:
|
||||
# We clean the first NIO
|
||||
yield from node1.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), timeout=120)
|
||||
await node1.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), timeout=120)
|
||||
raise e
|
||||
self._created = True
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self):
|
||||
async def update(self):
|
||||
"""
|
||||
Update the link on the nodes
|
||||
"""
|
||||
@@ -120,16 +118,15 @@ class UDPLink(Link):
|
||||
port_number1 = self._nodes[0]["port_number"]
|
||||
self._link_data[0]["filters"] = node1_filters
|
||||
self._link_data[0]["suspend"] = self._suspended
|
||||
yield from node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
await node1.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=self._link_data[0], timeout=120)
|
||||
|
||||
adapter_number2 = self._nodes[1]["adapter_number"]
|
||||
port_number2 = self._nodes[1]["port_number"]
|
||||
self._link_data[1]["filters"] = node2_filters
|
||||
self._link_data[1]["suspend"] = self._suspended
|
||||
yield from node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221)
|
||||
await node2.put("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=self._link_data[1], timeout=221)
|
||||
|
||||
@asyncio.coroutine
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
"""
|
||||
Delete the link and free the resources
|
||||
"""
|
||||
@@ -142,7 +139,7 @@ class UDPLink(Link):
|
||||
except IndexError:
|
||||
return
|
||||
try:
|
||||
yield from node1.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), timeout=120)
|
||||
await node1.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), timeout=120)
|
||||
# If the node is already delete (user selected multiple element and delete all in the same time)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
pass
|
||||
@@ -154,14 +151,13 @@ class UDPLink(Link):
|
||||
except IndexError:
|
||||
return
|
||||
try:
|
||||
yield from node2.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), timeout=120)
|
||||
await node2.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), timeout=120)
|
||||
# If the node is already delete (user selected multiple element and delete all in the same time)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
pass
|
||||
yield from super().delete()
|
||||
await super().delete()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start_capture(self, data_link_type="DLT_EN10MB", capture_file_name=None):
|
||||
async def start_capture(self, data_link_type="DLT_EN10MB", capture_file_name=None):
|
||||
"""
|
||||
Start capture on a link
|
||||
"""
|
||||
@@ -172,18 +168,17 @@ class UDPLink(Link):
|
||||
"capture_file_name": capture_file_name,
|
||||
"data_link_type": data_link_type
|
||||
}
|
||||
yield from self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data)
|
||||
yield from super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name)
|
||||
await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data)
|
||||
await super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop_capture(self):
|
||||
async def stop_capture(self):
|
||||
"""
|
||||
Stop capture on a link
|
||||
"""
|
||||
if self._capture_node:
|
||||
yield from self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/stop_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]))
|
||||
await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/stop_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]))
|
||||
self._capture_node = None
|
||||
yield from super().stop_capture()
|
||||
await super().stop_capture()
|
||||
|
||||
def _choose_capture_side(self):
|
||||
"""
|
||||
@@ -215,8 +210,7 @@ class UDPLink(Link):
|
||||
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot capture because there is no running device on this link")
|
||||
|
||||
@asyncio.coroutine
|
||||
def read_pcap_from_source(self):
|
||||
async def read_pcap_from_source(self):
|
||||
"""
|
||||
Return a FileStream of the Pcap from the compute node
|
||||
"""
|
||||
@@ -224,10 +218,9 @@ class UDPLink(Link):
|
||||
compute = self._capture_node["node"].compute
|
||||
return compute.stream_file(self._project, "tmp/captures/" + self._capture_file_name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def node_updated(self, node):
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
Called when a node member of the link is updated
|
||||
"""
|
||||
if self._capture_node and node == self._capture_node["node"] and node.status != "started":
|
||||
yield from self.stop_capture()
|
||||
await self.stop_capture()
|
||||
Reference in new issue
Block a user