summaryrefslogtreecommitdiff
path: root/virtinst/capabilities.py
blob: 533c0d9a4813a63cc74d5b0c06438f7aaa80514b (plain)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#
# Some code for parsing libvirt's capabilities XML
#
# Copyright 2007, 2012-2014 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

import pwd

from .logger import log
from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty


###################################
# capabilities host <cpu> parsing #
###################################

class _CapsCPU(XMLBuilder):
    XML_NAME = "cpu"
    arch = XMLProperty("./arch")
    model = XMLProperty("./model")


######################################
# Caps <host> and <secmodel> parsers #
######################################

class _CapsSecmodelBaselabel(XMLBuilder):
    XML_NAME = "baselabel"
    type = XMLProperty("./@type")
    content = XMLProperty(".")


class _CapsSecmodel(XMLBuilder):
    XML_NAME = "secmodel"
    model = XMLProperty("./model")
    baselabels = XMLChildProperty(_CapsSecmodelBaselabel)


class _CapsHost(XMLBuilder):
    XML_NAME = "host"
    secmodels = XMLChildProperty(_CapsSecmodel)
    cpu = XMLChildProperty(_CapsCPU, is_single=True)

    def get_qemu_baselabel(self):
        for secmodel in self.secmodels:
            if secmodel.model != "dac":
                continue

            label = None
            for baselabel in secmodel.baselabels:
                if baselabel.type in ["qemu", "kvm"]:
                    label = baselabel.content
                    break
            if not label:
                continue  # pragma: no cover

            # XML we are looking at is like:
            #
            # <secmodel>
            #   <model>dac</model>
            #   <doi>0</doi>
            #   <baselabel type='kvm'>+107:+107</baselabel>
            #   <baselabel type='qemu'>+107:+107</baselabel>
            # </secmodel>
            try:
                uid = int(label.split(":")[0].replace("+", ""))
                user = pwd.getpwuid(uid)[0]
                return user, uid
            except Exception:
                log.debug("Exception parsing qemu dac baselabel=%s",
                    label, exc_info=True)
        return None, None


################################
# <guest> and <domain> parsers #
################################

class _CapsMachine(XMLBuilder):
    XML_NAME = "machine"
    name = XMLProperty(".")
    canonical = XMLProperty("./@canonical")


class _CapsDomain(XMLBuilder):
    XML_NAME = "domain"
    hypervisor_type = XMLProperty("./@type")
    emulator = XMLProperty("./emulator")
    machines = XMLChildProperty(_CapsMachine)


class _CapsGuestFeatures(XMLBuilder):
    XML_NAME = "features"

    pae = XMLProperty("./pae", is_bool=True)
    acpi = XMLProperty("./acpi/@default", is_onoff=True)
    apic = XMLProperty("./apic/@default", is_onoff=True)


class _CapsGuest(XMLBuilder):
    XML_NAME = "guest"

    os_type = XMLProperty("./os_type")
    arch = XMLProperty("./arch/@name")
    loader = XMLProperty("./arch/loader")
    emulator = XMLProperty("./arch/emulator")

    domains = XMLChildProperty(_CapsDomain, relative_xpath="./arch")
    features = XMLChildProperty(_CapsGuestFeatures, is_single=True)
    machines = XMLChildProperty(_CapsMachine, relative_xpath="./arch")


    ###############
    # Public APIs #
    ###############

    def all_machine_names(self, domain):
        """
        Return all machine string names, including canonical aliases for
        the guest+domain combo but avoiding duplicates
        """
        mobjs = (domain and domain.machines) or self.machines
        ret = []
        for m in mobjs:
            ret.append(m.name)
            if m.canonical and m.canonical not in ret:
                ret.append(m.canonical)
        return ret

    def is_machine_alias(self, domain, src, tgt):
        """
        Determine if machine @src is an alias for machine @tgt
        """
        mobjs = (domain and domain.machines) or self.machines
        for m in mobjs:
            if m.name == src and m.canonical == tgt:
                return True
        return False

    def is_kvm_available(self):
        """
        Return True if kvm guests can be installed
        """
        for d in self.domains:
            if d.hypervisor_type == "kvm":
                return True
        return False

    def supports_pae(self):
        """
        Return True if capabilities report support for PAE
        """
        return bool(self.features.pae)

    def supports_acpi(self):
        """
        Return Tree if capabilities report support for ACPI
        """
        return bool(self.features.acpi)

    def supports_apic(self):
        """
        Return Tree if capabilities report support for APIC
        """
        return bool(self.features.apic)


