Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ jobs:
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/govc_Linux_x86_64.tar.gz$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/VMware-vix-disklib-.*[.]tar[.]gz$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/compat/esxi55/profile.json$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/VMware-vix-disklib-6[.]0[.]2-3566099[.]x86_64[.]tar[.]gz$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/VMware-vix-disklib-6[.]5[.]0-4604867[.]x86_64[.]tar[.]gz$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/nbdkit-vddk-legacy-1[.]14[.]2-rocky9-x86_64[.]tar[.]gz$" >/dev/null
rpm -qlp "${RPM_FILE}" | grep -E "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/wheels/pyvmomi-5[.]5[.]0[.]2014[.]1[.]1[.]tar[.]gz$" >/dev/null
if find winpe -maxdepth 1 -type f -iname '*.iso' | grep -q .; then
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ v2k-rpm:
(echo "[ERR] compat profile missing in RPM: $$RPM_FILE" >&2; exit 2); \
rpm -qlp "$$RPM_FILE" | grep -qE "/usr/share/ablestack/v2k/compat/esxi55/profile.json$$" || \
(echo "[ERR] esxi55 compat profile missing in RPM: $$RPM_FILE" >&2; exit 2); \
rpm -qlp "$$RPM_FILE" | grep -qE "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/VMware-vix-disklib-6[.]0[.]2-3566099[.]x86_64[.]tar[.]gz$$" || \
rpm -qlp "$$RPM_FILE" | grep -qE "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/VMware-vix-disklib-6[.]5[.]0-4604867[.]x86_64[.]tar[.]gz$$" || \
(echo "[ERR] esxi55 VDDK runtime asset missing in RPM: $$RPM_FILE" >&2; exit 2); \
rpm -qlp "$$RPM_FILE" | grep -qE "/usr/share/ablestack/v2k/runtime-assets/assets/compat/esxi55/nbdkit-vddk-legacy-1[.]14[.]2-rocky9-x86_64[.]tar[.]gz$$" || \
(echo "[ERR] esxi55 legacy nbdkit runtime asset missing in RPM: $$RPM_FILE" >&2; exit 2); \
Expand Down
6 changes: 3 additions & 3 deletions assets/compat/esxi55/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The role split for this profile is deliberate:
```text
assets/compat/esxi55/
govc_Linux_x86_64.tar.gz
VMware-vix-disklib-6.0.2-3566099.x86_64.tar.gz
VMware-vix-disklib-6.5.0-4604867.x86_64.tar.gz
nbdkit-vddk-legacy-1.14.2-rocky9-x86_64.tar.gz
wheels/
pyvmomi-5.5.0.2014.1.1.tar.gz
Expand All @@ -28,11 +28,11 @@ assets/compat/esxi55/
The installer intentionally does not fall back to top-level `govc`, VDDK,
legacy nbdkit, or wheel assets for this profile. Newer VDDK releases can fail
against ESXi 5.5 NFC sessions, while the system nbdkit VDDK plugin can reject
VDDK 6.0.2 at load time, so missing profile-local assets are treated as an
older VDDK releases at load time, so missing profile-local assets are treated as an
installation error.

The VDDK archive is operator-provided. The currently staged and validated
candidate is `VMware-vix-disklib-6.0.2-3566099.x86_64.tar.gz`.
candidate is `VMware-vix-disklib-6.5.0-4604867.x86_64.tar.gz`.

The legacy nbdkit archive is public redistributable runtime support built from
nbdkit 1.14.2. It is used only by the `esxi55` profile and is invoked with the
Expand Down
10 changes: 8 additions & 2 deletions assets/compat/esxi55/SOURCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ the repository's binary asset policy.
| Asset | Source | Selected version | Role |
| --- | --- | --- | --- |
| `govc_Linux_x86_64.tar.gz` | `https://github.com/vmware/govmomi/releases/download/v0.46.3/govc_Linux_x86_64.tar.gz` | `govc 0.46.3` | vSphere inventory, snapshot, power, and host queries |
| `nbdkit-vddk-legacy-1.14.2-rocky9-x86_64.tar.gz` | `https://download.libguestfs.org/nbdkit/1.14-stable/nbdkit-1.14.2.tar.gz` | `nbdkit 1.14.2` | Profile-local VDDK plugin runtime that can load VDDK 6.0.2 |
| `nbdkit-vddk-legacy-1.14.2-rocky9-x86_64.tar.gz` | `https://download.libguestfs.org/nbdkit/1.14-stable/nbdkit-1.14.2.tar.gz` | `nbdkit 1.14.2` | Profile-local VDDK plugin runtime that can load VDDK 6.5.0 |
| `wheels/pyvmomi-5.5.0.2014.1.1.tar.gz` | PyPI package `pyvmomi==5.5.0.2014.1.1` | `5.5.0.2014.1.1` | Changed block tracking API calls |
| `wheels/*.whl` | PyPI dependency resolver output for pyVmomi 5.5 | current compatible releases | Offline installation dependencies |

