diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 076f137..0f0097f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -292,14 +292,14 @@ jobs: if [[ "${ROCKY_MINOR}" == "9.6" ]]; then REPO_ROOT="https://dl.rockylinux.org/vault/rocky/${ROCKY_MINOR}" elif [[ "${ROCKY_MINOR}" == "9.7" ]]; then - REPO_ROOT="https://dl.rockylinux.org/pub/rocky/${ROCKY_MINOR}" + REPO_ROOT="https://dl.rockylinux.org/vault/rocky/${ROCKY_MINOR}" else echo "[ERR] Unsupported Rocky minor for V2K build: ${ROCKY_MINOR}" >&2 exit 2 fi # Keep V2K build inputs pinned to the intended Rocky minor. - # 9.6 uses vault; 9.7 uses the normal public tree. + # 9.6 and 9.7 use vault because fixed minor trees may be removed from pub. cat > "/etc/yum.repos.d/${REPO_PREFIX}.repo" <&2 exit 2 @@ -577,7 +577,7 @@ jobs: if [[ "${ROCKY_MINOR}" == "9.6" ]]; then REPO_ROOT="https://dl.rockylinux.org/vault/rocky/${ROCKY_MINOR}" elif [[ "${ROCKY_MINOR}" == "9.7" ]]; then - REPO_ROOT="https://dl.rockylinux.org/pub/rocky/${ROCKY_MINOR}" + REPO_ROOT="https://dl.rockylinux.org/vault/rocky/${ROCKY_MINOR}" else echo "[ERR] Unsupported Rocky minor for N2K build: ${ROCKY_MINOR}" >&2 exit 2 diff --git a/bin/ablestack_v2k.sh b/bin/ablestack_v2k.sh index 867bd21..486d90c 100644 --- a/bin/ablestack_v2k.sh +++ b/bin/ablestack_v2k.sh @@ -412,6 +412,8 @@ Options: --winpe-timeout Default: 600 --linux-bootstrap Default: auto for Linux guests --no-linux-bootstrap + --bootstrap-fallback sata|off Default: sata + --no-bootstrap-fallback Same as --bootstrap-fallback off --safe-mode Default: off --force-cleanup Default: off diff --git a/completions/ablestack_v2k b/completions/ablestack_v2k index 5e7a612..268de27 100644 --- a/completions/ablestack_v2k +++ b/completions/ablestack_v2k @@ -264,6 +264,13 @@ _ablestack_v2k() ;; cutover) + case "${prev}" in + --bootstrap-fallback) + COMPREPLY=( $(compgen -W "sata off" -- "${cur}") ) + return 0 + ;; + esac + local -a cutover_opts=( --shutdown --shutdown-force --shutdown-timeout --define-only --apply --start @@ -272,6 +279,7 @@ _ablestack_v2k() --winpe-bootstrap --no-winpe-bootstrap --winpe-iso --virtio-iso --winpe-timeout --linux-bootstrap --no-linux-bootstrap + --bootstrap-fallback --no-bootstrap-fallback --safe-mode --force-cleanup --target-provider diff --git a/lib/v2k/engine.sh b/lib/v2k/engine.sh index c2ed4ec..6eadebb 100644 --- a/lib/v2k/engine.sh +++ b/lib/v2k/engine.sh @@ -2540,6 +2540,65 @@ v2k_cloud_windows_winpe_bootstrap() { v2k_windows_winpe_bootstrap_libvirt "${tmp_manifest}" "${tmp_vm}" "${winpe_iso}" "${virtio_iso}" "${winpe_timeout}" 1 } +v2k_bootstrap_fallback_enabled() { + local policy="${1:-sata}" + case "${policy}" in + sata|auto|"") return 0 ;; + off|none|false|0) return 1 ;; + *) + echo "Invalid bootstrap fallback policy: ${policy}" >&2 + return 2 + ;; + esac +} + +v2k_select_bootstrap_fallback() { + local manifest="$1" phase="$2" code="$3" reason="$4" policy="${5:-sata}" + local bus="sata" + + if ! v2k_bootstrap_fallback_enabled "${policy}"; then + return 1 + fi + + v2k_manifest_runtime_set "${manifest}" ".bootstrap_fallback" \ + "$(jq -nc \ + --arg bus "${bus}" \ + --arg phase "${phase}" \ + --arg reason "${reason}" \ + --argjson code "${code}" \ + '{enabled:true,bus:$bus,phase:$phase,code:$code,reason:$reason}')" || true + v2k_event WARN "cutover" "" "bootstrap_fallback_selected" \ + "$(jq -nc \ + --arg bus "${bus}" \ + --arg phase "${phase}" \ + --arg reason "${reason}" \ + --argjson code "${code}" \ + '{bus:$bus,phase:$phase,code:$code,reason:$reason}')" || true + if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then + echo "[v2k] ${reason}. Falling back to SATA disk controller." + fi + return 0 +} + +v2k_libvirt_redefine_with_bootstrap_fallback() { + local manifest="$1" vcpu="$2" memory="$3" bridge="$4" vlan="$5" network="$6" + local vm xml_path + vm="$(jq -r '.target.libvirt.name' "${manifest}")" + v2k_target_undefine_libvirt "${vm}" || true + if [[ -n "${bridge}" ]]; then + xml_path="$(v2k_target_generate_libvirt_xml "${manifest}" \ + --vcpu "${vcpu}" --memory "${memory}" \ + --bridge "${bridge}" $( [[ -n "${vlan}" ]] && echo --vlan "${vlan}" ) \ + )" + else + xml_path="$(v2k_target_generate_libvirt_xml "${manifest}" \ + --vcpu "${vcpu}" --memory "${memory}" \ + --network "${network}" \ + )" + fi + v2k_target_define_libvirt "${xml_path}" +} + v2k_cmd_cutover() { v2k_require_manifest v2k_load_runtime_flags_from_manifest @@ -2561,6 +2620,7 @@ v2k_cmd_cutover() { local vcpu_set=0 memory_set=0 local network="default" bridge="" vlan="" local force_cleanup=0 + local bootstrap_fallback="${V2K_BOOTSTRAP_FALLBACK:-sata}" #Linux bootstrap (host-chroot initramfs rebuild) local linux_bootstrap=-1 @@ -2600,6 +2660,18 @@ v2k_cmd_cutover() { linux_bootstrap=0 shift 1 ;; + --bootstrap-fallback) + bootstrap_fallback="${2:-}" + case "${bootstrap_fallback}" in + sata|auto|off|none|false|0) ;; + *) echo "Invalid --bootstrap-fallback: ${bootstrap_fallback}" >&2; exit 2 ;; + esac + shift 2 + ;; + --no-bootstrap-fallback) + bootstrap_fallback="off" + shift 1 + ;; --target-provider) target_provider="${2:-}"; target_provider_arg_set=1; shift 2;; --cloud-endpoint) cloud_endpoint="${2:-}"; cloud_config_arg_set=1; shift 2;; --cloud-api-key) cloud_api_key="${2:-}"; shift 2;; @@ -2794,19 +2866,21 @@ v2k_cmd_cutover() { if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then echo "[v2k] Cloud Linux bootstrap(initramfs) requested (guestFamily=linuxGuest)" fi - v2k_linux_bootstrap_initramfs "${V2K_MANIFEST}" - local rc=$? - if [[ "${rc}" -ne 0 ]]; then + if v2k_linux_bootstrap_initramfs "${V2K_MANIFEST}"; then + v2k_event INFO "cutover" "" "cloud_linux_bootstrap_done" "{}" + if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then + echo "[v2k] Cloud Linux bootstrap(initramfs) done (guestFamily=linuxGuest)" + fi + else + local rc=$? v2k_event ERROR "cutover" "" "cloud_linux_bootstrap_failed" "{\"code\":${rc}}" if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then echo "[v2k] Cloud Linux bootstrap(initramfs) failed (guestFamily=linuxGuest)" fi - echo "Cloud Linux bootstrap (initramfs rebuild) failed. code=${rc}" >&2 - exit 74 - fi - v2k_event INFO "cutover" "" "cloud_linux_bootstrap_done" "{}" - if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then - echo "[v2k] Cloud Linux bootstrap(initramfs) done (guestFamily=linuxGuest)" + if ! v2k_select_bootstrap_fallback "${V2K_MANIFEST}" "linux_bootstrap" "${rc}" "Cloud Linux bootstrap(initramfs) failed" "${bootstrap_fallback}"; then + echo "Cloud Linux bootstrap (initramfs rebuild) failed. code=${rc}" >&2 + exit 74 + fi fi else v2k_event INFO "cutover" "" "cloud_linux_bootstrap_skipped" "{\"reason\":\"cli_or_policy\"}" @@ -2817,16 +2891,18 @@ v2k_cmd_cutover() { if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then echo "[v2k] Cloud Windows WinPE bootstrap requested (guestFamily=windowsGuest)" fi - v2k_cloud_windows_winpe_bootstrap "${winpe_iso}" "${virtio_iso}" "${winpe_timeout}" - local rc=$? - if [[ "${rc}" -ne 0 ]]; then + if v2k_cloud_windows_winpe_bootstrap "${winpe_iso}" "${virtio_iso}" "${winpe_timeout}"; then + v2k_event INFO "cutover" "" "cloud_winpe_bootstrap_done" "{}" + if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then + echo "[v2k] Cloud Windows WinPE bootstrap done (guestFamily=windowsGuest)" + fi + else + local rc=$? v2k_event ERROR "cutover" "" "cloud_winpe_bootstrap_failed" "{\"code\":${rc}}" - echo "Cloud Windows WinPE bootstrap failed. code=${rc}" >&2 - exit "${rc}" - fi - v2k_event INFO "cutover" "" "cloud_winpe_bootstrap_done" "{}" - if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then - echo "[v2k] Cloud Windows WinPE bootstrap done (guestFamily=windowsGuest)" + if ! v2k_select_bootstrap_fallback "${V2K_MANIFEST}" "winpe_bootstrap" "${rc}" "Cloud Windows WinPE bootstrap failed" "${bootstrap_fallback}"; then + echo "Cloud Windows WinPE bootstrap failed. code=${rc}" >&2 + exit "${rc}" + fi fi fi @@ -2876,19 +2952,21 @@ v2k_cmd_cutover() { if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then echo "[v2k] Linux bootstrap(initramfs) requested (guestFamily=linuxGuest)" fi - v2k_linux_bootstrap_initramfs "${V2K_MANIFEST}" - local rc=$? - if [[ "${rc}" -ne 0 ]]; then + if v2k_linux_bootstrap_initramfs "${V2K_MANIFEST}"; then + v2k_event INFO "cutover" "" "linux_bootstrap_done" "{}" + if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then + echo "[v2k] Linux bootstrap(initramfs) done (guestFamily=linuxGuest)" + fi + else + local rc=$? v2k_event ERROR "cutover" "" "linux_bootstrap_failed" "{\"code\":${rc}}" if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then echo "[v2k] Linux bootstrap(initramfs) failed (guestFamily=linuxGuest)" fi - echo "Linux bootstrap (initramfs rebuild) failed. code=${rc}" >&2 - exit 74 - fi - v2k_event INFO "cutover" "" "linux_bootstrap_done" "{}" - if [[ "${V2K_JSON_OUT:-0}" -ne 1 ]]; then - echo "[v2k] Linux bootstrap(initramfs) done (guestFamily=linuxGuest)" + if ! v2k_select_bootstrap_fallback "${V2K_MANIFEST}" "linux_bootstrap" "${rc}" "Linux bootstrap(initramfs) failed" "${bootstrap_fallback}"; then + echo "Linux bootstrap (initramfs rebuild) failed. code=${rc}" >&2 + exit 74 + fi fi else v2k_event INFO "cutover" "" "linux_bootstrap_skipped" "{\"reason\":\"cli_or_policy\"}" @@ -2941,7 +3019,21 @@ v2k_cmd_cutover() { if [[ "${winpe_bootstrap}" -eq 1 ]]; then local vm vm="$(jq -r '.target.libvirt.name' "${V2K_MANIFEST}")" - v2k_windows_winpe_bootstrap_libvirt "${V2K_MANIFEST}" "${vm}" "${winpe_iso}" "${virtio_iso}" "${winpe_timeout}" 0 + if v2k_windows_winpe_bootstrap_libvirt "${V2K_MANIFEST}" "${vm}" "${winpe_iso}" "${virtio_iso}" "${winpe_timeout}" 0; then + : + else + local rc=$? + v2k_event ERROR "cutover" "" "libvirt_winpe_bootstrap_failed" "{\"code\":${rc}}" + if v2k_select_bootstrap_fallback "${V2K_MANIFEST}" "winpe_bootstrap" "${rc}" "Windows WinPE bootstrap failed" "${bootstrap_fallback}"; then + if ! v2k_libvirt_redefine_with_bootstrap_fallback "${V2K_MANIFEST}" "${vcpu}" "${memory}" "${bridge}" "${vlan}" "${network}"; then + echo "libvirt redefine with SATA fallback failed." >&2 + exit 65 + fi + else + echo "Windows WinPE bootstrap failed. code=${rc}" >&2 + exit "${rc}" + fi + fi fi # WinPE may trigger host-side auto-unmap on guest shutdown. Re-prepare persistent RBD maps before the final boot. diff --git a/lib/v2k/target_cloud.sh b/lib/v2k/target_cloud.sh index d02db30..584d1af 100755 --- a/lib/v2k/target_cloud.sh +++ b/lib/v2k/target_cloud.sh @@ -223,8 +223,10 @@ v2k_cloud_target_source_deploy_params_json() { | ((.target.cloud.cpu_speed // "1000") | tostring) as $cpu_speed | (($vm.firmware // "") | tostring | ascii_downcase) as $firmware | (($vm.secure_boot // false) == true) as $secure_boot - | (controller(.disks[0].controller.type // "")) as $root_controller - | (controller((.disks[1:] // [] | map(.controller.type // "") | map(select((. | tostring | length) > 0)) | first) // "")) as $data_controller + | ((.runtime.bootstrap_fallback.bus // "") | tostring | ascii_downcase) as $fallback_bus + | ((.disks // []) | length) as $disk_count + | (if $fallback_bus == "sata" then "sata" else controller(.disks[0].controller.type // "") end) as $root_controller + | (if $fallback_bus == "sata" and $disk_count > 1 then "sata" else controller((.disks[1:] // [] | map(.controller.type // "") | map(select((. | tostring | length) > 0)) | first) // "") end) as $data_controller | {} + (if $cpu > 0 then {"details[0].cpuNumber": ($cpu | floor | tostring)} else {} end) + {"details[0].cpuSpeed": $cpu_speed} diff --git a/lib/v2k/target_libvirt.sh b/lib/v2k/target_libvirt.sh index 7dc223b..eb34261 100644 --- a/lib/v2k/target_libvirt.sh +++ b/lib/v2k/target_libvirt.sh @@ -86,9 +86,14 @@ _v2k_rbd_dev_path_from_uri() { _v2k_target_disk_xml() { local manifest="$1" idx="$2" - local path bus dev st + local path bus dev st fallback_bus path="$(jq -r ".disks[$idx].transfer.target_path" "${manifest}")" - bus="$(_v2k_disk_bus_from_controller_type "$(jq -r ".disks[$idx].controller.type // empty" "${manifest}")")" + fallback_bus="$(jq -r '.runtime.bootstrap_fallback.bus // empty' "${manifest}" 2>/dev/null || true)" + if [[ "${fallback_bus}" == "sata" ]]; then + bus="sata" + else + bus="$(_v2k_disk_bus_from_controller_type "$(jq -r ".disks[$idx].controller.type // empty" "${manifest}")")" + fi dev="sd$(_v2k_letter "$idx")" st="$(jq -r '.target.storage.type // "file"' "${manifest}")" @@ -98,6 +103,11 @@ _v2k_target_disk_xml() { if [[ -z "${block_path}" ]]; then block_path="$(_v2k_rbd_dev_path_from_uri "${path}")" fi + local address_xml="" + if [[ "${bus}" != "sata" ]]; then + address_xml=" +
" + fi serial="$(printf ''%s'' "${path#rbd:}" | tr -cd ''[:alnum:]'' | cut -c1-20)" [[ -n "${serial}" ]] || serial="rbd${idx}" cat < ${serial} - -
+${address_xml} EOF return 0 @@ -252,7 +261,14 @@ v2k_target_generate_libvirt_xml() { fi local disks_xml="" - local count + local fallback_bus controller_xml + fallback_bus="$(jq -r '.runtime.bootstrap_fallback.bus // empty' "${manifest}" 2>/dev/null || true)" + if [[ "${fallback_bus}" == "sata" ]]; then + controller_xml="" + else + controller_xml="" + fi + local count i count="$(jq -r '.disks | length' "${manifest}")" for ((i=0;i - + ${controller_xml} ${disks_xml} ${iface_xml} ${tpm_xml}