File: virsh.py

package info (click to toggle)
libreswan 5.2-2.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 81,632 kB
  • sloc: ansic: 129,988; sh: 32,018; xml: 20,646; python: 10,303; makefile: 3,022; javascript: 1,506; sed: 574; yacc: 511; perl: 264; awk: 52
file content (207 lines) | stat: -rw-r--r-- 7,464 bytes parent folder | download
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