1#
2# Some code for parsing libvirt's capabilities XML
3#
4# Copyright 2007, 2012-2014 Red Hat, Inc.
5#
6# This work is licensed under the GNU GPLv2 or later.
7# See the COPYING file in the top-level directory.
8
9import pwd
10
11from .domain import DomainCpu
12from .logger import log
13from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty
14
15
16###################################
17# capabilities host <cpu> parsing #
18###################################
19
20class _CapsCPU(DomainCpu):
21    arch = XMLProperty("./arch")
22
23
24###########################
25# Caps <topology> parsers #
26###########################
27
28class _CapsTopologyCPU(XMLBuilder):
29    XML_NAME = "cpu"
30    id = XMLProperty("./@id")
31
32
33class _TopologyCell(XMLBuilder):
34    XML_NAME = "cell"
35    cpus = XMLChildProperty(_CapsTopologyCPU, relative_xpath="./cpus")
36
37
38class _CapsTopology(XMLBuilder):
39    XML_NAME = "topology"
40    cells = XMLChildProperty(_TopologyCell, relative_xpath="./cells")
41
42
43######################################
44# Caps <host> and <secmodel> parsers #
45######################################
46
47class _CapsSecmodelBaselabel(XMLBuilder):
48    XML_NAME = "baselabel"
49    type = XMLProperty("./@type")
50    content = XMLProperty(".")
51
52
53class _CapsSecmodel(XMLBuilder):
54    XML_NAME = "secmodel"
55    model = XMLProperty("./model")
56    baselabels = XMLChildProperty(_CapsSecmodelBaselabel)
57
58
59class _CapsHost(XMLBuilder):
60    XML_NAME = "host"
61    secmodels = XMLChildProperty(_CapsSecmodel)
62    cpu = XMLChildProperty(_CapsCPU, is_single=True)
63    topology = XMLChildProperty(_CapsTopology, is_single=True)
64
65    def get_qemu_baselabel(self):
66        for secmodel in self.secmodels:
67            if secmodel.model != "dac":
68                continue
69
70            label = None
71            for baselabel in secmodel.baselabels:
72                if baselabel.type in ["qemu", "kvm"]:
73                    label = baselabel.content
74                    break
75            if not label:
76                continue  # pragma: no cover
77
78            # XML we are looking at is like:
79            #
80            # <secmodel>
81            #   <model>dac</model>
82            #   <doi>0</doi>
83            #   <baselabel type='kvm'>+107:+107</baselabel>
84            #   <baselabel type='qemu'>+107:+107</baselabel>
85            # </secmodel>
86            try:
87                uid = int(label.split(":")[0].replace("+", ""))
88                user = pwd.getpwuid(uid)[0]
89                return user, uid
90            except Exception:
91                log.debug("Exception parsing qemu dac baselabel=%s",
92                    label, exc_info=True)
93        return None, None
94
95
96################################
97# <guest> and <domain> parsers #
98################################
99
100class _CapsMachine(XMLBuilder):
101    XML_NAME = "machine"
102    name = XMLProperty(".")
103    canonical = XMLProperty("./@canonical")
104
105
106class _CapsDomain(XMLBuilder):
107    XML_NAME = "domain"
108    hypervisor_type = XMLProperty("./@type")
109    emulator = XMLProperty("./emulator")
110    machines = XMLChildProperty(_CapsMachine)
111
112
113class _CapsGuestFeatures(XMLBuilder):
114    XML_NAME = "features"
115
116    pae = XMLProperty("./pae", is_bool=True)
117    acpi = XMLProperty("./acpi/@default", is_onoff=True)
118    apic = XMLProperty("./apic/@default", is_onoff=True)
119
120
121class _CapsGuest(XMLBuilder):
122    XML_NAME = "guest"
123
124    os_type = XMLProperty("./os_type")
125    arch = XMLProperty("./arch/@name")
126    loader = XMLProperty("./arch/loader")
127    emulator = XMLProperty("./arch/emulator")
128
129    domains = XMLChildProperty(_CapsDomain, relative_xpath="./arch")
130    features = XMLChildProperty(_CapsGuestFeatures, is_single=True)
131    machines = XMLChildProperty(_CapsMachine, relative_xpath="./arch")
132
133
134    ###############
135    # Public APIs #
136    ###############
137
138    def all_machine_names(self, domain):
139        """
140        Return all machine string names, including canonical aliases for
141        the guest+domain combo but avoiding duplicates
142        """
143        mobjs = (domain and domain.machines) or self.machines
144        ret = []
145        for m in mobjs:
146            ret.append(m.name)
147            if m.canonical and m.canonical not in ret:
148                ret.append(m.canonical)
149        return ret
150
151    def is_kvm_available(self):
152        """
153        Return True if kvm guests can be installed
154        """
155        for d in self.domains:
156            if d.hypervisor_type == "kvm":
157                return True
158        return False
159
160    def supports_pae(self):
161        """
162        Return True if capabilities report support for PAE
163        """
164        return bool(self.features.pae)
165
166    def supports_acpi(self):
167        """
168        Return Tree if capabilities report support for ACPI
169        """
170        return bool(self.features.acpi)
171
172    def supports_apic(self):
173        """
174        Return Tree if capabilities report support for APIC
175        """
176        return bool(self.features.apic)
177
178
179############################
180# Main capabilities object #
181############################
182
183class _CapsInfo(object):
184    """
185    Container object to hold the results of guest_lookup, so users don't
186    need to juggle two objects
187    """
188    def __init__(self, conn, guest, domain):
189        self.conn = conn
190        self.guest = guest
191        self.domain = domain
192
193        self.hypervisor_type = self.domain.hypervisor_type
194        self.os_type = self.guest.os_type
195        self.arch = self.guest.arch
196        self.loader = self.guest.loader
197
198        self.emulator = self.domain.emulator or self.guest.emulator
199        self.machines = self.guest.all_machine_names(self.domain)
200
201
202class Capabilities(XMLBuilder):
203    def __init__(self, *args, **kwargs):
204        XMLBuilder.__init__(self, *args, **kwargs)
205        self._cpu_models_cache = {}
206
207    XML_NAME = "capabilities"
208
209    host = XMLChildProperty(_CapsHost, is_single=True)
210    guests = XMLChildProperty(_CapsGuest)
211
212
213    ############################
214    # Public XML building APIs #
215    ############################
216
217    def _guestForOSType(self, os_type, arch):
218        archs = [arch]
219        if arch is None:
220            archs = [self.host.cpu.arch, None]
221
222        for a in archs:
223            for g in self.guests:
224                if ((os_type is None or g.os_type == os_type) and
225                    (a is None or g.arch == a)):
226                    return g
227
228    def _bestDomainType(self, guest, dtype, machine):
229        """
230        Return the recommended domain for use if the user does not explicitly
231        request one.
232        """
233        domains = []
234        for d in guest.domains:
235            if dtype and d.hypervisor_type != dtype.lower():
236                continue
237            if machine and machine not in guest.all_machine_names(d):
238                continue
239
240            domains.append(d)
241
242        if not domains:
243            return None
244
245        priority = ["kvm", "xen", "qemu"]
246
247        for t in priority:
248            for d in domains:
249                if d.hypervisor_type == t:
250                    return d
251
252        # Fallback, just return last item in list
253        return domains[-1]
254
255    def has_install_options(self):
256        """
257        Return True if there are any install options available
258        """
259        for guest in self.guests:
260            if guest.domains:
261                return True
262        return False
263
264    def guest_lookup(self, os_type=None, arch=None, typ=None, machine=None):
265        """
266        Simple virtualization availability lookup
267
268        Convenience function for looking up 'Guest' and 'Domain' capabilities
269        objects for the desired virt type. If type, arch, or os_type are none,
270        we return the default virt type associated with those values. These are
271        typically:
272
273            - os_type : hvm, then xen
274            - typ     : kvm over plain qemu
275            - arch    : host arch over all others
276
277        Otherwise the default will be the first listed in the capabilities xml.
278        This function throws C{ValueError}s if any of the requested values are
279        not found.
280
281        :param typ: Virtualization type ('hvm', 'xen', ...)
282        :param arch: Guest architecture ('x86_64', 'i686' ...)
283        :param os_type: Hypervisor name ('qemu', 'kvm', 'xen', ...)
284        :param machine: Optional machine type to emulate
285
286        :returns: A _CapsInfo object containing the found guest and domain
287        """
288        # F22 libxl xen still puts type=linux in the XML, so we need
289        # to handle it for caps lookup
290        if os_type == "linux":
291            os_type = "xen"
292
293        guest = self._guestForOSType(os_type, arch)
294        if not guest:
295            if arch and os_type:
296                msg = (_("Host does not support virtualization type "
297                         "'%(virttype)s' for architecture '%(arch)s'") %
298                         {'virttype': os_type, 'arch': arch})
299            elif arch:
300                msg = (_("Host does not support any virtualization options "
301                         "for architecture '%(arch)s'") %
302                         {'arch': arch})
303            elif os_type:
304                msg = (_("Host does not support virtualization type "
305                         "'%(virttype)s'") %
306                         {'virttype': os_type})
307            else:
308                msg = _("Host does not support any virtualization options")
309            raise ValueError(msg)
310
311        domain = self._bestDomainType(guest, typ, machine)
312        if domain is None:
313            if machine:
314                msg = (_("Host does not support domain type %(domain)s with "
315                         "machine '%(machine)s' for virtualization type "
316                         "'%(virttype)s' with architecture '%(arch)s'") %
317                         {'domain': typ, 'virttype': guest.os_type,
318                         'arch': guest.arch, 'machine': machine})
319            else:
320                msg = (_("Host does not support domain type %(domain)s for "
321                         "virtualization type '%(virttype)s' with "
322                         "architecture '%(arch)s'") %
323                         {'domain': typ, 'virttype': guest.os_type,
324                         'arch': guest.arch})
325            raise ValueError(msg)
326
327        capsinfo = _CapsInfo(self.conn, guest, domain)
328        return capsinfo
329