This file is indexed.

/usr/lib/python2.7/dist-packages/provisioningserver/custom_hardware/virsh.py is in python-maas-provisioningserver 1.5.4+bzr2294-0ubuntu1.2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# Copyright 2014 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

str = None

__metaclass__ = type
__all__ = [
    'probe_virsh_and_enlist',
    ]

from lxml import etree
import pexpect
import provisioningserver.custom_hardware.utils as utils


XPATH_ARCH = "/domain/os/type/@arch"

# Virsh stores the architecture with a different
# label then MAAS. This maps virsh architecture to
# MAAS architecture.
ARCH_FIX = {
    'x86_64': 'amd64',
    'ppc64': 'ppc64el',
    }


class VirshVMState:
    OFF = "shut off"
    ON = "running"


class VirshError(Exception):
    """Failure communicating to virsh. """


class VirshSSH(pexpect.spawn):

    PROMPT = r"virsh \#"
    PROMPT_SSHKEY = "(?i)are you sure you want to continue connecting"
    PROMPT_PASSWORD = "(?i)(?:password)|(?:passphrase for key)"
    PROMPT_DENIED = "(?i)permission denied"
    PROMPT_CLOSED = "(?i)connection closed by remote host"

    PROMPTS = [
        PROMPT_SSHKEY,
        PROMPT_PASSWORD,
        PROMPT,
        PROMPT_DENIED,
        PROMPT_CLOSED,
        pexpect.TIMEOUT,
        pexpect.EOF,
    ]

    I_PROMPT = PROMPTS.index(PROMPT)
    I_PROMPT_SSHKEY = PROMPTS.index(PROMPT_SSHKEY)
    I_PROMPT_PASSWORD = PROMPTS.index(PROMPT_PASSWORD)

    def __init__(self, timeout=30, maxread=2000):
        super(VirshSSH, self).__init__(
            None, timeout=timeout, maxread=maxread)
        self.name = '<virssh>'

    def _execute(self, poweraddr):
        """Spawns the pexpect command."""
        cmd = 'virsh --connect %s' % poweraddr
        self._spawn(cmd)

    def login(self, poweraddr, password=None):
        """Starts connection to virsh."""
        self._execute(poweraddr)
        i = self.expect(self.PROMPTS, timeout=10)
        if i == self.I_PROMPT_SSHKEY:
            # New certificate, lets always accept but if
            # it changes it will fail to login.
            self.sendline("yes")
            i = self.expect(self.PROMPTS)
        elif i == self.I_PROMPT_PASSWORD:
            # Requesting password, give it if available.
            if password is None:
                self.close()
                return False
            self.sendline(password)
            i = self.expect(self.PROMPTS)

        if i != self.I_PROMPT:
            # Something bad happened, either disconnect,
            # timeout, wrong password.
            self.close()
            return False
        return True

    def logout(self):
        """Quits the virsh session."""
        self.sendline("quit")
        self.close()

    def prompt(self, timeout=None):
        """Waits for virsh prompt."""
        if timeout is None:
            timeout = self.timeout
        i = self.expect([self.PROMPT, pexpect.TIMEOUT], timeout=timeout)
        if i == 1:
            return False
        return True

    def run(self, args):
        cmd = ' '.join(args)
        self.sendline(cmd)
        self.prompt()
        result = self.before.splitlines()
        return '\n'.join(result[1:])

    def list(self):
        """Lists all virtual machines by name."""
        machines = self.run(['list', '--all', '--name'])
        return machines.strip().splitlines()

    def get_state(self, machine):
        """Gets the virtual machine state."""
        state = self.run(['domstate', machine])
        state = state.strip()
        if 'error' in state:
            return None
        return state

    def get_mac_addresses(self, machine):
        """Gets list of mac addressess assigned to the virtual machine."""
        output = self.run(['domiflist', machine]).strip()
        if 'error' in output:
            return None
        output = output.splitlines()[2:]
        return [line.split()[4] for line in output]

    def get_arch(self, machine):
        """Gets the virtual machine architecture."""
        output = self.run(['dumpxml', machine]).strip()
        if 'error' in output:
            return None

        doc = etree.XML(output)
        evaluator = etree.XPathEvaluator(doc)
        arch = evaluator(XPATH_ARCH)[0]

        # Fix architectures that need to be referenced by a different
        # name, that MAAS understands.
        return ARCH_FIX.get(arch, arch)

    def poweron(self, machine):
        """Poweron a virtual machine."""
        output = self.run(['start', machine]).strip()
        if 'error' in output:
            return False
        return True

    def poweroff(self, machine):
        """Poweroff a virtual machine."""
        output = self.run(['destroy', machine]).strip()
        if 'error' in output:
            return False
        return True


def probe_virsh_and_enlist(poweraddr, password=None):
    """Extracts all of the virtual machines from virsh and enlists them
    into MAAS.

    :param poweraddr: virsh connection string
    """
    conn = VirshSSH()
    if not conn.login(poweraddr, password):
        raise VirshError('Failed to login to virsh console.')

    for machine in conn.list():
        arch = conn.get_arch(machine)
        state = conn.get_state(machine)
        macs = conn.get_mac_addresses(machine)

        # Force the machine off, as MAAS will control the machine
        # and it needs to be in a known state of off.
        if state == VirshVMState.ON:
            conn.poweroff(machine)

        params = {
            'power_address': poweraddr,
            'power_id': machine,
        }
        if password is not None:
            params['power_pass'] = password
        utils.create_node(macs, arch, 'virsh', params)

    conn.logout()


def power_control_virsh(poweraddr, machine, power_change, password=None):
    """Powers controls a virtual machine using virsh."""

    # Force password to None if blank, as the power control
    # script will send a blank password if one is not set.
    if password == '':
        password = None

    conn = VirshSSH()
    if not conn.login(poweraddr, password):
        raise VirshError('Failed to login to virsh console.')

    state = conn.get_state(machine)
    if state is None:
        raise VirshError('Failed to get domain: %s' % machine)

    if state == VirshVMState.OFF:
        if power_change == 'on':
            if conn.poweron(machine) is False:
                raise VirshError('Failed to power on domain: %s' % machine)
    elif state == VirshVMState.ON:
        if power_change == 'off':
            if conn.poweroff(machine) is False:
                raise VirshError('Failed to power off domain: %s' % machine)