381 lines
13 KiB
Python
Executable File
381 lines
13 KiB
Python
Executable File
# Copyright 2016 Cloudbase Solutions Srl
|
|
# All Rights Reserved.
|
|
|
|
"""Python bindings for the VDDK vixdisklib library. Superseded by the
|
|
openvixdisklib, which avoids the proprietary VDDK SDK.
|
|
|
|
This module is used by the integration tests in order to cross-check the
|
|
openvixdisklib library. ctypes layouts follow `.vddk/vixDiskLib.h`.
|
|
"""
|
|
|
|
import contextlib
|
|
import ctypes
|
|
import logging
|
|
import os
|
|
import traceback
|
|
|
|
from tests.integration import vix_disklib_errors
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
VIXDISKLIB_VERSION_MAJOR = 8
|
|
VIXDISKLIB_VERSION_MINOR = 0
|
|
|
|
VIXDISKLIB_SECTOR_SIZE = 512
|
|
|
|
VIXDISKLIB_CRED_UID = 1
|
|
|
|
VIXDISKLIB_FLAG_OPEN_UNBUFFERED = 1
|
|
VIXDISKLIB_FLAG_OPEN_SINGLE_LINK = 2
|
|
VIXDISKLIB_FLAG_OPEN_READ_ONLY = 4
|
|
|
|
# NBD compression flags
|
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_ZLIB = 16
|
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ = 32
|
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ = 64
|
|
|
|
VIX_SUPPORTED_COMPATIBILITY_MODES = ["6.0", "6.5", "6.7", "7.0", "8.0"]
|
|
|
|
|
|
class VixDiskLibUidPasswdCreds(ctypes.Structure):
|
|
_fields_ = [
|
|
("userName", ctypes.c_char_p),
|
|
("password", ctypes.c_char_p),
|
|
]
|
|
|
|
|
|
class VixDiskLibSessionIdCreds(ctypes.Structure):
|
|
_fields_ = [
|
|
("cookie", ctypes.c_char_p),
|
|
("userName", ctypes.c_char_p),
|
|
("key", ctypes.c_char_p),
|
|
]
|
|
|
|
|
|
class VixDiskLibCreds(ctypes.Union):
|
|
_fields_ = [
|
|
("uid", VixDiskLibUidPasswdCreds),
|
|
("sessionId", VixDiskLibSessionIdCreds),
|
|
]
|
|
|
|
|
|
class VixDiskLibConnectParams(ctypes.Structure):
|
|
_fields_ = [
|
|
("vmxSpec", ctypes.c_char_p),
|
|
("serverName", ctypes.c_char_p),
|
|
("thumbPrint", ctypes.c_char_p),
|
|
# Note: this is 32bit on Windows
|
|
("privateUse", ctypes.c_longlong),
|
|
("credType", ctypes.c_uint32),
|
|
("creds", VixDiskLibCreds),
|
|
("port", ctypes.c_uint32),
|
|
("nfcHostPort", ctypes.c_uint32),
|
|
("vimApiVer", ctypes.c_char_p),
|
|
]
|
|
|
|
|
|
class VixDiskLibConnection(ctypes.Structure):
|
|
_fields_ = []
|
|
|
|
|
|
def get_buffer(size):
|
|
return ctypes.create_string_buffer(size)
|
|
|
|
|
|
class VixDiskLibHandle(object):
|
|
"""Class which acts as a proxy for vixDiskLib-related operations:"""
|
|
|
|
def __init__(self, config_path=None, vixdisklib_compatibility_version=None):
|
|
self._vix_disklib = ctypes.cdll.LoadLibrary(self.get_vix_disklib_name())
|
|
self._setup_vix_disklib()
|
|
|
|
if config_path:
|
|
config_path = config_path.encode()
|
|
|
|
target_versions = VIX_SUPPORTED_COMPATIBILITY_MODES
|
|
if vixdisklib_compatibility_version:
|
|
target_versions = [vixdisklib_compatibility_version]
|
|
LOG.debug("vixDiskLib versions targeted: %s", target_versions)
|
|
|
|
# NOTE: iterate through all versions and try to initialize using each:
|
|
version_used = None
|
|
for version in reversed(target_versions):
|
|
major_ver = None
|
|
minor_ver = None
|
|
try:
|
|
major_ver, minor_ver = version.split(".")
|
|
major_ver = int(major_ver)
|
|
minor_ver = int(minor_ver)
|
|
except ValueError as ex:
|
|
raise ValueError(
|
|
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
|
|
"compatibility mode must be of the form "
|
|
"'$major.$minor'" % version
|
|
) from ex
|
|
|
|
try:
|
|
self._check_err(
|
|
self._vix_disklib.VixDiskLib_InitEx(
|
|
major_ver, minor_ver, None, None, None, None, config_path
|
|
)
|
|
)
|
|
version_used = version
|
|
break
|
|
except Exception:
|
|
LOG.debug(
|
|
"Failed to initialize vixDiskLib using compatibility "
|
|
"version '%s'. Trying next version. Error trace: %s",
|
|
version,
|
|
traceback.format_exc(),
|
|
)
|
|
|
|
if not version_used:
|
|
raise Exception(
|
|
"Could not initialize vixDiskLib with any of the following "
|
|
"versions: %s" % target_versions
|
|
)
|
|
|
|
LOG.info(
|
|
"Successfully initialized vixDiskLib with target version '%s'", version_used
|
|
)
|
|
|
|
@classmethod
|
|
def get_vix_disklib_name(cls):
|
|
vixDiskLibName = None
|
|
if os.name == "nt":
|
|
vixDiskLibName = "vixDiskLib.dll"
|
|
else:
|
|
vixDiskLibName = "libvixDiskLib.so"
|
|
return vixDiskLibName
|
|
|
|
def _setup_vix_disklib(self):
|
|
self._vix_disklib.VixDiskLib_InitEx.argtypes = [
|
|
ctypes.c_uint32,
|
|
ctypes.c_uint32,
|
|
ctypes.c_void_p,
|
|
ctypes.c_void_p,
|
|
ctypes.c_void_p,
|
|
ctypes.c_char_p,
|
|
ctypes.c_char_p,
|
|
]
|
|
self._vix_disklib.VixDiskLib_InitEx.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_GetErrorText.argtypes = [
|
|
ctypes.c_uint64,
|
|
ctypes.c_char_p,
|
|
]
|
|
self._vix_disklib.VixDiskLib_GetErrorText.restype = ctypes.c_void_p
|
|
|
|
self._vix_disklib.VixDiskLib_FreeErrorText.arg_types = [ctypes.c_char_p]
|
|
self._vix_disklib.VixDiskLib_FreeErrorText.restype = None
|
|
|
|
self._vix_disklib.VixDiskLib_ListTransportModes.argtypes = []
|
|
self._vix_disklib.VixDiskLib_ListTransportModes.restype = ctypes.c_char_p
|
|
|
|
self._vix_disklib.VixDiskLib_GetTransportMode.argtypes = [ctypes.c_void_p]
|
|
self._vix_disklib.VixDiskLib_GetTransportMode.restype = ctypes.c_char_p
|
|
|
|
self._vix_disklib.VixDiskLib_ConnectEx.argtypes = [
|
|
ctypes.POINTER(VixDiskLibConnectParams),
|
|
ctypes.c_char,
|
|
ctypes.c_char_p,
|
|
ctypes.c_char_p,
|
|
ctypes.POINTER(ctypes.c_void_p),
|
|
]
|
|
self._vix_disklib.VixDiskLib_ConnectEx.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Open.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.c_char_p,
|
|
ctypes.c_uint32,
|
|
ctypes.POINTER(ctypes.c_void_p),
|
|
]
|
|
self._vix_disklib.VixDiskLib_Open.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Read.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.c_uint64,
|
|
ctypes.c_uint64,
|
|
ctypes.c_char_p,
|
|
]
|
|
self._vix_disklib.VixDiskLib_Read.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Write.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.c_uint64,
|
|
ctypes.c_uint64,
|
|
ctypes.c_char_p,
|
|
]
|
|
self._vix_disklib.VixDiskLib_Write.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_GetMetadataKeys.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.c_char_p,
|
|
ctypes.c_uint64,
|
|
ctypes.POINTER(ctypes.c_uint64),
|
|
]
|
|
self._vix_disklib.VixDiskLib_GetMetadataKeys.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_ReadMetadata.argtypes = [
|
|
ctypes.c_void_p,
|
|
ctypes.c_char_p,
|
|
ctypes.c_char_p,
|
|
ctypes.c_uint64,
|
|
ctypes.POINTER(ctypes.c_uint64),
|
|
]
|
|
self._vix_disklib.VixDiskLib_ReadMetadata.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Close.argtypes = [ctypes.c_void_p]
|
|
self._vix_disklib.VixDiskLib_Close.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Disconnect.argtypes = [ctypes.c_void_p]
|
|
self._vix_disklib.VixDiskLib_Disconnect.restype = ctypes.c_uint64
|
|
|
|
self._vix_disklib.VixDiskLib_Exit.argtypes = []
|
|
self._vix_disklib.VixDiskLib_Exit.restype = None
|
|
|
|
def _check_err(self, err, allowed_values=[vix_disklib_errors.VIX_OK]):
|
|
if err not in allowed_values:
|
|
err_msg = self._vix_disklib.VixDiskLib_GetErrorText(err, None)
|
|
err_msg_copy = str(ctypes.cast(err_msg, ctypes.c_char_p).value.decode())
|
|
self._vix_disklib.VixDiskLib_FreeErrorText(
|
|
ctypes.cast(err_msg, ctypes.c_char_p)
|
|
)
|
|
|
|
msg = None
|
|
if err == vix_disklib_errors.VIX_E_OUT_OF_MEMORY:
|
|
msg = (
|
|
"The ESXi host performing the CBT export ran out of RAM. "
|
|
"The host performing the export is automatically chosen "
|
|
"by vCenter, so enough RAM to run the export is required "
|
|
"on all hosts. To force the export from the specific host "
|
|
"the VM is on, create a Coriolis endpoint with the DNS "
|
|
"name/IP address of that host."
|
|
)
|
|
if err == vix_disklib_errors.VIX_E_HOST_NETWORK_CONN_REFUSED:
|
|
msg = (
|
|
"The ESXi host performing the CBT export refused "
|
|
"connection. The host is chosen automatically by vCenter, "
|
|
"so please ensure that the Coriolis deployment "
|
|
"can dial TCP/902 on all of the ESXi hosts of a vSphere, "
|
|
"and that DNS name resolution and firewalls are setup to "
|
|
"facilitate this. Alternatively, try connecting Coriolis "
|
|
"directly to the specific ESXi host which is running the "
|
|
"VM(s) to be migrated by creating a Coriolis endpoint "
|
|
"using the DNS name/IP address of the host itself."
|
|
)
|
|
|
|
if err == vix_disklib_errors.VIX_E_CANNOT_CONNECT_TO_HOST:
|
|
msg = (
|
|
"Coriolis lost connection to the ESXi host performing the "
|
|
"CBT export. If the Coriolis Endpoint connects to a "
|
|
"vSphere host, please try connecting Coriolis to the ESXi "
|
|
"host directly. If problem persists, try re-enabling CBT "
|
|
"on the VM, or moving it to another ESXi host."
|
|
)
|
|
|
|
err_msg = err_msg_copy
|
|
if msg:
|
|
LOG.debug("Original vixDiskLib error message: %s", err_msg_copy)
|
|
err_msg = msg
|
|
|
|
raise Exception(err_msg)
|
|
|
|
def get_transport_modes(self):
|
|
transport_modes = self._vix_disklib.VixDiskLib_ListTransportModes()
|
|
return transport_modes.decode().split(":")
|
|
|
|
def get_transport_mode(self, disk_handle):
|
|
t_mode = self._vix_disklib.VixDiskLib_GetTransportMode(disk_handle)
|
|
return t_mode.decode()
|
|
|
|
@contextlib.contextmanager
|
|
def connect(
|
|
self,
|
|
server_name,
|
|
thumbprint,
|
|
username,
|
|
password,
|
|
vmx_spec=None,
|
|
snapshot_ref=None,
|
|
read_only=True,
|
|
transport_modes=None,
|
|
port=443,
|
|
):
|
|
LOG.debug("Connecting VixDiskLib: %s", server_name)
|
|
|
|
connectParams = VixDiskLibConnectParams()
|
|
|
|
connectParams.serverName = server_name.encode()
|
|
if vmx_spec:
|
|
connectParams.vmxSpec = vmx_spec.encode()
|
|
if thumbprint:
|
|
connectParams.thumbPrint = thumbprint.encode()
|
|
|
|
connectParams.credType = VIXDISKLIB_CRED_UID
|
|
connectParams.creds.uid.userName = username.encode()
|
|
connectParams.creds.uid.password = password.encode()
|
|
connectParams.port = port
|
|
|
|
if transport_modes:
|
|
transport_modes = transport_modes.encode()
|
|
|
|
if snapshot_ref:
|
|
snapshot_ref = snapshot_ref.encode()
|
|
|
|
conn = ctypes.c_void_p()
|
|
self._check_err(
|
|
self._vix_disklib.VixDiskLib_ConnectEx(
|
|
connectParams,
|
|
read_only,
|
|
snapshot_ref,
|
|
transport_modes,
|
|
ctypes.byref(conn),
|
|
)
|
|
)
|
|
try:
|
|
yield conn
|
|
finally:
|
|
self.disconnect(conn)
|
|
|
|
@contextlib.contextmanager
|
|
def open(self, conn, disk_path, flags=VIXDISKLIB_FLAG_OPEN_READ_ONLY):
|
|
LOG.debug("Openning VixDiskLib disk: %s", disk_path)
|
|
|
|
disk_handle = ctypes.c_void_p()
|
|
self._check_err(
|
|
self._vix_disklib.VixDiskLib_Open(
|
|
conn, disk_path.encode(), flags, ctypes.byref(disk_handle)
|
|
)
|
|
)
|
|
try:
|
|
yield disk_handle
|
|
finally:
|
|
self.close(disk_handle)
|
|
|
|
def read(self, disk_handle, start_sector, num_sectors, buf):
|
|
self._check_err(
|
|
self._vix_disklib.VixDiskLib_Read(
|
|
disk_handle, start_sector, num_sectors, buf
|
|
)
|
|
)
|
|
|
|
def write(self, disk_handle, start_sector, num_sectors, buf):
|
|
"""Write ``num_sectors`` from ``buf`` starting at ``start_sector``."""
|
|
self._check_err(
|
|
self._vix_disklib.VixDiskLib_Write(
|
|
disk_handle, start_sector, num_sectors, buf
|
|
)
|
|
)
|
|
|
|
def close(self, disk_handle):
|
|
LOG.debug("Closing VixDiskLib disk handle: %s", disk_handle)
|
|
self._check_err(self._vix_disklib.VixDiskLib_Close(disk_handle))
|
|
|
|
def disconnect(self, conn):
|
|
LOG.debug("Disconnecting VixDiskLib")
|
|
self._check_err(self._vix_disklib.VixDiskLib_Disconnect(conn))
|
|
|
|
def exit(self):
|
|
self._vix_disklib.VixDiskLib_Exit()
|