libvirt-guests.sh.in 14.4 KB
Newer Older
1 2
#!/bin/sh

3 4 5
sysconfdir="@sysconfdir@"
localstatedir="@localstatedir@"
libvirtd="@sbindir@"/libvirtd
6 7

# Source function library.
8
test ! -r "$sysconfdir"/rc.d/init.d/functions ||
9 10 11 12 13 14 15
    . "$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@"
16 17 18 19

URIS=default
ON_BOOT=start
ON_SHUTDOWN=suspend
20 21
SHUTDOWN_TIMEOUT=300
PARALLEL_SHUTDOWN=0
22
START_DELAY=0
23
BYPASS_CACHE=0
24

25 26
test -f "$sysconfdir"/sysconfig/libvirt-guests &&
    . "$sysconfdir"/sysconfig/libvirt-guests
27 28

LISTFILE="$localstatedir"/lib/libvirt/libvirt-guests
J
Jiri Denemark 已提交
29
VAR_SUBSYS_LIBVIRT_GUESTS="$localstatedir"/lock/subsys/libvirt-guests
30 31 32

RETVAL=0

33 34 35
# retval COMMAND ARGUMENTS...
# run command with arguments and convert non-zero return value to 1 and set
# the global return variable
36 37 38 39 40 41 42 43 44 45
retval() {
    "$@"
    if [ $? -ne 0 ]; then
        RETVAL=1
        return 1
    else
        return 0
    fi
}

46 47 48 49
# 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)
50 51 52 53 54
run_virsh() {
    uri=$1
    shift

    if [ "x$uri" = xdefault ]; then
55
        virsh "$@" </dev/null
56
    else
57
        virsh -c "$uri" "$@" </dev/null
58 59 60
    fi
}

61 62 63
# run_virsh_c URI ARGUMENTS
# Same as "run_virsh" but the "C" locale is used instead of
# the system's locale.
64 65 66 67
run_virsh_c() {
    ( export LC_ALL=C; run_virsh "$@" )
}

68 69 70 71 72 73 74 75 76 77 78 79 80 81
# test_connect URI
# check if URI is reachable
test_connect()
{
    uri=$1

    run_virsh "$uri" connect 2>/dev/null
    if [ $? -ne 0 ]; then
        eval_gettext "Can't connect to \$uri. Skipping."
        echo
        return 1
    fi
}

82 83 84 85 86 87
# 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
88 89
list_guests() {
    uri=$1
90
    persistent=$2
91

92
    list=$(run_virsh_c "$uri" list --uuid $persistent)
93 94 95 96 97
    if [ $? -ne 0 ]; then
        RETVAL=1
        return 1
    fi

98
    echo $list
99 100
}

101 102
# guest_name URI UUID
# return name of guest UUID on URI
103 104 105 106
guest_name() {
    uri=$1
    uuid=$2

107
    run_virsh "$uri" domname "$uuid" 2>/dev/null
108 109
}

110 111 112
# guest_is_on URI UUID
# check if guest UUID on URI is running
# Result is returned by variable "guest_running"
113 114 115 116 117
guest_is_on() {
    uri=$1
    uuid=$2

    guest_running=false
118
    id=$(run_virsh "$uri" domid "$uuid")
119 120 121 122 123 124 125 126 127
    if [ $? -ne 0 ]; then
        RETVAL=1
        return 1
    fi

    [ -n "$id" ] && [ "x$id" != x- ] && guest_running=true
    return 0
}

128 129
# started
# Create the startup lock file
J
Jiri Denemark 已提交
130 131 132 133
started() {
    touch "$VAR_SUBSYS_LIBVIRT_GUESTS"
}

134 135
# start
# Start or resume the guests
136
start() {
J
Jiri Denemark 已提交
137
    [ -f "$LISTFILE" ] || { started; return 0; }
138 139

    if [ "x$ON_BOOT" != xstart ]; then
140 141
        gettext "libvirt-guests is configured not to start any guests on boot"
        echo
J
Jiri Denemark 已提交
142 143
        rm -f "$LISTFILE"
        started
144 145 146
        return 0
    fi

147
    isfirst=true
148 149
    bypass=
    test "x$BYPASS_CACHE" = x0 || bypass=--bypass-cache
150 151
    while read uri list; do
        configured=false
152
        set -f
153
        for confuri in $URIS; do
154
            set +f
155
            if [ "x$confuri" = "x$uri" ]; then
156 157 158 159
                configured=true
                break
            fi
        done
160
        set +f
161
        if ! "$configured"; then
162
            eval_gettext "Ignoring guests on \$uri URI"; echo
163 164 165
            continue
        fi

166 167
        test_connect "$uri" || continue

168
        eval_gettext "Resuming guests on \$uri URI..."; echo
169
        for guest in $list; do
170
            name=$(guest_name "$uri" "$guest")
171
            eval_gettext "Resuming guest \$name: "
172 173
            if guest_is_on "$uri" "$guest"; then
                if "$guest_running"; then
174
                    gettext "already active"; echo
175
                else
176 177 178 179 180
                    if "$isfirst"; then
                        isfirst=false
                    else
                        sleep $START_DELAY
                    fi
181 182
                    retval run_virsh "$uri" start $bypass "$name" \
                        >/dev/null && \
183
                    gettext "done"; echo
184 185 186
                fi
            fi
        done
J
Jiri Denemark 已提交
187
    done <"$LISTFILE"
188

J
Jiri Denemark 已提交
189 190
    rm -f "$LISTFILE"
    started
191 192
}