The VDDK archive is intentionally not sourced from public package feeds. The
operator must download the ESXi 5.5-compatible VMware VDDK archive after
accepting VMware/Broadcom license terms and place it in this directory. The
current staged candidate is
`VMware-vix-disklib-6.0.2-3566099.x86_64.tar.gz`.
`VMware-vix-disklib-6.5.0-4604867.x86_64.tar.gz`.

Current candidate checksum:

```text
SHA256 849afa4ee1fa62602adc1f152013797bdf1eb4572e2c6753b5410c79630e038b
```
Binary file not shown.
2 changes: 1 addition & 1 deletion bin/v2k_test_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Profiles can opt out of top-level fallback. The ESXi 5.5 profile is strict:
public govc/pyVmomi assets live under ./assets/compat/esxi55, and the operator
must add the licensed VMware VDDK archive there before --install-assets can
install it. The ESXi 5.5 profile also carries a legacy nbdkit VDDK runtime so
VDDK 6.0.2 can be loaded even when the system nbdkit plugin requires VDDK 6.5+.
the selected VDDK can be loaded without relying on the system nbdkit plugin.
EOF
}

Expand Down
2 changes: 1 addition & 1 deletion docs/v2k/v2k_cli_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Supported profile IDs in the current implementation:
Use `--compat-profile auto` for normal operation. The selected profile is saved in the manifest and reused for follow-up commands.
When the source VM runs on ESXi 5.5, auto-selection chooses `esxi55` even if the managing vCenter reports 6.0.
For `esxi55`, only the licensed VMware VDDK archive is operator-provided; the
current candidate is VDDK 6.0.2 for ESXi 5.5 compatibility. Public `govc` and
current candidate is VDDK 6.5.0 for ESXi 5.5 compatibility. Public `govc` and
pyVmomi offline dependency assets live under `assets/compat/esxi55/`.

## `run` / `auto`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# v2k ESXi 5.5 VDDK 6.0.2 Legacy nbdkit Design
# v2k ESXi 5.5 VDDK 6.5 Legacy nbdkit Design

## Background

The `esxi55` compatibility profile intentionally uses VMware VDDK 6.0.2
because newer VDDK 6.5.x packages are no longer practically obtainable through
the current VMware/Broadcom download path.
The `esxi55` compatibility profile uses a VMware VDDK 6.5.x-family payload
when it is available. This profile initially used VDDK 6.0.2 because the 6.5.x
packages were difficult to obtain, but real ESXi 5.5 testing exposed runtime
library conflicts and made VDDK 6.5.x the preferred candidate.

The host system nbdkit VDDK plugin cannot be used with this VDDK. Runtime logs
from ESXi 5.5 validation showed that the system plugin fails before connecting
to vCenter or NFC:
The host system nbdkit VDDK plugin is still not used for this profile. Runtime
logs from ESXi 5.5 validation showed that newer system plugins can fail before
connecting to vCenter or NFC when the selected VDDK is older than the plugin
expects:

```text
required VDDK symbol "VixDiskLib_Wait" is missing. VDDK version must be >= 6.5.
```

