From 98e01ff21de8a745358f0095a9ec00bf0db931f3 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 27 May 2015 21:06:18 -0600 Subject: [PATCH] Serial console implementation for VMware VMs. --- .../modules/virtualbox/virtualbox_vm.py | 2 +- gns3server/modules/vmware/vmware_vm.py | 122 ++++++++++++++++-- .../virtualbox => utils}/telnet_server.py | 1 + 3 files changed, 110 insertions(+), 15 deletions(-) rename gns3server/{modules/virtualbox => utils}/telnet_server.py (99%) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 4ec0aa37..a306db85 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -29,11 +29,11 @@ import socket import asyncio from pkg_resources import parse_version +from gns3server.utils.telnet_server import TelnetServer from .virtualbox_error import VirtualBoxError from ..nios.nio_udp import NIOUDP from ..nios.nio_nat import NIONAT from ..adapters.ethernet_adapter import EthernetAdapter -from .telnet_server import TelnetServer # TODO: port TelnetServer to asyncio from ..base_vm import BaseVM if sys.platform.startswith('win'): diff --git a/gns3server/modules/vmware/vmware_vm.py b/gns3server/modules/vmware/vmware_vm.py index 44694476..7dd82bd6 100644 --- a/gns3server/modules/vmware/vmware_vm.py +++ b/gns3server/modules/vmware/vmware_vm.py @@ -21,25 +21,30 @@ VMware VM instance. import sys import os +import socket import subprocess import configparser import shutil import asyncio +import tempfile from gns3server.utils.asyncio import wait_for_process_termination from gns3server.utils.asyncio import monitor_process +from gns3server.utils.telnet_server import TelnetServer from collections import OrderedDict from .vmware_error import VMwareError from ..nios.nio_udp import NIOUDP from ..adapters.ethernet_adapter import EthernetAdapter from ..base_vm import BaseVM +if sys.platform.startswith('win'): + import msvcrt + import win32file import logging log = logging.getLogger(__name__) - class VMwareVM(BaseVM): """ @@ -54,8 +59,11 @@ class VMwareVM(BaseVM): self._vmx_pairs = OrderedDict() self._ubridge_process = None self._ubridge_stdout_file = "" + self._telnet_server_thread = None + self._serial_pipe = None self._vmnets = [] self._maximum_adapters = 10 + self._started = False self._closed = False # VMware VM settings @@ -109,11 +117,6 @@ class VMwareVM(BaseVM): def _set_network_options(self): - try: - self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) - except OSError as e: - raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) - # first do some sanity checks for adapter_number in range(0, self._adapters): connected = "ethernet{}.startConnected".format(adapter_number) @@ -179,7 +182,6 @@ class VMwareVM(BaseVM): log.debug("disabling remaining adapter {}".format(adapter_number)) self._vmx_pairs["ethernet{}.startConnected".format(adapter_number)] = "FALSE" - self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) self._update_ubridge_config() def _update_ubridge_config(self): @@ -242,7 +244,7 @@ class VMwareVM(BaseVM): :returns: path to uBridge """ - path = self._manager.config.get_section_config("VMware").get("ubridge_path", "ubridge") + path = self._manager.config.get_section_config("Server").get("ubridge_path", "ubridge") if path == "ubridge": path = shutil.which("ubridge") return path @@ -333,13 +335,26 @@ class VMwareVM(BaseVM): if not ubridge_path or not os.path.isfile(ubridge_path): raise VMwareError("ubridge is necessary to start a VMware VM") - self._set_network_options() - yield from self._start_ubridge() + try: + self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) + except OSError as e: + raise VMwareError('Could not read VMware VMX file "{}": {}'.format(self._vmx_path, e)) + self._set_network_options() + self._set_serial_console() + self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs) + + yield from self._start_ubridge() if self._headless: yield from self._control_vm("start", "nogui") else: yield from self._control_vm("start") + + 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. + self._start_remote_console() + + self._started = True log.info("VMware VM '{name}' [{id}] started".format(name=self.name, id=self.id)) @asyncio.coroutine @@ -348,6 +363,7 @@ class VMwareVM(BaseVM): Stops this VMware VM. """ + self._stop_remote_console() if self.is_ubridge_running(): self._terminate_process_ubridge() try: @@ -361,9 +377,8 @@ class VMwareVM(BaseVM): try: yield from self._control_vm("stop") finally: - + self._started = False self._vmnets.clear() - try: self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path) except OSError as e: @@ -518,10 +533,11 @@ class VMwareVM(BaseVM): if enable_remote_console: log.info("VMware VM '{name}' [{id}] has enabled the console".format(name=self.name, id=self.id)) - #self._start_remote_console() + if self._started: + self._start_remote_console() else: log.info("VMware VM '{name}' [{id}] has disabled the console".format(name=self.name, id=self.id)) - #self._stop_remote_console() + self._stop_remote_console() self._enable_remote_console = enable_remote_console @property @@ -647,3 +663,81 @@ class VMwareVM(BaseVM): nio=nio, adapter_number=adapter_number)) return nio + + def _get_pipe_name(self): + """ + Returns the pipe name to create a serial connection. + + :returns: pipe path (string) + """ + + if sys.platform.startswith("win"): + pipe_name = r"\\.\pipe\gns3_vmware\{}".format(self.id) + else: + pipe_name = os.path.join(tempfile.gettempdir(), "gns3_vmware", "{}".format(self.id)) + try: + os.makedirs(os.path.dirname(pipe_name), exist_ok=True) + except OSError as e: + raise VMwareError("Could not create the VMware pipe directory: {}".format(e)) + return pipe_name + + def _set_serial_console(self): + """ + Configures the first serial port to allow a serial console connection. + """ + + pipe_name = self._get_pipe_name() + serial_port = {"serial0.present": "TRUE", + "serial0.fileType": "pipe", + "serial0.fileName": pipe_name, + "serial0.pipe.endPoint": "server"} + self._vmx_pairs.update(serial_port) + + def _start_remote_console(self): + """ + Starts remote console support for this VM. + """ + + # starts the Telnet to pipe thread + pipe_name = self._get_pipe_name() + if sys.platform.startswith("win"): + try: + self._serial_pipe = open(pipe_name, "a+b") + except OSError as e: + raise VMwareError("Could not open the pipe {}: {}".format(pipe_name, e)) + try: + self._telnet_server_thread = TelnetServer(self.name, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._manager.port_manager.console_host, self._console) + except OSError as e: + raise VMwareError("Unable to create Telnet server: {}".format(e)) + self._telnet_server_thread.start() + else: + try: + self._serial_pipe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self._serial_pipe.connect(pipe_name) + except OSError as e: + raise VMwareError("Could not connect to the pipe {}: {}".format(pipe_name, e)) + try: + self._telnet_server_thread = TelnetServer(self.name, self._serial_pipe, self._manager.port_manager.console_host, self._console) + except OSError as e: + raise VMwareError("Unable to create Telnet server: {}".format(e)) + self._telnet_server_thread.start() + + def _stop_remote_console(self): + """ + Stops remote console support for this VM. + """ + + if self._telnet_server_thread: + if self._telnet_server_thread.is_alive(): + self._telnet_server_thread.stop() + self._telnet_server_thread.join(timeout=3) + if self._telnet_server_thread.is_alive(): + log.warn("Serial pipe thread is still alive!") + self._telnet_server_thread = None + + if self._serial_pipe: + if sys.platform.startswith("win"): + win32file.CloseHandle(msvcrt.get_osfhandle(self._serial_pipe.fileno())) + else: + self._serial_pipe.close() + self._serial_pipe = None diff --git a/gns3server/modules/virtualbox/telnet_server.py b/gns3server/utils/telnet_server.py similarity index 99% rename from gns3server/modules/virtualbox/telnet_server.py rename to gns3server/utils/telnet_server.py index bfbee71b..a5d3ef4d 100644 --- a/gns3server/modules/virtualbox/telnet_server.py +++ b/gns3server/utils/telnet_server.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# TODO: port TelnetServer to asyncio import sys import time