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

Implements uBridge hypervisor.

This commit is contained in:
grossmj 2015-07-19 22:55:10 -06:00
parent 5125ddcde4
commit 1f890b4cad
7 changed files with 582 additions and 149 deletions

View File

@ -267,7 +267,7 @@ class VMwareHandler:
if nio_type != "nio_udp": if nio_type != "nio_udp":
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type)) raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
nio = vmware_manager.create_nio(None, request.json) nio = vmware_manager.create_nio(None, request.json)
vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio) yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
response.set_status(201) response.set_status(201)
response.json(nio) response.json(nio)
@ -290,5 +290,5 @@ class VMwareHandler:
vmware_manager = VMware.instance() vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"])) yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
response.set_status(204) response.set_status(204)

View File

@ -24,6 +24,7 @@ import subprocess
import tempfile import tempfile
import asyncio import asyncio
from gns3server.utils.asyncio import wait_for_process_termination
from .dynamips_hypervisor import DynamipsHypervisor from .dynamips_hypervisor import DynamipsHypervisor
from .dynamips_error import DynamipsError from .dynamips_error import DynamipsError
@ -146,7 +147,7 @@ class Hypervisor(DynamipsHypervisor):
# time to delete UNIX NIOs for instance. # time to delete UNIX NIOs for instance.
yield from asyncio.sleep(0.01) yield from asyncio.sleep(0.01)
try: try:
yield from asyncio.wait_for(self._process.wait(), timeout=3) yield from wait_for_process_termination(self._process, timeout=3)
except asyncio.TimeoutError: except asyncio.TimeoutError:
if self._process.returncode is None: if self._process.returncode is None:
log.warn("Dynamips process {} is still running... killing it".format(self._process.pid)) log.warn("Dynamips process {} is still running... killing it".format(self._process.pid))

View File

