1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

All current iou code is async

This commit is contained in:
Julien Duponchelle 2015-02-16 17:20:07 +01:00
parent d323234520
commit 15f89776d3
8 changed files with 97 additions and 30 deletions

View File

@ -22,10 +22,10 @@ order to run an IOU instance.
import os import os
import sys import sys
import subprocess
import signal import signal
import re import re
import asyncio import asyncio
import subprocess
import shutil import shutil
import argparse import argparse
import threading import threading
@ -40,6 +40,7 @@ from ..nios.nio_udp import NIO_UDP
from ..nios.nio_tap import NIO_TAP from ..nios.nio_tap import NIO_TAP
from ..base_vm import BaseVM from ..base_vm import BaseVM
from .ioucon import start_ioucon from .ioucon import start_ioucon
import gns3server.utils.asyncio
import logging import logging
@ -90,6 +91,7 @@ class IOUVM(BaseVM):
self._console_host = console_host self._console_host = console_host
# IOU settings # IOU settings
self._iourc = None
self._ethernet_adapters = [] self._ethernet_adapters = []
self._serial_adapters = [] self._serial_adapters = []
self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces self.ethernet_adapters = 2 if ethernet_adapters is None else ethernet_adapters # one adapter = 4 interfaces
@ -327,20 +329,21 @@ class IOUVM(BaseVM):
def application_id(self): def application_id(self):
return self._manager.get_application_id(self.id) return self._manager.get_application_id(self.id)
# TODO: ASYNCIO @asyncio.coroutine
def _library_check(self): def _library_check(self):
""" """
Checks for missing shared library dependencies in the IOU image. Checks for missing shared library dependencies in the IOU image.
""" """
try: try:
output = subprocess.check_output(["ldd", self._path]) output = yield from gns3server.utils.asyncio.subprocess_check_output("ldd", self._path)
except (FileNotFoundError, subprocess.SubprocessError) as e: except (FileNotFoundError, subprocess.SubprocessError) as e:
log.warn("could not determine the shared library dependencies for {}: {}".format(self._path, e)) log.warn("Could not determine the shared library dependencies for {}: {}".format(self._path, e))
return return
print(output)
p = re.compile("([\.\w]+)\s=>\s+not found") p = re.compile("([\.\w]+)\s=>\s+not found")
missing_libs = p.findall(output.decode("utf-8")) missing_libs = p.findall(output)
if missing_libs: if missing_libs:
raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path, raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path,
", ".join(missing_libs))) ", ".join(missing_libs)))
@ -354,10 +357,9 @@ class IOUVM(BaseVM):
self._check_requirements() self._check_requirements()
if not self.is_running(): if not self.is_running():
self._rename_nvram_file() yield from self._library_check()
# TODO: ASYNC self._rename_nvram_file()
# self._library_check()
if self._iourc_path and not os.path.isfile(self._iourc_path): if self._iourc_path and not os.path.isfile(self._iourc_path):
raise IOUError("A valid iourc file is necessary to start IOU") raise IOUError("A valid iourc file is necessary to start IOU")
@ -371,7 +373,7 @@ class IOUVM(BaseVM):
env = os.environ.copy() env = os.environ.copy()
if self._iourc_path: if self._iourc_path:
env["IOURC"] = self._iourc_path env["IOURC"] = self._iourc_path
self._command = self._build_command() self._command = yield from self._build_command()
try: try:
log.info("Starting IOU: {}".format(self._command)) log.info("Starting IOU: {}".format(self._command))
self._iou_stdout_file = os.path.join(self.working_dir, "iou.log") self._iou_stdout_file = os.path.join(self.working_dir, "iou.log")
@ -394,7 +396,7 @@ class IOUVM(BaseVM):
# start console support # start console support
self._start_ioucon() self._start_ioucon()
# connections support # connections support
self._start_iouyap() yield from self._start_iouyap()
def _rename_nvram_file(self): def _rename_nvram_file(self):
""" """
@ -405,6 +407,7 @@ class IOUVM(BaseVM):
for file_path in glob.glob(os.path.join(self.working_dir, "nvram_*")): for file_path in glob.glob(os.path.join(self.working_dir, "nvram_*")):
shutil.move(file_path, destination) shutil.move(file_path, destination)
@asyncio.coroutine
def _start_iouyap(self): def _start_iouyap(self):
""" """
Starts iouyap (handles connections to and from this IOU device). Starts iouyap (handles connections to and from this IOU device).
@ -417,10 +420,10 @@ class IOUVM(BaseVM):
self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log") self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log")
log.info("logging to {}".format(self._iouyap_stdout_file)) log.info("logging to {}".format(self._iouyap_stdout_file))
with open(self._iouyap_stdout_file, "w") as fd: with open(self._iouyap_stdout_file, "w") as fd:
self._iouyap_process = subprocess.Popen(command, self._iouyap_process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd, stdout=fd,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
cwd=self.working_dir) cwd=self.working_dir)
log.info("iouyap started PID={}".format(self._iouyap_process.pid)) log.info("iouyap started PID={}".format(self._iouyap_process.pid))
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e:
@ -596,6 +599,7 @@ class IOUVM(BaseVM):
except OSError as e: except OSError as e:
raise IOUError("Could not create {}: {}".format(netmap_path, e)) raise IOUError("Could not create {}: {}".format(netmap_path, e))
@asyncio.coroutine
def _build_command(self): def _build_command(self):
""" """
Command to start the IOU process. Command to start the IOU process.
@ -639,7 +643,7 @@ class IOUVM(BaseVM):
if initial_config_file: if initial_config_file:
command.extend(["-c", initial_config_file]) command.extend(["-c", initial_config_file])
if self._l1_keepalives: if self._l1_keepalives:
self._enable_l1_keepalives(command) yield from self._enable_l1_keepalives(command)
command.extend([str(self.application_id)]) command.extend([str(self.application_id)])
return command return command
@ -819,6 +823,7 @@ class IOUVM(BaseVM):
else: else:
log.info("IOU {name} [id={id}]: has deactivated layer 1 keepalive messages".format(name=self._name, id=self._id)) log.info("IOU {name} [id={id}]: has deactivated layer 1 keepalive messages".format(name=self._name, id=self._id))
@asyncio.coroutine
def _enable_l1_keepalives(self, command): def _enable_l1_keepalives(self, command):
""" """
Enables L1 keepalive messages if supported. Enables L1 keepalive messages if supported.
@ -828,8 +833,8 @@ class IOUVM(BaseVM):
env = os.environ.copy() env = os.environ.copy()
env["IOURC"] = self._iourc env["IOURC"] = self._iourc
try: try:
output = subprocess.check_output([self._path, "-h"], stderr=subprocess.STDOUT, cwd=self._working_dir, env=env) output = yield from gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, env=env)
if re.search("-l\s+Enable Layer 1 keepalive messages", output.decode("utf-8")): if re.search("-l\s+Enable Layer 1 keepalive messages", output):
command.extend(["-l"]) command.extend(["-l"])
else: else:
raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path))) raise IOUError("layer 1 keepalive messages are not supported by {}".format(os.path.basename(self._path)))

