mirror of https://github.com/nmasse-itix/zvirt.git
Compare commits
4 Commits
c972dd2d62
...
320c00179e
| Author | SHA1 | Date |
|---|---|---|
|
|
320c00179e | 2 weeks ago |
|
|
9cfb5b3643 | 2 weeks ago |
|
|
da8f3b359b | 2 weeks ago |
|
|
b84d07efec | 2 weeks ago |
16 changed files with 280 additions and 2555 deletions
@ -1,9 +0,0 @@ |
|||
[submodule "bats-support"] |
|||
path = test/test_helper/bats-support |
|||
url = https://github.com/bats-core/bats-support.git |
|||
[submodule "bats-assert"] |
|||
path = test/test_helper/bats-assert |
|||
url = https://github.com/bats-core/bats-assert.git |
|||
[submodule "bats-mock"] |
|||
path = test/test_helper/bats-mock |
|||
url = https://github.com/grayhemp/bats-mock.git |
|||
@ -0,0 +1,157 @@ |
|||
#!/bin/bash |
|||
|
|||
set -Eeuo pipefail |
|||
|
|||
hook="" |
|||
verbose=0 |
|||
live=0 |
|||
domain="" |
|||
domain_dir="" |
|||
|
|||
function show_help () { |
|||
cat << EOF |
|||
pre/post hook for zfs-autobackup to manage snapshots of KVM/QEMU virtual machines using virsh. |
|||
|
|||
Usage: ${0##*/} -k {pre|post} [-h] [-l] [-v] domain |
|||
|
|||
Options: |
|||
-h display this help and exit |
|||
-v verbose mode |
|||
-l live snapshot mode (default is crash-consistent) |
|||
-r dir specify the root directory of the domain's storage |
|||
-k {pre|post} specify the hook type (pre or post) |
|||
|
|||
Examples: |
|||
pre-hook for a crash-consistent snapshot of domain 'vm1': |
|||
${0##*/} -k pre vm1 |
|||
|
|||
post-hook for a crash-consistent snapshot of domain 'vm1': |
|||
${0##*/} -k post vm1 |
|||
|
|||
pre-hook for a live snapshot of domain 'vm1': |
|||
${0##*/} -k pre -l -r /var/lib/libvirt/images/vm1 vm1 |
|||
|
|||
post-hook for a live snapshot of domain 'vm1': |
|||
${0##*/} -k post -l -r /var/lib/libvirt/images/vm1 vm1 |
|||
EOF |
|||
} |
|||
|
|||
function run () { |
|||
if [ "$verbose" -eq 1 ]; then |
|||
echo "$*" >&2 |
|||
fi |
|||
"$@" |
|||
} |
|||
|
|||
OPTIND=1 # Reset in case getopts has been used previously in the shell. |
|||
|
|||
while getopts "h?lvk:r:" opt; do |
|||
case "$opt" in |
|||
h|\?) |
|||
show_help |
|||
exit 0 |
|||
;; |
|||
v) verbose=1 |
|||
;; |
|||
l) live=1 |
|||
;; |
|||
k) hook="$OPTARG" |
|||
;; |
|||
r) domain_dir="$OPTARG" |
|||
;; |
|||
*) show_help >&2 |
|||
exit 1 |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
shift $((OPTIND-1)) |
|||
|
|||
[ "${1:-}" = "--" ] && shift |
|||
|
|||
if [ $# -ne 1 ]; then |
|||
echo "Error: Unexpected number of positional arguments: $#" >&2 |
|||
show_help >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
domain="$1" |
|||
|
|||
if [ "$hook" == "" ]; then |
|||
echo "Error: Hook type not specified. Use -k to specify 'pre' or 'post'." >&2 |
|||
show_help >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if ! virsh dominfo "$domain" &> /dev/null; then |
|||
echo "Error: Domain '$domain' does not exist." >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
state=$(virsh domstate "$domain") |
|||
if [ "$live" -eq 1 ]; then |
|||
if [ "$hook" == "pre" ] && [ "$state" != "running" ]; then |
|||
echo "Error: Domain '$domain' is not running. Pre-hook can only work on running domains." >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ "$hook" == "post" ] && [ "$state" != "shut off" ]; then |
|||
echo "Error: Domain '$domain' is not shut off. Post-hook can only work on shut off domains." >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ "$domain_dir" == "" ]; then |
|||
echo "Error: Domain storage directory must be specified for live snapshots using the -r option." >&2 |
|||
show_help >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ ! -d "$domain_dir" ]; then |
|||
echo "Error: Specified domain directory '$domain_dir' does not exist." >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ "$hook" == "pre" ] && [ -f "$domain_dir/domain.save" ]; then |
|||
echo "Error: Specified domain directory '$domain_dir' already contains a save file." >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ "$hook" == "post" ] && [ ! -f "$domain_dir/domain.save" ]; then |
|||
echo "Error: Specified domain directory '$domain_dir' does not contain a save file." >&2 |
|||
exit 1 |
|||
fi |
|||
fi |
|||
|
|||
case "$hook" in |
|||
pre) |
|||
if [ "$live" -eq 1 ]; then |
|||
virsh_args=() |
|||
if [ "$verbose" -eq 1 ]; then |
|||
virsh_args+=(--verbose) |
|||
fi |
|||
run virsh save "$domain" "${domain_dir}/domain.save" "${virsh_args[@]}" --running --image-format raw |
|||
else |
|||
if [ "$state" == "running" ]; then |
|||
run virsh domfsfreeze "$domain" |
|||
fi |
|||
fi |
|||
;; |
|||
|
|||
post) |
|||
if [ "$live" -eq 1 ]; then |
|||
run virsh restore "${domain_dir}/domain.save" --running |
|||
run rm -f "${domain_dir}/domain.save" |
|||
else |
|||
if [ "$state" == "running" ]; then |
|||
run virsh domfsthaw "$domain" |
|||
fi |
|||
fi |
|||
;; |
|||
*) |
|||
echo "Error: Invalid hook type specified: '$hook'. Must be 'pre' or 'post'." >&2 |
|||
show_help >&2 |
|||
exit 1 |
|||
;; |
|||
esac |
|||
|
|||
exit 0 |
|||
@ -0,0 +1,89 @@ |
|||
#!/bin/bash |
|||
|
|||
set -Eeuo pipefail |
|||
|
|||
verbose=0 |
|||
live=0 |
|||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" |
|||
|
|||
function show_help () { |
|||
cat << EOF |
|||
Snapshots all libvirt domains on the host. It uses the zfs-autobackup tool |
|||
to create snapshots of the domains' using the ZFS native tools. |
|||
|
|||
Usage: ${0##*/} [-l] [-h] [-v] |
|||
|
|||
Options: |
|||
-h display this help and exit |
|||
-v verbose mode |
|||
-l live snapshot mode (default is crash-consistent) |
|||
EOF |
|||
} |
|||
|
|||
function run () { |
|||
if [ "$verbose" -eq 1 ]; then |
|||
echo "$*" >&2 |
|||
fi |
|||
"$@" |
|||
} |
|||
|
|||
OPTIND=1 # Reset in case getopts has been used previously in the shell. |
|||
|
|||
while getopts "h?lv" opt; do |
|||
case "$opt" in |
|||
h|\?) |
|||
show_help |
|||
exit 0 |
|||
;; |
|||
v) verbose=1 |
|||
;; |
|||
l) live=1 |
|||
;; |
|||
*) show_help >&2 |
|||
exit 1 |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
shift $((OPTIND-1)) |
|||
|
|||
[ "${1:-}" = "--" ] && shift |
|||
|
|||
if [ $# -ne 0 ]; then |
|||
echo "Error: Unexpected number of positional arguments: $#" >&2 |
|||
show_help >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
declare -a zfs_autobackup_args=() |
|||
if [ "$verbose" -eq 1 ]; then |
|||
zfs_autobackup_args+=("-v") |
|||
fi |
|||
zfs_autobackup_args+=("--no-send" "--no-thinning") |
|||
zfs_autobackup_args+=("--snapshot-format" "libvirt-%Y-%m-%d-%H:%M:%S") |
|||
|
|||
declare -a virsh_args=() |
|||
if [ "$live" -eq 1 ]; then |
|||
virsh_args+=("--state-running") |
|||
else |
|||
virsh_args+=("--all") |
|||
fi |
|||
for domain in $(virsh list --name "${virsh_args[@]}"); do |
|||
if [ "$(zfs get -t filesystem,volume autobackup:libvirt-${domain} -o value -H -s local)" == "" ]; then |
|||
echo "Skipping domain ${domain} because it is not configured for autobackup" >&2 |
|||
continue |
|||
fi |
|||
|
|||
declare -a zfs_autobackup_hooks_args=() |
|||
if [ "$live" -eq 1 ]; then |
|||
zfs_autobackup_hooks_args+=("-l" "-r" "/var/lib/libvirt/images/${domain}") |
|||
fi |
|||
if [ "$verbose" -eq 1 ]; then |
|||
zfs_autobackup_hooks_args+=("-v") |
|||
fi |
|||
|
|||
run zfs-autobackup "${zfs_autobackup_args[@]}" \ |
|||
--pre-snapshot-cmd "$SCRIPT_DIR/libvirt-hook ${zfs_autobackup_hooks_args[*]} -k pre $domain" \ |
|||
--post-snapshot-cmd "$SCRIPT_DIR/libvirt-hook ${zfs_autobackup_hooks_args[*]} -k post $domain" \ |
|||
"libvirt-${domain}" |
|||
done |
|||
@ -1,67 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
# This script takes snapshots or revert snapshots of libvirt domains using ZFS. |
|||
# |
|||
# It can take two kinds of snapshots: crash-consistent snapshots and live snapshots. |
|||
# |
|||
# - Crash-consistent snapshots are taken when the VM is powered off or powered on. |
|||
# They capture only the disk state at the time of the snapshot. |
|||
# Especially, those items are NOT included in crash-consistent snapshots: TPM, NVRAM, domain definition (XML). |
|||
# |
|||
# Taking crash-consistent snapshots makes use of ZFS filesystem snapshots of the underlying storage volumes |
|||
# |
|||
# Restoring from crash-consistent snapshots involves destroying the domain, reverting the ZFS snapshots and |
|||
# restarting the domain. |
|||
# |
|||
# - Live snapshots are taken while the VM is running and capture the entire state of the VM, including memory, |
|||
# CPU state, TPM, NVRAM, and domain definition (XML). |
|||
# |
|||
# Taking live snapshots makes use of libvirt's "save" functionality. The domain is paused, its state is saved |
|||
# to disk, the ZFS snapshots of the underlying storage volumes are taken, and then the domain is resumed. |
|||
# |
|||
# Restoring from live snapshots involves destroying the domain, reverting the ZFS snapshots, restoring the saved state, |
|||
# and restarting the domain. |
|||
# |
|||
|
|||
set -Eeuo pipefail |
|||
|
|||
# Make sure the output of underlying tool won't be altered by locale settings |
|||
export LANG=C |
|||
export LC_ALL=C |
|||
|
|||
# Load core library |
|||
script_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")/../")" |
|||
source "$script_dir/lib/zvirt/core.sh" |
|||
|
|||
# Parse command line arguments and act accordingly |
|||
init_global_variables |
|||
if ! parse_args "$@"; then |
|||
echo |
|||
show_help >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
case "$action" in |
|||
snapshot) |
|||
preflight_checks "$action" "$snapshot_name" "${domains[@]}" || fatal "Pre-flight checks failed." |
|||
take_snapshots || fatal "Failed to take snapshots." |
|||
;; |
|||
revert) |
|||
preflight_checks "$action" "$snapshot_name" "${domains[@]}" || fatal "Pre-flight checks failed." |
|||
revert_snapshots || fatal "Failed to revert snapshots." |
|||
;; |
|||
list) |
|||
preflight_checks "$action" "${domains[@]}" || fatal "Pre-flight checks failed." |
|||
list_snapshots "${domains[@]}" || fatal "Failed to list snapshots." |
|||
;; |
|||
prune) |
|||
preflight_checks "$action" "${domains[@]}" || fatal "Pre-flight checks failed." |
|||
prune_snapshots "${domains[@]}" || fatal "Failed to prune snapshots." |
|||
;; |
|||
*) |
|||
fatal "Unknown action '$action'." |
|||
;; |
|||
esac |
|||
|
|||
log_verbose "Operation '$action' completed successfully." |
|||
exit 0 |
|||
@ -1,581 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
## |
|||
## zvirt core library - Provides functions for taking and reverting snapshots of libvirt domains using ZFS. |
|||
## |
|||
|
|||
# Reports a verbose message to stdout if verbose mode is enabled. |
|||
function log_verbose () { |
|||
if [ "$verbose" -eq 1 ]; then |
|||
echo "$@" 2>&1 |
|||
fi |
|||
} |
|||
|
|||
# Reports a fatal error message to stderr and exits with a non-zero exit code. |
|||
function fatal () { |
|||
echo "Error: $*" 2>&1 |
|||
exit 1 |
|||
} |
|||
|
|||
# Reports an error message to stderr. |
|||
function error () { |
|||
echo "Error: $*" 2>&1 |
|||
} |
|||
|
|||
function show_help () { |
|||
cat << EOF |
|||
Usage: ${0##*/} action [-h] [-l] [-v] -d <domain_name> -s <snapshot_name> |
|||
|
|||
Options: |
|||
-h display this help and exit |
|||
-v verbose mode |
|||
-l live snapshot mode (default is crash-consistent) |
|||
-d DOMAIN specify domain name (you can specify multiple -d options) |
|||
-s SNAPSHOT specify snapshot name |
|||
-b batch mode (pause all domains, take snapshots, then resume all domains) |
|||
-k N keep at most N snapshots per domain (used with 'prune' action) |
|||
|
|||
Actions: |
|||
snapshot take a snapshot of the specified domain(s) |
|||
revert revert to a snapshot of the specified domain(s) |
|||
list list snapshots of the specified domain(s) (or all domains if none specified) |
|||
prune prune old snapshots of the specified domain(s) according to retention policy |
|||
|
|||
Examples: |
|||
Take a crash-consistent snapshot of domain 'vm1' named 'backup1': |
|||
${0##*/} snapshot -d vm1 -s backup1 |
|||
|
|||
Take a live snapshot of domains 'vm1' and 'vm2' in batch mode, named 'livebackup': |
|||
${0##*/} snapshot -l -b -d vm1 -d vm2 -s livebackup |
|||
|
|||
Revert domain 'vm1' to snapshot 'backup1': |
|||
${0##*/} revert -d vm1 -s backup1 |
|||
|
|||
List snapshots of domain 'vm1': |
|||
${0##*/} list -d vm1 |
|||
|
|||
List snapshots of all domains: |
|||
${0##*/} list |
|||
|
|||
Prune snapshots of all domains, keeping at most 5 snapshots: |
|||
${0##*/} prune -k 5 |
|||
EOF |
|||
} |
|||
|
|||
# Initialize the global variables |
|||
function init_global_variables () { |
|||
# Command line parsing variables |
|||
snapshot_name="" |
|||
domains=() |
|||
verbose=0 |
|||
action="" |
|||
batch=0 |
|||
live=0 |
|||
keep=0 |
|||
|
|||
# Cache for domain parameters to avoid redundant calls to the zfs command |
|||
declare -gA domain_params_cache=( ) |
|||
} |
|||
|
|||
# Parses the command-line arguments. |
|||
function parse_args () { |
|||
local should_exit=0 |
|||
|
|||
# Try to get the action from the first positional argument |
|||
if [ -n "${1:-}" ] && [[ ! "${1:-}" =~ ^- ]]; then |
|||
action="${1:-}" |
|||
shift || true |
|||
fi |
|||
|
|||
OPTIND=1 # Reset in case getopts has been used previously in the shell. |
|||
|
|||
while getopts "h?blvd:s:k:" opt; do |
|||
case "$opt" in |
|||
h|\?) |
|||
show_help |
|||
exit 0 |
|||
;; |
|||
v) verbose=1 |
|||
;; |
|||
d) domains+=( "$OPTARG" ) |
|||
;; |
|||
s) snapshot_name="$OPTARG" |
|||
;; |
|||
b) batch=1 |
|||
;; |
|||
l) live=1 |
|||
;; |
|||
k) keep="$OPTARG" |
|||
;; |
|||
*) show_help >&2 |
|||
exit 1 |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
shift $((OPTIND-1)) |
|||
|
|||
[ "${1:-}" = "--" ] && shift |
|||
|
|||
if [ $# -ne 0 ]; then |
|||
echo "Error: Unexpected positional arguments: $*" |
|||
should_exit=1 |
|||
fi |
|||
|
|||
if [ ${#domains[@]} -eq 0 ]; then |
|||
# Get all domains |
|||
mapfile -t domains < <(virsh list --all --name | grep -v '^$') |
|||
fi |
|||
|
|||
case "$action" in |
|||
snapshot) |
|||
if [ ${#domains[@]} -eq 0 ] || [ -z "$snapshot_name" ]; then |
|||
echo "Error: Domain name(s) and snapshot name must be specified." |
|||
should_exit=1 |
|||
fi |
|||
|
|||
if [[ ! "$snapshot_name" =~ ^[a-zA-Z0-9._-]+$ ]]; then |
|||
echo "Error: Snapshot name '$snapshot_name' contains invalid characters. Only alphanumeric characters, dots (.), underscores (_) and hyphens (-) are allowed." |
|||
should_exit=1 |
|||
fi |
|||
;; |
|||
revert) |
|||
if [ ${#domains[@]} -eq 0 ] || [ -z "$snapshot_name" ]; then |
|||
echo "Error: Domain name(s) and snapshot name must be specified." |
|||
should_exit=1 |
|||
fi |
|||
|
|||
if [ "$live" -eq 1 ]; then |
|||
echo "Error: Live mode is only supported for the 'snapshot' action." |
|||
should_exit=1 |
|||
fi |
|||
;; |
|||
list) |
|||
;; |
|||
prune) |
|||
if [ "$keep" -le 0 ]; then |
|||
echo "Error: The -k option with a positive integer value must be specified for the 'prune' action." |
|||
should_exit=1 |
|||
fi |
|||
;; |
|||
*) |
|||
echo "Error: Unsupported action '$action'." |
|||
should_exit=1 |
|||
;; |
|||
esac |
|||
|
|||
return $should_exit |
|||
} |
|||
|
|||
# Checks if the specified domain exists. |
|||
function domain_exists () { |
|||
local domain="$1" |
|||
if virsh dominfo "$domain" &> /dev/null; then |
|||
return 0 |
|||
else |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
# Performs various checks on the specified domain before taking or reverting a snapshot. |
|||
# All the checks are performed according to the specified action (snapshot or revert). |
|||
# Any errors are reported via stderr and the function returns a non-zero exit code. |
|||
function domain_checks () { |
|||
local action="$1" |
|||
local domain="$2" |
|||
local snapshot_name |
|||
if [ "$action" == "snapshot" ] || [ "$action" == "revert" ]; then |
|||
snapshot_name="$3" |
|||
fi |
|||
local error=0 |
|||
local state="" |
|||
|
|||
if ! domain_exists "$domain"; then |
|||
error "Domain '$domain' does not exist." |
|||
return 1 # There is no point in continuing checks if the domain does not exist |
|||
fi |
|||
|
|||
# ZFS dataset checks |
|||
zfs_datasets=( $(get_zfs_datasets_from_domain "$domain") ) |
|||
if [ ${#zfs_datasets[@]} -ne 1 ]; then |
|||
error "$domain: Wrong number of ZFS datasets (${#zfs_datasets[@]}) found." ; error=1 |
|||
fi |
|||
zfs_dataset="${zfs_datasets[0]:-}" |
|||
|
|||
# Zvols checks |
|||
zfs_zvols=( $(get_zfs_zvols_from_domain "$domain") ) |
|||
for zvol in "${zfs_zvols[@]}"; do |
|||
# Check if zvol is a child of $zfs_dataset |
|||
if [[ "$zvol" != "$zfs_dataset"* ]]; then |
|||
error "$domain: ZFS zvol '$zvol' is not a child of dataset '$zfs_dataset'." ; error=1 |
|||
fi |
|||
done |
|||
|
|||
zfs_dataset_snapshots=( $(get_zfs_snapshots_from_dataset "${zfs_dataset}") ) |
|||
zfs_mountpoint=$(get_zfs_dataset_mountpoint "${zfs_dataset}") |
|||
|
|||
if [ -z "$zfs_mountpoint" ] || [[ ! "$zfs_mountpoint" =~ ^/ ]]; then |
|||
error "$domain: Wrong ZFS mountpoint for dataset '$zfs_dataset': '$zfs_mountpoint'." ; error=1 |
|||
fi |
|||
|
|||
state=$(domain_state "$domain") |
|||
|
|||
# Store those values in cache for later use |
|||
domain_params_cache["$domain/state"]="${state}" |
|||
domain_params_cache["$domain/dataset"]="${zfs_dataset}" |
|||
domain_params_cache["$domain/mountpoint"]="${zfs_mountpoint}" |
|||
domain_params_cache["$domain/zvols"]="${zfs_zvols[*]}" |
|||
domain_params_cache["$domain/snapshots"]="${zfs_dataset_snapshots[*]}" |
|||
|
|||
case "$action" in |
|||
snapshot) |
|||
# Check domain state |
|||
if [ "$state" != "shut off" ] && [ "$state" != "running" ]; then |
|||
error "$domain: Domain must be either 'shut off' or 'running' to take a snapshot (current state: '$state')." ; error=1 |
|||
fi |
|||
|
|||
# Check if live snapshot requested on powered-off domain |
|||
if [ "$live" -eq 1 ] && [ "$state" != "running" ]; then |
|||
log_verbose "$domain: Live snapshot requested but domain is not running." |
|||
fi |
|||
|
|||
# Check if snapshot already exists |
|||
if printf '%s\n' "${zfs_dataset_snapshots[@]}" | grep -Fqx "$snapshot_name" ; then |
|||
error "$domain: Snapshot '$snapshot_name' already exists." ; error=1 |
|||
fi |
|||
for zvol in "${zfs_zvols[@]}"; do |
|||
zfs_zvol_snapshots=( $(get_zfs_snapshots_from_dataset "$zvol") ) |
|||
if printf '%s\n' "${zfs_zvol_snapshots[@]}" | grep -Fqx "$snapshot_name" ; then |
|||
error "$domain: Snapshot '$snapshot_name' already exists for ZFS zvol '$zvol'." ; error=1 |
|||
fi |
|||
done |
|||
|
|||
# Check if save file already exists for live snapshot |
|||
if [ "$live" -eq 1 ] && has_save_file "$domain"; then |
|||
error "$domain: Save file '${zfs_mountpoint}/domain.save' already exists." ; error=1 |
|||
fi |
|||
;; |
|||
revert) |
|||
# Check domain state |
|||
if [ "$state" != "shut off" ]; then |
|||
error "$domain: Domain must be 'shut off' to revert a snapshot (current state: '$state')." ; error=1 |
|||
fi |
|||
|
|||
# Check if snapshot exists |
|||
if ! printf '%s\n' "${zfs_dataset_snapshots[@]}" | grep -Fqx "$snapshot_name" ; then |
|||
error "$domain: Snapshot '$snapshot_name' does not exist for domain '$domain'." ; error=1 |
|||
fi |
|||
for zvol in "${zfs_zvols[@]}"; do |
|||
zfs_zvol_snapshots=( $(get_zfs_snapshots_from_dataset "$zvol") ) |
|||
if ! printf '%s\n' "${zfs_zvol_snapshots[@]}" | grep -Fqx "$snapshot_name" ; then |
|||
error "$domain: Snapshot '$snapshot_name' does not exist for ZFS zvol '$zvol'." ; error=1 |
|||
fi |
|||
done |
|||
;; |
|||
list) |
|||
;; |
|||
prune) |
|||
if [ ${#zfs_dataset_snapshots[@]} -le "$keep" ]; then |
|||
log_verbose "$domain: No snapshots to prune (total: ${#zfs_dataset_snapshots[@]}, keep: $keep)." |
|||
fi |
|||
;; |
|||
*) |
|||
# Should not reach here due to prior validation |
|||
error "$domain: Unknown action '$action'." |
|||
;; |
|||
esac |
|||
|
|||
if [ $error -ne 0 ]; then |
|||
error "$domain: Domain checks failed." |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
# Gets the mountpoint of the specified ZFS dataset. |
|||
function get_zfs_dataset_mountpoint () { |
|||
local zfs_dataset="$1" |
|||
zfs get -H -o value mountpoint "${zfs_dataset}" |
|||
} |
|||
|
|||
# Gets the current state of the specified domain. |
|||
function domain_state () { |
|||
local domain="$1" |
|||
virsh domstate "$domain" |
|||
} |
|||
|
|||
# Gets the list of ZFS datasets used by the specified domain (excluding zvols) |
|||
function get_zfs_datasets_from_domain () { |
|||
local domain="$1" |
|||
virsh domblklist "$domain" --details | awk '$1 == "file" && $2 == "disk" { print $4 }' | while read -r file; do df --output=source "$file" | tail -n 1; done | sort | uniq |
|||
} |
|||
|
|||
# Gets the list of ZFS zvols used by the specified domain |
|||
function get_zfs_zvols_from_domain () { |
|||
local domain="$1" |
|||
virsh domblklist "$domain" --details | awk '$1 == "block" && $2 == "disk" && $4 ~ /^\/dev\/zvol\// { gsub(/\/dev\/zvol\//, "", $4); print $4 }' |
|||
} |
|||
|
|||
# Gets the list of ZFS snapshots for the specified dataset. |
|||
function get_zfs_snapshots_from_dataset () { |
|||
local dataset="$1" |
|||
zfs list -H -t snapshot -o name "$dataset" | awk -F'@' '{print $2}' | sort | uniq |
|||
} |
|||
|
|||
# Takes a live snapshot of the specified domain. |
|||
function take_live_snapshot () { |
|||
local domain="$1" |
|||
local snapshot="$2" |
|||
|
|||
log_verbose "$domain: Taking live snapshot '$snapshot'..." |
|||
zfs_dataset="${domain_params_cache["$domain/dataset"]}" |
|||
zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" |
|||
virsh save "$domain" "${zfs_mountpoint}/domain.save" --running --verbose --image-format raw |
|||
zfs snapshot -r "${zfs_dataset}@${snapshot}" |
|||
} |
|||
|
|||
# Takes a crash-consistent snapshot of the specified domain. |
|||
function take_crash_consistent_snapshot () { |
|||
local domain="$1" |
|||
local snapshot="$2" |
|||
|
|||
log_verbose "$domain: Taking crash-consistent snapshot '$snapshot'..." |
|||
zfs_dataset="${domain_params_cache["$domain/dataset"]}" |
|||
zfs snapshot -r "${zfs_dataset}@${snapshot}" |
|||
} |
|||
|
|||
# Reverts the specified snapshot for the given domain. |
|||
function revert_snapshot () { |
|||
local domain="$1" |
|||
local snapshot="$2" |
|||
|
|||
log_verbose "$domain: Reverting snapshot '$snapshot'..." |
|||
zfs_dataset="${domain_params_cache["$domain/dataset"]}" |
|||
zfs list -H -r -o name "$zfs_dataset" | while read dataset; do |
|||
zfs rollback -Rrf "$dataset@$snapshot" |
|||
done |
|||
} |
|||
|
|||
# Restores a saved domain. |
|||
function restore_domain () { |
|||
local domain="$1" |
|||
|
|||
log_verbose "$domain: Restoring live snapshot..." |
|||
zfs_dataset="${domain_params_cache["$domain/dataset"]}" |
|||
zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" |
|||
virsh_restore_opts=( ) |
|||
if [ "$batch" -eq 1 ]; then |
|||
virsh_restore_opts+=( "--paused" ) |
|||
else |
|||
virsh_restore_opts+=( "--running" ) |
|||
fi |
|||
virsh restore "${zfs_mountpoint}/domain.save" "${virsh_restore_opts[@]}" |
|||
} |
|||
|
|||
# Pauses all domains in the list. |
|||
function pause_all_domains () { |
|||
local domains=( "$@" ) |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
log_verbose "$domain: Pausing domain..." |
|||
state="${domain_params_cache["$domain/state"]}" |
|||
if [ "$state" == "running" ]; then |
|||
virsh suspend "$domain" |
|||
fi |
|||
done |
|||
} |
|||
|
|||
# Resumes all domains in the list. |
|||
function resume_all_domains () { |
|||
local domains=( "$@" ) |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
log_verbose "$domain: Resuming domain..." |
|||
state="$(domain_state "$domain")" |
|||
case "$state" in |
|||
paused) |
|||
virsh resume "$domain" || true |
|||
;; |
|||
"shut off") |
|||
virsh start "$domain" || true |
|||
;; |
|||
*) |
|||
continue |
|||
;; |
|||
esac |
|||
done |
|||
} |
|||
|
|||
# Performs pre-flight checks for all specified domains according to the action. |
|||
function preflight_checks () { |
|||
local action="$1" ; shift |
|||
local snapshot_name |
|||
if [ "$action" == "snapshot" ] || [ "$action" == "revert" ]; then |
|||
snapshot_name="$1" ; shift |
|||
fi |
|||
local error=0 |
|||
local domains=( "$@" ) |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
log_verbose "$domain: Performing domain pre-flight checks for $action..." |
|||
local -a domain_checks_args=( "$action" "$domain" ) |
|||
if [ "$action" == "snapshot" ] || [ "$action" == "revert" ]; then |
|||
domain_checks_args+=( "$snapshot_name" ) |
|||
fi |
|||
if ! domain_checks "${domain_checks_args[@]}"; then |
|||
error=1 |
|||
fi |
|||
done |
|||
|
|||
return $error |
|||
} |
|||
|
|||
|
|||
# Removes the save file for the specified domain. |
|||
function remove_save_file () { |
|||
local domain="$1" |
|||
zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" |
|||
if [ -f "${zfs_mountpoint}/domain.save" ]; then |
|||
log_verbose "$domain: Removing save file '${zfs_mountpoint}/domain.save'..." |
|||
rm -f "${zfs_mountpoint}/domain.save" |
|||
fi |
|||
} |
|||
|
|||
# Checks if the save file exists for the specified domain. |
|||
function has_save_file () { |
|||
local domain="$1" |
|||
zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" |
|||
if [ -f "${zfs_mountpoint}/domain.save" ]; then |
|||
return 0 |
|||
else |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
# Thaws the specified domain filesystem. |
|||
function fsthaw_domain () { |
|||
local domain="$1" |
|||
virsh domfsthaw "$domain" |
|||
} |
|||
|
|||
# Freezes the specified domain filesystem. |
|||
function fsfreeze_domain () { |
|||
local domain="$1" |
|||
virsh domfsfreeze "$domain" |
|||
} |
|||
|
|||
# Thaws all domains in the list. |
|||
function fsthaw_all_domains () { |
|||
local domains=( "$@" ) |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
log_verbose "$domain: Thawing domain..." |
|||
state="${domain_params_cache["$domain/state"]}" |
|||
if [ "$state" == "running" ]; then |
|||
fsthaw_domain "$domain" |
|||
fi |
|||
done |
|||
} |
|||
|
|||
# Freezes all domains in the list. |
|||
function fsfreeze_all_domains () { |
|||
local domains=( "$@" ) |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
log_verbose "$domain: Freezing domain..." |
|||
state="${domain_params_cache["$domain/state"]}" |
|||
if [ "$state" == "running" ]; then |
|||
fsfreeze_domain "$domain" |
|||
fi |
|||
done |
|||
} |
|||
|
|||
# Takes snapshots for all specified domains. |
|||
function take_snapshots () { |
|||
if [ "$batch" -eq 1 ] && [ "$live" -eq 1 ]; then |
|||
pause_all_domains "${domains[@]}" |
|||
elif [ "$batch" -eq 1 ] && [ "$live" -eq 0 ]; then |
|||
fsfreeze_all_domains "${domains[@]}" |
|||
fi |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
state="${domain_params_cache["$domain/state"]}" |
|||
if [ "$live" -eq 1 ] && [ "$state" == "running" ]; then |
|||
take_live_snapshot "$domain" "$snapshot_name" |
|||
restore_domain "$domain" |
|||
if [ "$batch" -eq 1 ]; then |
|||
remove_save_file "$domain" |
|||
fi |
|||
else |
|||
if [ "$batch" -eq 0 ] && [ "$state" == "running" ]; then |
|||
fsfreeze_domain "$domain" |
|||
fi |
|||
take_crash_consistent_snapshot "$domain" "$snapshot_name" |
|||
if [ "$batch" -eq 0 ] && [ "$state" == "running" ]; then |
|||
fsthaw_domain "$domain" |
|||
fi |
|||
fi |
|||
done |
|||
|
|||
if [ "$batch" -eq 1 ] && [ "$live" -eq 1 ]; then |
|||
resume_all_domains "${domains[@]}" |
|||
elif [ "$batch" -eq 1 ] && [ "$live" -eq 0 ]; then |
|||
fsthaw_all_domains "${domains[@]}" |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
# Reverts snapshots for all specified domains. |
|||
function revert_snapshots () { |
|||
for domain in "${domains[@]}"; do |
|||
revert_snapshot "$domain" "$snapshot_name" |
|||
if has_save_file "$domain"; then |
|||
restore_domain "$domain" |
|||
if [ "$batch" -eq 1 ]; then |
|||
remove_save_file "$domain" |
|||
fi |
|||
fi |
|||
done |
|||
|
|||
if [ "$batch" -eq 1 ]; then |
|||
resume_all_domains "${domains[@]}" |
|||
fi |
|||
} |
|||
|
|||
# Lists snapshots for all specified domains. |
|||
function list_snapshots () { |
|||
local domains=( "$@" ) |
|||
local domain |
|||
local snapshot |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
echo "Snapshots for domain '$domain':" |
|||
for snapshot in ${domain_params_cache["$domain/snapshots"]}; do |
|||
echo " - $snapshot" |
|||
done |
|||
done |
|||
} |
|||
|
|||
# Prunes old snapshots for all specified domains according to the retention policy. |
|||
function prune_snapshots () { |
|||
local domains=( "$@" ) |
|||
local dataset |
|||
local snapshots |
|||
local domain |
|||
|
|||
for domain in "${domains[@]}"; do |
|||
snapshots=( ${domain_params_cache["$domain/snapshots"]} ) |
|||
dataset="${domain_params_cache["$domain/dataset"]}" |
|||
if [ "${#snapshots[@]}" -le "$keep" ]; then |
|||
continue |
|||
fi |
|||
local first_to_delete_idx=$(( ${#snapshots[@]} - keep - 1 )) |
|||
local first_to_delete="${snapshots[$first_to_delete_idx]}" |
|||
if [ -z "$first_to_delete" ]; then |
|||
continue |
|||
fi |
|||
zfs destroy -r "${dataset}@%${first_to_delete}" |
|||
done |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
#cloud-config |
|||
|
|||
bootcmd: |
|||
- setsebool -P virt_qemu_ga_run_unconfined on |
|||
- setsebool -P virt_qemu_ga_read_nonsecurity_files on |
|||
- setsebool -P virt_rw_qemu_ga_data on |
|||
- install -o root -g root -m 0777 --context=system_u:object_r:virt_qemu_ga_data_t:s0 -d /test/rootfs |
|||
|
|||
users: |
|||
- name: e2e |
|||
gecos: End-to-End Test User |
|||
sudo: ALL=(ALL) NOPASSWD:ALL |
|||
groups: wheel |
|||
lock_passwd: false |
|||
# echo -n test | mkpasswd -m bcrypt -s |
|||
passwd: $2b$05$Oh13XsRSrGrL/iSvV0Rax.w7rQMx/6lyBTCuaEVXrdh/qiagci9bS |
|||
@ -1,19 +0,0 @@ |
|||
#cloud-config |
|||
|
|||
mounts: |
|||
- [ data, /test/virtiofs, virtiofs, "defaults,context=system_u:object_r:virt_qemu_ga_data_t:s0", "0", "0" ] |
|||
|
|||
bootcmd: |
|||
- setsebool -P virt_qemu_ga_run_unconfined on |
|||
- setsebool -P virt_qemu_ga_read_nonsecurity_files on |
|||
- setsebool -P virt_rw_qemu_ga_data on |
|||
- install -o root -g root -d /test/virtiofs |
|||
|
|||
users: |
|||
- name: e2e |
|||
gecos: End-to-End Test User |
|||
sudo: ALL=(ALL) NOPASSWD:ALL |
|||
groups: wheel |
|||
lock_passwd: false |
|||
# echo -n test | mkpasswd -m bcrypt -s |
|||
passwd: $2b$05$Oh13XsRSrGrL/iSvV0Rax.w7rQMx/6lyBTCuaEVXrdh/qiagci9bS |
|||
@ -1,31 +0,0 @@ |
|||
#cloud-config |
|||
|
|||
disk_setup: |
|||
/dev/vdb: |
|||
table_type: gpt |
|||
layout: true |
|||
overwrite: true |
|||
|
|||
fs_setup: |
|||
- label: zvol |
|||
filesystem: xfs |
|||
device: /dev/vdb |
|||
partition: auto |
|||
|
|||
mounts: |
|||
- [ LABEL=zvol, /test/zvol, xfs, "defaults,context=system_u:object_r:virt_qemu_ga_data_t:s0", "0", "2" ] |
|||
|
|||
bootcmd: |
|||
- setsebool -P virt_qemu_ga_run_unconfined on |
|||
- setsebool -P virt_qemu_ga_read_nonsecurity_files on |
|||
- setsebool -P virt_rw_qemu_ga_data on |
|||
- mkdir -p /test/zvol |
|||
|
|||
users: |
|||
- name: e2e |
|||
gecos: End-to-End Test User |
|||
sudo: ALL=(ALL) NOPASSWD:ALL |
|||
groups: wheel |
|||
lock_passwd: false |
|||
# echo -n test | mkpasswd -m bcrypt -s |
|||
passwd: $2b$05$Oh13XsRSrGrL/iSvV0Rax.w7rQMx/6lyBTCuaEVXrdh/qiagci9bS |
|||
@ -1,836 +0,0 @@ |
|||
#!/usr/bin/env bats |
|||
|
|||
setup() { |
|||
bats_load_library 'bats-support' |
|||
bats_load_library 'bats-assert' |
|||
|
|||
set -Eeuo pipefail |
|||
export LANG=C LC_ALL=C |
|||
|
|||
zvirt () { |
|||
"${BATS_TEST_DIRNAME}/../../src/bin/zvirt" "$@" |
|||
} |
|||
|
|||
declare -g e2e_test_enable_debug=1 |
|||
e2e_test_debug_log(){ |
|||
if [ "$e2e_test_enable_debug" -eq 1 ]; then |
|||
echo "$@" >&3 |
|||
fi |
|||
} |
|||
|
|||
qemu_exec() { |
|||
domain="$1" |
|||
shift || true |
|||
local json_args="" |
|||
for arg in "${@:2}"; do |
|||
if [ -n "$json_args" ]; then |
|||
json_args+=", " |
|||
fi |
|||
json_args+="\"$arg\"" |
|||
done |
|||
local command="{\"execute\": \"guest-exec\", \"arguments\": {\"path\": \"$1\", \"arg\": [ $json_args ], \"capture-output\": true }}" |
|||
output="$(virsh qemu-agent-command "$domain" "$command")" |
|||
#e2e_test_debug_log "qemu_exec: command output: $output" |
|||
pid="$(echo "$output" | jq -r '.return.pid')" |
|||
if [ -z "$pid" ] || [ "$pid" == "null" ]; then |
|||
e2e_test_debug_log "qemu_exec: failed to get pid from command output" |
|||
return 1 |
|||
fi |
|||
sleep .25 |
|||
while true; do |
|||
local status_command="{\"execute\": \"guest-exec-status\", \"arguments\": {\"pid\": $pid}}" |
|||
status_output="$(virsh qemu-agent-command "$domain" "$status_command")" |
|||
#e2e_test_debug_log "qemu_exec: status output: $status_output" |
|||
exited="$(echo "$status_output" | jq -r '.return.exited')" |
|||
if [ "$exited" == "true" ]; then |
|||
stdout_base64="$(echo "$status_output" | jq -r '.return["out-data"]')" |
|||
if [ "$stdout_base64" != "null" ]; then |
|||
echo "$stdout_base64" | base64 --decode |
|||
fi |
|||
stderr_base64="$(echo "$status_output" | jq -r '.return["err-data"]')" |
|||
if [ "$stderr_base64" != "null" ]; then |
|||
echo "$stderr_base64" | base64 --decode >&2 |
|||
fi |
|||
exit_code="$(echo "$status_output" | jq -r '.return.exitcode')" |
|||
return $exit_code |
|||
fi |
|||
sleep 1 |
|||
done |
|||
} |
|||
|
|||
create_cloud_init_iso () { |
|||
local domain="$1" |
|||
local iso_path="/var/lib/libvirt/images/${domain}/cloud-init.iso" |
|||
local user_data_path="/var/lib/libvirt/images/${domain}/cloud-init/user-data" |
|||
local meta_data_path="/var/lib/libvirt/images/${domain}/cloud-init/meta-data" |
|||
|
|||
# Create cloud-init user-data and meta-data files |
|||
mkdir -p "/var/lib/libvirt/images/${domain}/cloud-init" |
|||
cp "${BATS_TEST_DIRNAME}/cloud-init/${domain}-user-data" "$user_data_path" |
|||
cat > "$meta_data_path" <<EOF |
|||
instance-id: ${domain} |
|||
local-hostname: ${domain} |
|||
EOF |
|||
|
|||
# Create ISO image |
|||
genisoimage -output "$iso_path" -volid cidata -joliet -rock "$user_data_path" "$meta_data_path" |
|||
} |
|||
|
|||
convert_cloud_image() { |
|||
local src="$1" |
|||
local dest="$2" |
|||
|
|||
# Convert qcow2 to raw and resize to 20G |
|||
qemu-img convert -f qcow2 -O raw "$src" "$dest" |
|||
qemu-img resize -f raw "$dest" 20G |
|||
} |
|||
|
|||
cleanup() { |
|||
e2e_test_debug_log "teardown: Cleaning up created domains and images..." |
|||
for domain in standard with-fs with-zvol; do |
|||
state="$(virsh domstate "$domain" 2>/dev/null || true)" |
|||
if [[ -n "$state" && "$state" != "shut off" ]]; then |
|||
virsh destroy "$domain" |
|||
fi |
|||
if virsh dominfo "$domain" &>/dev/null; then |
|||
virsh undefine "$domain" --nvram |
|||
fi |
|||
done |
|||
sleep 1 |
|||
sync |
|||
sleep 1 |
|||
for domain in standard with-fs with-zvol; do |
|||
if zfs list data/domains/"$domain" &>/dev/null; then |
|||
zfs destroy -rR data/domains/"$domain" |
|||
fi |
|||
sleep .2 |
|||
rm -rf "/var/lib/libvirt/images/${domain}" |
|||
done |
|||
} |
|||
|
|||
create_domains() { |
|||
# Create the standard VM |
|||
e2e_test_debug_log "setup: Creating the standard VM..." |
|||
mkdir -p /var/lib/libvirt/images/standard |
|||
zfs create -p data/domains/standard -o mountpoint=/var/lib/libvirt/images/standard |
|||
convert_cloud_image "$fedora_img" "/var/lib/libvirt/images/standard/root.img" |
|||
create_cloud_init_iso "standard" |
|||
virt-install --noautoconsole \ |
|||
--name=standard \ |
|||
--cpu=host-passthrough \ |
|||
--vcpus=1 \ |
|||
--ram=4096 \ |
|||
--os-variant=fedora-rawhide \ |
|||
--disk=path=/var/lib/libvirt/images/standard/root.img,target.dev=vda,bus=virtio,driver.discard=unmap,driver.io=io_uring,format=raw,sparse=True,blockio.logical_block_size=512,blockio.physical_block_size=512,serial=root,format=raw \ |
|||
--network=none \ |
|||
--console=pty,target.type=virtio \ |
|||
--serial=pty \ |
|||
--disk=path=/var/lib/libvirt/images/standard/cloud-init.iso,readonly=True \ |
|||
--import \ |
|||
--sysinfo=system.serial=ds=nocloud \ |
|||
--boot=uefi |
|||
|
|||
# Create the with-fs VM |
|||
e2e_test_debug_log "setup: Creating the with-fs VM..." |
|||
mkdir -p /var/lib/libvirt/images/with-fs /srv/with-fs |
|||
chmod 0777 /srv/with-fs |
|||
zfs create -p data/domains/with-fs -o mountpoint=/var/lib/libvirt/images/with-fs |
|||
zfs create -p data/domains/with-fs/virtiofs -o mountpoint=/srv/with-fs |
|||
convert_cloud_image "$fedora_img" "/var/lib/libvirt/images/with-fs/root.img" |
|||
create_cloud_init_iso "with-fs" |
|||
virt-install --noautoconsole \ |
|||
--name=with-fs \ |
|||
--cpu=host-passthrough \ |
|||
--vcpus=1 \ |
|||
--ram=4096 \ |
|||
--os-variant=fedora-rawhide \ |
|||
--disk=path=/var/lib/libvirt/images/with-fs/root.img,target.dev=vda,bus=virtio,driver.discard=unmap,driver.io=io_uring,format=raw,sparse=True,blockio.logical_block_size=512,blockio.physical_block_size=512,serial=root,format=raw \ |
|||
--network=none \ |
|||
--console=pty,target.type=virtio \ |
|||
--serial=pty \ |
|||
--disk=path=/var/lib/libvirt/images/with-fs/cloud-init.iso,readonly=True \ |
|||
--import \ |
|||
--sysinfo=system.serial=ds=nocloud \ |
|||
--boot=uefi \ |
|||
--memorybacking=access.mode=shared,source.type=memfd \ |
|||
--filesystem=type=mount,accessmode=passthrough,driver.type=virtiofs,driver.queue=1024,source.dir=/srv/with-fs,target.dir=data |
|||
|
|||
# Create the with-zvol VM |
|||
e2e_test_debug_log "setup: Creating the with-zvol VM..." |
|||
mkdir -p /var/lib/libvirt/images/with-zvol |
|||
zfs create -p data/domains/with-zvol -o mountpoint=/var/lib/libvirt/images/with-zvol |
|||
zfs create -V 10G data/domains/with-zvol/data |
|||
convert_cloud_image "$fedora_img" "/var/lib/libvirt/images/with-zvol/root.img" |
|||
create_cloud_init_iso "with-zvol" |
|||
virt-install --noautoconsole \ |
|||
--name=with-zvol \ |
|||
--cpu=host-passthrough \ |
|||
--vcpus=1 \ |
|||
--ram=4096 \ |
|||
--os-variant=fedora-rawhide \ |
|||
--disk=path=/var/lib/libvirt/images/with-zvol/root.img,target.dev=vda,bus=virtio,driver.discard=unmap,driver.io=io_uring,format=raw,sparse=True,blockio.logical_block_size=512,blockio.physical_block_size=512,serial=root,format=raw \ |
|||
--disk=path=/dev/zvol/data/domains/with-zvol/data,target.dev=vdb,bus=virtio,cache=directsync,blockio.logical_block_size=4096,blockio.physical_block_size=4096,driver.discard=unmap,driver.io=io_uring,serial=zvol \ |
|||
--network=none \ |
|||
--console=pty,target.type=virtio \ |
|||
--serial=pty \ |
|||
--disk=path=/var/lib/libvirt/images/with-zvol/cloud-init.iso,readonly=True \ |
|||
--import \ |
|||
--sysinfo=system.serial=ds=nocloud \ |
|||
--boot=uefi |
|||
} |
|||
|
|||
readiness_wait() { |
|||
e2e_test_debug_log "setup: Waiting for VMs to become ready..." |
|||
for domain in standard with-fs with-zvol; do |
|||
e2e_test_debug_log "setup: Waiting for qemu guest agent to be running in domain '$domain'..." |
|||
until virsh qemu-agent-command "$domain" '{"execute":"guest-ping"}' &>/dev/null; do |
|||
sleep 2 |
|||
done |
|||
done |
|||
e2e_test_debug_log "setup: all VMs started successfully" |
|||
for domain in standard with-fs with-zvol; do |
|||
e2e_test_debug_log "setup: Waiting for cloud-init to complete in domain '$domain'..." |
|||
until qemu_exec "$domain" test -f /var/lib/cloud/instance/boot-finished; do |
|||
sleep 2 |
|||
done |
|||
done |
|||
if ! qemu_exec with-fs grep -q /test/virtiofs /proc/mounts; then |
|||
e2e_test_debug_log "setup: virtiofs not mounted in 'with-fs' domain" |
|||
return 1 |
|||
fi |
|||
if ! qemu_exec with-zvol grep -q /test/zvol /proc/mounts; then |
|||
e2e_test_debug_log "setup: zvol not mounted in 'with-zvol' domain" |
|||
return 1 |
|||
fi |
|||
e2e_test_debug_log "setup: VMs are ready" |
|||
} |
|||
|
|||
local fedora_url="https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2" |
|||
local fedora_img="/var/lib/libvirt/images/$(basename "$fedora_url")" |
|||
if [ ! -f "$fedora_img" ]; then |
|||
e2e_test_debug_log "setup: downloading Fedora Cloud image to $fedora_img" |
|||
mkdir -p /var/lib/libvirt/images/library |
|||
curl -sSfL -o "$fedora_img" "$fedora_url" |
|||
fi |
|||
e2e_test_debug_log "setup: Fedora Cloud image is at $fedora_img" |
|||
|
|||
# Cleanup any leftover artifacts from previous runs |
|||
cleanup |
|||
create_domains |
|||
readiness_wait |
|||
} |
|||
|
|||
teardown() { |
|||
cleanup |
|||
} |
|||
|
|||
@test "zvirt: setup selftest" { |
|||
e2e_test_debug_log "setup: provisioning completed" |
|||
} |
|||
|
|||
@test "zvirt: prune snapshots" { |
|||
# Take five snapshots in a row, each time creating and deleting a witness file |
|||
for snap in s1 s2 s3 s4 s5; do |
|||
# Create witness files in all three domains before taking snapshots |
|||
qemu_exec standard touch /test/rootfs/witness-file.$snap |
|||
qemu_exec with-fs touch /test/virtiofs/witness-file.$snap |
|||
qemu_exec with-zvol touch /test/zvol/witness-file.$snap |
|||
|
|||
# Verify that the witness files exist in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file.$snap |
|||
assert_success |
|||
|
|||
# Take crash-consistent snapshots for all three domains |
|||
run zvirt snapshot -d standard -d with-zvol -d with-fs -s $snap |
|||
assert_success |
|||
|
|||
# Verify that the domains are still running |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Assert that the files created before the snapshot exist |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file.$snap" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file.$snap" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file.$snap" |
|||
|
|||
# Delete the witness files |
|||
run qemu_exec standard rm /test/rootfs/witness-file.$snap |
|||
assert_success |
|||
run qemu_exec with-fs rm /test/virtiofs/witness-file.$snap |
|||
assert_success |
|||
run qemu_exec with-zvol rm /test/zvol/witness-file.$snap |
|||
assert_success |
|||
|
|||
# Sync all filesystems |
|||
run qemu_exec standard sync |
|||
assert_success |
|||
run qemu_exec with-fs sync |
|||
assert_success |
|||
run qemu_exec with-zvol sync |
|||
assert_success |
|||
|
|||
# Wait a moment to ensure all writes are flushed |
|||
sleep 2 |
|||
|
|||
# Verify that the witness files have been deleted in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file.$snap |
|||
assert_failure |
|||
done |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- s1 |
|||
- s2 |
|||
- s3 |
|||
- s4 |
|||
- s5 |
|||
Snapshots for domain 'with-zvol': |
|||
- s1 |
|||
- s2 |
|||
- s3 |
|||
- s4 |
|||
- s5 |
|||
Snapshots for domain 'with-fs': |
|||
- s1 |
|||
- s2 |
|||
- s3 |
|||
- s4 |
|||
- s5" |
|||
|
|||
# Prune snapshots to keep only the latest two |
|||
run zvirt prune -k 2 -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- s4 |
|||
- s5 |
|||
Snapshots for domain 'with-zvol': |
|||
- s4 |
|||
- s5 |
|||
Snapshots for domain 'with-fs': |
|||
- s4 |
|||
- s5" |
|||
|
|||
# Stop all domains |
|||
run virsh destroy standard |
|||
assert_success |
|||
run virsh destroy with-fs |
|||
assert_success |
|||
run virsh destroy with-zvol |
|||
assert_success |
|||
|
|||
# Revert snapshots in batch mode |
|||
run zvirt revert -d standard -d with-zvol -d with-fs -s s4 |
|||
assert_success |
|||
|
|||
# Check all domains have been shut off |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "shut off" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "shut off" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "shut off" |
|||
|
|||
# Start all domains |
|||
run virsh start standard |
|||
assert_success |
|||
run virsh start with-fs |
|||
assert_success |
|||
run virsh start with-zvol |
|||
assert_success |
|||
|
|||
# Wait for all domains to be fully ready |
|||
readiness_wait |
|||
|
|||
# Verify that the witness files still exist after revert |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file.s4" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file.s4" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file.s4" |
|||
} |
|||
|
|||
@test "zvirt: take live snapshot in batch mode" { |
|||
# Create witness files in all three domains before taking snapshots |
|||
qemu_exec standard touch /test/rootfs/witness-file |
|||
qemu_exec with-fs touch /test/virtiofs/witness-file |
|||
qemu_exec with-zvol touch /test/zvol/witness-file |
|||
|
|||
# Verify that the witness files exist in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_success |
|||
|
|||
# Take live snapshots for all three domains |
|||
run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 -l |
|||
assert_success |
|||
|
|||
# Verify that the domains are still running |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Assert that the files created before the snapshot exist |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- backup1 |
|||
Snapshots for domain 'with-zvol': |
|||
- backup1 |
|||
Snapshots for domain 'with-fs': |
|||
- backup1" |
|||
|
|||
# Attempt to take the same snapshot again and expect failure |
|||
run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 -l |
|||
assert_failure |
|||
assert_output --partial "Snapshot 'backup1' already exists." |
|||
assert_output --partial "standard:" |
|||
assert_output --partial "with-zvol:" |
|||
assert_output --partial "with-fs:" |
|||
assert_output --partial "Pre-flight checks failed." |
|||
|
|||
# Delete the witness files |
|||
run qemu_exec standard rm /test/rootfs/witness-file |
|||
assert_success |
|||
run qemu_exec with-fs rm /test/virtiofs/witness-file |
|||
assert_success |
|||
run qemu_exec with-zvol rm /test/zvol/witness-file |
|||
assert_success |
|||
|
|||
# Sync all filesystems |
|||
run qemu_exec standard sync |
|||
assert_success |
|||
run qemu_exec with-fs sync |
|||
assert_success |
|||
run qemu_exec with-zvol sync |
|||
assert_success |
|||
|
|||
# Verify that the witness files have been deleted in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_failure |
|||
|
|||
# Stop all domains |
|||
run virsh destroy standard |
|||
assert_success |
|||
run virsh destroy with-fs |
|||
assert_success |
|||
run virsh destroy with-zvol |
|||
assert_success |
|||
|
|||
# Revert snapshots in batch mode |
|||
run zvirt revert -b -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Check all domains are running again |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Verify that the witness files still exist after revert |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
} |
|||
|
|||
@test "zvirt: take live snapshot without batch mode" { |
|||
# Create witness files in all three domains before taking snapshots |
|||
qemu_exec standard touch /test/rootfs/witness-file |
|||
qemu_exec with-fs touch /test/virtiofs/witness-file |
|||
qemu_exec with-zvol touch /test/zvol/witness-file |
|||
|
|||
# Verify that the witness files exist in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_success |
|||
|
|||
# Take live snapshots for all three domains |
|||
run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 -l |
|||
assert_success |
|||
|
|||
# Verify that the domains are still running |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Assert that the files created before the snapshot exist |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- backup1 |
|||
Snapshots for domain 'with-zvol': |
|||
- backup1 |
|||
Snapshots for domain 'with-fs': |
|||
- backup1" |
|||
|
|||
# Attempt to take the same snapshot again and expect failure |
|||
run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 -l |
|||
assert_failure |
|||
assert_output --partial "Snapshot 'backup1' already exists." |
|||
assert_output --partial "standard:" |
|||
assert_output --partial "with-zvol:" |
|||
assert_output --partial "with-fs:" |
|||
assert_output --partial "Pre-flight checks failed." |
|||
|
|||
# Delete the witness files |
|||
run qemu_exec standard rm /test/rootfs/witness-file |
|||
assert_success |
|||
run qemu_exec with-fs rm /test/virtiofs/witness-file |
|||
assert_success |
|||
run qemu_exec with-zvol rm /test/zvol/witness-file |
|||
assert_success |
|||
|
|||
# Sync all filesystems |
|||
run qemu_exec standard sync |
|||
assert_success |
|||
run qemu_exec with-fs sync |
|||
assert_success |
|||
run qemu_exec with-zvol sync |
|||
assert_success |
|||
|
|||
# Verify that the witness files have been deleted in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_failure |
|||
|
|||
# Stop all domains |
|||
run virsh destroy standard |
|||
assert_success |
|||
run virsh destroy with-fs |
|||
assert_success |
|||
run virsh destroy with-zvol |
|||
assert_success |
|||
|
|||
# Revert snapshots in batch mode |
|||
run zvirt revert -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Check all domains are running again |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Verify that the witness files still exist after revert |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
} |
|||
|
|||
@test "zvirt: take crash-consistent snapshot without batch mode" { |
|||
# Create witness files in all three domains before taking snapshots |
|||
qemu_exec standard touch /test/rootfs/witness-file |
|||
qemu_exec with-fs touch /test/virtiofs/witness-file |
|||
qemu_exec with-zvol touch /test/zvol/witness-file |
|||
|
|||
# Verify that the witness files exist in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_success |
|||
|
|||
# Take crash-consistent snapshots for all three domains |
|||
run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Verify that the domains are still running |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Assert that the files created before the snapshot exist |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- backup1 |
|||
Snapshots for domain 'with-zvol': |
|||
- backup1 |
|||
Snapshots for domain 'with-fs': |
|||
- backup1" |
|||
|
|||
# Attempt to take the same snapshot again and expect failure |
|||
run zvirt snapshot -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_failure |
|||
assert_output --partial "Snapshot 'backup1' already exists." |
|||
assert_output --partial "standard:" |
|||
assert_output --partial "with-zvol:" |
|||
assert_output --partial "with-fs:" |
|||
assert_output --partial "Pre-flight checks failed." |
|||
|
|||
# Delete the witness files |
|||
run qemu_exec standard rm /test/rootfs/witness-file |
|||
assert_success |
|||
run qemu_exec with-fs rm /test/virtiofs/witness-file |
|||
assert_success |
|||
run qemu_exec with-zvol rm /test/zvol/witness-file |
|||
assert_success |
|||
|
|||
# Sync all filesystems |
|||
run qemu_exec standard sync |
|||
assert_success |
|||
run qemu_exec with-fs sync |
|||
assert_success |
|||
run qemu_exec with-zvol sync |
|||
assert_success |
|||
|
|||
# Wait a moment to ensure all writes are flushed |
|||
sleep 2 |
|||
|
|||
# Verify that the witness files have been deleted in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_failure |
|||
|
|||
# Stop all domains |
|||
run virsh destroy standard |
|||
assert_success |
|||
run virsh destroy with-fs |
|||
assert_success |
|||
run virsh destroy with-zvol |
|||
assert_success |
|||
|
|||
# Revert snapshots in batch mode |
|||
run zvirt revert -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Check all domains have been shut off |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "shut off" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "shut off" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "shut off" |
|||
|
|||
# Start all domains |
|||
run virsh start standard |
|||
assert_success |
|||
run virsh start with-fs |
|||
assert_success |
|||
run virsh start with-zvol |
|||
assert_success |
|||
|
|||
# Wait for all domains to be fully ready |
|||
readiness_wait |
|||
|
|||
# Verify that the witness files still exist after revert |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
} |
|||
|
|||
@test "zvirt: take crash-consistent snapshot with batch mode" { |
|||
# Create witness files in all three domains before taking snapshots |
|||
qemu_exec standard touch /test/rootfs/witness-file |
|||
qemu_exec with-fs touch /test/virtiofs/witness-file |
|||
qemu_exec with-zvol touch /test/zvol/witness-file |
|||
|
|||
# Verify that the witness files exist in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_success |
|||
|
|||
# Take crash-consistent snapshots for all three domains |
|||
run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Verify that the domains are still running |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Assert that the files created before the snapshot exist |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
|
|||
# List snapshots and verify their existence |
|||
run zvirt list -d standard -d with-zvol -d with-fs |
|||
assert_success |
|||
assert_output "Snapshots for domain 'standard': |
|||
- backup1 |
|||
Snapshots for domain 'with-zvol': |
|||
- backup1 |
|||
Snapshots for domain 'with-fs': |
|||
- backup1" |
|||
|
|||
# Attempt to take the same snapshot again and expect failure |
|||
run zvirt snapshot -b -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_failure |
|||
assert_output --partial "Snapshot 'backup1' already exists." |
|||
assert_output --partial "standard:" |
|||
assert_output --partial "with-zvol:" |
|||
assert_output --partial "with-fs:" |
|||
assert_output --partial "Pre-flight checks failed." |
|||
|
|||
# Delete the witness files |
|||
run qemu_exec standard rm /test/rootfs/witness-file |
|||
assert_success |
|||
run qemu_exec with-fs rm /test/virtiofs/witness-file |
|||
assert_success |
|||
run qemu_exec with-zvol rm /test/zvol/witness-file |
|||
assert_success |
|||
|
|||
# Sync all filesystems |
|||
run qemu_exec standard sync |
|||
assert_success |
|||
run qemu_exec with-fs sync |
|||
assert_success |
|||
run qemu_exec with-zvol sync |
|||
assert_success |
|||
|
|||
# Wait a moment to ensure all writes are flushed |
|||
sleep 2 |
|||
|
|||
# Verify that the witness files have been deleted in the virtiofs host mount |
|||
run test -f /srv/with-fs/witness-file |
|||
assert_failure |
|||
|
|||
# Stop all domains |
|||
run virsh destroy standard |
|||
assert_success |
|||
run virsh destroy with-fs |
|||
assert_success |
|||
run virsh destroy with-zvol |
|||
assert_success |
|||
|
|||
# Revert snapshots in batch mode |
|||
run zvirt revert -b -d standard -d with-zvol -d with-fs -s backup1 |
|||
assert_success |
|||
|
|||
# Check all domains are running again |
|||
run virsh domstate standard |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-fs |
|||
assert_success |
|||
assert_output "running" |
|||
run virsh domstate with-zvol |
|||
assert_success |
|||
assert_output "running" |
|||
|
|||
# Wait for all domains to be fully ready |
|||
readiness_wait |
|||
|
|||
# Verify that the witness files still exist after revert |
|||
run qemu_exec standard ls -1 /test/rootfs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-fs ls -1 /test/virtiofs |
|||
assert_success |
|||
assert_output "witness-file" |
|||
run qemu_exec with-zvol ls -1 /test/zvol |
|||
assert_success |
|||
assert_output "witness-file" |
|||
} |
|||
@ -1 +0,0 @@ |
|||
Subproject commit 697471b7a89d3ab38571f38c6c7c4b460d1f5e35 |
|||
@ -1 +0,0 @@ |
|||
Subproject commit 48fce74482a4d2bb879b904ccab31b6bc98e3224 |
|||
@ -1 +0,0 @@ |
|||
Subproject commit 0954abb9925cad550424cebca2b99255d4eabe96 |
|||
@ -1,885 +0,0 @@ |
|||
#!/usr/bin/env bats |
|||
|
|||
setup() { |
|||
bats_load_library 'bats-support' |
|||
bats_load_library 'bats-assert' |
|||
bats_load_library 'bats-mock' |
|||
|
|||
# Load the core library and export its functions |
|||
local fn_before="$(declare -F | cut -d ' ' -f 3 | sort)" |
|||
set -Eeuo pipefail |
|||
source "${BATS_TEST_DIRNAME}/../../src/lib/zvirt/core.sh" |
|||
local fn_after="$(declare -F | cut -d ' ' -f 3 | sort)" |
|||
declare -a zvirt_fn=( $(comm -13 <(echo "$fn_before") <(echo "$fn_after")) ) |
|||
for fn in "${zvirt_fn[@]}"; do |
|||
export -f "${fn}" |
|||
done |
|||
|
|||
# Helper to run commands in a separate bash process with the proper flags |
|||
# and with access to the domain_params_cache associative array |
|||
in_bash() { |
|||
local vars="" |
|||
for var in domain_params_cache snapshot_name domains verbose action batch live keep; do |
|||
if declare -p "${var}" &>/dev/null; then |
|||
vars+="$(declare -p "${var}") ; " |
|||
fi |
|||
done |
|||
bash -Eeuo pipefail -c "init_global_variables ; $vars \"\$@\"" zvirt "$@" |
|||
} |
|||
|
|||
} |
|||
|
|||
@test "domain_state: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
[[ "$*" == "domstate foo" ]] && echo "running" |
|||
} |
|||
export -f virsh |
|||
|
|||
# Run the test |
|||
run in_bash domain_state "foo" |
|||
assert_success |
|||
assert_output "running" |
|||
} |
|||
|
|||
@test "domain_exists: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
[[ "$*" == "dominfo foo" ]] && return 0 |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
|
|||
# Run the test |
|||
run in_bash domain_exists "foo" |
|||
assert_success |
|||
run in_bash domain_exists "bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "get_zfs_datasets_from_domain: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
if [[ "$*" == "domblklist foo --details" ]]; then |
|||
cat <<-EOF |
|||
Type Device Target Source |
|||
------------------------------------------------------------------------ |
|||
file disk vda /var/lib/libvirt/images/foo/root.img |
|||
file disk vdb /var/lib/libvirt/images/foo/data.img |
|||
block disk vdc /dev/zvol/data/domains/foo/data-vol |
|||
block disk vdd /dev/sda1 |
|||
EOF |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
df() { |
|||
if [[ "$*" == "--output=source /var/lib/libvirt/images/foo/root.img" ]] || [[ "$*" == "--output=source /var/lib/libvirt/images/foo/data.img" ]]; then |
|||
echo Filesystem |
|||
echo "/var/lib/libvirt/images/foo" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh df |
|||
|
|||
# Run the test |
|||
run in_bash get_zfs_datasets_from_domain "foo" |
|||
assert_output "/var/lib/libvirt/images/foo" |
|||
assert_success |
|||
|
|||
run in_bash get_zfs_datasets_from_domain "bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "get_zfs_zvols_from_domain: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
if [[ "$*" == "domblklist foo --details" ]]; then |
|||
cat <<-EOF |
|||
Type Device Target Source |
|||
------------------------------------------------------------------------ |
|||
file disk vda /var/lib/libvirt/images/foo/root.img |
|||
file disk vdb /var/lib/libvirt/images/foo/data.img |
|||
block disk vdc /dev/zvol/data/domains/foo/data-vol |
|||
block disk vdd /dev/sda1 |
|||
EOF |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
|
|||
# Run the test |
|||
run in_bash get_zfs_zvols_from_domain "foo" |
|||
assert_output "data/domains/foo/data-vol" |
|||
assert_success |
|||
|
|||
run in_bash get_zfs_zvols_from_domain "bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "get_zfs_snapshots_from_dataset: nominal case" { |
|||
# Mock the underlying tools |
|||
zfs() { |
|||
if [[ "$*" == "list -H -t snapshot -o name data/domains/foo" ]]; then |
|||
cat <<-EOF |
|||
data/domains/foo@snapshot1 |
|||
data/domains/foo/virtiofs@snapshot1 |
|||
data/domains/foo@snapshot2 |
|||
data/domains/foo/virtiofs@snapshot2 |
|||
EOF |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f zfs |
|||
|
|||
# Run the test |
|||
run in_bash get_zfs_snapshots_from_dataset "data/domains/foo" |
|||
assert_output "snapshot1 |
|||
snapshot2" |
|||
assert_success |
|||
|
|||
run in_bash get_zfs_snapshots_from_dataset "data/domains/bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "get_zfs_dataset_mountpoint: nominal case" { |
|||
# Mock the underlying tools |
|||
zfs() { |
|||
if [[ "$*" == "get -H -o value mountpoint data/domains/foo" ]]; then |
|||
echo "/var/lib/libvirt/images/foo" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f zfs |
|||
|
|||
# Run the test |
|||
run in_bash get_zfs_dataset_mountpoint "data/domains/foo" |
|||
assert_output "/var/lib/libvirt/images/foo" |
|||
assert_success |
|||
|
|||
run in_bash get_zfs_dataset_mountpoint "data/domains/bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "has_save_file: nominal case" { |
|||
# Temporary directory for save files |
|||
local temp_dir="$(mktemp -d)" |
|||
mkdir -p "$temp_dir/foo" "$temp_dir/bar" |
|||
# Only foo has a save file |
|||
touch "$temp_dir/foo/domain.save" |
|||
|
|||
# Fill up the cache |
|||
declare -A domain_params_cache=( ["foo/mountpoint"]="$temp_dir/foo" ["bar/mountpoint"]="$temp_dir/bar" ) |
|||
|
|||
# Run the test |
|||
run in_bash has_save_file foo |
|||
assert_success |
|||
run in_bash has_save_file bar |
|||
assert_failure |
|||
} |
|||
|
|||
@test "take_live_snapshot: nominal case" { |
|||
# Mock the underlying tools |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) |
|||
virsh_mock="$(mock_create)" |
|||
virsh() { |
|||
if [[ "$*" == "save foo /var/lib/libvirt/images/foo/domain.save --running --verbose --image-format raw" ]]; then |
|||
$virsh_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
zfs_mock="$(mock_create)" |
|||
zfs() { |
|||
if [[ "$*" == "snapshot -r data/domains/foo@backup1" ]]; then |
|||
$zfs_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh zfs |
|||
export virsh_mock zfs_mock |
|||
|
|||
# Run the test |
|||
run in_bash take_live_snapshot foo backup1 |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] |
|||
[[ "$(mock_get_call_num ${zfs_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "take_crash_consistent_snapshot: nominal case" { |
|||
# Mock the underlying tools |
|||
declare -A domain_params_cache=( ["bar/state"]="running" ["bar/dataset"]="data/domains/bar" ["bar/mountpoint"]="/var/lib/libvirt/images/bar" ["bar/zvols"]="" ) |
|||
zfs_mock="$(mock_create)" |
|||
zfs() { |
|||
if [[ "$*" == "snapshot -r data/domains/bar@backup2" ]]; then |
|||
$zfs_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f zfs |
|||
export zfs_mock |
|||
|
|||
# Run the test |
|||
run in_bash take_crash_consistent_snapshot bar backup2 |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "revert_snapshot: nominal case" { |
|||
# Mock the underlying tools |
|||
verbose=1 |
|||
declare -A domain_params_cache=( ["baz/state"]="running" ["baz/dataset"]="data/domains/baz" ["baz/mountpoint"]="/var/lib/libvirt/images/baz" ["baz/zvols"]="" ) |
|||
zfs_mock="$(mock_create)" |
|||
zfs() { |
|||
rollback_pattern="^rollback -Rrf data/domains/baz(/virtiofs)?@backup3$" |
|||
if [[ "$*" == "list -H -r -o name data/domains/baz" ]]; then |
|||
echo "data/domains/baz |
|||
data/domains/baz/virtiofs" |
|||
return 0 |
|||
elif [[ "$*" =~ $rollback_pattern ]]; then |
|||
$zfs_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f zfs |
|||
export zfs_mock |
|||
|
|||
# Run the test |
|||
run in_bash revert_snapshot baz backup3 |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_mock})" -eq 2 ]] |
|||
} |
|||
|
|||
@test "restore_domain: batch mode" { |
|||
# Mock the underlying tools |
|||
batch=1 |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) |
|||
virsh_mock="$(mock_create)" |
|||
virsh() { |
|||
if [[ "$*" == "restore /var/lib/libvirt/images/foo/domain.save --paused" ]]; then |
|||
$virsh_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
export virsh_mock |
|||
|
|||
# Run the test |
|||
run in_bash restore_domain foo |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "restore_domain: nominal case" { |
|||
# Mock the underlying tools |
|||
batch=0 |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) |
|||
virsh_mock="$(mock_create)" |
|||
virsh() { |
|||
if [[ "$*" == "restore /var/lib/libvirt/images/foo/domain.save --running" ]]; then |
|||
$virsh_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
export virsh_mock |
|||
|
|||
# Run the test |
|||
run in_bash restore_domain foo |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "pause_all_domains: nominal case" { |
|||
# Mock the underlying tools |
|||
local domains=( "foo" "bar" ) |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
virsh_mock="$(mock_create)" |
|||
virsh() { |
|||
if [[ "$*" == "suspend foo" ]]; then |
|||
$virsh_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
export virsh_mock |
|||
|
|||
# Run the test |
|||
run in_bash pause_all_domains "${domains[@]}" |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "resume_all_domains: nominal case" { |
|||
# Mock the underlying tools |
|||
local domains=( "foo" "bar" ) |
|||
virsh_mock="$(mock_create)" |
|||
virsh() { |
|||
if [[ "$*" == "resume foo" ]] || [[ "$*" == "start bar" ]]; then |
|||
$virsh_mock "$@" |
|||
return $? |
|||
fi |
|||
if [[ "$*" == "domstate foo" ]]; then |
|||
echo "paused" |
|||
return 0 |
|||
elif [[ "$*" == "domstate bar" ]]; then |
|||
echo "shut off" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
export virsh_mock |
|||
|
|||
# Run the test |
|||
run in_bash resume_all_domains "${domains[@]}" |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${virsh_mock})" -eq 2 ]] |
|||
} |
|||
|
|||
@test "fsthaw_all_domains: nominal case" { |
|||
# Mock the underlying tools |
|||
local domains=( "foo" "bar" ) |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
fsthaw_mock="$(mock_create)" |
|||
fsthaw_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
$fsthaw_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f fsthaw_domain |
|||
export fsthaw_mock |
|||
|
|||
# Run the test |
|||
run in_bash fsthaw_all_domains "${domains[@]}" |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${fsthaw_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "fsfreeze_all_domains: nominal case" { |
|||
# Mock the underlying tools |
|||
local domains=( "foo" "bar" ) |
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
fsfreeze_mock="$(mock_create)" |
|||
fsfreeze_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
$fsfreeze_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f fsfreeze_domain |
|||
export fsfreeze_mock |
|||
|
|||
# Run the test |
|||
run in_bash fsfreeze_all_domains "${domains[@]}" |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${fsfreeze_mock})" -eq 1 ]] |
|||
} |
|||
|
|||
@test "fsthaw_domain: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
[[ "$*" == "domfsthaw foo" ]] && return 0 |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
|
|||
# Run the test |
|||
run in_bash virsh domfsthaw "foo" |
|||
assert_success |
|||
run in_bash virsh domfsthaw "bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "fsfreeze_domain: nominal case" { |
|||
# Mock the underlying tools |
|||
virsh() { |
|||
[[ "$*" == "domfsfreeze foo" ]] && return 0 |
|||
return 1 |
|||
} |
|||
export -f virsh |
|||
|
|||
# Run the test |
|||
run in_bash virsh domfsfreeze "foo" |
|||
assert_success |
|||
run in_bash virsh domfsfreeze "bar" |
|||
assert_failure |
|||
} |
|||
|
|||
@test "domain_checks: nominal case" { |
|||
# Mock the underlying tools |
|||
domain_exists() { |
|||
if [[ "$*" == "foo" ]] || [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
domain_state() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
echo "running" |
|||
return 0 |
|||
elif [[ "$*" == "bar" ]]; then |
|||
echo "shut off" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
get_zfs_datasets_from_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
echo "data/domains/foo" |
|||
return 0 |
|||
elif [[ "$*" == "bar" ]]; then |
|||
echo "data/domains/bar" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
get_zfs_zvols_from_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
elif [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
get_zfs_snapshots_from_dataset() { |
|||
if [[ "$*" == "data/domains/foo" ]]; then |
|||
echo "backup1" |
|||
return 0 |
|||
elif [[ "$*" == "data/domains/bar" ]]; then |
|||
echo "backup1" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
get_zfs_dataset_mountpoint() { |
|||
if [[ "$*" == "data/domains/foo" ]]; then |
|||
echo "/var/lib/libvirt/images/foo" |
|||
return 0 |
|||
elif [[ "$*" == "data/domains/bar" ]]; then |
|||
echo "/var/lib/libvirt/images/bar" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f domain_exists domain_state get_zfs_datasets_from_domain get_zfs_zvols_from_domain get_zfs_snapshots_from_dataset get_zfs_dataset_mountpoint |
|||
|
|||
# Run the test |
|||
run in_bash domain_checks snapshot foo backup2 |
|||
assert_success |
|||
run in_bash domain_checks revert bar backup1 |
|||
assert_success |
|||
|
|||
# Live mode with existing save file |
|||
has_save_file() { |
|||
return 0 |
|||
} |
|||
export -f has_save_file |
|||
live=1 |
|||
run in_bash domain_checks snapshot foo backup2 |
|||
assert_failure |
|||
|
|||
# Live mode with non-existing save file |
|||
has_save_file() { |
|||
return 1 |
|||
} |
|||
export -f has_save_file |
|||
live=1 |
|||
run in_bash domain_checks snapshot foo backup2 |
|||
assert_success |
|||
} |
|||
|
|||
@test "list_snapshots: nominal case" { |
|||
# Mock the underlying tools |
|||
declare -A domain_params_cache=( ["foo/snapshots"]="snapshot1 snapshot2" ["bar/snapshots"]="snapshot3 snapshot4" ) |
|||
|
|||
# Run the test |
|||
run in_bash list_snapshots foo |
|||
assert_success |
|||
assert_output "Snapshots for domain 'foo': |
|||
- snapshot1 |
|||
- snapshot2" |
|||
} |
|||
|
|||
@test "prune_snapshots: nominal case" { |
|||
# Mock the underlying tools |
|||
declare -A domain_params_cache=( ["foo/snapshots"]="s1 s2 s3 s4 s5" ["bar/snapshots"]="s1 s2 s3 s4 s5" ["baz/snapshots"]="s1" ["foo/dataset"]="data/domains/foo" ["bar/dataset"]="data/domains/bar" ["baz/dataset"]="data/domains/baz" ) |
|||
zfs_destroy_mock="$(mock_create)" |
|||
zfs() { |
|||
if [[ "$*" == "destroy -r data/domains/foo@%s3" ]] || [[ "$*" == "destroy -r data/domains/bar@%s2" ]]; then |
|||
$zfs_destroy_mock "$@" |
|||
return $? |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f zfs |
|||
export zfs_destroy_mock |
|||
|
|||
# Run the test |
|||
keep=2 |
|||
run in_bash prune_snapshots foo |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_destroy_mock})" -eq 1 ]] # Deletion up to s3 |
|||
|
|||
keep=3 |
|||
run in_bash prune_snapshots bar |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_destroy_mock})" -eq 2 ]] # Deletion up to s2 |
|||
|
|||
keep=5 |
|||
run in_bash prune_snapshots bar |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_destroy_mock})" -eq 2 ]] # No deletion should occur |
|||
|
|||
keep=1 |
|||
run in_bash prune_snapshots baz |
|||
assert_success |
|||
[[ "$(mock_get_call_num ${zfs_destroy_mock})" -eq 2 ]] # No deletion should occur |
|||
} |
|||
|
|||
@test "preflight_checks: nominal case" { |
|||
# Mock the underlying tools |
|||
domain_checks() { |
|||
if [[ "$*" == "snapshot foo backup2" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f domain_checks |
|||
|
|||
# Run the test |
|||
run in_bash preflight_checks snapshot backup2 foo |
|||
assert_success |
|||
} |
|||
|
|||
@test "take_snapshots: batch=0, live=0" { |
|||
# Mock the underlying tools |
|||
take_crash_consistent_snapshot() { |
|||
regex="^(foo|bar) backup$" |
|||
if [[ "$*" =~ $regex ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
pause_all_domains() { return 1; } |
|||
take_live_snapshot() { return 1; } |
|||
restore_domain() { return 1; } |
|||
resume_all_domains() { return 1; } |
|||
remove_save_file() { return 1; } |
|||
fsfreeze_all_domains() { return 1; } |
|||
fsthaw_all_domains() { return 1; } |
|||
fsfreeze_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
fsthaw_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain |
|||
|
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=0 |
|||
live=0 |
|||
run in_bash take_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash take_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@test "take_snapshots: batch=1, live=0" { |
|||
# Mock the underlying tools |
|||
take_crash_consistent_snapshot() { |
|||
regex="^(foo|bar) backup$" |
|||
if [[ "$*" =~ $regex ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
pause_all_domains() { return 1; } |
|||
take_live_snapshot() { return 1; } |
|||
restore_domain() { return 1; } |
|||
resume_all_domains() { return 1; } |
|||
remove_save_file() { return 1; } |
|||
fsfreeze_all_domains() { |
|||
if [[ "$*" == "foo bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
fsthaw_all_domains() { |
|||
if [[ "$*" == "foo bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
fsfreeze_domain() { return 1; } |
|||
fsthaw_domain() { return 1; } |
|||
export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain |
|||
|
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=1 |
|||
live=0 |
|||
run in_bash take_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash take_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@test "take_snapshots: batch=0, live=1" { |
|||
# Mock the underlying tools |
|||
take_crash_consistent_snapshot() { |
|||
if [[ "$*" == "bar backup" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
pause_all_domains() { return 1; } |
|||
take_live_snapshot() { |
|||
if [[ "$*" == "foo backup" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
restore_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
resume_all_domains() { return 1; } |
|||
remove_save_file() { return 1; } |
|||
fsfreeze_all_domains() { return 1; } |
|||
fsthaw_all_domains() { return 1; } |
|||
fsfreeze_domain() { |
|||
if [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
fsthaw_domain() { |
|||
if [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain |
|||
|
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=0 |
|||
live=1 |
|||
run in_bash take_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash take_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@test "take_snapshots: batch=1, live=1" { |
|||
# Mock the underlying tools |
|||
take_crash_consistent_snapshot() { |
|||
if [[ "$*" == "bar backup" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
pause_all_domains() { |
|||
if [[ "$*" == "foo bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
take_live_snapshot() { |
|||
if [[ "$*" == "foo backup" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
restore_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
resume_all_domains() { |
|||
if [[ "$*" == "foo bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
remove_save_file() { return 1; } |
|||
fsfreeze_all_domains() { return 1; } |
|||
fsthaw_all_domains() { return 1; } |
|||
fsfreeze_domain() { |
|||
if [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
fsthaw_domain() { |
|||
if [[ "$*" == "bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains remove_save_file fsfreeze_all_domains fsthaw_all_domains fsfreeze_domain fsthaw_domain |
|||
|
|||
declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=0 |
|||
live=1 |
|||
run in_bash take_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash take_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@test "revert_snapshots: batch=0" { |
|||
# Mock the underlying tools |
|||
revert_snapshot() { |
|||
regex="^(foo|bar) backup$" |
|||
if [[ "$*" =~ $regex ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
restore_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
resume_all_domains() { return 1; } |
|||
has_save_file() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
remove_save_file() { return 1; } |
|||
domain_state() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
echo "paused" |
|||
return 0 |
|||
elif [[ "$*" == "bar" ]]; then |
|||
echo "shut off" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f revert_snapshot restore_domain resume_all_domains has_save_file remove_save_file domain_state |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=0 |
|||
run in_bash revert_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash revert_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@test "revert_snapshots: batch=1" { |
|||
# Mock the underlying tools |
|||
revert_snapshot() { |
|||
regex="^(foo|bar) backup$" |
|||
if [[ "$*" =~ $regex ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
restore_domain() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
resume_all_domains() { |
|||
if [[ "$*" == "foo bar" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
has_save_file() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
remove_save_file() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
domain_state() { |
|||
if [[ "$*" == "foo" ]]; then |
|||
echo "paused" |
|||
return 0 |
|||
elif [[ "$*" == "bar" ]]; then |
|||
echo "shut off" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
export -f revert_snapshot restore_domain resume_all_domains has_save_file remove_save_file domain_state |
|||
|
|||
# Run the test |
|||
domains=( "foo" "bar" ) |
|||
snapshot_name="backup" |
|||
batch=1 |
|||
run in_bash revert_snapshots |
|||
assert_success |
|||
|
|||
# Add a non-existing domain to the list |
|||
domains+=( "baz" ) |
|||
run in_bash revert_snapshots |
|||
assert_failure |
|||
} |
|||
|
|||
@ -1,86 +0,0 @@ |
|||
#!/usr/bin/env bats |
|||
|
|||
setup() { |
|||
bats_load_library 'bats-support' |
|||
bats_load_library 'bats-assert' |
|||
|
|||
set -Eeuo pipefail |
|||
source "${BATS_TEST_DIRNAME}/../../src/lib/zvirt/core.sh" |
|||
|
|||
function call_parse_args () { |
|||
init_global_variables |
|||
parse_args "$@" |
|||
ret=$? |
|||
declare -p action batch live verbose domains snapshot_name keep |
|||
return $ret |
|||
} |
|||
} |
|||
|
|||
@test "call_parse_args: show help and exit" { |
|||
run call_parse_args -h |
|||
assert_success |
|||
assert_output --partial "Usage:" |
|||
} |
|||
|
|||
@test "call_parse_args: no action provided" { |
|||
run call_parse_args |
|||
assert_failure |
|||
assert_output --partial "Unsupported action" |
|||
} |
|||
|
|||
@test "call_parse_args: list snapshots for a single domain" { |
|||
run call_parse_args list -d foo |
|||
assert_success |
|||
assert_output --partial 'action="list"' |
|||
assert_output --partial 'domains=([0]="foo")' |
|||
} |
|||
|
|||
@test "call_parse_args: take a snapshot for two domains in batch mode" { |
|||
run call_parse_args snapshot -b -d foo -d bar -s backup1 -l |
|||
assert_success |
|||
assert_output --partial 'action="snapshot"' |
|||
assert_output --partial 'batch="1"' |
|||
assert_output --partial 'domains=([0]="foo" [1]="bar")' |
|||
assert_output --partial 'snapshot_name="backup1"' |
|||
assert_output --partial 'live="1"' |
|||
} |
|||
|
|||
@test "call_parse_args: take a crash-consistent snapshot for two domains" { |
|||
run call_parse_args snapshot -d foo -d bar -s backup2 |
|||
assert_success |
|||
assert_output --partial 'action="snapshot"' |
|||
assert_output --partial 'batch="0"' |
|||
assert_output --partial 'domains=([0]="foo" [1]="bar")' |
|||
assert_output --partial 'snapshot_name="backup2"' |
|||
assert_output --partial 'live="0"' |
|||
} |
|||
|
|||
@test "call_parse_args: revert snapshot for a domain" { |
|||
run call_parse_args revert -d foo -s backup2 |
|||
assert_success |
|||
assert_output --partial 'action="revert"' |
|||
assert_output --partial 'batch="0"' |
|||
assert_output --partial 'domains=([0]="foo")' |
|||
assert_output --partial 'snapshot_name="backup2"' |
|||
assert_output --partial 'live="0"' |
|||
} |
|||
|
|||
@test "call_parse_args: prune snapshots for all domains" { |
|||
virsh() { |
|||
if [[ "$*" == "list --all --name" ]]; then |
|||
echo -e "foo\nbar" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
run call_parse_args prune -k 5 |
|||
assert_success |
|||
assert_output --partial 'action="prune"' |
|||
assert_output --partial 'domains=([0]="foo" [1]="bar")' |
|||
assert_output --partial 'keep="5"' |
|||
|
|||
run call_parse_args prune |
|||
assert_failure |
|||
assert_output --partial "The -k option with a positive integer value must be specified for the 'prune' action" |
|||
} |
|||
Loading…
Reference in new issue