Source code for virttest.libvirt_xml.devices.base
"""
Common base classes for devices
"""
import logging
from StringIO import StringIO
from virttest import xml_utils
from virttest.libvirt_xml import base, xcepts, accessors
from virttest.xml_utils import ElementTree
[docs]class UntypedDeviceBase(base.LibvirtXMLBase):
"""
Base class implementing common functions for all device XML w/o a type attr.
"""
__slots__ = ('device_tag',)
# Subclasses are expected to hide device_tag
def __init__(self, device_tag, virsh_instance=base.virsh):
"""
Initialize untyped device instance's basic XML with device_tag
"""
super(UntypedDeviceBase, self).__init__(virsh_instance=virsh_instance)
# Just a regular dictionary value
# (Using a property to change element tag won't work)
self['device_tag'] = device_tag
# setup bare-bones XML
self.xml = u"<%s/>" % device_tag
[docs] def from_element(self, element):
"""
Stateful component to helper method for new_from_element.
"""
class_name = self.__class__.__name__
if element.tag != class_name.lower():
raise xcepts.LibvirtXMLError('Refusing to create %s instance'
'from %s tagged element'
% (class_name, element.tag))
# XMLTreeFile only supports element trees
etree = xml_utils.ElementTree.ElementTree(element)
# ET only writes to open file-like objects
xmlstr = StringIO()
# Need element tree string value to initialize LibvirtXMLBase.xml
etree.write(xmlstr, xml_utils.ENCODING)
# Create a new XMLTreeFile object based on string input
self.xml = xmlstr.getvalue()
@classmethod
[docs] def new_from_element(cls, element, virsh_instance=base.virsh):
"""
Create a new device XML instance from an single ElementTree element
"""
# subclasses __init__ only takes virsh_instance parameter
instance = cls(virsh_instance=virsh_instance)
instance.from_element(element)
return instance
@classmethod
[docs] def new_from_dict(cls, properties, virsh_instance=base.virsh):
"""
Create a new device XML instance from a dict-like object
"""
instance = cls(virsh_instance=virsh_instance)
for key, value in properties.items():
setattr(instance, key, value)
return instance
# Add accessors here to be used by any elements
def _get_list(self, tag_filter):
"""
Return a list of dictionaries containing element's attributes.
"""
dict_list = []
elements = self.xmltreefile.findall(tag_filter)
for element in elements:
dict_list.append(dict(element.items()))
return dict_list
def _set_list(self, tag_name, value):
"""
Set all elements to the value list of dictionaries of element's
attributes.
"""
xcept = xcepts.LibvirtXMLError("Must set %s child %s elements from"
" a list of dictionary"
% (self.device_tag, tag_name))
if not isinstance(value, list):
raise xcept
# Start with clean slate
self._del_list(tag_name)
for dict_item in value:
if not isinstance(dict_item, dict):
raise xcept
ElementTree.SubElement(self.xmltreefile.getroot(),
tag_name, dict_item)
self.xmltreefile.write()
def _del_list(self, tag_filter):
"""
Remove the list of dictionaries containing each element's attributes.
"""
element = self.xmltreefile.find(tag_filter)
while element is not None:
self.xmltreefile.getroot().remove(element)
element = self.xmltreefile.find(tag_filter)
self.xmltreefile.write()
def _add_item(self, prop_name, **attributes):
"""
Convenience method for appending an element from dictionary of
attributes.
"""
items = self[prop_name] # xml element name
items.append(attributes)
self[prop_name] = items
def _update_item(self, prop_name, index, **attributes):
"""
Convenience method for merging values into an element's attributes
"""
items = self[prop_name] # xml element name
item = items[index]
item.update(attributes)
self[prop_name] = items
[docs]class TypedDeviceBase(UntypedDeviceBase):
"""
Base class implementing common functions for all device XML w/o a type attr.
"""
__slots__ = ('type_name',)
# Subclasses are expected to hide device_tag
def __init__(self, device_tag, type_name, virsh_instance=base.virsh):
"""
Initialize Typed device instance's basic XML with type_name & device_tag
"""
# generate getter, setter, deleter for 'type_name' property
accessors.XMLAttribute('type_name', self,
# each device is it's own XML "document"
# because python 2.6 ElementPath is broken
parent_xpath='/',
tag_name=device_tag,
attribute='type')
super(TypedDeviceBase, self).__init__(device_tag=device_tag,
virsh_instance=virsh_instance)
# Calls accessor to modify xml
self.type_name = type_name
@classmethod
[docs] def new_from_element(cls, element, virsh_instance=base.virsh):
"""
Hides type_name from superclass new_from_element().
"""
type_name = element.get('type', None)
# subclasses must hide device_tag parameter
instance = cls(type_name=type_name,
virsh_instance=virsh_instance)
instance.from_element(element)
return instance
# Metaclass is a type-of-types or a class-generating class.
# Using it here to avoid copy-pasting very similar class
# definitions into every unwritten device module.
#
# Example usage for stub disk device:
#
# class Disk(base.TypedDeviceBase):
# __metaclass__ = base.StubDeviceMeta
# _device_tag = 'disk'
# _def_type_name = 'block'
#
# will become defined as:
#
# class Disk(base.TypedDeviceBase):
# def __init__(self, type_name='block', virsh_instance=base.virsh):
# issue_warning()
# super(Disk, self).__init__(device_tag='disk'),
# type_name=type_name,
# virsh_instance=virsh_instance)
#
[docs]class StubDeviceMeta(type):
"""
Metaclass for generating stub Device classes where not fully implemented yet
"""
warning_issued = False
# mcs is the class object being generated, name is it's name, bases
# is tuple of all baseclasses, and dct is what will become mcs's
# __dict__ after super(...).__init__() is called.
def __init__(mcs, name, bases, dct):
"""
Configuration for new class
"""
# Keep pylint happy
dct = dict(dct)
# Call type() to setup new class and store it as 'mcs'
super(StubDeviceMeta, mcs).__init__(name, bases, dct)
# Needed for UntypedDeviceBase __init__'s default argument value
# i.e. device_tag='disk' as specified by specific device class
if not hasattr(mcs, '_device_tag'):
raise ValueError(
"Class %s requires a _device_tag attribute" % name)
# Same message for both TypedDeviceBase & UntypedDeviceBase subclasses
message = ("Detected use of a stub device XML for a %s class. These "
"only implement a minimal interface that is very likely to "
"change in future versions. This warning will only be "
" logged once." % name)
def issue_warning():
"""
Closure for created __init__ to only print message once.
"""
# Examine the CLASS variable
if not StubDeviceMeta.warning_issued:
# Set the CLASS variable
StubDeviceMeta.warning_issued = True
logging.warning(message)
else:
pass # do nothing
# Create the proper init function for subclass type
if TypedDeviceBase in bases:
# Needed for TypedDeviceBase __init__'s default argument value
# i.e. type_name='pci' as specified by specific device class.
if not hasattr(mcs, '_def_type_name'):
raise ValueError("TypedDevice sub-Class %s must define a "
"_def_type_name attribute" % name)
# form __init__() and it's arguments for generated class
def stub_init(self, type_name=getattr(mcs, '_def_type_name'),
virsh_instance=base.virsh):
"""
Initialize stub typed device instance
"""
# issue warning only when some code instantiats
# object from generated class
issue_warning()
# Created class __init__ still needs to call superclass
# __init__ (i.e. UntypedDeviceBase or TypedDeviceBase)
TypedDeviceBase.__init__(self, device_tag=getattr(mcs,
'_device_tag'),
type_name=type_name,
virsh_instance=virsh_instance)
elif UntypedDeviceBase in bases:
# generate __init__() for untyped devices (similar to above)
def stub_init(self, virsh_instance=base.virsh):
"""
Initialize stub un-typed device instance
"""
issue_warning()
UntypedDeviceBase.__init__(self, device_tag=getattr(mcs,
'_device_tag'),
virsh_instance=virsh_instance)
else:
# unexpected usage
raise TypeError("Class %s is not a subclass of TypedDeviceBase or "
"UntypedDeviceBase")
# Point the generated class's __init__ at the generated function above
setattr(mcs, '__init__', stub_init)