View File

@ -32,6 +32,7 @@ from pkg_resources import parse_version
from .vpcs_error import VPCSError from .vpcs_error import VPCSError
from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.ethernet_adapter import EthernetAdapter
from ..base_vm import BaseVM from ..base_vm import BaseVM
from ...utils.asyncio import subprocess_check_output
import logging import logging
@ -195,7 +196,7 @@ class VPCSVM(BaseVM):
Checks if the VPCS executable version is >= 0.5b1. Checks if the VPCS executable version is >= 0.5b1.
""" """
try: try:
output = yield from self._get_vpcs_welcome() output = yield from subprocess_check_output(self.vpcs_path, "-v", cwd=self.working_dir)
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output) match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output)
if match: if match:
version = match.group(1) version = match.group(1)
@ -206,12 +207,6 @@ class VPCSVM(BaseVM):
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e:
raise VPCSError("Error while looking for the VPCS version: {}".format(e)) raise VPCSError("Error while looking for the VPCS version: {}".format(e))
@asyncio.coroutine
def _get_vpcs_welcome(self):
proc = yield from asyncio.create_subprocess_exec(self.vpcs_path, "-v", stdout=asyncio.subprocess.PIPE, cwd=self.working_dir)
out = yield from proc.stdout.read()
return out.decode("utf-8")
@asyncio.coroutine @asyncio.coroutine
def start(self): def start(self):
""" """

View File

@ -17,6 +17,7 @@
import asyncio import asyncio
import shutil
@asyncio.coroutine @asyncio.coroutine
@ -34,3 +35,21 @@ def wait_run_in_executor(func, *args):
future = loop.run_in_executor(None, func, *args) future = loop.run_in_executor(None, func, *args)
yield from asyncio.wait([future]) yield from asyncio.wait([future])
return future.result() return future.result()
@asyncio.coroutine
def subprocess_check_output(*args, working_dir=None, env=None):
"""
Run a command and capture output
:param *args: List of command arguments
:param working_dir: Working directory
:param env: Command environment
:returns: Command output
"""
proc = yield from asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, cwd=working_dir, env=env)
output = yield from proc.stdout.read()
if output is None:
return ""
return output.decode("utf-8")

View File

