2013-03-17 17:06:52 -04:00
#
# Some code for parsing libvirt's capabilities XML
#
2014-03-12 12:36:17 +01:00
# Copyright 2007, 2012-2014 Red Hat, Inc.
2013-03-17 17:06:52 -04:00
# Mark McLoughlin <markmc@redhat.com>
#
# 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
2013-10-27 21:59:47 +01:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-17 17:06:52 -04:00
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
import re
2014-09-12 15:59:22 -04:00
from . import util
2013-03-17 17:06:52 -04:00
# Whether a guest can be created with a certain feature on resp. off
FEATURE_ON = 0x01
FEATURE_OFF = 0x02
2013-04-13 14:34:52 -04:00
2013-07-06 14:12:13 -04:00
def xpathString ( node , path , default = None ) :
result = node . xpathEval ( " string( %s ) " % path )
if len ( result ) == 0 :
result = default
return result
2013-03-17 17:06:52 -04:00
class CPUValuesModel ( object ) :
"""
2014-03-12 20:59:33 +01:00
Single CPU model
2013-03-17 17:06:52 -04:00
"""
2014-03-12 20:59:33 +01:00
def __init__ ( self , model ) :
self . model = model
2013-03-17 17:06:52 -04:00
class CPUValuesArch ( object ) :
"""
Single < arch > instance of valid CPUs
"""
def __init__ ( self , arch , node = None ) :
self . arch = arch
self . vendors = [ ]
self . cpus = [ ]
if node :
self . _parseXML ( node )
def _parseXML ( self , node ) :
child = node . children
while child :
if child . name == " vendor " :
self . vendors . append ( child . prop ( " name " ) )
if child . name == " model " :
2014-03-12 20:59:33 +01:00
newcpu = CPUValuesModel ( child . prop ( " name " ) )
2013-03-17 17:06:52 -04:00
self . cpus . append ( newcpu )
child = child . next
self . vendors . sort ( )
def get_cpu ( self , model ) :
for c in self . cpus :
if c . model == model :
return c
raise ValueError ( _ ( " Unknown CPU model ' %s ' " ) % model )
2013-04-13 14:34:52 -04:00
2014-03-20 13:48:49 -04:00
class _CPUAPIValues ( object ) :
2013-03-17 17:06:52 -04:00
"""
2014-03-13 12:52:51 +01:00
Lists valid values for cpu models obtained trough libvirt ' s getCPUModelNames
2013-03-17 17:06:52 -04:00
"""
2014-03-13 12:52:51 +01:00
def __init__ ( self ) :
self . _cpus = None
def get_cpus ( self , arch , conn ) :
if self . _cpus is not None :
return self . _cpus
2014-03-20 13:45:52 -04:00
if ( conn and conn . check_support ( conn . SUPPORT_CONN_CPU_MODEL_NAMES ) ) :
names = conn . getCPUModelNames ( arch , 0 )
# Bindings were broke for a long time, so catch -1
if names != - 1 :
self . _cpus = [ CPUValuesModel ( i ) for i in names ]
return self . _cpus
2014-03-13 12:52:51 +01:00
return [ ]
2014-03-20 13:48:49 -04:00
class _CPUMapFileValues ( _CPUAPIValues ) :
2014-03-13 12:52:51 +01:00
"""
Fallback method to lists cpu models , parsed directly from libvirt ' s local
cpu_map . xml
"""
2014-04-04 21:13:20 +04:00
_cpu_filename = " /usr/share/libvirt/cpu_map.xml "
2014-03-13 12:52:51 +01:00
def __init__ ( self ) :
2014-03-20 13:48:49 -04:00
_CPUAPIValues . __init__ ( self )
2013-03-17 17:06:52 -04:00
self . archmap = { }
2014-04-04 21:13:20 +04:00
xml = file ( self . _cpu_filename ) . read ( )
2013-03-17 17:06:52 -04:00
2013-04-11 10:27:02 -04:00
util . parse_node_helper ( xml , " cpus " ,
2013-03-17 17:06:52 -04:00
self . _parseXML ,
2013-07-06 14:12:13 -04:00
RuntimeError )
2013-03-17 17:06:52 -04:00
2014-04-10 23:15:55 +08:00
@staticmethod
def update_cpu_filename ( name ) :
_CPUMapFileValues . _cpu_filename = name
2013-03-17 17:06:52 -04:00
def _parseXML ( self , node ) :
child = node . children
while child :
if child . name == " arch " :
arch = child . prop ( " name " )
self . archmap [ arch ] = CPUValuesArch ( arch , child )
child = child . next
2014-03-13 12:52:51 +01:00
def get_cpus ( self , arch , conn ) :
ignore = conn
2013-03-17 17:06:52 -04:00
if re . match ( r ' i[4-9]86 ' , arch ) :
arch = " x86 "
elif arch == " x86_64 " :
arch = " x86 "
cpumap = self . archmap . get ( arch )
if not cpumap :
cpumap = CPUValuesArch ( arch )
self . archmap [ arch ] = cpumap
2014-03-13 12:52:51 +01:00
return cpumap . cpus
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class Features ( object ) :
""" Represent a set of features. For each feature, store a bit mask of
FEATURE_ON and FEATURE_OFF to indicate whether the feature can
be turned on or off . For features for which toggling doesn ' t make sense
( e . g . , ' vmx ' ) store FEATURE_ON when the feature is present . """
def __init__ ( self , node = None ) :
self . features = { }
if node is not None :
self . parseXML ( node )
def __getitem__ ( self , feature ) :
if feature in self . features :
return self . features [ feature ]
return 0
def names ( self ) :
return self . features . keys ( )
def parseXML ( self , node ) :
d = self . features
feature_list = [ ]
if node . name == " features " :
node_list = node . xpathEval ( " * " )
for n in node_list :
feature_list . append ( n . name )
else :
# New style features
node_list = node . xpathEval ( " feature/@name " )
for n in node_list :
feature_list . append ( n . content )
for feature in feature_list :
if feature not in d :
d [ feature ] = 0
self . _extractFeature ( feature , d , n )
def _extractFeature ( self , feature , d , node ) :
""" Extract the value of FEATURE from NODE and set DICT[FEATURE] to
its value . Abstract method , must be overridden """
raise NotImplementedError ( " Abstract base class " )
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class CapabilityFeatures ( Features ) :
def __init__ ( self , node = None ) :
Features . __init__ ( self , node )
def _extractFeature ( self , feature , d , n ) :
default = xpathString ( n , " @default " )
toggle = xpathString ( n , " @toggle " )
if default is not None :
# Format for guest features
if default == " on " :
d [ feature ] = FEATURE_ON
elif default == " off " :
d [ feature ] = FEATURE_OFF
else :
2013-07-06 14:12:13 -04:00
raise RuntimeError ( " Feature %s : value of default must "
" be ' on ' or ' off ' , but is ' %s ' " %
( feature , default ) )
2013-03-17 17:06:52 -04:00
if toggle == " yes " :
d [ feature ] | = d [ feature ] ^ ( FEATURE_ON | FEATURE_OFF )
else :
# Format for old HOST features, on OLD old guest features
# back compat is just <$featurename>, like <svm/>
if feature == " nonpae " :
d [ " pae " ] | = FEATURE_OFF
else :
d [ feature ] | = FEATURE_ON
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class CPU ( object ) :
def __init__ ( self , node = None ) :
# e.g. "i686" or "x86_64"
self . arch = None
self . model = None
self . vendor = None
self . sockets = 1
self . cores = 1
self . threads = 1
self . features = CapabilityFeatures ( )
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
newstyle_features = False
child = node . children
while child :
# Do a first pass to try and detect new style features
if child . name == " feature " :
newstyle_features = True
break
child = child . next
if newstyle_features :
self . features = CapabilityFeatures ( node )
child = node . children
while child :
if child . name == " arch " :
self . arch = child . content
elif child . name == " model " :
self . model = child . content
elif child . name == " vendor " :
self . vendor = child . content
elif child . name == " topology " :
self . sockets = xpathString ( child , " @sockets " ) or 1
self . cores = xpathString ( child , " @cores " ) or 1
self . threads = xpathString ( child , " @threads " ) or 1
elif child . name == " features " and not newstyle_features :
self . features = CapabilityFeatures ( child )
child = child . next
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class Host ( object ) :
def __init__ ( self , node = None ) :
self . cpu = CPU ( )
self . topology = None
self . secmodels = [ ]
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def get_secmodel ( self ) :
return self . secmodels and self . secmodels [ 0 ] or None
secmodel = property ( get_secmodel )
def parseXML ( self , node ) :
child = node . children
while child :
if child . name == " topology " :
self . topology = Topology ( child )
if child . name == " secmodel " :
self . secmodels . append ( SecurityModel ( child ) )
if child . name == " cpu " :
self . cpu = CPU ( child )
child = child . next
class Guest ( object ) :
def __init__ ( self , node = None ) :
# e.g. "xen" or "hvm"
self . os_type = None
# e.g. "i686" or "x86_64"
self . arch = None
self . domains = [ ]
self . features = CapabilityFeatures ( )
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
child = node . children
while child :
if child . name == " os_type " :
self . os_type = child . content
elif child . name == " features " :
self . features = CapabilityFeatures ( child )
elif child . name == " arch " :
self . arch = child . prop ( " name " )
machines = [ ]
emulator = None
loader = None
n = child . children
while n :
if n . name == " machine " :
machines . append ( n . content )
canon = n . prop ( " canonical " )
if canon :
machines . append ( canon )
elif n . name == " emulator " :
emulator = n . content
elif n . name == " loader " :
loader = n . content
n = n . next
n = child . children
while n :
if n . name == " domain " :
self . domains . append ( Domain ( n . prop ( " type " ) ,
emulator , loader , machines , n ) )
n = n . next
child = child . next
2014-02-17 11:43:53 -05:00
def _favoredDomain ( self , domains ) :
2013-03-17 17:06:52 -04:00
"""
Return the recommended domain for use if the user does not explicitly
request one .
"""
2014-02-17 11:43:53 -05:00
if not domains :
return None
2013-03-17 17:06:52 -04:00
priority = [ " kvm " , " xen " , " kqemu " , " 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 ]
2014-02-17 11:43:53 -05:00
def bestDomainType ( self , dtype = None , machine = None ) :
2013-03-17 17:06:52 -04:00
domains = [ ]
for d in self . domains :
2014-09-23 16:05:48 -04:00
d . set_recommended_machine ( None )
2013-03-17 17:06:52 -04:00
if dtype and d . hypervisor_type != dtype . lower ( ) :
continue
if machine and machine not in d . machines :
continue
2014-09-23 16:05:48 -04:00
if machine :
d . set_recommended_machine ( machine )
2013-03-17 17:06:52 -04:00
domains . append ( d )
2014-02-17 11:43:53 -05:00
return self . _favoredDomain ( domains )
2013-03-17 17:06:52 -04:00
class Domain ( object ) :
def __init__ ( self , hypervisor_type ,
emulator = None , loader = None ,
machines = None , node = None ) :
self . hypervisor_type = hypervisor_type
self . emulator = emulator
self . loader = loader
self . machines = machines
2014-09-23 16:05:48 -04:00
self . _recommended_machine = None
2013-03-17 17:06:52 -04:00
if node is not None :
self . parseXML ( node )
2014-09-23 16:05:48 -04:00
def get_recommended_machine ( self , conn , capsguest ) :
if self . _recommended_machine :
return self . _recommended_machine
if not conn . is_test ( ) and not conn . is_qemu ( ) :
return None
if capsguest . arch == " ppc64 " and " pseries " in self . machines :
return " pseries "
if capsguest . arch in [ " armv7l " , " aarch64 " ] :
if " virt " in self . machines :
return " virt "
if " vexpress-a15 " in self . machines :
return " vexpress-a15 "
return None
def set_recommended_machine ( self , machine ) :
self . _recommended_machine = machine
2013-03-17 17:06:52 -04:00
def parseXML ( self , node ) :
child = node . children
machines = [ ]
while child :
if child . name == " emulator " :
self . emulator = child . content
elif child . name == " machine " :
machines . append ( child . content )
canon = child . prop ( " canonical " )
if canon :
machines . append ( canon )
machines . append ( child . content )
child = child . next
if len ( machines ) > 0 :
self . machines = machines
def is_accelerated ( self ) :
return self . hypervisor_type in [ " kvm " , " kqemu " ]
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class Topology ( object ) :
def __init__ ( self , node = None ) :
self . cells = [ ]
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
child = node . children
if child . name == " cells " :
for cell in child . children :
if cell . name == " cell " :
self . cells . append ( TopologyCell ( cell ) )
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class TopologyCell ( object ) :
def __init__ ( self , node = None ) :
self . id = None
self . cpus = [ ]
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
self . id = int ( node . prop ( " id " ) )
2013-11-22 11:28:09 +01:00
for child in node . children :
if child . name == " cpus " :
for cpu in child . children :
if cpu . name == " cpu " :
self . cpus . append ( TopologyCPU ( cpu ) )
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class TopologyCPU ( object ) :
def __init__ ( self , node = None ) :
self . id = None
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
self . id = int ( node . prop ( " id " ) )
class SecurityModel ( object ) :
def __init__ ( self , node = None ) :
self . model = None
self . doi = None
2013-11-15 11:08:41 +01:00
self . baselabels = { }
2013-03-17 17:06:52 -04:00
2014-05-02 10:20:59 -04:00
if node is not None :
2013-03-17 17:06:52 -04:00
self . parseXML ( node )
def parseXML ( self , node ) :
for child in node . children or [ ] :
if child . name == " model " :
self . model = child . content
elif child . name == " doi " :
self . doi = child . content
2013-11-15 11:08:41 +01:00
elif child . name == " baselabel " :
typ = child . prop ( " type " )
self . baselabels [ typ ] = child . content
2013-03-17 17:06:52 -04:00
2013-04-13 14:34:52 -04:00
2013-03-17 17:06:52 -04:00
class Capabilities ( object ) :
2013-07-06 14:12:13 -04:00
def __init__ ( self , xml ) :
2013-03-17 17:06:52 -04:00
self . host = None
self . guests = [ ]
2013-07-06 14:12:13 -04:00
self . xml = xml
2013-03-17 17:06:52 -04:00
self . _topology = None
self . _cpu_values = None
2013-07-06 14:12:13 -04:00
util . parse_node_helper ( self . xml , " capabilities " ,
self . parseXML ,
RuntimeError )
2013-03-17 17:06:52 -04:00
def _is_xen ( self ) :
for g in self . guests :
if g . os_type != " xen " :
continue
for d in g . domains :
if d . hypervisor_type == " xen " :
return True
return False
def no_install_options ( self ) :
"""
Return True if there are no install options available
"""
for g in self . guests :
if len ( g . domains ) > 0 :
return False
return True
def hw_virt_supported ( self ) :
"""
Return True if the machine supports hardware virtualization .
For some cases ( like qemu caps pre libvirt 0.7 .4 ) this info isn ' t
sufficiently provided , so we will return True in cases that we
aren ' t sure.
"""
has_hvm_guests = False
for g in self . guests :
if g . os_type == " hvm " :
has_hvm_guests = True
break
# Obvious case of feature being specified
2014-01-18 13:23:30 -05:00
if ( self . host . cpu . features [ " vmx " ] == FEATURE_ON or
self . host . cpu . features [ " svm " ] == FEATURE_ON ) :
2013-03-17 17:06:52 -04:00
return True
# Xen seems to block the vmx/svm feature bits from cpuinfo?
# so make sure no hvm guests are listed
if self . _is_xen ( ) and has_hvm_guests :
return True
# If there is other features, but no virt bit, then HW virt
# isn't supported
2014-01-18 13:23:30 -05:00
if len ( self . host . cpu . features . names ( ) ) :
2013-03-17 17:06:52 -04:00
return False
# Xen caps have always shown this info, so if we didn't find any
2013-06-30 14:33:01 -04:00
# features, the host really doesn't have the nec support
2013-03-17 17:06:52 -04:00
if self . _is_xen ( ) :
return False
# Otherwise, we can't be sure, because there was a period for along
# time that qemu caps gave no indication one way or the other.
return True
def is_kvm_available ( self ) :
"""
Return True if kvm guests can be installed
"""
for g in self . guests :
if g . os_type != " hvm " :
continue
for d in g . domains :
if d . hypervisor_type == " kvm " :
return True
return False
def is_xenner_available ( self ) :
"""
Return True if xenner install option is available
"""
for g in self . guests :
if g . os_type != " xen " :
continue
for d in g . domains :
if d . hypervisor_type == " kvm " :
return True
return False
def is_bios_virt_disabled ( self ) :
"""
Try to determine if fullvirt may be disabled in the bios .
Check is basically :
- We support HW virt
- We appear to be xen
- There are no HVM install options
We don ' t do this check for KVM, since no KVM options may mean
KVM isn ' t installed or the module isn ' t loaded ( and loading the
module will give an appropriate error
"""
if not self . hw_virt_supported ( ) :
return False
if not self . _is_xen ( ) :
return False
for g in self . guests :
if g . os_type == " hvm " :
return False
return True
def support_pae ( self ) :
for g in self . guests :
if " pae " in g . features . names ( ) :
return True
return False
2014-02-17 11:43:53 -05:00
def _guestForOSType ( self , typ = None , arch = None ) :
2013-03-17 17:06:52 -04:00
if self . host is None :
return None
if arch is None :
2014-01-18 13:23:30 -05:00
archs = [ self . host . cpu . arch , None ]
2013-03-17 17:06:52 -04:00
else :
archs = [ arch ]
for a in archs :
for g in self . guests :
2013-04-12 08:26:21 -04:00
if ( typ is None or g . os_type == typ ) and \
2013-03-17 17:06:52 -04:00
( a is None or g . arch == a ) :
return g
def parseXML ( self , node ) :
child = node . children
while child :
if child . name == " host " :
self . host = Host ( child )
elif child . name == " guest " :
self . guests . append ( Guest ( child ) )
child = child . next
2014-03-13 12:52:51 +01:00
def get_cpu_values ( self , conn , arch ) :
2014-03-20 13:48:49 -04:00
if not arch :
return [ ]
2014-03-13 12:52:51 +01:00
if self . _cpu_values :
return self . _cpu_values . get_cpus ( arch , conn )
2013-03-17 17:06:52 -04:00
2014-03-13 12:52:51 +01:00
# Iterate over the available methods until a set of CPU models is found
2014-03-20 13:48:49 -04:00
for mode in ( _CPUAPIValues , _CPUMapFileValues ) :
2014-03-13 12:52:51 +01:00
cpu_values = mode ( )
cpus = cpu_values . get_cpus ( arch , conn )
2014-03-20 13:48:49 -04:00
if len ( cpus ) > 0 :
2014-03-13 12:52:51 +01:00
self . _cpu_values = cpu_values
return cpus
2013-03-17 17:06:52 -04:00
2014-03-13 12:52:51 +01:00
return [ ]
2013-04-13 14:34:52 -04:00
2014-02-17 11:43:53 -05:00
def guest_lookup ( self , os_type = None , arch = None , typ = None , machine = None ) :
2013-07-06 14:12:13 -04:00
"""
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 ( Capabilities Guest , Capabilities Domain ) tuple
"""
2014-02-17 11:43:53 -05:00
guest = self . _guestForOSType ( os_type , arch )
2013-07-06 14:12:13 -04:00
if not guest :
archstr = _ ( " for arch ' %s ' " ) % arch
if not arch :
archstr = " "
osstr = _ ( " virtualization type ' %s ' " ) % os_type
if not os_type :
osstr = _ ( " any virtualization options " )
raise ValueError ( _ ( " Host does not support %(virttype)s %(arch)s " ) %
{ ' virttype ' : osstr , ' arch ' : archstr } )
2014-02-17 11:43:53 -05:00
domain = guest . bestDomainType ( dtype = typ , machine = machine )
2013-07-06 14:12:13 -04:00
if domain is None :
2014-02-17 11:43:53 -05:00
machinestr = " with machine ' %s ' " % machine
2013-07-06 14:12:13 -04:00
if not machine :
machinestr = " "
raise ValueError ( _ ( " Host does not support domain type %(domain)s "
" %(machine)s for virtualization type "
" ' %(virttype)s ' arch ' %(arch)s ' " ) %
{ ' domain ' : typ , ' virttype ' : guest . os_type ,
' arch ' : guest . arch , ' machine ' : machinestr } )
return ( guest , domain )
2013-07-17 07:53:47 -04:00
def build_virtinst_guest ( self , conn , guest , domain ) :
2014-09-12 15:59:22 -04:00
from . guest import Guest as VMGuest
gobj = VMGuest ( conn )
2013-07-17 07:53:47 -04:00
gobj . type = domain . hypervisor_type
gobj . os . os_type = guest . os_type
gobj . os . arch = guest . arch
gobj . os . loader = domain . loader
gobj . emulator = domain . emulator
2014-09-23 16:05:48 -04:00
gobj . os . machine = domain . get_recommended_machine ( conn , guest )
2013-07-17 07:53:47 -04:00
return gobj