1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
|
# Stuff to talk to virsh, for libreswan
#
# Copyright (C) 2015-2016 Andrew Cagney <cagney@gnu.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version. See <https://www.gnu.org/licenses/gpl2.txt>.
#
# This program 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 General Public License
# for more details.
import time
import subprocess
import pexpect
import sys
import time
import logging
import re
import os
from fab.console import Console
from fab import logutil
from fab import timing
class STATE:
RUNNING = "running"
IDLE = "idle"
PAUSED = "paused"
IN_SHUTDOWN = "in shutdown"
SHUT_OFF = "shut off"
CRASHED = "crashed"
DYING = "dying"
PMSUSPENDED = "pmsuspended"
_VIRSH = ["sudo", "virsh", "--connect", "qemu:///system"]
START_TIMEOUT = 90
# Can be anything as it either matches immediately or dies with EOF.
CONSOLE_TIMEOUT = 20
SHUTDOWN_TIMEOUT = 30
DESTROY_TIMEOUT = 20
TIMEOUT = 10
class Domain:
def __init__(self, logger, name=None, prefix=None, guest=None):
# Use the term "domain" just like virsh
self.name = name or prefix+guest.name
self.guest = guest
self.logger = logger.nest(self.name)
self.debug_handler = None
self.logger.debug("domain created")
self._mounts = None
self._xml = None
# ._console is three state: None when state unknown; False
# when shutdown (by us); else the console.
self._console = None
def __str__(self):
return "domain " + self.name
def nest(self, logger, prefix):
self.logger = logger.nest(prefix + self.name)
if self._console:
self._console.logger = self.logger
return self.logger
def _run_status_output(self, command, verbose=False):
if verbose:
self.logger.info("running: %s", command)
else:
self.logger.debug("running: %s", command)
# 3.5 has subprocess.run
process = subprocess.Popen(command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
status = process.returncode
output = stdout.decode('utf-8').strip()
if status:
self.logger.debug("virsh exited with unexpected status code %s\n%s",
status, output)
else:
self.logger.debug("output: %s", output)
return status, output
def state(self):
status, output = self._run_status_output(_VIRSH + ["domstate", self.name])
if status:
return None
else:
return output
def _shutdown(self):
self._run_status_output(_VIRSH + ["shutdown", self.name])
def shutdown(self, console=None):
"""Use the console to detect the shutdown - if/when the domain stops
it will exit giving an EOF.
"""
console = console or self.console()
if not console:
self.logger.error("domain already shutdown")
return True
self.logger.info("waiting %d seconds for domain to shutdown", SHUTDOWN_TIMEOUT)
lapsed_time = timing.Lapsed()
self._shutdown()
match console.expect([pexpect.EOF, pexpect.TIMEOUT],
timeout=SHUTDOWN_TIMEOUT):
case 0: #EOF
self.logger.info("got EOF; domain shutdown after %s", lapsed_time)
self._console = False
self.logger.info("domain state is: %s", self.state())
return True
case 1: #TIMEOUT
self.logger.error("TIMEOUT shutting down domain")
return self.destroy(console)
def _destroy(self):
return self._run_status_output(_VIRSH + ["destroy", self.name])
def __console(self, command, timeout):
self.logger.info("spawning: %s; waiting %d seconds", " ".join(command), timeout)
self._console = None
console = Console(command, self.logger, host_name=self.guest and self.guest.host.name)
# Give the virsh process a chance set up its control-c
# handler. Otherwise something like control-c as the first
# character sent might kill it. If the machine is down, it
# will get an EOF.
match console.expect([(r"Connected to domain '%s'\r\n" % self.name +
r"Escape character is \^] \(Ctrl \+ ]\)\r\n"),
pexpect.TIMEOUT,
pexpect.EOF],
timeout=timeout):
case 0: #success
self.logger.debug("domain connected");
self._console = console
return console
case 1: #TIMEOUT
self.logger.error("TIMEOUT opening console: %s", console.before)
console.close()
return None
case 2: #EOF
# already tried and failed
self.logger.error("EOF opening console: %s", console.before)
return None
def destroy(self, console=None):
# Use the console to detect a destroyed domain - if/when the
# domain stops it will exit giving an EOF.
console = console or self.console()
if not console:
self.logger.error("domain already destroyed")
return True
self.logger.info("waiting %d seconds for domain to be destroyed", DESTROY_TIMEOUT)
lapsed_time = timing.Lapsed()
self._destroy()
match console.expect([pexpect.EOF, pexpect.TIMEOUT],
timeout=DESTROY_TIMEOUT):
case 0: #EOF
self.logger.info("domain destroyed after %s", lapsed_time)
self._console = None
return True
case 1: #TIMEOUT
self.logger.error("TIMEOUT destroying domain, giving up")
self._console = None
return False
def start(self):
# A shutdown domain can linger for a bit
shutdown_timeout = START_TIMEOUT
while self.state() == STATE.IN_SHUTDOWN and shutdown_timeout > 0:
self.logger.info("waiting for domain to finish shutting down")
time.sleep(1)
shutdown_timeout = shutdown_timeout - 1;
self._console = None # status unknown
command = _VIRSH + ["start", self.name, "--console"]
return self.__console(command, START_TIMEOUT)
def dumpxml(self):
if self._xml == None:
status, self._xml = self._run_status_output(_VIRSH + ["dumpxml", self.name])
if status:
raise AssertionError("dumpxml failed: %s" % (output))
return self._xml
def console(self):
# self._console is None
command = _VIRSH + ["console", "--force", self.name]
return self.__console(command, CONSOLE_TIMEOUT)
def close(self):
if self._console:
self._console.close()
self._console = None # not False
|