mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
VirtualBox support refactoring.
This commit is contained in:
parent
77c583ca39
commit
569a68a486
@ -24,7 +24,6 @@ from ..dynamips_error import DynamipsError
|
|||||||
from .router import Router
|
from .router import Router
|
||||||
from ..adapters.c7200_io_2fe import C7200_IO_2FE
|
from ..adapters.c7200_io_2fe import C7200_IO_2FE
|
||||||
from ..adapters.c7200_io_ge_e import C7200_IO_GE_E
|
from ..adapters.c7200_io_ge_e import C7200_IO_GE_E
|
||||||
from pkg_resources import parse_version
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -24,6 +24,7 @@ import os
|
|||||||
import socket
|
import socket
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from pkg_resources import parse_version
|
||||||
from gns3server.modules import IModule
|
from gns3server.modules import IModule
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
from .virtualbox_vm import VirtualBoxVM
|
from .virtualbox_vm import VirtualBoxVM
|
||||||
@ -106,25 +107,30 @@ class VirtualBox(IModule):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
|
import pywintypes
|
||||||
import win32com.client
|
import win32com.client
|
||||||
|
|
||||||
if win32com.client.gencache.is_readonly is True:
|
|
||||||
# dynamically generate the cache
|
|
||||||
# http://www.py2exe.org/index.cgi/IncludingTypelibs
|
|
||||||
# http://www.py2exe.org/index.cgi/UsingEnsureDispatch
|
|
||||||
win32com.client.gencache.is_readonly = False
|
|
||||||
#win32com.client.gencache.Rebuild()
|
|
||||||
win32com.client.gencache.GetGeneratePath()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if win32com.client.gencache.is_readonly is True:
|
||||||
|
# dynamically generate the cache
|
||||||
|
# http://www.py2exe.org/index.cgi/IncludingTypelibs
|
||||||
|
# http://www.py2exe.org/index.cgi/UsingEnsureDispatch
|
||||||
|
win32com.client.gencache.is_readonly = False
|
||||||
|
#win32com.client.gencache.Rebuild()
|
||||||
|
win32com.client.gencache.GetGeneratePath()
|
||||||
|
|
||||||
win32com.client.gencache.EnsureDispatch("VirtualBox.VirtualBox")
|
win32com.client.gencache.EnsureDispatch("VirtualBox.VirtualBox")
|
||||||
except:
|
except pywintypes.com_error:
|
||||||
raise VirtualBoxError("VirtualBox is not installed.")
|
raise VirtualBoxError("VirtualBox is not installed.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .vboxapi_py3 import VirtualBoxManager
|
from .vboxapi_py3 import VirtualBoxManager
|
||||||
self._vboxmanager = VirtualBoxManager(None, None)
|
self._vboxmanager = VirtualBoxManager(None, None)
|
||||||
|
vbox_major_version, vbox_minor_version, _ = self._vboxmanager.vbox.version.split('.')
|
||||||
|
if parse_version("{}.{}".format(vbox_major_version, vbox_minor_version)) <= parse_version("4.1"):
|
||||||
|
raise VirtualBoxError("VirtualBox version must be >= 4.2")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self._vboxmanager = None
|
||||||
raise VirtualBoxError("Could not initialize the VirtualBox Manager: {}".format(e))
|
raise VirtualBoxError("Could not initialize the VirtualBox Manager: {}".format(e))
|
||||||
|
|
||||||
log.info("VirtualBox Manager has successful started: version is {} r{}".format(self._vboxmanager.vbox.version,
|
log.info("VirtualBox Manager has successful started: version is {} r{}".format(self._vboxmanager.vbox.version,
|
||||||
@ -139,7 +145,11 @@ class VirtualBox(IModule):
|
|||||||
|
|
||||||
self._vboxwrapper = VboxWrapperClient(self._vboxwrapper_path, self._tempdir, "127.0.0.1")
|
self._vboxwrapper = VboxWrapperClient(self._vboxwrapper_path, self._tempdir, "127.0.0.1")
|
||||||
#self._vboxwrapper.connect()
|
#self._vboxwrapper.connect()
|
||||||
self._vboxwrapper.start()
|
try:
|
||||||
|
self._vboxwrapper.start()
|
||||||
|
except VirtualBoxError:
|
||||||
|
self._vboxwrapper = None
|
||||||
|
raise
|
||||||
|
|
||||||
def stop(self, signum=None):
|
def stop(self, signum=None):
|
||||||
"""
|
"""
|
||||||
|
@ -26,6 +26,7 @@ import tempfile
|
|||||||
import socket
|
import socket
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from pkg_resources import parse_version
|
||||||
from ..attic import wait_socket_is_ready
|
from ..attic import wait_socket_is_ready
|
||||||
from .virtualbox_error import VirtualBoxError
|
from .virtualbox_error import VirtualBoxError
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ class VboxWrapperClient(object):
|
|||||||
self._command = []
|
self._command = []
|
||||||
self._process = None
|
self._process = None
|
||||||
self._working_dir = working_dir
|
self._working_dir = working_dir
|
||||||
self._stdout_file = ""
|
self._stderr_file = ""
|
||||||
self._started = False
|
self._started = False
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
@ -139,19 +140,31 @@ class VboxWrapperClient(object):
|
|||||||
try:
|
try:
|
||||||
log.info("starting VirtualBox wrapper: {}".format(self._command))
|
log.info("starting VirtualBox wrapper: {}".format(self._command))
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as fd:
|
with tempfile.NamedTemporaryFile(delete=False) as fd:
|
||||||
self._stdout_file = fd.name
|
with open(os.devnull, "w") as null:
|
||||||
log.info("VirtualBox wrapper process logging to {}".format(fd.name))
|
self._stderr_file = fd.name
|
||||||
self._process = subprocess.Popen(self._command,
|
log.info("VirtualBox wrapper process logging to {}".format(fd.name))
|
||||||
stdout=fd,
|
self._process = subprocess.Popen(self._command,
|
||||||
stderr=subprocess.STDOUT,
|
stdout=null,
|
||||||
cwd=self._working_dir)
|
stderr=fd,
|
||||||
|
cwd=self._working_dir)
|
||||||
log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
|
log.info("VirtualBox wrapper started PID={}".format(self._process.pid))
|
||||||
|
|
||||||
|
time.sleep(0.1) # give some time for vboxwrapper to start
|
||||||
|
if self._process.poll() is not None:
|
||||||
|
raise VirtualBoxError("Could not start VirtualBox wrapper: {}".format(self.read_stderr()))
|
||||||
|
|
||||||
self.wait_for_vboxwrapper(self._host, self._port)
|
self.wait_for_vboxwrapper(self._host, self._port)
|
||||||
self.connect()
|
self.connect()
|
||||||
self._started = True
|
self._started = True
|
||||||
|
|
||||||
|
version = self.send('vboxwrapper version')[0]
|
||||||
|
if parse_version(version) < parse_version("0.9.2"):
|
||||||
|
self.stop()
|
||||||
|
raise VirtualBoxError("VirtualBox wrapper version must be >= 0.9.1")
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.error("could not start VirtualBox wrapper: {}".format(e))
|
log.error("could not start VirtualBox wrapper: {}".format(e))
|
||||||
raise VirtualBoxError("could not start VirtualBox wrapper: {}".format(e))
|
raise VirtualBoxError("Could not start VirtualBox wrapper: {}".format(e))
|
||||||
|
|
||||||
def wait_for_vboxwrapper(self, host, port):
|
def wait_for_vboxwrapper(self, host, port):
|
||||||
"""
|
"""
|
||||||
@ -198,26 +211,26 @@ class VboxWrapperClient(object):
|
|||||||
if self._process.poll() is None:
|
if self._process.poll() is None:
|
||||||
log.warn("VirtualBox wrapper process {} is still running".format(self._process.pid))
|
log.warn("VirtualBox wrapper process {} is still running".format(self._process.pid))
|
||||||
|
|
||||||
if self._stdout_file and os.access(self._stdout_file, os.W_OK):
|
if self._stderr_file and os.access(self._stderr_file, os.W_OK):
|
||||||
try:
|
try:
|
||||||
os.remove(self._stdout_file)
|
os.remove(self._stderr_file)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.warning("could not delete temporary VirtualBox wrapper log file: {}".format(e))
|
log.warning("could not delete temporary VirtualBox wrapper log file: {}".format(e))
|
||||||
self._started = False
|
self._started = False
|
||||||
|
|
||||||
def read_stdout(self):
|
def read_stderr(self):
|
||||||
"""
|
"""
|
||||||
Reads the standard output of the VirtualBox wrapper process.
|
Reads the standard error output of the VirtualBox wrapper process.
|
||||||
Only use when the process has been stopped or has crashed.
|
Only use when the process has been stopped or has crashed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output = ""
|
output = ""
|
||||||
if self._stdout_file and os.access(self._stdout_file, os.R_OK):
|
if self._stderr_file and os.access(self._stderr_file, os.R_OK):
|
||||||
try:
|
try:
|
||||||
with open(self._stdout_file, errors="replace") as file:
|
with open(self._stderr_file, errors="replace") as file:
|
||||||
output = file.read()
|
output = file.read()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.warn("could not read {}: {}".format(self._stdout_file, e))
|
log.warn("could not read {}: {}".format(self._stderr_file, e))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
|
529
gns3server/modules/virtualbox/virtualbox_controller.py
Normal file
529
gns3server/modules/virtualbox/virtualbox_controller.py
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 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/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Controls VirtualBox using the VBox API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
import msvcrt
|
||||||
|
import win32file
|
||||||
|
|
||||||
|
from .virtualbox_error import VirtualBoxError
|
||||||
|
from .pipe_proxy import PipeProxy
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualBoxController(object):
|
||||||
|
|
||||||
|
def __init__(self, vmname, vboxmanager, host):
|
||||||
|
|
||||||
|
self._host = host
|
||||||
|
self._machine = None
|
||||||
|
self._session = None
|
||||||
|
self._vboxmanager = vboxmanager
|
||||||
|
self._maximum_adapters = 0
|
||||||
|
self._serial_pipe_thread = None
|
||||||
|
self._serial_pipe = None
|
||||||
|
|
||||||
|
self._vmname = vmname
|
||||||
|
self._console = 0
|
||||||
|
self._adapters = []
|
||||||
|
self._headless = False
|
||||||
|
self._adapter_type = "Automatic"
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._machine = self._vboxmanager.vbox.findMachine(self._vmname)
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
|
||||||
|
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vmname(self):
|
||||||
|
|
||||||
|
return self._vmname
|
||||||
|
|
||||||
|
@vmname.setter
|
||||||
|
def vmname(self, new_vmname):
|
||||||
|
|
||||||
|
self._vmname = new_vmname
|
||||||
|
try:
|
||||||
|
self._machine = self._vboxmanager.vbox.findMachine(new_vmname)
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
|
||||||
|
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def console(self):
|
||||||
|
|
||||||
|
return self._console
|
||||||
|
|
||||||
|
@console.setter
|
||||||
|
def console(self, console):
|
||||||
|
|
||||||
|
self._console = console
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headless(self):
|
||||||
|
|
||||||
|
return self._headless
|
||||||
|
|
||||||
|
@headless.setter
|
||||||
|
def headless(self, headless):
|
||||||
|
|
||||||
|
self._headless = headless
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adapters(self):
|
||||||
|
|
||||||
|
return self._adapters
|
||||||
|
|
||||||
|
@adapters.setter
|
||||||
|
def adapters(self, adapters):
|
||||||
|
|
||||||
|
self._adapters = adapters
|
||||||
|
|
||||||
|
@property
|
||||||
|
def adapter_type(self):
|
||||||
|
|
||||||
|
return self._adapter_type
|
||||||
|
|
||||||
|
@adapter_type.setter
|
||||||
|
def adapter_type(self, adapter_type):
|
||||||
|
|
||||||
|
self._adapter_type = adapter_type
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
|
||||||
|
if self._machine.state == self._vboxmanager.constants.MachineState_Paused:
|
||||||
|
self.resume()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._get_session()
|
||||||
|
self._set_network_options()
|
||||||
|
self._set_console_options()
|
||||||
|
|
||||||
|
progress = self._launch_vm_process()
|
||||||
|
log.info("VM is starting with {}% completed".format(progress.percent))
|
||||||
|
if progress.percent != 100:
|
||||||
|
# This will happen if you attempt to start VirtualBox with unloaded "vboxdrv" module.
|
||||||
|
# or have too little RAM or damaged vHDD, or connected to non-existent network.
|
||||||
|
# We must unlock machine, otherwise it locks the VirtualBox Manager GUI. (on Linux hosts)
|
||||||
|
self._unlock_machine()
|
||||||
|
raise VirtualBoxError("Unable to start the VM (failed at {}%)".format(progress.percent))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._machine.setGuestPropertyValue("NameInGNS3", self._name)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
self._serial_pipe_thread = PipeProxy(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console)
|
||||||
|
#self._serial_pipe_thread.setDaemon(True)
|
||||||
|
self._serial_pipe_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))
|
||||||
|
self._serial_pipe_thread = PipeProxy(self._vmname, self._serial_pipe, self._host, self._console)
|
||||||
|
#self._serial_pipe_thread.setDaemon(True)
|
||||||
|
self._serial_pipe_thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
|
||||||
|
if self._serial_pipe_thread:
|
||||||
|
self._serial_pipe_thread.stop()
|
||||||
|
self._serial_pipe_thread.join(1)
|
||||||
|
if self._serial_pipe_thread.isAlive():
|
||||||
|
log.warn("Serial pire thread is still alive!")
|
||||||
|
self._serial_pipe_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._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
||||||
|
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
||||||
|
try:
|
||||||
|
if sys.platform.startswith('win') and "VBOX_INSTALL_PATH" in os.environ:
|
||||||
|
# work around VirtualBox bug #9239
|
||||||
|
vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
|
||||||
|
command = '"{}" controlvm "{}" poweroff'.format(vboxmanage_path, self._vmname)
|
||||||
|
subprocess.call(command, timeout=3)
|
||||||
|
else:
|
||||||
|
progress = self._session.console.powerDown()
|
||||||
|
# wait for VM to actually go down
|
||||||
|
progress.waitForCompletion(3000)
|
||||||
|
log.info("VM is stopping with {}% completed".format(self.vmname, progress.percent))
|
||||||
|
|
||||||
|
self._lock_machine()
|
||||||
|
for adapter_id in range(0, len(self._adapters)):
|
||||||
|
self._disable_adapter(adapter_id, disable=True)
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
self._unlock_machine()
|
||||||
|
except Exception as e:
|
||||||
|
# Do not crash "vboxwrapper", if stopping VM fails.
|
||||||
|
# But return True anyway, so VM state in GNS3 can become "stopped"
|
||||||
|
# This can happen, if user manually kills VBox VM.
|
||||||
|
log.warn("could not stop VM for {}: {}".format(self._vmname, e))
|
||||||
|
return
|
||||||
|
|
||||||
|
def suspend(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._session.console.pause()
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._session.console.reset()
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._session.console.resume()
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
def _get_session(self):
|
||||||
|
|
||||||
|
log.debug("getting session for {}".format(self._vmname))
|
||||||
|
try:
|
||||||
|
self._session = self._vboxmanager.mgr.getSessionObject(self._vboxmanager.vbox)
|
||||||
|
except Exception as e:
|
||||||
|
# fails on heavily loaded hosts...
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
def _set_network_options(self):
|
||||||
|
|
||||||
|
log.debug("setting network options for {}".format(self._vmname))
|
||||||
|
|
||||||
|
self._lock_machine()
|
||||||
|
|
||||||
|
first_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
|
||||||
|
try:
|
||||||
|
first_adapter = self._session.machine.getNetworkAdapter(0)
|
||||||
|
first_adapter_type = first_adapter.adapterType
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
#raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
for adapter_id in range(0, len(self._adapters)):
|
||||||
|
try:
|
||||||
|
# VirtualBox starts counting from 0
|
||||||
|
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
||||||
|
vbox_adapter_type = adapter.adapterType
|
||||||
|
|
||||||
|
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C970A
|
||||||
|
if self._adapter_type == "PCNet-FAST III (Am79C973)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C973
|
||||||
|
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
|
||||||
|
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82543GC
|
||||||
|
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82545EM
|
||||||
|
if self._adapter_type == "Paravirtualized Network (virtio-net)":
|
||||||
|
vbox_adapter_type = self._vboxmanager.constants.NetworkAdapterType_Virtio
|
||||||
|
if self._adapter_type == "Automatic": # "Auto-guess, based on first NIC"
|
||||||
|
vbox_adapter_type = first_adapter_type
|
||||||
|
|
||||||
|
adapter.adapterType = vbox_adapter_type
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
nio = self._adapters[adapter_id].get_nio(0)
|
||||||
|
if nio:
|
||||||
|
log.debug("setting UDP params on adapter {}".format(adapter_id))
|
||||||
|
try:
|
||||||
|
adapter.enabled = True
|
||||||
|
adapter.cableConnected = True
|
||||||
|
adapter.traceEnabled = False
|
||||||
|
# Temporary hack around VBox-UDP patch limitation: inability to use DNS
|
||||||
|
if nio.rhost == 'localhost':
|
||||||
|
rhost = '127.0.0.1'
|
||||||
|
else:
|
||||||
|
rhost = nio.rhost
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
|
||||||
|
adapter.genericDriver = "UDPTunnel"
|
||||||
|
adapter.setProperty("sport", str(nio.lport))
|
||||||
|
adapter.setProperty("dest", rhost)
|
||||||
|
adapter.setProperty("dport", str(nio.rport))
|
||||||
|
except Exception as e:
|
||||||
|
# usually due to COM Error: "The object is not ready"
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
if nio.capturing:
|
||||||
|
self._enable_capture(adapter, nio.pcap_output_file)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# shutting down unused adapters...
|
||||||
|
try:
|
||||||
|
adapter.enabled = True
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
||||||
|
adapter.cableConnected = False
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
#for adapter_id in range(len(self._ethernet_adapters), self._maximum_adapters):
|
||||||
|
# log.debug("disabling remaining adapter {}".format(adapter_id))
|
||||||
|
# self._disable_adapter(adapter_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
self._unlock_machine()
|
||||||
|
|
||||||
|
def _disable_adapter(self, adapter_id, disable=True):
|
||||||
|
|
||||||
|
log.debug("disabling network adapter for {}".format(self._vmname))
|
||||||
|
# this command is retried several times, because it fails more often...
|
||||||
|
retries = 6
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not disable network adapter after 4 retries: {}".format(last_exception))
|
||||||
|
try:
|
||||||
|
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
||||||
|
adapter.traceEnabled = False
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
||||||
|
if disable:
|
||||||
|
adapter.enabled = False
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# usually due to COM Error: "The object is not ready"
|
||||||
|
log.warn("cannot disable network adapter for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _enable_capture(self, adapter, output_file):
|
||||||
|
|
||||||
|
log.debug("enabling capture for {}".format(self._vmname))
|
||||||
|
# this command is retried several times, because it fails more often...
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not enable packet capture after 4 retries: {}".format(last_exception))
|
||||||
|
try:
|
||||||
|
adapter.traceEnabled = True
|
||||||
|
adapter.traceFile = output_file
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.warn("cannot enable packet capture for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(0.75)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def create_udp(self, adapter_id, sport, daddr, dport):
|
||||||
|
|
||||||
|
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
||||||
|
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
||||||
|
# the machine is being executed
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not create an UDP tunnel after 4 retries :{}".format(last_exception))
|
||||||
|
try:
|
||||||
|
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
||||||
|
adapter.cableConnected = True
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
|
||||||
|
adapter.genericDriver = "UDPTunnel"
|
||||||
|
adapter.setProperty("sport", str(sport))
|
||||||
|
adapter.setProperty("dest", daddr)
|
||||||
|
adapter.setProperty("dport", str(dport))
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# usually due to COM Error: "The object is not ready"
|
||||||
|
log.warn("cannot create UDP tunnel for {}: {}".format(self._vmname, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(0.75)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def delete_udp(self, adapter_id):
|
||||||
|
|
||||||
|
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
||||||
|
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
||||||
|
# the machine is being executed
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not delete an UDP tunnel after 4 retries :{}".format(last_exception))
|
||||||
|
try:
|
||||||
|
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
||||||
|
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
||||||
|
adapter.cableConnected = False
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# usually due to COM Error: "The object is not ready"
|
||||||
|
log.debug("cannot delete UDP tunnel for {}: {}".format(self._vmname, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(0.75)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _get_pipe_name(self):
|
||||||
|
|
||||||
|
p = re.compile('\s+', re.UNICODE)
|
||||||
|
pipe_name = p.sub("_", self._vmname)
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
|
||||||
|
else:
|
||||||
|
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
|
||||||
|
return pipe_name
|
||||||
|
|
||||||
|
def _set_console_options(self):
|
||||||
|
"""
|
||||||
|
# Example to manually set serial parameters using Python
|
||||||
|
|
||||||
|
from vboxapi import VirtualBoxManager
|
||||||
|
mgr = VirtualBoxManager(None, None)
|
||||||
|
mach = mgr.vbox.findMachine("My VM")
|
||||||
|
session = mgr.mgr.getSessionObject(mgr.vbox)
|
||||||
|
mach.lockMachine(session, 1)
|
||||||
|
mach2=session.machine
|
||||||
|
serial_port = mach2.getSerialPort(0)
|
||||||
|
serial_port.enabled = True
|
||||||
|
serial_port.path = "/tmp/test_pipe"
|
||||||
|
serial_port.hostMode = 1
|
||||||
|
serial_port.server = True
|
||||||
|
session.unlockMachine()
|
||||||
|
"""
|
||||||
|
|
||||||
|
log.info("setting console options for {}".format(self._vmname))
|
||||||
|
|
||||||
|
self._lock_machine()
|
||||||
|
pipe_name = self._get_pipe_name()
|
||||||
|
|
||||||
|
try:
|
||||||
|
serial_port = self._session.machine.getSerialPort(0)
|
||||||
|
serial_port.enabled = True
|
||||||
|
serial_port.path = pipe_name
|
||||||
|
serial_port.hostMode = 1
|
||||||
|
serial_port.server = True
|
||||||
|
self._session.machine.saveSettings()
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
self._unlock_machine()
|
||||||
|
|
||||||
|
def _launch_vm_process(self):
|
||||||
|
|
||||||
|
log.debug("launching VM {}".format(self._vmname))
|
||||||
|
# this command is retried several times, because it fails more often...
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not launch the VM after 4 retries: {}".format(last_exception))
|
||||||
|
try:
|
||||||
|
if self._headless:
|
||||||
|
mode = "headless"
|
||||||
|
else:
|
||||||
|
mode = "gui"
|
||||||
|
log.info("starting {} in {} mode".format(self._vmname, mode))
|
||||||
|
progress = self._machine.launchVMProcess(self._session, mode, "")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
# This will usually happen if you try to start the same VM twice,
|
||||||
|
# but may happen on loaded hosts too...
|
||||||
|
log.warn("cannot launch VM {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(0.6)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
progress.waitForCompletion(-1)
|
||||||
|
except Exception as e:
|
||||||
|
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
||||||
|
|
||||||
|
return progress
|
||||||
|
|
||||||
|
def _lock_machine(self):
|
||||||
|
|
||||||
|
log.debug("locking machine for {}".format(self._vmname))
|
||||||
|
# this command is retried several times, because it fails more often...
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not lock the machine after 4 retries: {}".format(last_exception))
|
||||||
|
try:
|
||||||
|
self._machine.lockMachine(self._session, 1)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.warn("cannot lock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
||||||
|
last_exception = e
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def _unlock_machine(self):
|
||||||
|
|
||||||
|
log.debug("unlocking machine for {}".format(self._vmname))
|
||||||
|
# this command is retried several times, because it fails more often...
|
||||||
|
retries = 4
|
||||||
|
last_exception = None
|
||||||
|
for retry in range(retries):
|
||||||
|
if retry == (retries - 1):
|
||||||
|
raise VirtualBoxError("Could not unlock the machine after 4 retries: {}".format(last_exception))
|
||||||
|
try:
|
||||||
|
self._session.unlockMachine()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.warn("cannot unlock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
||||||
|
time.sleep(1)
|
||||||
|
last_exception = e
|
||||||
|
continue
|
@ -19,24 +19,14 @@
|
|||||||
VirtualBox VM instance.
|
VirtualBox VM instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .pipe_proxy import PipeProxy
|
|
||||||
from .virtualbox_error import VirtualBoxError
|
from .virtualbox_error import VirtualBoxError
|
||||||
|
from .virtualbox_controller import VirtualBoxController
|
||||||
from .adapters.ethernet_adapter import EthernetAdapter
|
from .adapters.ethernet_adapter import EthernetAdapter
|
||||||
from ..attic import find_unused_port
|
from ..attic import find_unused_port
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
import msvcrt
|
|
||||||
import win32file
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -97,16 +87,6 @@ class VirtualBoxVM(object):
|
|||||||
self._console_start_port_range = console_start_port_range
|
self._console_start_port_range = console_start_port_range
|
||||||
self._console_end_port_range = console_end_port_range
|
self._console_end_port_range = console_end_port_range
|
||||||
|
|
||||||
# Telnet to pipe mini-server
|
|
||||||
self._serial_pipe_thread = None
|
|
||||||
self._serial_pipe = None
|
|
||||||
|
|
||||||
# VirtualBox API variables
|
|
||||||
self._machine = None
|
|
||||||
self._session = None
|
|
||||||
self._vboxmanager = vboxmanager
|
|
||||||
self._maximum_adapters = 0
|
|
||||||
|
|
||||||
# VirtualBox settings
|
# VirtualBox settings
|
||||||
self._console = console
|
self._console = console
|
||||||
self._ethernet_adapters = []
|
self._ethernet_adapters = []
|
||||||
@ -140,19 +120,11 @@ class VirtualBoxVM(object):
|
|||||||
self._vboxwrapper.send('vbox create vbox "{}"'.format(self._name))
|
self._vboxwrapper.send('vbox create vbox "{}"'.format(self._name))
|
||||||
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
|
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
|
||||||
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
|
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
|
||||||
self._vboxwrapper.send('vbox setattr "{}" console_support True'.format(self._name))
|
|
||||||
self._vboxwrapper.send('vbox setattr "{}" console_telnet_server True'.format(self._name))
|
|
||||||
else:
|
else:
|
||||||
try:
|
self._vboxcontroller = VirtualBoxController(self._vmname, vboxmanager, self._host)
|
||||||
self._machine = self._vboxmanager.vbox.findMachine(self._vmname)
|
self._vboxcontroller.console = self._console
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
|
|
||||||
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
|
|
||||||
|
|
||||||
self.adapters = 2
|
|
||||||
|
|
||||||
|
self.adapters = 2 # creates 2 adapters by default
|
||||||
log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}] has been created".format(name=self._name,
|
||||||
id=self._id))
|
id=self._id))
|
||||||
|
|
||||||
@ -274,6 +246,8 @@ class VirtualBoxVM(object):
|
|||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
|
self._vboxwrapper.send('vbox setattr "{}" console {}'.format(self._name, self._console))
|
||||||
|
else:
|
||||||
|
self._vboxcontroller.console = console
|
||||||
|
|
||||||
log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
@ -344,10 +318,14 @@ class VirtualBoxVM(object):
|
|||||||
if headless:
|
if headless:
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" headless_mode True'.format(self._name))
|
self._vboxwrapper.send('vbox setattr "{}" headless_mode True'.format(self._name))
|
||||||
|
else:
|
||||||
|
self._vboxcontroller.headless = True
|
||||||
log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id))
|
log.info("VirtualBox VM {name} [id={id}] has enabled the headless mode".format(name=self._name, id=self._id))
|
||||||
else:
|
else:
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" headless_mode False'.format(self._name))
|
self._vboxwrapper.send('vbox setattr "{}" headless_mode False'.format(self._name))
|
||||||
|
else:
|
||||||
|
self._vboxcontroller.headless = False
|
||||||
log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id))
|
log.info("VirtualBox VM {name} [id={id}] has disabled the headless mode".format(name=self._name, id=self._id))
|
||||||
self._headless = headless
|
self._headless = headless
|
||||||
|
|
||||||
@ -372,13 +350,7 @@ class VirtualBoxVM(object):
|
|||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
|
self._vboxwrapper.send('vbox setattr "{}" image "{}"'.format(self._name, vmname))
|
||||||
else:
|
else:
|
||||||
try:
|
self._vboxcontroller.vmname = vmname
|
||||||
self._machine = self._vboxmanager.vbox.findMachine(vmname)
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
# The maximum support network cards depends on the Chipset (PIIX3 or ICH9)
|
|
||||||
self._maximum_adapters = self._vboxmanager.vbox.systemProperties.getMaxNetworkAdapters(self._machine.chipsetType)
|
|
||||||
|
|
||||||
log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname))
|
log.info("VirtualBox VM {name} [id={id}] has set the VM name to {vmname}".format(name=self._name, id=self._id, vmname=vmname))
|
||||||
self._vmname = vmname
|
self._vmname = vmname
|
||||||
@ -407,6 +379,8 @@ class VirtualBoxVM(object):
|
|||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" nics {}'.format(self._name, len(self._ethernet_adapters)))
|
self._vboxwrapper.send('vbox setattr "{}" nics {}'.format(self._name, len(self._ethernet_adapters)))
|
||||||
|
else:
|
||||||
|
self._vboxcontroller.adapters = self._ethernet_adapters
|
||||||
|
|
||||||
log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}]: number of Ethernet adapters changed to {adapters}".format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
@ -434,6 +408,8 @@ class VirtualBoxVM(object):
|
|||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox setattr "{}" netcard "{}"'.format(self._name, adapter_type))
|
self._vboxwrapper.send('vbox setattr "{}" netcard "{}"'.format(self._name, adapter_type))
|
||||||
|
else:
|
||||||
|
self._vboxcontroller.adapter_type = adapter_type
|
||||||
|
|
||||||
log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}]: adapter type changed to {adapter_type}".format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
@ -445,54 +421,9 @@ class VirtualBoxVM(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
status = int(self._vboxwrapper.send('vbox status "{}"'.format(self._name))[0])
|
|
||||||
if status == 6: # paused
|
|
||||||
self.resume()
|
|
||||||
return
|
|
||||||
self._vboxwrapper.send('vbox start "{}"'.format(self._name))
|
self._vboxwrapper.send('vbox start "{}"'.format(self._name))
|
||||||
else:
|
else:
|
||||||
|
self._vboxcontroller.start()
|
||||||
if self._machine.state == self._vboxmanager.constants.MachineState_Paused:
|
|
||||||
self.resume()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._get_session()
|
|
||||||
self._set_network_options()
|
|
||||||
self._set_console_options()
|
|
||||||
|
|
||||||
progress = self._launch_vm_process()
|
|
||||||
log.info("VM is starting with {}% completed".format(progress.percent))
|
|
||||||
if progress.percent != 100:
|
|
||||||
# This will happen if you attempt to start VirtualBox with unloaded "vboxdrv" module.
|
|
||||||
# or have too little RAM or damaged vHDD, or connected to non-existent network.
|
|
||||||
# We must unlock machine, otherwise it locks the VirtualBox Manager GUI. (on Linux hosts)
|
|
||||||
self._unlock_machine()
|
|
||||||
raise VirtualBoxError("Unable to start the VM (failed at {}%)".format(progress.percent))
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._machine.setGuestPropertyValue("NameInGNS3", self._name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 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))
|
|
||||||
self._serial_pipe_thread = PipeProxy(self._vmname, msvcrt.get_osfhandle(self._serial_pipe.fileno()), self._host, self._console)
|
|
||||||
#self._serial_pipe_thread.setDaemon(True)
|
|
||||||
self._serial_pipe_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))
|
|
||||||
self._serial_pipe_thread = PipeProxy(self._vmname, self._serial_pipe, self._host, self._console)
|
|
||||||
#self._serial_pipe_thread.setDaemon(True)
|
|
||||||
self._serial_pipe_thread.start()
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
@ -500,48 +431,13 @@ class VirtualBoxVM(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox stop "{}"'.format(self._name))
|
try:
|
||||||
|
self._vboxwrapper.send('vbox stop "{}"'.format(self._name))
|
||||||
|
except VirtualBoxError:
|
||||||
|
# probably lost the connection
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
|
self._vboxcontroller.stop()
|
||||||
if self._serial_pipe_thread:
|
|
||||||
self._serial_pipe_thread.stop()
|
|
||||||
self._serial_pipe_thread.join(1)
|
|
||||||
if self._serial_pipe_thread.isAlive():
|
|
||||||
log.warn("Serial pire thread is still alive!")
|
|
||||||
self._serial_pipe_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._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
|
||||||
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
|
||||||
try:
|
|
||||||
if sys.platform.startswith('win') and "VBOX_INSTALL_PATH" in os.environ:
|
|
||||||
# work around VirtualBox bug #9239
|
|
||||||
vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe")
|
|
||||||
command = '"{}" controlvm "{}" poweroff'.format(vboxmanage_path, self._vmname)
|
|
||||||
subprocess.call(command, timeout=3)
|
|
||||||
else:
|
|
||||||
progress = self._session.console.powerDown()
|
|
||||||
# wait for VM to actually go down
|
|
||||||
progress.waitForCompletion(3000)
|
|
||||||
log.info("VM is stopping with {}% completed".format(self.vmname, progress.percent))
|
|
||||||
|
|
||||||
self._lock_machine()
|
|
||||||
for adapter_id in range(0, len(self._ethernet_adapters)):
|
|
||||||
self._disable_adapter(adapter_id, disable=True)
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
self._unlock_machine()
|
|
||||||
except Exception as e:
|
|
||||||
# Do not crash "vboxwrapper", if stopping VM fails.
|
|
||||||
# But return True anyway, so VM state in GNS3 can become "stopped"
|
|
||||||
# This can happen, if user manually kills VBox VM.
|
|
||||||
log.warn("could not stop VM for {}: {}".format(self._vmname, e))
|
|
||||||
return
|
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self):
|
||||||
"""
|
"""
|
||||||
@ -551,10 +447,7 @@ class VirtualBoxVM(object):
|
|||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox suspend "{}"'.format(self._name))
|
self._vboxwrapper.send('vbox suspend "{}"'.format(self._name))
|
||||||
else:
|
else:
|
||||||
try:
|
self._vboxcontroller.suspend()
|
||||||
self._session.console.pause()
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
"""
|
"""
|
||||||
@ -564,11 +457,7 @@ class VirtualBoxVM(object):
|
|||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox reset "{}"'.format(self._name))
|
self._vboxwrapper.send('vbox reset "{}"'.format(self._name))
|
||||||
else:
|
else:
|
||||||
try:
|
self._vboxcontroller.reload()
|
||||||
progress = self._session.console.reset()
|
|
||||||
progress.waitForCompletion(-1)
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
"""
|
"""
|
||||||
@ -578,10 +467,7 @@ class VirtualBoxVM(object):
|
|||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox resume "{}"'.format(self._name))
|
self._vboxwrapper.send('vbox resume "{}"'.format(self._name))
|
||||||
else:
|
else:
|
||||||
try:
|
self._vboxcontroller.resume()
|
||||||
self._session.console.resume()
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
def port_add_nio_binding(self, adapter_id, nio):
|
def port_add_nio_binding(self, adapter_id, nio):
|
||||||
"""
|
"""
|
||||||
@ -599,12 +485,12 @@ class VirtualBoxVM(object):
|
|||||||
|
|
||||||
if self._vboxwrapper:
|
if self._vboxwrapper:
|
||||||
self._vboxwrapper.send('vbox create_udp "{}" {} {} {} {}'.format(self._name,
|
self._vboxwrapper.send('vbox create_udp "{}" {} {} {} {}'.format(self._name,
|
||||||
adapter_id,
|
adapter_id,
|
||||||
nio.lport,
|
nio.lport,
|
||||||
nio.rhost,
|
nio.rhost,
|
||||||
nio.rport))
|
nio.rport))
|
||||||
else:
|
else:
|
||||||
self._create_udp(adapter_id, nio.lport, nio.rhost, nio.rport)
|
self._vboxcontroller.create_udp(adapter_id, nio.lport, nio.rhost, nio.rport)
|
||||||
|
|
||||||
adapter.add_nio(0, nio)
|
adapter.add_nio(0, nio)
|
||||||
log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}]: {nio} added to adapter {adapter_id}".format(name=self._name,
|
||||||
@ -631,7 +517,7 @@ class VirtualBoxVM(object):
|
|||||||
self._vboxwrapper.send('vbox delete_udp "{}" {}'.format(self._name,
|
self._vboxwrapper.send('vbox delete_udp "{}" {}'.format(self._name,
|
||||||
adapter_id))
|
adapter_id))
|
||||||
else:
|
else:
|
||||||
self._delete_udp(adapter_id)
|
self._vboxcontroller.delete_udp(adapter_id)
|
||||||
|
|
||||||
nio = adapter.get_nio(0)
|
nio = adapter.get_nio(0)
|
||||||
adapter.remove_nio(0)
|
adapter.remove_nio(0)
|
||||||
@ -700,287 +586,3 @@ class VirtualBoxVM(object):
|
|||||||
log.info("VirtualBox VM {name} [id={id}]: stopping packet capture on adapter {adapter_id}".format(name=self._name,
|
log.info("VirtualBox VM {name} [id={id}]: stopping packet capture on adapter {adapter_id}".format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
adapter_id=adapter_id))
|
adapter_id=adapter_id))
|
||||||
|
|
||||||
def _get_session(self):
|
|
||||||
|
|
||||||
log.debug("getting session for {}".format(self._vmname))
|
|
||||||
try:
|
|
||||||
self._session = self._vboxmanager.mgr.getSessionObject(self._vboxmanager.vbox)
|
|
||||||
except Exception as e:
|
|
||||||
# fails on heavily loaded hosts...
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
def _set_network_options(self):
|
|
||||||
|
|
||||||
log.debug("setting network options for {}".format(self._vmname))
|
|
||||||
|
|
||||||
self._lock_machine()
|
|
||||||
|
|
||||||
first_adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
|
|
||||||
try:
|
|
||||||
first_adapter = self._session.machine.getNetworkAdapter(0)
|
|
||||||
first_adapter_type = first_adapter.adapterType
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
#raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
for adapter_id in range(0, len(self._ethernet_adapters)):
|
|
||||||
try:
|
|
||||||
# VirtualBox starts counting from 0
|
|
||||||
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
|
||||||
adapter_type = adapter.adapterType
|
|
||||||
|
|
||||||
if self._adapter_type == "PCnet-PCI II (Am79C970A)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C970A
|
|
||||||
if self._adapter_type == "PCNet-FAST III (Am79C973)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Am79C973
|
|
||||||
if self._adapter_type == "Intel PRO/1000 MT Desktop (82540EM)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82540EM
|
|
||||||
if self._adapter_type == "Intel PRO/1000 T Server (82543GC)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82543GC
|
|
||||||
if self._adapter_type == "Intel PRO/1000 MT Server (82545EM)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_I82545EM
|
|
||||||
if self._adapter_type == "Paravirtualized Network (virtio-net)":
|
|
||||||
adapter_type = self._vboxmanager.constants.NetworkAdapterType_Virtio
|
|
||||||
if self._adapter_type == "Automatic": # "Auto-guess, based on first NIC"
|
|
||||||
adapter_type = first_adapter_type
|
|
||||||
|
|
||||||
adapter.adapterType = adapter_type
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
nio = self._ethernet_adapters[adapter_id].get_nio(0)
|
|
||||||
if nio:
|
|
||||||
log.debug("setting UDP params on adapter {}".format(adapter_id))
|
|
||||||
try:
|
|
||||||
adapter.enabled = True
|
|
||||||
adapter.cableConnected = True
|
|
||||||
adapter.traceEnabled = False
|
|
||||||
# Temporary hack around VBox-UDP patch limitation: inability to use DNS
|
|
||||||
if nio.rhost == 'localhost':
|
|
||||||
rhost = '127.0.0.1'
|
|
||||||
else:
|
|
||||||
rhost = nio.rhost
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
|
|
||||||
adapter.genericDriver = "UDPTunnel"
|
|
||||||
adapter.setProperty("sport", str(nio.lport))
|
|
||||||
adapter.setProperty("dest", rhost)
|
|
||||||
adapter.setProperty("dport", str(nio.rport))
|
|
||||||
except Exception as e:
|
|
||||||
# usually due to COM Error: "The object is not ready"
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
if nio.capturing:
|
|
||||||
self._enable_capture(adapter, nio.pcap_output_file)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# shutting down unused adapters...
|
|
||||||
try:
|
|
||||||
adapter.enabled = True
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
|
||||||
adapter.cableConnected = False
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
#for adapter_id in range(len(self._ethernet_adapters), self._maximum_adapters):
|
|
||||||
# log.debug("disabling remaining adapter {}".format(adapter_id))
|
|
||||||
# self._disable_adapter(adapter_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
self._unlock_machine()
|
|
||||||
|
|
||||||
def _disable_adapter(self, adapter_id, disable=True):
|
|
||||||
|
|
||||||
log.debug("disabling network adapter for {}".format(self._vmname))
|
|
||||||
# this command is retried several times, because it fails more often...
|
|
||||||
retries = 6
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not disable network adapter after 4 retries: {}".format(last_exception))
|
|
||||||
try:
|
|
||||||
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
|
||||||
adapter.traceEnabled = False
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
|
||||||
if disable:
|
|
||||||
adapter.enabled = False
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
# usually due to COM Error: "The object is not ready"
|
|
||||||
log.warn("cannot disable network adapter for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _enable_capture(self, adapter, output_file):
|
|
||||||
|
|
||||||
log.debug("enabling capture for {}".format(self._vmname))
|
|
||||||
# this command is retried several times, because it fails more often...
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not enable packet capture after 4 retries: {}".format(last_exception))
|
|
||||||
try:
|
|
||||||
adapter.traceEnabled = True
|
|
||||||
adapter.traceFile = output_file
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
log.warn("cannot enable packet capture for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(0.75)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _create_udp(self, adapter_id, sport, daddr, dport):
|
|
||||||
|
|
||||||
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
|
||||||
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
|
||||||
# the machine is being executed
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not create an UDP tunnel after 4 retries :{}".format(last_exception))
|
|
||||||
try:
|
|
||||||
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
|
||||||
adapter.cableConnected = True
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Generic
|
|
||||||
adapter.genericDriver = "UDPTunnel"
|
|
||||||
adapter.setProperty("sport", str(sport))
|
|
||||||
adapter.setProperty("dest", daddr)
|
|
||||||
adapter.setProperty("dport", str(dport))
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
# usually due to COM Error: "The object is not ready"
|
|
||||||
log.warn("cannot create UDP tunnel for {}: {}".format(self._vmname, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(0.75)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _delete_udp(self, adapter_id):
|
|
||||||
|
|
||||||
if self._machine.state >= self._vboxmanager.constants.MachineState_FirstOnline and \
|
|
||||||
self._machine.state <= self._vboxmanager.constants.MachineState_LastOnline:
|
|
||||||
# the machine is being executed
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not delete an UDP tunnel after 4 retries :{}".format(last_exception))
|
|
||||||
try:
|
|
||||||
adapter = self._session.machine.getNetworkAdapter(adapter_id)
|
|
||||||
adapter.attachmentType = self._vboxmanager.constants.NetworkAttachmentType_Null
|
|
||||||
adapter.cableConnected = False
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
# usually due to COM Error: "The object is not ready"
|
|
||||||
log.debug("cannot delete UDP tunnel for {}: {}".format(self._vmname, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(0.75)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _get_pipe_name(self):
|
|
||||||
|
|
||||||
p = re.compile('\s+', re.UNICODE)
|
|
||||||
pipe_name = p.sub("_", self._vmname)
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
pipe_name = r"\\.\pipe\VBOX\{}".format(pipe_name)
|
|
||||||
else:
|
|
||||||
pipe_name = os.path.join(tempfile.gettempdir(), "pipe_{}".format(pipe_name))
|
|
||||||
return pipe_name
|
|
||||||
|
|
||||||
def _set_console_options(self):
|
|
||||||
|
|
||||||
log.info("setting console options for {}".format(self.vmname))
|
|
||||||
|
|
||||||
self._lock_machine()
|
|
||||||
pipe_name = self._get_pipe_name()
|
|
||||||
|
|
||||||
try:
|
|
||||||
serial_port = self._session.machine.getSerialPort(0)
|
|
||||||
serial_port.enabled = True
|
|
||||||
serial_port.path = pipe_name
|
|
||||||
serial_port.hostMode = 1
|
|
||||||
serial_port.server = True
|
|
||||||
self._session.machine.saveSettings()
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
self._unlock_machine()
|
|
||||||
|
|
||||||
def _launch_vm_process(self):
|
|
||||||
|
|
||||||
log.debug("launching VM {}".format(self._vmname))
|
|
||||||
# this command is retried several times, because it fails more often...
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not launch the VM after 4 retries: {}".format(last_exception))
|
|
||||||
try:
|
|
||||||
if self._headless:
|
|
||||||
mode = "headless"
|
|
||||||
else:
|
|
||||||
mode = "gui"
|
|
||||||
log.info("starting {} in {} mode".format(self._vmname, mode))
|
|
||||||
progress = self._machine.launchVMProcess(self._session, mode, "")
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
# This will usually happen if you try to start the same VM twice,
|
|
||||||
# but may happen on loaded hosts too...
|
|
||||||
log.warn("cannot launch VM {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(0.6)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
progress.waitForCompletion(-1)
|
|
||||||
except Exception as e:
|
|
||||||
raise VirtualBoxError("VirtualBox error: {}".format(e))
|
|
||||||
|
|
||||||
return progress
|
|
||||||
|
|
||||||
def _lock_machine(self):
|
|
||||||
|
|
||||||
log.debug("locking machine for {}".format(self._vmname))
|
|
||||||
# this command is retried several times, because it fails more often...
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not lock the machine after 4 retries: {}".format(last_exception))
|
|
||||||
try:
|
|
||||||
self._machine.lockMachine(self._session, 1)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
log.warn("cannot lock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
|
||||||
last_exception = e
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _unlock_machine(self):
|
|
||||||
|
|
||||||
log.debug("unlocking machine for {}".format(self._vmname))
|
|
||||||
# this command is retried several times, because it fails more often...
|
|
||||||
retries = 4
|
|
||||||
last_exception = None
|
|
||||||
for retry in range(retries):
|
|
||||||
if retry == (retries - 1):
|
|
||||||
raise VirtualBoxError("Could not unlock the machine after 4 retries: {}".format(last_exception))
|
|
||||||
try:
|
|
||||||
self._session.unlockMachine()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
log.warn("cannot unlock the machine for {}, retrying {}: {}".format(self._vmname, retry + 1, e))
|
|
||||||
time.sleep(1)
|
|
||||||
last_exception = e
|
|
||||||
continue
|
|
||||||
|
@ -23,5 +23,5 @@
|
|||||||
# or negative for a release candidate or beta (after the base version
|
# or negative for a release candidate or beta (after the base version
|
||||||
# number has been incremented)
|
# number has been incremented)
|
||||||
|
|
||||||
__version__ = "1.0beta2.dev1"
|
__version__ = "1.0beta2.dev2"
|
||||||
__version_info__ = (1, 0, 0, -99)
|
__version_info__ = (1, 0, 0, -99)
|
||||||
|
Loading…
Reference in New Issue
Block a user