@ -22,15 +22,12 @@ VMware VM instance.
import sys import sys
import os import os
import socket import socket
import subprocess
import configparser
import shutil import shutil
import asyncio import asyncio
import tempfile import tempfile
import signal
from gns3server.utils.asyncio import wait_for_process_termination from pkg_resources import parse_version
from gns3server.utils.asyncio import monitor_process from gns3server.ubridge.hypervisor import Hypervisor
from gns3server.utils.telnet_server import TelnetServer from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.interfaces import get_windows_interfaces from gns3server.utils.interfaces import get_windows_interfaces
from collections import OrderedDict from collections import OrderedDict
@ -59,8 +56,7 @@ class VMwareVM(BaseVM):
self._linked_clone = linked_clone self._linked_clone = linked_clone
self._vmx_pairs = OrderedDict() self._vmx_pairs = OrderedDict()
self._ubridge_process = None self._ubridge_hypervisor = None
self._ubridge_stdout_file = ""
self._telnet_server_thread = None self._telnet_server_thread = None
self._serial_pipe = None self._serial_pipe = None
self._vmnets = [] self._vmnets = []
@ -259,27 +255,23 @@ class VMwareVM(BaseVM):
log.debug("disabling remaining adapter {}".format(adapter_number)) log.debug("disabling remaining adapter {}".format(adapter_number))
self._vmx_pairs["ethernet{}.startconnected".format(adapter_number)] = "FALSE" self._vmx_pairs["ethernet{}.startconnected".format(adapter_number)] = "FALSE"
self._update_ubridge_config() @asyncio.coroutine
def _add_ubridge_connection(self, nio, adapter_number):
def _update_ubridge_config(self):
"""
Updates the ubrige.ini file.
""" """
Creates a connection in uBridge.
ubridge_ini = os.path.join(self.working_dir, "ubridge.ini") :param nio: NIO instance
config = configparser.ConfigParser() :param adapter_number: adapter number
for adapter_number in range(0, self._adapters): """
nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio:
bridge_name = "bridge{}".format(adapter_number)
vnet = "ethernet{}.vnet".format(adapter_number) vnet = "ethernet{}.vnet".format(adapter_number)
if vnet not in self._vmx_pairs: if vnet not in self._vmx_pairs:
continue raise VMwareError("vnet {} not in VMX file".format(vnet))
yield from self._ubridge_hypervisor.send("bridge create {name}".format(name=vnet))
vmnet_interface = os.path.basename(self._vmx_pairs[vnet]) vmnet_interface = os.path.basename(self._vmx_pairs[vnet])
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
config[bridge_name] = {"source_linux_raw": vmnet_interface} yield from self._ubridge_hypervisor.send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet,
interface=vmnet_interface))
elif sys.platform.startswith("win"): elif sys.platform.startswith("win"):
windows_interfaces = get_windows_interfaces() windows_interfaces = get_windows_interfaces()
npf = None npf = None
@ -289,29 +281,38 @@ class VMwareVM(BaseVM):
elif vmnet_interface in interface["name"]: elif vmnet_interface in interface["name"]:
npf = interface["id"] npf = interface["id"]
if npf: if npf:
config[bridge_name] = {"source_ethernet": '"' + npf + '"'} yield from self._ubridge_hypervisor.send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet,
interface=npf))
else: else:
raise VMwareError("Could not find NPF id for VMnet interface {}".format(vmnet_interface)) raise VMwareError("Could not find NPF id for VMnet interface {}".format(vmnet_interface))
else: else:
config[bridge_name] = {"source_ethernet": vmnet_interface} yield from self._ubridge_hypervisor.send('bridge add_nio_ethernet {name} "{interface}"'.format(name=vnet,
interface=vmnet_interface))
if isinstance(nio, NIOUDP): if isinstance(nio, NIOUDP):
udp_tunnel_info = {"destination_udp": "{lport}:{rhost}:{rport}".format(lport=nio.lport, yield from self._ubridge_hypervisor.send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet,
lport=nio.lport,
rhost=nio.rhost, rhost=nio.rhost,
rport=nio.rport)} rport=nio.rport))
config[bridge_name].update(udp_tunnel_info)
if nio.capturing: if nio.capturing:
capture_info = {"pcap_file": "{pcap_file}".format(pcap_file=nio.pcap_output_file)} yield from self._ubridge_hypervisor.send('bridge start_capture {name} "{pcap_file}"'.format(name=vnet,
config[bridge_name].update(capture_info) pcap_file=nio.pcap_output_file))
try: yield from self._ubridge_hypervisor.send('bridge start {name}'.format(name=vnet))
with open(ubridge_ini, "w", encoding="utf-8") as config_file:
config.write(config_file) @asyncio.coroutine
log.info('VMware VM "{name}" [id={id}]: ubridge.ini updated'.format(name=self._name, def _delete_ubridge_connection(self, adapter_number):
id=self._id)) """
except OSError as e: Deletes a connection in uBridge.
raise VMwareError("Could not create {}: {}".format(ubridge_ini, e))
:param adapter_number: adapter number
"""
vnet = "ethernet{}.vnet".format(adapter_number)
if vnet not in self._vmx_pairs:
raise VMwareError("vnet {} not in VMX file".format(vnet))
yield from self._ubridge_hypervisor.send("bridge delete {name}".format(name=vnet))
@property @property
def ubridge_path(self): def ubridge_path(self):
@ -332,74 +333,16 @@ class VMwareVM(BaseVM):
Starts uBridge (handles connections to and from this VMware VM). Starts uBridge (handles connections to and from this VMware VM).
""" """
try: server_config = self._manager.config.get_section_config("Server")
# self._update_ubridge_config() server_host = server_config.get("host")
command = [self.ubridge_path] self._ubridge_hypervisor = Hypervisor(self._project, self.ubridge_path, self.working_dir, server_host)
log.info("starting ubridge: {}".format(command))
self._ubridge_stdout_file = os.path.join(self.working_dir, "ubridge.log")
log.info("logging to {}".format(self._ubridge_stdout_file))
with open(self._ubridge_stdout_file, "w", encoding="utf-8") as fd:
self._ubridge_process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self.working_dir)
#monitor_process(self._ubridge_process, self._termination_callback) log.info("Starting new uBridge hypervisor {}:{}".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
log.info("ubridge started PID={}".format(self._ubridge_process.pid)) yield from self._ubridge_hypervisor.start()
except (OSError, subprocess.SubprocessError) as e: log.info("Hypervisor {}:{} has successfully started".format(self._ubridge_hypervisor.host, self._ubridge_hypervisor.port))
ubridge_stdout = self.read_ubridge_stdout() yield from self._ubridge_hypervisor.connect()
log.error("Could not start ubridge: {}\n{}".format(e, ubridge_stdout)) if parse_version(self._ubridge_hypervisor.version) < parse_version('0.9.1'):
raise VMwareError("Could not start ubridge: {}\n{}".format(e, ubridge_stdout)) raise VMwareError("uBridge version must be >= 0.9.1, detected version is {}".format(self._ubridge_hypervisor.version))
def _termination_callback(self, returncode):
"""
Called when the process has stopped.
:param returncode: Process returncode
"""
log.info("uBridge process has stopped, return code: %d", returncode)
if returncode != 0:
self.project.emit("log.error", {"message": "uBridge process has stopped, return code: {}\n{}".format(returncode, self.read_ubridge_stdout())})
def is_ubridge_running(self):
"""
Checks if the ubridge process is running
:returns: True or False
"""
if self._ubridge_process and self._ubridge_process.returncode is None:
return True
return False
def read_ubridge_stdout(self):
"""
Reads the standard output of the uBridge process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._ubridge_stdout_file:
try:
with open(self._ubridge_stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._ubridge_stdout_file, e))
return output
def _terminate_process_ubridge(self):
"""
Terminate the ubridge process if running.
"""
if self._ubridge_process:
log.info('Stopping uBridge process for VMware VM "{}" PID={}'.format(self.name, self._ubridge_process.pid))
try:
self._ubridge_process.terminate()
# Sometime the process can already be dead when we garbage collect
except ProcessLookupError:
pass
@asyncio.coroutine @asyncio.coroutine
def start(self): def start(self):
@ -427,12 +370,17 @@ class VMwareVM(BaseVM):
except OSError as e: except OSError as e:
raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e)) raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e))
yield from self._start_ubridge()
if self._headless: if self._headless:
yield from self._control_vm("start", "nogui") yield from self._control_vm("start", "nogui")
else: else:
yield from self._control_vm("start") yield from self._control_vm("start")
yield from self._start_ubridge()
for adapter_number in range(0, self._adapters):
nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio:
yield from self._add_ubridge_connection(nio, adapter_number)
if self._enable_remote_console and self._console is not None: if self._enable_remote_console and self._console is not None:
yield from asyncio.sleep(1) # give some time to VMware to create the pipe file. yield from asyncio.sleep(1) # give some time to VMware to create the pipe file.
self._start_remote_console() self._start_remote_console()
@ -447,15 +395,8 @@ class VMwareVM(BaseVM):
""" """
self._stop_remote_console() self._stop_remote_console()
if self.is_ubridge_running(): if self._ubridge_hypervisor and self._ubridge_hypervisor.is_running():
self._terminate_process_ubridge() yield from self._ubridge_hypervisor.stop()
try:
yield from wait_for_process_termination(self._ubridge_process, timeout=3)
except asyncio.TimeoutError:
if self._ubridge_process.returncode is None:
log.warn("uBridge process {} is still running... killing it".format(self._ubridge_process.pid))
self._ubridge_process.kill()
self._ubridge_process = None
try: try:
if self.acpi_shutdown: if self.acpi_shutdown:
@ -697,7 +638,7 @@ class VMwareVM(BaseVM):
:param adapters: number of adapters :param adapters: number of adapters
""" """
# VMware VMs are limit to 10 adapters # VMware VMs are limited to 10 adapters
if adapters > 10: if adapters > 10:
raise VMwareError("Number of adapters above the maximum supported of 10") raise VMwareError("Number of adapters above the maximum supported of 10")
@ -757,20 +698,7 @@ class VMwareVM(BaseVM):
log.info("VMware VM '{name}' [{id}] is not allowed to use any adapter".format(name=self.name, id=self.id)) log.info("VMware VM '{name}' [{id}] is not allowed to use any adapter".format(name=self.name, id=self.id))
self._use_any_adapter = use_any_adapter self._use_any_adapter = use_any_adapter
def _reload_ubridge(self): @asyncio.coroutine
"""
Reloads ubridge.
"""
if self.is_ubridge_running():
self._update_ubridge_config()
if not sys.platform.startswith("win"):
os.kill(self._ubridge_process.pid, signal.SIGHUP)
else:
# Windows doesn't support SIGHUP...
self._terminate_process_ubridge()
self._start_ubridge()
def adapter_add_nio_binding(self, adapter_number, nio): def adapter_add_nio_binding(self, adapter_number, nio):
""" """
Adds an adapter NIO binding. Adds an adapter NIO binding.
@ -786,13 +714,16 @@ class VMwareVM(BaseVM):
adapter_number=adapter_number)) adapter_number=adapter_number))
adapter.add_nio(0, nio) adapter.add_nio(0, nio)
if self._started:
yield from self._add_ubridge_connection(nio, adapter_number)
log.info("VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, log.info("VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
id=self.id, id=self.id,
nio=nio, nio=nio,
adapter_number=adapter_number)) adapter_number=adapter_number))
self._reload_ubridge()
@asyncio.coroutine
def adapter_remove_nio_binding(self, adapter_number): def adapter_remove_nio_binding(self, adapter_number):
""" """
Removes an adapter NIO binding. Removes an adapter NIO binding.
@ -812,13 +743,14 @@ class VMwareVM(BaseVM):
if isinstance(nio, NIOUDP): if isinstance(nio, NIOUDP):
self.manager.port_manager.release_udp_port(nio.lport, self._project) self.manager.port_manager.release_udp_port(nio.lport, self._project)
adapter.remove_nio(0) adapter.remove_nio(0)
if self._started:
yield from self._delete_ubridge_connection(adapter_number)
log.info("VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name, log.info("VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
id=self.id, id=self.id,
nio=nio, nio=nio,
adapter_number=adapter_number)) adapter_number=adapter_number))
self._reload_ubridge()
return nio return nio
def _get_pipe_name(self): def _get_pipe_name(self):

View File

View File

@ -0,0 +1,210 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Represents a uBridge hypervisor and starts/stops the associated uBridge process.
"""
import os
import subprocess
import asyncio
import socket
from gns3server.utils.asyncio import wait_for_process_termination
from gns3server.utils.asyncio import monitor_process
from .ubridge_hypervisor import UBridgeHypervisor
from .ubridge_error import UbridgeError
import logging
log = logging.getLogger(__name__)
class Hypervisor(UBridgeHypervisor):
"""
Hypervisor.
:param project: Project instance
:param path: path to uBridge executable
:param working_dir: working directory
:param host: host/address for this hypervisor
:param port: port for this hypervisor
"""
_instance_count = 1
def __init__(self, project, path, working_dir, host, port=None):
if port is None:
try:
port = None
info = socket.getaddrinfo(host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
if not info:
raise UbridgeError("getaddrinfo returns an empty list on {}".format(host))
for res in info:
af, socktype, proto, _, sa = res
# let the OS find an unused port for the uBridge hypervisor
with socket.socket(af, socktype, proto) as sock:
sock.bind(sa)
port = sock.getsockname()[1]
break
except OSError as e:
raise UbridgeError("Could not find free port for the uBridge hypervisor: {}".format(e))
super().__init__(host, port)
self._project = project
self._path = path
self._working_dir = working_dir
self._command = []
self._process = None
self._stdout_file = ""
self._started = False
@property
def process(self):
"""
Returns the subprocess of the Hypervisor
:returns: subprocess
"""
return self._process
@property
def started(self):
"""
Returns either this hypervisor has been started or not.
:returns: boolean
"""
return self._started
@property
def path(self):
"""
Returns the path to the uBridge executable.
:returns: path to uBridge
"""
return self._path
@path.setter
def path(self, path):
"""
Sets the path to the uBridge executable.
:param path: path to uBridge
"""
self._path = path
@asyncio.coroutine
def start(self):
"""
Starts the uBridge hypervisor process.
"""
try:
# self._update_ubridge_config()
command = self._build_command()
log.info("starting ubridge: {}".format(command))
self._stdout_file = os.path.join(self._working_dir, "ubridge.log")
log.info("logging to {}".format(self._stdout_file))
with open(self._stdout_file, "w", encoding="utf-8") as fd:
self._process = yield from asyncio.create_subprocess_exec(*command,
stdout=fd,
stderr=subprocess.STDOUT,
cwd=self._working_dir)
monitor_process(self._process, self._termination_callback)
log.info("ubridge started PID={}".format(self._process.pid))
except (OSError, subprocess.SubprocessError) as e:
ubridge_stdout = self.read_stdout()
log.error("Could not start ubridge: {}\n{}".format(e, ubridge_stdout))
raise UBridgeHypervisor("Could not start ubridge: {}\n{}".format(e, ubridge_stdout))
def _termination_callback(self, returncode):
"""
Called when the process has stopped.
:param returncode: Process returncode
"""
log.info("uBridge process has stopped, return code: %d", returncode)
if returncode != 0:
self._project.emit("log.error", {"message": "uBridge process has stopped, return code: {}\n{}".format(returncode, self.read_stdout())})
@asyncio.coroutine
def stop(self):
"""
Stops the uBridge hypervisor process.
"""
if self.is_running():
log.info("Stopping uBridge process PID={}".format(self._process.pid))
yield from UBridgeHypervisor.stop(self)
try:
yield from wait_for_process_termination(self._process, timeout=3)
except asyncio.TimeoutError:
if self._process.returncode is None:
log.warn("uBridge process {} is still running... killing it".format(self._process.pid))
self._process.kill()
if self._stdout_file and os.access(self._stdout_file, os.W_OK):
try:
os.remove(self._stdout_file)
except OSError as e:
log.warning("could not delete temporary uBridge log file: {}".format(e))
self._started = False
def read_stdout(self):
"""
Reads the standard output of the uBridge process.
Only use when the process has been stopped or has crashed.
"""
output = ""
if self._stdout_file and os.access(self._stdout_file, os.R_OK):
try:
with open(self._stdout_file, "rb") as file:
output = file.read().decode("utf-8", errors="replace")
except OSError as e:
log.warn("could not read {}: {}".format(self._stdout_file, e))
return output
def is_running(self):
"""
Checks if the process is running
:returns: True or False
"""
if self._process and self._process.returncode is None:
return True
return False
def _build_command(self):
"""
Command to start the uBridge hypervisor process.
(to be passed to subprocess.Popen())
"""
command = [self._path]
command.extend(["-H", "{}:{}".format(self._host, self._port)])
return command

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Custom exceptions for the ubridge.
"""
class UbridgeError(Exception):
def __init__(self, message):
Exception.__init__(self, message)

View File

@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import time
import logging
import asyncio
from .ubridge_error import UbridgeError
log = logging.getLogger(__name__)
class UBridgeHypervisor:
"""
Creates a new connection to uBridge hypervisor.
:param host: the hostname or ip address string of the uBridge hypervisor
:param port: the tcp port integer
:param timeout: timeout integer for how long to wait for a response to commands sent to the
hypervisor (defaults to 30 seconds)
"""
# Used to parse Ubridge response codes
error_re = re.compile(r"""^2[0-9]{2}-""")
success_re = re.compile(r"""^1[0-9]{2}\s{1}""")
def __init__(self, host, port, timeout=30.0):
self._host = host
self._port = port
self._version = "N/A"
self._timeout = timeout
self._reader = None
self._writer = None
self._io_lock = asyncio.Lock()
@asyncio.coroutine
def connect(self, timeout=10):
"""
Connects to the hypervisor.
"""
# connect to a local address by default
# if listening to all addresses (IPv4 or IPv6)
if self._host == "0.0.0.0":
host = "127.0.0.1"
elif self._host == "::":
host = "::1"
else:
host = self._host
begin = time.time()
connection_success = False
last_exception = None
while time.time() - begin < timeout:
yield from asyncio.sleep(0.01)
try:
self._reader, self._writer = yield from asyncio.open_connection(host, self._port)
except OSError as e:
last_exception = e
continue
connection_success = True
break
if not connection_success:
raise UbridgeError("Couldn't connect to hypervisor on {}:{} :{}".format(host, self._port, last_exception))
else:
log.info("Connected to uBridge hypervisor after {:.4f} seconds".format(time.time() - begin))
try:
version = yield from self.send("hypervisor version")
self._version = version[0].split("-", 1)[0]
except IndexError:
self._version = "Unknown"
@property
def version(self):
"""
Returns uBridge version.
:returns: version string
"""
return self._version
@asyncio.coroutine
def close(self):
"""
Closes the connection to this hypervisor (but leave it running).
"""
yield from self.send("hypervisor close")
self._writer.close()
self._reader, self._writer = None
@asyncio.coroutine
def stop(self):
"""
Stops this hypervisor (will no longer run).
"""
try:
# try to properly stop the hypervisor
yield from self.send("hypervisor stop")
except UbridgeError:
pass
try:
if self._writer is not None:
yield from self._writer.drain()
self._writer.close()
except OSError as e:
log.debug("Stopping hypervisor {}:{} {}".format(self._host, self._port, e))
self._reader = self._writer = None
@asyncio.coroutine
def reset(self):
"""
Resets this hypervisor (used to get an empty configuration).
"""
yield from self.send("hypervisor reset")
@property
def port(self):
"""
Returns the port used to start the hypervisor.
:returns: port number (integer)
"""
return self._port
@port.setter
def port(self, port):
"""
Sets the port used to start the hypervisor.
:param port: port number (integer)
"""
self._port = port
@property
def host(self):
"""
Returns the host (binding) used to start the hypervisor.
:returns: host/address (string)
"""
return self._host
@host.setter
def host(self, host):
"""
Sets the host (binding) used to start the hypervisor.
:param host: host/address (string)
"""
self._host = host
@asyncio.coroutine
def send(self, command):
"""
Sends commands to this hypervisor.
:param command: a uBridge hypervisor command
:returns: results as a list
"""
# uBridge responses are of the form:
# 1xx yyyyyy\r\n
# 1xx yyyyyy\r\n
# ...
# 100-yyyy\r\n
# or
# 2xx-yyyy\r\n
#
# Where 1xx is a code from 100-199 for a success or 200-299 for an error
# The result might be multiple lines and might be less than the buffer size
# but still have more data. The only thing we know for sure is the last line
# will begin with '100-' or a '2xx-' and end with '\r\n'
with (yield from self._io_lock):
if self._writer is None or self._reader is None:
raise UbridgeError("Not connected")
try:
command = command.strip() + '\n'
log.debug("sending {}".format(command))
self._writer.write(command.encode())
yield from self._writer.drain()
except OSError as e:
raise UbridgeError("Lost communication with {host}:{port} :{error}, Dynamips process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
# Now retrieve the result
data = []
buf = ''
while True:
try:
try:
chunk = yield from self._reader.read(1024)
except asyncio.CancelledError:
# task has been canceled but continue to read
# any remaining data sent by the hypervisor
continue
if not chunk:
raise UbridgeError("No data returned from {host}:{port}, uBridge process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
buf += chunk.decode("utf-8")
except OSError as e:
raise UbridgeError("Lost communication with {host}:{port} :{error}, uBridge process running: {run}"
.format(host=self._host, port=self._port, error=e, run=self.is_running()))
# If the buffer doesn't end in '\n' then we can't be done
try:
if buf[-1] != '\n':
continue
except IndexError:
raise UbridgeError("Could not communicate with {host}:{port}, uBridge process running: {run}"
.format(host=self._host, port=self._port, run=self.is_running()))
data += buf.split('\r\n')
if data[-1] == '':
data.pop()
buf = ''
# Does it contain an error code?
if self.error_re.search(data[-1]):
raise UbridgeError(data[-1][4:])
# Or does the last line begin with '100-'? Then we are done!
if data[-1][:4] == '100-':
data[-1] = data[-1][4:]
if data[-1] == 'OK':
data.pop()
break
# Remove success responses codes
for index in range(len(data)):
if self.success_re.search(data[index]):
data[index] = data[index][4:]
log.debug("returned result {}".format(data))
return data