Fix code formatting - tox -e fmt
This commit is contained in:
@@ -73,3 +73,5 @@
|
|||||||
- When writing unit tests, mock decorators are preferred instead of context
|
- When writing unit tests, mock decorators are preferred instead of context
|
||||||
managers.
|
managers.
|
||||||
- If a folder or file under this directory is inaccessible, ask for permissions.
|
- If a folder or file under this directory is inaccessible, ask for permissions.
|
||||||
|
- Use "tox -e fmt" to apply code formatting, "tox -e pep8" and "tox -e fmt"
|
||||||
|
and "tox -e mypy" for liniting / static code analysis.
|
||||||
|
|||||||
+91
-44
@@ -24,7 +24,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from pyVim.connect import Disconnect, SmartConnect
|
from pyVim.connect import Disconnect, SmartConnect
|
||||||
from pyVmomi import vim
|
from pyVmomi import vim
|
||||||
from pyVmomi.VmomiSupport import CreateManagedType, F_OPTIONAL, GetVmodlType
|
from pyVmomi.VmomiSupport import F_OPTIONAL, CreateManagedType, GetVmodlType
|
||||||
|
|
||||||
NFC_SERVICE_MOID = "nfcService"
|
NFC_SERVICE_MOID = "nfcService"
|
||||||
AUTHD_DEFAULT_PORT = 902
|
AUTHD_DEFAULT_PORT = 902
|
||||||
@@ -59,28 +59,62 @@ def _register_nfc_types() -> None:
|
|||||||
"vim.version.version1",
|
"vim.version.version1",
|
||||||
[],
|
[],
|
||||||
[
|
[
|
||||||
("getVmFiles", "NfcGetVmFiles", "vim.version.version1",
|
(
|
||||||
|
"getVmFiles",
|
||||||
|
"NfcGetVmFiles",
|
||||||
|
"vim.version.version1",
|
||||||
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),),
|
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),),
|
||||||
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
|
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
|
||||||
("randomAccessOpen", "NfcRandomAccessOpenDisk",
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"randomAccessOpen",
|
||||||
|
"NfcRandomAccessOpenDisk",
|
||||||
"vim.version.version1",
|
"vim.version.version1",
|
||||||
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
|
(
|
||||||
|
("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
|
||||||
("diskDeviceKey", "int", "vim.version.version1", 0, None),
|
("diskDeviceKey", "int", "vim.version.version1", 0, None),
|
||||||
("hostForAccess", "vim.HostSystem", "vim.version.version1",
|
(
|
||||||
F_OPTIONAL, None),),
|
"hostForAccess",
|
||||||
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
|
"vim.HostSystem",
|
||||||
("randomAccessOpenReadonly", "NfcRandomAccessOpenReadonly",
|
|
||||||
"vim.version.version1",
|
"vim.version.version1",
|
||||||
(("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
|
F_OPTIONAL,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"randomAccessOpenReadonly",
|
||||||
|
"NfcRandomAccessOpenReadonly",
|
||||||
|
"vim.version.version1",
|
||||||
|
(
|
||||||
|
("vm", "vim.VirtualMachine", "vim.version.version1", 0, None),
|
||||||
("diskDeviceKey", "int", "vim.version.version1", 0, None),
|
("diskDeviceKey", "int", "vim.version.version1", 0, None),
|
||||||
("hostForAccess", "vim.HostSystem", "vim.version.version1",
|
(
|
||||||
F_OPTIONAL, None),),
|
"hostForAccess",
|
||||||
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"), None, None),
|
"vim.HostSystem",
|
||||||
("getServerNfcLibVersion", "NfcGetServerNfcLibVersion",
|
|
||||||
"vim.version.version1",
|
"vim.version.version1",
|
||||||
(("hostForAccess", "vim.HostSystem", "vim.version.version1",
|
F_OPTIONAL,
|
||||||
0, None),),
|
None,
|
||||||
(0, "int", "int"), None, None),
|
),
|
||||||
|
),
|
||||||
|
(0, "vim.HostServiceTicket", "vim.HostServiceTicket"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"getServerNfcLibVersion",
|
||||||
|
"NfcGetServerNfcLibVersion",
|
||||||
|
"vim.version.version1",
|
||||||
|
(("hostForAccess", "vim.HostSystem", "vim.version.version1", 0, None),),
|
||||||
|
(0, "int", "int"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
_NFC_TYPES_REGISTERED = True
|
_NFC_TYPES_REGISTERED = True
|
||||||
@@ -103,7 +137,8 @@ def connect_vim(
|
|||||||
password: str,
|
password: str,
|
||||||
port: int = 443,
|
port: int = 443,
|
||||||
thumbprint: Optional[str] = None,
|
thumbprint: Optional[str] = None,
|
||||||
allow_untrusted: bool = False) -> vim.ServiceInstance:
|
allow_untrusted: bool = False,
|
||||||
|
) -> vim.ServiceInstance:
|
||||||
"""Login to vCenter or ESXi using pyVim.connect.SmartConnect.
|
"""Login to vCenter or ESXi using pyVim.connect.SmartConnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -124,7 +159,8 @@ def connect_vim(
|
|||||||
port=port,
|
port=port,
|
||||||
thumbprint=thumbprint,
|
thumbprint=thumbprint,
|
||||||
sslContext=ssl_context,
|
sslContext=ssl_context,
|
||||||
disableSslCertValidation=allow_untrusted)
|
disableSslCertValidation=allow_untrusted,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _virtual_disk_key(vm: vim.VirtualMachine, disk_path: str) -> int:
|
def _virtual_disk_key(vm: vim.VirtualMachine, disk_path: str) -> int:
|
||||||
@@ -144,8 +180,7 @@ def _virtual_disk_key(vm: vim.VirtualMachine, disk_path: str) -> int:
|
|||||||
if getattr(backing, "fileName", None) == disk_path:
|
if getattr(backing, "fileName", None) == disk_path:
|
||||||
return device.key
|
return device.key
|
||||||
backing = getattr(backing, "parent", None)
|
backing = getattr(backing, "parent", None)
|
||||||
raise ValueError(
|
raise ValueError(f"VMDK path {disk_path!r} is not attached to {vm._moId}")
|
||||||
f"VMDK path {disk_path!r} is not attached to {vm._moId}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_nfc_ticket(
|
def get_nfc_ticket(
|
||||||
@@ -154,7 +189,8 @@ def get_nfc_ticket(
|
|||||||
disk_device_key: Optional[int] = None,
|
disk_device_key: Optional[int] = None,
|
||||||
host_for_access: Optional[vim.HostSystem] = None,
|
host_for_access: Optional[vim.HostSystem] = None,
|
||||||
read_only: bool = True,
|
read_only: bool = True,
|
||||||
disk_path: Optional[str] = None) -> vim.HostServiceTicket:
|
disk_path: Optional[str] = None,
|
||||||
|
) -> vim.HostServiceTicket:
|
||||||
"""Return a one-time NFC HostServiceTicket for ``vm``.
|
"""Return a one-time NFC HostServiceTicket for ``vm``.
|
||||||
|
|
||||||
Matches VDDK: ``NfcGetVmFiles`` when only the VM is known (read-only),
|
Matches VDDK: ``NfcGetVmFiles`` when only the VM is known (read-only),
|
||||||
@@ -174,14 +210,12 @@ def get_nfc_ticket(
|
|||||||
return nfc.GetVmFiles(vm)
|
return nfc.GetVmFiles(vm)
|
||||||
if disk_device_key is None:
|
if disk_device_key is None:
|
||||||
if disk_path is None:
|
if disk_path is None:
|
||||||
raise ValueError(
|
raise ValueError("writable NFC tickets need disk_path or disk_device_key")
|
||||||
"writable NFC tickets need disk_path or disk_device_key")
|
|
||||||
disk_device_key = _virtual_disk_key(vm, disk_path)
|
disk_device_key = _virtual_disk_key(vm, disk_path)
|
||||||
if host_for_access is None:
|
if host_for_access is None:
|
||||||
host_for_access = vm.runtime.host
|
host_for_access = vm.runtime.host
|
||||||
if read_only:
|
if read_only:
|
||||||
return nfc.RandomAccessOpenReadonly(
|
return nfc.RandomAccessOpenReadonly(vm, disk_device_key, host_for_access)
|
||||||
vm, disk_device_key, host_for_access)
|
|
||||||
return nfc.RandomAccessOpen(vm, disk_device_key, host_for_access)
|
return nfc.RandomAccessOpen(vm, disk_device_key, host_for_access)
|
||||||
|
|
||||||
|
|
||||||
@@ -202,7 +236,8 @@ def get_ssl_cert_thumbprint(
|
|||||||
port: int = 443,
|
port: int = 443,
|
||||||
digest_algorithm: str = "sha1",
|
digest_algorithm: str = "sha1",
|
||||||
ssl_context: Optional[ssl.SSLContext] = None,
|
ssl_context: Optional[ssl.SSLContext] = None,
|
||||||
timeout: float = 30.0) -> str:
|
timeout: float = 30.0,
|
||||||
|
) -> str:
|
||||||
"""Return the TLS certificate thumbprint of ``host``:``port``.
|
"""Return the TLS certificate thumbprint of ``host``:``port``.
|
||||||
|
|
||||||
Reads the peer certificate in DER form and hashes it with ``hashlib``.
|
Reads the peer certificate in DER form and hashes it with ``hashlib``.
|
||||||
@@ -223,14 +258,11 @@ def get_ssl_cert_thumbprint(
|
|||||||
if ssl_context is None:
|
if ssl_context is None:
|
||||||
ssl_context = _ssl_client_context(verify=False)
|
ssl_context = _ssl_client_context(verify=False)
|
||||||
with socket.create_connection((host, port), timeout=timeout) as sock:
|
with socket.create_connection((host, port), timeout=timeout) as sock:
|
||||||
with ssl_context.wrap_socket(
|
with ssl_context.wrap_socket(sock, server_hostname=host) as ssock:
|
||||||
sock, server_hostname=host) as ssock:
|
|
||||||
cert = ssock.getpeercert(binary_form=True)
|
cert = ssock.getpeercert(binary_form=True)
|
||||||
if not cert:
|
if not cert:
|
||||||
raise ConnectionError(
|
raise ConnectionError(f"no peer certificate from {host}:{port}")
|
||||||
f"no peer certificate from {host}:{port}")
|
return _format_thumbprint(hashlib.new(digest_algorithm, cert).digest())
|
||||||
return _format_thumbprint(
|
|
||||||
hashlib.new(digest_algorithm, cert).digest())
|
|
||||||
|
|
||||||
|
|
||||||
def _readline(sock: socket.socket) -> str:
|
def _readline(sock: socket.socket) -> str:
|
||||||
@@ -248,7 +280,7 @@ def _readline(sock: socket.socket) -> str:
|
|||||||
def _expect_code(line: str, code: str, what: str) -> str:
|
def _expect_code(line: str, code: str, what: str) -> str:
|
||||||
if not line.startswith(code):
|
if not line.startswith(code):
|
||||||
raise ConnectionError(f"authd {what} failed: {line}")
|
raise ConnectionError(f"authd {what} failed: {line}")
|
||||||
return line[len(code):].lstrip()
|
return line[len(code) :].lstrip()
|
||||||
|
|
||||||
|
|
||||||
def nfcssl_service_name(service: str) -> str:
|
def nfcssl_service_name(service: str) -> str:
|
||||||
@@ -269,7 +301,8 @@ def connect_authd(
|
|||||||
ticket: vim.HostServiceTicket,
|
ticket: vim.HostServiceTicket,
|
||||||
allow_untrusted: bool = False,
|
allow_untrusted: bool = False,
|
||||||
timeout: float = 30.0,
|
timeout: float = 30.0,
|
||||||
nfc_ssl: bool = True) -> ssl.SSLSocket:
|
nfc_ssl: bool = True,
|
||||||
|
) -> ssl.SSLSocket:
|
||||||
"""Complete the ESXi authd handshake using an NFC HostServiceTicket.
|
"""Complete the ESXi authd handshake using an NFC HostServiceTicket.
|
||||||
|
|
||||||
Wire sequence captured from VDDK against authd on TCP 902:
|
Wire sequence captured from VDDK against authd on TCP 902:
|
||||||
@@ -310,10 +343,12 @@ def connect_authd(
|
|||||||
if not allow_untrusted and ticket.sslThumbprint:
|
if not allow_untrusted and ticket.sslThumbprint:
|
||||||
peer = _sha1_thumbprint(ssock.getpeercert(True))
|
peer = _sha1_thumbprint(ssock.getpeercert(True))
|
||||||
if _normalize_thumbprint(peer) != _normalize_thumbprint(
|
if _normalize_thumbprint(peer) != _normalize_thumbprint(
|
||||||
ticket.sslThumbprint):
|
ticket.sslThumbprint
|
||||||
|
):
|
||||||
raise ConnectionError(
|
raise ConnectionError(
|
||||||
f"ESXi SSL thumbprint mismatch: got {peer}, "
|
f"ESXi SSL thumbprint mismatch: got {peer}, "
|
||||||
f"expected {ticket.sslThumbprint}")
|
f"expected {ticket.sslThumbprint}"
|
||||||
|
)
|
||||||
|
|
||||||
ssock.sendall(f"SESSION {ticket.sessionId}\r\n".encode("ascii"))
|
ssock.sendall(f"SESSION {ticket.sessionId}\r\n".encode("ascii"))
|
||||||
# Trailing space is part of the BANNER command token used by authd.
|
# Trailing space is part of the BANNER command token used by authd.
|
||||||
@@ -342,7 +377,8 @@ class NfcAuthSession:
|
|||||||
si: vim.ServiceInstance,
|
si: vim.ServiceInstance,
|
||||||
ticket: vim.HostServiceTicket,
|
ticket: vim.HostServiceTicket,
|
||||||
authd_sock: ssl.SSLSocket,
|
authd_sock: ssl.SSLSocket,
|
||||||
nfc_ssl: bool = True) -> None:
|
nfc_ssl: bool = True,
|
||||||
|
) -> None:
|
||||||
self.si = si
|
self.si = si
|
||||||
self.ticket = ticket
|
self.ticket = ticket
|
||||||
self.authd_sock = authd_sock
|
self.authd_sock = authd_sock
|
||||||
@@ -373,7 +409,8 @@ def authenticate(
|
|||||||
disk_device_key: Optional[int] = None,
|
disk_device_key: Optional[int] = None,
|
||||||
disk_path: Optional[str] = None,
|
disk_path: Optional[str] = None,
|
||||||
read_only: bool = True,
|
read_only: bool = True,
|
||||||
nfc_ssl: bool = True) -> NfcAuthSession:
|
nfc_ssl: bool = True,
|
||||||
|
) -> NfcAuthSession:
|
||||||
"""Login to vSphere and complete NFC authd authentication for a VM.
|
"""Login to vSphere and complete NFC authd authentication for a VM.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -393,15 +430,25 @@ def authenticate(
|
|||||||
PROXY service used by nbdssl. Pass False for nbd.
|
PROXY service used by nbdssl. Pass False for nbd.
|
||||||
"""
|
"""
|
||||||
si = connect_vim(
|
si = connect_vim(
|
||||||
host, username, password, port=port,
|
host,
|
||||||
thumbprint=thumbprint, allow_untrusted=allow_untrusted)
|
username,
|
||||||
|
password,
|
||||||
|
port=port,
|
||||||
|
thumbprint=thumbprint,
|
||||||
|
allow_untrusted=allow_untrusted,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
vm = vim.VirtualMachine(vm_moref, si._stub)
|
vm = vim.VirtualMachine(vm_moref, si._stub)
|
||||||
ticket = get_nfc_ticket(
|
ticket = get_nfc_ticket(
|
||||||
si, vm, disk_device_key=disk_device_key,
|
si,
|
||||||
disk_path=disk_path, read_only=read_only)
|
vm,
|
||||||
|
disk_device_key=disk_device_key,
|
||||||
|
disk_path=disk_path,
|
||||||
|
read_only=read_only,
|
||||||
|
)
|
||||||
authd_sock = connect_authd(
|
authd_sock = connect_authd(
|
||||||
ticket, allow_untrusted=allow_untrusted, nfc_ssl=nfc_ssl)
|
ticket, allow_untrusted=allow_untrusted, nfc_ssl=nfc_ssl
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
Disconnect(si)
|
Disconnect(si)
|
||||||
raise
|
raise
|
||||||
|
|||||||
+84
-109
@@ -98,15 +98,14 @@ def takeover_authd_socket(ssock: ssl.SSLSocket) -> socket.socket:
|
|||||||
family=ssock.family,
|
family=ssock.family,
|
||||||
type=ssock.type,
|
type=ssock.type,
|
||||||
proto=ssock.proto,
|
proto=ssock.proto,
|
||||||
fileno=os.dup(ssock.fileno()))
|
fileno=os.dup(ssock.fileno()),
|
||||||
|
)
|
||||||
raw.settimeout(timeout)
|
raw.settimeout(timeout)
|
||||||
_enable_tcp_nodelay(raw)
|
_enable_tcp_nodelay(raw)
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
||||||
def wrap_nfcssl_socket(
|
def wrap_nfcssl_socket(ssock: ssl.SSLSocket, server_hostname: str) -> ssl.SSLSocket:
|
||||||
ssock: ssl.SSLSocket,
|
|
||||||
server_hostname: str) -> ssl.SSLSocket:
|
|
||||||
"""Start the second TLS session used by NBDSSL after PROXY.
|
"""Start the second TLS session used by NBDSSL after PROXY.
|
||||||
|
|
||||||
After ``200 Connect ha-nfcssl``, authd TLS is finished and
|
After ``200 Connect ha-nfcssl``, authd TLS is finished and
|
||||||
@@ -121,8 +120,7 @@ def wrap_nfcssl_socket(
|
|||||||
raw = takeover_authd_socket(ssock)
|
raw = takeover_authd_socket(ssock)
|
||||||
ssl_context = _ssl_client_context(verify=False)
|
ssl_context = _ssl_client_context(verify=False)
|
||||||
try:
|
try:
|
||||||
return ssl_context.wrap_socket(
|
return ssl_context.wrap_socket(raw, server_hostname=server_hostname)
|
||||||
raw, server_hostname=server_hostname)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
raw.close()
|
raw.close()
|
||||||
raise
|
raise
|
||||||
@@ -139,13 +137,13 @@ def _recvn(sock: socket.socket, size: int) -> bytes:
|
|||||||
chunk = sock.recv(size - len(buf))
|
chunk = sock.recv(size - len(buf))
|
||||||
if not chunk:
|
if not chunk:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"NFC connection closed, needed {size} bytes, got {len(buf)}")
|
f"NFC connection closed, needed {size} bytes, got {len(buf)}"
|
||||||
|
)
|
||||||
buf.extend(chunk)
|
buf.extend(chunk)
|
||||||
return bytes(buf)
|
return bytes(buf)
|
||||||
|
|
||||||
|
|
||||||
def _send_nfc_msg(
|
def _send_nfc_msg(sock: socket.socket, msg_type: int, body: bytes = b"") -> None:
|
||||||
sock: socket.socket, msg_type: int, body: bytes = b"") -> None:
|
|
||||||
if len(body) > NFC_MSG_SIZE - 4:
|
if len(body) > NFC_MSG_SIZE - 4:
|
||||||
raise ValueError("NFC classic message body too large")
|
raise ValueError("NFC classic message body too large")
|
||||||
frame = struct.pack("<I", msg_type) + body
|
frame = struct.pack("<I", msg_type) + body
|
||||||
@@ -159,16 +157,15 @@ def _recv_nfc_msg(sock: socket.socket) -> tuple[int, bytes]:
|
|||||||
|
|
||||||
|
|
||||||
def _pack_aio_hdr(msg_type: int, payload_size: int, op_id: int) -> bytes:
|
def _pack_aio_hdr(msg_type: int, payload_size: int, op_id: int) -> bytes:
|
||||||
return struct.pack(
|
return struct.pack("<IIII", NFC_AIO_MAGIC, msg_type, payload_size, op_id)
|
||||||
"<IIII", NFC_AIO_MAGIC, msg_type, payload_size, op_id)
|
|
||||||
|
|
||||||
|
|
||||||
def _unpack_aio_hdr(hdr: bytes) -> tuple[int, int, int]:
|
def _unpack_aio_hdr(hdr: bytes) -> tuple[int, int, int]:
|
||||||
magic, msg_type, size, op_id = struct.unpack_from("<IIII", hdr)
|
magic, msg_type, size, op_id = struct.unpack_from("<IIII", hdr)
|
||||||
if magic != NFC_AIO_MAGIC:
|
if magic != NFC_AIO_MAGIC:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO header magic mismatch: 0x{magic:x}, "
|
f"AIO header magic mismatch: 0x{magic:x}, expected 0x{NFC_AIO_MAGIC:x}"
|
||||||
f"expected 0x{NFC_AIO_MAGIC:x}")
|
)
|
||||||
if msg_type == NFC_AIO_MSG_ERROR:
|
if msg_type == NFC_AIO_MSG_ERROR:
|
||||||
raise NfcProtocolError(f"AIO error opId={op_id} size={size}")
|
raise NfcProtocolError(f"AIO error opId={op_id} size={size}")
|
||||||
return msg_type, size, op_id
|
return msg_type, size, op_id
|
||||||
@@ -183,7 +180,8 @@ class NfcDisk:
|
|||||||
path: str,
|
path: str,
|
||||||
handle: int,
|
handle: int,
|
||||||
sector_size: int,
|
sector_size: int,
|
||||||
compression: int = NFC_COMPRESSION_NONE) -> None:
|
compression: int = NFC_COMPRESSION_NONE,
|
||||||
|
) -> None:
|
||||||
"""Wrap an AIO session that already has ``path`` open.
|
"""Wrap an AIO session that already has ``path`` open.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -207,15 +205,12 @@ class NfcDisk:
|
|||||||
self._op_id += 1
|
self._op_id += 1
|
||||||
return op_id
|
return op_id
|
||||||
|
|
||||||
def _aio_send(
|
def _aio_send(self, msg_type: int, payload: bytes, extra: bytes = b"") -> int:
|
||||||
self,
|
|
||||||
msg_type: int,
|
|
||||||
payload: bytes,
|
|
||||||
extra: bytes = b"") -> int:
|
|
||||||
"""Send one AIO request (header, payload, and extra in one write)."""
|
"""Send one AIO request (header, payload, and extra in one write)."""
|
||||||
op_id = self._next_op_id()
|
op_id = self._next_op_id()
|
||||||
self._sock.sendall(
|
self._sock.sendall(
|
||||||
_pack_aio_hdr(msg_type, len(payload), op_id) + payload + extra)
|
_pack_aio_hdr(msg_type, len(payload), op_id) + payload + extra
|
||||||
|
)
|
||||||
return op_id
|
return op_id
|
||||||
|
|
||||||
def _aio_recv_reply(self) -> tuple[int, int, bytes]:
|
def _aio_recv_reply(self) -> tuple[int, int, bytes]:
|
||||||
@@ -224,27 +219,24 @@ class NfcDisk:
|
|||||||
magic, rtype, rsize, rop = struct.unpack_from("<IIII", rhdr)
|
magic, rtype, rsize, rop = struct.unpack_from("<IIII", rhdr)
|
||||||
if magic != NFC_AIO_MAGIC:
|
if magic != NFC_AIO_MAGIC:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO header magic mismatch: 0x{magic:x}, "
|
f"AIO header magic mismatch: 0x{magic:x}, expected 0x{NFC_AIO_MAGIC:x}"
|
||||||
f"expected 0x{NFC_AIO_MAGIC:x}")
|
)
|
||||||
body = _recvn(self._sock, rsize) if rsize else b""
|
body = _recvn(self._sock, rsize) if rsize else b""
|
||||||
if rtype == NFC_AIO_MSG_ERROR:
|
if rtype == NFC_AIO_MSG_ERROR:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(f"AIO error opId={rop} size={rsize} {body.hex()}")
|
||||||
f"AIO error opId={rop} size={rsize} {body.hex()}")
|
|
||||||
return rtype, rop, body
|
return rtype, rop, body
|
||||||
|
|
||||||
def _aio_roundtrip(
|
def _aio_roundtrip(
|
||||||
self,
|
self, msg_type: int, payload: bytes, extra: bytes = b"", extra_recv: int = 0
|
||||||
msg_type: int,
|
) -> bytes:
|
||||||
payload: bytes,
|
|
||||||
extra: bytes = b"",
|
|
||||||
extra_recv: int = 0) -> bytes:
|
|
||||||
"""Send one AIO request and return the reply payload (+ extra)."""
|
"""Send one AIO request and return the reply payload (+ extra)."""
|
||||||
op_id = self._aio_send(msg_type, payload, extra)
|
op_id = self._aio_send(msg_type, payload, extra)
|
||||||
rtype, rop, body = self._aio_recv_reply()
|
rtype, rop, body = self._aio_recv_reply()
|
||||||
if rtype != msg_type or rop != op_id:
|
if rtype != msg_type or rop != op_id:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO reply type={rtype} opId={rop}, "
|
f"AIO reply type={rtype} opId={rop}, "
|
||||||
f"expected type={msg_type} opId={op_id}")
|
f"expected type={msg_type} opId={op_id}"
|
||||||
|
)
|
||||||
if extra_recv:
|
if extra_recv:
|
||||||
body += _recvn(self._sock, extra_recv)
|
body += _recvn(self._sock, extra_recv)
|
||||||
return body
|
return body
|
||||||
@@ -269,17 +261,10 @@ class NfcDisk:
|
|||||||
offset = start_sector * self.sector_size
|
offset = start_sector * self.sector_size
|
||||||
opcode = NFC_AIO_IO_READ | (self.compression << 32)
|
opcode = NFC_AIO_IO_READ | (self.compression << 32)
|
||||||
payload = struct.pack(
|
payload = struct.pack(
|
||||||
"<QQQQIII",
|
"<QQQQIII", self.handle, opcode, offset, length, length, length, 0
|
||||||
self.handle,
|
)
|
||||||
opcode,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
length,
|
|
||||||
length,
|
|
||||||
0)
|
|
||||||
op_id = self._next_op_id()
|
op_id = self._next_op_id()
|
||||||
self._sock.sendall(
|
self._sock.sendall(_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload)
|
||||||
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload)
|
|
||||||
data = bytearray(length)
|
data = bytearray(length)
|
||||||
filled = 0
|
filled = 0
|
||||||
seen: set[int] = set()
|
seen: set[int] = set()
|
||||||
@@ -289,11 +274,11 @@ class NfcDisk:
|
|||||||
if rtype != NFC_AIO_MSG_IO or rop != op_id:
|
if rtype != NFC_AIO_MSG_IO or rop != op_id:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO IO reply type={rtype} opId={rop}, "
|
f"AIO IO reply type={rtype} opId={rop}, "
|
||||||
f"expected type={NFC_AIO_MSG_IO} opId={op_id}")
|
f"expected type={NFC_AIO_MSG_IO} opId={op_id}"
|
||||||
|
)
|
||||||
body = _recvn(self._sock, rsize)
|
body = _recvn(self._sock, rsize)
|
||||||
if rsize < 36:
|
if rsize < 36:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(f"AIO IO reply payload too short: {rsize}")
|
||||||
f"AIO IO reply payload too short: {rsize}")
|
|
||||||
# Fragments may arrive out of order. Offset 28 is the byte
|
# Fragments may arrive out of order. Offset 28 is the byte
|
||||||
# offset of this chunk within the request (0, 65536, …),
|
# offset of this chunk within the request (0, 65536, …),
|
||||||
# not a 0-based index. Offset 32 is the uncompressed
|
# not a 0-based index. Offset 32 is the uncompressed
|
||||||
@@ -301,13 +286,11 @@ class NfcDisk:
|
|||||||
# (2), extra data is compressed and offset 36 is its size.
|
# (2), extra data is compressed and offset 36 is its size.
|
||||||
opcode = struct.unpack_from("<Q", body, 8)[0]
|
opcode = struct.unpack_from("<Q", body, 8)[0]
|
||||||
dest, chunk_len = struct.unpack_from("<II", body, 28)
|
dest, chunk_len = struct.unpack_from("<II", body, 28)
|
||||||
if (
|
if dest in seen or chunk_len == 0 or dest + chunk_len > length:
|
||||||
dest in seen
|
|
||||||
or chunk_len == 0
|
|
||||||
or dest + chunk_len > length):
|
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO IO chunk offset={dest} length={chunk_len} invalid, "
|
f"AIO IO chunk offset={dest} length={chunk_len} invalid, "
|
||||||
f"request {length}")
|
f"request {length}"
|
||||||
|
)
|
||||||
seen.add(dest)
|
seen.add(dest)
|
||||||
ctype = opcode >> 32
|
ctype = opcode >> 32
|
||||||
if ctype == NFC_COMPRESSION_FASTLZ:
|
if ctype == NFC_COMPRESSION_FASTLZ:
|
||||||
@@ -317,25 +300,21 @@ class NfcDisk:
|
|||||||
chunk = fastlz.decompress(extra, chunk_len)
|
chunk = fastlz.decompress(extra, chunk_len)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"FastLZ read fragment failed: {exc}") from exc
|
f"FastLZ read fragment failed: {exc}"
|
||||||
|
) from exc
|
||||||
if len(chunk) != chunk_len:
|
if len(chunk) != chunk_len:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"FastLZ read got {len(chunk)} bytes, "
|
f"FastLZ read got {len(chunk)} bytes, expected {chunk_len}"
|
||||||
f"expected {chunk_len}")
|
)
|
||||||
elif ctype == NFC_COMPRESSION_NONE:
|
elif ctype == NFC_COMPRESSION_NONE:
|
||||||
chunk = _recvn(self._sock, chunk_len)
|
chunk = _recvn(self._sock, chunk_len)
|
||||||
else:
|
else:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(f"unsupported NFC IO compression type {ctype}")
|
||||||
f"unsupported NFC IO compression type {ctype}")
|
data[dest : dest + chunk_len] = chunk
|
||||||
data[dest:dest + chunk_len] = chunk
|
|
||||||
filled += chunk_len
|
filled += chunk_len
|
||||||
return bytes(data)
|
return bytes(data)
|
||||||
|
|
||||||
def write(
|
def write(self, start_sector: int, num_sectors: int, data: bytes) -> None:
|
||||||
self,
|
|
||||||
start_sector: int,
|
|
||||||
num_sectors: int,
|
|
||||||
data: bytes) -> None:
|
|
||||||
"""Write ``num_sectors`` starting at ``start_sector``.
|
"""Write ``num_sectors`` starting at ``start_sector``.
|
||||||
|
|
||||||
Matches ``VixDiskLib_Write``: one ``NFC_AIO_MSG_IO`` ``opId``
|
Matches ``VixDiskLib_Write``: one ``NFC_AIO_MSG_IO`` ``opId``
|
||||||
@@ -352,19 +331,16 @@ class NfcDisk:
|
|||||||
raise ValueError("num_sectors must be at least 1")
|
raise ValueError("num_sectors must be at least 1")
|
||||||
length = num_sectors * self.sector_size
|
length = num_sectors * self.sector_size
|
||||||
if len(data) != length:
|
if len(data) != length:
|
||||||
raise ValueError(
|
raise ValueError(f"write data is {len(data)} bytes, need {length}")
|
||||||
f"write data is {len(data)} bytes, need {length}")
|
|
||||||
disk_offset = start_sector * self.sector_size
|
disk_offset = start_sector * self.sector_size
|
||||||
op_id = self._next_op_id()
|
op_id = self._next_op_id()
|
||||||
frag_offset = 0
|
frag_offset = 0
|
||||||
while frag_offset < length:
|
while frag_offset < length:
|
||||||
chunk = data[frag_offset:frag_offset + NFC_AIO_BUFFER_SIZE]
|
chunk = data[frag_offset : frag_offset + NFC_AIO_BUFFER_SIZE]
|
||||||
extra = chunk
|
extra = chunk
|
||||||
extra_len = len(chunk)
|
extra_len = len(chunk)
|
||||||
ctype = NFC_COMPRESSION_NONE
|
ctype = NFC_COMPRESSION_NONE
|
||||||
if (
|
if self.compression == NFC_COMPRESSION_FASTLZ and extra_len >= 16:
|
||||||
self.compression == NFC_COMPRESSION_FASTLZ
|
|
||||||
and extra_len >= 16):
|
|
||||||
compressed = fastlz.compress(chunk)
|
compressed = fastlz.compress(chunk)
|
||||||
if compressed and len(compressed) < extra_len:
|
if compressed and len(compressed) < extra_len:
|
||||||
extra = compressed
|
extra = compressed
|
||||||
@@ -380,16 +356,18 @@ class NfcDisk:
|
|||||||
frag_offset,
|
frag_offset,
|
||||||
len(chunk),
|
len(chunk),
|
||||||
extra_len,
|
extra_len,
|
||||||
0)
|
0,
|
||||||
|
)
|
||||||
self._sock.sendall(
|
self._sock.sendall(
|
||||||
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id)
|
_pack_aio_hdr(NFC_AIO_MSG_IO, len(payload), op_id) + payload + extra
|
||||||
+ payload + extra)
|
)
|
||||||
frag_offset += len(chunk)
|
frag_offset += len(chunk)
|
||||||
rtype, rop, _body = self._aio_recv_reply()
|
rtype, rop, _body = self._aio_recv_reply()
|
||||||
if rtype != NFC_AIO_MSG_IO or rop != op_id:
|
if rtype != NFC_AIO_MSG_IO or rop != op_id:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"AIO IO write reply type={rtype} opId={rop}, "
|
f"AIO IO write reply type={rtype} opId={rop}, "
|
||||||
f"expected type={NFC_AIO_MSG_IO} opId={op_id}")
|
f"expected type={NFC_AIO_MSG_IO} opId={op_id}"
|
||||||
|
)
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Close the VMDK, the AIO session, and the classic NFC session."""
|
"""Close the VMDK, the AIO session, and the classic NFC session."""
|
||||||
@@ -397,10 +375,8 @@ class NfcDisk:
|
|||||||
return
|
return
|
||||||
self._closed = True
|
self._closed = True
|
||||||
try:
|
try:
|
||||||
self._aio_roundtrip(
|
self._aio_roundtrip(NFC_AIO_MSG_CLOSE_FILE, struct.pack("<Q", self.handle))
|
||||||
NFC_AIO_MSG_CLOSE_FILE, struct.pack("<Q", self.handle))
|
self._aio_roundtrip(NFC_AIO_MSG_CLOSE_SESSION, struct.pack("<I", 0))
|
||||||
self._aio_roundtrip(
|
|
||||||
NFC_AIO_MSG_CLOSE_SESSION, struct.pack("<I", 0))
|
|
||||||
_send_nfc_msg(self._sock, NFC_MSG_SESSION_COMPLETE)
|
_send_nfc_msg(self._sock, NFC_MSG_SESSION_COMPLETE)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
@@ -415,11 +391,7 @@ class NfcDisk:
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
def _handshake(
|
def _handshake(sock: socket.socket, client_name: str, op_id: str, version: int) -> None:
|
||||||
sock: socket.socket,
|
|
||||||
client_name: str,
|
|
||||||
op_id: str,
|
|
||||||
version: int) -> None:
|
|
||||||
"""Run the classic NFC session handshake used by VDDK NBD."""
|
"""Run the classic NFC session handshake used by VDDK NBD."""
|
||||||
_send_nfc_msg(sock, NFC_MSG_HANDSHAKE, b"PlainText")
|
_send_nfc_msg(sock, NFC_MSG_HANDSHAKE, b"PlainText")
|
||||||
_send_nfc_msg(sock, NFC_MSG_SESSION_PARAMS)
|
_send_nfc_msg(sock, NFC_MSG_SESSION_PARAMS)
|
||||||
@@ -427,43 +399,46 @@ def _handshake(
|
|||||||
if reply_type != NFC_MSG_SESSION_PARAMS_REPLY:
|
if reply_type != NFC_MSG_SESSION_PARAMS_REPLY:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"expected session-params reply {NFC_MSG_SESSION_PARAMS_REPLY}, "
|
f"expected session-params reply {NFC_MSG_SESSION_PARAMS_REPLY}, "
|
||||||
f"got {reply_type}")
|
f"got {reply_type}"
|
||||||
|
)
|
||||||
|
|
||||||
_send_nfc_msg(sock, NFC_MSG_VERSION, struct.pack("<I", version))
|
_send_nfc_msg(sock, NFC_MSG_VERSION, struct.pack("<I", version))
|
||||||
reply_type, body = _recv_nfc_msg(sock)
|
reply_type, body = _recv_nfc_msg(sock)
|
||||||
if reply_type != NFC_MSG_VERSION:
|
if reply_type != NFC_MSG_VERSION:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"expected version reply {NFC_MSG_VERSION}, got {reply_type}")
|
f"expected version reply {NFC_MSG_VERSION}, got {reply_type}"
|
||||||
|
)
|
||||||
remote_version = struct.unpack_from("<I", body)[0]
|
remote_version = struct.unpack_from("<I", body)[0]
|
||||||
if remote_version < 3:
|
if remote_version < 3:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"NFC server version {remote_version} is too old for AIO")
|
f"NFC server version {remote_version} is too old for AIO"
|
||||||
|
)
|
||||||
|
|
||||||
name_b = client_name.encode("ascii")
|
name_b = client_name.encode("ascii")
|
||||||
op_b = op_id.encode("ascii")
|
op_b = op_id.encode("ascii")
|
||||||
_send_nfc_msg(
|
_send_nfc_msg(
|
||||||
sock, NFC_MSG_CONNECTION_DATA,
|
sock, NFC_MSG_CONNECTION_DATA, struct.pack("<II", len(name_b), len(op_b))
|
||||||
struct.pack("<II", len(name_b), len(op_b)))
|
)
|
||||||
sock.sendall(name_b)
|
sock.sendall(name_b)
|
||||||
sock.sendall(op_b)
|
sock.sendall(op_b)
|
||||||
_send_nfc_msg(
|
_send_nfc_msg(
|
||||||
sock, NFC_MSG_SESSION_FEATURES,
|
sock,
|
||||||
struct.pack("<I", NFC_SESSION_FEATURE_INTERRUPTION_SWITCH))
|
NFC_MSG_SESSION_FEATURES,
|
||||||
|
struct.pack("<I", NFC_SESSION_FEATURE_INTERRUPTION_SWITCH),
|
||||||
|
)
|
||||||
_send_nfc_msg(sock, NFC_MSG_AIO_SESSION_OPEN)
|
_send_nfc_msg(sock, NFC_MSG_AIO_SESSION_OPEN)
|
||||||
reply_type, _ = _recv_nfc_msg(sock)
|
reply_type, _ = _recv_nfc_msg(sock)
|
||||||
if reply_type != NFC_MSG_AIO_SESSION_OPEN:
|
if reply_type != NFC_MSG_AIO_SESSION_OPEN:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(
|
||||||
f"expected AIO session-open reply "
|
f"expected AIO session-open reply "
|
||||||
f"{NFC_MSG_AIO_SESSION_OPEN}, got {reply_type}")
|
f"{NFC_MSG_AIO_SESSION_OPEN}, got {reply_type}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _aio_prepare(disk: NfcDisk) -> None:
|
def _aio_prepare(disk: NfcDisk) -> None:
|
||||||
disk._aio_roundtrip(
|
disk._aio_roundtrip(NFC_AIO_MSG_OPEN_SESSION, bytes(16))
|
||||||
NFC_AIO_MSG_OPEN_SESSION, bytes(16))
|
disk._aio_roundtrip(NFC_AIO_MSG_SET_SOCK_OPTS, bytes(12))
|
||||||
disk._aio_roundtrip(
|
disk._aio_roundtrip(NFC_AIO_MSG_SET_RES_POOL, struct.pack("<I", 1))
|
||||||
NFC_AIO_MSG_SET_SOCK_OPTS, bytes(12))
|
|
||||||
disk._aio_roundtrip(
|
|
||||||
NFC_AIO_MSG_SET_RES_POOL, struct.pack("<I", 1))
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_open_reply(body: bytes) -> tuple[int, int]:
|
def _parse_open_reply(body: bytes) -> tuple[int, int]:
|
||||||
@@ -472,8 +447,7 @@ def _parse_open_reply(body: bytes) -> tuple[int, int]:
|
|||||||
handle, file_type, _flags = struct.unpack_from("<QII", body, 8)
|
handle, file_type, _flags = struct.unpack_from("<QII", body, 8)
|
||||||
sector_size = struct.unpack_from("<I", body, 36)[0]
|
sector_size = struct.unpack_from("<I", body, 36)[0]
|
||||||
if file_type != NFC_DISK:
|
if file_type != NFC_DISK:
|
||||||
raise NfcProtocolError(
|
raise NfcProtocolError(f"opened file type {file_type}, expected NFC_DISK")
|
||||||
f"opened file type {file_type}, expected NFC_DISK")
|
|
||||||
if sector_size == 0:
|
if sector_size == 0:
|
||||||
sector_size = NFC_SECTOR_SIZE
|
sector_size = NFC_SECTOR_SIZE
|
||||||
return handle, sector_size
|
return handle, sector_size
|
||||||
@@ -486,7 +460,8 @@ def open_disk(
|
|||||||
op_id: str = "nbdmode",
|
op_id: str = "nbdmode",
|
||||||
version: int = NFC_PROTOCOL_VERSION,
|
version: int = NFC_PROTOCOL_VERSION,
|
||||||
read_only: bool = True,
|
read_only: bool = True,
|
||||||
compression: int = NFC_COMPRESSION_NONE) -> NfcDisk:
|
compression: int = NFC_COMPRESSION_NONE,
|
||||||
|
) -> NfcDisk:
|
||||||
"""Open ``disk_path`` over the authenticated authd socket.
|
"""Open ``disk_path`` over the authenticated authd socket.
|
||||||
|
|
||||||
Matches VDDK ``VixDiskLib_Open`` of a datastore path after the NFC
|
Matches VDDK ``VixDiskLib_Open`` of a datastore path after the NFC
|
||||||
@@ -506,31 +481,31 @@ def open_disk(
|
|||||||
compression: ``NFC_COMPRESSION_NONE`` or ``NFC_COMPRESSION_FASTLZ``.
|
compression: ``NFC_COMPRESSION_NONE`` or ``NFC_COMPRESSION_FASTLZ``.
|
||||||
OPEN_FILE flags are unchanged; compression is per IO message.
|
OPEN_FILE flags are unchanged; compression is per IO message.
|
||||||
"""
|
"""
|
||||||
if compression not in (
|
if compression not in (NFC_COMPRESSION_NONE, NFC_COMPRESSION_FASTLZ):
|
||||||
NFC_COMPRESSION_NONE, NFC_COMPRESSION_FASTLZ):
|
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"NFC compression type {compression} is not supported")
|
f"NFC compression type {compression} is not supported"
|
||||||
|
)
|
||||||
if session.nfc_ssl:
|
if session.nfc_ssl:
|
||||||
sock = wrap_nfcssl_socket(
|
sock = wrap_nfcssl_socket(session.authd_sock, session.ticket.host)
|
||||||
session.authd_sock, session.ticket.host)
|
|
||||||
else:
|
else:
|
||||||
sock = takeover_authd_socket(session.authd_sock)
|
sock = takeover_authd_socket(session.authd_sock)
|
||||||
try:
|
try:
|
||||||
_handshake(sock, client_name, op_id, version)
|
_handshake(sock, client_name, op_id, version)
|
||||||
disk = NfcDisk(
|
disk = NfcDisk(
|
||||||
sock, disk_path, handle=0, sector_size=NFC_SECTOR_SIZE,
|
sock,
|
||||||
compression=compression)
|
disk_path,
|
||||||
|
handle=0,
|
||||||
|
sector_size=NFC_SECTOR_SIZE,
|
||||||
|
compression=compression,
|
||||||
|
)
|
||||||
_aio_prepare(disk)
|
_aio_prepare(disk)
|
||||||
path_b = disk_path.encode("utf-8")
|
path_b = disk_path.encode("utf-8")
|
||||||
open_flags = (
|
open_flags = (
|
||||||
NFC_OPEN_FLAGS_READ_ONLY if read_only
|
NFC_OPEN_FLAGS_READ_ONLY if read_only else NFC_OPEN_FLAGS_READ_WRITE
|
||||||
else NFC_OPEN_FLAGS_READ_WRITE)
|
)
|
||||||
open_body = struct.pack(
|
open_body = struct.pack("<IIIIII", len(path_b), 0, 0, 0, NFC_DISK, open_flags)
|
||||||
"<IIIIII",
|
|
||||||
len(path_b), 0, 0, 0, NFC_DISK, open_flags)
|
|
||||||
open_body = open_body.ljust(60, b"\x00")
|
open_body = open_body.ljust(60, b"\x00")
|
||||||
reply = disk._aio_roundtrip(
|
reply = disk._aio_roundtrip(NFC_AIO_MSG_OPEN_FILE, open_body, extra=path_b)
|
||||||
NFC_AIO_MSG_OPEN_FILE, open_body, extra=path_b)
|
|
||||||
handle, sector_size = _parse_open_reply(reply)
|
handle, sector_size = _parse_open_reply(reply)
|
||||||
disk.handle = handle
|
disk.handle = handle
|
||||||
disk.sector_size = sector_size
|
disk.sector_size = sector_size
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ from typing import Iterator, Optional, Union
|
|||||||
from pyVim.connect import Disconnect
|
from pyVim.connect import Disconnect
|
||||||
from pyVmomi import vim
|
from pyVmomi import vim
|
||||||
|
|
||||||
from openvixdisklib import nfc_auth
|
from openvixdisklib import nfc_auth, nfc_open
|
||||||
from openvixdisklib import nfc_open
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,21 +49,20 @@ def _nfc_compression(flags: int) -> int:
|
|||||||
alg = flags & (
|
alg = flags & (
|
||||||
VIXDISKLIB_FLAG_OPEN_COMPRESSION_ZLIB
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_ZLIB
|
||||||
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ
|
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ
|
||||||
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ)
|
| VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ
|
||||||
|
)
|
||||||
if alg == 0:
|
if alg == 0:
|
||||||
return nfc_open.NFC_COMPRESSION_NONE
|
return nfc_open.NFC_COMPRESSION_NONE
|
||||||
if alg == VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ:
|
if alg == VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ:
|
||||||
return nfc_open.NFC_COMPRESSION_FASTLZ
|
return nfc_open.NFC_COMPRESSION_FASTLZ
|
||||||
if alg & (alg - 1):
|
if alg & (alg - 1):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Cannot set two or more NBD compression algorithms at the "
|
"Cannot set two or more NBD compression algorithms at the same time"
|
||||||
"same time")
|
)
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(f"NBD compression open flag 0x{alg:x} is not supported")
|
||||||
f"NBD compression open flag 0x{alg:x} is not supported")
|
|
||||||
|
|
||||||
|
|
||||||
VIX_SUPPORTED_COMPATIBILITY_MODES = [
|
VIX_SUPPORTED_COMPATIBILITY_MODES = ["6.0", "6.5", "6.7", "7.0", "8.0"]
|
||||||
"6.0", "6.5", "6.7", "7.0", "8.0"]
|
|
||||||
|
|
||||||
|
|
||||||
def get_buffer(size: int):
|
def get_buffer(size: int):
|
||||||
@@ -74,8 +72,7 @@ def get_buffer(size: int):
|
|||||||
|
|
||||||
def _parse_vm_moref(vmx_spec: Optional[str]) -> str:
|
def _parse_vm_moref(vmx_spec: Optional[str]) -> str:
|
||||||
if not vmx_spec:
|
if not vmx_spec:
|
||||||
raise ValueError(
|
raise ValueError("vmx_spec is required (for example 'moref=vm-13098')")
|
||||||
"vmx_spec is required (for example 'moref=vm-13098')")
|
|
||||||
if "=" in vmx_spec:
|
if "=" in vmx_spec:
|
||||||
kind, value = vmx_spec.split("=", 1)
|
kind, value = vmx_spec.split("=", 1)
|
||||||
if kind.lower() != "moref" or not value:
|
if kind.lower() != "moref" or not value:
|
||||||
@@ -97,7 +94,8 @@ def _select_transport(transport_modes: Optional[str]) -> str:
|
|||||||
if mode in ("nbdssl", "nbd"):
|
if mode in ("nbdssl", "nbd"):
|
||||||
return mode
|
return mode
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"supported transports are nbdssl and nbd, got {transport_modes!r}")
|
f"supported transports are nbdssl and nbd, got {transport_modes!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _Connection:
|
class _Connection:
|
||||||
@@ -111,7 +109,8 @@ class _Connection:
|
|||||||
thumbprint: Optional[str],
|
thumbprint: Optional[str],
|
||||||
allow_untrusted: bool,
|
allow_untrusted: bool,
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
transport_mode: str) -> None:
|
transport_mode: str,
|
||||||
|
) -> None:
|
||||||
self.si = si
|
self.si = si
|
||||||
self.vm_moref = vm_moref
|
self.vm_moref = vm_moref
|
||||||
self.snapshot_ref = snapshot_ref
|
self.snapshot_ref = snapshot_ref
|
||||||
@@ -124,11 +123,7 @@ class _Connection:
|
|||||||
class _DiskHandle:
|
class _DiskHandle:
|
||||||
"""Opened NFC disk plus the authd TLS socket it was taken from."""
|
"""Opened NFC disk plus the authd TLS socket it was taken from."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, disk: nfc_open.NfcDisk, authd_sock, transport_mode: str) -> None:
|
||||||
self,
|
|
||||||
disk: nfc_open.NfcDisk,
|
|
||||||
authd_sock,
|
|
||||||
transport_mode: str) -> None:
|
|
||||||
self.disk = disk
|
self.disk = disk
|
||||||
self.authd_sock = authd_sock
|
self.authd_sock = authd_sock
|
||||||
self.transport_mode = transport_mode
|
self.transport_mode = transport_mode
|
||||||
@@ -140,7 +135,8 @@ class VixDiskLibHandle:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config_path: Optional[str] = None,
|
config_path: Optional[str] = None,
|
||||||
vixdisklib_compatibility_version: Optional[str] = None) -> None:
|
vixdisklib_compatibility_version: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
"""Accept the VDDK wrapper constructor; no native library is loaded.
|
"""Accept the VDDK wrapper constructor; no native library is loaded.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -164,18 +160,20 @@ class VixDiskLibHandle:
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
|
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
|
||||||
"compatibility mode must be of the form "
|
"compatibility mode must be of the form "
|
||||||
"'$major.$minor'" % version) from ex
|
"'$major.$minor'" % version
|
||||||
|
) from ex
|
||||||
version_used = version
|
version_used = version
|
||||||
break
|
break
|
||||||
|
|
||||||
if not version_used:
|
if not version_used:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Could not initialize vixDiskLib with any of the following "
|
"Could not initialize vixDiskLib with any of the following "
|
||||||
"versions: %s" % target_versions)
|
"versions: %s" % target_versions
|
||||||
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Successfully initialized vixDiskLib with target version '%s'",
|
"Successfully initialized vixDiskLib with target version '%s'", version_used
|
||||||
version_used)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_vix_disklib_name(cls) -> str:
|
def get_vix_disklib_name(cls) -> str:
|
||||||
@@ -204,7 +202,8 @@ class VixDiskLibHandle:
|
|||||||
read_only: bool = True,
|
read_only: bool = True,
|
||||||
transport_modes: Optional[str] = None,
|
transport_modes: Optional[str] = None,
|
||||||
port: int = 443,
|
port: int = 443,
|
||||||
allow_untrusted: bool = False) -> Iterator[_Connection]:
|
allow_untrusted: bool = False,
|
||||||
|
) -> Iterator[_Connection]:
|
||||||
"""Login to vCenter/ESXi. Matches ``VixDiskLib_ConnectEx``.
|
"""Login to vCenter/ESXi. Matches ``VixDiskLib_ConnectEx``.
|
||||||
|
|
||||||
The NFC ticket and authd handshake are deferred to ``open``, as in
|
The NFC ticket and authd handshake are deferred to ``open``, as in
|
||||||
@@ -235,11 +234,17 @@ class VixDiskLibHandle:
|
|||||||
password,
|
password,
|
||||||
port=port,
|
port=port,
|
||||||
thumbprint=thumbprint,
|
thumbprint=thumbprint,
|
||||||
allow_untrusted=allow_untrusted or not thumbprint)
|
allow_untrusted=allow_untrusted or not thumbprint,
|
||||||
|
)
|
||||||
conn = _Connection(
|
conn = _Connection(
|
||||||
si, vm_moref, snapshot_ref, thumbprint,
|
si,
|
||||||
allow_untrusted or not thumbprint, read_only,
|
vm_moref,
|
||||||
transport_mode)
|
snapshot_ref,
|
||||||
|
thumbprint,
|
||||||
|
allow_untrusted or not thumbprint,
|
||||||
|
read_only,
|
||||||
|
transport_mode,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
yield conn
|
yield conn
|
||||||
finally:
|
finally:
|
||||||
@@ -250,7 +255,8 @@ class VixDiskLibHandle:
|
|||||||
self,
|
self,
|
||||||
conn: _Connection,
|
conn: _Connection,
|
||||||
disk_path: str,
|
disk_path: str,
|
||||||
flags: int = VIXDISKLIB_FLAG_OPEN_READ_ONLY) -> Iterator[_DiskHandle]:
|
flags: int = VIXDISKLIB_FLAG_OPEN_READ_ONLY,
|
||||||
|
) -> Iterator[_DiskHandle]:
|
||||||
"""Open ``disk_path`` over NFC. Matches ``VixDiskLib_Open``.
|
"""Open ``disk_path`` over NFC. Matches ``VixDiskLib_Open``.
|
||||||
|
|
||||||
Read-only opens request ``NfcGetVmFiles`` (VM only). The VMDK
|
Read-only opens request ``NfcGetVmFiles`` (VM only). The VMDK
|
||||||
@@ -271,22 +277,21 @@ class VixDiskLibHandle:
|
|||||||
compression = _nfc_compression(flags)
|
compression = _nfc_compression(flags)
|
||||||
read_only = bool(flags & VIXDISKLIB_FLAG_OPEN_READ_ONLY)
|
read_only = bool(flags & VIXDISKLIB_FLAG_OPEN_READ_ONLY)
|
||||||
if not read_only and conn.read_only:
|
if not read_only and conn.read_only:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError("ConnectEx was read-only; cannot open for write")
|
||||||
"ConnectEx was read-only; cannot open for write")
|
|
||||||
|
|
||||||
vm = vim.VirtualMachine(conn.vm_moref, conn.si._stub)
|
vm = vim.VirtualMachine(conn.vm_moref, conn.si._stub)
|
||||||
nfc_ssl = conn.transport_mode == "nbdssl"
|
nfc_ssl = conn.transport_mode == "nbdssl"
|
||||||
ticket = nfc_auth.get_nfc_ticket(
|
ticket = nfc_auth.get_nfc_ticket(
|
||||||
conn.si, vm, read_only=read_only,
|
conn.si, vm, read_only=read_only, disk_path=None if read_only else disk_path
|
||||||
disk_path=None if read_only else disk_path)
|
)
|
||||||
authd_sock = nfc_auth.connect_authd(
|
authd_sock = nfc_auth.connect_authd(
|
||||||
ticket, allow_untrusted=conn.allow_untrusted, nfc_ssl=nfc_ssl)
|
ticket, allow_untrusted=conn.allow_untrusted, nfc_ssl=nfc_ssl
|
||||||
session = nfc_auth.NfcAuthSession(
|
)
|
||||||
conn.si, ticket, authd_sock, nfc_ssl=nfc_ssl)
|
session = nfc_auth.NfcAuthSession(conn.si, ticket, authd_sock, nfc_ssl=nfc_ssl)
|
||||||
try:
|
try:
|
||||||
disk = nfc_open.open_disk(
|
disk = nfc_open.open_disk(
|
||||||
session, disk_path, read_only=read_only,
|
session, disk_path, read_only=read_only, compression=compression
|
||||||
compression=compression)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
authd_sock.close()
|
authd_sock.close()
|
||||||
raise
|
raise
|
||||||
@@ -301,7 +306,8 @@ class VixDiskLibHandle:
|
|||||||
disk_handle: _DiskHandle,
|
disk_handle: _DiskHandle,
|
||||||
start_sector: int,
|
start_sector: int,
|
||||||
num_sectors: int,
|
num_sectors: int,
|
||||||
buf: Union[ctypes.Array, bytearray, memoryview]) -> None:
|
buf: Union[ctypes.Array, bytearray, memoryview],
|
||||||
|
) -> None:
|
||||||
"""Read ``num_sectors`` from ``start_sector`` into ``buf``.
|
"""Read ``num_sectors`` from ``start_sector`` into ``buf``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -313,9 +319,8 @@ class VixDiskLibHandle:
|
|||||||
data = disk_handle.disk.read(start_sector, num_sectors)
|
data = disk_handle.disk.read(start_sector, num_sectors)
|
||||||
if isinstance(buf, (bytearray, memoryview)):
|
if isinstance(buf, (bytearray, memoryview)):
|
||||||
if len(buf) < len(data):
|
if len(buf) < len(data):
|
||||||
raise Exception(
|
raise Exception(f"read buffer is {len(buf)} bytes, need {len(data)}")
|
||||||
f"read buffer is {len(buf)} bytes, need {len(data)}")
|
buf[: len(data)] = data
|
||||||
buf[:len(data)] = data
|
|
||||||
return
|
return
|
||||||
ctypes.memmove(buf, data, len(data))
|
ctypes.memmove(buf, data, len(data))
|
||||||
|
|
||||||
@@ -324,7 +329,8 @@ class VixDiskLibHandle:
|
|||||||
disk_handle: _DiskHandle,
|
disk_handle: _DiskHandle,
|
||||||
start_sector: int,
|
start_sector: int,
|
||||||
num_sectors: int,
|
num_sectors: int,
|
||||||
buf: Union[ctypes.Array, bytes, bytearray, memoryview]) -> None:
|
buf: Union[ctypes.Array, bytes, bytearray, memoryview],
|
||||||
|
) -> None:
|
||||||
"""Write ``num_sectors`` from ``buf`` starting at ``start_sector``.
|
"""Write ``num_sectors`` from ``buf`` starting at ``start_sector``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
+59
-48
@@ -20,8 +20,7 @@ from pyVmomi import vim
|
|||||||
from openvixdisklib import nfc_auth
|
from openvixdisklib import nfc_auth
|
||||||
from openvixdisklib.nfc_auth import NfcAuthSession
|
from openvixdisklib.nfc_auth import NfcAuthSession
|
||||||
|
|
||||||
_REPO_ROOT = os.path.abspath(
|
_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
||||||
_CONFIG_PATH = os.path.join(_REPO_ROOT, ".test_config.yaml")
|
_CONFIG_PATH = os.path.join(_REPO_ROOT, ".test_config.yaml")
|
||||||
_CONFIG_KEYS = (
|
_CONFIG_KEYS = (
|
||||||
"host",
|
"host",
|
||||||
@@ -61,9 +60,8 @@ class LabEnv:
|
|||||||
disk_path: str
|
disk_path: str
|
||||||
|
|
||||||
def authenticate(
|
def authenticate(
|
||||||
self,
|
self, read_only: bool = True, nfc_ssl: bool = True
|
||||||
read_only: bool = True,
|
) -> NfcAuthSession:
|
||||||
nfc_ssl: bool = True) -> NfcAuthSession:
|
|
||||||
"""Login to the lab vCenter and complete NFC authd for the temp VM."""
|
"""Login to the lab vCenter and complete NFC authd for the temp VM."""
|
||||||
return nfc_auth.authenticate(
|
return nfc_auth.authenticate(
|
||||||
host=self.host,
|
host=self.host,
|
||||||
@@ -74,10 +72,12 @@ class LabEnv:
|
|||||||
allow_untrusted=self.allow_untrusted,
|
allow_untrusted=self.allow_untrusted,
|
||||||
disk_path=None if read_only else self.disk_path,
|
disk_path=None if read_only else self.disk_path,
|
||||||
read_only=read_only,
|
read_only=read_only,
|
||||||
nfc_ssl=nfc_ssl)
|
nfc_ssl=nfc_ssl,
|
||||||
|
)
|
||||||
|
|
||||||
def vixdisklib_connect_kwargs(
|
def vixdisklib_connect_kwargs(
|
||||||
self, extra: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
self, extra: Optional[dict[str, Any]] = None
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Return common ``VixDiskLib_ConnectEx`` arguments for the temp VM."""
|
"""Return common ``VixDiskLib_ConnectEx`` arguments for the temp VM."""
|
||||||
kwargs: dict[str, Any] = {
|
kwargs: dict[str, Any] = {
|
||||||
"server_name": self.host,
|
"server_name": self.host,
|
||||||
@@ -107,7 +107,8 @@ def ensure_vddk_library_path() -> None:
|
|||||||
parts = [p for p in current.split(":") if p]
|
parts = [p for p in current.split(":") if p]
|
||||||
if VDDK_DIR not in parts:
|
if VDDK_DIR not in parts:
|
||||||
os.environ["LD_LIBRARY_PATH"] = (
|
os.environ["LD_LIBRARY_PATH"] = (
|
||||||
VDDK_DIR if not current else f"{VDDK_DIR}:{current}")
|
VDDK_DIR if not current else f"{VDDK_DIR}:{current}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def require_vddk() -> None:
|
def require_vddk() -> None:
|
||||||
@@ -123,13 +124,13 @@ def _load_test_config() -> dict[str, Any]:
|
|||||||
if not os.path.isfile(_CONFIG_PATH):
|
if not os.path.isfile(_CONFIG_PATH):
|
||||||
pytest.skip(
|
pytest.skip(
|
||||||
"integration tests need .test_config.yaml in the repo "
|
"integration tests need .test_config.yaml in the repo "
|
||||||
"root; see README.md for a sample")
|
"root; see README.md for a sample"
|
||||||
|
)
|
||||||
with open(_CONFIG_PATH, encoding="utf-8") as config_file:
|
with open(_CONFIG_PATH, encoding="utf-8") as config_file:
|
||||||
data = yaml.safe_load(config_file) or {}
|
data = yaml.safe_load(config_file) or {}
|
||||||
missing = [key for key in _CONFIG_KEYS if key not in data]
|
missing = [key for key in _CONFIG_KEYS if key not in data]
|
||||||
if missing:
|
if missing:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"{_CONFIG_PATH} is missing keys: {', '.join(missing)}")
|
||||||
f"{_CONFIG_PATH} is missing keys: {', '.join(missing)}")
|
|
||||||
return {
|
return {
|
||||||
"host": str(data["host"]),
|
"host": str(data["host"]),
|
||||||
"port": int(data["port"]),
|
"port": int(data["port"]),
|
||||||
@@ -147,23 +148,23 @@ def _connect_vim(
|
|||||||
password: str,
|
password: str,
|
||||||
port: int,
|
port: int,
|
||||||
thumbprint: str,
|
thumbprint: str,
|
||||||
allow_untrusted: bool) -> vim.ServiceInstance:
|
allow_untrusted: bool,
|
||||||
|
) -> vim.ServiceInstance:
|
||||||
return nfc_auth.connect_vim(
|
return nfc_auth.connect_vim(
|
||||||
host,
|
host,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
port=port,
|
port=port,
|
||||||
thumbprint=thumbprint,
|
thumbprint=thumbprint,
|
||||||
allow_untrusted=allow_untrusted)
|
allow_untrusted=allow_untrusted,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_task(task: vim.Task) -> Any:
|
def _wait_for_task(task: vim.Task) -> Any:
|
||||||
deadline = time.monotonic() + _TASK_TIMEOUT_S
|
deadline = time.monotonic() + _TASK_TIMEOUT_S
|
||||||
while task.info.state in (
|
while task.info.state in (vim.TaskInfo.State.running, vim.TaskInfo.State.queued):
|
||||||
vim.TaskInfo.State.running, vim.TaskInfo.State.queued):
|
|
||||||
if time.monotonic() > deadline:
|
if time.monotonic() > deadline:
|
||||||
raise TimeoutError(
|
raise TimeoutError(f"timed out waiting for vSphere task {task}")
|
||||||
f"timed out waiting for vSphere task {task}")
|
|
||||||
time.sleep(_TASK_POLL_S)
|
time.sleep(_TASK_POLL_S)
|
||||||
if task.info.state != vim.TaskInfo.State.success:
|
if task.info.state != vim.TaskInfo.State.success:
|
||||||
raise RuntimeError(f"vSphere task failed: {task.info.error}")
|
raise RuntimeError(f"vSphere task failed: {task.info.error}")
|
||||||
@@ -171,26 +172,28 @@ def _wait_for_task(task: vim.Task) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def _find_datacenter(
|
def _find_datacenter(
|
||||||
content: vim.ServiceInstanceContent,
|
content: vim.ServiceInstanceContent, datacenter_name: str
|
||||||
datacenter_name: str) -> vim.Datacenter:
|
) -> vim.Datacenter:
|
||||||
matches = [
|
matches = [
|
||||||
entity for entity in content.rootFolder.childEntity
|
entity
|
||||||
if isinstance(entity, vim.Datacenter)
|
for entity in content.rootFolder.childEntity
|
||||||
and entity.name == datacenter_name]
|
if isinstance(entity, vim.Datacenter) and entity.name == datacenter_name
|
||||||
|
]
|
||||||
if not matches:
|
if not matches:
|
||||||
raise RuntimeError(f"datacenter {datacenter_name!r} not found")
|
raise RuntimeError(f"datacenter {datacenter_name!r} not found")
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
def _find_datastore(
|
def _find_datastore(datacenter: vim.Datacenter, datastore_name: str) -> vim.Datastore:
|
||||||
datacenter: vim.Datacenter, datastore_name: str) -> vim.Datastore:
|
|
||||||
matches = [
|
matches = [
|
||||||
datastore for datastore in datacenter.datastore
|
datastore
|
||||||
if datastore.name == datastore_name]
|
for datastore in datacenter.datastore
|
||||||
|
if datastore.name == datastore_name
|
||||||
|
]
|
||||||
if not matches:
|
if not matches:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"datastore {datastore_name!r} not found in "
|
f"datastore {datastore_name!r} not found in datacenter {datacenter.name!r}"
|
||||||
f"datacenter {datacenter.name!r}")
|
)
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
@@ -200,17 +203,14 @@ def _vm_config_spec(vm_name: str, datastore_name: str) -> vim.vm.ConfigSpec:
|
|||||||
config.guestId = "otherGuest64"
|
config.guestId = "otherGuest64"
|
||||||
config.memoryMB = 128
|
config.memoryMB = 128
|
||||||
config.numCPUs = 1
|
config.numCPUs = 1
|
||||||
config.files = vim.vm.FileInfo(
|
config.files = vim.vm.FileInfo(vmPathName=f"[{datastore_name}]")
|
||||||
vmPathName=f"[{datastore_name}]")
|
|
||||||
|
|
||||||
controller = vim.vm.device.ParaVirtualSCSIController()
|
controller = vim.vm.device.ParaVirtualSCSIController()
|
||||||
controller.key = 1000
|
controller.key = 1000
|
||||||
controller.busNumber = 0
|
controller.busNumber = 0
|
||||||
controller.sharedBus = (
|
controller.sharedBus = vim.vm.device.VirtualSCSIController.Sharing.noSharing
|
||||||
vim.vm.device.VirtualSCSIController.Sharing.noSharing)
|
|
||||||
controller_spec = vim.vm.device.VirtualDeviceSpec()
|
controller_spec = vim.vm.device.VirtualDeviceSpec()
|
||||||
controller_spec.operation = (
|
controller_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
|
||||||
vim.vm.device.VirtualDeviceSpec.Operation.add)
|
|
||||||
controller_spec.device = controller
|
controller_spec.device = controller
|
||||||
|
|
||||||
backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
|
backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
|
||||||
@@ -225,8 +225,7 @@ def _vm_config_spec(vm_name: str, datastore_name: str) -> vim.vm.ConfigSpec:
|
|||||||
disk.backing = backing
|
disk.backing = backing
|
||||||
disk_spec = vim.vm.device.VirtualDeviceSpec()
|
disk_spec = vim.vm.device.VirtualDeviceSpec()
|
||||||
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
|
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
|
||||||
disk_spec.fileOperation = (
|
disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.create
|
||||||
vim.vm.device.VirtualDeviceSpec.FileOperation.create)
|
|
||||||
disk_spec.device = disk
|
disk_spec.device = disk
|
||||||
|
|
||||||
config.deviceChange = [controller_spec, disk_spec]
|
config.deviceChange = [controller_spec, disk_spec]
|
||||||
@@ -238,8 +237,13 @@ def create_lab_vm() -> LabEnv:
|
|||||||
cfg = _load_test_config()
|
cfg = _load_test_config()
|
||||||
thumbprint = nfc_auth.get_ssl_cert_thumbprint(cfg["host"], cfg["port"])
|
thumbprint = nfc_auth.get_ssl_cert_thumbprint(cfg["host"], cfg["port"])
|
||||||
si = _connect_vim(
|
si = _connect_vim(
|
||||||
cfg["host"], cfg["username"], cfg["password"], cfg["port"],
|
cfg["host"],
|
||||||
thumbprint, cfg["allow_untrusted"])
|
cfg["username"],
|
||||||
|
cfg["password"],
|
||||||
|
cfg["port"],
|
||||||
|
thumbprint,
|
||||||
|
cfg["allow_untrusted"],
|
||||||
|
)
|
||||||
vm = None
|
vm = None
|
||||||
try:
|
try:
|
||||||
content = si.RetrieveContent()
|
content = si.RetrieveContent()
|
||||||
@@ -247,22 +251,23 @@ def create_lab_vm() -> LabEnv:
|
|||||||
datastore = _find_datastore(datacenter, cfg["datastore"])
|
datastore = _find_datastore(datacenter, cfg["datastore"])
|
||||||
if not datastore.host:
|
if not datastore.host:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"datastore {cfg['datastore']!r} is not mounted on any host")
|
f"datastore {cfg['datastore']!r} is not mounted on any host"
|
||||||
|
)
|
||||||
host = datastore.host[0].key
|
host = datastore.host[0].key
|
||||||
pool = host.parent.resourcePool
|
pool = host.parent.resourcePool
|
||||||
vm_name = _LAB_VM_PREFIX + uuid.uuid4().hex[:12]
|
vm_name = _LAB_VM_PREFIX + uuid.uuid4().hex[:12]
|
||||||
vm = _wait_for_task(
|
vm = _wait_for_task(
|
||||||
datacenter.vmFolder.CreateVM_Task(
|
datacenter.vmFolder.CreateVM_Task(
|
||||||
config=_vm_config_spec(vm_name, datastore.name),
|
config=_vm_config_spec(vm_name, datastore.name), pool=pool, host=host
|
||||||
pool=pool,
|
)
|
||||||
host=host))
|
)
|
||||||
disks = [
|
disks = [
|
||||||
device.backing.fileName
|
device.backing.fileName
|
||||||
for device in vm.config.hardware.device
|
for device in vm.config.hardware.device
|
||||||
if isinstance(device, vim.vm.device.VirtualDisk)]
|
if isinstance(device, vim.vm.device.VirtualDisk)
|
||||||
|
]
|
||||||
if not disks:
|
if not disks:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"temporary VM {vm_name!r} has no virtual disks")
|
||||||
f"temporary VM {vm_name!r} has no virtual disks")
|
|
||||||
return LabEnv(
|
return LabEnv(
|
||||||
host=cfg["host"],
|
host=cfg["host"],
|
||||||
port=cfg["port"],
|
port=cfg["port"],
|
||||||
@@ -274,7 +279,8 @@ def create_lab_vm() -> LabEnv:
|
|||||||
thumbprint=thumbprint,
|
thumbprint=thumbprint,
|
||||||
vm_moref=vm._moId,
|
vm_moref=vm._moId,
|
||||||
vmx_spec=f"moref={vm._moId}",
|
vmx_spec=f"moref={vm._moId}",
|
||||||
disk_path=disks[0])
|
disk_path=disks[0],
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
if vm is not None:
|
if vm is not None:
|
||||||
try:
|
try:
|
||||||
@@ -289,8 +295,13 @@ def create_lab_vm() -> LabEnv:
|
|||||||
def destroy_lab_vm(lab: LabEnv) -> None:
|
def destroy_lab_vm(lab: LabEnv) -> None:
|
||||||
"""Power off and delete the temporary lab VM if it still exists."""
|
"""Power off and delete the temporary lab VM if it still exists."""
|
||||||
si = _connect_vim(
|
si = _connect_vim(
|
||||||
lab.host, lab.username, lab.password, lab.port,
|
lab.host,
|
||||||
lab.thumbprint, lab.allow_untrusted)
|
lab.username,
|
||||||
|
lab.password,
|
||||||
|
lab.port,
|
||||||
|
lab.thumbprint,
|
||||||
|
lab.allow_untrusted,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
vm = vim.VirtualMachine(lab.vm_moref, si._stub)
|
vm = vim.VirtualMachine(lab.vm_moref, si._stub)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import pytest
|
|||||||
|
|
||||||
from openvixdisklib import openvixdisklib as open_vix
|
from openvixdisklib import openvixdisklib as open_vix
|
||||||
from tests.integration import vixdisklib
|
from tests.integration import vixdisklib
|
||||||
from tests.integration.base import (
|
from tests.integration.base import SECTOR_AT_1GB, SECTOR_SIZE, LabEnv, pattern_bytes
|
||||||
LabEnv, SECTOR_AT_1GB, SECTOR_SIZE, pattern_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
def _connect_extra(lab: LabEnv, module: Any) -> Optional[dict[str, Any]]:
|
def _connect_extra(lab: LabEnv, module: Any) -> Optional[dict[str, Any]]:
|
||||||
@@ -21,14 +20,12 @@ def _connect_extra(lab: LabEnv, module: Any) -> Optional[dict[str, Any]]:
|
|||||||
|
|
||||||
|
|
||||||
def _write_sectors(
|
def _write_sectors(
|
||||||
lab: LabEnv,
|
lab: LabEnv, module: Any, payloads: dict[int, bytes], flags: int = 0
|
||||||
module: Any,
|
) -> None:
|
||||||
payloads: dict[int, bytes],
|
|
||||||
flags: int = 0) -> None:
|
|
||||||
"""Write one sector at each index using a vixdisklib-compatible module."""
|
"""Write one sector at each index using a vixdisklib-compatible module."""
|
||||||
handle = module.VixDiskLibHandle(
|
handle = module.VixDiskLibHandle(
|
||||||
vixdisklib_compatibility_version="8.0",
|
vixdisklib_compatibility_version="8.0", config_path=None
|
||||||
config_path=None)
|
)
|
||||||
buf = module.get_buffer(SECTOR_SIZE)
|
buf = module.get_buffer(SECTOR_SIZE)
|
||||||
kwargs = lab.vixdisklib_connect_kwargs(_connect_extra(lab, module))
|
kwargs = lab.vixdisklib_connect_kwargs(_connect_extra(lab, module))
|
||||||
with handle.connect(**kwargs) as conn:
|
with handle.connect(**kwargs) as conn:
|
||||||
@@ -39,14 +36,12 @@ def _write_sectors(
|
|||||||
|
|
||||||
|
|
||||||
def _read_sectors(
|
def _read_sectors(
|
||||||
lab: LabEnv,
|
lab: LabEnv, module: Any, sectors: tuple[int, ...], flags: int = 0
|
||||||
module: Any,
|
) -> dict[int, bytes]:
|
||||||
sectors: tuple[int, ...],
|
|
||||||
flags: int = 0) -> dict[int, bytes]:
|
|
||||||
"""Read one sector at each index using a vixdisklib-compatible module."""
|
"""Read one sector at each index using a vixdisklib-compatible module."""
|
||||||
handle = module.VixDiskLibHandle(
|
handle = module.VixDiskLibHandle(
|
||||||
vixdisklib_compatibility_version="8.0",
|
vixdisklib_compatibility_version="8.0", config_path=None
|
||||||
config_path=None)
|
)
|
||||||
buf = module.get_buffer(SECTOR_SIZE)
|
buf = module.get_buffer(SECTOR_SIZE)
|
||||||
result: dict[int, bytes] = {}
|
result: dict[int, bytes] = {}
|
||||||
kwargs = lab.vixdisklib_connect_kwargs(_connect_extra(lab, module))
|
kwargs = lab.vixdisklib_connect_kwargs(_connect_extra(lab, module))
|
||||||
@@ -60,26 +55,26 @@ def _read_sectors(
|
|||||||
|
|
||||||
|
|
||||||
def _assert_both_read(
|
def _assert_both_read(
|
||||||
lab: LabEnv,
|
lab: LabEnv, sectors: tuple[int, ...], expected: dict[int, bytes], flags: int = 0
|
||||||
sectors: tuple[int, ...],
|
) -> None:
|
||||||
expected: dict[int, bytes],
|
|
||||||
flags: int = 0) -> None:
|
|
||||||
vddk_data = _read_sectors(lab, vixdisklib, sectors, flags=flags)
|
vddk_data = _read_sectors(lab, vixdisklib, sectors, flags=flags)
|
||||||
replacement = _read_sectors(lab, open_vix, sectors, flags=flags)
|
replacement = _read_sectors(lab, open_vix, sectors, flags=flags)
|
||||||
for start in sectors:
|
for start in sectors:
|
||||||
assert vddk_data[start] == expected[start], (
|
assert vddk_data[start] == expected[start], f"VDDK mismatch at sector {start}"
|
||||||
f"VDDK mismatch at sector {start}")
|
|
||||||
assert replacement[start] == expected[start], (
|
assert replacement[start] == expected[start], (
|
||||||
f"openvixdisklib mismatch at sector {start}")
|
f"openvixdisklib mismatch at sector {start}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestCrosscheck:
|
class TestCrosscheck:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"open_flags",
|
"open_flags",
|
||||||
[0, vixdisklib.VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ],
|
[0, vixdisklib.VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ],
|
||||||
ids=["plain", "fastlz"])
|
ids=["plain", "fastlz"],
|
||||||
|
)
|
||||||
def test_openvixdisklib_matches_vddk_sectors(
|
def test_openvixdisklib_matches_vddk_sectors(
|
||||||
self, lab: LabEnv, vddk: None, open_flags: int) -> None:
|
self, lab: LabEnv, vddk: None, open_flags: int
|
||||||
|
) -> None:
|
||||||
"""Writes from either library must be visible to both readers."""
|
"""Writes from either library must be visible to both readers."""
|
||||||
sectors = (0, 1, SECTOR_AT_1GB)
|
sectors = (0, 1, SECTOR_AT_1GB)
|
||||||
vddk_payloads = {
|
vddk_payloads = {
|
||||||
|
|||||||
@@ -6,24 +6,25 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from openvixdisklib import nfc_open
|
from openvixdisklib import nfc_open
|
||||||
from tests.integration.base import LabEnv, SECTOR_SIZE, pattern_bytes
|
from tests.integration.base import SECTOR_SIZE, LabEnv, pattern_bytes
|
||||||
|
|
||||||
|
|
||||||
class TestNfcOpen:
|
class TestNfcOpen:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("nfc_ssl", [True, False], ids=["nbdssl", "nbd"])
|
||||||
"nfc_ssl", [True, False], ids=["nbdssl", "nbd"])
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"compression",
|
"compression",
|
||||||
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
||||||
ids=["plain", "fastlz"])
|
ids=["plain", "fastlz"],
|
||||||
|
)
|
||||||
def test_open_disk_and_read_first_sector(
|
def test_open_disk_and_read_first_sector(
|
||||||
self, lab: LabEnv, nfc_ssl: bool, compression: int) -> None:
|
self, lab: LabEnv, nfc_ssl: bool, compression: int
|
||||||
|
) -> None:
|
||||||
"""Open the temp VMDK, write sector 0, and read it back."""
|
"""Open the temp VMDK, write sector 0, and read it back."""
|
||||||
expected = pattern_bytes(SECTOR_SIZE, b"NFC-OPEN-S0")
|
expected = pattern_bytes(SECTOR_SIZE, b"NFC-OPEN-S0")
|
||||||
with lab.authenticate(read_only=False, nfc_ssl=nfc_ssl) as session:
|
with lab.authenticate(read_only=False, nfc_ssl=nfc_ssl) as session:
|
||||||
with nfc_open.open_disk(
|
with nfc_open.open_disk(
|
||||||
session, lab.disk_path, read_only=False,
|
session, lab.disk_path, read_only=False, compression=compression
|
||||||
compression=compression) as disk:
|
) as disk:
|
||||||
assert disk.path == lab.disk_path
|
assert disk.path == lab.disk_path
|
||||||
assert disk.handle > 0
|
assert disk.handle > 0
|
||||||
assert disk.sector_size == SECTOR_SIZE
|
assert disk.sector_size == SECTOR_SIZE
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import os
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from openvixdisklib import nfc_open
|
from openvixdisklib import nfc_open
|
||||||
from tests.integration.base import LabEnv, SECTOR_SIZE, pattern_bytes
|
from tests.integration.base import SECTOR_SIZE, LabEnv, pattern_bytes
|
||||||
|
|
||||||
_32MIB = 32 * 1024 * 1024
|
_32MIB = 32 * 1024 * 1024
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ class TestNfcReadWrite:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"compression",
|
"compression",
|
||||||
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
||||||
ids=["plain", "fastlz"])
|
ids=["plain", "fastlz"],
|
||||||
def test_sector_writes_and_reads(
|
)
|
||||||
self, lab: LabEnv, compression: int) -> None:
|
def test_sector_writes_and_reads(self, lab: LabEnv, compression: int) -> None:
|
||||||
"""Write known patterns and read them back at several ranges."""
|
"""Write known patterns and read them back at several ranges."""
|
||||||
ranges = [
|
ranges = [
|
||||||
(0, 1),
|
(0, 1),
|
||||||
@@ -32,8 +32,8 @@ class TestNfcReadWrite:
|
|||||||
]
|
]
|
||||||
with lab.authenticate(read_only=False) as session:
|
with lab.authenticate(read_only=False) as session:
|
||||||
with nfc_open.open_disk(
|
with nfc_open.open_disk(
|
||||||
session, lab.disk_path, read_only=False,
|
session, lab.disk_path, read_only=False, compression=compression
|
||||||
compression=compression) as disk:
|
) as disk:
|
||||||
for start, n_sectors in ranges:
|
for start, n_sectors in ranges:
|
||||||
length = n_sectors * SECTOR_SIZE
|
length = n_sectors * SECTOR_SIZE
|
||||||
seed = f"NFC-R{start}:{n_sectors}-".encode()
|
seed = f"NFC-R{start}:{n_sectors}-".encode()
|
||||||
@@ -53,29 +53,29 @@ class TestNfcReadWrite:
|
|||||||
assert disk.read(1, 1) == two_to_write[SECTOR_SIZE:]
|
assert disk.read(1, 1) == two_to_write[SECTOR_SIZE:]
|
||||||
|
|
||||||
big_seed = b"NFC-129-SECTOR-WRITE"
|
big_seed = b"NFC-129-SECTOR-WRITE"
|
||||||
big_to_write = pattern_bytes(
|
big_to_write = pattern_bytes(129 * SECTOR_SIZE, big_seed)
|
||||||
129 * SECTOR_SIZE, big_seed)
|
|
||||||
disk.write(0, 129, big_to_write)
|
disk.write(0, 129, big_to_write)
|
||||||
big_got = disk.read(0, 129)
|
big_got = disk.read(0, 129)
|
||||||
assert big_got is not big_to_write
|
assert big_got is not big_to_write
|
||||||
assert big_got == big_to_write
|
assert big_got == big_to_write
|
||||||
assert (
|
assert (
|
||||||
big_got[SECTOR_SIZE:2 * SECTOR_SIZE]
|
big_got[SECTOR_SIZE : 2 * SECTOR_SIZE]
|
||||||
== big_to_write[SECTOR_SIZE:2 * SECTOR_SIZE])
|
== big_to_write[SECTOR_SIZE : 2 * SECTOR_SIZE]
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"compression",
|
"compression",
|
||||||
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
[nfc_open.NFC_COMPRESSION_NONE, nfc_open.NFC_COMPRESSION_FASTLZ],
|
||||||
ids=["plain", "fastlz"])
|
ids=["plain", "fastlz"],
|
||||||
def test_write_and_read_32mb(
|
)
|
||||||
self, lab: LabEnv, compression: int) -> None:
|
def test_write_and_read_32mb(self, lab: LabEnv, compression: int) -> None:
|
||||||
"""Write 32 MiB (512 AIO chunks) and read it back in one request."""
|
"""Write 32 MiB (512 AIO chunks) and read it back in one request."""
|
||||||
n_sectors = _32MIB // SECTOR_SIZE
|
n_sectors = _32MIB // SECTOR_SIZE
|
||||||
to_write = os.urandom(_32MIB)
|
to_write = os.urandom(_32MIB)
|
||||||
with lab.authenticate(read_only=False) as session:
|
with lab.authenticate(read_only=False) as session:
|
||||||
with nfc_open.open_disk(
|
with nfc_open.open_disk(
|
||||||
session, lab.disk_path, read_only=False,
|
session, lab.disk_path, read_only=False, compression=compression
|
||||||
compression=compression) as disk:
|
) as disk:
|
||||||
disk.write(0, n_sectors, to_write)
|
disk.write(0, n_sectors, to_write)
|
||||||
got = disk.read(0, n_sectors)
|
got = disk.read(0, n_sectors)
|
||||||
assert got is not to_write
|
assert got is not to_write
|
||||||
|
|||||||
@@ -3,18 +3,24 @@
|
|||||||
|
|
||||||
"""Exercise the VDDK-compatible openvixdisklib handle against the lab."""
|
"""Exercise the VDDK-compatible openvixdisklib handle against the lab."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
from pyVim.connect import Disconnect
|
from pyVim.connect import Disconnect
|
||||||
from pyVmomi import vim
|
from pyVmomi import vim
|
||||||
import pytest
|
|
||||||
|
|
||||||
from openvixdisklib import openvixdisklib as vixdisklib
|
from openvixdisklib import openvixdisklib as vixdisklib
|
||||||
from tests.integration.base import (
|
from tests.integration.base import (
|
||||||
LabEnv, SECTOR_AT_1GB, SECTOR_SIZE, _connect_vim, _wait_for_task,
|
SECTOR_AT_1GB,
|
||||||
pattern_bytes)
|
SECTOR_SIZE,
|
||||||
|
LabEnv,
|
||||||
|
_connect_vim,
|
||||||
|
_wait_for_task,
|
||||||
|
pattern_bytes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _virtual_disk_backing(
|
def _virtual_disk_backing(
|
||||||
vm: vim.VirtualMachine) -> vim.vm.device.VirtualDevice.BackingInfo:
|
vm: vim.VirtualMachine,
|
||||||
|
) -> vim.vm.device.VirtualDevice.BackingInfo:
|
||||||
"""Return the lab VM's first virtual disk backing."""
|
"""Return the lab VM's first virtual disk backing."""
|
||||||
for device in vm.config.hardware.device:
|
for device in vm.config.hardware.device:
|
||||||
if isinstance(device, vim.vm.device.VirtualDisk):
|
if isinstance(device, vim.vm.device.VirtualDisk):
|
||||||
@@ -27,19 +33,23 @@ class TestOpenvixdisklib:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"open_flags",
|
"open_flags",
|
||||||
[0, vixdisklib.VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ],
|
[0, vixdisklib.VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ],
|
||||||
ids=["plain", "fastlz"])
|
ids=["plain", "fastlz"],
|
||||||
|
)
|
||||||
def test_write_and_read_sector_zero_and_one_gib(
|
def test_write_and_read_sector_zero_and_one_gib(
|
||||||
self, lab: LabEnv, transport_mode: str, open_flags: int) -> None:
|
self, lab: LabEnv, transport_mode: str, open_flags: int
|
||||||
|
) -> None:
|
||||||
"""Write then read sector 0 and the sector at a 1 GiB offset."""
|
"""Write then read sector 0 and the sector at a 1 GiB offset."""
|
||||||
handle = vixdisklib.VixDiskLibHandle(
|
handle = vixdisklib.VixDiskLibHandle(
|
||||||
vixdisklib_compatibility_version="8.0",
|
vixdisklib_compatibility_version="8.0", config_path=None
|
||||||
config_path=None)
|
)
|
||||||
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
connect_kwargs = lab.vixdisklib_connect_kwargs({
|
connect_kwargs = lab.vixdisklib_connect_kwargs(
|
||||||
|
{
|
||||||
"allow_untrusted": lab.allow_untrusted,
|
"allow_untrusted": lab.allow_untrusted,
|
||||||
"transport_modes": transport_mode,
|
"transport_modes": transport_mode,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
patterns = {
|
patterns = {
|
||||||
0: pattern_bytes(SECTOR_SIZE, b"OVDL-S0"),
|
0: pattern_bytes(SECTOR_SIZE, b"OVDL-S0"),
|
||||||
SECTOR_AT_1GB: pattern_bytes(SECTOR_SIZE, b"OVDL-1GB"),
|
SECTOR_AT_1GB: pattern_bytes(SECTOR_SIZE, b"OVDL-1GB"),
|
||||||
@@ -63,19 +73,22 @@ class TestOpenvixdisklib:
|
|||||||
``device.backing.fileName``; VDDK still opens it with a VM-only
|
``device.backing.fileName``; VDDK still opens it with a VM-only
|
||||||
ticket and NFC ``OPEN_FILE``.
|
ticket and NFC ``OPEN_FILE``.
|
||||||
"""
|
"""
|
||||||
handle = vixdisklib.VixDiskLibHandle(
|
handle = vixdisklib.VixDiskLibHandle(vixdisklib_compatibility_version="8.0")
|
||||||
vixdisklib_compatibility_version="8.0")
|
|
||||||
expected = pattern_bytes(SECTOR_SIZE, b"OVDL-RO")
|
expected = pattern_bytes(SECTOR_SIZE, b"OVDL-RO")
|
||||||
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
write_buf[:SECTOR_SIZE] = expected
|
write_buf[:SECTOR_SIZE] = expected
|
||||||
write_kwargs = lab.vixdisklib_connect_kwargs({
|
write_kwargs = lab.vixdisklib_connect_kwargs(
|
||||||
|
{
|
||||||
"allow_untrusted": lab.allow_untrusted,
|
"allow_untrusted": lab.allow_untrusted,
|
||||||
})
|
}
|
||||||
read_kwargs = lab.vixdisklib_connect_kwargs({
|
)
|
||||||
|
read_kwargs = lab.vixdisklib_connect_kwargs(
|
||||||
|
{
|
||||||
"allow_untrusted": lab.allow_untrusted,
|
"allow_untrusted": lab.allow_untrusted,
|
||||||
"read_only": True,
|
"read_only": True,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
read_flags = vixdisklib.VIXDISKLIB_FLAG_OPEN_READ_ONLY
|
read_flags = vixdisklib.VIXDISKLIB_FLAG_OPEN_READ_ONLY
|
||||||
|
|
||||||
def read_sector(path: str) -> bytes:
|
def read_sector(path: str) -> bytes:
|
||||||
@@ -92,12 +105,16 @@ class TestOpenvixdisklib:
|
|||||||
assert read_sector(lab.disk_path) == expected
|
assert read_sector(lab.disk_path) == expected
|
||||||
|
|
||||||
si = _connect_vim(
|
si = _connect_vim(
|
||||||
lab.host, lab.username, lab.password, lab.port,
|
lab.host,
|
||||||
lab.thumbprint, lab.allow_untrusted)
|
lab.username,
|
||||||
|
lab.password,
|
||||||
|
lab.port,
|
||||||
|
lab.thumbprint,
|
||||||
|
lab.allow_untrusted,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
vm = vim.VirtualMachine(lab.vm_moref, si._stub)
|
vm = vim.VirtualMachine(lab.vm_moref, si._stub)
|
||||||
_wait_for_task(
|
_wait_for_task(vm.CreateSnapshot_Task("ovdl-readonly", "", False, False))
|
||||||
vm.CreateSnapshot_Task("ovdl-readonly", "", False, False))
|
|
||||||
backing = _virtual_disk_backing(vm)
|
backing = _virtual_disk_backing(vm)
|
||||||
parent = getattr(backing, "parent", None)
|
parent = getattr(backing, "parent", None)
|
||||||
assert parent is not None
|
assert parent is not None
|
||||||
|
|||||||
@@ -4,16 +4,15 @@
|
|||||||
"""Exercise native VDDK via tests.integration.vixdisklib against the lab."""
|
"""Exercise native VDDK via tests.integration.vixdisklib against the lab."""
|
||||||
|
|
||||||
from tests.integration import vixdisklib
|
from tests.integration import vixdisklib
|
||||||
from tests.integration.base import LabEnv, SECTOR_SIZE, pattern_bytes
|
from tests.integration.base import SECTOR_SIZE, LabEnv, pattern_bytes
|
||||||
|
|
||||||
|
|
||||||
class TestVddk:
|
class TestVddk:
|
||||||
def test_write_and_read_first_sector(
|
def test_write_and_read_first_sector(self, lab: LabEnv, vddk: None) -> None:
|
||||||
self, lab: LabEnv, vddk: None) -> None:
|
|
||||||
"""Open the temp VMDK with VDDK, write sector 0, and read it back."""
|
"""Open the temp VMDK with VDDK, write sector 0, and read it back."""
|
||||||
handle = vixdisklib.VixDiskLibHandle(
|
handle = vixdisklib.VixDiskLibHandle(
|
||||||
vixdisklib_compatibility_version="8.0",
|
vixdisklib_compatibility_version="8.0", config_path=None
|
||||||
config_path=None)
|
)
|
||||||
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
write_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
read_buf = vixdisklib.get_buffer(SECTOR_SIZE)
|
||||||
expected = pattern_bytes(SECTOR_SIZE, b"VDDK-S0")
|
expected = pattern_bytes(SECTOR_SIZE, b"VDDK-S0")
|
||||||
|
|||||||
@@ -534,5 +534,5 @@ VIX_ERROR_CODE_MAP = {
|
|||||||
VIX_E_NET_HTTP_TOO_MANY_REDIRECTS: "Reached the maximum number of redirects.",
|
VIX_E_NET_HTTP_TOO_MANY_REDIRECTS: "Reached the maximum number of redirects.",
|
||||||
VIX_E_NET_HTTP_TRANSFER: "Failure sending/receiving network data.",
|
VIX_E_NET_HTTP_TRANSFER: "Failure sending/receiving network data.",
|
||||||
VIX_E_NET_HTTP_SSL_SECURITY: "An SSL error occurred.",
|
VIX_E_NET_HTTP_SSL_SECURITY: "An SSL error occurred.",
|
||||||
VIX_E_NET_HTTP_GENERIC: "A generic HTTP error occurred."
|
VIX_E_NET_HTTP_GENERIC: "A generic HTTP error occurred.",
|
||||||
}
|
}
|
||||||
|
|||||||
+114
-58
@@ -34,8 +34,7 @@ VIXDISKLIB_FLAG_OPEN_COMPRESSION_ZLIB = 16
|
|||||||
VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ = 32
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_FASTLZ = 32
|
||||||
VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ = 64
|
VIXDISKLIB_FLAG_OPEN_COMPRESSION_SKIPZ = 64
|
||||||
|
|
||||||
VIX_SUPPORTED_COMPATIBILITY_MODES = [
|
VIX_SUPPORTED_COMPATIBILITY_MODES = ["6.0", "6.5", "6.7", "7.0", "8.0"]
|
||||||
"6.0", "6.5", "6.7", "7.0", "8.0"]
|
|
||||||
|
|
||||||
|
|
||||||
class VixDiskLibUidPasswdCreds(ctypes.Structure):
|
class VixDiskLibUidPasswdCreds(ctypes.Structure):
|
||||||
@@ -84,12 +83,10 @@ def get_buffer(size):
|
|||||||
|
|
||||||
|
|
||||||
class VixDiskLibHandle(object):
|
class VixDiskLibHandle(object):
|
||||||
""" Class which acts as a proxy for vixDiskLib-related operations:
|
"""Class which acts as a proxy for vixDiskLib-related operations:"""
|
||||||
"""
|
|
||||||
def __init__(
|
def __init__(self, config_path=None, vixdisklib_compatibility_version=None):
|
||||||
self, config_path=None, vixdisklib_compatibility_version=None):
|
self._vix_disklib = ctypes.cdll.LoadLibrary(self.get_vix_disklib_name())
|
||||||
self._vix_disklib = ctypes.cdll.LoadLibrary(
|
|
||||||
self.get_vix_disklib_name())
|
|
||||||
self._setup_vix_disklib()
|
self._setup_vix_disklib()
|
||||||
|
|
||||||
if config_path:
|
if config_path:
|
||||||
@@ -113,86 +110,119 @@ class VixDiskLibHandle(object):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
|
"Unsupported vixDiskLib version format '%s'. vixDiskLib "
|
||||||
"compatibility mode must be of the form "
|
"compatibility mode must be of the form "
|
||||||
"'$major.$minor'" % version) from ex
|
"'$major.$minor'" % version
|
||||||
|
) from ex
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._check_err(self._vix_disklib.VixDiskLib_InitEx(
|
self._check_err(
|
||||||
major_ver, minor_ver, None, None, None, None, config_path))
|
self._vix_disklib.VixDiskLib_InitEx(
|
||||||
|
major_ver, minor_ver, None, None, None, None, config_path
|
||||||
|
)
|
||||||
|
)
|
||||||
version_used = version
|
version_used = version
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Failed to initialize vixDiskLib using compatibility "
|
"Failed to initialize vixDiskLib using compatibility "
|
||||||
"version '%s'. Trying next version. Error trace: %s",
|
"version '%s'. Trying next version. Error trace: %s",
|
||||||
version, traceback.format_exc())
|
version,
|
||||||
|
traceback.format_exc(),
|
||||||
|
)
|
||||||
|
|
||||||
if not version_used:
|
if not version_used:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Could not initialize vixDiskLib with any of the following "
|
"Could not initialize vixDiskLib with any of the following "
|
||||||
"versions: %s" % target_versions)
|
"versions: %s" % target_versions
|
||||||
|
)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Successfully initialized vixDiskLib with target version '%s'",
|
"Successfully initialized vixDiskLib with target version '%s'", version_used
|
||||||
version_used)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_vix_disklib_name(cls):
|
def get_vix_disklib_name(cls):
|
||||||
vixDiskLibName = None
|
vixDiskLibName = None
|
||||||
if os.name == 'nt':
|
if os.name == "nt":
|
||||||
vixDiskLibName = 'vixDiskLib.dll'
|
vixDiskLibName = "vixDiskLib.dll"
|
||||||
else:
|
else:
|
||||||
vixDiskLibName = 'libvixDiskLib.so'
|
vixDiskLibName = "libvixDiskLib.so"
|
||||||
return vixDiskLibName
|
return vixDiskLibName
|
||||||
|
|
||||||
def _setup_vix_disklib(self):
|
def _setup_vix_disklib(self):
|
||||||
self._vix_disklib.VixDiskLib_InitEx.argtypes = [
|
self._vix_disklib.VixDiskLib_InitEx.argtypes = [
|
||||||
ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p,
|
ctypes.c_uint32,
|
||||||
ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
|
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_InitEx.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_GetErrorText.argtypes = [
|
self._vix_disklib.VixDiskLib_GetErrorText.argtypes = [
|
||||||
ctypes.c_uint64, ctypes.c_char_p]
|
ctypes.c_uint64,
|
||||||
|
ctypes.c_char_p,
|
||||||
|
]
|
||||||
self._vix_disklib.VixDiskLib_GetErrorText.restype = ctypes.c_void_p
|
self._vix_disklib.VixDiskLib_GetErrorText.restype = ctypes.c_void_p
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_FreeErrorText.arg_types = [
|
self._vix_disklib.VixDiskLib_FreeErrorText.arg_types = [ctypes.c_char_p]
|
||||||
ctypes.c_char_p]
|
|
||||||
self._vix_disklib.VixDiskLib_FreeErrorText.restype = None
|
self._vix_disklib.VixDiskLib_FreeErrorText.restype = None
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_ListTransportModes.argtypes = []
|
self._vix_disklib.VixDiskLib_ListTransportModes.argtypes = []
|
||||||
self._vix_disklib.VixDiskLib_ListTransportModes.restype = (
|
self._vix_disklib.VixDiskLib_ListTransportModes.restype = ctypes.c_char_p
|
||||||
ctypes.c_char_p)
|
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_GetTransportMode.argtypes = [
|
self._vix_disklib.VixDiskLib_GetTransportMode.argtypes = [ctypes.c_void_p]
|
||||||
ctypes.c_void_p]
|
self._vix_disklib.VixDiskLib_GetTransportMode.restype = ctypes.c_char_p
|
||||||
self._vix_disklib.VixDiskLib_GetTransportMode.restype = (
|
|
||||||
ctypes.c_char_p)
|
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_ConnectEx.argtypes = [
|
self._vix_disklib.VixDiskLib_ConnectEx.argtypes = [
|
||||||
ctypes.POINTER(VixDiskLibConnectParams), ctypes.c_char,
|
ctypes.POINTER(VixDiskLibConnectParams),
|
||||||
ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
|
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_ConnectEx.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_Open.argtypes = [
|
self._vix_disklib.VixDiskLib_Open.argtypes = [
|
||||||
ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32,
|
ctypes.c_void_p,
|
||||||
ctypes.POINTER(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_Open.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_Read.argtypes = [
|
self._vix_disklib.VixDiskLib_Read.argtypes = [
|
||||||
ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_char_p]
|
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_Read.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_Write.argtypes = [
|
self._vix_disklib.VixDiskLib_Write.argtypes = [
|
||||||
ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_char_p]
|
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_Write.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_GetMetadataKeys.argtypes = [
|
self._vix_disklib.VixDiskLib_GetMetadataKeys.argtypes = [
|
||||||
ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint64,
|
ctypes.c_void_p,
|
||||||
ctypes.POINTER(ctypes.c_uint64)]
|
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_GetMetadataKeys.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_ReadMetadata.argtypes = [
|
self._vix_disklib.VixDiskLib_ReadMetadata.argtypes = [
|
||||||
ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint64,
|
ctypes.c_void_p,
|
||||||
ctypes.POINTER(ctypes.c_uint64)]
|
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_ReadMetadata.restype = ctypes.c_uint64
|
||||||
|
|
||||||
self._vix_disklib.VixDiskLib_Close.argtypes = [ctypes.c_void_p]
|
self._vix_disklib.VixDiskLib_Close.argtypes = [ctypes.c_void_p]
|
||||||
@@ -207,10 +237,10 @@ class VixDiskLibHandle(object):
|
|||||||
def _check_err(self, err, allowed_values=[vix_disklib_errors.VIX_OK]):
|
def _check_err(self, err, allowed_values=[vix_disklib_errors.VIX_OK]):
|
||||||
if err not in allowed_values:
|
if err not in allowed_values:
|
||||||
err_msg = self._vix_disklib.VixDiskLib_GetErrorText(err, None)
|
err_msg = self._vix_disklib.VixDiskLib_GetErrorText(err, None)
|
||||||
err_msg_copy = str(ctypes.cast(
|
err_msg_copy = str(ctypes.cast(err_msg, ctypes.c_char_p).value.decode())
|
||||||
err_msg, ctypes.c_char_p).value.decode())
|
|
||||||
self._vix_disklib.VixDiskLib_FreeErrorText(
|
self._vix_disklib.VixDiskLib_FreeErrorText(
|
||||||
ctypes.cast(err_msg, ctypes.c_char_p))
|
ctypes.cast(err_msg, ctypes.c_char_p)
|
||||||
|
)
|
||||||
|
|
||||||
msg = None
|
msg = None
|
||||||
if err == vix_disklib_errors.VIX_E_OUT_OF_MEMORY:
|
if err == vix_disklib_errors.VIX_E_OUT_OF_MEMORY:
|
||||||
@@ -220,7 +250,8 @@ class VixDiskLibHandle(object):
|
|||||||
"by vCenter, so enough RAM to run the export is required "
|
"by vCenter, so enough RAM to run the export is required "
|
||||||
"on all hosts. To force the export from the specific host "
|
"on all hosts. To force the export from the specific host "
|
||||||
"the VM is on, create a Coriolis endpoint with the DNS "
|
"the VM is on, create a Coriolis endpoint with the DNS "
|
||||||
"name/IP address of that host.")
|
"name/IP address of that host."
|
||||||
|
)
|
||||||
if err == vix_disklib_errors.VIX_E_HOST_NETWORK_CONN_REFUSED:
|
if err == vix_disklib_errors.VIX_E_HOST_NETWORK_CONN_REFUSED:
|
||||||
msg = (
|
msg = (
|
||||||
"The ESXi host performing the CBT export refused "
|
"The ESXi host performing the CBT export refused "
|
||||||
@@ -231,7 +262,8 @@ class VixDiskLibHandle(object):
|
|||||||
"facilitate this. Alternatively, try connecting Coriolis "
|
"facilitate this. Alternatively, try connecting Coriolis "
|
||||||
"directly to the specific ESXi host which is running the "
|
"directly to the specific ESXi host which is running the "
|
||||||
"VM(s) to be migrated by creating a Coriolis endpoint "
|
"VM(s) to be migrated by creating a Coriolis endpoint "
|
||||||
"using the DNS name/IP address of the host itself.")
|
"using the DNS name/IP address of the host itself."
|
||||||
|
)
|
||||||
|
|
||||||
if err == vix_disklib_errors.VIX_E_CANNOT_CONNECT_TO_HOST:
|
if err == vix_disklib_errors.VIX_E_CANNOT_CONNECT_TO_HOST:
|
||||||
msg = (
|
msg = (
|
||||||
@@ -239,7 +271,8 @@ class VixDiskLibHandle(object):
|
|||||||
"CBT export. If the Coriolis Endpoint connects to a "
|
"CBT export. If the Coriolis Endpoint connects to a "
|
||||||
"vSphere host, please try connecting Coriolis to the ESXi "
|
"vSphere host, please try connecting Coriolis to the ESXi "
|
||||||
"host directly. If problem persists, try re-enabling CBT "
|
"host directly. If problem persists, try re-enabling CBT "
|
||||||
"on the VM, or moving it to another ESXi host.")
|
"on the VM, or moving it to another ESXi host."
|
||||||
|
)
|
||||||
|
|
||||||
err_msg = err_msg_copy
|
err_msg = err_msg_copy
|
||||||
if msg:
|
if msg:
|
||||||
@@ -250,7 +283,7 @@ class VixDiskLibHandle(object):
|
|||||||
|
|
||||||
def get_transport_modes(self):
|
def get_transport_modes(self):
|
||||||
transport_modes = self._vix_disklib.VixDiskLib_ListTransportModes()
|
transport_modes = self._vix_disklib.VixDiskLib_ListTransportModes()
|
||||||
return transport_modes.decode().split(':')
|
return transport_modes.decode().split(":")
|
||||||
|
|
||||||
def get_transport_mode(self, disk_handle):
|
def get_transport_mode(self, disk_handle):
|
||||||
t_mode = self._vix_disklib.VixDiskLib_GetTransportMode(disk_handle)
|
t_mode = self._vix_disklib.VixDiskLib_GetTransportMode(disk_handle)
|
||||||
@@ -258,9 +291,17 @@ class VixDiskLibHandle(object):
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def connect(
|
def connect(
|
||||||
self, server_name, thumbprint, username, password,
|
self,
|
||||||
vmx_spec=None, snapshot_ref=None, read_only=True,
|
server_name,
|
||||||
transport_modes=None, port=443):
|
thumbprint,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
vmx_spec=None,
|
||||||
|
snapshot_ref=None,
|
||||||
|
read_only=True,
|
||||||
|
transport_modes=None,
|
||||||
|
port=443,
|
||||||
|
):
|
||||||
LOG.debug("Connecting VixDiskLib: %s", server_name)
|
LOG.debug("Connecting VixDiskLib: %s", server_name)
|
||||||
|
|
||||||
connectParams = VixDiskLibConnectParams()
|
connectParams = VixDiskLibConnectParams()
|
||||||
@@ -283,9 +324,15 @@ class VixDiskLibHandle(object):
|
|||||||
snapshot_ref = snapshot_ref.encode()
|
snapshot_ref = snapshot_ref.encode()
|
||||||
|
|
||||||
conn = ctypes.c_void_p()
|
conn = ctypes.c_void_p()
|
||||||
self._check_err(self._vix_disklib.VixDiskLib_ConnectEx(
|
self._check_err(
|
||||||
connectParams, read_only, snapshot_ref, transport_modes,
|
self._vix_disklib.VixDiskLib_ConnectEx(
|
||||||
ctypes.byref(conn)))
|
connectParams,
|
||||||
|
read_only,
|
||||||
|
snapshot_ref,
|
||||||
|
transport_modes,
|
||||||
|
ctypes.byref(conn),
|
||||||
|
)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
yield conn
|
yield conn
|
||||||
finally:
|
finally:
|
||||||
@@ -296,21 +343,30 @@ class VixDiskLibHandle(object):
|
|||||||
LOG.debug("Openning VixDiskLib disk: %s", disk_path)
|
LOG.debug("Openning VixDiskLib disk: %s", disk_path)
|
||||||
|
|
||||||
disk_handle = ctypes.c_void_p()
|
disk_handle = ctypes.c_void_p()
|
||||||
self._check_err(self._vix_disklib.VixDiskLib_Open(
|
self._check_err(
|
||||||
conn, disk_path.encode(), flags, ctypes.byref(disk_handle)))
|
self._vix_disklib.VixDiskLib_Open(
|
||||||
|
conn, disk_path.encode(), flags, ctypes.byref(disk_handle)
|
||||||
|
)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
yield disk_handle
|
yield disk_handle
|
||||||
finally:
|
finally:
|
||||||
self.close(disk_handle)
|
self.close(disk_handle)
|
||||||
|
|
||||||
def read(self, disk_handle, start_sector, num_sectors, buf):
|
def read(self, disk_handle, start_sector, num_sectors, buf):
|
||||||
self._check_err(self._vix_disklib.VixDiskLib_Read(
|
self._check_err(
|
||||||
disk_handle, start_sector, num_sectors, buf))
|
self._vix_disklib.VixDiskLib_Read(
|
||||||
|
disk_handle, start_sector, num_sectors, buf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def write(self, 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``."""
|
"""Write ``num_sectors`` from ``buf`` starting at ``start_sector``."""
|
||||||
self._check_err(self._vix_disklib.VixDiskLib_Write(
|
self._check_err(
|
||||||
disk_handle, start_sector, num_sectors, buf))
|
self._vix_disklib.VixDiskLib_Write(
|
||||||
|
disk_handle, start_sector, num_sectors, buf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def close(self, disk_handle):
|
def close(self, disk_handle):
|
||||||
LOG.debug("Closing VixDiskLib disk handle: %s", disk_handle)
|
LOG.debug("Closing VixDiskLib disk handle: %s", disk_handle)
|
||||||
|
|||||||
+33
-19
@@ -10,7 +10,7 @@ from typing import Any
|
|||||||
|
|
||||||
from openvixdisklib import openvixdisklib as open_vix
|
from openvixdisklib import openvixdisklib as open_vix
|
||||||
from tests.integration import vixdisklib
|
from tests.integration import vixdisklib
|
||||||
from tests.integration.base import LabEnv, SECTOR_SIZE, pattern_bytes
|
from tests.integration.base import SECTOR_SIZE, LabEnv, pattern_bytes
|
||||||
|
|
||||||
_SIZES = (
|
_SIZES = (
|
||||||
("64KiB", 64 * 1024),
|
("64KiB", 64 * 1024),
|
||||||
@@ -19,8 +19,7 @@ _SIZES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _connect_extra(
|
def _connect_extra(lab: LabEnv, module: Any, transport_mode: str) -> dict[str, Any]:
|
||||||
lab: LabEnv, module: Any, transport_mode: str) -> dict[str, Any]:
|
|
||||||
"""Return extra ``connect`` kwargs needed by ``module``."""
|
"""Return extra ``connect`` kwargs needed by ``module``."""
|
||||||
extra: dict[str, Any] = {"transport_modes": transport_mode}
|
extra: dict[str, Any] = {"transport_modes": transport_mode}
|
||||||
if module is open_vix:
|
if module is open_vix:
|
||||||
@@ -33,27 +32,27 @@ def _time_write_read(
|
|||||||
module: Any,
|
module: Any,
|
||||||
payload: bytes,
|
payload: bytes,
|
||||||
flags: int = 0,
|
flags: int = 0,
|
||||||
transport_mode: str = "nbdssl") -> tuple[float, float]:
|
transport_mode: str = "nbdssl",
|
||||||
|
) -> tuple[float, float]:
|
||||||
"""Write ``payload`` at sector 0, read it back, and return durations."""
|
"""Write ``payload`` at sector 0, read it back, and return durations."""
|
||||||
n_sectors = len(payload) // SECTOR_SIZE
|
n_sectors = len(payload) // SECTOR_SIZE
|
||||||
handle = module.VixDiskLibHandle(
|
handle = module.VixDiskLibHandle(
|
||||||
vixdisklib_compatibility_version="8.0",
|
vixdisklib_compatibility_version="8.0", config_path=None
|
||||||
config_path=None)
|
)
|
||||||
write_buf = module.get_buffer(len(payload))
|
write_buf = module.get_buffer(len(payload))
|
||||||
read_buf = module.get_buffer(len(payload))
|
read_buf = module.get_buffer(len(payload))
|
||||||
write_buf[:len(payload)] = payload
|
write_buf[: len(payload)] = payload
|
||||||
kwargs = lab.vixdisklib_connect_kwargs(
|
kwargs = lab.vixdisklib_connect_kwargs(_connect_extra(lab, module, transport_mode))
|
||||||
_connect_extra(lab, module, transport_mode))
|
|
||||||
with handle.connect(**kwargs) as conn:
|
with handle.connect(**kwargs) as conn:
|
||||||
with handle.open(conn, lab.disk_path, flags=flags) as disk:
|
with handle.open(conn, lab.disk_path, flags=flags) as disk:
|
||||||
started = time.perf_counter()
|
started = time.perf_counter()
|
||||||
handle.write(disk, 0, n_sectors, write_buf)
|
handle.write(disk, 0, n_sectors, write_buf)
|
||||||
write_s = time.perf_counter() - started
|
write_s = time.perf_counter() - started
|
||||||
read_buf[:len(payload)] = b"\xa5" * len(payload)
|
read_buf[: len(payload)] = b"\xa5" * len(payload)
|
||||||
started = time.perf_counter()
|
started = time.perf_counter()
|
||||||
handle.read(disk, 0, n_sectors, read_buf)
|
handle.read(disk, 0, n_sectors, read_buf)
|
||||||
read_s = time.perf_counter() - started
|
read_s = time.perf_counter() - started
|
||||||
assert read_buf.raw[:len(payload)] == payload
|
assert read_buf.raw[: len(payload)] == payload
|
||||||
return write_s, read_s
|
return write_s, read_s
|
||||||
|
|
||||||
|
|
||||||
@@ -82,9 +81,14 @@ class TestCompare:
|
|||||||
for mode_name, flags in open_modes:
|
for mode_name, flags in open_modes:
|
||||||
for name, module in libraries:
|
for name, module in libraries:
|
||||||
write_s, read_s = _time_write_read(
|
write_s, read_s = _time_write_read(
|
||||||
lab, module, payload, flags=flags,
|
lab,
|
||||||
transport_mode=transport_mode)
|
module,
|
||||||
rows.append((
|
payload,
|
||||||
|
flags=flags,
|
||||||
|
transport_mode=transport_mode,
|
||||||
|
)
|
||||||
|
rows.append(
|
||||||
|
(
|
||||||
label,
|
label,
|
||||||
transport_mode,
|
transport_mode,
|
||||||
mode_name,
|
mode_name,
|
||||||
@@ -93,16 +97,26 @@ class TestCompare:
|
|||||||
read_s,
|
read_s,
|
||||||
_mib_per_s(nbytes, write_s),
|
_mib_per_s(nbytes, write_s),
|
||||||
_mib_per_s(nbytes, read_s),
|
_mib_per_s(nbytes, read_s),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
print()
|
print()
|
||||||
print(
|
print(
|
||||||
f"{'size':<14} {'transport':<10} {'flags':<8} {'library':<16} "
|
f"{'size':<14} {'transport':<10} {'flags':<8} {'library':<16} "
|
||||||
f"{'write_s':>10} {'read_s':>10} "
|
f"{'write_s':>10} {'read_s':>10} "
|
||||||
f"{'write_MiB/s':>12} {'read_MiB/s':>12}")
|
f"{'write_MiB/s':>12} {'read_MiB/s':>12}"
|
||||||
|
)
|
||||||
for (
|
for (
|
||||||
label, transport_mode, mode_name, name,
|
label,
|
||||||
write_s, read_s, write_r, read_r) in rows:
|
transport_mode,
|
||||||
|
mode_name,
|
||||||
|
name,
|
||||||
|
write_s,
|
||||||
|
read_s,
|
||||||
|
write_r,
|
||||||
|
read_r,
|
||||||
|
) in rows:
|
||||||
print(
|
print(
|
||||||
f"{label:<14} {transport_mode:<10} {mode_name:<8} {name:<16} "
|
f"{label:<14} {transport_mode:<10} {mode_name:<8} {name:<16} "
|
||||||
f"{write_s:10.3f} {read_s:10.3f} "
|
f"{write_s:10.3f} {read_s:10.3f} "
|
||||||
f"{write_r:12.1f} {read_r:12.1f}")
|
f"{write_r:12.1f} {read_r:12.1f}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user