1

I start a QEMU/KVM Ubuntu 15.10 virtual machine on boot and let it run in the background (as webserver).

What happens now if I shut down the host (also 15.10)?
Will it kill the VM and result in a virtual power cut or even worse?
Or will it trigger a "power-button-pressed" event in the VM and wait for it to shut down cleanly?

The guest system is set up to shut down properly when such a "power-button-pressed" event occurs. It's off after less than 5-10 seconds usually.

If the default behaviour on host shutdown is to kill the VM, how can I change this to a clean shutdown of the guest and waiting until it's off?

2

With the help of @Serg's answer, I crafted this set of three scripts (Python 3 and Bash) which listens for Unity Shutdown/Logout dialogs, checks for running VMs, blocks the Unity dialog, displays a nice progress bar, waits until all VMs are off or the timeout is reached, asks if remaining VMs should be forcibly killed and finally displays a custom shutdown/logout dialog.

Here are the scripts. Place them in a location contained in the $PATH variable, like /usr/local/bin/. Make sure they're owned by root and have all execution bits set (chmod +x).

vm-terminator (in Bash, the GUI):

#! /bin/bash

# Use first command-line argument as timeout, if given and numeric, else 30 sec
if [ "$1" -eq "$1" ] 2> /dev/null
    then timeout=$1
    else timeout=30
fi


# Define function to ask whether to shut down / log out / reboot later.
function end_session () {
    action=$(zenity --list --title="VM Terminator" --text="All VMs are shut down. What to do now?" --radiolist --hide-header --column="" --column="" TRUE "Log out" FALSE "Reboot" FALSE "Shut down")

    case $action in
        "Log out")
          gnome-session-quit --logout --no-prompt
          ;;
        "Reboot")
          systemctl reboot
          ;;
        "Shut down")
          systemctl poweroff
          ;;
        *)
          echo "Not ending current session."
          ;;
    esac
}


# Try to shut down VMs with
(
    set -o pipefail
    shutdown-all-vms -i 0.5 -t $timeout -z |
      zenity --progress --title="VM Terminator" --auto-close --auto-kill --width=400
) &> /dev/null
succeeded=$?

# Evaluate whether the task was successful and show host shutdown/logout dialog or kill/manual dialog or error message.
case $succeeded in
    0)
      end_session
      ;;
    1)
      zenity --question --title="VM Terminator" --text="The timeout was reached.\n\nWould you like to forcibly power off all remaining VMs\nor abort and take care of them yourself?" --ok-label="Kill them!" --cancel-label="I'll do it myself" --default-cancel
      if [ $? == 0 ]
        then shutdown-all-vms -t 0 -k
            end_session
        else exit 1
      fi
      ;;
    129)
      zenity --question --title="VM Terminator" --text="You cancelled the timeout.\n\nWould you like to forcibly power off all remaining VMs\nor abort and take care of them yourself?" --ok-label="Kill them!" --cancel-label="I'll do it myself" --default-cancel
      if [ $? == 0 ]
        then shutdown-all-vms -t 0 -k
            end_session
        else exit 1
      fi
      ;;
    *)
      zenity --error --title="VM Terminator" --text="An error occured while trying to shut down some VMs. Please review them manualy!"
      exit 2
      ;;
esac

shutdown-all-vms (in Python 3, the core):

#! /usr/bin/env python3

# Script to gracefully shut down all running virtual machines accessible to the 'virtsh' command.
# It was initially designed for QEMU/KVM machines, but might work with more hypervisors.

# The VMs are tried to be shut down by triggering a "power-button-pressed" event in each machine.
# Each guest OS is responsible to shut down when detecting one. By default, some systems may just show
# an user dialog prompt instead and do nothing. If configured, this script can turn them off forcibly.
# That would be similar to holding the power button or pulling the AC plug on a real machine.

