Add openvixdisklib as an open NBD replacement for VMware VDDK.
VDDK is no longer publicly distributed, so this library reverse-engineers the vSphere NFC path and exposes ConnectEx, Open, Read, and Write without the proprietary SDK. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Executable
+324
@@ -0,0 +1,324 @@
|
||||
# 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()
|
||||
Reference in New Issue
Block a user