2013-03-18 05:06:52 +08:00
|
|
|
#
|
|
|
|
# Base class for all VM devices
|
|
|
|
#
|
|
|
|
# Copyright 2008 Red Hat, Inc.
|
|
|
|
# Cole Robinson <crobinso@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
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# 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 copy
|
2013-07-13 03:16:29 +08:00
|
|
|
import os
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
import libxml2
|
|
|
|
|
2013-04-11 07:48:07 +08:00
|
|
|
from virtinst import util
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-15 21:49:46 +08:00
|
|
|
# pylint: disable=W0212
|
|
|
|
# This whole file is calling around into non-public functions that we
|
|
|
|
# don't want regular API users to touch
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-13 03:16:29 +08:00
|
|
|
_trackprops = bool("VIRTINST_TEST_TRACKPROPS" in os.environ)
|
|
|
|
_allprops = []
|
|
|
|
_seenprops = []
|
|
|
|
|
|
|
|
|
2013-07-10 20:05:08 +08:00
|
|
|
class _DocCleanupWrapper(object):
|
|
|
|
def __init__(self, doc):
|
|
|
|
self._doc = doc
|
|
|
|
def __del__(self):
|
|
|
|
self._doc.freeDoc()
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-04-12 04:48:39 +08:00
|
|
|
|
2013-07-14 08:47:19 +08:00
|
|
|
class _CtxCleanupWrapper(object):
|
|
|
|
def __init__(self, ctx):
|
|
|
|
self._ctx = ctx
|
|
|
|
def __del__(self):
|
|
|
|
self._ctx.xpathFreeContext()
|
|
|
|
self._ctx = None
|
|
|
|
def __getattr__(self, attrname):
|
|
|
|
return getattr(self._ctx, attrname)
|
|
|
|
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _indent(xmlstr, level):
|
|
|
|
xml = ""
|
|
|
|
if not xmlstr:
|
|
|
|
return xml
|
|
|
|
if not level:
|
|
|
|
return xmlstr
|
|
|
|
return "\n".join((" " * level + l) for l in xmlstr.splitlines())
|
|
|
|
|
|
|
|
|
2013-04-12 04:48:39 +08:00
|
|
|
def _tuplify_lists(*args):
|
|
|
|
"""
|
|
|
|
Similar to zip(), but use None if lists aren't long enough, and
|
|
|
|
don't skip any None list entry
|
|
|
|
"""
|
|
|
|
args = [util.listify(l) for l in args]
|
|
|
|
maxlen = max([len(l) for l in args])
|
|
|
|
|
|
|
|
ret = []
|
|
|
|
for idx in range(maxlen):
|
|
|
|
tup = tuple()
|
|
|
|
for l in args:
|
|
|
|
tup += (idx >= len(l) and (None,) or (l[idx],))
|
|
|
|
ret.append(tup)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
def _sanitize_libxml_xml(xml):
|
|
|
|
# Strip starting <?...> line
|
|
|
|
if xml.startswith("<?"):
|
|
|
|
ignore, xml = xml.split("\n", 1)
|
|
|
|
if not xml.endswith("\n") and xml.count("\n"):
|
|
|
|
xml += "\n"
|
|
|
|
return xml
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
def _get_xpath_node(ctx, xpath, is_multi=False):
|
|
|
|
node = ctx.xpathEval(xpath)
|
|
|
|
if not is_multi:
|
|
|
|
return (node and node[0] or None)
|
|
|
|
return node
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
def _build_xpath_node(ctx, xpath, addnode=None):
|
|
|
|
"""
|
|
|
|
Build all nodes required to set an xpath. If we have XML <foo/>, and want
|
|
|
|
to set xpath /foo/bar/baz@booyeah, we create node 'bar' and 'baz'
|
|
|
|
returning the last node created.
|
|
|
|
"""
|
|
|
|
parentpath = ""
|
|
|
|
parentnode = None
|
|
|
|
|
|
|
|
def prevSibling(node):
|
|
|
|
parent = node.get_parent()
|
|
|
|
if not parent:
|
|
|
|
return None
|
|
|
|
|
|
|
|
prev = None
|
|
|
|
for child in parent.children:
|
|
|
|
if child == node:
|
|
|
|
return prev
|
|
|
|
prev = child
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
def make_node(parentnode, newnode):
|
|
|
|
# Add the needed parent node, try to preserve whitespace by
|
|
|
|
# looking for a starting TEXT node, and copying it
|
|
|
|
def node_is_text(n):
|
|
|
|
return bool(n and n.type == "text" and not n.content.count("<"))
|
|
|
|
|
|
|
|
sib = parentnode.get_last()
|
|
|
|
if not node_is_text(sib):
|
|
|
|
# This case is when we add a child element to a node for the
|
|
|
|
# first time, like:
|
|
|
|
#
|
|
|
|
# <features/>
|
|
|
|
# to
|
|
|
|
# <features>
|
|
|
|
# <acpi/>
|
|
|
|
# </features>
|
|
|
|
prevsib = prevSibling(parentnode)
|
|
|
|
if node_is_text(prevsib):
|
|
|
|
sib = libxml2.newText(prevsib.content)
|
|
|
|
else:
|
2013-07-15 21:49:46 +08:00
|
|
|
sib = libxml2.newText("\n")
|
2013-03-18 05:06:52 +08:00
|
|
|
parentnode.addChild(sib)
|
|
|
|
|
2013-07-15 21:49:46 +08:00
|
|
|
# This case is adding a child element to an already properly
|
2013-03-18 05:06:52 +08:00
|
|
|
# spaced element. Example:
|
|
|
|
# <features>
|
2013-07-15 21:49:46 +08:00
|
|
|
# <acpi/>
|
2013-03-18 05:06:52 +08:00
|
|
|
# </features>
|
|
|
|
# to
|
|
|
|
# <features>
|
2013-07-15 21:49:46 +08:00
|
|
|
# <acpi/>
|
|
|
|
# <apic/>
|
2013-03-18 05:06:52 +08:00
|
|
|
# </features>
|
|
|
|
sib = parentnode.get_last()
|
|
|
|
content = sib.content
|
|
|
|
sib = sib.addNextSibling(libxml2.newText(" "))
|
|
|
|
txt = libxml2.newText(content)
|
|
|
|
|
|
|
|
sib.addNextSibling(newnode)
|
|
|
|
newnode.addNextSibling(txt)
|
|
|
|
return newnode
|
|
|
|
|
|
|
|
nodelist = xpath.split("/")
|
|
|
|
for idx in range(len(nodelist)):
|
|
|
|
nodename = nodelist[idx]
|
|
|
|
if not nodename:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# If xpath is a node property, set it and move on
|
|
|
|
if nodename.startswith("@"):
|
|
|
|
nodename = nodename.strip("@")
|
|
|
|
parentnode = parentnode.setProp(nodename, "")
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not parentpath:
|
|
|
|
parentpath = nodename
|
|
|
|
else:
|
|
|
|
parentpath += "/%s" % nodename
|
|
|
|
|
|
|
|
# Node found, nothing to create for now
|
|
|
|
node = _get_xpath_node(ctx, parentpath)
|
|
|
|
if node:
|
|
|
|
parentnode = node
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not parentnode:
|
|
|
|
raise RuntimeError("Could not find XML root node")
|
|
|
|
|
|
|
|
# Remove conditional xpath elements for node creation
|
|
|
|
if nodename.count("["):
|
|
|
|
nodename = nodename[:nodename.index("[")]
|
|
|
|
|
|
|
|
newnode = libxml2.newNode(nodename)
|
|
|
|
parentnode = make_node(parentnode, newnode)
|
|
|
|
|
|
|
|
if addnode:
|
|
|
|
parentnode = make_node(parentnode, addnode)
|
|
|
|
|
|
|
|
return parentnode
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-07-17 04:24:24 +08:00
|
|
|
def _remove_xpath_node(ctx, xpath, dofree=True, root_name=None):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Remove an XML node tree if it has no content
|
|
|
|
"""
|
|
|
|
curxpath = xpath
|
|
|
|
|
|
|
|
while curxpath:
|
|
|
|
is_orig = (curxpath == xpath)
|
|
|
|
node = _get_xpath_node(ctx, curxpath)
|
|
|
|
if curxpath.count("/"):
|
|
|
|
curxpath, ignore = curxpath.rsplit("/", 1)
|
|
|
|
else:
|
|
|
|
curxpath = None
|
|
|
|
|
|
|
|
if not node:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if node.type not in ["attribute", "element"]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if node.type == "element" and (node.children or node.properties):
|
|
|
|
# Only do a deep unlink if it was the original requested path
|
|
|
|
if not is_orig:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Look for preceding whitespace and remove it
|
|
|
|
white = node.get_prev()
|
|
|
|
if white and white.type == "text" and not white.content.count("<"):
|
|
|
|
white.unlinkNode()
|
|
|
|
white.freeNode()
|
|
|
|
|
2013-07-17 04:24:24 +08:00
|
|
|
# Don't unlink the root node. This is usually a programming error,
|
|
|
|
# but the error usually cascades to a different spot and is hard
|
|
|
|
# to pin down. With this we usually get invalid XML which is
|
|
|
|
# easier to debug.
|
|
|
|
if root_name and node.name == root_name:
|
|
|
|
break
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
node.unlinkNode()
|
|
|
|
if dofree:
|
|
|
|
node.freeNode()
|
|
|
|
|
|
|
|
|
2013-07-14 06:56:09 +08:00
|
|
|
class XMLProperty(property):
|
2013-07-18 05:58:24 +08:00
|
|
|
def __init__(self, doc=None, xpath=None, name=None,
|
|
|
|
set_converter=None, validate_cb=None,
|
|
|
|
make_getter_xpath_cb=None, make_setter_xpath_cb=None,
|
2013-07-17 01:04:24 +08:00
|
|
|
is_bool=False, is_tri=False, is_int=False,
|
|
|
|
is_multi=False, is_yesno=False,
|
2013-07-15 22:57:16 +08:00
|
|
|
clear_first=None, default_cb=None, default_name=None):
|
2013-07-14 05:54:46 +08:00
|
|
|
"""
|
|
|
|
Set a XMLBuilder class property that represents a value in the
|
|
|
|
<domain> XML. For example
|
|
|
|
|
2013-07-14 06:56:09 +08:00
|
|
|
name = XMLProperty(get_name, set_name, xpath="/domain/name")
|
2013-07-14 05:54:46 +08:00
|
|
|
|
|
|
|
When building XML from scratch (virt-install), name is a regular
|
|
|
|
class property. When parsing and editting existing guest XML, we
|
|
|
|
use the xpath value to map the name property to the underlying XML
|
|
|
|
definition.
|
|
|
|
|
|
|
|
@param doc: option doc string for the property
|
|
|
|
@param xpath: xpath string which maps to the associated property
|
|
|
|
in a typical XML document
|
2013-07-14 10:13:13 +08:00
|
|
|
@param name: Just a string to print for debugging, only needed
|
|
|
|
if xpath isn't specified.
|
2013-07-14 05:54:46 +08:00
|
|
|
@param set_converter: optional function for converting the property
|
|
|
|
value from the virtinst API to the guest XML. For example,
|
2013-07-14 11:07:01 +08:00
|
|
|
the Guest.memory API was once in MB, but the libvirt domain
|
|
|
|
memory API is in KB. So, if xpath is specified, on a 'get'
|
|
|
|
operation we convert the XML value with int(val) / 1024.
|
2013-07-18 05:58:24 +08:00
|
|
|
@param validate_cb: Called once when value is set, should
|
|
|
|
raise a RuntimeError if the value is not proper.
|
|
|
|
@param make_getter_xpath_cb:
|
|
|
|
@param make_setter_xpath_cb: Not all props map cleanly to a
|
|
|
|
static xpath. This allows passing functions which generate
|
|
|
|
an xpath for getting or setting.
|
2013-07-14 05:54:46 +08:00
|
|
|
@param is_bool: Whether this is a boolean property in the XML
|
2013-07-14 09:04:27 +08:00
|
|
|
@param is_tri: Boolean XML property, but return None if there's
|
|
|
|
no value set.
|
2013-07-14 05:54:46 +08:00
|
|
|
@param is_multi: Whether data is coming multiple or a single node
|
2013-07-14 09:49:32 +08:00
|
|
|
@param is_int: Whethere this is an integer property in the XML
|
2013-07-17 01:04:24 +08:00
|
|
|
@param is_yesno: Whethere this is a yes/no property in the XML
|
2013-07-14 05:54:46 +08:00
|
|
|
@param clear_first: List of xpaths to unset before any 'set' operation.
|
|
|
|
For those weird interdependent XML props like disk source type and
|
|
|
|
path attribute.
|
2013-07-15 05:02:42 +08:00
|
|
|
@param default_cb: If building XML from scratch, and this property
|
|
|
|
is never explicitly altered, this function is called for setting
|
|
|
|
a default value in the XML, and for any 'get' call before the
|
|
|
|
first explicit 'set'.
|
2013-07-15 22:57:16 +08:00
|
|
|
@param default_name: If the user does a set and passes in this
|
|
|
|
value, instead use the value of default_cb()
|
2013-07-14 05:54:46 +08:00
|
|
|
"""
|
|
|
|
|
2013-07-13 03:16:29 +08:00
|
|
|
self._xpath = xpath
|
2013-07-14 10:13:13 +08:00
|
|
|
self._name = name or xpath
|
|
|
|
if not self._name:
|
|
|
|
raise RuntimeError("XMLProperty: name or xpath must be passed.")
|
2013-07-13 03:16:29 +08:00
|
|
|
|
2013-07-14 09:04:27 +08:00
|
|
|
self._is_tri = is_tri
|
|
|
|
self._is_bool = is_bool or is_tri
|
2013-07-14 09:49:32 +08:00
|
|
|
self._is_int = is_int
|
2013-07-14 05:54:46 +08:00
|
|
|
self._is_multi = is_multi
|
2013-07-17 01:04:24 +08:00
|
|
|
self._is_yesno = is_yesno
|
2013-07-14 05:54:46 +08:00
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
self._xpath_for_getter_cb = make_getter_xpath_cb
|
|
|
|
self._xpath_for_setter_cb = make_setter_xpath_cb
|
2013-07-14 05:54:46 +08:00
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
self._validate_cb = validate_cb
|
2013-07-14 05:54:46 +08:00
|
|
|
self._convert_value_for_setter_cb = set_converter
|
|
|
|
self._setter_clear_these_first = clear_first or []
|
2013-07-15 05:02:42 +08:00
|
|
|
self._default_cb = default_cb
|
2013-07-15 22:57:16 +08:00
|
|
|
self._default_name = default_name
|
2013-07-15 05:02:42 +08:00
|
|
|
|
2013-07-17 21:57:15 +08:00
|
|
|
if sum([int(bool(i)) for i in
|
|
|
|
[self._is_bool, self._is_int, self._is_yesno]]) > 1:
|
2013-07-15 05:02:42 +08:00
|
|
|
raise RuntimeError("Conflict property converter options.")
|
2013-07-14 05:54:46 +08:00
|
|
|
|
2013-07-15 22:57:16 +08:00
|
|
|
if self._default_name and not self._default_cb:
|
|
|
|
raise RuntimeError("default_name requires default_cb.")
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
if _trackprops:
|
|
|
|
_allprops.append(self)
|
2013-07-14 05:54:46 +08:00
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
property.__init__(self, fget=self.getter, fset=self.setter)
|
2013-07-16 21:14:37 +08:00
|
|
|
self.__doc__ = doc
|
2013-07-14 05:54:46 +08:00
|
|
|
|
|
|
|
|
2013-07-13 03:16:29 +08:00
|
|
|
def __repr__(self):
|
2013-07-14 10:13:13 +08:00
|
|
|
return "<XMLProperty %s %s>" % (str(self._name), id(self))
|
2013-07-13 03:16:29 +08:00
|
|
|
|
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
####################
|
|
|
|
# Internal helpers #
|
|
|
|
####################
|
|
|
|
|
|
|
|
def _findpropname(self, xmlbuilder):
|
|
|
|
"""
|
|
|
|
Map the raw property() instance to the param name it's exposed
|
|
|
|
as in the XMLBuilder class. This is just for debug purposes.
|
|
|
|
"""
|
2013-07-16 21:14:37 +08:00
|
|
|
for key, val in xmlbuilder.all_xml_props().items():
|
2013-07-14 05:54:46 +08:00
|
|
|
if val is self:
|
2013-07-12 22:49:15 +08:00
|
|
|
return key
|
|
|
|
raise RuntimeError("Didn't find expected property")
|
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
def _xpath_for_getter(self, xmlbuilder):
|
2013-07-14 10:13:13 +08:00
|
|
|
ret = self._xpath
|
2013-07-14 05:54:46 +08:00
|
|
|
if self._xpath_for_getter_cb:
|
2013-07-14 10:13:13 +08:00
|
|
|
ret = self._xpath_for_getter_cb(xmlbuilder)
|
|
|
|
if ret is None:
|
|
|
|
raise RuntimeError("%s: didn't generate any setter xpath." % self)
|
2013-07-24 20:46:55 +08:00
|
|
|
return xmlbuilder.fix_relative_xpath(ret)
|
2013-07-14 05:54:46 +08:00
|
|
|
def _xpath_for_setter(self, xmlbuilder):
|
2013-07-14 10:13:13 +08:00
|
|
|
ret = self._xpath
|
2013-07-14 05:54:46 +08:00
|
|
|
if self._xpath_for_setter_cb:
|
2013-07-14 10:13:13 +08:00
|
|
|
ret = self._xpath_for_setter_cb(xmlbuilder)
|
|
|
|
if ret is None:
|
|
|
|
raise RuntimeError("%s: didn't generate any setter xpath." % self)
|
2013-07-24 20:46:55 +08:00
|
|
|
return xmlbuilder.fix_relative_xpath(ret)
|
2013-07-15 21:49:46 +08:00
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
|
2013-07-14 06:04:49 +08:00
|
|
|
def _xpath_list_for_setter(self, xpath, setval, nodelist):
|
|
|
|
if not self._is_multi:
|
|
|
|
return [xpath]
|
|
|
|
|
|
|
|
ret = []
|
|
|
|
list_length = max(len(nodelist), len(setval), 1)
|
|
|
|
|
|
|
|
# This might not generally work, but as of this writing there's
|
|
|
|
# only one user of is_multi and it works for that. It's probably
|
|
|
|
# generalizable though.
|
|
|
|
for i in range(list_length):
|
|
|
|
idxstr = "[%d]/" % (i + 1)
|
|
|
|
splitpath = xpath.rsplit("/", 1)
|
|
|
|
ret.append("%s%s%s" % (splitpath[0], idxstr, splitpath[1]))
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
def _convert_value_for_setter(self, xmlbuilder):
|
|
|
|
# Convert from API value to XML value
|
2013-07-18 05:58:24 +08:00
|
|
|
val = self._nonxml_fget(xmlbuilder)
|
2013-07-17 21:57:15 +08:00
|
|
|
if self._default_name and val == self._default_name:
|
2013-07-15 22:57:16 +08:00
|
|
|
val = self._default_cb(xmlbuilder)
|
2013-07-17 01:04:24 +08:00
|
|
|
elif self._is_yesno:
|
|
|
|
if val is not None:
|
|
|
|
val = bool(val) and "yes" or "no"
|
2013-07-17 21:57:15 +08:00
|
|
|
|
|
|
|
if self._convert_value_for_setter_cb:
|
|
|
|
val = self._convert_value_for_setter_cb(xmlbuilder, val)
|
2013-07-14 05:54:46 +08:00
|
|
|
return val
|
|
|
|
|
|
|
|
def _build_node_list(self, xmlbuilder, xpath):
|
|
|
|
"""
|
|
|
|
Build list of nodes that the passed xpaths reference
|
|
|
|
"""
|
|
|
|
root_ctx = getattr(xmlbuilder, "_xml_ctx")
|
|
|
|
nodes = _get_xpath_node(root_ctx, xpath, self._is_multi)
|
|
|
|
return util.listify(nodes)
|
|
|
|
|
|
|
|
def _build_clear_list(self, xmlbuilder, setternodes):
|
|
|
|
"""
|
|
|
|
Build a list of nodes that we should erase first before performing
|
|
|
|
a set operation. But we don't want to unset a node that we are
|
|
|
|
just going to 'set' on top of afterwards, so skip those ones.
|
|
|
|
"""
|
|
|
|
root_ctx = getattr(xmlbuilder, "_xml_ctx")
|
|
|
|
clear_nodes = []
|
|
|
|
|
|
|
|
for cpath in self._setter_clear_these_first:
|
2013-07-24 20:46:55 +08:00
|
|
|
cpath = xmlbuilder.fix_relative_xpath(cpath)
|
2013-07-14 05:54:46 +08:00
|
|
|
cnode = _get_xpath_node(root_ctx, cpath, False)
|
|
|
|
if not cnode:
|
|
|
|
continue
|
|
|
|
if any([(n and n.nodePath() == cnode.nodePath())
|
|
|
|
for n in setternodes]):
|
|
|
|
continue
|
|
|
|
clear_nodes.append(cnode)
|
|
|
|
return clear_nodes
|
|
|
|
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _convert_get_value(self, val, initial=False):
|
2013-07-14 09:04:27 +08:00
|
|
|
if self._is_bool:
|
2013-07-15 04:05:59 +08:00
|
|
|
if initial and self._is_tri and val is None:
|
2013-07-17 21:57:15 +08:00
|
|
|
ret = None
|
|
|
|
else:
|
|
|
|
ret = bool(val)
|
2013-07-15 04:05:59 +08:00
|
|
|
elif self._is_int and val is not None:
|
2013-07-16 09:52:18 +08:00
|
|
|
intkwargs = {}
|
2013-07-15 21:49:46 +08:00
|
|
|
if "0x" in str(val):
|
2013-07-16 09:52:18 +08:00
|
|
|
intkwargs["base"] = 16
|
2013-07-17 21:57:15 +08:00
|
|
|
ret = int(val, **intkwargs)
|
2013-07-17 01:04:24 +08:00
|
|
|
elif self._is_yesno and val is not None:
|
2013-07-17 21:57:15 +08:00
|
|
|
ret = bool(val == "yes")
|
2013-07-15 04:05:59 +08:00
|
|
|
elif self._is_multi and val is None:
|
2013-07-17 21:57:15 +08:00
|
|
|
ret = []
|
|
|
|
else:
|
|
|
|
ret = val
|
|
|
|
return ret
|
2013-07-14 09:04:27 +08:00
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _prop_is_unset(self, xmlbuilder):
|
2013-07-14 21:31:14 +08:00
|
|
|
propstore = getattr(xmlbuilder, "_propstore")
|
|
|
|
propname = self._findpropname(xmlbuilder)
|
2013-07-18 05:58:24 +08:00
|
|
|
return (propname not in propstore)
|
|
|
|
|
|
|
|
def _set_default(self, xmlbuilder):
|
|
|
|
"""
|
|
|
|
Encode the property default into the XML and propstore, but
|
|
|
|
only if a default is registered, and only if the property was
|
|
|
|
not already explicitly set by the API user.
|
|
|
|
|
|
|
|
This is called during the get_xml_config process and shouldn't
|
|
|
|
be called from outside this file.
|
|
|
|
"""
|
|
|
|
if not self._prop_is_unset(xmlbuilder):
|
|
|
|
return
|
|
|
|
if not self._default_cb:
|
|
|
|
return
|
|
|
|
if self._default_cb(xmlbuilder) is None:
|
|
|
|
return
|
|
|
|
self.setter(xmlbuilder, self.getter(xmlbuilder), validate=False)
|
2013-07-14 21:31:14 +08:00
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _nonxml_fset(self, xmlbuilder, val):
|
2013-07-14 21:31:14 +08:00
|
|
|
"""
|
2013-07-18 05:58:24 +08:00
|
|
|
This stores the value in XMLBuilder._propstore
|
2013-07-14 21:31:14 +08:00
|
|
|
dict as propname->value. This saves us from having to explicitly
|
|
|
|
track every variable.
|
|
|
|
"""
|
|
|
|
propstore = getattr(xmlbuilder, "_propstore")
|
|
|
|
proporder = getattr(xmlbuilder, "_proporder")
|
|
|
|
|
|
|
|
if _trackprops and self not in _seenprops:
|
|
|
|
_seenprops.append(self)
|
|
|
|
propname = self._findpropname(xmlbuilder)
|
|
|
|
propstore[propname] = val
|
|
|
|
|
|
|
|
if propname in proporder:
|
|
|
|
proporder.remove(propname)
|
|
|
|
proporder.append(propname)
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _nonxml_fget(self, xmlbuilder):
|
|
|
|
"""
|
|
|
|
The flip side to nonxml_fset, fetch the value from
|
|
|
|
XMLBuilder._propstore
|
|
|
|
"""
|
|
|
|
propstore = getattr(xmlbuilder, "_propstore")
|
|
|
|
propname = self._findpropname(xmlbuilder)
|
|
|
|
unset = (propname not in propstore)
|
|
|
|
if unset and self._default_cb:
|
|
|
|
if self._default_name:
|
|
|
|
return self._default_name
|
|
|
|
return self._default_cb(xmlbuilder)
|
|
|
|
return propstore.get(propname, None)
|
|
|
|
|
|
|
|
def _clear(self, xmlbuilder):
|
|
|
|
val = None
|
|
|
|
if self._is_multi:
|
|
|
|
val = []
|
|
|
|
self.setter(xmlbuilder, val)
|
2013-07-14 21:31:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
##################################
|
|
|
|
# The actual getter/setter impls #
|
|
|
|
##################################
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def getter(self, xmlbuilder):
|
2013-07-14 05:54:46 +08:00
|
|
|
root_node = getattr(xmlbuilder, "_xml_node")
|
|
|
|
if root_node is None:
|
2013-07-24 20:46:55 +08:00
|
|
|
fgetval = self._nonxml_fget(xmlbuilder)
|
2013-07-18 05:58:24 +08:00
|
|
|
return self._convert_get_value(fgetval, initial=True)
|
2013-07-14 05:54:46 +08:00
|
|
|
|
|
|
|
xpath = self._xpath_for_getter(xmlbuilder)
|
|
|
|
nodelist = self._build_node_list(xmlbuilder, xpath)
|
|
|
|
|
2013-07-15 04:05:59 +08:00
|
|
|
if not nodelist:
|
2013-07-18 05:58:24 +08:00
|
|
|
return self._convert_get_value(None)
|
2013-07-15 04:05:59 +08:00
|
|
|
|
|
|
|
ret = []
|
|
|
|
for node in nodelist:
|
|
|
|
content = node.content
|
|
|
|
if self._is_bool:
|
|
|
|
content = True
|
2013-07-18 05:58:24 +08:00
|
|
|
val = self._convert_get_value(content)
|
2013-07-15 04:05:59 +08:00
|
|
|
if not self._is_multi:
|
|
|
|
return val
|
|
|
|
# If user is querying multiple nodes, return a list of results
|
|
|
|
ret.append(val)
|
|
|
|
return ret
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def setter(self, xmlbuilder, val, call_fset=True, validate=True):
|
2013-07-15 05:02:42 +08:00
|
|
|
if call_fset:
|
2013-07-18 05:58:24 +08:00
|
|
|
if validate and self._validate_cb:
|
|
|
|
self._validate_cb(xmlbuilder, val)
|
|
|
|
self._nonxml_fset(xmlbuilder, val)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
root_node = getattr(xmlbuilder, "_xml_node")
|
|
|
|
if root_node is None:
|
|
|
|
return
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-14 05:54:46 +08:00
|
|
|
xpath = self._xpath_for_setter(xmlbuilder)
|
|
|
|
setval = self._convert_value_for_setter(xmlbuilder)
|
|
|
|
nodelist = self._build_node_list(xmlbuilder, xpath)
|
|
|
|
clearlist = self._build_clear_list(xmlbuilder, nodelist)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-13 22:09:00 +08:00
|
|
|
node_map = []
|
2013-07-14 05:54:46 +08:00
|
|
|
if clearlist:
|
|
|
|
node_map += _tuplify_lists(clearlist, None, "")
|
2013-07-14 06:04:49 +08:00
|
|
|
node_map += _tuplify_lists(nodelist, setval,
|
|
|
|
self._xpath_list_for_setter(xpath, setval, nodelist))
|
|
|
|
|
2013-07-12 22:49:15 +08:00
|
|
|
for node, val, use_xpath in node_map:
|
2013-03-18 05:06:52 +08:00
|
|
|
if node:
|
2013-07-12 22:49:15 +08:00
|
|
|
use_xpath = node.nodePath()
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-15 04:01:38 +08:00
|
|
|
if val is None or val is False:
|
2013-07-17 04:24:24 +08:00
|
|
|
_remove_xpath_node(root_node, use_xpath,
|
|
|
|
root_name=root_node.name)
|
2013-07-15 04:01:38 +08:00
|
|
|
continue
|
|
|
|
|
|
|
|
if not node:
|
|
|
|
node = _build_xpath_node(root_node, use_xpath)
|
|
|
|
|
|
|
|
if val is True:
|
|
|
|
# Boolean property, creating the node is enough
|
|
|
|
continue
|
|
|
|
node.setContent(util.xml_escape(str(val)))
|
|
|
|
|
2013-04-14 02:34:52 +08:00
|
|
|
|
2013-07-14 06:56:09 +08:00
|
|
|
class XMLBuilder(object):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Base for all classes which build or parse domain XML
|
|
|
|
"""
|
2013-07-17 00:48:52 +08:00
|
|
|
# Order that we should apply values to the XML. Keeps XML generation
|
|
|
|
# consistent with what the test suite expects.
|
2013-07-15 21:49:46 +08:00
|
|
|
_XML_PROP_ORDER = []
|
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
# Absolute xpath this object is rooted at
|
|
|
|
_XML_ROOT_XPATH = None
|
2013-07-18 05:58:24 +08:00
|
|
|
|
2013-07-07 02:12:13 +08:00
|
|
|
def __init__(self, conn, parsexml=None, parsexmlnode=None):
|
2013-03-18 05:06:52 +08:00
|
|
|
"""
|
|
|
|
Initialize state
|
|
|
|
|
|
|
|
@param conn: libvirt connection to validate device against
|
2013-07-05 20:59:58 +08:00
|
|
|
@type conn: VirtualConnection
|
2013-03-18 05:06:52 +08:00
|
|
|
@param parsexml: Optional XML string to parse
|
|
|
|
@type parsexml: C{str}
|
|
|
|
@param parsexmlnode: Option xpathNode to use
|
|
|
|
"""
|
2013-07-24 20:46:55 +08:00
|
|
|
self.conn = conn
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
xpath = self._XML_ROOT_XPATH
|
|
|
|
if xpath is None or not xpath.startswith("/"):
|
|
|
|
raise RuntimeError("xpath=%s must start with /" % xpath)
|
|
|
|
|
|
|
|
self._xml_root_name = xpath.split("/")[-1]
|
|
|
|
self._xml_indent = (xpath.count("/") - 1) * 2
|
|
|
|
self._xml_dump_xpath = xpath
|
2013-07-24 20:46:55 +08:00
|
|
|
self._xml_root_xpath = ""
|
2013-07-25 00:02:37 +08:00
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
self._xml_node = None
|
|
|
|
self._xml_ctx = None
|
2013-07-10 20:05:08 +08:00
|
|
|
self._xml_root_doc = None
|
2013-07-12 22:49:15 +08:00
|
|
|
self._propstore = {}
|
|
|
|
self._proporder = []
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
if parsexml or parsexmlnode:
|
|
|
|
self._parsexml(parsexml, parsexmlnode)
|
|
|
|
|
|
|
|
|
2013-07-15 06:31:33 +08:00
|
|
|
##############
|
|
|
|
# Public API #
|
|
|
|
##############
|
|
|
|
|
2013-03-18 05:06:52 +08:00
|
|
|
def copy(self):
|
2013-07-14 09:04:27 +08:00
|
|
|
ret = copy.copy(self)
|
|
|
|
ret._propstore = ret._propstore.copy()
|
|
|
|
ret._proporder = ret._proporder[:]
|
|
|
|
return ret
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-24 20:46:55 +08:00
|
|
|
def set_new_xml(self, xml):
|
|
|
|
self._parsexml(xml, None)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-24 20:46:55 +08:00
|
|
|
def set_root_xpath(self, xpath):
|
|
|
|
self._xml_root_xpath = xpath
|
|
|
|
xmlprops = self.all_xml_props()
|
2013-07-24 23:32:30 +08:00
|
|
|
|
2013-07-24 20:46:55 +08:00
|
|
|
for propname in self._XML_PROP_ORDER:
|
2013-07-24 23:32:30 +08:00
|
|
|
if propname in xmlprops:
|
2013-07-24 20:46:55 +08:00
|
|
|
continue
|
|
|
|
for prop in util.listify(getattr(self, propname)):
|
|
|
|
prop.set_root_xpath(xpath)
|
|
|
|
|
|
|
|
def get_root_xpath(self):
|
|
|
|
return self._xml_root_xpath
|
|
|
|
|
|
|
|
def fix_relative_xpath(self, xpath):
|
|
|
|
if not self._xml_root_xpath:
|
|
|
|
return xpath
|
|
|
|
return "%s%s" % (self._xml_root_xpath, xpath.strip("."))
|
|
|
|
|
2013-07-24 23:32:30 +08:00
|
|
|
def get_xml_config(self):
|
2013-07-24 20:46:55 +08:00
|
|
|
data = self._prepare_get_xml()
|
|
|
|
try:
|
2013-07-25 00:02:37 +08:00
|
|
|
return self._do_get_xml_config()
|
2013-07-24 20:46:55 +08:00
|
|
|
finally:
|
|
|
|
self._finish_get_xml(data)
|
|
|
|
|
|
|
|
def clear(self):
|
|
|
|
for prop in self.all_xml_props().values():
|
|
|
|
prop._clear(self)
|
2013-03-18 05:06:52 +08:00
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
def _do_get_xml_config(self):
|
2013-07-15 06:31:33 +08:00
|
|
|
"""
|
|
|
|
Construct and return object xml
|
|
|
|
|
|
|
|
@return: object xml representation as a string
|
|
|
|
@rtype: str
|
|
|
|
"""
|
|
|
|
if self._xml_ctx:
|
2013-07-25 00:02:37 +08:00
|
|
|
node = _get_xpath_node(self._xml_ctx, self._xml_dump_xpath)
|
2013-07-15 06:31:33 +08:00
|
|
|
if not node:
|
|
|
|
ret = ""
|
|
|
|
else:
|
|
|
|
ret = _sanitize_libxml_xml(node.serialize())
|
|
|
|
else:
|
2013-07-25 00:02:37 +08:00
|
|
|
xmlstub = self._make_xml_stub()
|
|
|
|
if xmlstub is None:
|
2013-07-18 05:11:18 +08:00
|
|
|
return None
|
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
ret = self._add_parse_bits(xmlstub)
|
2013-07-18 05:11:18 +08:00
|
|
|
if ret == xmlstub:
|
|
|
|
ret = ""
|
2013-07-15 06:31:33 +08:00
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
if ret and self._xml_root_name == "domain" and not ret.endswith("\n"):
|
2013-07-24 22:53:19 +08:00
|
|
|
ret += "\n"
|
2013-07-18 05:58:24 +08:00
|
|
|
return ret
|
|
|
|
|
2013-07-15 06:31:33 +08:00
|
|
|
|
|
|
|
#######################
|
|
|
|
# Internal helper API #
|
|
|
|
#######################
|
|
|
|
|
|
|
|
def _is_parse(self):
|
|
|
|
return bool(self._xml_node or self._xml_ctx)
|
|
|
|
|
|
|
|
|
|
|
|
###################
|
|
|
|
# Child overrides #
|
|
|
|
###################
|
|
|
|
|
|
|
|
def set_defaults(self):
|
|
|
|
pass
|
|
|
|
|
2013-07-16 00:36:57 +08:00
|
|
|
def validate(self):
|
|
|
|
pass
|
|
|
|
|
2013-07-18 05:58:24 +08:00
|
|
|
def _prepare_get_xml(self):
|
|
|
|
return None
|
|
|
|
def _finish_get_xml(self, data):
|
|
|
|
ignore = data
|
|
|
|
|
2013-07-15 06:31:33 +08:00
|
|
|
|
|
|
|
########################
|
|
|
|
# Internal XML parsers #
|
|
|
|
########################
|
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
def _make_xml_stub(self):
|
|
|
|
return _indent("<%s/>" % (self._xml_root_name), self._xml_indent)
|
2013-07-15 21:49:46 +08:00
|
|
|
|
2013-07-24 20:46:55 +08:00
|
|
|
def _add_child(self, parent_xpath, dev):
|
|
|
|
"""
|
|
|
|
Insert the passed XMLBuilder object into our XML document at the
|
|
|
|
specified path
|
|
|
|
"""
|
|
|
|
dev.set_new_xml(dev.get_xml_config())
|
|
|
|
newnode = dev._xml_node
|
2013-03-18 05:06:52 +08:00
|
|
|
ret = _build_xpath_node(self._xml_ctx, parent_xpath, newnode)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def _remove_child_xpath(self, xpath):
|
|
|
|
_remove_xpath_node(self._xml_ctx, xpath, dofree=False)
|
|
|
|
self._set_xml_context()
|
|
|
|
|
|
|
|
def _set_xml_context(self):
|
|
|
|
doc = self._xml_node.doc
|
2013-07-14 08:47:19 +08:00
|
|
|
ctx = _CtxCleanupWrapper(doc.xpathNewContext())
|
2013-03-18 05:06:52 +08:00
|
|
|
ctx.setContextNode(self._xml_node)
|
|
|
|
self._xml_ctx = ctx
|
|
|
|
|
|
|
|
def _parsexml(self, xml, node):
|
|
|
|
if xml:
|
2013-07-10 20:05:08 +08:00
|
|
|
doc = libxml2.parseDoc(xml)
|
|
|
|
self._xml_root_doc = _DocCleanupWrapper(doc)
|
|
|
|
self._xml_node = doc.children
|
2013-07-25 00:02:37 +08:00
|
|
|
self._xml_dump_xpath = "."
|
2013-07-24 20:46:55 +08:00
|
|
|
|
|
|
|
# This just stores a reference to our root doc wrapper in
|
|
|
|
# the root node, so when the node goes away it triggers
|
|
|
|
# auto free'ing of the doc
|
2013-07-10 20:05:08 +08:00
|
|
|
self._xml_node.virtinst_root_doc = self._xml_root_doc
|
2013-03-18 05:06:52 +08:00
|
|
|
else:
|
|
|
|
self._xml_node = node
|
2013-07-25 00:02:37 +08:00
|
|
|
self._xml_dump_xpath = self._XML_ROOT_XPATH
|
2013-03-18 05:06:52 +08:00
|
|
|
|
|
|
|
self._set_xml_context()
|
|
|
|
|
2013-07-16 21:14:37 +08:00
|
|
|
def all_xml_props(self):
|
2013-07-16 00:18:23 +08:00
|
|
|
ret = {}
|
|
|
|
for c in reversed(type.mro(self.__class__)[:-1]):
|
|
|
|
for key, val in c.__dict__.items():
|
|
|
|
if val.__class__ is XMLProperty:
|
|
|
|
ret[key] = val
|
|
|
|
return ret
|
|
|
|
|
2013-07-24 22:53:19 +08:00
|
|
|
def _do_add_parse_bits(self, xml, node):
|
2013-07-18 05:58:24 +08:00
|
|
|
# Set all defaults if the properties have one registered
|
2013-07-16 21:14:37 +08:00
|
|
|
xmlprops = self.all_xml_props()
|
2013-07-18 05:58:24 +08:00
|
|
|
for prop in xmlprops.values():
|
|
|
|
prop._set_default(self)
|
2013-07-15 05:02:42 +08:00
|
|
|
|
|
|
|
# Default props alter our _propstore. But at this point _propstore
|
|
|
|
# is empty, there's nothing for us to do, so exit early
|
|
|
|
if not self._propstore:
|
2013-07-12 22:49:15 +08:00
|
|
|
return xml
|
|
|
|
|
2013-07-15 21:49:46 +08:00
|
|
|
# Unindent XML
|
|
|
|
indent = 0
|
2013-07-18 05:58:24 +08:00
|
|
|
if xml:
|
|
|
|
for c in xml:
|
|
|
|
if c != " ":
|
|
|
|
break
|
|
|
|
indent += 1
|
|
|
|
xml = "\n".join([l[indent:] for l in xml.splitlines()])
|
2013-07-15 21:49:46 +08:00
|
|
|
|
2013-07-18 01:06:01 +08:00
|
|
|
# Parse the XML into our internal state. Use the raw
|
|
|
|
# _parsexml so we don't hit Guest parsing into its internal state
|
2013-07-18 05:58:24 +08:00
|
|
|
XMLBuilder._parsexml(self, xml, node)
|
2013-07-15 05:02:42 +08:00
|
|
|
|
|
|
|
# Set up preferred XML ordering
|
|
|
|
do_order = self._proporder[:]
|
2013-07-15 21:49:46 +08:00
|
|
|
for key in reversed(self._XML_PROP_ORDER):
|
2013-07-15 05:02:42 +08:00
|
|
|
if key in do_order:
|
|
|
|
do_order.remove(key)
|
|
|
|
do_order.insert(0, key)
|
2013-07-18 05:58:24 +08:00
|
|
|
elif key not in xmlprops:
|
|
|
|
do_order.insert(0, key)
|
2013-07-15 05:02:42 +08:00
|
|
|
|
|
|
|
# Alter the XML
|
|
|
|
for key in do_order:
|
2013-07-18 05:58:24 +08:00
|
|
|
if key in xmlprops:
|
|
|
|
xmlprops[key].setter(self, self._propstore[key],
|
|
|
|
validate=False)
|
|
|
|
else:
|
|
|
|
for obj in util.listify(getattr(self, key)):
|
2013-07-24 20:46:55 +08:00
|
|
|
if self._xml_root_xpath and not obj._xml_root_xpath:
|
|
|
|
obj._xml_root_xpath = self._xml_root_xpath
|
2013-07-18 05:58:24 +08:00
|
|
|
obj._add_parse_bits(xml=None, node=self._xml_node)
|
|
|
|
|
2013-07-25 00:02:37 +08:00
|
|
|
return _indent(self._do_get_xml_config(), indent)
|
2013-07-15 03:06:40 +08:00
|
|
|
|
2013-07-24 22:53:19 +08:00
|
|
|
def _add_parse_bits(self, xml, node=None):
|
2013-07-15 05:02:42 +08:00
|
|
|
"""
|
|
|
|
Callback that adds the implicitly tracked XML properties to
|
|
|
|
the manually generated xml. This should only exist until all
|
|
|
|
classes are converted to all parsing all the time
|
|
|
|
"""
|
|
|
|
if self._is_parse():
|
|
|
|
return xml
|
2013-07-15 03:06:40 +08:00
|
|
|
|
2013-07-15 05:02:42 +08:00
|
|
|
origproporder = self._proporder[:]
|
|
|
|
origpropstore = self._propstore.copy()
|
2013-07-12 22:49:15 +08:00
|
|
|
try:
|
2013-07-24 22:53:19 +08:00
|
|
|
return self._do_add_parse_bits(xml, node)
|
2013-07-12 22:49:15 +08:00
|
|
|
finally:
|
|
|
|
self._xml_root_doc = None
|
|
|
|
self._xml_node = None
|
|
|
|
self._xml_ctx = None
|
2013-07-15 05:02:42 +08:00
|
|
|
self._proporder = origproporder
|
|
|
|
self._propstore = origpropstore
|