XMLBuilder: Provide default fget and fset impls

This is the first step towards unifying the XML handling split between
parsing and creating from scratch. Demonstrate it with VirtualDisk.iotune
bits.
This commit is contained in:
Cole Robinson 2013-07-12 10:49:15 -04:00
parent f620b317b9
commit dcd632da80
7 changed files with 132 additions and 135 deletions

View File

@ -43,11 +43,21 @@
</disk>
<disk type="block" device="floppy">
<target dev="fda" bus="fdc"/>
<iotune>
<total_bytes_sec>1</total_bytes_sec>
<total_iops_sec>2</total_iops_sec>
</iotune>
</disk>
<disk type="block" device="floppy">
<driver name="phy" cache="none"/>
<source dev="/dev/loop0"/>
<target dev="fdb" bus="fdc"/>
<iotune>
<read_bytes_sec>5555</read_bytes_sec>
<read_iops_sec>1234</read_iops_sec>
<write_bytes_sec>3</write_bytes_sec>
<write_iops_sec>4</write_iops_sec>
</iotune>
</disk>
<disk type="block" device="disk">
<driver name="qemu" type="qcow2" cache="none" io="threads"/>

View File

@ -550,19 +550,28 @@ class TestXMLConfig(unittest.TestCase):
g.add_device(VirtualDisk(g.conn, path=None,
device=VirtualDisk.DEVICE_CDROM,
bus="scsi"))
g.add_device(VirtualDisk(g.conn, path=None,
device=VirtualDisk.DEVICE_FLOPPY))
d1 = VirtualDisk(g.conn, path="/dev/loop0",
device=VirtualDisk.DEVICE_FLOPPY,
driverName="phy")
d1.driver_cache = "none"
g.add_device(d1)
d2 = VirtualDisk(g.conn, path="/dev/loop0",
bus="virtio", driverName="qemu",
driverType="qcow2")
d2.driver_cache = "none"
d2.driver_io = "threads"
g.add_device(d2)
d = VirtualDisk(g.conn, path=None,
device=VirtualDisk.DEVICE_FLOPPY)
d.iotune_tbs = 1
d.iotune_tis = 2
g.add_device(d)
d = VirtualDisk(g.conn, path="/dev/loop0",
device=VirtualDisk.DEVICE_FLOPPY,
driverName="phy")
d.driver_cache = "none"
d.iotune_rbs = 5555
d.iotune_ris = 1234
d.iotune_wbs = 3
d.iotune_wis = 4
g.add_device(d)
d = VirtualDisk(g.conn, path="/dev/loop0",
bus="virtio", driverName="qemu",
driverType="qcow2")
d.driver_cache = "none"
d.driver_io = "threads"
g.add_device(d)
self._compare(g, "boot-many-disks2", False)

View File

@ -45,6 +45,12 @@
</disk>
<disk type="block" device="floppy">
<target dev="fda" bus="fdc"/>
<iotune>
<read_iops_sec>1</read_iops_sec>
<read_bytes_sec>2</read_bytes_sec>
<write_iops_sec>3</write_iops_sec>
<write_bytes_sec>4</write_bytes_sec>
</iotune>
</disk>
<disk type="block" device="floppy">
<driver name="phy" cache="none"/>

View File

@ -42,6 +42,10 @@
</disk>
<disk type="file" device="floppy">
<target dev="fda" bus="fdc"/>
<iotune>
<total_iops_sec>5</total_iops_sec>
<total_bytes_sec>6</total_bytes_sec>
</iotune>
<source file="/default-pool/default-vol"/>
<shareable/>
<driver cache="writeback" io="native"/>

View File

@ -249,10 +249,10 @@ class XMLParseTest(unittest.TestCase):
disks = guest.get_devices("disk")
disk1 = disks[0]
disk1.size = 1
disk2 = disks[2]
disk2.size = 1
disk3 = disks[5]
disk3 = disks[2]
disk3.size = 1
disk6 = disks[5]
disk6.size = 1
check = self._make_checker(disk1)
check("path", "/tmp/test.img", "/dev/loop0")
@ -260,7 +260,7 @@ class XMLParseTest(unittest.TestCase):
check("driver_type", None, "raw")
check("serial", "WD-WMAP9A966149", "frob")
check = self._make_checker(disk2)
check = self._make_checker(disk3)
check("path", "/dev/loop0", None)
check("device", "cdrom", "floppy")
check("read_only", True, False)
@ -268,12 +268,19 @@ class XMLParseTest(unittest.TestCase):
check("bus", None, "fdc")
check("error_policy", "stop", None)
check = self._make_checker(disk3)
check = self._make_checker(disk6)
check("path", None, "/default-pool/default-vol")
check("shareable", False, True)
check("driver_cache", None, "writeback")
check("driver_io", None, "threads")
check("driver_io", "threads", "native")
check("iotune_ris", 1, 0)
check("iotune_rbs", 2, 0)
check("iotune_wis", 3, 0)
check("iotune_wbs", 4, 0)
check("iotune_tis", 0, 5)
check("iotune_tbs", 0, 6)
self._alter_compare(guest.get_xml_config(), outfile)

