Encerreu com cuidado o QEMU / KVM no encerramento do host

1

Eu inicio uma máquina virtual QEMU / KVM Ubuntu 15.10 na inicialização e deixo ela rodar em segundo plano (como servidor web).

O que acontece agora se eu desligar o host (também 15.10)?
Isso matará a VM e resultará em um corte de energia virtual ou ainda pior?
Ou isso acionará um evento "pressionado pelo botão liga / desliga" na VM e esperará que ele seja desligado corretamente?

O sistema convidado é configurado para desligar corretamente quando ocorre um evento desse tipo "pressionado pelo botão liga / desliga". Está desligado depois de menos de 5-10 segundos normalmente.

Se o comportamento padrão no desligamento do host é matar a VM, como posso alterar isso para um desligamento limpo do convidado e esperar até que ele seja desativado?

    
por Byte Commander 03.02.2016 / 15:19

2 respostas

2

Com a ajuda de @ Serg 's answer , criei este conjunto de três scripts (Python 3 e Bash) que atende a diálogos Unity Shutdown / Logout, verifica se há VMs em execução, bloqueia a caixa de diálogo Unity, exibe uma boa barra de progresso, aguarda até que todas as VMs estejam desligadas ou que o tempo limite seja atingido, pergunta se as VMs restantes devem ser forçadas a morrer e, finalmente, exibir uma caixa de diálogo de desligamento / logout personalizado.

Aqui estão os scripts. Coloque-os em um local contido na variável $PATH , como /usr/local/bin/ . Certifique-se de que sejam de propriedade do root e que todos os bits de execução sejam definidos ( chmod +x ).

vm-terminator (no Bash, a GUI):

#! /bin/bash

# Use first command-line argument as timeout, if given and numeric, else 30 sec
if [ "" -eq "" ] 2> /dev/null
    then timeout=
    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 (no Python 3, o núcleo):

#! /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 (no Bash, o watchdog de encerramento / logout do Unity):

#!/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

Todos os três scripts são diretamente solicitados, o script principal shutdown-all-vms tem até mesmo uma ajuda de linha de comando:

$ 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)

Além disso, você pode colocar shutdown-dialog-listener nos aplicativos de inicialização da sua conta de usuário.

    
por Byte Commander 04.02.2016 / 17:13
0

Abaixo está um pequeno script que deve ser executado como entrada de início automático ou manualmente (se o usuário preferir). A idéia básica é esta: continue pesquisando dbus bus da sessão e, se conseguirmos reinicializar, desligar ou sair, podemos verificar se o QEMU está em execução; se estiver, mate a caixa de diálogo de desligamento, execute o comando para desligar as VMs e, em seguida, chame o dbus para o desligamento ou até chame um script separado com script-name.sh &

O exemplo que eu tenho abaixo foi testado com o firefox apenas por uma questão de exemplo (já que eu não tenho QEMU), mas ele pode ser facilmente adaptado. Comentários incluídos para orientação

#!/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
    
por Sergiy Kolodyazhnyy 03.02.2016 / 18:28