2013-03-18 01:06:52 +04:00
#
# Base class for all VM devices
#
2013-10-28 00:59:47 +04:00
# Copyright 2008, 2013 Red Hat, Inc.
2013-03-18 01:06:52 +04:00
# 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
2013-10-28 00:59:47 +04:00
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
2013-03-18 01: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 copy
2014-02-25 23:56:54 +04:00
import logging
2013-07-12 23:16:29 +04:00
import os
2013-09-11 18:16:12 +04:00
import re
2015-03-27 01:04:23 +03:00
import string # pylint: disable=deprecated-module
2013-03-18 01:06:52 +04:00
import libxml2
2014-09-12 23:59:22 +04:00
from . import util
2013-03-18 01:06:52 +04:00
2013-08-09 04:47:17 +04:00
2014-04-03 02:39:43 +04:00
# pylint: disable=protected-access
2013-07-15 17:49:46 +04:00
# This whole file is calling around into non-public functions that we
# don't want regular API users to touch
2013-03-18 01:06:52 +04:00
2014-02-10 23:47:20 +04:00
_trackprops = bool ( " VIRTINST_TEST_SUITE " in os . environ )
2013-07-12 23:16:29 +04:00
_allprops = [ ]
_seenprops = [ ]
2016-05-20 21:23:39 +03:00
# Convenience global to prevent _remove_xpath_node from unlinking the
# top relavtive node in certain cases
_top_node = None
2013-07-12 23:16:29 +04:00
2013-07-10 16:05:08 +04:00
class _DocCleanupWrapper ( object ) :
def __init__ ( self , doc ) :
self . _doc = doc
def __del__ ( self ) :
self . _doc . freeDoc ( )
2013-03-18 01:06:52 +04:00
2013-04-12 00:48:39 +04:00
2013-07-14 04:47:19 +04: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-25 19:02:56 +04:00
def _make_xml_context ( node ) :
doc = node . doc
ctx = _CtxCleanupWrapper ( doc . xpathNewContext ( ) )
ctx . setContextNode ( node )
return ctx
2013-04-12 00:48:39 +04: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 01:06:52 +04: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-13 22:34:52 +04:00
2013-07-24 20:35:03 +04:00
def _get_xpath_node ( ctx , xpath ) :
2013-03-18 01:06:52 +04:00
node = ctx . xpathEval ( xpath )
2013-07-24 20:35:03 +04:00
return ( node and node [ 0 ] or None )
2013-03-18 01:06:52 +04:00
2013-04-13 22:34:52 +04:00
2016-07-18 20:23:43 +03:00
def _add_pretty_child ( parentnode , newnode ) :
2013-03-18 01:06:52 +04:00
"""
2016-07-18 20:23:43 +03:00
Add ' newnode ' as a child of ' parentnode ' , but try to preserve
whitespace and nicely format the result .
2013-03-18 01:06:52 +04:00
"""
2016-07-18 20:23:43 +03:00
def node_is_text ( n ) :
return bool ( n and n . type == " text " and not n . content . count ( " < " ) )
2013-03-18 01:06:52 +04:00
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
2016-07-18 20:23:43 +03:00
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/>
2013-03-18 01:06:52 +04:00
# to
# <features>
2013-07-15 17:49:46 +04:00
# <acpi/>
2013-03-18 01:06:52 +04:00
# </features>
2016-07-18 20:23:43 +03:00
prevsib = prevSibling ( parentnode )
if node_is_text ( prevsib ) :
sib = libxml2 . newText ( prevsib . content )
else :
sib = libxml2 . newText ( " \n " )
parentnode . addChild ( sib )
# This case is adding a child element to an already properly
# spaced element. Example:
# <features>
# <acpi/>
# </features>
# to
# <features>
# <acpi/>
# <apic/>
# </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
2013-03-18 01:06:52 +04:00
2016-07-18 20:23:43 +03:00
2016-07-18 20:34:06 +03:00
def _build_xpath_node ( ctx , xpath ) :
2016-07-18 20:23:43 +03:00
"""
2016-07-18 20:50:25 +03:00
Build all nodes for the passed xpath . For example , if ' ctx ' xml = < foo / > ,
and xpath = . / bar / @baz , after this function the ' ctx ' XML will be :
2013-03-18 01:06:52 +04:00
2016-07-18 20:50:25 +03:00
< foo >
< bar baz = ' ' / >
< / foo >
And the node pointing to @baz will be returned , for the caller to
do with as they please .
"""
def _handle_node ( nodename , parentnode , parentpath ) :
# If the passed xpath snippet (nodename) exists, return the node
# If it doesn't exist, create it, and return the new node
2013-03-18 01:06:52 +04:00
2016-07-18 20:50:25 +03:00
# If nodename is a node property, we can handle it up front
2013-03-18 01:06:52 +04:00
if nodename . startswith ( " @ " ) :
nodename = nodename . strip ( " @ " )
2016-07-18 20:50:25 +03:00
return parentnode . setProp ( nodename , " " ) , parentpath
2013-03-18 01:06:52 +04:00
if not parentpath :
parentpath = nodename
else :
parentpath + = " / %s " % nodename
2016-07-18 20:50:25 +03:00
# See if the xpath node already exists
2013-03-18 01:06:52 +04:00
node = _get_xpath_node ( ctx , parentpath )
if node :
2016-07-18 20:50:25 +03:00
# xpath node already exists, so we don't need to create anything
return node , parentpath
2013-03-18 01:06:52 +04:00
2016-07-18 20:50:25 +03:00
# If we don't have a parentnode by this point, the root of the
# xpath didn't find anything. Usually a coding error
2013-03-18 01:06:52 +04:00
if not parentnode :
raise RuntimeError ( " Could not find XML root node " )
2016-07-18 20:50:25 +03:00
# Remove conditional xpath elements for node creation. We preserved
# them up until this point since it was needed for proper xpath
# lookup, but they aren't valid syntax when creating the node
2013-03-18 01:06:52 +04:00
if nodename . count ( " [ " ) :
nodename = nodename [ : nodename . index ( " [ " ) ]
newnode = libxml2 . newNode ( nodename )
2016-07-18 20:50:25 +03:00
return _add_pretty_child ( parentnode , newnode ) , parentpath
# Split the xpath and lookup/create each individual piece
parentpath = None
parentnode = None
for nodename in xpath . split ( " / " ) :
parentnode , parentpath = _handle_node ( nodename , parentnode , parentpath )
2013-03-18 01:06:52 +04:00
return parentnode
2013-04-13 22:34:52 +04:00
2014-01-25 23:48:27 +04:00
def _remove_xpath_node ( ctx , xpath , dofree = True ) :
2013-03-18 01:06:52 +04:00
"""
Remove an XML node tree if it has no content
"""
2015-09-05 00:03:31 +03:00
nextxpath = xpath
2014-01-25 23:41:55 +04:00
root_node = ctx . contextNode ( )
2013-03-18 01:06:52 +04:00
2015-09-05 00:03:31 +03:00
while nextxpath :
curxpath = nextxpath
2013-03-18 01:06:52 +04:00
is_orig = ( curxpath == xpath )
node = _get_xpath_node ( ctx , curxpath )
2015-09-05 00:03:31 +03:00
2013-03-18 01:06:52 +04:00
if curxpath . count ( " / " ) :
2015-09-05 00:03:31 +03:00
nextxpath , ignore = curxpath . rsplit ( " / " , 1 )
2013-03-18 01:06:52 +04:00
else :
2015-09-05 00:03:31 +03:00
nextxpath = None
2013-03-18 01:06:52 +04:00
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
2016-05-20 21:23:39 +03:00
if node == root_node or node == _top_node :
# Don't unlink the root node, since it's spread out to all
# child objects and it ends up wreaking havoc.
break
2013-03-18 01:06:52 +04:00
# 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 ( )
node . unlinkNode ( )
if dofree :
node . freeNode ( )
2013-09-11 02:32:10 +04:00
class XMLChildProperty ( property ) :
"""
Property that points to a class used for parsing a subsection of
of the parent XML . For example when we deligate parsing
/ domain / cpu / feature of the / domain / cpu class .
2013-09-11 18:16:12 +04:00
@child_classes : Single class or list of classes to parse state into
@relative_xpath : Relative location where the class is rooted compared
to its _XML_ROOT_PATH . So interface xml can have nested
interfaces rooted at / interface / bridge / interface , so we pass
. / bridge / interface here for example .
2013-09-11 02:32:10 +04:00
"""
2013-09-11 19:47:09 +04:00
def __init__ ( self , child_classes , relative_xpath = " . " , is_single = False ) :
2013-09-11 02:32:10 +04:00
self . child_classes = util . listify ( child_classes )
2013-09-11 18:16:12 +04:00
self . relative_xpath = relative_xpath
2013-09-11 19:47:09 +04:00
self . is_single = is_single
2013-09-19 21:25:52 +04:00
self . _propname = None
2013-09-11 19:47:09 +04:00
if self . is_single and len ( self . child_classes ) > 1 :
raise RuntimeError ( " programming error: Can ' t specify multiple "
" child_classes with is_single " )
2013-09-11 02:32:10 +04:00
property . __init__ ( self , self . _fget )
def __repr__ ( self ) :
return " <XMLChildProperty %s %s > " % ( str ( self . child_classes ) , id ( self ) )
def _findpropname ( self , xmlbuilder ) :
2013-09-19 21:25:52 +04:00
if self . _propname is None :
for key , val in xmlbuilder . _all_child_props ( ) . items ( ) :
if val is self :
self . _propname = key
break
if self . _propname is None :
raise RuntimeError ( " Didn ' t find expected property= %s " % self )
return self . _propname
2013-09-11 02:32:10 +04:00
2013-09-11 19:47:09 +04:00
def _get ( self , xmlbuilder ) :
2013-09-11 02:32:10 +04:00
propname = self . _findpropname ( xmlbuilder )
2013-09-11 19:47:09 +04:00
if propname not in xmlbuilder . _propstore and not self . is_single :
2013-09-11 02:32:10 +04:00
xmlbuilder . _propstore [ propname ] = [ ]
return xmlbuilder . _propstore [ propname ]
def _fget ( self , xmlbuilder ) :
2013-09-11 19:47:09 +04:00
if self . is_single :
return self . _get ( xmlbuilder )
return self . _get ( xmlbuilder ) [ : ]
def clear ( self , xmlbuilder ) :
if self . is_single :
self . _get ( xmlbuilder ) . clear ( )
else :
for obj in self . _get ( xmlbuilder ) [ : ] :
2015-09-04 22:45:45 +03:00
xmlbuilder . remove_child ( obj )
2013-09-11 02:32:10 +04:00
2013-09-20 18:47:36 +04:00
def append ( self , xmlbuilder , newobj ) :
# Keep the list ordered by the order of passed in child classes
objlist = self . _get ( xmlbuilder )
if len ( self . child_classes ) == 1 :
objlist . append ( newobj )
return
idx = 0
2016-04-18 23:42:12 +03:00
for idx , obj in enumerate ( objlist ) :
2013-09-20 18:47:36 +04:00
obj = objlist [ idx ]
if ( obj . __class__ not in self . child_classes or
( self . child_classes . index ( newobj . __class__ ) <
self . child_classes . index ( obj . __class__ ) ) ) :
break
idx + = 1
objlist . insert ( idx , newobj )
2013-09-11 02:32:10 +04:00
def remove ( self , xmlbuilder , obj ) :
2013-09-11 19:47:09 +04:00
self . _get ( xmlbuilder ) . remove ( obj )
def set ( self , xmlbuilder , obj ) :
xmlbuilder . _propstore [ self . _findpropname ( xmlbuilder ) ] = obj
2013-09-11 02:32:10 +04:00
2013-09-11 19:47:09 +04:00
def get_prop_xpath ( self , xmlbuilder , obj ) :
relative_xpath = self . relative_xpath + " / " + obj . _XML_ROOT_NAME
2013-09-11 18:16:12 +04:00
match = re . search ( " % \ ((.*) \ ) " , self . relative_xpath )
if match :
valuedict = { }
for paramname in match . groups ( ) :
valuedict [ paramname ] = getattr ( xmlbuilder , paramname )
relative_xpath = relative_xpath % valuedict
2013-09-11 19:47:09 +04:00
return relative_xpath
2013-09-11 18:16:12 +04:00
2013-09-11 02:32:10 +04:00
2013-07-14 02:56:09 +04:00
class XMLProperty ( property ) :
2015-09-06 00:20:43 +03:00
def __init__ ( self , xpath , doc = None ,
set_converter = None , validate_cb = None ,
2013-09-03 10:47:26 +04:00
is_bool = False , is_int = False , is_yesno = False , is_onoff = False ,
2014-12-04 23:27:30 +03:00
default_cb = None , default_name = None , do_abspath = False ) :
2013-07-14 01:54:46 +04:00
"""
2016-07-18 19:46:59 +03:00
Set a XMLBuilder class property that maps to a value in an XML
document , indicated by the passed xpath . For example , for a
< domain > < name > the definition may look like :
2013-07-14 01:54:46 +04:00
2016-07-18 19:46:59 +03:00
name = XMLProperty ( " ./name " )
2013-07-14 01:54:46 +04:00
2016-07-18 19:46:59 +03:00
When building XML from scratch ( virt - install ) , ' name ' works
similar to a regular class property ( ) . When parsing and editing
existing guest XML , we use the xpath value to get / set the value
in the parsed XML document .
2013-07-14 01:54:46 +04:00
@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 06:13:13 +04:00
@param name : Just a string to print for debugging , only needed
if xpath isn ' t specified.
2013-07-14 01:54:46 +04:00
@param set_converter : optional function for converting the property
value from the virtinst API to the guest XML . For example ,
2014-06-17 05:33:14 +04:00
the Guest . memory API was once in MiB , but the libvirt domain
memory API is in KiB . So , if xpath is specified , on a ' get '
2013-07-14 07:07:01 +04:00
operation we convert the XML value with int ( val ) / 1024.
2013-07-18 01:58:24 +04:00
@param validate_cb : Called once when value is set , should
raise a RuntimeError if the value is not proper .
2013-07-14 01:54:46 +04:00
@param is_bool : Whether this is a boolean property in the XML
2013-09-03 10:47:26 +04:00
@param is_int : Whether this is an integer property in the XML
@param is_yesno : Whether this is a yes / no property in the XML
@param is_onoff : Whether this is an on / off property in the XML
2013-07-15 01:02:42 +04: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 18:57:16 +04:00
@param default_name : If the user does a set and passes in this
value , instead use the value of default_cb ( )
2014-09-23 22:25:14 +04:00
@param do_abspath : If True , run os . path . abspath on the passed value
2013-07-14 01:54:46 +04:00
"""
2013-07-12 23:16:29 +04:00
self . _xpath = xpath
2015-09-06 00:20:43 +03:00
if not self . _xpath :
raise RuntimeError ( " XMLProperty: xpath must be passed. " )
2013-09-19 21:25:52 +04:00
self . _propname = None
2013-07-12 23:16:29 +04:00
2013-07-24 20:51:53 +04:00
self . _is_bool = is_bool
2013-07-14 05:49:32 +04:00
self . _is_int = is_int
2013-07-16 21:04:24 +04:00
self . _is_yesno = is_yesno
2013-09-02 18:56:22 +04:00
self . _is_onoff = is_onoff
2014-09-23 22:25:14 +04:00
self . _do_abspath = do_abspath
2013-07-14 01:54:46 +04:00
2013-07-18 01:58:24 +04:00
self . _validate_cb = validate_cb
2013-07-14 01:54:46 +04:00
self . _convert_value_for_setter_cb = set_converter
2013-07-15 01:02:42 +04:00
self . _default_cb = default_cb
2013-07-15 18:57:16 +04:00
self . _default_name = default_name
2013-07-15 01:02:42 +04:00
2013-07-17 17:57:15 +04:00
if sum ( [ int ( bool ( i ) ) for i in
2013-09-02 18:56:22 +04:00
[ self . _is_bool , self . _is_int ,
self . _is_yesno , self . _is_onoff ] ] ) > 1 :
2013-07-15 01:02:42 +04:00
raise RuntimeError ( " Conflict property converter options. " )
2013-07-14 01:54:46 +04:00
2013-07-15 18:57:16 +04:00
if self . _default_name and not self . _default_cb :
raise RuntimeError ( " default_name requires default_cb. " )
2015-04-22 21:44:52 +03:00
self . _is_tracked = False
if _trackprops :
2013-07-18 01:58:24 +04:00
_allprops . append ( self )
2013-07-14 01:54:46 +04:00
2013-07-18 01:58:24 +04:00
property . __init__ ( self , fget = self . getter , fset = self . setter )
2013-07-16 17:14:37 +04:00
self . __doc__ = doc
2013-07-14 01:54:46 +04:00
2013-07-12 23:16:29 +04:00
def __repr__ ( self ) :
2015-09-06 00:20:43 +03:00
return " <XMLProperty %s %s > " % ( str ( self . _xpath ) , id ( self ) )
2013-07-12 23:16:29 +04:00
2013-07-14 01:54:46 +04: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-09-19 21:25:52 +04:00
if self . _propname is None :
for key , val in xmlbuilder . _all_xml_props ( ) . items ( ) :
if val is self :
self . _propname = key
break
if self . _propname is None :
raise RuntimeError ( " Didn ' t find expected property= %s " % self )
return self . _propname
2013-07-12 18:49:15 +04:00
2013-09-24 17:25:05 +04:00
def _make_xpath ( self , xmlbuilder ) :
2015-09-06 00:20:43 +03:00
return xmlbuilder . fix_relative_xpath ( self . _xpath )
2013-07-15 17:49:46 +04:00
2013-07-14 01:54:46 +04:00
def _build_node_list ( self , xmlbuilder , xpath ) :
"""
Build list of nodes that the passed xpaths reference
"""
2013-07-25 19:02:56 +04:00
nodes = _get_xpath_node ( xmlbuilder . _xmlstate . xml_ctx , xpath )
2013-07-14 01:54:46 +04:00
return util . listify ( nodes )
2013-07-24 20:51:53 +04:00
def _convert_get_value ( self , val ) :
2016-04-18 23:42:12 +03:00
# pylint: disable=redefined-variable-type
2013-07-24 20:51:53 +04:00
if self . _default_name and val == self . _default_name :
ret = val
elif self . _is_bool :
ret = bool ( val )
2013-07-15 00:05:59 +04:00
elif self . _is_int and val is not None :
2013-07-16 05:52:18 +04:00
intkwargs = { }
2013-07-15 17:49:46 +04:00
if " 0x " in str ( val ) :
2013-07-16 05:52:18 +04:00
intkwargs [ " base " ] = 16
2013-07-17 17:57:15 +04:00
ret = int ( val , * * intkwargs )
2013-07-16 21:04:24 +04:00
elif self . _is_yesno and val is not None :
2013-07-17 17:57:15 +04:00
ret = bool ( val == " yes " )
2013-09-02 18:56:22 +04:00
elif self . _is_onoff and val is not None :
ret = bool ( val == " on " )
2013-07-17 17:57:15 +04:00
else :
ret = val
return ret
2013-07-14 05:04:27 +04:00
2013-07-24 22:37:07 +04:00
def _convert_set_value ( self , xmlbuilder , val ) :
if self . _default_name and val == self . _default_name :
val = self . _default_cb ( xmlbuilder )
2014-09-23 22:25:14 +04:00
elif self . _do_abspath and val is not None :
val = os . path . abspath ( val )
2013-09-02 18:56:22 +04:00
elif self . _is_onoff and val is not None :
val = bool ( val ) and " on " or " off "
2013-07-25 23:27:42 +04:00
elif self . _is_yesno and val is not None :
val = bool ( val ) and " yes " or " no "
elif self . _is_int and val is not None :
intkwargs = { }
if " 0x " in str ( val ) :
intkwargs [ " base " ] = 16
val = int ( val , * * intkwargs )
2013-07-24 22:37:07 +04:00
if self . _convert_value_for_setter_cb :
val = self . _convert_value_for_setter_cb ( xmlbuilder , val )
return val
2013-07-18 01:58:24 +04:00
def _prop_is_unset ( self , xmlbuilder ) :
2013-07-14 17:31:14 +04:00
propname = self . _findpropname ( xmlbuilder )
2013-07-25 19:02:56 +04:00
return ( propname not in xmlbuilder . _propstore )
2013-07-18 01:58:24 +04:00
2013-07-24 22:37:07 +04:00
def _default_get_value ( self , xmlbuilder ) :
"""
Return ( can use default , default value )
"""
ret = ( False , - 1 )
2013-07-25 20:34:37 +04:00
if not xmlbuilder . _xmlstate . is_build :
return ret
2013-07-24 22:37:07 +04:00
if not self . _prop_is_unset ( xmlbuilder ) :
return ret
if not self . _default_cb :
return ret
if self . _default_name :
return ( True , self . _default_name )
return ( True , self . _default_cb ( xmlbuilder ) )
2013-07-18 01:58:24 +04:00
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 .
"""
2013-07-24 22:37:07 +04:00
candefault , val = self . _default_get_value ( xmlbuilder )
if not candefault :
2013-07-18 01:58:24 +04:00
return
2013-07-24 22:37:07 +04:00
self . setter ( xmlbuilder , val , validate = False )
2013-07-14 17:31:14 +04:00
2013-07-18 01:58:24 +04:00
def _nonxml_fset ( self , xmlbuilder , val ) :
2013-07-14 17:31:14 +04:00
"""
2013-07-18 01:58:24 +04:00
This stores the value in XMLBuilder . _propstore
2013-07-14 17:31:14 +04:00
dict as propname - > value . This saves us from having to explicitly
track every variable .
"""
2013-07-25 19:02:56 +04:00
propstore = xmlbuilder . _propstore
proporder = xmlbuilder . _proporder
2013-07-14 17:31:14 +04:00
propname = self . _findpropname ( xmlbuilder )
propstore [ propname ] = val
if propname in proporder :
proporder . remove ( propname )
proporder . append ( propname )
2013-07-18 01:58:24 +04:00
def _nonxml_fget ( self , xmlbuilder ) :
"""
The flip side to nonxml_fset , fetch the value from
XMLBuilder . _propstore
"""
2013-07-24 22:37:07 +04:00
candefault , val = self . _default_get_value ( xmlbuilder )
if candefault :
return val
2013-07-18 01:58:24 +04:00
propname = self . _findpropname ( xmlbuilder )
2013-07-25 19:02:56 +04:00
return xmlbuilder . _propstore . get ( propname , None )
2013-07-18 01:58:24 +04:00
2013-09-11 19:47:09 +04:00
def clear ( self , xmlbuilder ) :
2013-07-24 20:35:03 +04:00
self . setter ( xmlbuilder , None )
2013-07-24 22:37:07 +04:00
self . _set_xml ( xmlbuilder , None )
2013-07-14 17:31:14 +04:00
##################################
# The actual getter/setter impls #
##################################
2013-07-18 01:58:24 +04:00
def getter ( self , xmlbuilder ) :
2013-07-25 19:02:56 +04:00
"""
Fetch value at user request . If we are parsing existing XML and
the user hasn ' t done a ' set ' yet, return the value from the XML,
otherwise return the value from propstore
2013-07-25 20:34:37 +04:00
If this is a built from scratch object , we never pull from XML
since it ' s known to the empty, and we may want to return
a ' default ' value
2013-07-25 19:02:56 +04:00
"""
2015-04-22 21:44:52 +03:00
if _trackprops and not self . _is_tracked :
_seenprops . append ( self )
self . _is_tracked = True
2016-04-18 23:42:12 +03:00
# pylint: disable=redefined-variable-type
2013-07-25 20:34:37 +04:00
if ( self . _prop_is_unset ( xmlbuilder ) and
not xmlbuilder . _xmlstate . is_build ) :
2013-07-24 22:37:07 +04:00
val = self . _get_xml ( xmlbuilder )
else :
val = self . _nonxml_fget ( xmlbuilder )
ret = self . _convert_get_value ( val )
return ret
2013-07-14 01:54:46 +04:00
2013-07-24 22:37:07 +04:00
def _get_xml ( self , xmlbuilder ) :
2013-07-25 19:02:56 +04:00
"""
Actually fetch the associated value from the backing XML
"""
2013-09-24 17:25:05 +04:00
xpath = self . _make_xpath ( xmlbuilder )
2013-07-25 19:02:56 +04:00
node = _get_xpath_node ( xmlbuilder . _xmlstate . xml_ctx , xpath )
2013-07-24 20:35:03 +04:00
if not node :
2013-07-24 22:37:07 +04:00
return None
2013-07-15 00:05:59 +04:00
2013-07-24 20:35:03 +04:00
content = node . content
if self . _is_bool :
content = True
2013-07-24 22:37:07 +04:00
return content
2013-03-18 01:06:52 +04:00
2013-07-24 22:37:07 +04:00
def setter ( self , xmlbuilder , val , validate = True ) :
2013-07-25 19:02:56 +04:00
"""
Set the value at user request . This just stores the value
in propstore . Setting the actual XML is only done at
get_xml_config time .
"""
2015-04-22 21:44:52 +03:00
if _trackprops and not self . _is_tracked :
_seenprops . append ( self )
self . _is_tracked = True
2013-07-24 22:37:07 +04:00
if validate and self . _validate_cb :
self . _validate_cb ( xmlbuilder , val )
self . _nonxml_fset ( xmlbuilder ,
self . _convert_set_value ( xmlbuilder , val ) )
2013-03-18 01:06:52 +04:00
2013-07-24 22:37:07 +04:00
def _set_xml ( self , xmlbuilder , setval , root_node = None ) :
2013-07-25 19:02:56 +04:00
"""
Actually set the passed value in the XML document
"""
2013-07-24 22:37:07 +04:00
if root_node is None :
2013-07-25 19:02:56 +04:00
root_node = xmlbuilder . _xmlstate . xml_node
2014-01-25 23:41:55 +04:00
ctx = xmlbuilder . _xmlstate . xml_ctx
else :
ctx = _make_xml_context ( root_node )
2013-09-24 17:25:05 +04:00
xpath = self . _make_xpath ( xmlbuilder )
2013-03-18 01:06:52 +04:00
2014-12-04 23:27:30 +03:00
if setval is None or setval is False :
_remove_xpath_node ( ctx , xpath )
return
2013-07-14 02:04:49 +04:00
2016-05-20 20:29:16 +03:00
node = _get_xpath_node ( ctx , xpath )
2014-12-04 23:27:30 +03:00
if not node :
2016-05-20 20:29:16 +03:00
node = _build_xpath_node ( ctx , xpath )
2013-07-15 00:01:38 +04:00
2014-12-04 23:27:30 +03:00
if setval is True :
# Boolean property, creating the node is enough
return
2013-07-15 00:01:38 +04:00
2014-12-04 23:27:30 +03:00
node . setContent ( util . xml_escape ( str ( setval ) ) )
2013-07-15 00:01:38 +04:00
2013-04-13 22:34:52 +04:00
2013-07-25 19:02:56 +04:00
class _XMLState ( object ) :
2013-09-11 19:47:09 +04:00
def __init__ ( self , root_name , parsexml , parsexmlnode ,
parent_xpath , relative_object_xpath ) :
self . root_name = root_name
self . stub_path = " / %s " % self . root_name
2013-07-25 19:02:56 +04:00
self . xml_ctx = None
self . xml_node = None
self . xml_root_doc = None
2013-09-11 18:16:12 +04:00
# xpath of this object relative to its parent. So for a standalone
# <disk> this is empty, but if the disk is the forth one in a <domain>
# it will be set to ./devices/disk[4]
2013-09-11 19:47:09 +04:00
self . _relative_object_xpath = relative_object_xpath or " "
2013-09-11 18:16:12 +04:00
# xpath of the parent. For a disk in a standalone <domain>, this
# is empty, but if the <domain> is part of a <domainsnapshot>,
# it will be "./domain"
2013-09-11 19:47:09 +04:00
self . _parent_xpath = parent_xpath or " "
2013-07-25 19:02:56 +04:00
self . is_build = False
2013-09-11 02:32:10 +04:00
if not parsexml and not parsexmlnode :
2013-07-25 19:02:56 +04:00
self . is_build = True
self . _parse ( parsexml , parsexmlnode )
def _parse ( self , xml , node ) :
2013-09-11 02:32:10 +04:00
if node :
self . xml_root_doc = None
self . xml_node = node
self . is_build = ( getattr ( node , " virtinst_is_build " , False ) or
self . is_build )
else :
if not xml :
xml = self . make_xml_stub ( )
2014-02-25 21:45:15 +04:00
try :
doc = libxml2 . parseDoc ( xml )
except :
logging . debug ( " Error parsing xml= \n %s " , xml )
raise
2013-07-25 19:02:56 +04:00
self . xml_root_doc = _DocCleanupWrapper ( doc )
self . xml_node = doc . children
self . xml_node . virtinst_is_build = self . is_build
2013-09-11 19:47:09 +04:00
self . xml_node . virtinst_node_top_xpath = self . stub_path
2013-07-25 19:02:56 +04: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
self . xml_node . virtinst_root_doc = self . xml_root_doc
self . xml_ctx = _make_xml_context ( self . xml_node )
def make_xml_stub ( self ) :
2013-09-11 19:47:09 +04:00
return " < %s /> " % self . root_name
2013-07-25 19:02:56 +04:00
2013-09-11 18:16:12 +04:00
def set_relative_object_xpath ( self , xpath ) :
self . _relative_object_xpath = xpath or " "
def set_parent_xpath ( self , xpath ) :
self . _parent_xpath = xpath or " "
def get_root_xpath ( self ) :
2013-09-11 19:47:09 +04:00
relpath = self . _relative_object_xpath
if not self . _parent_xpath :
return relpath
return self . _parent_xpath + ( relpath . startswith ( " . " ) and
relpath [ 1 : ] or relpath )
2013-07-25 19:02:56 +04:00
def fix_relative_xpath ( self , xpath ) :
2013-09-11 18:16:12 +04:00
fullpath = self . get_root_xpath ( )
2013-09-11 19:47:09 +04:00
if not fullpath or fullpath == self . stub_path :
2013-07-25 19:02:56 +04:00
return xpath
2013-09-11 18:16:12 +04:00
if xpath . startswith ( " . " ) :
return " %s %s " % ( fullpath , xpath . strip ( " . " ) )
if xpath . count ( " / " ) == 1 :
return fullpath
return fullpath + " / " + xpath . split ( " / " , 2 ) [ 2 ]
def get_node_top_xpath ( self ) :
"""
Return the XML path of the root xml_node
"""
return self . xml_node . virtinst_node_top_xpath
2014-01-25 23:18:54 +04:00
def get_node_xml ( self , ctx ) :
2013-09-11 19:47:09 +04:00
node = _get_xpath_node ( ctx , self . fix_relative_xpath ( " . " ) )
2013-07-25 19:02:56 +04:00
if not node :
return " "
return _sanitize_libxml_xml ( node . serialize ( ) )
2013-07-14 02:56:09 +04:00
class XMLBuilder ( object ) :
2013-03-18 01:06:52 +04:00
"""
Base for all classes which build or parse domain XML
"""
2013-07-16 20:48:52 +04:00
# Order that we should apply values to the XML. Keeps XML generation
# consistent with what the test suite expects.
2013-07-15 17:49:46 +04:00
_XML_PROP_ORDER = [ ]
2013-09-11 19:47:09 +04:00
# Name of the root XML element
_XML_ROOT_NAME = None
2013-07-18 01:58:24 +04:00
2015-03-27 01:04:23 +03:00
# In some cases, libvirt can incorrectly generate unparseable XML.
# These are libvirt bugs, but this allows us to work around it in
# for specific XML classes.
#
# Example: nodedev 'system' XML:
# https://bugzilla.redhat.com/show_bug.cgi?id=1184131
_XML_SANITIZE = False
2013-09-11 19:47:09 +04:00
def __init__ ( self , conn , parsexml = None , parsexmlnode = None ,
parent_xpath = None , relative_object_xpath = None ) :
2013-03-18 01:06:52 +04:00
"""
Initialize state
@param conn : libvirt connection to validate device against
2013-07-05 16:59:58 +04:00
@type conn : VirtualConnection
2013-03-18 01:06:52 +04:00
@param parsexml : Optional XML string to parse
@type parsexml : C { str }
2013-09-11 19:47:09 +04:00
The rest of the parameters are for internal use only
2013-03-18 01:06:52 +04:00
"""
2013-07-24 16:46:55 +04:00
self . conn = conn
2013-03-18 01:06:52 +04:00
2015-03-27 01:04:23 +03:00
if self . _XML_SANITIZE :
parsexml = parsexml . decode ( ' ascii ' , ' ignore ' ) . encode ( ' ascii ' )
parsexml = " " . join ( [ c for c in parsexml if c in string . printable ] )
2013-07-12 18:49:15 +04:00
self . _propstore = { }
self . _proporder = [ ]
2013-09-11 19:47:09 +04:00
self . _xmlstate = _XMLState ( self . _XML_ROOT_NAME ,
parsexml , parsexmlnode ,
parent_xpath , relative_object_xpath )
2013-09-11 18:16:12 +04:00
2013-09-19 21:25:52 +04:00
self . _initial_child_parse ( )
def _initial_child_parse ( self ) :
2013-09-11 18:16:12 +04:00
# Walk the XML tree and hand of parsing to any registered
# child classes
for xmlprop in self . _all_child_props ( ) . values ( ) :
2013-09-11 19:47:09 +04:00
if xmlprop . is_single :
child_class = xmlprop . child_classes [ 0 ]
prop_path = xmlprop . get_prop_xpath ( self , child_class )
obj = child_class ( self . conn ,
2013-09-20 19:30:33 +04:00
parsexmlnode = self . _xmlstate . xml_node ,
2013-09-11 19:47:09 +04:00
parent_xpath = self . get_root_xpath ( ) ,
relative_object_xpath = prop_path )
xmlprop . set ( self , obj )
continue
if self . _xmlstate . is_build :
continue
2013-09-11 18:16:12 +04:00
for child_class in xmlprop . child_classes :
prop_path = xmlprop . get_prop_xpath ( self , child_class )
2013-09-20 19:30:33 +04:00
nodecount = int ( self . _xmlstate . xml_node . xpathEval (
2013-09-11 19:47:09 +04:00
" count( %s ) " % self . fix_relative_xpath ( prop_path ) ) )
for idx in range ( nodecount ) :
idxstr = " [ %d ] " % ( idx + 1 )
obj = child_class ( self . conn ,
2013-09-20 19:30:33 +04:00
parsexmlnode = self . _xmlstate . xml_node ,
2013-09-11 19:47:09 +04:00
parent_xpath = self . get_root_xpath ( ) ,
relative_object_xpath = ( prop_path + idxstr ) )
xmlprop . append ( self , obj )
2013-09-19 21:25:52 +04:00
2013-09-11 18:16:12 +04:00
self . _set_child_xpaths ( )
2013-03-18 01:06:52 +04:00
2013-07-25 19:02:56 +04:00
########################
# Public XML Internals #
########################
2013-07-15 02:31:33 +04:00
2013-03-18 01:06:52 +04:00
def copy ( self ) :
2013-07-25 19:02:56 +04:00
"""
Do a shallow copy of the device
"""
2013-07-14 05:04:27 +04:00
ret = copy . copy ( self )
ret . _propstore = ret . _propstore . copy ( )
ret . _proporder = ret . _proporder [ : ]
2013-09-11 02:32:10 +04:00
# XMLChildProperty stores a list in propstore, which dict shallow
# copy won't fix for us.
for name , value in ret . _propstore . items ( ) :
if type ( value ) is not list :
continue
ret . _propstore [ name ] = [ obj . copy ( ) for obj in ret . _propstore [ name ] ]
2013-07-14 05:04:27 +04:00
return ret
2013-03-18 01:06:52 +04:00
2013-07-24 16:46:55 +04:00
def get_root_xpath ( self ) :
2013-09-11 18:16:12 +04:00
return self . _xmlstate . get_root_xpath ( )
2013-07-24 16:46:55 +04:00
def fix_relative_xpath ( self , xpath ) :
2013-07-25 19:02:56 +04:00
return self . _xmlstate . fix_relative_xpath ( xpath )
############################
# Public XML managing APIs #
############################
2013-07-24 16:46:55 +04:00
2013-07-24 19:32:30 +04:00
def get_xml_config ( self ) :
2013-07-25 19:02:56 +04:00
"""
Return XML string of the object
"""
2013-07-24 16:46:55 +04:00
data = self . _prepare_get_xml ( )
try :
2013-07-24 20:02:37 +04:00
return self . _do_get_xml_config ( )
2013-07-24 16:46:55 +04:00
finally :
self . _finish_get_xml ( data )
2016-05-20 21:23:39 +03:00
def clear ( self , leave_stub = False ) :
2013-07-25 19:02:56 +04:00
"""
Wipe out all properties of the object
2016-05-20 21:23:39 +03:00
: param leave_stub : if True , don ' t unlink the top stub node,
see virtinst / cli usage for an explanation
2013-07-25 19:02:56 +04:00
"""
2016-05-20 21:23:39 +03:00
global _top_node
old_top_node = _top_node
try :
if leave_stub :
_top_node = _get_xpath_node ( self . _xmlstate . xml_ctx ,
self . get_root_xpath ( ) )
props = self . _all_xml_props ( ) . values ( )
props + = self . _all_child_props ( ) . values ( )
for prop in props :
prop . clear ( self )
finally :
_top_node = old_top_node
2013-03-18 01:06:52 +04:00
2015-09-05 00:03:31 +03:00
is_child = bool ( re . match ( " ^.* \ [ \ d+ \ ]$ " , self . get_root_xpath ( ) ) )
2016-05-20 21:23:39 +03:00
if is_child or leave_stub :
2015-09-05 00:03:31 +03:00
# User requested to clear an object that is the child of
# another object (xpath ends in [1] etc). We can't fully remove
# the node in that case, since then the xmlbuilder object is
# no longer valid, and all the other child xpaths will be
# pointing to the wrong node. So just stub out the content
node = _get_xpath_node ( self . _xmlstate . xml_ctx ,
self . get_root_xpath ( ) )
indent = 2 * self . get_root_xpath ( ) . count ( " / " )
2016-05-20 21:23:39 +03:00
if node :
node . setContent ( " \n " + ( indent * " " ) )
2015-09-05 00:03:31 +03:00
else :
_remove_xpath_node ( self . _xmlstate . xml_ctx ,
self . get_root_xpath ( ) )
2014-01-25 23:57:10 +04:00
2013-07-25 19:02:56 +04:00
def validate ( self ) :
"""
Validate any set values and raise an exception if there ' s
a problem
"""
pass
2013-07-15 02:31:33 +04:00
2013-10-06 21:17:35 +04:00
def set_defaults ( self , guest ) :
2013-07-25 19:02:56 +04:00
"""
Encode any default values if needed
"""
2013-10-06 21:17:35 +04:00
ignore = guest
2013-07-15 02:31:33 +04:00
###################
# Child overrides #
###################
2013-07-18 01:58:24 +04:00
def _prepare_get_xml ( self ) :
2013-07-25 19:02:56 +04:00
"""
Subclasses can override this to do any pre - get_xml_config setup .
Returns data to pass to finish_get_xml
"""
2013-07-18 01:58:24 +04:00
return None
2013-07-25 19:02:56 +04:00
2013-07-18 01:58:24 +04:00
def _finish_get_xml ( self , data ) :
2013-07-25 19:02:56 +04:00
"""
Called after get_xml_config . Data is whatever was returned by
_prepare_get_xml
"""
2013-07-18 01:58:24 +04:00
ignore = data
2013-07-15 02:31:33 +04:00
2013-07-25 19:02:56 +04:00
################
# Internal API #
################
2013-09-23 15:37:33 +04:00
def __get_prop_cache ( self , cachename , checkclass ) :
if not hasattr ( self . __class__ , cachename ) :
2013-09-20 19:30:33 +04:00
ret = { }
for c in reversed ( type . mro ( self . __class__ ) [ : - 1 ] ) :
for key , val in c . __dict__ . items ( ) :
2013-09-23 02:13:24 +04:00
if isinstance ( val , checkclass ) :
2013-09-20 19:30:33 +04:00
ret [ key ] = val
2013-09-23 15:37:33 +04:00
setattr ( self . __class__ , cachename , ret )
return getattr ( self . __class__ , cachename )
def _all_xml_props ( self ) :
"""
Return a list of all XMLProperty instances that this class has .
"""
cachename = self . __class__ . __name__ + " _cached_xml_props "
return self . __get_prop_cache ( cachename , XMLProperty )
2013-07-25 19:02:56 +04:00
2013-09-11 02:32:10 +04:00
def _all_child_props ( self ) :
2013-07-25 19:02:56 +04:00
"""
2013-09-11 02:32:10 +04:00
Return a list of all XMLChildProperty instances that this class has .
2013-07-25 19:02:56 +04:00
"""
2013-09-23 15:37:33 +04:00
cachename = self . __class__ . __name__ + " _cached_child_props "
return self . __get_prop_cache ( cachename , XMLChildProperty )
2013-09-20 19:30:33 +04:00
2013-09-11 02:32:10 +04:00
2013-09-11 18:16:12 +04:00
def _set_parent_xpath ( self , xpath ) :
self . _xmlstate . set_parent_xpath ( xpath )
2013-09-19 21:25:52 +04:00
for propname in self . _all_child_props ( ) :
for p in util . listify ( getattr ( self , propname , [ ] ) ) :
p . _set_parent_xpath ( self . get_root_xpath ( ) )
2013-09-11 18:16:12 +04:00
def _set_relative_object_xpath ( self , xpath ) :
self . _xmlstate . set_relative_object_xpath ( xpath )
2013-09-19 21:25:52 +04:00
for propname in self . _all_child_props ( ) :
for p in util . listify ( getattr ( self , propname , [ ] ) ) :
p . _set_parent_xpath ( self . get_root_xpath ( ) )
2013-09-11 18:16:12 +04:00
2015-09-05 00:03:31 +03:00
def _find_child_prop ( self , child_class , return_single = False ) :
2013-09-11 02:32:10 +04:00
xmlprops = self . _all_child_props ( )
for xmlprop in xmlprops . values ( ) :
2015-09-05 00:03:31 +03:00
if xmlprop . is_single and not return_single :
2013-09-11 19:47:09 +04:00
continue
2013-09-11 02:32:10 +04:00
if child_class in xmlprop . child_classes :
return xmlprop
raise RuntimeError ( " programming error: "
" Didn ' t find child property for child_class= %s " %
child_class )
2014-01-28 04:54:41 +04:00
def _parse_with_children ( self , * args , * * kwargs ) :
"""
Set new backing XML objects in ourselves and all our child props
"""
self . _xmlstate . _parse ( * args , * * kwargs )
for propname in self . _all_child_props ( ) :
for p in util . listify ( getattr ( self , propname , [ ] ) ) :
p . _xmlstate . _parse ( None , self . _xmlstate . xml_node )
2015-09-04 22:45:45 +03:00
def add_child ( self , obj ) :
2013-09-11 02:32:10 +04:00
"""
Insert the passed XMLBuilder object into our XML document . The
object needs to have an associated mapping via XMLChildProperty
2015-09-04 22:45:45 +03:00
or an error is thrown .
2013-09-11 02:32:10 +04:00
"""
xmlprop = self . _find_child_prop ( obj . __class__ )
2013-09-11 18:16:12 +04:00
xml = obj . get_xml_config ( )
2013-09-11 02:32:10 +04:00
xmlprop . append ( self , obj )
self . _set_child_xpaths ( )
if not obj . _xmlstate . is_build :
2013-09-11 18:16:12 +04:00
use_xpath = obj . get_root_xpath ( ) . rsplit ( " / " , 1 ) [ 0 ]
2013-09-10 01:14:16 +04:00
indent = 2 * obj . get_root_xpath ( ) . count ( " / " )
2013-09-20 04:18:12 +04:00
newnode = libxml2 . parseDoc ( util . xml_indent ( xml , indent ) ) . children
2016-07-18 20:34:06 +03:00
parentnode = _build_xpath_node ( self . _xmlstate . xml_ctx , use_xpath )
# Tack newnode on the end
_add_pretty_child ( parentnode , newnode )
2014-01-28 04:54:41 +04:00
obj . _parse_with_children ( None , self . _xmlstate . xml_node )
2013-09-10 03:45:19 +04:00
2015-09-04 22:45:45 +03:00
def remove_child ( self , obj ) :
2013-07-25 19:02:56 +04:00
"""
Remove the passed XMLBuilder object from our XML document , but
2015-09-04 22:45:45 +03:00
ensure its data isn ' t altered.
2013-07-25 19:02:56 +04:00
"""
2013-09-11 02:32:10 +04:00
xmlprop = self . _find_child_prop ( obj . __class__ )
xmlprop . remove ( self , obj )
xpath = obj . get_root_xpath ( )
xml = obj . get_xml_config ( )
2013-09-11 18:16:12 +04:00
obj . _set_parent_xpath ( None )
obj . _set_relative_object_xpath ( None )
2014-01-28 04:54:41 +04:00
obj . _parse_with_children ( xml , None )
2013-09-11 02:32:10 +04:00
_remove_xpath_node ( self . _xmlstate . xml_ctx , xpath , dofree = False )
self . _set_child_xpaths ( )
2015-09-04 23:07:01 +03:00
def list_children_for_class ( self , klass ) :
"""
Return a list of all XML child objects with the passed class
"""
ret = [ ]
for prop in self . _all_child_props ( ) . values ( ) :
ret + = [ obj for obj in util . listify ( prop . _get ( self ) )
if obj . __class__ == klass ]
return ret
2015-09-05 00:03:31 +03:00
def child_class_is_singleton ( self , klass ) :
"""
Return True if the passed class is registered as a singleton
child property
"""
return self . _find_child_prop ( klass , return_single = True ) . is_single
2013-07-25 19:02:56 +04:00
#################################
# Private XML building routines #
#################################
2013-07-24 22:37:07 +04:00
2013-09-11 02:32:10 +04:00
def _set_child_xpaths ( self ) :
"""
Walk the list of child properties and make sure their
xpaths point at their particular element
"""
typecount = { }
2013-09-11 18:16:12 +04:00
for propname , xmlprop in self . _all_child_props ( ) . items ( ) :
2013-09-11 19:47:09 +04:00
for obj in util . listify ( getattr ( self , propname ) ) :
idxstr = " "
if not xmlprop . is_single :
class_type = obj . __class__
if class_type not in typecount :
typecount [ class_type ] = 0
typecount [ class_type ] + = 1
idxstr = " [ %d ] " % typecount [ class_type ]
2013-09-11 02:32:10 +04:00
2013-09-11 18:16:12 +04:00
prop_path = xmlprop . get_prop_xpath ( self , obj )
obj . _set_parent_xpath ( self . get_root_xpath ( ) )
2013-09-11 19:47:09 +04:00
obj . _set_relative_object_xpath ( prop_path + idxstr )
2013-09-11 02:32:10 +04:00
2013-07-24 22:37:07 +04:00
def _do_get_xml_config ( self ) :
2013-07-25 19:02:56 +04:00
xmlstub = self . _xmlstate . make_xml_stub ( )
2013-07-24 22:37:07 +04:00
try :
node = None
2014-01-25 23:18:54 +04:00
ctx = self . _xmlstate . xml_ctx
2013-07-25 20:34:37 +04:00
if self . _xmlstate . is_build :
2013-07-25 19:02:56 +04:00
node = self . _xmlstate . xml_node . docCopyNodeList (
self . _xmlstate . xml_node . doc )
2014-01-25 23:18:54 +04:00
ctx = node
self . _add_parse_bits ( node )
ret = self . _xmlstate . get_node_xml ( ctx )
2013-07-24 22:37:07 +04:00
finally :
if node :
node . freeNode ( )
if ret == xmlstub :
ret = " "
2013-09-19 21:48:28 +04:00
# Ensure top level XML object always ends with a newline, just
# for back compat and readability
if ( ret and not self . get_root_xpath ( ) and not ret . endswith ( " \n " ) ) :
2013-07-24 22:37:07 +04:00
ret + = " \n "
return ret
2014-01-25 23:18:54 +04:00
def _add_parse_bits ( self , node ) :
2013-07-24 16:46:55 +04:00
"""
2013-07-25 19:02:56 +04:00
Callback that adds the implicitly tracked XML properties to
the backing xml .
2013-07-24 16:46:55 +04:00
"""
2013-07-25 19:02:56 +04:00
origproporder = self . _proporder [ : ]
origpropstore = self . _propstore . copy ( )
try :
2014-01-25 23:18:54 +04:00
return self . _do_add_parse_bits ( node )
2013-07-25 19:02:56 +04:00
finally :
self . _proporder = origproporder
self . _propstore = origpropstore
2013-03-18 01:06:52 +04:00
2014-01-25 23:18:54 +04:00
def _do_add_parse_bits ( self , node ) :
2013-07-18 01:58:24 +04:00
# Set all defaults if the properties have one registered
2013-09-11 02:32:10 +04:00
xmlprops = self . _all_xml_props ( )
2013-09-20 19:17:11 +04:00
childprops = self . _all_child_props ( )
2013-07-12 18:49:15 +04:00
2013-07-25 20:34:37 +04:00
for prop in xmlprops . values ( ) :
prop . _set_default ( self )
2013-07-15 01:02:42 +04:00
# Set up preferred XML ordering
do_order = self . _proporder [ : ]
2013-07-15 17:49:46 +04:00
for key in reversed ( self . _XML_PROP_ORDER ) :
2013-09-20 19:17:11 +04:00
if key not in xmlprops and key not in childprops :
raise RuntimeError ( " programming error: key ' %s ' must be "
" xml prop or child prop " % key )
2013-07-15 01:02:42 +04:00
if key in do_order :
do_order . remove ( key )
do_order . insert ( 0 , key )
2013-09-20 19:17:11 +04:00
elif key in childprops :
2013-07-18 01:58:24 +04:00
do_order . insert ( 0 , key )
2013-07-15 01:02:42 +04:00
2013-09-20 19:17:11 +04:00
for key in childprops . keys ( ) :
if key not in do_order :
do_order . append ( key )
2013-07-15 01:02:42 +04:00
# Alter the XML
for key in do_order :
2013-07-18 01:58:24 +04:00
if key in xmlprops :
2013-07-24 22:37:07 +04:00
xmlprops [ key ] . _set_xml ( self , self . _propstore [ key ] , node )
2013-09-20 19:17:11 +04:00
elif key in childprops :
for obj in util . listify ( getattr ( self , key ) ) :
2014-01-25 23:18:54 +04:00
obj . _add_parse_bits ( node )