193 194 195
# suspend_guest URI GUEST
# Do a managed save on a GUEST on URI. This function returns after the guest
# was saved.
196 197 198 199 200
suspend_guest()
{
    uri=$1
    guest=$2

201
    name=$(guest_name "$uri" "$guest")
202
    label=$(eval_gettext "Suspending \$name: ")
203
    bypass=
204
    slept=0
205
    test "x$BYPASS_CACHE" = x0 || bypass=--bypass-cache
206
    printf '%s...\n' "$label"
207
    run_virsh "$uri" managedsave $bypass "$guest" >/dev/null &
208 209 210
    virsh_pid=$!
    while true; do
        sleep 1
211
        kill -0 "$virsh_pid" >/dev/null 2>&1 || break
212 213 214 215 216 217 218 219 220 221

        slept=$(($slept + 1))
        if [ $(($slept % 5)) -eq 0 ]; then
            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
222 223
        fi
    done
224
    retval wait "$virsh_pid" && printf '%s%s\n' "$label" "$(gettext "done")"
225 226
}

227 228 229
# shutdown_guest URI GUEST
# Start a ACPI shutdown of GUEST on URI. This function return after the quest
# was successfully shutdown or the timeout defined by $SHUTDOWN_TIMEOUT expires.
230 231 232 233 234
shutdown_guest()
{
    uri=$1
    guest=$2

235
    name=$(guest_name "$uri" "$guest")
236 237
    eval_gettext "Starting shutdown on guest: \$name"
    echo
238
    retval run_virsh "$uri" shutdown "$guest" >/dev/null || return
239
    timeout=$SHUTDOWN_TIMEOUT
240 241 242
    check_timeout=false
    if [ $timeout -gt 0 ]; then
        check_timeout=true
243 244 245 246
        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")
247 248
    fi
    while ! $check_timeout || [ "$timeout" -gt 0 ]; do
249
        sleep 1
250 251
        guest_is_on "$uri" "$guest" || return
        "$guest_running" || break
252

253
        if $check_timeout; then
254 255 256 257 258 259 260 261 262
            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
263
        fi
264 265
    done

266 267
    if guest_is_on "$uri" "$guest"; then
        if "$guest_running"; then
268
            eval_gettext "Shutdown of guest \$name failed to complete in time."
269
        else
270
            eval_gettext "Shutdown of guest \$name complete."
271
        fi
272
        echo
273 274 275
    fi
}

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
# 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()
{
    uri=$1
    guest=$2

    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
check_guests_shutdown()
{
    uri=$1
    guests=$2

    guests_up=
    for guest in $guests; 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_up="$guests_up $guest"
        fi
    done
    echo "$guests_up"
}

# 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()
{
    uri=$1
    before=$2
    after=$3

    for guest in $before; do
        case " $after " in
            *" $guest "*) continue;;
        esac

        name=$(guest_name "$uri" "$guest")
        eval_gettext "Shutdown of guest \$name complete."
        echo
    done
}

# shutdown_guests_parallel URI GUESTS
# Shutdown guests GUESTS on machine URI in parallel
shutdown_guests_parallel()
{
    uri=$1
    guests=$2

    on_shutdown=
    check_timeout=false
    timeout=$SHUTDOWN_TIMEOUT
    if [ $timeout -gt 0 ]; then
        check_timeout=true
352 353 354 355
        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")
356 357 358 359 360 361 362 363 364 365 366 367
    fi
    while [ -n "$on_shutdown" ] || [ -n "$guests" ]; do
        while [ -n "$guests" ] &&
              [ $(guest_count "$on_shutdown") -lt "$PARALLEL_SHUTDOWN" ]; do
            set -- $guests
            guest=$1
            shift
            guests=$*
            shutdown_guest_async "$uri" "$guest"
            on_shutdown="$on_shutdown $guest"
        done
        sleep 1
368 369 370 371 372 373

        set -- $guests
        guestcount=$#
        set -- $on_shutdown
        shutdowncount=$#

374
        if $check_timeout; then
375 376 377
            if [ $(($timeout % 5)) -eq 0 ]; then
                printf "$format" $(($guestcount + $shutdowncount)) "$timeout"
            fi
378 379 380 381 382 383
            timeout=$(($timeout - 1))
            if [ $timeout -le 0 ]; then
                eval_gettext "Timeout expired while shutting down domains"; echo
                RETVAL=1
                return
            fi
384 385 386 387 388
        else
            slept=$(($slept + 1))
            if [ $(($slept % 5)) -eq 0 ]; then
                printf "$format" $(($guestcount + $shutdowncount))
            fi
389
        fi
390

391 392 393 394 395 396
        on_shutdown_prev=$on_shutdown
        on_shutdown=$(check_guests_shutdown "$uri" "$on_shutdown")
        print_guests_shutdown "$uri" "$on_shutdown_prev" "$on_shutdown"
    done
}