@ -37,6 +37,17 @@ from tests.api.base import Query
os.environ["PATH"] = tempfile.mkdtemp() os.environ["PATH"] = tempfile.mkdtemp()
@pytest.yield_fixture
def restore_original_path():
"""
Temporary restore a standard path environnement. This allow
to run external binaries.
"""
os.environ["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
yield
os.environ["PATH"] = tempfile.mkdtemp()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def loop(request): def loop(request):
"""Return an event loop and destroy it at the end of test""" """Return an event loop and destroy it at the end of test"""

View File

@ -184,18 +184,18 @@ def test_create_netmap_config(vm):
assert "513:15/3 1:15/3" in content assert "513:15/3 1:15/3" in content
def test_build_command(vm): def test_build_command(vm, loop):
assert vm._build_command() == [vm.path, "-L", str(vm.application_id)] assert loop.run_until_complete(asyncio.async(vm._build_command())) == [vm.path, "-L", str(vm.application_id)]
def test_build_command_initial_config(vm): def test_build_command_initial_config(vm, loop):
filepath = os.path.join(vm.working_dir, "initial-config.cfg") filepath = os.path.join(vm.working_dir, "initial-config.cfg")
with open(filepath, "w+") as f: with open(filepath, "w+") as f:
f.write("service timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption") f.write("service timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption")
assert vm._build_command() == [vm.path, "-L", "-c", vm.initial_config_file, str(vm.application_id)] assert loop.run_until_complete(asyncio.async(vm._build_command())) == [vm.path, "-L", "-c", vm.initial_config_file, str(vm.application_id)]
def test_get_initial_config(vm): def test_get_initial_config(vm):
@ -231,3 +231,30 @@ def test_change_name(vm, tmpdir):
assert vm.name == "hello" assert vm.name == "hello"
with open(path) as f: with open(path) as f:
assert f.read() == "hostname hello" assert f.read() == "hostname hello"
def test_library_check(loop, vm):
with asyncio_patch("gns3server.utils.asyncio.subprocess_check_output", return_value="") as mock:
loop.run_until_complete(asyncio.async(vm._library_check()))
with asyncio_patch("gns3server.utils.asyncio.subprocess_check_output", return_value="libssl => not found") as mock:
with pytest.raises(IOUError):
loop.run_until_complete(asyncio.async(vm._library_check()))
def test_enable_l1_keepalives(loop, vm):
with asyncio_patch("gns3server.utils.asyncio.subprocess_check_output", return_value="***************************************************************\n\n-l Enable Layer 1 keepalive messages\n-u <n> UDP port base for distributed networks\n") as mock:
command = ["test"]
loop.run_until_complete(asyncio.async(vm._enable_l1_keepalives(command)))
assert command == ["test", "-l"]
with asyncio_patch("gns3server.utils.asyncio.subprocess_check_output", return_value="***************************************************************\n\n-u <n> UDP port base for distributed networks\n") as mock:
command = ["test"]
with pytest.raises(IOUError):
loop.run_until_complete(asyncio.async(vm._enable_l1_keepalives(command)))
assert command == ["test"]

View File

@ -47,7 +47,7 @@ def test_vm(project, manager):
def test_vm_invalid_vpcs_version(loop, project, manager): def test_vm_invalid_vpcs_version(loop, project, manager):
with asyncio_patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM._get_vpcs_welcome", return_value="Welcome to Virtual PC Simulator, version 0.1"): with asyncio_patch("gns3server.utils.asyncio.subprocess_check_output", return_value="Welcome to Virtual PC Simulator, version 0.1"):
with pytest.raises(VPCSError): with pytest.raises(VPCSError):
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
nio = manager.create_nio(vm.vpcs_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) nio = manager.create_nio(vm.vpcs_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})

0
tests/test_asyncio.py Normal file
View File

View File

@ -19,7 +19,7 @@
import asyncio import asyncio
import pytest import pytest
from gns3server.utils.asyncio import wait_run_in_executor from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output
def test_wait_run_in_executor(loop): def test_wait_run_in_executor(loop):
@ -40,3 +40,13 @@ def test_exception_wait_run_in_executor(loop):
exec = wait_run_in_executor(raise_exception) exec = wait_run_in_executor(raise_exception)
with pytest.raises(Exception): with pytest.raises(Exception):
result = loop.run_until_complete(asyncio.async(exec)) result = loop.run_until_complete(asyncio.async(exec))
def test_subprocess_check_output(loop, tmpdir, restore_original_path):
path = str(tmpdir / "test")
with open(path, "w+") as f:
f.write("TEST")
exec = subprocess_check_output("cat", path)
result = loop.run_until_complete(asyncio.async(exec))
assert result == "TEST"