############################
# Main capabilities object #
############################

class _CapsInfo(object):
    """
    Container object to hold the results of guest_lookup, so users don't
    need to juggle two objects
    """
    def __init__(self, conn, guest, domain):
        self.conn = conn
        self.guest = guest
        self.domain = domain

        self.hypervisor_type = self.domain.hypervisor_type
        self.os_type = self.guest.os_type
        self.arch = self.guest.arch
        self.loader = self.guest.loader

        self.emulator = self.domain.emulator or self.guest.emulator
        self.machines = self.guest.all_machine_names(self.domain)

    def is_machine_alias(self, src, tgt):
        return self.guest.is_machine_alias(self.domain, src, tgt)


class Capabilities(XMLBuilder):
    def __init__(self, *args, **kwargs):
        XMLBuilder.__init__(self, *args, **kwargs)
        self._cpu_models_cache = {}

    XML_NAME = "capabilities"

    host = XMLChildProperty(_CapsHost, is_single=True)
    guests = XMLChildProperty(_CapsGuest)


    ############################
    # Public XML building APIs #
    ############################

    def _guestForOSType(self, os_type, arch):
        archs = [arch]
        if arch is None:
            archs = [self.host.cpu.arch, None]

        for a in archs:
            for g in self.guests:
                if ((os_type is None or g.os_type == os_type) and
                    (a is None or g.arch == a)):
                    return g

    def _bestDomainType(self, guest, dtype, machine):
        """
        Return the recommended domain for use if the user does not explicitly
        request one.
        """
        domains = []
        for d in guest.domains:
            if dtype and d.hypervisor_type != dtype.lower():
                continue
            if machine and machine not in guest.all_machine_names(d):
                continue

            domains.append(d)

        if not domains:
            return None

        priority = ["kvm", "xen", "qemu"]

        for t in priority:
            for d in domains:
                if d.hypervisor_type == t:
                    return d

        # Fallback, just return last item in list
        return domains[-1]

    def has_install_options(self):
        """
        Return True if there are any install options available
        """
        for guest in self.guests:
            if guest.domains:
                return True
        return False

    def guest_lookup(self, os_type=None, arch=None, typ=None, machine=None):
        """
        Simple virtualization availability lookup

        Convenience function for looking up 'Guest' and 'Domain' capabilities
        objects for the desired virt type. If type, arch, or os_type are none,
        we return the default virt type associated with those values. These are
        typically:

            - os_type : hvm, then xen
            - typ     : kvm over plain qemu
            - arch    : host arch over all others

        Otherwise the default will be the first listed in the capabilities xml.
        This function throws C{ValueError}s if any of the requested values are
        not found.

        :param typ: Virtualization type ('hvm', 'xen', ...)
        :param arch: Guest architecture ('x86_64', 'i686' ...)
        :param os_type: Hypervisor name ('qemu', 'kvm', 'xen', ...)
        :param machine: Optional machine type to emulate

        :returns: A _CapsInfo object containing the found guest and domain
        """
        # F22 libxl xen still puts type=linux in the XML, so we need
        # to handle it for caps lookup
        if os_type == "linux":
            os_type = "xen"

        guest = self._guestForOSType(os_type, arch)
        if not guest:
            if arch and os_type:
                msg = (_("Host does not support virtualization type "
                         "'%(virttype)s' for architecture '%(arch)s'") %
                         {'virttype': os_type, 'arch': arch})
            elif arch:
                msg = (_("Host does not support any virtualization options "
                         "for architecture '%(arch)s'") %
                         {'arch': arch})
            elif os_type:
                msg = (_("Host does not support virtualization type "
                         "'%(virttype)s'") %
                         {'virttype': os_type})
            else:
                msg = _("Host does not support any virtualization options")
            raise ValueError(msg)

        domain = self._bestDomainType(guest, typ, machine)
        if domain is None:
            if machine:
                msg = (_("Host does not support domain type %(domain)s with "
                         "machine '%(machine)s' for virtualization type "
                         "'%(virttype)s' with architecture '%(arch)s'") %
                         {'domain': typ, 'virttype': guest.os_type,
                         'arch': guest.arch, 'machine': machine})
            else:
                msg = (_("Host does not support domain type %(domain)s for "
                         "virtualization type '%(virttype)s' with "
                         "architecture '%(arch)s'") %
                         {'domain': typ, 'virttype': guest.os_type,
                         'arch': guest.arch})
            raise ValueError(msg)

        capsinfo = _CapsInfo(self.conn, guest, domain)
        return capsinfo