1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-25 01:38:08 +00:00

Replace iouyap by ubridge to handle IOU connections. Fixes #614.

This commit is contained in:
grossmj 2016-11-06 21:27:49 +11:00
parent c271ef8c6a
commit 183f602fc0
2 changed files with 92 additions and 210 deletions

View File

@ -21,7 +21,6 @@ order to run an IOU VM.
""" """
import os import os
import signal
import socket import socket
import re import re
import asyncio import asyncio
@ -40,12 +39,11 @@ from .iou_error import IOUError
from ..adapters.ethernet_adapter import EthernetAdapter from ..adapters.ethernet_adapter import EthernetAdapter
from ..adapters.serial_adapter import SerialAdapter from ..adapters.serial_adapter import SerialAdapter
from ..nios.nio_udp import NIOUDP from ..nios.nio_udp import NIOUDP
from ..nios.nio_tap import NIOTAP
from ..nios.nio_ethernet import NIOEthernet
from ..base_node import BaseNode from ..base_node import BaseNode
from .utils.iou_import import nvram_import from .utils.iou_import import nvram_import
from .utils.iou_export import nvram_export from .utils.iou_export import nvram_export
from .ioucon import start_ioucon from .ioucon import start_ioucon
from gns3server.ubridge.ubridge_error import UbridgeError
from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.file_watcher import FileWatcher
import gns3server.utils.asyncio import gns3server.utils.asyncio
import gns3server.utils.images import gns3server.utils.images
@ -72,12 +70,12 @@ class IOUVM(BaseNode):
super().__init__(name, node_id, project, manager, console=console) super().__init__(name, node_id, project, manager, console=console)
self._iouyap_process = None
self._iou_process = None self._iou_process = None
self._iou_stdout_file = "" self._iou_stdout_file = ""
self._started = False self._started = False
self._path = None self._path = None
self._ioucon_thread = None self._ioucon_thread = None
self._nvram_watcher = None
# IOU settings # IOU settings
self._ethernet_adapters = [] self._ethernet_adapters = []
@ -91,8 +89,6 @@ class IOUVM(BaseNode):
self._ram = 256 # Megabytes self._ram = 256 # Megabytes
self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes).
self._nvram_watcher = None
def _config(self): def _config(self):
return self._manager.config.get_section_config("IOU") return self._manager.config.get_section_config("IOU")
@ -166,7 +162,7 @@ class IOUVM(BaseNode):
def _check_requirements(self): def _check_requirements(self):
""" """
Checks if IOUYAP executable is available and if image is accessible. Checks the IOU image.
""" """
if not self._path: if not self._path:
@ -192,16 +188,6 @@ class IOUVM(BaseNode):
if not os.access(self._path, os.X_OK): if not os.access(self._path, os.X_OK):
raise IOUError("IOU image '{}' is not executable".format(self._path)) raise IOUError("IOU image '{}' is not executable".format(self._path))
path = self.iouyap_path
if not path:
raise IOUError("No path to iouyap program has been set")
if not os.path.isfile(path):
raise IOUError("iouyap program '{}' is not accessible".format(path))
if not os.access(path, os.X_OK):
raise IOUError("iouyap program '{}' is not executable".format(path))
def __json__(self): def __json__(self):
iou_vm_info = {"name": self.name, iou_vm_info = {"name": self.name,
@ -227,21 +213,6 @@ class IOUVM(BaseNode):
iou_vm_info["path"] = self.manager.get_relative_image_path(self.path) iou_vm_info["path"] = self.manager.get_relative_image_path(self.path)
return iou_vm_info return iou_vm_info
@property
def iouyap_path(self):
"""
Returns the IOUYAP executable path.
:returns: path to IOUYAP
"""
search_path = self._config().get("iouyap_path", "iouyap")
path = shutil.which(search_path)
# shutil.which return None if the path doesn't exists
if not path:
return search_path
return path
@property @property
def iourc_path(self): def iourc_path(self):
""" """
@ -496,9 +467,7 @@ class IOUVM(BaseNode):
raise IOUError("The iourc path '{}' is not a regular file".format(iourc_path)) raise IOUError("The iourc path '{}' is not a regular file".format(iourc_path))
yield from self._check_iou_licence() yield from self._check_iou_licence()
iouyap_path = self.iouyap_path yield from self._start_ubridge()
if not iouyap_path or not os.path.isfile(iouyap_path):
raise IOUError("iouyap is necessary to start IOU")
self._create_netmap_config() self._create_netmap_config()
self._push_configs_to_nvram() self._push_configs_to_nvram()
@ -539,8 +508,46 @@ class IOUVM(BaseNode):
# start console support # start console support
self._start_ioucon() self._start_ioucon()
# connections support
yield from self._start_iouyap() # configure networking support
yield from self._networking()
@asyncio.coroutine
def _networking(self):
"""
Configures the IOL bridge in uBridge.
"""
bridge_name = "IOL-BRIDGE-{}".format(self.application_id + 512)
try:
# delete any previous bridge if it exists
yield from self._ubridge_send("iol_bridge delete {name}".format(name=bridge_name))
except UbridgeError:
pass
yield from self._ubridge_send("iol_bridge create {name} {bridge_id}".format(name=bridge_name, bridge_id=self.application_id + 512))
bay_id = 0
for adapter in self._adapters:
unit_id = 0
for unit in adapter.ports.keys():
nio = adapter.get_nio(unit)
if nio and isinstance(nio, NIOUDP):
yield from self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
iol_id=self.application_id,
bay=bay_id,
unit=unit_id,
lport=nio.lport,
rhost=nio.rhost,
rport=nio.rport))
if nio.capturing:
yield from self._ubridge_send('iol_bridge start_capture {name} "{output_file}" {data_link_type}'.format(name=bridge_name,
output_file=nio.pcap_output_file,
data_link_type=nio.pcap_data_link_type))
unit_id += 1
bay_id += 1
yield from self._ubridge_send("iol_bridge start {name}".format(name=bridge_name))
def _termination_callback(self, process_name, returncode): def _termination_callback(self, process_name, returncode):
""" """
@ -550,12 +557,9 @@ class IOUVM(BaseNode):
""" """
self._terminate_process_iou() self._terminate_process_iou()
self._terminate_process_iouyap()
self._ioucon_thread_stop_event.set() self._ioucon_thread_stop_event.set()
if returncode != 0: if returncode != 0:
log.info("{} process has stopped, return code: {}".format(process_name, returncode))
else:
if returncode == 11: if returncode == 11:
message = "{} process has stopped, return code: {}. This could be an issue with the image using a different image can fix the issue.\n{}".format(process_name, returncode, self.read_iou_stdout()) message = "{} process has stopped, return code: {}. This could be an issue with the image using a different image can fix the issue.\n{}".format(process_name, returncode, self.read_iou_stdout())
else: else:
@ -575,99 +579,13 @@ class IOUVM(BaseNode):
for file_path in glob.glob(os.path.join(glob.escape(self.working_dir), "vlan.dat-*")): for file_path in glob.glob(os.path.join(glob.escape(self.working_dir), "vlan.dat-*")):
shutil.move(file_path, destination) shutil.move(file_path, destination)
@asyncio.coroutine
def _start_iouyap(self):
"""
Starts iouyap (handles connections to and from this IOU VM).
"""
try:
self._update_iouyap_config()
command = [self.iouyap_path, "-q", str(self.application_id + 512)] # iouyap has always IOU ID + 512
log.info("starting iouyap: {}".format(command))
self._iouyap_stdout_file = os.path.join(self.working_dir, "iouyap.log")
log.info("logging to {}".format(self._iouyap_stdout_file))
with open(self._iouyap_stdout_file, "w", encoding="utf-8") as fd:
self._iouyap_process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self.working_dir)
callback = functools.partial(self._termination_callback, "iouyap")
gns3server.utils.asyncio.monitor_process(self._iouyap_process, callback)
log.info("iouyap started PID={}".format(self._iouyap_process.pid))
except (OSError, subprocess.SubprocessError) as e:
iouyap_stdout = self.read_iouyap_stdout()
log.error("Could not start iouyap: {}\n{}".format(e, iouyap_stdout))
raise IOUError("Could not start iouyap: {}\n{}".format(e, iouyap_stdout))
def _update_iouyap_config(self):
"""
Updates the iouyap.ini file.
"""
iouyap_ini = os.path.join(self.working_dir, "iouyap.ini")
config = configparser.ConfigParser()
config["default"] = {"netmap": "NETMAP",
"base_port": "49000"}
bay_id = 0
for adapter in self._adapters:
unit_id = 0
for unit in adapter.ports.keys():
nio = adapter.get_nio(unit)
if nio:
connection = None
if isinstance(nio, NIOUDP):
# UDP tunnel
connection = {"tunnel_udp": "{lport}:{rhost}:{rport}".format(lport=nio.lport,
rhost=nio.rhost,
rport=nio.rport)}
elif isinstance(nio, NIOTAP):
# TAP interface
connection = {"tap_dev": "{tap_device}".format(tap_device=nio.tap_device)}
elif isinstance(nio, NIOEthernet):
# Ethernet interface
connection = {"eth_dev": "{ethernet_device}".format(ethernet_device=nio.ethernet_device)}
if connection:
interface = "{iouyap_id}:{bay}/{unit}".format(iouyap_id=str(self.application_id + 512), bay=bay_id, unit=unit_id)
config[interface] = connection
if nio.capturing:
pcap_data_link_type = nio.pcap_data_link_type.upper()
if pcap_data_link_type == "DLT_PPP_SERIAL":
pcap_protocol = "ppp"
elif pcap_data_link_type == "DLT_C_HDLC":
pcap_protocol = "hdlc"
elif pcap_data_link_type == "DLT_FRELAY":
pcap_protocol = "fr"
else:
pcap_protocol = "ethernet"
capture_info = {"pcap_file": "{pcap_file}".format(pcap_file=nio.pcap_output_file),
"pcap_protocol": pcap_protocol,
"pcap_overwrite": "y"}
config[interface].update(capture_info)
unit_id += 1
bay_id += 1
try:
with open(iouyap_ini, "w", encoding="utf-8") as config_file:
config.write(config_file)
log.info("IOU {name} [id={id}]: iouyap.ini updated".format(name=self._name,
id=self._id))
except OSError as e:
raise IOUError("Could not create {}: {}".format(iouyap_ini, e))
@asyncio.coroutine @asyncio.coroutine
def stop(self): def stop(self):
""" """
Stops the IOU process. Stops the IOU process.
""" """
yield from self._stop_ubridge()
if self._nvram_watcher: if self._nvram_watcher:
self._nvram_watcher.close() self._nvram_watcher.close()
self._nvram_watcher = None self._nvram_watcher = None
@ -693,35 +611,9 @@ class IOUVM(BaseNode):
pass pass
self._iou_process = None self._iou_process = None
if self.is_iouyap_running():
self._terminate_process_iouyap()
try:
yield from gns3server.utils.asyncio.wait_for_process_termination(self._iouyap_process, timeout=3)
except asyncio.TimeoutError:
if self._iouyap_process.returncode is None:
log.warn("IOUYAP process {} is still running... killing it".format(self._iouyap_process.pid))
try:
self._iouyap_process.kill()
except ProcessLookupError:
pass
self._iouyap_process = None
self._started = False self._started = False
self.save_configs() self.save_configs()
def _terminate_process_iouyap(self):
"""
Terminate the IOUYAP process if running.
"""
if self._iouyap_process:
log.info('Stopping IOUYAP process for IOU VM "{}" PID={}'.format(self.name, self._iouyap_process.pid))
try:
self._iouyap_process.terminate()
# Sometime the process can already be dead when we garbage collect
except ProcessLookupError:
pass
def _terminate_process_iou(self): def _terminate_process_iou(self):
""" """
Terminate the IOU process if running Terminate the IOU process if running
@ -757,17 +649,6 @@ class IOUVM(BaseNode):
return True return True
return False return False
def is_iouyap_running(self):
"""
Checks if the IOUYAP process is running
:returns: True or False
"""
if self._iouyap_process and self._iouyap_process.returncode is None:
return True
return False
def _create_netmap_config(self): def _create_netmap_config(self):
""" """
Creates the NETMAP file. Creates the NETMAP file.
@ -778,10 +659,10 @@ class IOUVM(BaseNode):
with open(netmap_path, "w", encoding="utf-8") as f: with open(netmap_path, "w", encoding="utf-8") as f:
for bay in range(0, 16): for bay in range(0, 16):
for unit in range(0, 4): for unit in range(0, 4):
f.write("{iouyap_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(iouyap_id=str(self.application_id + 512), f.write("{ubridge_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(ubridge_id=str(self.application_id + 512),
bay=bay, bay=bay,
unit=unit, unit=unit,
iou_id=self.application_id)) iou_id=self.application_id))
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name, log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
id=self._id)) id=self._id))
except OSError as e: except OSError as e:
@ -853,21 +734,6 @@ class IOUVM(BaseNode):
log.warn("could not read {}: {}".format(self._iou_stdout_file, e)) log.warn("could not read {}: {}".format(self._iou_stdout_file, e))
return output return output
def read_iouyap_stdout(self):
"""
Reads the standard output of the iouyap process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._iouyap_stdout_file:
try:
with open(self._iouyap_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._iouyap_stdout_file, e))
return output
def _start_ioucon(self): def _start_ioucon(self):
""" """
Starts ioucon thread (for console connections). Starts ioucon thread (for console connections).
@ -963,12 +829,16 @@ class IOUVM(BaseNode):
nio=nio, nio=nio,
adapter_number=adapter_number, adapter_number=adapter_number,
port_number=port_number)) port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config() if self.ubridge and self.ubridge.is_running():
try: bridge_name = "IOL-BRIDGE-{}".format(self.application_id + 512)
os.kill(self._iouyap_process.pid, signal.SIGHUP) yield from self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
except ProcessLookupError: iol_id=self.application_id,
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid)) bay=adapter_number,
unit=port_number,
lport=nio.lport,
rhost=nio.rhost,
rport=nio.rport))
@asyncio.coroutine @asyncio.coroutine
def adapter_remove_nio_binding(self, adapter_number, port_number): def adapter_remove_nio_binding(self, adapter_number, port_number):
@ -999,12 +869,13 @@ class IOUVM(BaseNode):
nio=nio, nio=nio,
adapter_number=adapter_number, adapter_number=adapter_number,
port_number=port_number)) port_number=port_number))
if self.is_iouyap_running():
self._update_iouyap_config() if self.ubridge and self.ubridge.is_running():
try: bridge_name = "IOL-BRIDGE-{}".format(self.application_id + 512)
os.kill(self._iouyap_process.pid, signal.SIGHUP) yield from self._ubridge_send("iol_bridge delete_nio_udp {name} {bay} {unit}".format(name=bridge_name,
except ProcessLookupError: bay=adapter_number,
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid)) unit=port_number))
return nio return nio
@property @property
@ -1292,12 +1163,13 @@ class IOUVM(BaseNode):
port_number=port_number, port_number=port_number,
output_file=output_file)) output_file=output_file))
if self.is_iouyap_running(): if self.ubridge and self.ubridge.is_running():
self._update_iouyap_config() bridge_name = "IOL-BRIDGE-{}".format(self.application_id + 512)
try: yield from self._ubridge_send('iol_bridge start_capture {name} {bay} {unit} "{output_file}" {data_link_type}'.format(name=bridge_name,
os.kill(self._iouyap_process.pid, signal.SIGHUP) bay=adapter_number,
except ProcessLookupError: unit=port_number,
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid)) output_file=output_file,
data_link_type=data_link_type))
@asyncio.coroutine @asyncio.coroutine
def stop_capture(self, adapter_number, port_number): def stop_capture(self, adapter_number, port_number):
@ -1328,9 +1200,8 @@ class IOUVM(BaseNode):
id=self._id, id=self._id,
adapter_number=adapter_number, adapter_number=adapter_number,
port_number=port_number)) port_number=port_number))
if self.is_iouyap_running(): if self.ubridge and self.ubridge.is_running():
self._update_iouyap_config() bridge_name = "IOL-BRIDGE-{}".format(self.application_id + 512)
try: yield from self._ubridge_send('iol_bridge stop_capture {name} {bay} {unit}'.format(name=bridge_name,
os.kill(self._iouyap_process.pid, signal.SIGHUP) bay=adapter_number,
except ProcessLookupError: unit=port_number))
log.error("Could not update iouyap configuration: process (PID={}) not found".format(self._iouyap_process.pid))

View File

@ -76,6 +76,7 @@ class Hypervisor(UBridgeHypervisor):
self._process = None self._process = None
self._stdout_file = "" self._stdout_file = ""
self._started = False self._started = False
self._version = ""
@property @property
def process(self): def process(self):
@ -117,6 +118,16 @@ class Hypervisor(UBridgeHypervisor):
self._path = path self._path = path
@property
def version(self):
"""
Returns the uBridge version.
:returns: string
"""
return self._version
@asyncio.coroutine @asyncio.coroutine
def _check_ubridge_version(self): def _check_ubridge_version(self):
""" """
@ -126,9 +137,9 @@ class Hypervisor(UBridgeHypervisor):
output = yield from subprocess_check_output(self._path, "-v", cwd=self._working_dir) output = yield from subprocess_check_output(self._path, "-v", cwd=self._working_dir)
match = re.search("ubridge version ([0-9a-z\.]+)", output) match = re.search("ubridge version ([0-9a-z\.]+)", output)
if match: if match:
version = match.group(1) self._version = match.group(1)
if parse_version(version) < parse_version("0.9.5"): if parse_version(self._version) < parse_version("0.9.6"):
raise UbridgeError("uBridge executable version must be >= 0.9.5") raise UbridgeError("uBridge executable version must be >= 0.9.6")
else: else:
raise UbridgeError("Could not determine uBridge version for {}".format(self._path)) raise UbridgeError("Could not determine uBridge version for {}".format(self._path))
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e: