Skip to content

Commit

Permalink
Support for serial console for Virtual BOX and VMware using asyncio
Browse files Browse the repository at this point in the history
Ref #747
  • Loading branch information
julien-duponchelle committed Nov 7, 2016
1 parent 3c5cbeb commit 553e137
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 565 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,4 +1,5 @@
*.py[cod]
__pycache__

#py.test
.cache
Expand Down
13 changes: 8 additions & 5 deletions gns3server/compute/project.py
Expand Up @@ -24,7 +24,7 @@
import zipfile
import json

from uuid import UUID
from uuid import UUID, uuid4
from .port_manager import PortManager
from .notification_manager import NotificationManager
from ..config import Config
Expand All @@ -49,10 +49,13 @@ class Project:
def __init__(self, name=None, project_id=None, path=None):

self._name = name
try:
UUID(project_id, version=4)
except ValueError:
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id))
if project_id:
try:
UUID(project_id, version=4)
except ValueError:
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id))
else:
project_id = str(uuid4())
self._id = project_id

self._nodes = set()
Expand Down
77 changes: 18 additions & 59 deletions gns3server/compute/virtualbox/virtualbox_vm.py
Expand Up @@ -30,12 +30,13 @@
import xml.etree.ElementTree as ET

from gns3server.utils import parse_version
from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation, locked_coroutine
from .virtualbox_error import VirtualBoxError
from ..nios.nio_udp import NIOUDP
from ..adapters.ethernet_adapter import EthernetAdapter
from ..base_node import BaseNode
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.asyncio.serial import asyncio_open_serial
from gns3server.utils.asyncio import locked_coroutine
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.nios.nio_udp import NIOUDP
from gns3server.compute.adapters.ethernet_adapter import EthernetAdapter
from gns3server.compute.base_node import BaseNode

if sys.platform.startswith('win'):
import msvcrt
Expand All @@ -53,12 +54,11 @@ class VirtualBoxVM(BaseNode):

def __init__(self, name, node_id, project, manager, vmname, linked_clone=False, console=None, adapters=0):

super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone)
super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type="telnet")

self._maximum_adapters = 8
self._system_properties = {}
self._telnet_server_thread = None
self._serial_pipe = None
self._telnet_server = None
self._local_udp_tunnels = {}

# VirtualBox settings
Expand All @@ -81,6 +81,7 @@ def __json__(self):
json = {"name": self.name,
"node_id": self.id,
"console": self.console,
"console_type": self.console_type,
"project_id": self.project.id,
"vmname": self.vmname,
"headless": self.headless,
Expand Down Expand Up @@ -243,16 +244,7 @@ def start(self):
self._local_udp_tunnels[adapter_number][1],
nio)

if self._console is not None:
try:
# wait for VirtualBox to create the pipe file.
if sys.platform.startswith("win"):
yield from wait_for_named_pipe_creation(self._get_pipe_name())
else:
yield from wait_for_file_creation(self._get_pipe_name())
except asyncio.TimeoutError:
raise VirtualBoxError('Pipe file "{}" for remote console has not been created by VirtualBox'.format(self._get_pipe_name()))
self._start_remote_console()
yield from self._start_console()

if (yield from self.check_hw_virtualization()):
self._hw_virtualization = True
Expand Down Expand Up @@ -874,54 +866,21 @@ def _create_linked_clone(self):

os.makedirs(os.path.join(self.working_dir, self._vmname), exist_ok=True)

