From 3308b7cf88a4283d3f9b57fe23b81c71a0c6ebe5 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 19 Oct 2026 18:41:48 +0200 Subject: [PATCH 01/26] setup: update the Debian 13 templates, add build-acs-image.sh - templates.sql: use the patched Debian 13 images (20261017-2634) from http://10.0.34.8/templates/debian-13-genericcloud-amd64/ for the KVM, XenServer (vhd.bz2) and VMware (ova) templates, with their SHA-512 checksums - scripts/util/build-acs-image.sh: new script to patch, shrink and convert a cloud image (qcow2) to VHD, OVA and XenServer VHD for CloudStack, and to create SHA512SUMS - schema-21to22.sql, schema-2214to30.sql: remove the inserts of the old CentOS 5 builtin templates (ids 4, 7 and 5) --- .../resources/META-INF/db/schema-21to22.sql | 5 - .../resources/META-INF/db/schema-2214to30.sql | 3 - scripts/util/build-acs-image.sh | 601 ++++++++++++++++++ setup/db/templates.sql | 6 +- 4 files changed, 604 insertions(+), 11 deletions(-) create mode 100755 scripts/util/build-acs-image.sh diff --git a/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql b/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql index 8da29caae53b..6905f62e84ca 100755 --- a/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-21to22.sql @@ -964,11 +964,6 @@ INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type) VALUES (8, 'routing-8', 'SystemVM Template (vSphere)', 0, now(), 'SYSTEM', 0, 32, 1, 'http://download.cloudstack.org/releases/2.2.0/systemvm.ova', '3c9d4c704af44ebd1736e1bc78cec1fa', 0, 'SystemVM Template (vSphere)', 'OVA', 15, 0, 1, 'VMware'); -INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, 'centos55-x86_64', 'CentOS 5.5(64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/2.2.0/eec2209b-9875-3c8d-92be-c001bd8a0faf.qcow2.bz2', 'ed0e788280ff2912ea40f7f91ca7a249', 'CentOS 5.5(64-bit) no GUI (KVM)', 0, 'QCOW2', 112, 1, 1, 'KVM', 1); - -INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, 'centos53-x64', 'CentOS 5.3(64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/2.2.0/CentOS5.3-x86_64.ova', 'f6f881b7f2292948d8494db837fe0f47', 0, 'CentOS 5.3(64-bit) no GUI (vSphere)', 'OVA', 12, 1, 1, 'VMware', 1); UPDATE vm_instance SET guest_os_id=15 where vm_template_id=1; UPDATE vm_instance SET vm_template_id=(SELECT id FROM vm_template WHERE name='systemvm-xenserver-2.2.4' AND removed IS NULL) where vm_template_id=1; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql b/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql index 8be481e47368..fdb260e57728 100755 --- a/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-2214to30.sql @@ -625,9 +625,6 @@ UPDATE `cloud`.`event` e set e.domain_id = (select acc.domain_id from `cloud`.`a update `cloud`.`vm_template` set removed=now() where id=2; -INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, 'centos56-x86_64-xen', 'CentOS 5.6(64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/centos56-x86_64.vhd.bz2', '905cec879afd9c9d22ecc8036131a180', 0, 'CentOS 5.6(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); - DELETE from `cloud`.`configuration` where name='firewall.rule.ui.enabled'; DELETE FROM `cloud`.`resource_limit` WHERE domain_id = 1 AND account_id IS NULL; diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh new file mode 100755 index 000000000000..57fb7ef24ced --- /dev/null +++ b/scripts/util/build-acs-image.sh @@ -0,0 +1,601 @@ +#!/bin/bash +# Prepare a generic cloud qcow2 image for CloudStack: patch it, shrink it and convert it to VHD/OVA. +# +# Usage: build-acs-image.sh [-a ACTIONS] [-f FORMATS] [-o OUTDIR] [-g GUESTOS] [-u] [-z] +# [-d debian|rhel|suse] [-p ROOT_PASSWORD] +# -a actions, a list separated by commas or spaces, done in this order (default: all) +# patch install the guest tools, set the root password, cloud-init and sshd config +# (OS detected from the image: Debian/Ubuntu, EL, SUSE and variants) +# shrink virt-sparsify and qemu-img convert -c +# convert convert to the formats of -f +# all patch, shrink and convert +# -f formats for the convert action, a list like "vhd,ova" or "ova, xen" (default: all) +# vhd plain VHD for Hyper-V (also hyperv) -acs.vhd (removed if xen is built too) +# ova OVA for VMware (also vmware) -acs.ova +# xen VHD for XenServer, bzip2 (also xenserver) -acs-xen.vhd.bz2 +# all vhd, ova and xen +# -o output directory (default: directory of the image) +# -g VMware guestos id for the OVA (default: detected from /etc/os-release in the image, +# e.g. debian12-64, ubuntu-64, rhel9-64, sles15-64, otherlinux-64) +# -u with patch: also run a full package upgrade inside the image +# -z with shrink: do not compress (plain qcow2 rewrite) +# -d with patch: OS family, if it cannot be detected from the image +# -p with patch: root password to set (default: password) +# +# The image is never modified. Patch and shrink work on a copy named -acs. in the +# output directory (the name is kept if it already ends with -acs), and the converted files +# are named -acs.vhd and -acs.ova. Convert alone reads the original image. +# +# SHA512SUMS with the checksums of the output files is written to the output directory when all +# is done (the entries of other files are kept). The tools needed by the actions and formats are +# checked first, and nothing is installed. +set -euo pipefail + +usage() { sed -n '2,29p' "$0"; exit 1; } + +ACTIONS="all" +FORMATS="all" +OUTDIR="" +GUESTOS="" +UPDATE="" +COMPRESS="-c" +OS_FAMILY="" +ROOT_PASSWORD="password" +while getopts "a:f:o:g:uzd:p:" opt; do + case $opt in + a) ACTIONS="$OPTARG" ;; + f) FORMATS="$OPTARG" ;; + o) OUTDIR="$OPTARG" ;; + g) GUESTOS="$OPTARG" ;; + u) UPDATE="--update" ;; + z) COMPRESS="" ;; + d) OS_FAMILY="$OPTARG" ;; + p) ROOT_PASSWORD="$OPTARG" ;; + *) usage ;; + esac +done +shift $((OPTIND - 1)) + +IMAGE="${1:-}" +[ -f "$IMAGE" ] || { echo "Image not found: '$IMAGE'" >&2; usage; } +case "$OS_FAMILY" in ""|debian|rhel|suse) ;; *) echo "Invalid OS family: $OS_FAMILY" >&2; exit 1 ;; esac + +# ACTIONS and FORMATS are lists separated by commas and/or spaces +DO_PATCH="" DO_SHRINK="" DO_CONVERT="" +for a in $(echo "$ACTIONS" | tr 'A-Z,;' 'a-z '); do + case "$a" in + patch) DO_PATCH=1 ;; + shrink) DO_SHRINK=1 ;; + convert) DO_CONVERT=1 ;; + all) DO_PATCH=1; DO_SHRINK=1; DO_CONVERT=1 ;; + *) echo "Invalid action: '$a' (supported: patch, shrink, convert, all)" >&2; exit 1 ;; + esac +done +[ -n "$DO_PATCH$DO_SHRINK$DO_CONVERT" ] || { echo "No action given" >&2; exit 1; } + +WANT_VHD="" WANT_OVA="" WANT_XEN="" +for f in $(echo "$FORMATS" | tr 'A-Z,;' 'a-z '); do + case "$f" in + vhd|hyperv) WANT_VHD=1 ;; + ova|vmware) WANT_OVA=1 ;; + xen|xenserver) WANT_XEN=1 ;; + all) WANT_VHD=1; WANT_OVA=1; WANT_XEN=1 ;; + *) echo "Invalid format: '$f' (supported: vhd, ova, xen, all, or a list like vhd,ova)" >&2; exit 1 ;; + esac +done +[ -n "$WANT_VHD$WANT_OVA$WANT_XEN" ] || { echo "No output format given" >&2; exit 1; } +# the formats are only needed by the convert action +if [ -z "$DO_CONVERT" ]; then WANT_VHD=""; WANT_OVA=""; WANT_XEN=""; fi + +# names: -acs. for the copy that is patched and shrunk, -acs. for the conversions +IMAGE=$(readlink -f "$IMAGE") +[ -n "$OUTDIR" ] || OUTDIR=$(dirname "$IMAGE") +mkdir -p "$OUTDIR" +OUTDIR=$(readlink -f "$OUTDIR") +file=$(basename "$IMAGE") +if [[ "$file" == *.* ]]; then stem="${file%.*}"; ext=".${file##*.}"; else stem="$file"; ext=""; fi +case "$stem" in *-acs) OUT_BASE="$stem" ;; *) OUT_BASE="${stem}-acs" ;; esac +WORK_IMAGE="$OUTDIR/$OUT_BASE$ext" +# patch and shrink work on the copy, convert alone reads the original image +SOURCE="$IMAGE" +if [ -n "$DO_PATCH$DO_SHRINK" ]; then SOURCE="$WORK_IMAGE"; fi + +export LIBGUESTFS_BACKEND=direct + +# Hint on the package names for the missing tools, according to the package manager of the host +pkg_hint() { + local what="$1" + if command -v apt-get >/dev/null 2>&1; then + [ "$what" = "libguestfs" ] && echo "apt install libguestfs-tools qemu-utils" || echo "apt install $what" + elif command -v zypper >/dev/null 2>&1; then + [ "$what" = "libguestfs" ] && echo "zypper install guestfs-tools libguestfs qemu-tools" || echo "zypper install $what" + elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then + [ "$what" = "libguestfs" ] && echo "dnf install guestfs-tools (libguestfs-tools-c on EL8) qemu-img" || echo "dnf install $what" + else + echo "install libguestfs tools and qemu-img" + fi +} + +# Check what the actions and formats need, before anything is created, and report all the +# problems at once. Packages are never installed here, as the script runs on Debian/Ubuntu, +# EL, SUSE, ... +check_dependencies() { + local missing=() warnings=() + local cmd + + for cmd in qemu-img cp mktemp sha512sum; do + command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd not found ($(pkg_hint libguestfs))") + done + if [ -n "$DO_PATCH" ]; then + for cmd in virt-customize virt-cat virt-ls virt-inspector; do + command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd not found ($(pkg_hint libguestfs))") + done + fi + if [ -n "$DO_SHRINK" ]; then + command -v virt-sparsify >/dev/null 2>&1 || missing+=("virt-sparsify not found ($(pkg_hint libguestfs))") + fi + + # the formats needed by qemu-img + if command -v qemu-img >/dev/null 2>&1 && [ -n "$DO_CONVERT" ]; then + local formats + formats=$(qemu-img --help 2>&1 || true) + if [ -n "$WANT_VHD" ]; then + echo "$formats" | grep -qw vpc || missing+=("qemu-img does not support the vpc (VHD) format") + fi + if [ -n "$WANT_OVA" ]; then + echo "$formats" | grep -qw vmdk || missing+=("qemu-img does not support the vmdk format") + fi + fi + + # the VMware guestos of the OVA is detected from the image, unless -g is given + if [ -n "$WANT_OVA" ] && [ -z "$GUESTOS" ] && [ -z "$DO_PATCH" ]; then + command -v virt-cat >/dev/null 2>&1 || missing+=("virt-cat not found, needed to detect the VMware guestos of the OVA from the image ($(pkg_hint libguestfs)), or use -g") + fi + + # ovftool must be runnable, not only be present + if [ -n "$WANT_OVA" ]; then + if ! command -v ovftool >/dev/null 2>&1; then + missing+=("ovftool not found, required for OVA (VMware OVF Tool, add it to PATH)") + elif ! ovftool --version >/dev/null 2>&1; then + missing+=("ovftool cannot be run (check its libraries and that the whole ovftool directory is installed)") + fi + fi + + # the XenServer VHD: bzip2, and the 32-bit vhd-util with faketime (as in tools/appliance/build.sh) + if [ -n "$WANT_XEN" ]; then + command -v bzip2 >/dev/null 2>&1 || missing+=("bzip2 not found, required for the XenServer VHD (install bzip2)") + command -v faketime >/dev/null 2>&1 || missing+=("faketime not found, required for the XenServer VHD (install faketime)") + if ! command -v vhd-util >/dev/null 2>&1; then + missing+=("vhd-util not found, required for the XenServer VHD (see the comments in tools/appliance/build.sh, or http://packages.shapeblue.com/systemvmtemplate/vhd-util)") + else + # vhd-util is a 32-bit binary: it needs the i386 libraries, and faketime must be preloadable into it + local vhd_out + vhd_out=$(ldd "$(command -v vhd-util)" 2>&1 || true) + if echo "$vhd_out" | grep -qE "not found|not a dynamic|No such file"; then + missing+=("vhd-util cannot be run, missing libraries (32-bit libuuid and libc, and libvhd.so.1.0 in the ldconfig path)") + elif command -v faketime >/dev/null 2>&1; then + vhd_out=$(faketime '2010-01-01' vhd-util 2>&1 || true) + echo "$vhd_out" | grep -q "cannot be preloaded" && \ + missing+=("faketime cannot be used with the 32-bit vhd-util (install the 32-bit faketime library, e.g. faketime:i386)") + fi + fi + fi + + # the image must be a readable qcow2 file + local info vsize_gb=1 + if command -v qemu-img >/dev/null 2>&1; then + if ! info=$(qemu-img info --output=json "$IMAGE" 2>&1); then + missing+=("cannot read the image: $(echo "$info" | head -1)") + else + echo "$info" | grep -q '"format": *"qcow2"' || missing+=("$IMAGE is not a qcow2 image") + vsize_gb=$(echo "$info" | sed -n 's/.*"virtual-size": *\([0-9]*\).*/\1/p' | head -1) + vsize_gb=$(( ${vsize_gb:-0} / 1024 / 1024 / 1024 + 1 )) + fi + fi + + # the architecture of the guest: commands cannot be run in a guest of another architecture + # (virt-customize --install/--run-command), and the VHD/OVA are for x86_64 hypervisors + if command -v virt-inspector >/dev/null 2>&1 && [ -z "${missing[*]:-}" ]; then + local guest_arch host_arch + guest_arch=$(virt-inspector -a "$IMAGE" 2>/dev/null | sed -n 's#.*\(.*\).*#\1#p' | head -1) + host_arch=$(uname -m) + if [ -n "$guest_arch" ] && [ "$guest_arch" != "$host_arch" ]; then + if [ -n "$DO_PATCH" ]; then + missing+=("the image is $guest_arch but the host is $host_arch: virt-customize cannot run commands (install packages) in the guest, so the patch action is not possible here; run it on a $guest_arch host, or use -a shrink,convert") + fi + fi + if [ -n "$guest_arch" ] && [ "$guest_arch" != "x86_64" ] && [ -n "$WANT_VHD$WANT_OVA$WANT_XEN" ]; then + warnings+=("the image is $guest_arch, but the VHD (Hyper-V, XenServer) and the OVA (VMware) are normally used with x86_64 images") + fi + fi + + # the output + [ -w "$OUTDIR" ] || missing+=("$OUTDIR is not writable") + if [ -n "$DO_PATCH$DO_SHRINK" ]; then + if [ "$WORK_IMAGE" = "$IMAGE" ]; then + missing+=("$IMAGE is already an -acs image, the original image is never modified (use convert only, or pass the original image)") + elif [ -e "$WORK_IMAGE" ]; then + missing+=("$WORK_IMAGE already exists, remove it or move it away first") + fi + fi + + # disk space in the output directory: the copy, the sparsified and converted copies, the VHD, the VMDK and the OVA + local need_gb=0 free_gb + [ -n "$DO_PATCH$DO_SHRINK" ] && need_gb=$(( need_gb + vsize_gb )) + [ -n "$DO_SHRINK" ] && need_gb=$(( need_gb + 2 * vsize_gb )) + [ -n "$WANT_VHD" ] && need_gb=$(( need_gb + vsize_gb )) + [ -n "$WANT_OVA" ] && need_gb=$(( need_gb + 2 * vsize_gb )) + [ -n "$WANT_XEN" ] && need_gb=$(( need_gb + 2 * vsize_gb )) + free_gb=$(df -P -BG "$OUTDIR" | awk 'NR==2 {gsub("G","",$4); print $4}') + [ -n "$free_gb" ] && [ "$free_gb" -lt "$need_gb" ] 2>/dev/null && \ + warnings+=("only ${free_gb}G free in $OUTDIR, up to ${need_gb}G may be needed") + + if [ -n "$DO_PATCH$DO_SHRINK" ]; then + # libguestfs builds its appliance in TMPDIR (or LIBGUESTFS_CACHEDIR), which needs about 2G + local cache_dir="${LIBGUESTFS_CACHEDIR:-${TMPDIR:-/var/tmp}}" + local cache_free + cache_free=$(df -P -BG "$cache_dir" 2>/dev/null | awk 'NR==2 {gsub("G","",$4); print $4}') + if [ -n "$cache_free" ] && [ "$cache_free" -lt 2 ] 2>/dev/null; then + missing+=("only ${cache_free}G free in $cache_dir for the libguestfs appliance (set LIBGUESTFS_CACHEDIR and TMPDIR to a bigger disk)") + fi + + # the libguestfs appliance is built from the kernel of the host (not of the image): on Ubuntu + # /boot/vmlinuz-* is only readable by root, so supermin fails for other users + if [ "$(id -u)" != "0" ] && ls /boot/vmlinuz-* >/dev/null 2>&1; then + local k + for k in /boot/vmlinuz-*; do [ -r "$k" ] && break; k=; done + [ -n "${k:-}" ] || warnings+=("the host kernel in /boot is not readable by $(id -un), run as root or chmod a+r /boot/vmlinuz-*") + fi + fi + + local item + for item in "${warnings[@]}"; do echo "WARN: $item" >&2; done + if [ ${#missing[@]} -gt 0 ]; then + for item in "${missing[@]}"; do echo "ERROR: $item" >&2; done + echo "ERROR: ${#missing[@]} dependency problem(s), see above" >&2 + exit 1 + fi +} + +# Read /etc/os-release from inside the image (read-only) +load_os_release() { + local osr + osr=$(virt-cat -a "$IMAGE" /etc/os-release 2>/dev/null || virt-cat -a "$IMAGE" /usr/lib/os-release 2>/dev/null || true) + osr_value() { echo "$osr" | sed -n "s/^$1=//p" | head -1 | tr -d "\"'" ; } + OS_ID=$(osr_value ID | tr 'A-Z' 'a-z') + OS_ID_LIKE=$(osr_value ID_LIKE | tr 'A-Z' 'a-z') + OS_VERSION_ID=$(osr_value VERSION_ID) + OS_NAME=$(osr_value PRETTY_NAME) +} + +# Map /etc/os-release to a VMware guestos id (the ids known by ESXi 7/8; newer releases of a +# distribution use the newest id, as older vSphere versions do not know the newest ids) +vmware_guestos() { + local id="$OS_ID" like="$OS_ID_LIKE" major="${OS_VERSION_ID%%.*}" + local n + case "$id" in + debian) + n=$major; [[ "$n" =~ ^[0-9]+$ ]] || n=12 + [ "$n" -gt 12 ] && n=12; [ "$n" -lt 8 ] && n=8 + echo "debian${n}-64"; return ;; + ubuntu) echo "ubuntu-64"; return ;; + rhel|centos|ol|oracle) + n=$major; [[ "$n" =~ ^[0-9]+$ ]] || n=9 + [ "$n" -gt 9 ] && n=9; [ "$n" -lt 6 ] && n=6 + case "$id" in + rhel) echo "rhel${n}-64" ;; + centos) echo "centos${n}-64" ;; + *) echo "oraclelinux${n}-64" ;; + esac + return ;; + rocky) echo "rockylinux-64"; return ;; + almalinux|alma) echo "almalinux-64"; return ;; + fedora) echo "fedora-64"; return ;; + sles|sled|sle-micro) + n=$major; [[ "$n" =~ ^[0-9]+$ ]] || n=15 + [ "$id" = "sle-micro" ] && n=15 + [ "$n" -gt 15 ] && n=15; [ "$n" -lt 11 ] && n=11 + echo "sles${n}-64"; return ;; + opensuse*|suse) echo "opensuse-64"; return ;; + esac + # variants: use the family of ID_LIKE + case " $like " in + *" ubuntu "*) echo "ubuntu-64"; return ;; + *" debian "*) echo "debian12-64"; return ;; + *" rhel "*|*" fedora "*) + n=$major; [[ "$n" =~ ^[0-9]+$ ]] || n=9 + [ "$n" -gt 9 ] && n=9; [ "$n" -lt 6 ] && n=6 + echo "rhel${n}-64"; return ;; + *" suse "*) echo "sles15-64"; return ;; + esac + echo "otherlinux-64" +} + +# Detect the OS family from the os-release of the image, to decide the packages and the cleanup +detect_os() { + if [ -z "$OS_FAMILY" ]; then + local id + for id in $OS_ID $OS_ID_LIKE; do + case "$id" in + debian|ubuntu|linuxmint|raspbian|kali) OS_FAMILY=debian ;; + rhel|centos|fedora|rocky|almalinux|alma|ol|oracle|amzn|scientific|eurolinux|cloudlinux|anolis|openeuler) OS_FAMILY=rhel ;; + suse|sles|sled|sle-micro|opensuse|opensuse-leap|opensuse-tumbleweed|opensuse-microos) OS_FAMILY=suse ;; + esac + [ -n "$OS_FAMILY" ] && break + done + fi + if [ -z "$OS_FAMILY" ]; then + echo "ERROR: unsupported OS in the image (ID='${OS_ID}', ID_LIKE='${OS_ID_LIKE}'), use -d debian|rhel|suse to force a family" >&2 + exit 1 + fi + echo "Detected OS: ${OS_NAME:-$OS_ID} (family: $OS_FAMILY)" + + local base_cleanup='journalctl --vacuum-time=1s; rm -rf /var/log/*; rm -f /root/.bash_history' + case "$OS_FAMILY" in + debian) + PACKAGES="qemu-guest-agent,open-vm-tools,xenstore-utils,libxenstore4" + CLEANUP="apt clean; apt autoclean; apt autoremove --purge -y; $base_cleanup" ;; + rhel) + # there is no xenstore package in the EL repositories + PACKAGES="qemu-guest-agent,open-vm-tools" + CLEANUP="(dnf clean all || yum clean all); rm -rf /var/cache/dnf /var/cache/yum; $base_cleanup" ;; + suse) + PACKAGES="qemu-guest-agent,open-vm-tools,xen-tools-domU" + CLEANUP="zypper --non-interactive clean --all; $base_cleanup" ;; + esac +} + +do_patch() { + echo "== Patching $WORK_IMAGE ==" + local tmp + tmp=$(mktemp -d) + CLEAN_PATHS+=("$tmp") + echo "policy: enabled" > "$tmp/ds-identify.cfg" + cat > "$tmp/cloudstack.cfg" <<'EOF' +datasource_list: [ ConfigDrive, CloudStack, None ] +datasource: + CloudStack: {} + None: {} +EOF + # sshd uses the first value it reads, so 40- loads before cloud-init's 50-cloud-init.conf + printf 'PermitRootLogin yes\nPasswordAuthentication yes\n' > "$tmp/40-password-login.conf" + + # the drop-in only works if sshd_config includes sshd_config.d (not in EL8, Ubuntu 20.04, ...); + # otherwise put the settings at the top of sshd_config, as the first value wins + local sshd_fix='f=/etc/ssh/sshd_config; d=/etc/ssh/sshd_config.d/40-password-login.conf; +if [ -f "$f" ] && ! grep -qiE "^[[:space:]]*Include[[:space:]].*sshd_config\.d" "$f"; then + { cat "$d"; cat "$f"; } > "$f.new" && cat "$f.new" > "$f" && rm -f "$f.new" +fi' + + virt-customize -a "$WORK_IMAGE" \ + $UPDATE \ + --install "$PACKAGES" \ + --root-password "password:$ROOT_PASSWORD" \ + --upload "$tmp/ds-identify.cfg:/etc/cloud/ds-identify.cfg" \ + --upload "$tmp/cloudstack.cfg:/etc/cloud/cloud.cfg.d/cloudstack.cfg" \ + --mkdir /etc/ssh/sshd_config.d \ + --upload "$tmp/40-password-login.conf:/etc/ssh/sshd_config.d/40-password-login.conf" \ + --run-command "$sshd_fix" \ + --run-command "$CLEANUP" + rm -rf "$tmp" + + echo "--- verification ---" + virt-cat -a "$WORK_IMAGE" /etc/cloud/ds-identify.cfg /etc/cloud/cloud.cfg.d/cloudstack.cfg /etc/ssh/sshd_config.d/40-password-login.conf + local d + for d in /usr/sbin /usr/bin; do + virt-ls -a "$WORK_IMAGE" $d | grep -E '^(qemu-ga|vmtoolsd|xenstore)' | sed "s#^#$d/#" || true + done +} + +do_shrink() { + echo "== Shrinking $WORK_IMAGE ==" + local sparse out + sparse=$(mktemp "$OUTDIR/.sparse.XXXXXX") + out=$(mktemp "$OUTDIR/.shrink.XXXXXX") + CLEAN_PATHS+=("$sparse" "$out") + rm -f "$sparse" + virt-sparsify --convert qcow2 "$WORK_IMAGE" "$sparse" + qemu-img convert $COMPRESS -f qcow2 -O qcow2 "$sparse" "$out" + qemu-img check "$out" >/dev/null + chmod --reference="$WORK_IMAGE" "$out" + mv -f "$out" "$WORK_IMAGE" + rm -f "$sparse" + qemu-img info "$WORK_IMAGE" | grep -E 'virtual size|disk size' +} + +do_convert_vhd() { + echo "== Converting to VHD ==" + qemu-img convert -f qcow2 -O vpc "$SOURCE" "$OUTDIR/$OUT_BASE.vhd" + chmod a+r "$OUTDIR/$OUT_BASE.vhd" + echo "Created $OUTDIR/$OUT_BASE.vhd" +} + +do_convert_xen() { + echo "== Converting to the XenServer VHD ==" + local work + work=$(mktemp -d "$OUTDIR/.xen-work.XXXXXX") + CLEAN_PATHS+=("$work") + qemu-img convert -f qcow2 -O raw "$SOURCE" "$work/img.raw" + vhd-util convert -s 0 -t 1 -i "$work/img.raw" -o "$work/stagefixed.vhd" + faketime '2010-01-01' vhd-util convert -s 1 -t 2 -i "$work/stagefixed.vhd" -o "$work/$OUT_BASE-xen.vhd" + bzip2 -f "$work/$OUT_BASE-xen.vhd" + mv -f "$work/$OUT_BASE-xen.vhd.bz2" "$OUTDIR/$OUT_BASE-xen.vhd.bz2" + chmod a+r "$OUTDIR/$OUT_BASE-xen.vhd.bz2" + rm -rf "$work" + echo "Created $OUTDIR/$OUT_BASE-xen.vhd.bz2" +} + +do_convert_ova() { + echo "== Converting to OVA ==" + local work + work=$(mktemp -d "$OUTDIR/.ova-work.XXXXXX") + CLEAN_PATHS+=("$work") + + qemu-img convert -f qcow2 -O vmdk "$SOURCE" "$work/$OUT_BASE.vmdk" + + cat > "$work/$OUT_BASE.vmx" <&2 + fi +} +trap cleanup EXIT + +check_dependencies +# detect the OS before anything is created, so that an unsupported OS stops here +if [ -n "$DO_PATCH" ] || { [ -n "$WANT_OVA" ] && [ -z "$GUESTOS" ]; }; then + load_os_release +fi +[ -n "$DO_PATCH" ] && detect_os +if [ -n "$WANT_OVA" ] && [ -z "$GUESTOS" ]; then + GUESTOS=$(vmware_guestos) + echo "VMware guestos: $GUESTOS (from ID='${OS_ID}', ID_LIKE='${OS_ID_LIKE}', VERSION_ID='${OS_VERSION_ID}')" +fi + +if [ -n "$DO_PATCH$DO_SHRINK" ]; then + cp --reflink=auto "$IMAGE" "$WORK_IMAGE" + WORK_BUSY=1 + echo "Created $WORK_IMAGE from $IMAGE (the original is not modified)" +fi +[ -n "$DO_PATCH" ] && do_patch +[ -n "$DO_SHRINK" ] && do_shrink +WORK_BUSY="" +if [ -n "$DO_CONVERT" ]; then + [ -n "$WANT_VHD" ] && do_convert_vhd + [ -n "$WANT_OVA" ] && do_convert_ova + if [ -n "$WANT_XEN" ]; then + do_convert_xen + # the plain .vhd is not kept when the XenServer .vhd.bz2 is built too + if [ -n "$WANT_VHD" ]; then + rm -f "$OUTDIR/$OUT_BASE.vhd" + echo "Removed $OUTDIR/$OUT_BASE.vhd, as $OUT_BASE-xen.vhd.bz2 was created" + fi + fi +fi + +# SHA512SUMS of the output files, when all is done; the entries of other files are kept +make_sha512sums() { + local f n h + local -A mine=() + local -a names=() + for f in "$(basename "$WORK_IMAGE")" "$OUT_BASE.vhd" "$OUT_BASE.ova" "$OUT_BASE-xen.vhd.bz2"; do + [ -f "$OUTDIR/$f" ] || continue + mine[$f]=1 + names+=("$f") + done + [ ${#names[@]} -gt 0 ] || return 0 + local tmp + tmp=$(mktemp "$OUTDIR/.sums.XXXXXX") + CLEAN_PATHS+=("$tmp") + if [ -f "$OUTDIR/SHA512SUMS" ]; then + while read -r h n; do + [ -n "$n" ] && [ -z "${mine[${n#\*}]:-}" ] && echo "$h $n" >> "$tmp" + done < "$OUTDIR/SHA512SUMS" + fi + (cd "$OUTDIR" && sha512sum "${names[@]}") >> "$tmp" + chmod a+r "$tmp" + mv -f "$tmp" "$OUTDIR/SHA512SUMS" + echo "Created $OUTDIR/SHA512SUMS" +} +make_sha512sums + +echo "== Done ==" +ls -lh "$WORK_IMAGE" "$OUTDIR/$OUT_BASE".vhd "$OUTDIR/$OUT_BASE".ova "$OUTDIR/$OUT_BASE"-xen.vhd.bz2 "$OUTDIR/SHA512SUMS" 2>/dev/null || true +exit 0 diff --git a/setup/db/templates.sql b/setup/db/templates.sql index 8e8f7b7f804d..a6dee71c8480 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'centos55-x86_64', 'CentOS 5.5(64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/2.2.0/eec2209b-9875-3c8d-92be-c001bd8a0faf.qcow2.bz2', 'ed0e788280ff2912ea40f7f91ca7a249', 'CentOS 5.5(64-bit) no GUI (KVM)', 0, 'QCOW2', 112, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.qcow2', '{SHA-512}38389fb9e29448e6d7da460c0342217bd1a8aa6e32e6192fa1c6dbc6f7078f6c8210dde110d8631cc823b2762fb1738f8ae94b3e5a3774cf30a0cf11af69dfb8', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'centos56-x86_64-xen', 'CentOS 5.6(64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/centos56-x86_64.vhd.bz2', '905cec879afd9c9d22ecc8036131a180', 0, 'CentOS 5.6(64-bit) no GUI (XenServer)', 'VHD', 142, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs-xen.vhd.bz2', '{SHA-512}b2687bf7f739f49ed36b266e69d44ed49283b398497d85bda17f0eabc447934ba571629c01d94d8ac985f8133b218c8558cac22a88f78a3efd5f81bcf0689ab7', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'centos53-x64', 'CentOS 5.3(64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/2.2.0/CentOS5.3-x86_64.ova', 'f6f881b7f2292948d8494db837fe0f47', 0, 'CentOS 5.3(64-bit) no GUI (vSphere)', 'OVA', 12, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}a090fafc53cbcf375937a8328500689543d9716a527f8a6059daee49712c56cc0e1553f386af8df1a650fa92819b235f4066df1076f242bf9a9e225f18a10b2b', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From 06a9466ac7702106219beddc2f266620d61d3971 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 20 Oct 2026 09:54:22 +0200 Subject: [PATCH 02/26] schema: set the guest OS of the built-in Debian 13 templates to Debian 12 (64-bit) templates.sql cannot reference the 'Debian GNU/Linux 12 (64-bit)' guest OS because it is only added by a later upgrade script with an auto-generated id. Update the guest_os_id of the built-in Debian 13 templates (KVM, XenServer, VMware) in the 4.23.0.0 to 4.24.0.0 upgrade, looking the guest OS up by name. Also rename the VMware built-in template to debian13-x86_64-vmware to match the KVM and XenServer names, and drop the 4.3 upgrade statement that forced the XenServer built-in template back to guest OS 142. --- .../src/main/resources/META-INF/db/schema-421to430.sql | 1 - .../src/main/resources/META-INF/db/schema-42300to2400.sql | 8 ++++++++ setup/db/templates.sql | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-421to430.sql b/engine/schema/src/main/resources/META-INF/db/schema-421to430.sql index 80d32dab3fb3..838263c99915 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-421to430.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-421to430.sql @@ -129,7 +129,6 @@ ALTER TABLE `cloud`.`volumes` ADD COLUMN `hv_ss_reserve` int(32) unsigned DEFAUL UPDATE `cloud`.`disk_offering` SET `state`='Inactive' WHERE `removed` IS NOT NULL; UPDATE `cloud`.`disk_offering` SET `removed`=NULL; -UPDATE `cloud`.`vm_template` SET `guest_os_id`= "142" WHERE `id` = "5"; UPDATE `cloud`.`vm_template` SET `state`='Inactive' WHERE `removed` IS NOT NULL; UPDATE `cloud`.`vm_template` SET `state`='Active' WHERE `removed` IS NULL; UPDATE `cloud`.`vm_template` SET `removed`=NULL; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 8f59321627db..1ff71f4e0eb1 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -226,3 +226,11 @@ CREATE TABLE IF NOT EXISTS `cloud`.`instance_boot_group_details` ( PRIMARY KEY (`id`), CONSTRAINT `fk_instance_boot_group_details__group_id` FOREIGN KEY (`boot_group_id`) REFERENCES `instance_boot_group` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Set the guest OS of the built-in Debian 13 templates to 'Debian GNU/Linux 12 (64-bit)', which is only added by later upgrade scripts and is not available in templates.sql +UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`guest_os` WHERE `display_name` = 'Debian GNU/Linux 12 (64-bit)' AND `removed` IS NULL LIMIT 1) + WHERE `id` = 4 AND `unique_name` = 'debian13-x86_64-kvm' AND `type` = 'BUILTIN'; +UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`guest_os` WHERE `display_name` = 'Debian GNU/Linux 12 (64-bit)' AND `removed` IS NULL LIMIT 1) + WHERE `id` = 5 AND `unique_name` = 'debian13-x86_64-xen' AND `type` = 'BUILTIN'; +UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`guest_os` WHERE `display_name` = 'Debian GNU/Linux 12 (64-bit)' AND `removed` IS NULL LIMIT 1) + WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN'; diff --git a/setup/db/templates.sql b/setup/db/templates.sql index a6dee71c8480..f4a65e1461a7 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -43,7 +43,7 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}a090fafc53cbcf375937a8328500689543d9716a527f8a6059daee49712c56cc0e1553f386af8df1a650fa92819b235f4066df1076f242bf9a9e225f18a10b2b', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}a090fafc53cbcf375937a8328500689543d9716a527f8a6059daee49712c56cc0e1553f386af8df1a650fa92819b235f4066df1076f242bf9a9e225f18a10b2b', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From 363dea6cf5d90351a9ed381ac5571fa551637826 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 21 Oct 2026 08:30:23 +0200 Subject: [PATCH 03/26] marvin: use the shared public tiny template in the smoke tests get_suitable_test_template now returns the shared public test template (tiny-kvm on KVM), registered by the admin only when it is missing, and falls back to the built-in template. It was already doing this for XenServer. The hypervisor argument is optional and is detected from the hosts when not given. Tests that only need a small guest now use it instead of the built-in template, and test_ipv4_routing, test_snapshots and test_restore_vm no longer register their own copy of the tiny template. Tests that depend on the built-in template are not changed, for example test_deploy_vm_root_resize (needs a template of at least 1 GiB), test_templates, test_sharedfs_lifecycle and test_vm_autoscaling. --- test/integration/smoke/test_affinity_groups.py | 8 ++++---- .../smoke/test_backup_recovery_dummy.py | 6 +++--- .../smoke/test_backup_recovery_kboss.py | 6 +++--- .../smoke/test_backup_recovery_nas.py | 8 ++++---- .../smoke/test_backup_recovery_veeam.py | 6 +++--- test/integration/smoke/test_console_endpoint.py | 8 ++++---- .../smoke/test_deploy_vm_extra_config_data.py | 4 ++-- .../smoke/test_gateway_on_shared_networks.py | 4 ++-- .../smoke/test_host_control_state.py | 6 +++--- .../smoke/test_instance_boot_group.py | 8 ++++---- test/integration/smoke/test_ipv4_routing.py | 14 +++++++------- test/integration/smoke/test_kms_lifecycle.py | 4 ++-- test/integration/smoke/test_migration.py | 4 ++-- test/integration/smoke/test_network.py | 6 +++--- .../smoke/test_persistent_network.py | 4 ++-- .../smoke/test_purge_expunged_vms.py | 6 +++--- test/integration/smoke/test_restore_vm.py | 9 +++------ test/integration/smoke/test_snapshots.py | 11 +++-------- test/integration/smoke/test_usage_events.py | 4 ++-- .../smoke/test_vm_deployment_planner.py | 12 ++++++------ ...test_vm_lifecycle_with_snapshot_or_volume.py | 8 +++----- test/integration/smoke/test_vm_schedule.py | 4 ++-- test/integration/smoke/test_vnf_templates.py | 4 ++-- tools/marvin/marvin/lib/common.py | 17 +++++++++++------ 24 files changed, 83 insertions(+), 88 deletions(-) diff --git a/test/integration/smoke/test_affinity_groups.py b/test/integration/smoke/test_affinity_groups.py index 26271f69e3fd..d6c37d107224 100644 --- a/test/integration/smoke/test_affinity_groups.py +++ b/test/integration/smoke/test_affinity_groups.py @@ -23,7 +23,7 @@ from marvin.lib.base import * from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_virtual_machines) from nose.plugins.attrib import attr @@ -44,14 +44,14 @@ def setUpClass(cls): cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.hypervisor = cls.testClient.getHypervisorInfo() - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, cls.zone.id, - cls.hypervisor + hypervisor=cls.hypervisor ) if cls.template == FAILED: - assert False, "get_template() failed to return template" + assert False, "get_suitable_test_template() failed to return template" cls.services["virtual_machine"]["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_backup_recovery_dummy.py b/test/integration/smoke/test_backup_recovery_dummy.py index 3e2f16f8c686..ab57a797ea89 100644 --- a/test/integration/smoke/test_backup_recovery_dummy.py +++ b/test/integration/smoke/test_backup_recovery_dummy.py @@ -19,7 +19,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.utils import (cleanup_resources) from marvin.lib.base import (Account, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, Configurations, Backup, Volume) -from marvin.lib.common import (get_domain, get_zone, get_template) +from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) from nose.plugins.attrib import attr from marvin.codes import FAILED import time @@ -37,9 +37,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.api_client) - cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + cls.template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] diff --git a/test/integration/smoke/test_backup_recovery_kboss.py b/test/integration/smoke/test_backup_recovery_kboss.py index 10d823da195c..ee2c0bb9fb28 100644 --- a/test/integration/smoke/test_backup_recovery_kboss.py +++ b/test/integration/smoke/test_backup_recovery_kboss.py @@ -20,7 +20,7 @@ from marvin.lib import utils from marvin.lib.base import (Account, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, Backup, Configurations, Volume, StoragePool) -from marvin.lib.common import (get_domain, get_zone, get_template) +from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) from nose.plugins.attrib import attr from marvin.codes import FAILED import time @@ -43,9 +43,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.api_client) - cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + cls.template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] diff --git a/test/integration/smoke/test_backup_recovery_nas.py b/test/integration/smoke/test_backup_recovery_nas.py index 96021b9edff8..ccee60a3806e 100644 --- a/test/integration/smoke/test_backup_recovery_nas.py +++ b/test/integration/smoke/test_backup_recovery_nas.py @@ -21,7 +21,7 @@ from marvin.lib.utils import (cleanup_resources) from marvin.lib.base import (Account, Network, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, NetworkOffering, BackupRepository, Backup, Configurations, Volume, StoragePool) -from marvin.lib.common import (get_domain, get_zone, get_template) +from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) from nose.plugins.attrib import attr from marvin.codes import FAILED import time @@ -39,9 +39,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.api_client) - cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + cls.template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] @@ -245,7 +245,7 @@ def test_vm_backup_create_vm_from_backup_in_another_zone(self): return self.destZone = zones[1] - template = get_template(self.api_client, self.destZone.id, self.services["ostype"]) + template = get_suitable_test_template(self.api_client, self.destZone.id, self.services["ostype"]) list_isolated_network_offerings_response = NetworkOffering.list( self.apiclient, diff --git a/test/integration/smoke/test_backup_recovery_veeam.py b/test/integration/smoke/test_backup_recovery_veeam.py index f99b3db17d44..b36f311b60d0 100644 --- a/test/integration/smoke/test_backup_recovery_veeam.py +++ b/test/integration/smoke/test_backup_recovery_veeam.py @@ -20,7 +20,7 @@ from marvin.lib.utils import wait_until from marvin.lib.base import (Account, ServiceOffering, DiskOffering, Volume, VirtualMachine, BackupOffering, Configurations, Backup, BackupSchedule) -from marvin.lib.common import (get_domain, get_zone, get_template) +from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) from nose.plugins.attrib import attr from marvin.codes import FAILED @@ -39,9 +39,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.apiclient) - cls.template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) + cls.template = get_suitable_test_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] diff --git a/test/integration/smoke/test_console_endpoint.py b/test/integration/smoke/test_console_endpoint.py index 84b893e57012..afd4631d87bc 100644 --- a/test/integration/smoke/test_console_endpoint.py +++ b/test/integration/smoke/test_console_endpoint.py @@ -21,7 +21,7 @@ from marvin.lib.base import * from marvin.lib.common import (get_domain, get_zone, - get_template) + get_suitable_test_template) from nose.plugins.attrib import attr class TestConsoleEndpoint(cloudstackTestCase): @@ -36,14 +36,14 @@ def setUpClass(cls): cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.hypervisor = cls.testClient.getHypervisorInfo() - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, cls.zone.id, - cls.hypervisor + hypervisor=cls.hypervisor ) if cls.template == FAILED: - assert False, "get_template() failed to return template" + assert False, "get_suitable_test_template() failed to return template" cls.services["virtual_machine"]["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_deploy_vm_extra_config_data.py b/test/integration/smoke/test_deploy_vm_extra_config_data.py index 98e60f9d287c..c224e151d194 100644 --- a/test/integration/smoke/test_deploy_vm_extra_config_data.py +++ b/test/integration/smoke/test_deploy_vm_extra_config_data.py @@ -35,7 +35,7 @@ Host) from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_hosts) from marvin.lib.utils import * from nose.plugins.attrib import attr @@ -155,7 +155,7 @@ def deploy_vm(self, hypervisor, extra_config=None): if extra_config is not None: cmd.extraconfig = extra_config - template = get_template( + template = get_suitable_test_template( self.apiclient, self.zone.id, hypervisor=hypervisor diff --git a/test/integration/smoke/test_gateway_on_shared_networks.py b/test/integration/smoke/test_gateway_on_shared_networks.py index dd2982f569db..bcf59a4bf093 100644 --- a/test/integration/smoke/test_gateway_on_shared_networks.py +++ b/test/integration/smoke/test_gateway_on_shared_networks.py @@ -29,7 +29,7 @@ ) from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, find_storage_pool_type, get_builtin_template_info) from marvin.codes import ( @@ -69,7 +69,7 @@ def setUpClass(cls): cls.services["template"]["hypervisor"] = builtin_info[1] cls.services["template"]["format"] = builtin_info[2] - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, cls.zone.id ) diff --git a/test/integration/smoke/test_host_control_state.py b/test/integration/smoke/test_host_control_state.py index 4b8409ecc276..7edb39322660 100644 --- a/test/integration/smoke/test_host_control_state.py +++ b/test/integration/smoke/test_host_control_state.py @@ -25,7 +25,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_hosts, list_routers, list_ssvms, @@ -55,10 +55,10 @@ def setUpClass(cls): cls.hypervisor = cls.testClient.getHypervisorInfo() cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, cls.zone.id, - cls.hypervisor + hypervisor=cls.hypervisor ) cls.services["virtual_machine"]["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_instance_boot_group.py b/test/integration/smoke/test_instance_boot_group.py index 69c0e333637e..e0c9b9beb7dd 100644 --- a/test/integration/smoke/test_instance_boot_group.py +++ b/test/integration/smoke/test_instance_boot_group.py @@ -26,7 +26,7 @@ InstanceBootGroupReadinessRule) from marvin.lib.common import (get_domain, get_zone, - get_template) + get_suitable_test_template) from marvin.lib.utils import (random_gen) from marvin.cloudstackException import CloudstackAPIException from marvin.codes import FAILED @@ -51,13 +51,13 @@ def setUpClass(cls): cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) cls.hypervisor = testClient.getHypervisorInfo() - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, - zone_id=cls.zone.id, + zoneid=cls.zone.id, hypervisor=cls.hypervisor ) if cls.template == FAILED: - assert False, "get_template() failed to return template" + assert False, "get_suitable_test_template() failed to return template" cls._cleanup = [] cls.account = Account.create( diff --git a/test/integration/smoke/test_ipv4_routing.py b/test/integration/smoke/test_ipv4_routing.py index 124be678965c..d6461610b0bd 100644 --- a/test/integration/smoke/test_ipv4_routing.py +++ b/test/integration/smoke/test_ipv4_routing.py @@ -22,10 +22,11 @@ import time from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.codes import FAILED from marvin.lib.base import ZoneIpv4Subnet, Domain, Account, ServiceOffering, NetworkOffering, VpcOffering, Network, \ - Ipv4SubnetForGuestNetwork, VirtualMachine, VPC, NetworkACLList, NetworkACL, RoutingFirewallRule, Template, ASNRange, \ + Ipv4SubnetForGuestNetwork, VirtualMachine, VPC, NetworkACLList, NetworkACL, RoutingFirewallRule, ASNRange, \ BgpPeer, Router -from marvin.lib.common import get_domain, get_zone, list_routers, list_hosts +from marvin.lib.common import get_domain, get_zone, get_test_template, list_routers, list_hosts from marvin.lib.utils import get_host_credentials, get_process_status from nose.plugins.attrib import attr @@ -167,11 +168,10 @@ def setUpClass(cls): cls.logger.setLevel(logging.DEBUG) cls.logger.addHandler(cls.stream_handler) - # 0. register template - cls.template = Template.register(cls.apiclient, cls.services["test_templates"][cls.hypervisor.lower()], - zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower()) - cls.template.download(cls.apiclient) - cls._cleanup.append(cls.template) + # 0. get the shared public test template + cls.template = get_test_template(cls.apiclient, cls.zone.id, cls.hypervisor) + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" # 1.1 create subnet for zone cls.subnet_1 = ZoneIpv4Subnet.create( diff --git a/test/integration/smoke/test_kms_lifecycle.py b/test/integration/smoke/test_kms_lifecycle.py index 79deb5bdf0e2..f23e0bc3aa8f 100644 --- a/test/integration/smoke/test_kms_lifecycle.py +++ b/test/integration/smoke/test_kms_lifecycle.py @@ -42,7 +42,7 @@ ServiceOffering, Volume, ) -from marvin.lib.common import get_zone, get_domain, get_template +from marvin.lib.common import get_zone, get_domain, get_suitable_test_template from marvin.lib.utils import cleanup_resources from nose.plugins.attrib import attr @@ -428,7 +428,7 @@ def test_12_deploy_vm_with_root_disk_encryption(self): key = self._create_kms_key(name=_random_name("vm-root-key"), profile_id=self.default_profile.id, apiclient=self.user_apiclient) # 2. Get a template and a service offering - template = get_template( + template = get_suitable_test_template( self.apiclient, self.zone.id, self.test_client.getParsedTestDataConfig().get("ostype", "CentOS 7.0 (64-bit)") diff --git a/test/integration/smoke/test_migration.py b/test/integration/smoke/test_migration.py index cadb50692a9b..24b74d73f526 100644 --- a/test/integration/smoke/test_migration.py +++ b/test/integration/smoke/test_migration.py @@ -31,7 +31,7 @@ ) from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_virtual_machines) from marvin.lib.utils import (get_hypervisor_type, @@ -90,7 +90,7 @@ def setUpClass(cls): cls.zone = get_zone( cls.api_client, cls.testClient.getZoneForTests()) - cls.template = get_template( + cls.template = get_suitable_test_template( cls.api_client, cls.zone.id, cls.test_data["ostype"] diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index 3a0db0feafee..a47a5c37e776 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -45,7 +45,7 @@ from marvin.lib.common import (get_domain, get_free_vlan, get_zone, - get_template, + get_suitable_test_template, get_test_template, list_hosts, list_publicIP, @@ -1925,8 +1925,8 @@ def setUpClass(cls): # Get Zone, Domain and templates cls.domain = get_domain(cls.apiclient) cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) - cls.template = get_template(cls.apiclient, cls.zone.id, - cls.services["ostype"]) + cls.template = get_suitable_test_template(cls.apiclient, cls.zone.id, + cls.services["ostype"]) cls.services["virtual_machine"]["zoneid"] = cls.zone.id cls.services["virtual_machine"]["template"] = cls.template.id diff --git a/test/integration/smoke/test_persistent_network.py b/test/integration/smoke/test_persistent_network.py index 5ca3d6a506aa..d8ceaad6edc2 100644 --- a/test/integration/smoke/test_persistent_network.py +++ b/test/integration/smoke/test_persistent_network.py @@ -30,7 +30,7 @@ ServiceOffering, NetworkOffering) from marvin.lib.common import (get_zone, - get_template, + get_suitable_test_template, verifyNetworkState, wait_for_cleanup, list_routers, list_hosts) from nose.plugins.attrib import attr @@ -74,7 +74,7 @@ def setUpClass(cls): 0].__dict__ # Get Zone and templates cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) - cls.template = get_template( + cls.template = get_suitable_test_template( cls.api_client, cls.zone.id, cls.services["ostype"] diff --git a/test/integration/smoke/test_purge_expunged_vms.py b/test/integration/smoke/test_purge_expunged_vms.py index 4a5da9d1c3ac..3939a30284b8 100644 --- a/test/integration/smoke/test_purge_expunged_vms.py +++ b/test/integration/smoke/test_purge_expunged_vms.py @@ -33,7 +33,7 @@ Configurations) from marvin.lib.common import (get_domain, get_zone, - get_template) + get_suitable_test_template) from marvin.lib.utils import (random_gen) from marvin.lib.decoratorGenerators import skipTestIf from marvin.sshClient import SshClient @@ -75,12 +75,12 @@ def setUpClass(cls): cls.logger = logging.getLogger('TestPurgeExpungedVms') cls.logger.setLevel(logging.DEBUG) - template = get_template( + template = get_suitable_test_template( cls.apiclient, cls.zone.id, cls.services["ostype"]) if template == FAILED: - assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] # Set Zones and disk offerings cls.services["small"]["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_restore_vm.py b/test/integration/smoke/test_restore_vm.py index b961bee39f28..a97395d7b55a 100644 --- a/test/integration/smoke/test_restore_vm.py +++ b/test/integration/smoke/test_restore_vm.py @@ -22,7 +22,7 @@ # Import Local Modules from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.base import (VirtualMachine, Volume, DiskOffering, ServiceOffering, Template) -from marvin.lib.common import (get_zone, get_domain, list_storage_pools) +from marvin.lib.common import (get_zone, get_domain, get_test_template, list_storage_pools) from nose.plugins.attrib import attr _multiprocess_shared_ = True @@ -51,11 +51,8 @@ def setUpClass(cls): cls.disk_offering = DiskOffering.create(cls.apiclient, cls.services["disk_offering"], disksize='8') cls._cleanup.append(cls.disk_offering) - template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][ - cls.hypervisor.lower() if cls.hypervisor.lower() != 'simulator' else 'xenserver'], - zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower()) - cls._cleanup.append(template_t1) - template_t1.download(cls.apiclient) + # the first template is the shared public test template, the restore needs another one + template_t1 = get_test_template(cls.apiclient, cls.zone.id, cls.hypervisor) cls.template_t1 = Template.list(cls.apiclient, templatefilter='all', id=template_t1.id)[0] template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][ diff --git a/test/integration/smoke/test_snapshots.py b/test/integration/smoke/test_snapshots.py index b1a2569d9694..b7ea437285b4 100644 --- a/test/integration/smoke/test_snapshots.py +++ b/test/integration/smoke/test_snapshots.py @@ -434,14 +434,9 @@ def setUpClass(cls): UserName=cls.account.name, DomainName=cls.account.domain ) - cls.template = Template.register( - cls.userapiclient, - cls.services["test_templates"][cls.hypervisor.lower()], - zoneid=cls.zone.id, - hypervisor=cls.hypervisor - ) - cls._cleanup.append(cls.template) - cls.template.download(cls.apiclient) + cls.template = get_test_template(cls.apiclient, cls.zone.id, cls.hypervisor) + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" cls.ostypeid = cls.template.ostypeid cls.virtual_machine = VirtualMachine.create( cls.userapiclient, diff --git a/test/integration/smoke/test_usage_events.py b/test/integration/smoke/test_usage_events.py index 94bf75ac3e31..890c93163b72 100644 --- a/test/integration/smoke/test_usage_events.py +++ b/test/integration/smoke/test_usage_events.py @@ -24,7 +24,7 @@ VirtualMachine) from marvin.lib.common import (get_domain, get_zone, - get_template) + get_suitable_test_template) from marvin.codes import (PASS) class TestUsageEvents(cloudstackTestCase): @@ -39,7 +39,7 @@ def setUpClass(cls): cls.domain = get_domain(cls.apiclient) cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, cls.zone.id, cls.testdata["ostype"]) diff --git a/test/integration/smoke/test_vm_deployment_planner.py b/test/integration/smoke/test_vm_deployment_planner.py index e8d24cbf31e5..418264f5533b 100644 --- a/test/integration/smoke/test_vm_deployment_planner.py +++ b/test/integration/smoke/test_vm_deployment_planner.py @@ -24,7 +24,7 @@ Host, Pod, Cluster) from marvin.lib.common import (get_domain, get_zone, - get_template) + get_suitable_test_template) from marvin.lib.utils import cleanup_resources from nose.plugins.attrib import attr @@ -67,7 +67,7 @@ def tearDownClass(cls): def deploy_vm(self, destination_id): cmd = deployVirtualMachine.deployVirtualMachineCmd() - template = get_template( + template = get_suitable_test_template( self.apiclient, self.zone.id ) @@ -112,7 +112,7 @@ def test_02_deploy_vm_on_specific_cluster(self): target_id = target_cluster.id cluster_hypervisor = target_cluster.hypervisortype - template = get_template( + template = get_suitable_test_template( self.apiclient, hypervisor=cluster_hypervisor ) @@ -153,7 +153,7 @@ def test_03_deploy_vm_on_specific_pod(self): cmd.zoneid = target_pod.zoneid cmd.serviceofferingid = self.service_offering.id - template = get_template( + template = get_suitable_test_template( self.apiclient, hypervisor=host[0].hypervisortype ) @@ -193,7 +193,7 @@ def test_04_deploy_vm_on_host_override_pod_and_cluster(self): # Required parameters cmd.zoneid = self.zone.id cmd.serviceofferingid = self.service_offering.id - template = get_template(self.apiclient, zone_id=self.zone.id, hypervisor=host.hypervisor) + template = get_suitable_test_template(self.apiclient, zoneid=self.zone.id, hypervisor=host.hypervisor) cmd.templateid = template.id # Add optional deployment params @@ -229,7 +229,7 @@ def test_05_deploy_vm_on_cluster_override_pod(self): # Required parameters cmd.zoneid = self.zone.id cmd.serviceofferingid = self.service_offering.id - template = get_template(self.apiclient, zone_id=self.zone.id, hypervisor=clusters[0].hypervisortype) + template = get_suitable_test_template(self.apiclient, zoneid=self.zone.id, hypervisor=clusters[0].hypervisortype) cmd.templateid = template.id # Add optional deployment params diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py index 871cf25a4021..c67914b583f5 100644 --- a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -33,7 +33,7 @@ Template) from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_hosts, list_volumes, list_storage_pools) @@ -60,11 +60,9 @@ def setUpClass(cls): if cls.hypervisor.lower() != "kvm": raise unittest.SkipTest("Only KVM hypervisor is supported for deployment of a VM with volume/snapshot") - cls.template = get_template( + cls.template = get_suitable_test_template( cls.apiclient, - cls.zone.id, - account="system" - ) + cls.zone.id) if cls.template == FAILED: assert False, "get_template failed to return template with description [system]" diff --git a/test/integration/smoke/test_vm_schedule.py b/test/integration/smoke/test_vm_schedule.py index 1e476c55fc87..99f590e9d3f8 100644 --- a/test/integration/smoke/test_vm_schedule.py +++ b/test/integration/smoke/test_vm_schedule.py @@ -18,7 +18,7 @@ """ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.base import Account, ServiceOffering, VirtualMachine, ResourceSchedule -from marvin.lib.common import get_domain, get_zone, get_template +from marvin.lib.common import get_domain, get_zone, get_suitable_test_template from marvin.lib.utils import cleanup_resources # Import Local Modules @@ -96,7 +96,7 @@ def setUpClass(cls): cls.domain = get_domain(cls.api_client) cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) cls.services["mode"] = cls.zone.networktype - template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) cls.services["domainid"] = cls.domain.id cls.services["server"]["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_vnf_templates.py b/test/integration/smoke/test_vnf_templates.py index f963ce41b384..757f128ff182 100644 --- a/test/integration/smoke/test_vnf_templates.py +++ b/test/integration/smoke/test_vnf_templates.py @@ -28,7 +28,7 @@ VnfAppliance, VnfTemplate, Zone) -from marvin.lib.common import get_zone, get_template +from marvin.lib.common import get_zone, get_suitable_test_template from nose.plugins.attrib import attr import time @@ -55,7 +55,7 @@ def setUpClass(cls): cls.hypervisor = cls.testClient.getHypervisorInfo() zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.zone = Zone(zone.__dict__) - cls.template = get_template(cls.apiclient, cls.zone.id) + cls.template = get_suitable_test_template(cls.apiclient, cls.zone.id) cls.domain = Domain.create( cls.apiclient, diff --git a/tools/marvin/marvin/lib/common.py b/tools/marvin/marvin/lib/common.py index cc773654faa0..5e574b742c1b 100644 --- a/tools/marvin/marvin/lib/common.py +++ b/tools/marvin/marvin/lib/common.py @@ -74,7 +74,8 @@ xsplit, get_process_status, random_gen, - format_volume_to_ext3) + format_volume_to_ext3, + get_hypervisor_type) from marvin.lib.base import (PhysicalNetwork, PublicIPAddress, NetworkOffering, @@ -516,19 +517,23 @@ def get_windows_template( return FAILED -def get_suitable_test_template(apiclient, zoneid, ostypeid, hypervisor, deploy_as_is=False): +def get_suitable_test_template(apiclient, zoneid=None, ostypeid=None, hypervisor=None, deploy_as_is=False): ''' @Name : get_suitable_test_template @Desc : Retrieves the test template information based upon inputs provided - For Xenserver, get_test_template is used for retrieving the template - while get_template is used for other hypervisors or when - get_test_template fails + For XenServer and KVM, get_test_template is used for retrieving the + shared public test template (tiny), which is registered by the + admin only when it is missing. get_template is used for other + hypervisors or when get_test_template fails + The hypervisor is detected from the hosts when it is not given @Input : returns a template" @Output : FAILED in case of any failure template Information matching the inputs ''' template = FAILED - if hypervisor.lower() in ["xenserver"] or (hypervisor.lower() in ["vmware"] and deploy_as_is): + if hypervisor is None: + hypervisor = get_hypervisor_type(apiclient) + if hypervisor.lower() in ["xenserver", "kvm"] or (hypervisor.lower() in ["vmware"] and deploy_as_is): template = get_test_template( apiclient, zoneid, From 5ae335ce3c80d1cac9da47c22fa018d046dded0f Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 21 Oct 2026 14:28:38 +0200 Subject: [PATCH 04/26] test_snapshots: register the template in the standalone backup class again TestSnapshotStandaloneBackup takes a snapshot, expunges the VM and then deletes its template, to test the snapshot without its template. It needs its own template, so it cannot use the shared public test template. --- test/integration/smoke/test_snapshots.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/integration/smoke/test_snapshots.py b/test/integration/smoke/test_snapshots.py index b7ea437285b4..b1a2569d9694 100644 --- a/test/integration/smoke/test_snapshots.py +++ b/test/integration/smoke/test_snapshots.py @@ -434,9 +434,14 @@ def setUpClass(cls): UserName=cls.account.name, DomainName=cls.account.domain ) - cls.template = get_test_template(cls.apiclient, cls.zone.id, cls.hypervisor) - if cls.template == FAILED: - assert False, "get_test_template() failed to return template" + cls.template = Template.register( + cls.userapiclient, + cls.services["test_templates"][cls.hypervisor.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hypervisor + ) + cls._cleanup.append(cls.template) + cls.template.download(cls.apiclient) cls.ostypeid = cls.template.ostypeid cls.virtual_machine = VirtualMachine.create( cls.userapiclient, From b8ad53f0e22695bb3f2425a8828610d7dd845763 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 21 Oct 2026 21:13:21 +0200 Subject: [PATCH 05/26] build-acs-image.sh: install nfs-common in the Debian and Ubuntu images The Debian 13 cloud image has no NFS client, so mounting an NFS share in the guest fails with "NFS: mount program didn't pass remote address". The shared file system smoke test mounts the share in the guest. Install nfs-common with the other guest packages. --- scripts/util/build-acs-image.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh index 57fb7ef24ced..15ad42d03d5b 100755 --- a/scripts/util/build-acs-image.sh +++ b/scripts/util/build-acs-image.sh @@ -333,7 +333,8 @@ detect_os() { local base_cleanup='journalctl --vacuum-time=1s; rm -rf /var/log/*; rm -f /root/.bash_history' case "$OS_FAMILY" in debian) - PACKAGES="qemu-guest-agent,open-vm-tools,xenstore-utils,libxenstore4" + # nfs-common is the NFS client, the cloud images do not have it and the tests mount NFS in the guest + PACKAGES="qemu-guest-agent,open-vm-tools,xenstore-utils,libxenstore4,nfs-common" CLEANUP="apt clean; apt autoclean; apt autoremove --purge -y; $base_cleanup" ;; rhel) # there is no xenstore package in the EL repositories From 9729c4f16a221adebff16ce6a9bed3bffe51ada2 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 22 Oct 2026 11:10:37 +0200 Subject: [PATCH 06/26] smoke tests: use the Debian 13 built-in template on all hypervisors Moving the smoke tests to the shared tiny-kvm template broke the tests that need more from the guest. Run these on the Debian 13 built-in template, taken with get_template on every hypervisor. - test_attach_multiple_volumes, test_volumes: they detach data disks from a running VM, which the tiny-kvm guest never releases. - test_backup_recovery_nas, test_backup_recovery_kboss: they create a VM from a backup and expect the files written in the guest to be in it. - test_primary_storage: it puts a primary storage with running VMs in maintenance, which needs a guest that shuts down on request. - test_volumes: use the 512 MiB big offering, as the Debian 13 guest does not boot with the 128 MiB tiny or the 256 MiB small offering. - test_vm_autoscaling: the scale up condition needs at least 1 percent CPU, and an idle Debian 13 VM uses about 0.4 percent. Start a CPU loop from the template user data. --- .../smoke/test_attach_multiple_volumes.py | 11 ++--- .../smoke/test_backup_recovery_kboss.py | 6 +-- .../smoke/test_backup_recovery_nas.py | 8 ++-- .../integration/smoke/test_primary_storage.py | 18 ++------ test/integration/smoke/test_vm_autoscaling.py | 5 ++- test/integration/smoke/test_volumes.py | 41 ++++++------------- 6 files changed, 31 insertions(+), 58 deletions(-) diff --git a/test/integration/smoke/test_attach_multiple_volumes.py b/test/integration/smoke/test_attach_multiple_volumes.py index 9357e4d65090..89bf780140f0 100644 --- a/test/integration/smoke/test_attach_multiple_volumes.py +++ b/test/integration/smoke/test_attach_multiple_volumes.py @@ -27,9 +27,8 @@ Volume, DiskOffering, ) -from marvin.lib.common import (get_domain, +from marvin.lib.common import (get_template, get_domain, get_zone, - get_suitable_test_template, find_storage_pool_type) from marvin.codes import ( PASS, @@ -69,13 +68,9 @@ def setUpClass(cls): ) cls._cleanup.append(cls.disk_offering) - template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor) + template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["domainid"] = cls.domain.id cls.services["zoneid"] = cls.zone.id diff --git a/test/integration/smoke/test_backup_recovery_kboss.py b/test/integration/smoke/test_backup_recovery_kboss.py index ee2c0bb9fb28..971f379f6db5 100644 --- a/test/integration/smoke/test_backup_recovery_kboss.py +++ b/test/integration/smoke/test_backup_recovery_kboss.py @@ -20,7 +20,7 @@ from marvin.lib import utils from marvin.lib.base import (Account, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, Backup, Configurations, Volume, StoragePool) -from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) +from marvin.lib.common import (get_template, get_domain, get_zone) from nose.plugins.attrib import attr from marvin.codes import FAILED import time @@ -43,9 +43,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.api_client) - cls.template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] diff --git a/test/integration/smoke/test_backup_recovery_nas.py b/test/integration/smoke/test_backup_recovery_nas.py index ccee60a3806e..31169e188739 100644 --- a/test/integration/smoke/test_backup_recovery_nas.py +++ b/test/integration/smoke/test_backup_recovery_nas.py @@ -21,7 +21,7 @@ from marvin.lib.utils import (cleanup_resources) from marvin.lib.base import (Account, Network, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, NetworkOffering, BackupRepository, Backup, Configurations, Volume, StoragePool) -from marvin.lib.common import (get_domain, get_zone, get_suitable_test_template) +from marvin.lib.common import (get_template, get_domain, get_zone) from nose.plugins.attrib import attr from marvin.codes import FAILED import time @@ -39,9 +39,9 @@ def setUpClass(cls): cls.services["mode"] = cls.zone.networktype cls.hypervisor = cls.testClient.getHypervisorInfo() cls.domain = get_domain(cls.api_client) - cls.template = get_suitable_test_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = cls.template.id cls._cleanup = [] @@ -245,7 +245,7 @@ def test_vm_backup_create_vm_from_backup_in_another_zone(self): return self.destZone = zones[1] - template = get_suitable_test_template(self.api_client, self.destZone.id, self.services["ostype"]) + template = get_template(self.api_client, self.destZone.id, self.services["ostype"]) list_isolated_network_offerings_response = NetworkOffering.list( self.apiclient, diff --git a/test/integration/smoke/test_primary_storage.py b/test/integration/smoke/test_primary_storage.py index 477d3317ad69..569335e7e77e 100644 --- a/test/integration/smoke/test_primary_storage.py +++ b/test/integration/smoke/test_primary_storage.py @@ -42,14 +42,9 @@ def setUp(self): self.pod = get_pod(self.apiclient, self.zone.id) self.hypervisor = self.testClient.getHypervisorInfo() self.domain = get_domain(self.apiclient) - self.template = get_suitable_test_template( - self.apiclient, - self.zone.id, - self.services["ostype"], - self.hypervisor - ) + self.template = get_template(self.apiclient, self.zone.id, self.services["ostype"]) if self.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % self.services["ostype"] + assert False, "get_template() failed to return template with description %s" % self.services["ostype"] self.excluded_pools = [] storage_pool_list = StoragePool.list(self.apiclient, zoneid=self.zone.id) @@ -392,14 +387,9 @@ def setUpClass(cls): cls.pod = get_pod(cls.apiclient, cls.zone.id) cls.hypervisor = testClient.getHypervisorInfo() cls.domain = get_domain(cls.apiclient) - cls.template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor - ) + cls.template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["virtual_machine"]["zoneid"] = cls.zone.id cls.services["virtual_machine"]["template"] = cls.template.id cls.services["storage_tags"] = StorageTagsServices().storage_tags diff --git a/test/integration/smoke/test_vm_autoscaling.py b/test/integration/smoke/test_vm_autoscaling.py index dd507ece0391..1b41e8b3b3b4 100644 --- a/test/integration/smoke/test_vm_autoscaling.py +++ b/test/integration/smoke/test_vm_autoscaling.py @@ -220,11 +220,14 @@ def setUpClass(cls): cls.templateUserdata = UserData.register( cls.apiclient, name="TemplateUserdata", - userdata="IyMgdGVtcGxhdGU6IGppbmphCiNjbG91ZC1jb25maWcKcnVuY21kOgogICAgLSBlY2hvICdrZXkge3sgZHMubWV0YV9kYXRhLmtleTEgfX0nID4+IC9yb290L2luc3RhbmNlX21ldGFkYXRhCgo=", + userdata="IyMgdGVtcGxhdGU6IGppbmphCiNjbG91ZC1jb25maWcKcnVuY21kOgogICAgLSBlY2hvICdrZXkge3sgZHMubWV0YV9kYXRhLmtleTEgfX0nID4+IC9yb290L2luc3RhbmNlX21ldGFkYXRhCiAgICAtIG5vaHVwIHNoIC1jICd3aGlsZSB0cnVlOyBkbyA6OyBkb25lJyA+IC9kZXYvbnVsbCAyPiYxICYKCg==", # ## template: jinja # #cloud-config # runcmd: # - echo 'key {{ ds.meta_data.key1 }}' >> /root/instance_metadata + # - nohup sh -c 'while true; do :; done' > /dev/null 2>&1 & + # (the CPU loop keeps the VM above the 1 percent CPU threshold of the scale up condition, + # an idle Debian 13 VM only uses about 0.4 percent) # account=cls.regular_user.name, domainid=cls.regular_user.domainid diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index ad1c359c27b5..441f10c2c034 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -38,8 +38,7 @@ Host, DiskOffering, StoragePool) -from marvin.lib.common import (get_domain, - get_suitable_test_template, +from marvin.lib.common import (get_template, get_domain, get_zone, find_storage_pool_type, get_pod, @@ -94,14 +93,9 @@ def setUpClass(cls): ) cls._cleanup.append(cls.custom_disk_offering) - template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor - ) + template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["domainid"] = cls.domain.id cls.services["zoneid"] = cls.zone.id @@ -115,9 +109,10 @@ def setUpClass(cls): domainid=cls.domain.id ) cls._cleanup.append(cls.account) + # the Debian 13 built-in template does not boot with the 128 MiB of the tiny offering cls.service_offering = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["tiny"] + cls.services["service_offerings"]["big"] ) cls.virtual_machine = VirtualMachine.create( cls.apiclient, @@ -307,15 +302,9 @@ def setUpClass(cls): ) cls._cleanup.append(cls.custom_resized_disk_offering) - cls.template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor, - deploy_as_is=cls.hypervisor.lower() == "vmware" - ) + cls.template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if cls.template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["domainid"] = cls.domain.id cls.services["zoneid"] = cls.zone.id @@ -331,9 +320,10 @@ def setUpClass(cls): domainid=cls.domain.id ) cls._cleanup.append(cls.account) + # the Debian 13 built-in template does not boot with the 128 MiB of the tiny offering cls.service_offering = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["tiny"] + cls.services["service_offerings"]["big"] ) cls._cleanup.append(cls.service_offering) cls.virtual_machine = VirtualMachine.create( @@ -1121,14 +1111,9 @@ def setUpClass(cls): cls.services["zoneid"] = cls.zone.id # Get template - template = get_suitable_test_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"], - cls.hypervisor - ) + template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) if template == FAILED: - assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] cls.services["template"] = template.id cls.services["diskname"] = cls.services["volume"]["diskname"] @@ -1146,14 +1131,14 @@ def setUpClass(cls): # Create Service Offering cls.service_offering = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["small"] + cls.services["service_offerings"]["big"] ) cls._cleanup.append(cls.service_offering) # Create Service Offering with encryptRoot true cls.service_offering_encrypt = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["small"], + cls.services["service_offerings"]["big"], name="Small Encrypted Instance", encryptroot=True ) From 54d8e6e316b8efbcb8183f883b289ce8d6c4fa6e Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 22 Oct 2026 11:14:30 +0200 Subject: [PATCH 07/26] templates.sql: update the SHA-512 checksums of the Debian 13 built-in templates The images were rebuilt with the nfs-common package installed. The URLs are unchanged, so the KVM, XenServer and VMware templates get the checksums of the new files. --- setup/db/templates.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/templates.sql b/setup/db/templates.sql index f4a65e1461a7..e2ba6cc7625e 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.qcow2', '{SHA-512}38389fb9e29448e6d7da460c0342217bd1a8aa6e32e6192fa1c6dbc6f7078f6c8210dde110d8631cc823b2762fb1738f8ae94b3e5a3774cf30a0cf11af69dfb8', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.qcow2', '{SHA-512}b2e89ab21b6c11f183178da2cb1a04c89cb6b4c43e0968503d050710b4eb37426f78d71b4a503b282bca8c6ddd680a84f2ed9992752ef20d4288b7487d7c228b', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs-xen.vhd.bz2', '{SHA-512}b2687bf7f739f49ed36b266e69d44ed49283b398497d85bda17f0eabc447934ba571629c01d94d8ac985f8133b218c8558cac22a88f78a3efd5f81bcf0689ab7', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs-xen.vhd.bz2', '{SHA-512}ba1805e0a4f73725cc463215020dc77c9501b121c112f3c8205c4bce4e40fe4fc75381018a4863dd9dce0a6d2fe5c47480793283aa1df88c2a0217dd44c2117f', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}a090fafc53cbcf375937a8328500689543d9716a527f8a6059daee49712c56cc0e1553f386af8df1a650fa92819b235f4066df1076f242bf9a9e225f18a10b2b', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}2e8f81e9ea62820cdf936a73d3c02e1a840a6b96f18163b1f4617fd48ccae792e08ef2b3ccf0be1c7c3d86dff65200b655b54a7468775806ca78c1adf50c9935', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From da04f2152d3cbc0f3e07c9b3a0c1077f6e295ddf Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 22 Oct 2026 11:44:04 +0200 Subject: [PATCH 08/26] templates.sql: use the 20261021-2638 Debian 13 images for the built-in templates The images are built from the Debian 13 genericcloud image of 20261021-2638 with the nfs-common package installed. Update the URLs and the SHA-512 checksums of the KVM, XenServer and VMware templates. --- setup/db/templates.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/templates.sql b/setup/db/templates.sql index e2ba6cc7625e..8d7a613724e1 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.qcow2', '{SHA-512}b2e89ab21b6c11f183178da2cb1a04c89cb6b4c43e0968503d050710b4eb37426f78d71b4a503b282bca8c6ddd680a84f2ed9992752ef20d4288b7487d7c228b', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.qcow2', '{SHA-512}732c2b760089386ac8017ed64568c2441923e0107b9b6262586096d5fcf78393b83b561d289a7eb3f008187c17258d0be53a42651040168fc11468197619adcc', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs-xen.vhd.bz2', '{SHA-512}ba1805e0a4f73725cc463215020dc77c9501b121c112f3c8205c4bce4e40fe4fc75381018a4863dd9dce0a6d2fe5c47480793283aa1df88c2a0217dd44c2117f', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs-xen.vhd.bz2', '{SHA-512}454ad1a131f05bb50938ef1b4b28068181015c8afb492ad2e8f83d4cd2bf16cda55a142eae6f550ebb936a37a58b60bf7bd1e98ff965db338544db84f5fd85bd', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261017-2634-acs.ova', '{SHA-512}2e8f81e9ea62820cdf936a73d3c02e1a840a6b96f18163b1f4617fd48ccae792e08ef2b3ccf0be1c7c3d86dff65200b655b54a7468775806ca78c1adf50c9935', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.ova', '{SHA-512}d199ef7728c73b6f172429aacd7f5942737d8bde5742be8a8b0d01b9330621eb55c5a521251cbb44ef091023af02ece38d5b7120d81f6b7dbeac98f4e4ec94e0', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From d823180f5e42f7deb796a156eb6a140a59fee27d Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Fri, 23 Oct 2026 14:22:29 +0200 Subject: [PATCH 09/26] test_backup_recovery_nas: sync the guest after writing the marker files The tests write a file in the guest and take a live backup a few seconds later. The backup is not quiesced, so a file that was not flushed yet can be missing from the backup. The guest root filesystem commits its journal about every 5 seconds, which is also the wait before the backup. Run sync after each touch, so that the file is on the disk before the backup starts. --- .../smoke/test_backup_recovery_nas.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/integration/smoke/test_backup_recovery_nas.py b/test/integration/smoke/test_backup_recovery_nas.py index 31169e188739..9240432c7b90 100644 --- a/test/integration/smoke/test_backup_recovery_nas.py +++ b/test/integration/smoke/test_backup_recovery_nas.py @@ -122,7 +122,7 @@ def vm_backup_create_vm_from_backup_int(self, templateid=None, networkids=None): # Create a file and take backup try: ssh_client_vm = self.vm.get_ssh_client(reconnect=True) - ssh_client_vm.execute("touch test_backup_and_recovery.txt") + ssh_client_vm.execute("touch test_backup_and_recovery.txt; sync") except Exception as err: self.fail("SSH failed for Virtual machine: %s due to %s" % (self.vm.ipaddress, err)) @@ -314,7 +314,7 @@ def test_incremental_chain_cadence(self): self._set_full_every(3) try: ssh_client_vm = self.vm.get_ssh_client(reconnect=True) - ssh_client_vm.execute("touch /root/incremental_marker_1.txt") + ssh_client_vm.execute("touch /root/incremental_marker_1.txt; sync") created = [] for i in range(5): @@ -435,15 +435,15 @@ def test_restore_from_incremental(self): self._set_full_every(5) try: ssh_client_vm = self.vm.get_ssh_client(reconnect=True) - ssh_client_vm.execute("touch /root/marker_full.txt") + ssh_client_vm.execute("touch /root/marker_full.txt; sync") Backup.create(self.apiclient, self.vm.id, "rfi_full") time.sleep(3) - ssh_client_vm.execute("touch /root/marker_inc1.txt") + ssh_client_vm.execute("touch /root/marker_inc1.txt; sync") Backup.create(self.apiclient, self.vm.id, "rfi_inc1") time.sleep(3) - ssh_client_vm.execute("touch /root/marker_inc2.txt") + ssh_client_vm.execute("touch /root/marker_inc2.txt; sync") Backup.create(self.apiclient, self.vm.id, "rfi_inc2") time.sleep(3) @@ -484,13 +484,13 @@ def test_delete_middle_incremental_repairs_chain(self): self._set_full_every(5) try: ssh_client_vm = self.vm.get_ssh_client(reconnect=True) - ssh_client_vm.execute("touch /root/dmi_full.txt") + ssh_client_vm.execute("touch /root/dmi_full.txt; sync") Backup.create(self.apiclient, self.vm.id, "dmi_full") time.sleep(3) - ssh_client_vm.execute("touch /root/dmi_inc1.txt") + ssh_client_vm.execute("touch /root/dmi_inc1.txt; sync") Backup.create(self.apiclient, self.vm.id, "dmi_inc1") time.sleep(3) - ssh_client_vm.execute("touch /root/dmi_inc2.txt") + ssh_client_vm.execute("touch /root/dmi_inc2.txt; sync") Backup.create(self.apiclient, self.vm.id, "dmi_inc2") time.sleep(3) From 4d0d651e9b88326673ecbb0029673994962e17f5 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 09:09:47 +0100 Subject: [PATCH 10/26] build-acs-image.sh: add the Apache license header The usage text is printed from the header comment, so the line range it prints is moved down by the length of the license. --- scripts/util/build-acs-image.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh index 15ad42d03d5b..06ce920abcd5 100755 --- a/scripts/util/build-acs-image.sh +++ b/scripts/util/build-acs-image.sh @@ -1,4 +1,21 @@ #!/bin/bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Prepare a generic cloud qcow2 image for CloudStack: patch it, shrink it and convert it to VHD/OVA. # # Usage: build-acs-image.sh [-a ACTIONS] [-f FORMATS] [-o OUTDIR] [-g GUESTOS] [-u] [-z] @@ -31,7 +48,7 @@ # checked first, and nothing is installed. set -euo pipefail -usage() { sed -n '2,29p' "$0"; exit 1; } +usage() { sed -n '19,46p' "$0"; exit 1; } ACTIONS="all" FORMATS="all" From fccbcbb236029845e423f83dbb0206e21accb6bb Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 09:09:54 +0100 Subject: [PATCH 11/26] templates.sql: download the Debian 13 built-in templates from download.cloudstack.org The images are the same 20261021-2638 files with the same checksums, now served from download.cloudstack.org instead of the build server. --- setup/db/templates.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/templates.sql b/setup/db/templates.sql index 8d7a613724e1..8b60d9cf3607 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) no GUI (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.qcow2', '{SHA-512}732c2b760089386ac8017ed64568c2441923e0107b9b6262586096d5fcf78393b83b561d289a7eb3f008187c17258d0be53a42651040168fc11468197619adcc', 'Debian 13 (64-bit) no GUI (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) genericcloud (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.qcow2', '{SHA-512}732c2b760089386ac8017ed64568c2441923e0107b9b6262586096d5fcf78393b83b561d289a7eb3f008187c17258d0be53a42651040168fc11468197619adcc', 'Debian 13 (64-bit) genericcloud (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) no GUI (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs-xen.vhd.bz2', '{SHA-512}454ad1a131f05bb50938ef1b4b28068181015c8afb492ad2e8f83d4cd2bf16cda55a142eae6f550ebb936a37a58b60bf7bd1e98ff965db338544db84f5fd85bd', 0, 'Debian 13 (64-bit) no GUI (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) genericcloud (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs-xen.vhd.bz2', '{SHA-512}454ad1a131f05bb50938ef1b4b28068181015c8afb492ad2e8f83d4cd2bf16cda55a142eae6f550ebb936a37a58b60bf7bd1e98ff965db338544db84f5fd85bd', 0, 'Debian 13 (64-bit) genericcloud (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) no GUI (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.ova', '{SHA-512}d199ef7728c73b6f172429aacd7f5942737d8bde5742be8a8b0d01b9330621eb55c5a521251cbb44ef091023af02ece38d5b7120d81f6b7dbeac98f4e4ec94e0', 0, 'Debian 13 (64-bit) no GUI (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) genericcloud (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.ova', '{SHA-512}d199ef7728c73b6f172429aacd7f5942737d8bde5742be8a8b0d01b9330621eb55c5a521251cbb44ef091023af02ece38d5b7120d81f6b7dbeac98f4e4ec94e0', 0, 'Debian 13 (64-bit) genericcloud (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From a005111ef9cc37b781611daaa49a99b10e3cf7da Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 09:10:43 +0100 Subject: [PATCH 12/26] Keep the template install path when a download fails without one An error answer, like the timeout when the secondary storage VM disconnects during a download, has no install path. The download callback wrote it to the template_store_ref entry anyway, so the install path became null and the next download of the template went to a directory named "null". Only update the install path when the answer has one. --- .../image/BaseImageStoreDriverImpl.java | 7 +- .../image/BaseImageStoreDriverImplTest.java | 123 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 engine/storage/src/test/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImplTest.java diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index 26b39e30776f..d3c4750de07c 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -52,6 +52,7 @@ import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; import org.apache.cloudstack.storage.endpoint.DefaultEndPointSelector; import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -229,7 +230,11 @@ protected Void createTemplateAsyncCallback(AsyncCallbackDispatcher parentCallback; + @Mock + private AsyncCallbackDispatcher callback; + @Mock + private TemplateDataStoreVO existingRef; + @Mock + private VMTemplateZoneVO templateZone; + + private BaseImageStoreDriverImpl driver; + private TemplateDataStoreVO updateBuilder; + + @Before + public void setUp() { + driver = Mockito.mock(BaseImageStoreDriverImpl.class, Mockito.CALLS_REAL_METHODS); + ReflectionTestUtils.setField(driver, "logger", org.apache.logging.log4j.LogManager.getLogger(BaseImageStoreDriverImpl.class)); + ReflectionTestUtils.setField(driver, "_templateDao", templateDao); + ReflectionTestUtils.setField(driver, "_templateStoreDao", templateStoreDao); + ReflectionTestUtils.setField(driver, "_vmTemplateZoneDao", vmTemplateZoneDao); + ReflectionTestUtils.setField(driver, "_alertMgr", alertManager); + + Mockito.when(data.getId()).thenReturn(7L); + Mockito.when(data.getDataStore()).thenReturn(store); + Mockito.when(store.getId()).thenReturn(1L); + Mockito.when(templateDao.findById(7L)).thenReturn(Mockito.mock(VMTemplateVO.class)); + Mockito.when(templateDao.createForUpdate()).thenReturn(Mockito.mock(VMTemplateVO.class)); + Mockito.when(existingRef.getId()).thenReturn(3L); + Mockito.when(existingRef.getDownloadState()).thenReturn(Status.DOWNLOAD_IN_PROGRESS); + Mockito.when(templateStoreDao.findByStoreTemplate(1L, 7L)).thenReturn(existingRef); + updateBuilder = Mockito.mock(TemplateDataStoreVO.class); + Mockito.when(templateStoreDao.createForUpdate()).thenReturn(updateBuilder); + Mockito.when(templateZone.getZoneId()).thenReturn(2L); + Mockito.when(vmTemplateZoneDao.listByTemplateId(7L)).thenReturn(Collections.singletonList(templateZone)); + } + + private void callWith(DownloadAnswer answer) { + Mockito.when(callback.getResult()).thenReturn(answer); + BaseImageStoreDriverImpl.CreateContext context = driver.new CreateContext<>(parentCallback, data); + driver.createTemplateAsyncCallback(callback, context); + } + + @Test + public void testErrorAnswerWithoutInstallPathKeepsInstallPath() { + DownloadAnswer answer = new DownloadAnswer("Timeout waiting for response from storage host", Status.DOWNLOAD_ERROR); + + callWith(answer); + + Mockito.verify(updateBuilder, Mockito.never()).setInstallPath(Mockito.any()); + Mockito.verify(updateBuilder).setDownloadState(Status.DOWNLOAD_ERROR); + Mockito.verify(templateStoreDao).update(3L, updateBuilder); + } + + @Test + public void testAnswerWithInstallPathSetsInstallPath() { + DownloadAnswer answer = new DownloadAnswer("job", 100, null, Status.DOWNLOADED, "/dl/path", "template/tmpl/2/7", 10L, 10L, "checksum"); + + callWith(answer); + + Mockito.verify(updateBuilder).setInstallPath("template/tmpl/2/7"); + Mockito.verify(templateStoreDao).update(3L, updateBuilder); + } +} From 2a518d7eea994b751bf8efe0196fa72f8894d1d8 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 10:03:55 +0100 Subject: [PATCH 13/26] schema: deploy the built-in Debian 13 VMware template as is Set deploy_as_is on the built-in Debian 13 VMware template in the 4.23.0.0 to 24.0.0 upgrade, so that VMs take the NIC and disk controller of the OVA (vmxnet3, pvscsi). Without it CloudStack uses an e1000 NIC, which the Debian cloud kernel has no driver for, and the VM gets no network. --- .../src/main/resources/META-INF/db/schema-42300to2400.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 1ff71f4e0eb1..5e68bb8bb813 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -234,3 +234,6 @@ UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`gues WHERE `id` = 5 AND `unique_name` = 'debian13-x86_64-xen' AND `type` = 'BUILTIN'; UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`guest_os` WHERE `display_name` = 'Debian GNU/Linux 12 (64-bit)' AND `removed` IS NULL LIMIT 1) WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN'; + +-- Deploy the built-in Debian 13 VMware template as is, with the NIC and disk controller of the OVA (vmxnet3, pvscsi). +UPDATE `cloud`.`vm_template` SET `deploy_as_is` = 1 WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN'; From fc3a97edcdc63748b68efcae8612c7a693548c2c Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 17:38:52 +0100 Subject: [PATCH 14/26] build-acs-image.sh: do not force PasswordAuthentication in sshd The sshd drop-in only sets PermitRootLogin yes now, and it is named 40-root-login.conf. PasswordAuthentication is left to the default of the image instead of being forced to yes. --- scripts/util/build-acs-image.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh index 06ce920abcd5..e12d7891fb2d 100755 --- a/scripts/util/build-acs-image.sh +++ b/scripts/util/build-acs-image.sh @@ -376,11 +376,11 @@ datasource: None: {} EOF # sshd uses the first value it reads, so 40- loads before cloud-init's 50-cloud-init.conf - printf 'PermitRootLogin yes\nPasswordAuthentication yes\n' > "$tmp/40-password-login.conf" + printf 'PermitRootLogin yes\n' > "$tmp/40-root-login.conf" # the drop-in only works if sshd_config includes sshd_config.d (not in EL8, Ubuntu 20.04, ...); # otherwise put the settings at the top of sshd_config, as the first value wins - local sshd_fix='f=/etc/ssh/sshd_config; d=/etc/ssh/sshd_config.d/40-password-login.conf; + local sshd_fix='f=/etc/ssh/sshd_config; d=/etc/ssh/sshd_config.d/40-root-login.conf; if [ -f "$f" ] && ! grep -qiE "^[[:space:]]*Include[[:space:]].*sshd_config\.d" "$f"; then { cat "$d"; cat "$f"; } > "$f.new" && cat "$f.new" > "$f" && rm -f "$f.new" fi' @@ -392,13 +392,13 @@ fi' --upload "$tmp/ds-identify.cfg:/etc/cloud/ds-identify.cfg" \ --upload "$tmp/cloudstack.cfg:/etc/cloud/cloud.cfg.d/cloudstack.cfg" \ --mkdir /etc/ssh/sshd_config.d \ - --upload "$tmp/40-password-login.conf:/etc/ssh/sshd_config.d/40-password-login.conf" \ + --upload "$tmp/40-root-login.conf:/etc/ssh/sshd_config.d/40-root-login.conf" \ --run-command "$sshd_fix" \ --run-command "$CLEANUP" rm -rf "$tmp" echo "--- verification ---" - virt-cat -a "$WORK_IMAGE" /etc/cloud/ds-identify.cfg /etc/cloud/cloud.cfg.d/cloudstack.cfg /etc/ssh/sshd_config.d/40-password-login.conf + virt-cat -a "$WORK_IMAGE" /etc/cloud/ds-identify.cfg /etc/cloud/cloud.cfg.d/cloudstack.cfg /etc/ssh/sshd_config.d/40-root-login.conf local d for d in /usr/sbin /usr/bin; do virt-ls -a "$WORK_IMAGE" $d | grep -E '^(qemu-ga|vmtoolsd|xenstore)' | sed "s#^#$d/#" || true From ef01fce7e00c1b5142825c575d55b2425076683c Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 21:05:12 +0100 Subject: [PATCH 15/26] schema: set the NIC and disk controller of the built-in Debian 13 VMware template The guest kernel has no driver for the default e1000 NIC, so set the nicAdapter (Vmxnet3) and rootDiskController (pvscsi) details of the template instead of deploying it as is, which would prevent resizing the root disk. --- .../main/resources/META-INF/db/schema-42300to2400.sql | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 5e68bb8bb813..efbf480f2966 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -235,5 +235,12 @@ UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`gues UPDATE `cloud`.`vm_template` SET `guest_os_id` = (SELECT `id` FROM `cloud`.`guest_os` WHERE `display_name` = 'Debian GNU/Linux 12 (64-bit)' AND `removed` IS NULL LIMIT 1) WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN'; --- Deploy the built-in Debian 13 VMware template as is, with the NIC and disk controller of the OVA (vmxnet3, pvscsi). -UPDATE `cloud`.`vm_template` SET `deploy_as_is` = 1 WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN'; +-- Use the NIC and disk controller of the built-in Debian 13 VMware template image (vmxnet3, pvscsi), as the guest kernel has no driver for the default e1000 NIC +INSERT INTO `cloud`.`vm_template_details` (`template_id`, `name`, `value`) + SELECT `id`, 'nicAdapter', 'Vmxnet3' FROM `cloud`.`vm_template` + WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN' + AND NOT EXISTS (SELECT 1 FROM `cloud`.`vm_template_details` WHERE `template_id` = 7 AND `name` = 'nicAdapter'); +INSERT INTO `cloud`.`vm_template_details` (`template_id`, `name`, `value`) + SELECT `id`, 'rootDiskController', 'pvscsi' FROM `cloud`.`vm_template` + WHERE `id` = 7 AND `unique_name` = 'debian13-x86_64-vmware' AND `type` = 'BUILTIN' + AND NOT EXISTS (SELECT 1 FROM `cloud`.`vm_template_details` WHERE `template_id` = 7 AND `name` = 'rootDiskController'); From 7050148bdb8f8b4aac127cc03536443ac5e26936 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 26 Oct 2026 21:05:54 +0100 Subject: [PATCH 16/26] marvin: allow ssh login with password on the built-in template, use the tiny template on VMware VirtualMachine.create adds a cloud-init userdata to the VMs deployed from a built-in template, to allow ssh login with the root password, as the template disables it, so the tests can reach the VMs on any template. get_suitable_test_template returns the tiny template on VMware too, and test_update_security_group uses it. --- .../smoke/test_update_security_group.py | 4 ++-- tools/marvin/marvin/lib/base.py | 19 +++++++++++++++++++ tools/marvin/marvin/lib/common.py | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/test/integration/smoke/test_update_security_group.py b/test/integration/smoke/test_update_security_group.py index 47fb01ba8115..eb9202f564ee 100644 --- a/test/integration/smoke/test_update_security_group.py +++ b/test/integration/smoke/test_update_security_group.py @@ -41,7 +41,7 @@ SecurityGroup) from marvin.lib.common import (get_domain, get_zone, - get_template, + get_suitable_test_template, list_virtual_machines, list_routers, list_hosts, @@ -63,7 +63,7 @@ def setUpClass(cls): zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.zone = Zone(zone.__dict__) - cls.template = get_template(cls.apiclient, cls.zone.id) + cls.template = get_suitable_test_template(cls.apiclient, cls.zone.id) cls._cleanup = [] if str(cls.zone.securitygroupsenabled) != "True": diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index b95a710db989..f57d746c6370 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -369,6 +369,13 @@ def login(cls, apiclient, username, password, domain=None, domainid=None): return apiclient.login(cmd) +# cloud-init userdata to allow ssh login with password (root/password) on the built-in templates +BUILTIN_TEMPLATE_USERDATA = """#cloud-config +ssh_pwauth: true +disable_root: false +""" + + class VirtualMachine: """Manage virtual machine lifecycle""" @@ -555,6 +562,15 @@ def program_ssh_access( raise Exception( "The following exception appeared while programming ssh access - %s" % e) + @classmethod + def is_builtin_template(cls, apiclient, templateid): + """Check if the template is a built-in template""" + cmd = listTemplates.listTemplatesCmd() + cmd.id = templateid + cmd.templatefilter = "all" + templates = apiclient.listTemplates(cmd) + return bool(templates and templates[0].templatetype == "BUILTIN") + @classmethod def create(cls, apiclient, services, templateid=None, accountid=None, domainid=None, zoneid=None, networkids=None, @@ -664,6 +680,9 @@ def create(cls, apiclient, services, templateid=None, accountid=None, if "userdata" in services: cmd.userdata = base64.urlsafe_b64encode(services["userdata"].encode()).decode() + elif userdataid is None and getattr(cmd, "templateid", None) and cls.is_builtin_template(apiclient, cmd.templateid): + # the built-in templates do not allow ssh login with password by default + cmd.userdata = base64.urlsafe_b64encode(BUILTIN_TEMPLATE_USERDATA.encode()).decode() if userdataid is not None: cmd.userdataid = userdataid diff --git a/tools/marvin/marvin/lib/common.py b/tools/marvin/marvin/lib/common.py index 5e574b742c1b..26a4b6af7902 100644 --- a/tools/marvin/marvin/lib/common.py +++ b/tools/marvin/marvin/lib/common.py @@ -521,7 +521,7 @@ def get_suitable_test_template(apiclient, zoneid=None, ostypeid=None, hypervisor ''' @Name : get_suitable_test_template @Desc : Retrieves the test template information based upon inputs provided - For XenServer and KVM, get_test_template is used for retrieving the + For XenServer, KVM and VMware, get_test_template is used for retrieving the shared public test template (tiny), which is registered by the admin only when it is missing. get_template is used for other hypervisors or when get_test_template fails @@ -533,7 +533,7 @@ def get_suitable_test_template(apiclient, zoneid=None, ostypeid=None, hypervisor template = FAILED if hypervisor is None: hypervisor = get_hypervisor_type(apiclient) - if hypervisor.lower() in ["xenserver", "kvm"] or (hypervisor.lower() in ["vmware"] and deploy_as_is): + if hypervisor.lower() in ["xenserver", "kvm", "vmware"]: template = get_test_template( apiclient, zoneid, From 075180d7fbd23d1eeff153c08b6958a6ecdc3514 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 27 Oct 2026 12:58:59 +0100 Subject: [PATCH 17/26] templates.sql: use the rebuilt Debian 13 built-in templates (20261026-2643) Download the images from 10.0.34.8 and update the checksums. --- setup/db/templates.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/templates.sql b/setup/db/templates.sql index 8b60d9cf3607..e93f132ef98d 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) genericcloud (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.qcow2', '{SHA-512}732c2b760089386ac8017ed64568c2441923e0107b9b6262586096d5fcf78393b83b561d289a7eb3f008187c17258d0be53a42651040168fc11468197619adcc', 'Debian 13 (64-bit) genericcloud (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) genericcloud (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.qcow2', '{SHA-512}d9c8768e89fa6861e273932095b4f0199a8c9285af47a5428547b004ba28ce80ed9ae1ca4439bb4a3dc2634672fe06c0ccc7fa7e8d3113efc1762f58d8c9a870', 'Debian 13 (64-bit) genericcloud (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) genericcloud (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs-xen.vhd.bz2', '{SHA-512}454ad1a131f05bb50938ef1b4b28068181015c8afb492ad2e8f83d4cd2bf16cda55a142eae6f550ebb936a37a58b60bf7bd1e98ff965db338544db84f5fd85bd', 0, 'Debian 13 (64-bit) genericcloud (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) genericcloud (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs-xen.vhd.bz2', '{SHA-512}a4eed88cbcd9c98656179d2bf58077a35d92ab966c2aabde4f7e2bf6aabced051b3b0e98958f68d7393750d3b3a69d8b6abe8d97f324ae07fd21664e7dbb514e', 0, 'Debian 13 (64-bit) genericcloud (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) genericcloud (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261021-2638-acs.ova', '{SHA-512}d199ef7728c73b6f172429aacd7f5942737d8bde5742be8a8b0d01b9330621eb55c5a521251cbb44ef091023af02ece38d5b7120d81f6b7dbeac98f4e4ec94e0', 0, 'Debian 13 (64-bit) genericcloud (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) genericcloud (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.ova', '{SHA-512}0529980319d1d58037fc06bb82abf097003e2c2c332205be57755bf4c8152e86a3e178394c0ecc9b620b0f7679987881bc862230c56eaea5de0e58a2f4ad6f83', 0, 'Debian 13 (64-bit) genericcloud (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian'); From d6d7eb0cc5c8c1168be501a7285c99d802924f5b Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 27 Oct 2026 20:54:00 +0100 Subject: [PATCH 18/26] systemvm: set the hostname without dbus if hostnamectl fails init.sh runs before dbus is started on a slow boot, e.g. on XenServer, so hostnamectl fails and the hostname stays 'systemvm'. The agent then registers the system VM with that name and its agent state is not shown. --- systemvm/debian/opt/cloud/bin/setup/common.sh | 2 +- systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/systemvm/debian/opt/cloud/bin/setup/common.sh b/systemvm/debian/opt/cloud/bin/setup/common.sh index ef1576ab588c..e573342fcb8b 100755 --- a/systemvm/debian/opt/cloud/bin/setup/common.sh +++ b/systemvm/debian/opt/cloud/bin/setup/common.sh @@ -338,7 +338,7 @@ setup_common() { echo $NAME > /etc/hostname echo 'AVAHI_DAEMON_DETECT_LOCAL=0' > /etc/default/avahi-daemon - hostnamectl set-hostname $NAME + hostnamectl set-hostname $NAME || hostname $NAME #Nameserver sed -i -e "/^nameserver.*$/d" /etc/resolv.conf # remove previous entries diff --git a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh index 767f87848dd3..cae51742a347 100755 --- a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh +++ b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh @@ -27,7 +27,7 @@ setup_vpcrouter() { echo $NAME > /etc/hostname echo 'AVAHI_DAEMON_DETECT_LOCAL=0' > /etc/default/avahi-daemon - hostnamectl set-hostname $NAME + hostnamectl set-hostname $NAME || hostname $NAME #Nameserver sed -i -e "/^nameserver.*$/d" /etc/resolv.conf # remove previous entries From 599eb01531e0f1abcb463eb3d603a9d0c9e6f599 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Tue, 27 Oct 2026 21:04:55 +0100 Subject: [PATCH 19/26] marvin: do not fail to deploy a VM from an ISO or as a normal user The check for a built-in template listed the template with the filter 'all', which fails for an ISO (incorrect format) and for a non-admin user. Use the filter 'executable' and consider the template as not built-in on any error. --- tools/marvin/marvin/lib/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index f57d746c6370..02b3d3e29bf9 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -567,8 +567,12 @@ def is_builtin_template(cls, apiclient, templateid): """Check if the template is a built-in template""" cmd = listTemplates.listTemplatesCmd() cmd.id = templateid - cmd.templatefilter = "all" - templates = apiclient.listTemplates(cmd) + cmd.templatefilter = "executable" + try: + templates = apiclient.listTemplates(cmd) + except Exception: + # not a template (an ISO), or not visible to the user + return False return bool(templates and templates[0].templatetype == "BUILTIN") @classmethod From c4e58cd2b79dbcfded0e52b17fd2e7711681748b Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 08:10:02 +0100 Subject: [PATCH 20/26] build-acs-image.sh: install xe-guest-utilities in the Debian and EL images The XenServer/XCP-ng guest agent is needed to attach volumes to a running VM (PV drivers are not detected without it). The packages are downloaded from the GitHub release on the build host. On Debian the deb provides the xenstore tools, so xenstore-utils and libxenstore4 are no longer installed. --- scripts/util/build-acs-image.sh | 42 +++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh index e12d7891fb2d..41cf8edd87fe 100755 --- a/scripts/util/build-acs-image.sh +++ b/scripts/util/build-acs-image.sh @@ -21,7 +21,8 @@ # Usage: build-acs-image.sh [-a ACTIONS] [-f FORMATS] [-o OUTDIR] [-g GUESTOS] [-u] [-z] # [-d debian|rhel|suse] [-p ROOT_PASSWORD] # -a actions, a list separated by commas or spaces, done in this order (default: all) -# patch install the guest tools, set the root password, cloud-init and sshd config +# patch install the guest tools (including xe-guest-utilities for XenServer on Debian/Ubuntu +# and EL), set the root password, cloud-init and sshd config # (OS detected from the image: Debian/Ubuntu, EL, SUSE and variants) # shrink virt-sparsify and qemu-img convert -c # convert convert to the formats of -f @@ -147,6 +148,7 @@ check_dependencies() { for cmd in virt-customize virt-cat virt-ls virt-inspector; do command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd not found ($(pkg_hint libguestfs))") done + command -v curl >/dev/null 2>&1 || missing+=("curl not found, needed to download xe-guest-utilities ($(pkg_hint curl))") fi if [ -n "$DO_SHRINK" ]; then command -v virt-sparsify >/dev/null 2>&1 || missing+=("virt-sparsify not found ($(pkg_hint libguestfs))") @@ -347,15 +349,25 @@ detect_os() { fi echo "Detected OS: ${OS_NAME:-$OS_ID} (family: $OS_FAMILY)" + XE_GUEST_UTILITIES_VERSION="10.0.0-1" # xe-guest-utilities (the XenServer/XCP-ng guest agent) + XE_PACKAGES=() XE_INSTALL="" + local base_cleanup='journalctl --vacuum-time=1s; rm -rf /var/log/*; rm -f /root/.bash_history' case "$OS_FAMILY" in debian) - # nfs-common is the NFS client, the cloud images do not have it and the tests mount NFS in the guest - PACKAGES="qemu-guest-agent,open-vm-tools,xenstore-utils,libxenstore4,nfs-common" + # nfs-common is the NFS client, the cloud images do not have it and the tests mount NFS in the guest; + # the xenstore tools come with xe-guest-utilities (xenstore-utils would conflict with its files) + PACKAGES="qemu-guest-agent,open-vm-tools,nfs-common" + XE_PACKAGES=("xe-guest-utilities_${XE_GUEST_UTILITIES_VERSION}_amd64.deb") + # no service is started in the image build, and the init script is enabled by the package + XE_INSTALL='printf "#!/bin/sh\nexit 101\n" > /usr/sbin/policy-rc.d; chmod +x /usr/sbin/policy-rc.d; dpkg -i /tmp/xe-pkgs/*.deb; rc=$?; rm -rf /usr/sbin/policy-rc.d /tmp/xe-pkgs; exit $rc' CLEANUP="apt clean; apt autoclean; apt autoremove --purge -y; $base_cleanup" ;; rhel) - # there is no xenstore package in the EL repositories + # there is no xenstore package in the EL repositories, xe-guest-utilities has the xenstore tools PACKAGES="qemu-guest-agent,open-vm-tools" + # the xenstore tools are a separate package here, as in the deb + XE_PACKAGES=("xe-guest-utilities-${XE_GUEST_UTILITIES_VERSION}.x86_64.rpm" "xe-guest-utilities-xenstore-${XE_GUEST_UTILITIES_VERSION}.x86_64.rpm") + XE_INSTALL='rpm -Uvh /tmp/xe-pkgs/*.rpm; rc=$?; rm -rf /tmp/xe-pkgs; exit $rc' CLEANUP="(dnf clean all || yum clean all); rm -rf /var/cache/dnf /var/cache/yum; $base_cleanup" ;; suse) PACKAGES="qemu-guest-agent,open-vm-tools,xen-tools-domU" @@ -385,9 +397,29 @@ if [ -f "$f" ] && ! grep -qiE "^[[:space:]]*Include[[:space:]].*sshd_config\.d" { cat "$d"; cat "$f"; } > "$f.new" && cat "$f.new" > "$f" && rm -f "$f.new" fi' + # xe-guest-utilities is for x86_64 guests only, it is downloaded here as the guest may have no network + local xe_args=() + if [ ${#XE_PACKAGES[@]} -gt 0 ]; then + local guest_arch + guest_arch=$(virt-inspector -a "$IMAGE" 2>/dev/null | sed -n 's#.*\(.*\).*#\1#p' | head -1) + if [ "$guest_arch" = "x86_64" ]; then + xe_args=(--mkdir /tmp/xe-pkgs) + local pkg + for pkg in "${XE_PACKAGES[@]}"; do + echo "Downloading $pkg" + curl -fsSL -o "$tmp/$pkg" "https://github.com/xenserver/xe-guest-utilities/releases/download/v10.0.0/$pkg" + xe_args+=(--upload "$tmp/$pkg:/tmp/xe-pkgs/$pkg") + done + xe_args+=(--run-command "$XE_INSTALL") + else + echo "WARN: the image is '$guest_arch', xe-guest-utilities is not installed" >&2 + fi + fi + virt-customize -a "$WORK_IMAGE" \ $UPDATE \ --install "$PACKAGES" \ + "${xe_args[@]}" \ --root-password "password:$ROOT_PASSWORD" \ --upload "$tmp/ds-identify.cfg:/etc/cloud/ds-identify.cfg" \ --upload "$tmp/cloudstack.cfg:/etc/cloud/cloud.cfg.d/cloudstack.cfg" \ @@ -401,7 +433,7 @@ fi' virt-cat -a "$WORK_IMAGE" /etc/cloud/ds-identify.cfg /etc/cloud/cloud.cfg.d/cloudstack.cfg /etc/ssh/sshd_config.d/40-root-login.conf local d for d in /usr/sbin /usr/bin; do - virt-ls -a "$WORK_IMAGE" $d | grep -E '^(qemu-ga|vmtoolsd|xenstore)' | sed "s#^#$d/#" || true + virt-ls -a "$WORK_IMAGE" $d | grep -E '^(qemu-ga|vmtoolsd|xenstore|xe-daemon)' | sed "s#^#$d/#" || true done } From 29f68cb00c7309a3105dc3ab8601d63197d6de98 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 13:15:56 +0100 Subject: [PATCH 21/26] build-acs-image.sh: run set-passwords and ssh on every boot cloud-init applies set-passwords and ssh (the public keys of the instance) once per instance, so the password and the SSH key pair reset in CloudStack are not applied when the VM is started again. Set both modules to always in cloud.cfg. The ssh module deletes and regenerates the host keys whenever it runs, so set ssh_deletekeys to false to keep them across the boots. --- scripts/util/build-acs-image.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/util/build-acs-image.sh b/scripts/util/build-acs-image.sh index 41cf8edd87fe..c44aa7172c18 100755 --- a/scripts/util/build-acs-image.sh +++ b/scripts/util/build-acs-image.sh @@ -386,6 +386,7 @@ datasource_list: [ ConfigDrive, CloudStack, None ] datasource: CloudStack: {} None: {} +ssh_deletekeys: false EOF # sshd uses the first value it reads, so 40- loads before cloud-init's 50-cloud-init.conf printf 'PermitRootLogin yes\n' > "$tmp/40-root-login.conf" @@ -397,6 +398,11 @@ if [ -f "$f" ] && ! grep -qiE "^[[:space:]]*Include[[:space:]].*sshd_config\.d" { cat "$d"; cat "$f"; } > "$f.new" && cat "$f.new" > "$f" && rm -f "$f.new" fi' + # run set-passwords and ssh (the public keys) on every boot instead of once per instance, so that the + # password and the SSH key pair reset in CloudStack are applied at the next start; the host keys are + # kept (ssh_deletekeys: false above), as the ssh module would otherwise replace them on every boot + local cloud_cfg_fix='sed -i -E "s/^([[:space:]]*)- (set[-_]passwords|ssh)[[:space:]]*$/\1- [\2, always]/" /etc/cloud/cloud.cfg' + # xe-guest-utilities is for x86_64 guests only, it is downloaded here as the guest may have no network local xe_args=() if [ ${#XE_PACKAGES[@]} -gt 0 ]; then @@ -426,11 +432,13 @@ fi' --mkdir /etc/ssh/sshd_config.d \ --upload "$tmp/40-root-login.conf:/etc/ssh/sshd_config.d/40-root-login.conf" \ --run-command "$sshd_fix" \ + --run-command "$cloud_cfg_fix" \ --run-command "$CLEANUP" rm -rf "$tmp" echo "--- verification ---" virt-cat -a "$WORK_IMAGE" /etc/cloud/ds-identify.cfg /etc/cloud/cloud.cfg.d/cloudstack.cfg /etc/ssh/sshd_config.d/40-root-login.conf + virt-cat -a "$WORK_IMAGE" /etc/cloud/cloud.cfg | grep -E 'set[-_]passwords|- (\[)?ssh\b' || true local d for d in /usr/sbin /usr/bin; do virt-ls -a "$WORK_IMAGE" $d | grep -E '^(qemu-ga|vmtoolsd|xenstore|xe-daemon)' | sed "s#^#$d/#" || true From f1e79c411946b881de9d89403ae24d23988ca66d Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 13:17:07 +0100 Subject: [PATCH 22/26] marvin: retry attach and detach volume until the XenServer PV drivers are detected XenServer and XCP-ng refuse to hot-attach or hot-detach a volume until the guest tools report in, which is about 45 seconds after the start of a Debian 13 guest. The tests attach a volume right after the VM is Running or restarted, so retry for up to 3 minutes when the error is about the PV drivers. --- tools/marvin/marvin/lib/base.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 02b3d3e29bf9..3be8c9012b46 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -979,13 +979,26 @@ def attach_volume(self, apiclient, volume, deviceid=None): if deviceid is not None: cmd.deviceid = deviceid - return apiclient.attachVolume(cmd) + return self._retry_until_pv_drivers(apiclient.attachVolume, cmd) + + @staticmethod + def _retry_until_pv_drivers(api, cmd, timeout=180): + """XenServer/XCP-ng refuses to hot-attach or hot-detach a volume until the guest tools report in, + which is some time after the VM is Running or restarted (about 45 seconds for a Debian 13 guest)""" + while True: + try: + return api(cmd) + except Exception as e: + if "PV drivers" not in str(e) or timeout <= 0: + raise + time.sleep(10) + timeout -= 10 def detach_volume(self, apiclient, volume): """Detach volume to instance""" cmd = detachVolume.detachVolumeCmd() cmd.id = volume.id - return apiclient.detachVolume(cmd) + return self._retry_until_pv_drivers(apiclient.detachVolume, cmd) def add_nic(self, apiclient, networkId, ipaddress=None, macaddress=None, dhcpoptions=None): """Add a NIC to a VM""" From d9198be176e9475879b7bb4ab917c99885d45f69 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 13:17:07 +0100 Subject: [PATCH 23/26] test_primary_storage: use 512 MiB service offerings The Debian 13 built-in template does not boot with the 128 MiB of the tiny offering, so the volume cannot be attached to the VM. --- test/integration/smoke/test_primary_storage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/integration/smoke/test_primary_storage.py b/test/integration/smoke/test_primary_storage.py index 569335e7e77e..9dc5c37f9ab6 100644 --- a/test/integration/smoke/test_primary_storage.py +++ b/test/integration/smoke/test_primary_storage.py @@ -315,9 +315,10 @@ def test_01_add_primary_storage_disabled_host(self): domainid=self.domain.id ) + # the Debian 13 built-in template does not boot with the 128 MiB of the tiny offering service_offering = ServiceOffering.create( self.apiclient, - self.services["service_offerings"]["tiny"] + dict(self.services["service_offerings"]["tiny"], memory=512) ) self.cleanup.append(service_offering) @@ -423,15 +424,16 @@ def setUpClass(cls): assert storage_response.id == cls.storage_pool_1.id and storage_response.type == cls.storage_pool_1.type # Create Service Offerings with different Storage Tags + # (the Debian 13 built-in template does not boot with the 128 MiB of the tiny offering) cls.service_offering_1 = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["tiny"], + dict(cls.services["service_offerings"]["tiny"], memory=512), tags=cls.services["storage_tags"]["a"] ) cls._cleanup.append(cls.service_offering_1) cls.service_offering_2 = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["tiny"], + dict(cls.services["service_offerings"]["tiny"], memory=512), tags=cls.services["storage_tags"]["b"] ) cls._cleanup.append(cls.service_offering_2) From 6c3e8c82f0c3c23c46d82c4a9e68185a74eeaacf Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 08:24:35 +0100 Subject: [PATCH 24/26] test_scale_vm: use a 4 GiB disk offering --- test/integration/smoke/test_scale_vm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/smoke/test_scale_vm.py b/test/integration/smoke/test_scale_vm.py index 73aeedac36fa..f0593376f121 100644 --- a/test/integration/smoke/test_scale_vm.py +++ b/test/integration/smoke/test_scale_vm.py @@ -159,7 +159,7 @@ def setUp(self): self.apiclient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() self.cleanup = [] - self.services["disk_offering"]["disksize"] = 2 + self.services["disk_offering"]["disksize"] = 4 if self.unsupportedHypervisor: self.skipTest("Skipping test because unsupported hypervisor\ From f2490027b12a131cb4f650fe2618ab9333d21403 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 13:17:08 +0100 Subject: [PATCH 25/26] test_scale_vm: fix the tests on the Debian 13 built-in template Use 512 MiB and 1024 MiB service offerings, as the guest does not have enough memory with 256 MiB when the dynamic scaling is enabled. Check the open-vm-tools service too, as the guest does not have the vmware-tools service, so all the tests were skipped on VMware. --- test/integration/smoke/test_scale_vm.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/test/integration/smoke/test_scale_vm.py b/test/integration/smoke/test_scale_vm.py index f0593376f121..6786733a1e79 100644 --- a/test/integration/smoke/test_scale_vm.py +++ b/test/integration/smoke/test_scale_vm.py @@ -102,15 +102,20 @@ def setUpClass(cls): ) cls._cleanup.append(cls.account) + # the Debian 13 built-in template needs more memory than the 256 MiB of the default small offering + # (with dynamic scaling, the memory hot-add leaves too little for the guest) + small_offering_data = dict(cls.services["service_offerings"]["small"], memory=512) + big_offering_data = dict(cls.services["service_offerings"]["big"], memory=1024) + cls.small_offering = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["small"] + small_offering_data ) cls._cleanup.append(cls.small_offering) cls.big_offering = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["big"] + big_offering_data ) cls._cleanup.append(cls.big_offering) @@ -122,13 +127,13 @@ def setUpClass(cls): cls.small_offering_dynamic_scaling_disabled = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["small"], + small_offering_data, dynamicscalingenabled=False ) cls.big_offering_dynamic_scaling_disabled = ServiceOffering.create( cls.apiclient, - cls.services["service_offerings"]["small"], + small_offering_data, dynamicscalingenabled=False ) @@ -219,7 +224,7 @@ def test_01_scale_vm(self): if self.hypervisor.lower() == "vmware": sshClient = self.virtual_machine.get_ssh_client() result = str( - sshClient.execute("service vmware-tools status")).lower() + sshClient.execute("service vmware-tools status || systemctl status open-vm-tools")).lower() self.debug("and result is: %s" % result) if "running" not in result: self.skipTest("Skipping scale VM operation because\ @@ -353,7 +358,7 @@ def test_02_scale_vm_negative_offering_disable_scaling(self): if self.hypervisor.lower() == "vmware": sshClient = self.virtual_machine_with_service_offering_dynamic_scaling_disabled.get_ssh_client() result = str( - sshClient.execute("service vmware-tools status")).lower() + sshClient.execute("service vmware-tools status || systemctl status open-vm-tools")).lower() self.debug("and result is: %s" % result) if "running" not in result: self.skipTest("Skipping scale VM operation because\ @@ -462,7 +467,7 @@ def test_03_scale_vm_negative_vm_disable_scaling(self): if self.hypervisor.lower() == "vmware": sshClient = self.virtual_machine_not_dynamically_scalable.get_ssh_client() result = str( - sshClient.execute("service vmware-tools status")).lower() + sshClient.execute("service vmware-tools status || systemctl status open-vm-tools")).lower() self.debug("and result is: %s" % result) if "running" not in result: self.skipTest("Skipping scale VM operation because\ @@ -553,7 +558,7 @@ def test_04_scale_vm_with_user_account(self): if self.hypervisor.lower() == "vmware": sshClient = self.virtual_machine_in_user_account.get_ssh_client() result = str( - sshClient.execute("service vmware-tools status")).lower() + sshClient.execute("service vmware-tools status || systemctl status open-vm-tools")).lower() self.debug("and result is: %s" % result) if "running" not in result: self.skipTest("Skipping scale VM operation because\ @@ -737,7 +742,7 @@ def test_05_scale_vm_dont_allow_disk_offering_change(self): if self.hypervisor.lower() == "vmware": sshClient = self.virtual_machine_test.get_ssh_client() result = str( - sshClient.execute("service vmware-tools status")).lower() + sshClient.execute("service vmware-tools status || systemctl status open-vm-tools")).lower() self.debug("and result is: %s" % result) if "running" not in result: self.skipTest("Skipping scale VM operation because\ From c46250d4fd78ab6be6be9a6926d55a07e55892b6 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 29 Oct 2026 14:05:42 +0100 Subject: [PATCH 26/26] templates.sql: update the checksums of the Debian 13 built-in templates The images are rebuilt with the changes of build-acs-image.sh: set-passwords and ssh run on every boot, and xe-guest-utilities is installed. --- setup/db/templates.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/templates.sql b/setup/db/templates.sql index e93f132ef98d..5e98fede0ce3 100755 --- a/setup/db/templates.sql +++ b/setup/db/templates.sql @@ -34,16 +34,16 @@ INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, VALUES (2, UUID(), 'centos53-x86_64', 'CentOS 5.3(64-bit) no GUI (XenServer)', 1, now(), now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/templates/builtin/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2', 'b63d854a9560c013142567bbae8d98cf', 0, 'CentOS 5.3(64-bit) no GUI (XenServer)', 'VHD', 12, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, display_text, enable_password, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) genericcloud (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.qcow2', '{SHA-512}d9c8768e89fa6861e273932095b4f0199a8c9285af47a5428547b004ba28ce80ed9ae1ca4439bb4a3dc2634672fe06c0ccc7fa7e8d3113efc1762f58d8c9a870', 'Debian 13 (64-bit) genericcloud (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); + VALUES (4, UUID(), 'debian13-x86_64-kvm', 'Debian 13 (64-bit) genericcloud (KVM)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.qcow2', '{SHA-512}bd95d28cc1e42e2f9d54c16c1015fee0204677dc6122c9a10bec8aaf0b3730b426f47e1c50e046f21add95f917344fe07089d791eb996fbd996257879022aafe', 'Debian 13 (64-bit) genericcloud (KVM)', 0, 'QCOW2', 184, 1, 1, 'KVM', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) genericcloud (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs-xen.vhd.bz2', '{SHA-512}a4eed88cbcd9c98656179d2bf58077a35d92ab966c2aabde4f7e2bf6aabced051b3b0e98958f68d7393750d3b3a69d8b6abe8d97f324ae07fd21664e7dbb514e', 0, 'Debian 13 (64-bit) genericcloud (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); + VALUES (5, UUID(), 'debian13-x86_64-xen', 'Debian 13 (64-bit) genericcloud (XenServer)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs-xen.vhd.bz2', '{SHA-512}1126134bdec1db47a34a0ca320b2f7d51e3d2fd2f7c3194ed855e592a3c92a7327edeafe8e8f05a1f2faac8a586d76d38c4003a669a7f28bda891c54261ad94c', 0, 'Debian 13 (64-bit) genericcloud (XenServer)', 'VHD', 184, 1, 1, 'XenServer', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) VALUES (6, UUID(), 'centos64-x64', 'CentOS 6.4(64-bit) GUI (Hyperv)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://download.cloudstack.org/releases/4.3/centos6_4_64bit.vhd.bz2', 'eef6b9940ea3ed01221d963d4a012d0a', 0, 'CentOS 6.4 (64-bit) GUI (Hyperv)', 'VHD', 182, 1, 1, 'Hyperv', 1); INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type, extractable) - VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) genericcloud (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.ova', '{SHA-512}0529980319d1d58037fc06bb82abf097003e2c2c332205be57755bf4c8152e86a3e178394c0ecc9b620b0f7679987881bc862230c56eaea5de0e58a2f4ad6f83', 0, 'Debian 13 (64-bit) genericcloud (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); + VALUES (7, UUID(), 'debian13-x86_64-vmware', 'Debian 13 (64-bit) genericcloud (vSphere)', 1, now(), 'BUILTIN', 0, 64, 1, 'http://10.0.34.8/templates/debian-13-genericcloud-amd64/debian-13-genericcloud-amd64-20261026-2643-acs.ova', '{SHA-512}01500faa467c718561624ae697d1866476792c265714d2a742ae0ffba39108c8f245897d3eba6623ef3cecbf410fae490ee47f41b4b1a237eb586f4a79e5c165', 0, 'Debian 13 (64-bit) genericcloud (vSphere)', 'OVA', 184, 1, 1, 'VMware', 1); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (1, UUID(), 'CentOS'); INSERT INTO `cloud`.`guest_os_category` (id, uuid, name) VALUES (2, UUID(), 'Debian');