This means profile selection is working, but the selected VDDK is too old for
the installed system `nbdkit-vddk-plugin`.
This means profile selection is working, but the VDDK/runtime pair must be
managed as a unit instead of relying on whatever system plugin is installed.

## Decision

The `esxi55` profile must carry a profile-local legacy nbdkit runtime that can
load VDDK 6.0.2. The system nbdkit remains the default for other profiles.
load the selected VDDK 6.5.x-family payload. The system nbdkit remains the
default for other profiles.

The selected runtime layout is:

Expand Down Expand Up @@ -54,13 +57,20 @@ LD_LIBRARY_PATH=/path/to/vddk/lib64 \

When `esxi55` is active:

- `VDDK_LIBDIR` points to the profile-local VDDK 6.0.2 directory.
- `VDDK_LIBDIR` points to the profile-local VDDK 6.5.x-family directory.
- `V2K_NBDKIT_BIN` points to the profile-local legacy nbdkit binary.
- `V2K_NBDKIT_VDDK_PLUGIN` points to the profile-local VDDK plugin `.so`.
- transfer code invokes the plugin by absolute path, not by the short name
`vddk`, so the compiled default plugin directory in the legacy binary is not
relevant.
- `LD_LIBRARY_PATH` includes both `VDDK_LIBDIR/lib64` and `VDDK_LIBDIR`.
- nbdkit child commands such as `qemu-img` are executed with VDDK paths removed
from `LD_LIBRARY_PATH`. VDDK redistributes libraries such as GLib, OpenSSL,
libcurl, and libstdc++; those libraries must not be allowed to override the
system libraries used by transfer tools.
- V2K creates a workdir-local `vddk.conf` and passes it to nbdkit with
`config=...` so host-level VMware config files do not influence migration
runs.

Other profiles continue to use the system `nbdkit` and the short plugin name
`vddk`.
Expand All @@ -85,5 +95,5 @@ This catches ABI mismatches during installation instead of failing later in
Passing `--dump-plugin` proves local ABI compatibility only. It does not prove
that ESXi 5.5 NFC transfer succeeds in the target environment. After this
change, ESXi 5.5 validation must be rerun through at least base sync to confirm
that the original NFC error is resolved or to expose the next compatibility
issue.
that NFC transfer succeeds in the target environment or to expose the next
compatibility issue.
2 changes: 1 addition & 1 deletion docs/v2k/v2k_migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ under `assets/compat/esxi55/` and are not allowed to fall back to top-level
runtime assets.

For `esxi55`, the operator supplies only the licensed VMware VDDK archive. The
current candidate is VDDK 6.0.2 for ESXi 5.5 compatibility. The public assets
current candidate is VDDK 6.5.0 for ESXi 5.5 compatibility. The public assets
(`govc` and pyVmomi offline dependencies) are staged in `assets/compat/esxi55/`
with the repository assets.

Expand Down
126 changes: 121 additions & 5 deletions lib/v2k/cloudstack_api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,39 @@ v2k_cloud_signed_query() {
printf '%s&signature=%s' "${query}" "${signature_enc}"
}

v2k_cloud_api_prefers_post() {
local command="$1"
case "${command}" in
createDiskOffering|importVolume|deployVirtualMachineForVolume|updateVolume|attachVolume|startVirtualMachine)
return 0
;;
*)
return 1
;;
esac
}

v2k_cloud_api_method() {
local command="$1" query_len="$2"
local requested threshold
requested="$(printf '%s' "${V2K_CLOUD_API_METHOD:-auto}" | tr '[:lower:]' '[:upper:]')"
threshold="${V2K_CLOUD_POST_THRESHOLD:-1800}"
[[ "${threshold}" =~ ^[0-9]+$ ]] || threshold=1800

case "${requested}" in
GET|POST)
printf '%s' "${requested}"
return 0
;;
esac

if v2k_cloud_api_prefers_post "${command}" || (( query_len >= threshold )); then
printf '%s' "POST"
else
printf '%s' "GET"
fi
}