def _start_remote_console(self):
@asyncio.coroutine
def _start_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 VirtualBoxError("Could not open the pipe {}: {}".format(pipe_name, e))
try:
self._telnet_server_thread = TelnetServer(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._manager.port_manager.console_host, self._console)
except OSError as e:
raise VirtualBoxError("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 VirtualBoxError("Could not connect to the pipe {}: {}".format(pipe_name, e))
try:
self._telnet_server_thread = TelnetServer(self._vmname, self._serial_pipe, self._manager.port_manager.console_host, self._console)
except OSError as e:
raise VirtualBoxError("Unable to create Telnet server: {}".format(e))
self._telnet_server_thread.start()
pipe = yield from asyncio_open_serial(self._get_pipe_name())
server = AsyncioTelnetServer(reader=pipe, writer=pipe, binary=True, echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, '127.0.0.1', self.console)

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
if self._telnet_server:
self._telnet_server.close()

@asyncio.coroutine
def adapter_add_nio_binding(self, adapter_number, nio):
Expand Down
72 changes: 15 additions & 57 deletions gns3server/compute/vmware/vmware_vm.py
Expand Up @@ -25,18 +25,16 @@
import asyncio
import tempfile

from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.interfaces import interfaces
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation, locked_coroutine
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.asyncio.serial import asyncio_open_serial
from gns3server.utils.asyncio import locked_coroutine
from collections import OrderedDict
from .vmware_error import VMwareError
from ..nios.nio_udp import NIOUDP
from ..adapters.ethernet_adapter import EthernetAdapter
from ..base_node import BaseNode

if sys.platform.startswith('win'):
import msvcrt
import win32file

import logging
log = logging.getLogger(__name__)
Expand All @@ -53,8 +51,7 @@ def __init__(self, name, node_id, project, manager, vmx_path, linked_clone=False
super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone)

self._vmx_pairs = OrderedDict()
self._telnet_server_thread = None
self._serial_pipe = None
self._telnet_server = None
self._vmnets = []
self._maximum_adapters = 10
self._started = False
Expand Down Expand Up @@ -82,6 +79,7 @@ def __json__(self):
json = {"name": self.name,
"node_id": self.id,
"console": self.console,
"console_type": self.console_type,
"project_id": self.project.id,
"vmx_path": self.vmx_path,
"headless": self.headless,
Expand Down Expand Up @@ -440,15 +438,7 @@ def start(self):
if nio:
yield from self._add_ubridge_connection(nio, adapter_number)

# if self._console is not None:
# try:
# if sys.platform.startswith("win"):
# yield from wait_for_named_pipe_creation(self._get_pipe_name())
# else:
# yield from wait_for_file_creation(self._get_pipe_name()) # wait for VMware to create the pipe file.
# except asyncio.TimeoutError:
# raise VMwareError('Pipe file "{}" for remote console has not been created by VMware'.format(self._get_pipe_name()))
# self._start_remote_console()
yield from self._start_console()
except VMwareError:
yield from self.stop()
raise
Expand Down Expand Up @@ -797,57 +787,25 @@ def _set_serial_console(self):
serial_port = {"serial0.present": "TRUE",
"serial0.filetype": "pipe",
"serial0.filename": pipe_name,
"serial0.pipe.endpoint": "server"}
"serial0.pipe.endpoint": "server",
"serial0.startconnected": "TRUE"}
self._vmx_pairs.update(serial_port)

def _start_remote_console(self):
@asyncio.coroutine
def _start_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()
pipe = yield from asyncio_open_serial(self._get_pipe_name())
server = AsyncioTelnetServer(reader=pipe, writer=pipe, binary=True, echo=True)
self._telnet_server = yield from asyncio.start_server(server.run, '127.0.0.1', self.console)

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
if self._telnet_server:
self._telnet_server.close()

@asyncio.coroutine
def start_capture(self, adapter_number, output_file):
Expand Down
8 changes: 8 additions & 0 deletions gns3server/schemas/virtualbox.py
Expand Up @@ -66,6 +66,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"ram": {
"description": "Amount of RAM",
"minimum": 0,
Expand Down Expand Up @@ -152,6 +156,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"ram": {
"description": "Amount of RAM",
"minimum": 0,
Expand Down
8 changes: 8 additions & 0 deletions gns3server/schemas/vmware.py
Expand Up @@ -48,6 +48,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"headless": {
"description": "Headless mode",
"type": "boolean"
Expand Down Expand Up @@ -143,6 +147,10 @@
"maximum": 65535,
"type": "integer"
},
"console_type": {
"description": "Console type",
"enum": ["telnet"]
},
"linked_clone": {
"description": "Whether the VM is a linked clone or not",
"type": "boolean"
Expand Down

0 comments on commit 553e137

Please sign in to comment.