397 398
# stop
# Shutdown or save guests on the configured uris
399 400
stop() {
    # last stop was not followed by start
J
Jiri Denemark 已提交
401
    [ -f "$LISTFILE" ] && return 0
402 403 404 405

    suspending=true
    if [ "x$ON_SHUTDOWN" = xshutdown ]; then
        suspending=false
406 407
        if [ $SHUTDOWN_TIMEOUT -lt 0 ]; then
            gettext "SHUTDOWN_TIMEOUT must be equal or greater than 0"
408
            echo
409 410 411 412 413
            RETVAL=6
            return
        fi
    fi

J
Jiri Denemark 已提交
414
    : >"$LISTFILE"
415
    set -f
416
    for uri in $URIS; do
417
        set +f
418

419 420 421
        test_connect "$uri" || continue

        eval_gettext "Running guests on \$uri URI: "
422

423
        list=$(list_guests "$uri")
424 425 426
        if [ $? -eq 0 ]; then
            empty=true
            for uuid in $list; do
427 428
                "$empty" || printf ", "
                printf %s "$(guest_name "$uri" "$uuid")"
429 430
                empty=false
            done
431

432
            if "$empty"; then
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
                gettext "no running guests."
            fi
            echo
        fi

        if "$suspending"; then
            transient=$(list_guests "$uri" "--transient")
            if [ $? -eq 0 ]; then
                empty=true
                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
461
            else
462
                gettext "Failed to list transient guests"
463
                echo
464 465 466
                RETVAL=1
                set +f
                return
467 468
            fi
        fi
469 470 471 472

        if [ -n "$list" ]; then
            echo "$uri" "$list" >>"$LISTFILE"
        fi
473
    done
474
    set +f
475

476 477 478 479 480 481 482
    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
483

484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
            if [ "$PARALLEL_SHUTDOWN" -gt 1 ] &&
               ! "$suspending"; then
                shutdown_guests_parallel "$uri" "$list"
            else
                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
J
Jiri Denemark 已提交
500 501

    rm -f "$VAR_SUBSYS_LIBVIRT_GUESTS"
502 503
}

504 505
# gueststatus
# List status of guests
506
gueststatus() {
507
    set -f
508
    for uri in $URIS; do
509
        set +f
510
        echo "* $uri URI:"
511
        retval run_virsh "$uri" list || echo
512
    done
513
    set +f
514 515
}

E
Eric Blake 已提交
516 517 518 519 520 521
# 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
522
        gettext "stopped, with saved guests"; echo
E
Eric Blake 已提交
523 524 525
        RETVAL=3
    else
        if [ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ]; then
526
            gettext "started"; echo
527
            RETVAL=0
E
Eric Blake 已提交
528
        else
529
            gettext "stopped, with no saved guests"; echo
530
            RETVAL=3
E
Eric Blake 已提交
531 532 533 534
        fi
    fi
}

535 536 537
# usage [val]
# Display usage string, then exit with VAL (defaults to 2).
usage() {
538 539 540
    program_name=$0
    eval_gettext "Usage: \$program_name {start|stop|status|restart|"\
"condrestart|try-restart|reload|force-reload|gueststatus|shutdown}"; echo
541 542 543
    exit ${1-2}
}

544
# See how we were called.
545 546 547
if test $# != 1; then
    usage
fi
548
case "$1" in
549 550 551
    --help)
        usage 0
        ;;
552
    start|stop|gueststatus)
553
        "$1"
554 555 556 557
        ;;
    restart)
        stop && start
        ;;
558 559 560 561 562
    condrestart|try-restart)
        [ -f "$VAR_SUBSYS_LIBVIRT_GUESTS" ] && stop && start
        ;;
    reload|force-reload)
        # Nothing to do; we reread configuration on each invocation
563 564
        ;;
    status)
E
Eric Blake 已提交
565
        rh_status
566 567 568 569 570 571
        ;;
    shutdown)
        ON_SHUTDOWN=shutdown
        stop
        ;;
    *)
572
        usage
573 574 575
        ;;
esac
exit $RETVAL