libvirt/tools/libvirt-guests.sh.in

623 lines
16 KiB
Bash

#!/bin/sh
# Copyright (C) 2011-2014 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see
# <http://www.gnu.org/licenses/>.
sysconfdir="@sysconfdir@"
localstatedir="@localstatedir@"
initconfdir="@initconfdir@"
# Source function library.
test ! -r "$sysconfdir"/rc.d/init.d/functions ||
. "$sysconfdir"/rc.d/init.d/functions
# Source gettext library.
# Make sure this file is recognized as having translations: _("dummy")
. "@bindir@"/gettext.sh
export TEXTDOMAIN="@PACKAGE@" TEXTDOMAINDIR="@localedir@"
URIS="default"
ON_BOOT="start"
ON_SHUTDOWN="suspend"
SHUTDOWN_TIMEOUT=300
PARALLEL_SHUTDOWN=0
START_DELAY=0
BYPASS_CACHE=0
SYNC_TIME=0
test -f "$initconfdir"/libvirt-guests &&
. "$initconfdir"/libvirt-guests
LISTFILE="$localstatedir"/lib/libvirt/libvirt-guests
VAR_SUBSYS_LIBVIRT_GUESTS="$localstatedir"/lock/subsys/libvirt-guests
RETVAL=0
# retval COMMAND ARGUMENTS...
# run command with arguments and convert non-zero return value to 1 and set
# the global return variable
retval() {
"$@"
if [ $? -ne 0 ]; then
RETVAL=1
return 1
else
return 0
fi
}
# run_virsh URI ARGUMENTS...
# start virsh and let it execute ARGUMENTS on URI
# If URI is "default" virsh is called without the "-c" argument
# (using libvirt's default connection)
run_virsh() {
local uri="$1"
shift
if [ "x$uri" = xdefault ]; then
virsh "$@" </dev/null
else
virsh -c "$uri" "$@" </dev/null
fi
}
# run_virsh_c URI ARGUMENTS
# Same as "run_virsh" but the "C" locale is used instead of
# the system's locale.
run_virsh_c() {
( export LC_ALL=C; run_virsh "$@" )
}
# test_connect URI
# check if URI is reachable
test_connect()
{
local uri="$1"
if run_virsh "$uri" connect 2>/dev/null; then
return 0;
else
eval_gettext "Can't connect to \$uri. Skipping."
return 1
fi
}
# list_guests URI PERSISTENT
# List running guests on URI.
# PERSISTENT argument options:
# --persistent: list only persistent guests
# --transient: list only transient guests
# [none]: list both persistent and transient guests
list_guests() {
local uri="$1"
local persistent="$2"
local list="$(run_virsh_c "$uri" list --uuid $persistent)"
if [ $? -ne 0 ]; then
RETVAL=1
return 1
fi
echo "$list" | sed "/00000000-0000-0000-0000-000000000000/d"
}
# guest_name URI UUID
# return name of guest UUID on URI
guest_name() {
local uri="$1"
local uuid="$2"
run_virsh "$uri" domname "$uuid" 2>/dev/null
}
# guest_is_on URI UUID
# check if guest UUID on URI is running
# Result is returned by variable "guest_running"
guest_is_on() {
local uri="$1"
local uuid="$2"
local id="$(run_virsh "$uri" domid "$uuid")"
guest_running="false"
if [ $? -ne 0 ]; then
RETVAL=1
return 1
fi
[ -n "$id" ] && [ "x$id" != x- ] && guest_running="true"
return 0
}
# started
# Create the startup lock file
started() {
touch "$VAR_SUBSYS_LIBVIRT_GUESTS"
}
# start
# Start or resume the guests
start() {
local isfirst="true"
local bypass=
local sync_time="false"
local uri=
local list=
[ -f "$LISTFILE" ] || { started; return 0; }
if [ "x$ON_BOOT" != xstart ]; then
gettext "libvirt-guests is configured not to start any guests on boot"
echo
rm -f "$LISTFILE"
started
return 0
fi
test "x$BYPASS_CACHE" = x0 || bypass="--bypass-cache"
test "x$SYNC_TIME" = x0 || sync_time="true"
while read uri list; do
local configured="false"
local confuri=
local guest=
set -f
for confuri in $URIS; do
set +f
if [ "x$confuri" = "x$uri" ]; then
configured="true"
break
fi
done
set +f
if ! "$configured"; then
eval_gettext "Ignoring guests on \$uri URI"; echo
continue
fi
test_connect "$uri" || continue
eval_gettext "Resuming guests on \$uri URI..."; echo
for guest in $list; do
local name="$(guest_name "$uri" "$guest")"
eval_gettext "Resuming guest \$name: "
if guest_is_on "$uri" "$guest"; then
if "$guest_running"; then
gettext "already active"; echo
else
if "$isfirst"; then
isfirst="false"
else
sleep $START_DELAY
fi
retval run_virsh "$uri" start $bypass "$name" \
>/dev/null && \
gettext "done"; echo
fi
if "$sync_time"; then
run_virsh "$uri" domtime --sync "$name" >/dev/null
fi
fi
done
done <"$LISTFILE"
rm -f "$LISTFILE"
started
}
# suspend_guest URI GUEST
# Do a managed save on a GUEST on URI. This function returns after the guest
# was saved.
suspend_guest()
{
local uri="$1"
local guest="$2"
local name="$(guest_name "$uri" "$guest")"
local label="$(eval_gettext "Suspending \$name: ")"
local bypass=
local slept=0
test "x$BYPASS_CACHE" = x0 || bypass="--bypass-cache"
printf '%s...\n' "$label"
run_virsh "$uri" managedsave $bypass "$guest" >/dev/null &
local virsh_pid="$!"
while true; do
sleep 1
kill -0 "$virsh_pid" >/dev/null 2>&1 || break
slept=$(($slept + 1))
if [ $(($slept % 5)) -eq 0 ]; then
local progress="$(run_virsh_c "$uri" domjobinfo "$guest" 2>/dev/null | \
awk '/^Data processed:/{print $3, $4}')"
if [ -n "$progress" ]; then
printf '%s%s\n' "$label" "$progress"
else
printf '%s%s\n' "$label" "..."
fi
fi
done
retval wait "$virsh_pid" && printf '%s%s\n' "$label" "$(gettext "done")"
}
# shutdown_guest URI GUEST
# Start an ACPI shutdown of GUEST on URI. This function returns after the guest
# was successfully shutdown or the timeout defined by $SHUTDOWN_TIMEOUT expired.
shutdown_guest()
{
local uri="$1"
local guest="$2"
local name="$(guest_name "$uri" "$guest")"
local timeout="$SHUTDOWN_TIMEOUT"
local check_timeout="false"
local format=
local slept=
eval_gettext "Starting shutdown on guest: \$name"
echo
retval run_virsh "$uri" shutdown "$guest" >/dev/null || return
if [ $timeout -gt 0 ]; then
check_timeout="true"
format="$(eval_gettext "Waiting for guest %s to shut down, %d seconds left\n")"
else
slept=0
format="$(eval_gettext "Waiting for guest %s to shut down\n")"
fi
while ! $check_timeout || [ "$timeout" -gt 0 ]; do
sleep 1
guest_is_on "$uri" "$guest" || return
"$guest_running" || break
if $check_timeout; then
if [ $(($timeout % 5)) -eq 0 ]; then
printf "$format" "$name" "$timeout"
fi
timeout=$(($timeout - 1))
else
slept=$(($slept + 1))
if [ $(($slept % 5)) -eq 0 ]; then
printf "$format" "$name"
fi
fi
done
if guest_is_on "$uri" "$guest"; then
if "$guest_running"; then
eval_gettext "Shutdown of guest \$name failed to complete in time."
else
eval_gettext "Shutdown of guest \$name complete."
fi
echo
fi
}
# shutdown_guest_async URI GUEST
# Start a ACPI shutdown of GUEST on URI. This function returns after the command
# was issued to libvirt to allow parallel shutdown.
shutdown_guest_async()
{
local uri="$1"
local guest="$2"
local name="$(guest_name "$uri" "$guest")"
eval_gettext "Starting shutdown on guest: \$name"
echo
retval run_virsh "$uri" shutdown "$guest" > /dev/null
}
# guest_count GUEST_LIST
# Returns number of guests in GUEST_LIST
guest_count()
{
set -- $1
echo $#
}
# check_guests_shutdown URI GUESTS
# check if shutdown is complete on guests in "GUESTS" and returns only
# guests that are still shutting down
# Result is returned in "guests_shutting_down"
check_guests_shutdown()
{
local uri="$1"
local guests_to_check="$2"
local guest=
guests_shutting_down=
for guest in $guests_to_check; do
if ! guest_is_on "$uri" "$guest" >/dev/null 2>&1; then
eval_gettext "Failed to determine state of guest: \$guest. Not tracking it anymore."
echo
continue
fi
if "$guest_running"; then
guests_shutting_down="$guests_shutting_down $guest"
fi
done
}
# print_guests_shutdown URI BEFORE AFTER
# Checks for differences in the lists BEFORE and AFTER and prints
# a shutdown complete notice for guests that have finished
print_guests_shutdown()
{
local uri="$1"
local before="$2"
local after="$3"
local guest=
for guest in $before; do
case " $after " in
*" $guest "*) continue;;
esac
local name="$(guest_name "$uri" "$guest")"
if [ -n "$name" ]; then
eval_gettext "Shutdown of guest \$name complete."
echo
fi
done
}
# shutdown_guests_parallel URI GUESTS
# Shutdown guests GUESTS on machine URI in parallel
shutdown_guests_parallel()
{
local uri="$1"
local guests="$2"
local on_shutdown=
local check_timeout="false"
local timeout="$SHUTDOWN_TIMEOUT"
local slept=
local format=
if [ $timeout -gt 0 ]; then
check_timeout="true"
format="$(eval_gettext "Waiting for %d guests to shut down, %d seconds left\n")"
else
slept=0
format="$(eval_gettext "Waiting for %d guests to shut down\n")"
fi
while [ -n "$on_shutdown" ] || [ -n "$guests" ]; do
while [ -n "$guests" ] &&
[ $(guest_count "$on_shutdown") -lt "$PARALLEL_SHUTDOWN" ]; do
set -- $guests
local guest="$1"
shift
guests="$*"
if [ -z "$(echo $on_shutdown | grep $guest)" ] &&
[ -n "$(guest_name "$uri" "$guest")" ]; then
shutdown_guest_async "$uri" "$guest"
on_shutdown="$on_shutdown $guest"
fi
done
sleep 1
set -- $guests
local guestcount=$#
set -- $on_shutdown
local shutdowncount=$#
if $check_timeout; then
if [ $(($timeout % 5)) -eq 0 ]; then
printf "$format" $(($guestcount + $shutdowncount)) "$timeout"
fi
timeout=$(($timeout - 1))
if [ $timeout -le 0 ]; then
eval_gettext "Timeout expired while shutting down domains"; echo
RETVAL=1
return
fi
else
slept=$(($slept + 1))
if [ $(($slept % 5)) -eq 0 ]; then
printf "$format" $(($guestcount + $shutdowncount))
fi
fi
local on_shutdown_prev="$on_shutdown"
check_guests_shutdown "$uri" "$on_shutdown"
on_shutdown="$guests_shutting_down"
print_guests_shutdown "$uri" "$on_shutdown_prev" "$on_shutdown"
done
}
# stop
# Shutdown or save guests on the configured uris
stop() {
local suspending="true"
local uri=
# last stop was not followed by start
[ -f "$LISTFILE" ] && return 0
if [ "x$ON_SHUTDOWN" = xshutdown ]; then
suspending="false"
if [ $SHUTDOWN_TIMEOUT -lt 0 ]; then
gettext "SHUTDOWN_TIMEOUT must be equal or greater than 0"
echo
RETVAL=6
return
fi
fi
: >"$LISTFILE"
set -f
for uri in $URIS; do
set +f
test_connect "$uri" || continue
eval_gettext "Running guests on \$uri URI: "
local list="$(list_guests "$uri")"
if [ $? -eq 0 ]; then
local empty=true
for uuid in $list; do
"$empty" || printf ", "
printf %s "$(guest_name "$uri" "$uuid")"
empty="false"
done
if "$empty"; then
gettext "no running guests."
fi
echo
fi
if "$suspending"; then
local transient="$(list_guests "$uri" "--transient")"
if [ $? -eq 0 ]; then
local empty="true"
local uuid=
for uuid in $transient; do
if "$empty"; then
eval_gettext "Not suspending transient guests on URI: \$uri: "
empty="false"
else
printf ", "
fi
printf %s "$(guest_name "$uri" "$uuid")"
done
echo
# reload domain list to contain only persistent guests
list="$(list_guests "$uri" "--persistent")"
if [ $? -ne 0 ]; then
eval_gettext "Failed to list persistent guests on \$uri"
echo
RETVAL=1
set +f
return
fi
else
gettext "Failed to list transient guests"
echo
RETVAL=1
set +f
return
fi
fi
if [ -n "$list" ]; then
echo "$uri" $list >>"$LISTFILE"
fi
done
set +f
if [ -s "$LISTFILE" ]; then
while read uri list; do
if "$suspending"; then
eval_gettext "Suspending guests on \$uri URI..."; echo
else
eval_gettext "Shutting down guests on \$uri URI..."; echo
fi
if [ "$PARALLEL_SHUTDOWN" -gt 1 ] &&
! "$suspending"; then
shutdown_guests_parallel "$uri" "$list"
else
local guest=
for guest in $list; do
if "$suspending"; then
suspend_guest "$uri" "$guest"
else
shutdown_guest "$uri" "$guest"
fi
done
fi
done <"$LISTFILE"
else
rm -f "$LISTFILE"
fi
rm -f "$VAR_SUBSYS_LIBVIRT_GUESTS"
}
# gueststatus
# List status of guests
gueststatus() {
local uri=
set -f
for uri in $URIS; do
set +f
echo "* $uri URI:"
retval run_virsh "$uri" list | grep -v "Domain-0" || echo
done
set +f
}
# rh_status
# Display current status: whether saved state exists, and whether start
# has been executed. We cannot use status() from the functions library,
# since there is no external daemon process matching this init script.
rh_status() {
if [ -f "$LISTFILE" ]; then
gettext "stopped, with saved guests"; echo
RETVAL=3
else
if [ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ]; then
gettext "started"; echo
RETVAL=0
else
gettext "stopped, with no saved guests"; echo
RETVAL=3
fi
fi
}
# usage [val]
# Display usage string, then exit with VAL (defaults to 2).
usage() {
local program_name="$0"
eval_gettext "Usage: \$program_name {start|stop|status|restart|"\
"condrestart|try-restart|reload|force-reload|gueststatus|shutdown}"; echo
exit ${1-2}
}
# See how we were called.
if test $# != 1; then
usage
fi
case "$1" in
--help)
usage 0
;;
start|stop|gueststatus)
"$1"
;;
restart)
stop && start
;;
condrestart|try-restart)
[ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ] && stop && start
;;
reload|force-reload)
# Nothing to do; we reread configuration on each invocation
;;
status)
rh_status
;;
shutdown)
ON_SHUTDOWN="shutdown"
stop
;;
*)
usage
;;
esac
exit $RETVAL