# This script exits with code 0 when all VMs could be shut down or were forced off at timeout.
# If the 'virsh shutdown VM_NAME' command returned an error, this script will exit with error code 1.
# On timeout with KILL_ON_TIMEOUT set to False, the script will exit with error code 2.
# If KILL_ON_TIMEOUT is active and the timeout was reached, but one of the 'virsh destroy VM_NAME' commands
# returned an error, this script exits with error code 3.


import subprocess
import time
from optparse import OptionParser

# Function to get a list of running VM names:
def list_running_vms():
    as_string = subprocess.check_output(["virsh", "list", "--state-running", "--name"], universal_newlines=True).strip()
    return [] if not as_string else as_string.split("\n")

# Evaluate command-line arguments:
parser = OptionParser(version="%prog 1.0")
parser.add_option("-i", "--interval", type="float", dest="interval", default=1,
                  help="Interval to use for polling the VM state after sending the shutdown command. (default: %default)")
parser.add_option("-t", "--timeout", type="float", dest="timeout", default=30,
                  help="Time to wait for all VMs to shut down. (default: %default)")
parser.add_option("-k", "--kill-on-timeout", action="store_true", dest="kill", default=False,
                  help="Kill (power cut) all remaining VMs when the timeout is reached. "
                       "Otherwise exit with error code 1. (default: %default)")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
                  help="Print verbose status output. (default: %default)")
parser.add_option("-z", "--zenity", action="store_true", dest="zenity", default=False,
                  help="Print progress lines for 'zenity --progress' GUI progress dialog. (default: %default)")
(options, args) = parser.parse_args()

# List all running VMs:
running_vms = list_running_vms()

# Print summary of what will happen:
print("Shutting down all running VMs (currently {}) within {} seconds. {} remaining VMs.".format(
       len(running_vms), options.timeout, "Kill all" if options.kill else "Do not kill any"))

# Send shutdown command ("power-button-pressed" event) to all running VMs:
any_errors = False
if options.zenity:
    print("# Sending shutdown signals...", flush=True)
