mirror of
https://github.com/GNS3/gns3-server.git
synced 2025-01-18 15:33:49 +02:00
Serial console implementation for VMware VMs.
This commit is contained in:
parent
c782784bf5
commit
98e01ff21d
@ -29,11 +29,11 @@ import socket
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from pkg_resources import parse_version
|
from pkg_resources import parse_version
|
||||||
|
from gns3server.utils.telnet_server import TelnetServer
|
||||||
from .virtualbox_error import VirtualBoxError
|
from .virtualbox_error import VirtualBoxError
|
||||||
from ..nios.nio_udp import NIOUDP
|
from ..nios.nio_udp import NIOUDP
|
||||||
from ..nios.nio_nat import NIONAT
|
from ..nios.nio_nat import NIONAT
|
||||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||||
from .telnet_server import TelnetServer # TODO: port TelnetServer to asyncio
|
|
||||||
from ..base_vm import BaseVM
|
from ..base_vm import BaseVM
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
|
@ -21,25 +21,30 @@ VMware VM instance.
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import configparser
|
import configparser
|
||||||
import shutil
|
import shutil
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from gns3server.utils.asyncio import wait_for_process_termination
|
from gns3server.utils.asyncio import wait_for_process_termination
|
||||||
from gns3server.utils.asyncio import monitor_process
|
from gns3server.utils.asyncio import monitor_process
|
||||||
|
from gns3server.utils.telnet_server import TelnetServer
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .vmware_error import VMwareError
|
from .vmware_error import VMwareError
|
||||||
from ..nios.nio_udp import NIOUDP
|
from ..nios.nio_udp import NIOUDP
|
||||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||||
from ..base_vm import BaseVM
|
from ..base_vm import BaseVM
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import msvcrt
|
||||||
|
import win32file
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class VMwareVM(BaseVM):
|
class VMwareVM(BaseVM):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -54,8 +59,11 @@ class VMwareVM(BaseVM):
|
|||||||
self._vmx_pairs = OrderedDict()
|
self._vmx_pairs = OrderedDict()
|
||||||
self._ubridge_process = None
|
self._ubridge_process = None
|
||||||
self._ubridge_stdout_file = ""
|
self._ubridge_stdout_file = ""
|
||||||
|
self._telnet_server_thread = None
|
||||||
|
self._serial_pipe = None
|
||||||
self._vmnets = []
|
self._vmnets = []
|
||||||
self._maximum_adapters = 10
|
self._maximum_adapters = 10
|
||||||
|
self._started = False
|
||||||
self._closed = False
|
self._closed = False
|
||||||
|
|
||||||
# VMware VM settings
|
# VMware VM settings
|
||||||
@ -109,11 +117,6 @@ class VMwareVM(BaseVM):
|
|||||||
|
|
||||||
def _set_network_options(self):
|
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
|
# first do some sanity checks
|
||||||
for adapter_number in range(0, self._adapters):
|
for adapter_number in range(0, self._adapters):
|
||||||
connected = "ethernet{}.startConnected".format(adapter_number)
|
connected = "ethernet{}.startConnected".format(adapter_number)
|
||||||
@ -179,7 +182,6 @@ 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.manager.write_vmx_file(self._vmx_path, self._vmx_pairs)
|
|
||||||
self._update_ubridge_config()
|
self._update_ubridge_config()
|
||||||
|
|
||||||
def _update_ubridge_config(self):
|
def _update_ubridge_config(self):
|
||||||
@ -242,7 +244,7 @@ class VMwareVM(BaseVM):
|
|||||||
:returns: path to uBridge
|
: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":
|
if path == "ubridge":
|
||||||
path = shutil.which("ubridge")
|
path = shutil.which("ubridge")
|
||||||
return path
|
return path
|
||||||
@ -333,13 +335,26 @@ class VMwareVM(BaseVM):
|
|||||||
if not ubridge_path or not os.path.isfile(ubridge_path):
|
if not ubridge_path or not os.path.isfile(ubridge_path):
|
||||||
raise VMwareError("ubridge is necessary to start a VMware VM")
|
raise VMwareError("ubridge is necessary to start a VMware VM")
|
||||||
|
|
||||||
self._set_network_options()
|
try:
|
||||||
yield from self._start_ubridge()
|
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:
|
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")
|
||||||
|
|
||||||
|
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))
|
log.info("VMware VM '{name}' [{id}] started".format(name=self.name, id=self.id))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -348,6 +363,7 @@ class VMwareVM(BaseVM):
|
|||||||
Stops this VMware VM.
|
Stops this VMware VM.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self._stop_remote_console()
|
||||||
if self.is_ubridge_running():
|
if self.is_ubridge_running():
|
||||||
self._terminate_process_ubridge()
|
self._terminate_process_ubridge()
|
||||||
try:
|
try:
|
||||||
@ -361,9 +377,8 @@ class VMwareVM(BaseVM):
|
|||||||
try:
|
try:
|
||||||
yield from self._control_vm("stop")
|
yield from self._control_vm("stop")
|
||||||
finally:
|
finally:
|
||||||
|
self._started = False
|
||||||
self._vmnets.clear()
|
self._vmnets.clear()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path)
|
self._vmx_pairs = self.manager.parse_vmware_file(self._vmx_path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@ -518,10 +533,11 @@ class VMwareVM(BaseVM):
|
|||||||
|
|
||||||
if enable_remote_console:
|
if enable_remote_console:
|
||||||
log.info("VMware VM '{name}' [{id}] has enabled the console".format(name=self.name, id=self.id))
|
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:
|
else:
|
||||||
log.info("VMware VM '{name}' [{id}] has disabled the console".format(name=self.name, id=self.id))
|
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
|
self._enable_remote_console = enable_remote_console
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -647,3 +663,81 @@ class VMwareVM(BaseVM):
|
|||||||
nio=nio,
|
nio=nio,
|
||||||
adapter_number=adapter_number))
|
adapter_number=adapter_number))
|
||||||
return nio
|
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
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# TODO: port TelnetServer to asyncio
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
Loading…
Reference in New Issue
Block a user