View File

@ -579,12 +579,6 @@ class VirtualDisk(VirtualDevice):
self._error_policy = None
self._serial = None
self._target = None
self._iotune_rbs = None
self._iotune_ris = None
self._iotune_tbs = None
self._iotune_tis = None
self._iotune_wbs = None
self._iotune_wis = None
self._validate = validate
self._nomanaged = nomanaged
@ -833,81 +827,22 @@ class VirtualDisk(VirtualDevice):
serial = _xml_property(_get_serial, _set_serial,
xpath="./serial")
def _get_iotune_rbs(self):
return self._iotune_rbs
def _set_iotune_rbs(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune read bytes per second value must be an "
"integer"))
self._iotune_rbs = val
iotune_rbs = _xml_property(_get_iotune_rbs,
_set_iotune_rbs,
xpath="./iotune/read_bytes_sec",
iotune_rbs = _xml_property(xpath="./iotune/read_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_ris(self):
return self._iotune_ris
def _set_iotune_ris(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune read iops per second value must be an "
"integer"))
self._iotune_ris = val
iotune_ris = _xml_property(_get_iotune_ris,
_set_iotune_ris,
xpath="./iotune/read_iops_sec",
iotune_ris = _xml_property(xpath="./iotune/read_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_tbs(self):
return self._iotune_tbs
def _set_iotune_tbs(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune total bytes per second value must be an "
"integer"))
self._iotune_tbs = val
iotune_tbs = _xml_property(_get_iotune_tbs,
_set_iotune_tbs,
xpath="./iotune/total_bytes_sec",
iotune_tbs = _xml_property(xpath="./iotune/total_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_tis(self):
return self._iotune_tis
def _set_iotune_tis(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune total iops per second value must be an "
"integer"))
self._iotune_tis = val
iotune_tis = _xml_property(_get_iotune_tis,
_set_iotune_tis,
xpath="./iotune/total_iops_sec",
iotune_tis = _xml_property(xpath="./iotune/total_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_wbs(self):
return self._iotune_wbs
def _set_iotune_wbs(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune write bytes per second value must be an "
"integer"))
self._iotune_wbs = val
iotune_wbs = _xml_property(_get_iotune_wbs,
_set_iotune_wbs,
xpath="./iotune/write_bytes_sec",
iotune_wbs = _xml_property(xpath="./iotune/write_bytes_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
def _get_iotune_wis(self):
return self._iotune_wis
def _set_iotune_wis(self, val):
if not isinstance(val, int) or val < 0:
raise ValueError(_("IOTune write iops per second value must be an "
"integer"))
self._iotune_wis = val
iotune_wis = _xml_property(_get_iotune_wis,
_set_iotune_wis,
xpath="./iotune/write_iops_sec",
iotune_wis = _xml_property(xpath="./iotune/write_iops_sec",
get_converter=lambda s, x: int(x or 0),
set_converter=lambda s, x: int(x))
@ -1445,29 +1380,11 @@ class VirtualDisk(VirtualDevice):
ret += (" <serial>%s</serial>\n" %
util.xml_escape(self.serial))
if (self.iotune_rbs or self.iotune_ris or
self.iotune_tbs or self.iotune_tis or
self.iotune_wbs or self.iotune_wis):
ret += " <iotune>\n"
if self.iotune_rbs:
ret += " <read_bytes_sec>%s</read_bytes_sec>\n" % (self.iotune_rbs)
if self.iotune_ris:
ret += " <read_iops_sec>%s</read_iops_sec>\n" % (self.iotune_ris)
if self.iotune_tbs:
ret += " <total_bytes_sec>%s</total_bytes_sec>\n" % (self.iotune_tbs)
if self.iotune_tis:
ret += " <total_iops_sec>%s</total_iops_sec>\n" % (self.iotune_tis)
if self.iotune_wbs:
ret += " <write_bytes_sec>%s</write_bytes_sec>\n" % (self.iotune_wbs)
if self.iotune_wis:
ret += " <write_iops_sec>%s</write_iops_sec>\n" % (self.iotune_wis)
ret += " </iotune>\n"
addr = self.indent(self.address.get_xml_config(), 6)
if addr:
ret += addr
ret += " </disk>"
return ret
return self._add_parse_bits(ret)
def is_size_conflict(self):
"""

View File

@ -244,10 +244,33 @@ def _xml_property(fget=None, fset=None, fdel=None, doc=None,
@param default_converter: If the virtinst value is "default", use
this function to get the actual XML value
"""
# pylint: disable=W0212
# Accessing _xml vals of self. This should be a class method, but
# we break it out to be more readable
def _findpropname(self):
for key, val in self.__class__.__dict__.items():
if val is retprop:
return key
raise RuntimeError("Didn't find expected property")
def _default_fset(self, val, *args, **kwargs):
ignore = args
ignore = kwargs
propname = _findpropname(self)
self._propstore[propname] = val
if propname in self._proporder:
self._proporder.remove(propname)
self._proporder.append(propname)
def _default_fget(self, *args, **kwargs):
ignore = args
ignore = kwargs
return self._propstore.get(_findpropname(self), None)
if not fget:
fget = _default_fget
if not fset:
fset = _default_fset
def new_getter(self, *args, **kwargs):
val = None
@ -258,15 +281,14 @@ def _xml_property(fget=None, fset=None, fdel=None, doc=None,
if default_converter and getval == "default":
return getval
usexpath = xpath
node_xpath = xpath
if xml_get_xpath:
usexpath = xml_get_xpath(self)
if usexpath is None:
node_xpath = xml_get_xpath(self)
if node_xpath is None:
return getval
nodes = util.listify(_get_xpath_node(self._xml_ctx,
usexpath, is_multi))
node_xpath, is_multi))
if nodes:
ret = []
for node in nodes:
@ -292,7 +314,6 @@ def _xml_property(fget=None, fset=None, fdel=None, doc=None,
def new_setter(self, val, *args, **kwargs):
# Do this regardless, for validation purposes
fset(self, val, *args, **kwargs)
if not self._xml_node:
return
@ -303,28 +324,28 @@ def _xml_property(fget=None, fset=None, fdel=None, doc=None,
elif default_converter and val == "default":
val = default_converter(self)
nodexpath = xpath
node_xpath = xpath
if xml_set_xpath:
nodexpath = xml_set_xpath(self)
node_xpath = xml_set_xpath(self)
if nodexpath is None:
if node_xpath is None:
return
nodes = util.listify(_get_xpath_node(self._xml_ctx,
nodexpath, is_multi))
node_xpath, is_multi))
xpath_list = nodexpath
xpath_list = node_xpath
if xml_set_list:
xpath_list = xml_set_list(self)
node_map = _tuplify_lists(nodes, val, xpath_list)
for node, val, usexpath in node_map:
for node, val, use_xpath in node_map:
if node:
usexpath = node.nodePath()
use_xpath = node.nodePath()
if val not in [None, False]:
if not node:
node = _build_xpath_node(self._xml_node, usexpath)
node = _build_xpath_node(self._xml_node, use_xpath)
if val is True:
# Boolean property, creating the node is enough
@ -332,20 +353,30 @@ def _xml_property(fget=None, fset=None, fdel=None, doc=None,
else:
node.setContent(util.xml_escape(str(val)))
else:
_remove_xpath_node(self._xml_node, usexpath)
_remove_xpath_node(self._xml_node, use_xpath)
if fdel:
# Not tested
raise RuntimeError("XML deleter not yet supported.")
return property(fget=new_getter, fset=new_setter, doc=doc)
retprop = property(fget=new_getter, fset=new_setter, doc=doc)
return retprop
class XMLBuilderDomain(object):
"""
Base for all classes which build or parse domain XML
"""
@staticmethod
def indent(xmlstr, level):
xml = ""
if not xmlstr:
return xml
for l in iter(xmlstr.splitlines()):
xml += " " * level + l + "\n"
return xml
_dumpxml_xpath = "."
def __init__(self, conn, parsexml=None, parsexmlnode=None):
@ -363,6 +394,8 @@ class XMLBuilderDomain(object):
self._xml_node = None
self._xml_ctx = None
self._xml_root_doc = None
self._propstore = {}
self._proporder = []
if parsexml or parsexmlnode:
self._parsexml(parsexml, parsexmlnode)
@ -429,6 +462,27 @@ class XMLBuilderDomain(object):
self._set_xml_context()
def _add_parse_bits(self, xml):
if not self._propstore or self._is_parse():
return xml
try:
self._parsexml(xml, None)
for key in self._proporder[:]:
setattr(self, key, self._propstore[key])
ret = self.get_xml_config()
for c in xml:
if c != " ":
break
ret = " " + ret
return ret.strip("\n")
finally:
self._xml_root_doc = None
self._xml_node = None
if self._xml_ctx:
self._xml_ctx.xpathFreeContext()
self._xml_ctx = None
def _get_xml_config(self):
"""
Internal XML building function. Must be overwritten by subclass
@ -449,13 +503,3 @@ class XMLBuilderDomain(object):
return _sanitize_libxml_xml(node.serialize())
return self._get_xml_config(*args, **kwargs)
@staticmethod
def indent(xmlstr, level):
xml = ""
if not xmlstr:
return xml
for l in iter(xmlstr.splitlines()):
xml += " " * level + l + "\n"
return xml