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