Files
OpenVixDiskLib/tests/integration/vixdisklib.py
T
2026-09-09 08:43:42 +00:00

383 lines
13 KiB
Python

# 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:
"""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 RuntimeError(
"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=None):
if allowed_values is None:
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 RuntimeError(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()