v2k_cloud_command_params_json() {
local command="$1" api_key="$2" params_json="${3:-}"
[[ -n "${params_json}" ]] || params_json="{}"
Expand All @@ -111,9 +144,56 @@ v2k_cloud_command_params_json() {
'. + {command:$command, apiKey:$api_key, response:"json"}'
}

v2k_cloud_response_error_summary() {
local body_file="$1" header_file="$2"
local json_summary="" header_desc=""

if [[ -s "${body_file}" ]]; then
json_summary="$(jq -r '
to_entries[0].value as $v
| [
(if ($v.errorcode // null) != null then "errorcode=" + ($v.errorcode | tostring) else empty end),
(if ($v.cserrorcode // null) != null then "cserrorcode=" + ($v.cserrorcode | tostring) else empty end),
(if (($v.errortext // "") | tostring | length) > 0 then "errortext=" + ($v.errortext | tostring) else empty end)
]
| join(" ")
' "${body_file}" 2>/dev/null || true)"
fi

if [[ -s "${header_file}" ]]; then
header_desc="$(awk -F': *' 'tolower($1) == "x-description" {print substr($0, index($0, $2))}' "${header_file}" | tail -n 1 | tr -d '\r' || true)"
fi

if [[ -n "${json_summary}" && -n "${header_desc}" && "${json_summary}" != *"${header_desc}"* ]]; then
printf '%s x-description=%s' "${json_summary}" "${header_desc}"
elif [[ -n "${json_summary}" ]]; then
printf '%s' "${json_summary}"
elif [[ -n "${header_desc}" ]]; then
printf 'x-description=%s' "${header_desc}"
elif [[ -s "${body_file}" ]]; then
head -c 512 "${body_file}" | tr '\n' ' '
fi
}

v2k_cloud_api_report_failure() {
local command="$1" method="$2" query_len="$3" http_status="$4" body_file="$5" header_file="$6" curl_err_file="$7"
local summary curl_err

echo "Cloud API request failed: command=${command} method=${method} http_status=${http_status:-000} query_length=${query_len}" >&2
summary="$(v2k_cloud_response_error_summary "${body_file}" "${header_file}")"
if [[ -n "${summary}" ]]; then
echo "Cloud API error: ${summary}" >&2
fi
if [[ -s "${curl_err_file}" ]]; then
curl_err="$(tr '\n' ' ' <"${curl_err_file}")"
[[ -n "${curl_err}" ]] && echo "Cloud API curl error: ${curl_err}" >&2
fi
}

v2k_cloud_api_get() {
local endpoint="$1" api_key="$2" secret_key="$3" command="$4" params_json="${5:-}"
local connect_timeout max_time body_params query url
local connect_timeout max_time body_params query url method query_len
local body_file header_file curl_err_file http_status curl_rc
[[ -n "${params_json}" ]] || params_json="{}"
endpoint="$(v2k_cloud_normalize_endpoint "${endpoint}")"
v2k_cloud_require_credentials "${endpoint}" "${api_key}" "${secret_key}"
Expand All @@ -122,12 +202,48 @@ v2k_cloud_api_get() {

body_params="$(v2k_cloud_command_params_json "${command}" "${api_key}" "${params_json}")"
query="$(v2k_cloud_signed_query "${body_params}" "${secret_key}")"
query_len="${#query}"
method="$(v2k_cloud_api_method "${command}" "${query_len}")"
url="${endpoint}?${query}"

curl --globoff --silent --show-error --fail \
--connect-timeout "${connect_timeout}" \
--max-time "${max_time}" \
"${url}"
body_file="$(mktemp "${TMPDIR:-/tmp}/v2k-cloud-body.XXXXXX")"
header_file="$(mktemp "${TMPDIR:-/tmp}/v2k-cloud-headers.XXXXXX")"
curl_err_file="$(mktemp "${TMPDIR:-/tmp}/v2k-cloud-curl.XXXXXX")"

if [[ "${method}" == "POST" ]]; then
http_status="$(curl --globoff --silent --show-error \
--request POST \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-binary "${query}" \
--connect-timeout "${connect_timeout}" \
--max-time "${max_time}" \
--dump-header "${header_file}" \
--output "${body_file}" \
--write-out "%{http_code}" \
"${endpoint}" 2>"${curl_err_file}")"
curl_rc=$?
if (( curl_rc != 0 )) || ! [[ "${http_status}" =~ ^[0-9]{3}$ ]] || (( http_status >= 400 )); then
v2k_cloud_api_report_failure "${command}" "POST" "${query_len}" "${http_status}" "${body_file}" "${header_file}" "${curl_err_file}"
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
return 1
fi
else
http_status="$(curl --globoff --silent --show-error \
--connect-timeout "${connect_timeout}" \
--max-time "${max_time}" \
--dump-header "${header_file}" \
--output "${body_file}" \
--write-out "%{http_code}" \
"${url}" 2>"${curl_err_file}")"
curl_rc=$?
if (( curl_rc != 0 )) || ! [[ "${http_status}" =~ ^[0-9]{3}$ ]] || (( http_status >= 400 )); then
v2k_cloud_api_report_failure "${command}" "GET" "${query_len}" "${http_status}" "${body_file}" "${header_file}" "${curl_err_file}"
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
return 1
fi
fi
cat "${body_file}"
rm -f "${body_file}" "${header_file}" "${curl_err_file}"
}

v2k_cloud_response_body() {
Expand Down
56 changes: 56 additions & 0 deletions lib/v2k/compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,62 @@ v2k_compat_vddk_ld_library_path() {
printf '%s' "${out}"
}

v2k_compat_child_ld_library_path() {
local vddk="${VDDK_LIBDIR:-}"
local vddk_lib64="" vddk_root="" part out=""
if [[ -n "${vddk}" ]]; then
if [[ -d "${vddk}/lib64" ]]; then
vddk_lib64="$(cd "${vddk}/lib64" 2>/dev/null && pwd -P || printf '%s' "${vddk}/lib64")"
fi
if [[ -d "${vddk}" ]]; then
vddk_root="$(cd "${vddk}" 2>/dev/null && pwd -P || printf '%s' "${vddk}")"
fi
fi

IFS=':' read -r -a _v2k_ld_parts <<< "${LD_LIBRARY_PATH:-}"
for part in "${_v2k_ld_parts[@]}"; do
[[ -n "${part}" ]] || continue
local real_part="${part}"
if [[ -d "${part}" ]]; then
real_part="$(cd "${part}" 2>/dev/null && pwd -P || printf '%s' "${part}")"
fi
[[ -n "${vddk_lib64}" && "${real_part}" == "${vddk_lib64}" ]] && continue
[[ -n "${vddk_root}" && "${real_part}" == "${vddk_root}" ]] && continue
out="${out:+${out}:}${part}"
done
printf '%s' "${out}"
}

v2k_compat_vddk_child_env_prefix() {
local child_ld
child_ld="$(v2k_compat_child_ld_library_path)"
if [[ -n "${child_ld}" ]]; then
printf 'env LD_LIBRARY_PATH="%s"' "${child_ld}"
else
printf 'env -u LD_LIBRARY_PATH'
fi
}

v2k_compat_vddk_config_file() {
if [[ -n "${V2K_VDDK_CONFIG:-}" ]]; then
[[ -f "${V2K_VDDK_CONFIG}" ]] || {
echo "V2K_VDDK_CONFIG not found: ${V2K_VDDK_CONFIG}" >&2
return 1
}
printf '%s' "${V2K_VDDK_CONFIG}"
return 0
fi

local dir="${V2K_WORKDIR:-/tmp}" cfg
mkdir -p "${dir}"
cfg="${dir}/vddk.conf"
if [[ ! -f "${cfg}" ]]; then
: > "${cfg}"
chmod 600 "${cfg}" 2>/dev/null || true
fi
printf '%s' "${cfg}"
}

v2k_compat_extract_version_from_about_json() {
local about_json="${1:-}"
[[ -n "${about_json}" ]] || return 1
Expand Down
Loading
Loading