for vm in running_vms:
    if options.verbose:
        ok = subprocess.call(["virsh", "shutdown", vm])
    else:
        ok = subprocess.call(["virsh", "shutdown", vm], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    if ok != 0:
        print("Error trying to shut down VM '{}' (code {})!".format(vm, ok))
        any_errors = True

# Don't start waiting if there was any error sending the shutdown command, exit with error:
if any_errors:
    print("ERROR: could not successfully send all shutdown commands!")
    exit(3)

# Wait for all VMs to shut down, but at most MAX_WAIT seconds. Poll every INTERVAL seconds::
t0 = time.time()
while running_vms:
    num_of_vms = len(running_vms)
    t = time.time() - t0
    if options.zenity:
        print("# Waiting for {} VM{} to shut down... ({} seconds left)".format(
               num_of_vms, "" if num_of_vms == 1 else "s", int(options.timeout - t)), flush=True)
        print(int(100 * t/options.timeout) if t < options.timeout else 99, flush=True)
    if options.verbose or t > options.timeout:
        print("\n[{:5.1f}s] Still waiting for {} VMs to shut down:".format(t, num_of_vms))
        print(" > " + "\n > ".join(running_vms))
    if t > options.timeout:
        if options.kill:
            print("\nTimeout of {} seconds reached! Killing all remaining VMs now!".format(options.timeout))
            if options.zenity:
                print("# Timeout reached! Have to kill the remaining {}.".format(
                       "VM" if num_of_vms == 1 else "{} VMs".format(num_of_vms)), flush=True)
            for vm in running_vms:
                if options.verbose:
                    ok = subprocess.call(["virsh", "destroy", vm])
                else:
                    ok = subprocess.call(["virsh", "destroy", vm], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                if ok != 0:
                    if options.verbose:
                        print("Error trying to forcibly kill VM '{}' (code {})!".format(vm, ok))
                    any_errors = True
            if any_errors:
                print("ERROR: could not successfully send all destroy commands!")
                exit(3)
        else:
            print("ERROR: Timeout of {} seconds reached!".format(options.timeout))
            exit(1)
        break
    time.sleep(options.interval)
    running_vms = list_running_vms()

print("#" if options.zenity else "" + " All VMs were shut down successfully.", flush=True)
if options.zenity:
    print(100, flush=True)
exit(0)

shutdown-dialog-listener (in Bash, the Unity shutdown/logout watchdog):

#!/bin/bash

DISPLAY=:0
dbus-monitor --session "interface='com.canonical.Unity.Session'" | \
  while read LINE;do \
  if grep -qi 'reboot\|shutdown\|logout' <<< "$LINE" ;then \
    VAR="$(virsh list --state-running --name)"
    if [ $(wc -w <<<$VAR) -gt 0 ]; then
      qdbus com.canonical.Unity /org/gnome/SessionManager/EndSessionDialog \
      org.gnome.SessionManager.EndSessionDialog.Close

      vm-terminator
    fi
  fi ;done

All three scripts are directly callable, the core script shutdown-all-vms even has a nice command-line help:

$ shutdown-all-vms --help
Usage: shutdown-all-vms [options]

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -i INTERVAL, --interval=INTERVAL
                        Interval to use for polling the VM state after sending
                        the shutdown command. (default: 1)
  -t TIMEOUT, --timeout=TIMEOUT
                        Time to wait for all VMs to shut down. (default: 30)
  -k, --kill-on-timeout
                        Kill (power cut) all remaining VMs when the timeout is
                        reached. Otherwise exit with error code 1. (default:
                        False)
  -v, --verbose         Print verbose status output. (default: False)
  -z, --zenity          Print progress lines for 'zenity --progress' GUI
                        progress dialog. (default: False)

Additionally, you may place shutdown-dialog-listener into your user account's startup applications.

1
  • Screenshots of the GUI might follow.
    – Byte Commander
    Feb 4 '16 at 16:14
0

Below is a small script that should be run as autostart entry or manually (if user prefers so). The basic idea is this: keep polling dbus session bus, and if we get reboot, or shutdown, or logout, then we can check if QEMU is running; if it is, kill the shutdown dialog, run the command to shutdown the VMs, and then call dbus to shutdown, or even call separate script with script-name.sh &

The example I have below was tested with firefox just for the sake of example (since I do not have QEMU), but it can be easily adapted. Comments included for guidance

#!/bin/bash

# You will need the DISPLAY variable, if you
# are running the script as an autostart entry
# DISPLAY=:0
dbus-monitor --session "interface='com.canonical.Unity.Session'" | \
  while read LINE;do \
  if grep -qi 'reboot\|shutdown\|logout' <<< "$LINE" ;then \
    # This part could be either pgrep , or 
    # VAR="$(virsh list --state-running --name)"
    # And then you can test whether or not variable is empty to see
    # if there are running processes
    PID="$(pgrep firefox)"
    if [ ! -z $PID  ]; then
      # This is where you can take action
      # For instance the qdbus lengthy command closes the End Session dialog
      # which effectively prevents user from clicking shutdown
      # You can append another command, such as
      # virsh shutdown VMNAME or run an external script that does it.
      # Since this script is constantly polling dbus-monitor, we need to avoid
      # it's better to call external , in my opinion.

      notify-send "GOTCHA $PID";
      qdbus com.canonical.Unity /org/gnome/SessionManager/EndSessionDialog \
      org.gnome.SessionManager.EndSessionDialog.Close
    # After the action part is done, one could call
    # dbus to shutdown
    # qdbus  com.canonical.Unity /com/canonical/Unity/Session com.canonical.Unity.Session.Shutdown 
    fi
  fi ;done
1
  • I wrote my own answer below.
    – Byte Commander
    Feb 4 '16 at 16:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.