From 3308b7cf88a4283d3f9b57fe23b81c71a0c6ebe5 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Mon, 19 Oct 2026 18:41:48 +0200 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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)