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
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2013-03-18 01:06:52 +04:00
2018-03-20 18:06:23 +03:00
import collections
2013-07-12 23:16:29 +04:00
import os
2013-09-11 18:16:12 +04:00
import re
2019-06-07 00:59:51 +03:00
import string
import textwrap
2013-03-18 01:06:52 +04:00
2019-06-17 04:12:39 +03:00
from . logger import log
2018-02-14 23:35:53 +03:00
from . xmlapi import XMLAPI
2019-06-08 01:21:24 +03:00
from . import xmlutil
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 = [ ]
2013-03-18 01:06:52 +04:00
2020-09-10 20:52:07 +03:00
class XMLManualAction ( object ) :
"""
Helper class for tracking and performing the user requested manual
XML action
"""
ACTION_CREATE = 1
ACTION_DELETE = 2
ACTION_SET = 3
2022-02-27 19:38:13 +03:00
xpath_delete = None
xpath_create = None
xpath_value = None
xpath_set = None
def _process_args ( self ) :
def _ret ( x , a , v = None ) :
return ( x , a , v )
if self . xpath_delete :
return _ret ( self . xpath_delete , XMLManualAction . ACTION_DELETE )
if self . xpath_create :
return _ret ( self . xpath_create , XMLManualAction . ACTION_CREATE )
xpath = self . xpath_set
if self . xpath_value :
val = self . xpath_value
else :
if " = " not in str ( xpath ) :
2023-09-12 18:54:04 +03:00
raise ValueError (
2022-02-27 19:38:13 +03:00
" %s : Setting xpath must be in the form of XPATH=VALUE " %
xpath )
xpath , val = xpath . rsplit ( " = " , 1 )
return _ret ( xpath , XMLManualAction . ACTION_SET , val )
2020-09-10 20:52:07 +03:00
def perform ( self , xmlstate ) :
2022-02-27 19:38:13 +03:00
if ( not self . xpath_delete and
not self . xpath_create and
not self . xpath_value and
not self . xpath_set ) :
return
xpath , action , value = self . _process_args ( )
2020-09-10 20:52:07 +03:00
if xpath . startswith ( " . " ) :
2022-02-27 19:38:13 +03:00
xpath = xmlstate . make_abs_xpath ( xpath )
if action == self . ACTION_DELETE :
2020-09-10 20:52:07 +03:00
setval = False
2022-02-27 19:38:13 +03:00
elif action == self . ACTION_CREATE :
2020-09-10 20:52:07 +03:00
setval = True
else :
2022-02-27 19:38:13 +03:00
setval = value or None
2020-09-10 20:52:07 +03:00
xmlstate . xmlapi . set_xpath_content ( xpath , setval )
2018-03-20 18:56:12 +03:00
class _XMLPropertyCache ( object ) :
"""
Cache lookup tables mapping classes to their associated
XMLProperty and XMLChildProperty classes
"""
def __init__ ( self ) :
self . _name_to_prop = { }
self . _prop_to_name = { }
def _get_prop_cache ( self , cls , checkclass ) :
cachename = str ( cls ) + " - " + checkclass . __name__
if cachename not in self . _name_to_prop :
ret = { }
for c in reversed ( type . mro ( cls ) [ : - 1 ] ) :
for key , val in c . __dict__ . items ( ) :
if isinstance ( val , checkclass ) :
ret [ key ] = val
self . _prop_to_name [ val ] = key
self . _name_to_prop [ cachename ] = ret
return self . _name_to_prop [ cachename ]
def get_xml_props ( self , inst ) :
return self . _get_prop_cache ( inst . __class__ , XMLProperty )
def get_child_props ( self , inst ) :
return self . _get_prop_cache ( inst . __class__ , XMLChildProperty )
def get_prop_name ( self , propinst ) :
return self . _prop_to_name [ propinst ]
_PropCache = _XMLPropertyCache ( )
2018-02-08 01:27:56 +03:00
class _XMLChildList ( list ) :
"""
Little wrapper for a list containing XMLChildProperty output .
This is just to insert a dynamically created add_new ( ) function
which instantiates and appends a new child object
"""
2022-02-27 19:38:13 +03:00
def __init__ ( self , childclass , copylist , xmlbuilder , is_xml = True ) :
2018-02-08 01:27:56 +03:00
list . __init__ ( self )
self . _childclass = childclass
self . _xmlbuilder = xmlbuilder
2022-02-27 19:38:13 +03:00
self . _is_xml = is_xml
2018-02-08 01:27:56 +03:00
for i in copylist :
self . append ( i )
def new ( self ) :
"""
Instantiate a new child object and return it
"""
2022-02-27 19:38:13 +03:00
args = ( )
if self . _is_xml :
args = ( self . _xmlbuilder . conn , )
return self . _childclass ( * args )
2018-02-08 01:27:56 +03:00
def add_new ( self ) :
"""
Instantiate a new child object , append it , and return it
"""
obj = self . new ( )
2022-02-27 19:38:13 +03:00
if self . _is_xml :
self . _xmlbuilder . add_child ( obj )
else :
self . append ( obj )
2018-02-08 01:27:56 +03:00
return obj
2018-03-20 18:56:12 +03:00
class _XMLPropertyBase ( property ) :
def __init__ ( self , fget , fset ) :
self . _propname = None
property . __init__ ( self , fget = fget , fset = fset )
@property
def propname ( self ) :
"""
The variable name associated with this XMLProperty . So with
a definition like
foo = XMLProperty ( " ./@bar " )
and this will return " foo " .
"""
if not self . _propname :
self . _propname = _PropCache . get_prop_name ( self )
return self . _propname
class XMLChildProperty ( _XMLPropertyBase ) :
2013-09-11 02:32:10 +04:00
"""
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
2018-03-21 13:01:28 +03:00
@child_class : XMLBuilder class this property is tracking . So for
guest . devices . disk this is DeviceDisk
2013-09-11 18:16:12 +04:00
@relative_xpath : Relative location where the class is rooted compared
2018-03-21 17:49:29 +03:00
to its xmlbuilder root path . So if xmlbuilder is . / foo and we
want to track . / foo / bar / baz instances , set relative_xpath = . / bar
@is_single : If True , this represents an XML node that is only expected
to appear once , like < domain > < cpu >
2013-09-11 02:32:10 +04:00
"""
2018-03-21 13:01:28 +03:00
def __init__ ( self , child_class , relative_xpath = " . " , is_single = False ) :
self . child_class = child_class
2013-09-11 19:47:09 +04:00
self . is_single = is_single
2018-03-21 17:49:29 +03:00
self . relative_xpath = relative_xpath
2013-09-11 19:47:09 +04:00
2018-03-20 18:56:12 +03:00
_XMLPropertyBase . __init__ ( self , self . _fget , None )
2013-09-11 02:32:10 +04:00
def __repr__ ( self ) :
2018-03-21 13:01:28 +03:00
return " <XMLChildProperty %s %s > " % ( str ( self . child_class ) , id ( self ) )
2013-09-11 02:32:10 +04:00
2013-09-11 19:47:09 +04:00
def _get ( self , xmlbuilder ) :
2018-03-20 18:56:12 +03:00
if self . propname not in xmlbuilder . _propstore and not self . is_single :
xmlbuilder . _propstore [ self . propname ] = [ ]
return xmlbuilder . _propstore [ self . propname ]
2013-09-11 02:32:10 +04:00
def _fget ( self , xmlbuilder ) :
2013-09-11 19:47:09 +04:00
if self . is_single :
return self . _get ( xmlbuilder )
2018-03-21 13:01:28 +03:00
return _XMLChildList ( self . child_class ,
2018-02-08 01:27:56 +03:00
self . _get ( xmlbuilder ) ,
xmlbuilder )
2013-09-11 19:47:09 +04:00
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
2018-10-13 17:23:00 +03:00
def insert ( self , xmlbuilder , newobj , idx ) :
self . _get ( xmlbuilder ) . insert ( idx , newobj )
2013-09-20 18:47:36 +04:00
def append ( self , xmlbuilder , newobj ) :
2018-03-21 13:01:28 +03:00
self . _get ( xmlbuilder ) . append ( 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 ) :
2018-03-20 18:56:12 +03:00
xmlbuilder . _propstore [ self . propname ] = obj
2013-09-11 02:32:10 +04:00
2018-03-21 17:49:29 +03:00
def get_prop_xpath ( self , _xmlbuilder , obj ) :
2018-03-21 17:53:34 +03:00
return self . relative_xpath + " / " + obj . XML_NAME
2013-09-11 18:16:12 +04:00
2013-09-11 02:32:10 +04:00
2018-03-20 18:56:12 +03:00
class XMLProperty ( _XMLPropertyBase ) :
2018-02-23 04:44:09 +03:00
def __init__ ( self , xpath ,
2013-09-03 10:47:26 +04:00
is_bool = False , is_int = False , is_yesno = False , is_onoff = False ,
2018-09-02 22:08:10 +03:00
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
2018-02-14 15:17:31 +03:00
: param xpath : xpath string which maps to the associated property
2013-07-14 01:54:46 +04:00
in a typical XML document
2018-02-14 15:17:31 +03:00
: param name : Just a string to print for debugging , only needed
2013-07-14 06:13:13 +04:00
if xpath isn ' t specified.
2018-02-14 15:17:31 +03:00
: param is_bool : Whether this is a boolean property in the XML
: 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
: 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
2020-07-18 01:58:00 +03:00
if not self . _xpath :
raise xmlutil . DevError ( " XMLProperty: xpath must be passed. " )
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
2022-12-13 18:51:14 +03:00
conflicts = sum ( int ( bool ( i ) ) for i in
2013-09-02 18:56:22 +04:00
[ self . _is_bool , self . _is_int ,
2022-12-13 18:51:14 +03:00
self . _is_yesno , self . _is_onoff ] )
2020-07-18 01:58:00 +03:00
if conflicts > 1 :
raise xmlutil . DevError ( " Conflict property converter options. " )
2013-07-14 01:54:46 +04:00
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
2018-03-20 18:56:12 +03:00
_XMLPropertyBase . __init__ ( self , self . getter , self . setter )
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 #
####################
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
2018-09-02 22:08:10 +03:00
if self . _is_bool :
2019-05-11 00:47:54 +03:00
return bool ( val )
2013-07-15 00:05:59 +04:00
elif self . _is_int and val is not None :
2019-05-11 00:47:54 +03:00
try :
intkwargs = { }
if " 0x " in str ( val ) :
intkwargs [ " base " ] = 16
ret = int ( val , * * intkwargs )
except ValueError as e :
2019-06-17 04:12:39 +03:00
log . debug ( " Error converting XML value to int: %s " , e )
2019-05-11 00:47:54 +03:00
ret = val
elif self . _is_yesno :
if val == " yes " :
ret = True
elif val == " no " :
ret = False
else :
ret = val
elif self . _is_onoff :
if val == " on " :
ret = True
elif val == " off " :
ret = False
else :
ret = val
2013-07-17 17:57:15 +04:00
else :
ret = val
return ret
2013-07-14 05:04:27 +04:00
2018-09-04 00:03:02 +03:00
def _convert_set_value ( self , val ) :
2018-09-02 22:08:10 +03:00
if self . _do_abspath and val is not None :
2014-09-23 22:25:14 +04:00
val = os . path . abspath ( val )
2019-05-11 00:47:54 +03:00
elif self . _is_onoff :
if val is True :
val = " on "
elif val is False :
val = " off "
elif self . _is_yesno :
if val is True :
val = " yes "
elif val is False :
val = " no "
2013-07-25 23:27:42 +04:00
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
return val
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
2013-07-14 17:31:14 +04:00
2018-03-20 18:56:12 +03:00
if self . propname in propstore :
2022-12-13 18:51:14 +03:00
del propstore [ self . propname ]
2018-03-20 18:56:12 +03:00
propstore [ self . propname ] = val
2013-07-14 17:31:14 +04:00
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
"""
2018-03-20 18:56:12 +03:00
return xmlbuilder . _propstore . get ( self . propname , None )
2013-07-18 01:58:24 +04:00
2013-09-11 19:47:09 +04:00
def clear ( self , xmlbuilder ) :
2018-02-14 23:35:53 +03:00
# We only unset the cached data, since XML will be cleared elsewhere
propstore = xmlbuilder . _propstore
2018-03-20 18:56:12 +03:00
if self . propname in propstore :
2018-02-14 23:35:53 +03:00
self . setter ( 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
2018-09-02 22:08:10 +03:00
if self . propname in xmlbuilder . _propstore :
2013-07-24 22:37:07 +04:00
val = self . _nonxml_fget ( xmlbuilder )
2018-03-20 18:56:12 +03:00
else :
val = self . _get_xml ( xmlbuilder )
return self . _convert_get_value ( val )
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
"""
2018-02-14 23:35:53 +03:00
xpath = xmlbuilder . _xmlstate . make_abs_xpath ( self . _xpath )
return xmlbuilder . _xmlstate . xmlapi . get_xpath_content (
xpath , self . _is_bool )
2013-03-18 01:06:52 +04:00
2018-09-03 23:13:49 +03:00
def setter ( self , xmlbuilder , val ) :
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
2018-08-31 23:52:02 +03:00
get_xml time .
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
2018-09-04 00:03:02 +03:00
setval = self . _convert_set_value ( val )
2018-09-03 23:13:49 +03:00
self . _nonxml_fset ( xmlbuilder , setval )
2013-03-18 01:06:52 +04:00
2018-02-14 17:57:22 +03:00
def _set_xml ( self , xmlbuilder , setval ) :
2013-07-25 19:02:56 +04:00
"""
Actually set the passed value in the XML document
"""
2018-02-14 23:35:53 +03:00
xpath = xmlbuilder . _xmlstate . make_abs_xpath ( self . _xpath )
xmlbuilder . _xmlstate . xmlapi . set_xpath_content ( xpath , 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 ) :
2017-03-06 02:32:53 +03:00
def __init__ ( self , root_name , parsexml , parentxmlstate ,
relative_object_xpath ) :
self . _root_name = root_name
2018-02-23 22:39:47 +03:00
self . _namespace = " "
if " : " in self . _root_name :
ns = self . _root_name . split ( " : " ) [ 0 ]
self . _namespace = " xmlns: %s = ' %s ' " % ( ns , XMLAPI . NAMESPACES [ ns ] )
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"
2017-03-06 02:32:53 +03:00
self . _parent_xpath = (
2018-02-14 23:35:53 +03:00
parentxmlstate and parentxmlstate . abs_xpath ( ) ) or " "
2013-07-25 19:02:56 +04:00
2018-02-14 18:48:53 +03:00
self . xmlapi = None
2018-09-02 22:08:10 +03:00
self . is_build = not parsexml and not parentxmlstate
2018-02-07 23:38:30 +03:00
self . parse ( parsexml , parentxmlstate )
2017-03-06 02:32:53 +03:00
2018-02-07 23:38:30 +03:00
def parse ( self , parsexml , parentxmlstate ) :
2017-03-06 02:32:53 +03:00
if parentxmlstate :
2018-02-07 23:38:30 +03:00
self . is_build = parentxmlstate . is_build or self . is_build
2018-02-14 18:48:53 +03:00
self . xmlapi = parentxmlstate . xmlapi
2017-03-06 02:32:53 +03:00
return
2014-02-25 21:45:15 +04:00
2018-02-23 22:39:47 +03:00
# Make sure passed in XML has required xmlns inserted
2017-03-06 02:32:53 +03:00
if not parsexml :
2018-02-23 22:39:47 +03:00
parsexml = " < %s %s /> " % ( self . _root_name , self . _namespace )
elif self . _namespace and " xmlns " not in parsexml :
2018-02-26 22:56:24 +03:00
parsexml = parsexml . replace ( " < " + self . _root_name ,
" < " + self . _root_name + self . _namespace )
2014-02-25 21:45:15 +04:00
2017-03-06 02:32:53 +03:00
try :
2018-02-14 23:35:53 +03:00
self . xmlapi = XMLAPI ( parsexml )
2017-07-24 11:26:48 +03:00
except Exception :
2019-06-17 04:12:39 +03:00
log . debug ( " Error parsing xml= \n %s " , parsexml )
2017-03-06 02:32:53 +03:00
raise
2019-05-08 20:02:35 +03:00
if not self . is_build :
# Ensure parsexml has the correct root node
self . xmlapi . validate_root_name ( self . _root_name . split ( " : " ) [ - 1 ] )
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 " "
2018-02-14 23:35:53 +03:00
def _join_xpath ( self , x1 , x2 ) :
if x2 . startswith ( " . " ) :
x2 = x2 [ 1 : ]
return x1 + x2
2013-07-25 19:02:56 +04:00
2018-02-14 23:35:53 +03:00
def abs_xpath ( self ) :
return self . _join_xpath ( self . _parent_xpath or " . " ,
self . _relative_object_xpath or " . " )
def make_abs_xpath ( self , xpath ) :
"""
2018-03-20 19:18:35 +03:00
Convert a relative xpath to an absolute xpath . So for DeviceDisk
2018-02-14 23:35:53 +03:00
that ' s part of a Guest, accessing driver_name will do convert:
. / driver / @name
to an absolute xpath like :
. / devices / disk [ 3 ] / driver / @name
"""
return self . _join_xpath ( self . abs_xpath ( ) or " . " , xpath )
2013-09-11 18:16:12 +04:00
2013-07-25 19:02:56 +04:00
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
2018-03-21 17:53:34 +03:00
XML_NAME = None
2013-07-18 01:58:24 +04:00
2020-06-03 16:14:31 +03:00
# In some cases, libvirt can incorrectly generate unparsable XML.
2015-03-27 01:04:23 +03:00
# 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
2018-09-13 16:34:04 +03:00
@staticmethod
def register_namespace ( nsname , uri ) :
XMLAPI . register_namespace ( nsname , uri )
2019-06-08 01:02:42 +03:00
@staticmethod
def validate_generic_name ( name_label , val ) :
# Rather than try and match libvirt's regex, just forbid things we
# know don't work
forbid = [ " " ]
if not val :
2020-07-12 01:46:33 +03:00
# translators: value is a generic object type name
raise ValueError ( _ ( " A name must be specified for the %s " ) %
name_label )
2019-06-08 01:02:42 +03:00
for c in forbid :
if c not in val :
continue
2020-07-12 00:31:40 +03:00
msg = ( _ ( " %(objecttype)s name ' %(name)s ' can not contain "
" ' %(char)s ' character. " ) %
{ " objecttype " : name_label , " name " : val , " char " : c } )
raise ValueError ( msg )
2019-06-08 01:02:42 +03:00
2018-09-13 16:34:04 +03:00
2017-03-06 02:32:53 +03:00
def __init__ ( self , conn , parsexml = None ,
parentxmlstate = None , relative_object_xpath = None ) :
2013-03-18 01:06:52 +04:00
"""
Initialize state
2018-03-20 19:18:35 +03:00
: param conn : VirtinstConnection to validate device against
2018-02-14 15:17:31 +03:00
: param parsexml : Optional XML string to parse
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 :
2019-06-10 01:16:51 +03:00
parsexml = parsexml . encode ( " ascii " , " ignore " ) . decode ( " ascii " )
2015-03-27 01:04:23 +03:00
parsexml = " " . join ( [ c for c in parsexml if c in string . printable ] )
2018-03-20 18:06:23 +03:00
self . _propstore = collections . OrderedDict ( )
2018-03-21 17:53:34 +03:00
self . _xmlstate = _XMLState ( self . XML_NAME ,
2017-03-06 02:32:53 +03:00
parsexml , parentxmlstate ,
relative_object_xpath )
2013-09-11 18:16:12 +04:00
2018-03-20 18:56:12 +03:00
self . _validate_xmlbuilder ( )
2013-09-19 21:25:52 +04:00
self . _initial_child_parse ( )
2022-02-27 19:38:13 +03:00
self . xml_actions = _XMLChildList (
XMLManualAction , [ ] , self , is_xml = False )
2013-09-19 21:25:52 +04:00
2018-03-20 18:56:12 +03:00
def _validate_xmlbuilder ( self ) :
# This is one time validation we run once per XMLBuilder class
cachekey = self . __class__ . __name__ + " _xmlbuilder_validated "
if getattr ( self . __class__ , cachekey , False ) :
return
xmlprops = self . _all_xml_props ( )
childprops = self . _all_child_props ( )
for key in self . _XML_PROP_ORDER :
2020-07-18 01:58:00 +03:00
if key not in xmlprops and key not in childprops :
raise xmlutil . DevError (
" key ' %s ' must be xml prop or child prop " % key )
2018-03-20 18:56:12 +03:00
childclasses = [ ]
for childprop in childprops . values ( ) :
2020-07-18 01:58:00 +03:00
if childprop . child_class in childclasses :
raise xmlutil . DevError (
" can ' t register duplicate child_class= %s " %
childprop . child_class )
2018-03-20 18:56:12 +03:00
childclasses . append ( childprop . child_class )
setattr ( self . __class__ , cachekey , True )
2013-09-19 21:25:52 +04:00
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
2017-10-11 14:35:46 +03:00
for xmlprop in list ( self . _all_child_props ( ) . values ( ) ) :
2018-03-21 13:01:28 +03:00
child_class = xmlprop . child_class
prop_path = xmlprop . get_prop_xpath ( self , child_class )
2013-09-11 19:47:09 +04:00
if xmlprop . is_single :
obj = child_class ( self . conn ,
2017-03-06 02:32:53 +03:00
parentxmlstate = self . _xmlstate ,
2013-09-11 19:47:09 +04:00
relative_object_xpath = prop_path )
xmlprop . set ( self , obj )
continue
2018-03-21 13:01:28 +03:00
nodecount = self . _xmlstate . xmlapi . count (
self . _xmlstate . make_abs_xpath ( prop_path ) )
for idx in range ( nodecount ) :
idxstr = " [ %d ] " % ( idx + 1 )
obj = child_class ( self . conn ,
parentxmlstate = self . _xmlstate ,
relative_object_xpath = ( prop_path + idxstr ) )
xmlprop . append ( self , obj )
2013-09-19 21:25:52 +04:00
2018-02-14 23:35:53 +03:00
def __repr__ ( self ) :
return " < %s %s %s > " % ( self . __class__ . __name__ . split ( " . " ) [ - 1 ] ,
2018-03-21 17:53:34 +03:00
self . XML_NAME , id ( self ) )
2013-07-25 19:02:56 +04:00
############################
# Public XML managing APIs #
############################
2013-07-24 16:46:55 +04:00
2018-08-31 23:52:02 +03:00
def get_xml ( self ) :
2013-07-25 19:02:56 +04:00
"""
Return XML string of the object
"""
2018-02-23 03:49:08 +03:00
xmlapi = self . _xmlstate . xmlapi
if self . _xmlstate . is_build :
xmlapi = xmlapi . copy_api ( )
self . _add_parse_bits ( xmlapi )
ret = xmlapi . get_xml ( self . _xmlstate . make_abs_xpath ( " . " ) )
2021-10-12 20:38:15 +03:00
if not ret :
return ret
lastline = ret . rstrip ( ) . splitlines ( ) [ - 1 ]
if not ret . startswith ( " " ) and lastline . startswith ( " " ) :
ret = lastline . split ( " < " ) [ 0 ] + ret
if not ret . endswith ( " \n " ) :
2018-02-23 03:49:08 +03:00
ret + = " \n "
return ret
2013-07-24 16:46:55 +04:00
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
"""
2018-02-14 23:35:53 +03:00
props = list ( self . _all_xml_props ( ) . values ( ) )
props + = list ( self . _all_child_props ( ) . values ( ) )
for prop in props :
prop . clear ( self )
2013-03-18 01:06:52 +04:00
2018-09-29 20:59:19 +03:00
is_child = bool ( re . match ( r " ^.* \ [ \ d+ \ ]$ " , self . _xmlstate . abs_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
2018-02-14 23:35:53 +03:00
self . _xmlstate . xmlapi . node_clear ( self . _xmlstate . abs_xpath ( ) )
2015-09-05 00:03:31 +03:00
else :
2018-02-14 23:35:53 +03:00
self . _xmlstate . xmlapi . node_force_remove ( self . _xmlstate . abs_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
"""
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
2018-02-14 23:35:53 +03:00
def get_xml_id ( self ) :
"""
Return the location of the object in the XML document . This is
basically the absolute xpath , but the value returned should be
treated as opaque , it ' s just for cross XML comparisons. Used
in virt - manager code
"""
return self . _xmlstate . abs_xpath ( )
2018-03-21 21:42:50 +03:00
def get_xml_idx ( self ) :
"""
This is basically the offset parsed out of the object ' s xpath,
minus 1. So if this is the fifth < disk > in a < domain > , ret = 4.
If this is the only < cpu > in a domain , ret = 0.
"""
xpath = self . _xmlstate . abs_xpath ( )
if " [ " not in xpath :
return 0
return int ( xpath . rsplit ( " [ " , 1 ) [ 1 ] . strip ( " ] " ) ) - 1
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 _all_xml_props ( self ) :
"""
Return a list of all XMLProperty instances that this class has .
"""
2018-03-20 18:56:12 +03:00
return _PropCache . get_xml_props ( self )
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
"""
2018-03-20 18:56:12 +03:00
return _PropCache . get_child_props ( self )
2013-09-20 19:30:33 +04:00
2018-03-20 18:56:12 +03:00
def _find_child_prop ( self , child_class ) :
2013-09-11 02:32:10 +04:00
xmlprops = self . _all_child_props ( )
2019-06-10 01:16:51 +03:00
ret = None
2017-10-11 14:35:46 +03:00
for xmlprop in list ( xmlprops . values ( ) ) :
2018-03-20 18:56:12 +03:00
if xmlprop . is_single :
2013-09-11 19:47:09 +04:00
continue
2018-03-21 13:01:28 +03:00
if child_class is xmlprop . child_class :
2019-06-10 01:16:51 +03:00
ret = xmlprop
break
2020-07-18 01:58:00 +03:00
if not ret :
raise xmlutil . DevError (
2019-06-10 01:16:51 +03:00
" Didn ' t find child property for child_class= %s " % child_class )
return ret
2013-09-11 02:32:10 +04:00
2018-02-14 23:35:53 +03:00
def _set_xpaths ( self , parent_xpath , relative_object_xpath = - 1 ) :
"""
Change the object hierarchy ' s cached xpaths
"""
self . _xmlstate . set_parent_xpath ( parent_xpath )
if relative_object_xpath != - 1 :
self . _xmlstate . set_relative_object_xpath ( relative_object_xpath )
for propname in self . _all_child_props ( ) :
2019-06-08 01:21:24 +03:00
for p in xmlutil . listify ( getattr ( self , propname , [ ] ) ) :
2018-02-14 23:35:53 +03:00
p . _set_xpaths ( self . _xmlstate . abs_xpath ( ) )
def _set_child_xpaths ( self ) :
"""
Walk the list of child properties and make sure their
xpaths point at their particular element . Needs to be called
whenever child objects are added or removed
"""
typecount = { }
for propname , xmlprop in self . _all_child_props ( ) . items ( ) :
2019-06-08 01:21:24 +03:00
for obj in xmlutil . listify ( getattr ( self , propname ) ) :
2018-02-14 23:35:53 +03:00
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 ]
prop_path = xmlprop . get_prop_xpath ( self , obj )
obj . _set_xpaths ( self . _xmlstate . abs_xpath ( ) ,
prop_path + idxstr )
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
"""
2018-02-07 23:38:30 +03:00
self . _xmlstate . parse ( * args , * * kwargs )
2014-01-28 04:54:41 +04:00
for propname in self . _all_child_props ( ) :
2019-06-08 01:21:24 +03:00
for p in xmlutil . listify ( getattr ( self , propname , [ ] ) ) :
2018-02-14 23:35:53 +03:00
p . _parse_with_children ( None , self . _xmlstate )
2014-01-28 04:54:41 +04:00
2018-10-13 17:23:00 +03:00
def add_child ( self , obj , idx = None ) :
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
"""
xmlprop = self . _find_child_prop ( obj . __class__ )
2018-08-31 23:52:02 +03:00
xml = obj . get_xml ( )
2018-10-13 17:23:00 +03:00
if idx is None :
xmlprop . append ( self , obj )
else :
xmlprop . insert ( self , obj , idx )
2013-09-11 02:32:10 +04:00
self . _set_child_xpaths ( )
2018-09-02 22:08:10 +03:00
# Only insert the XML directly into the parent XML for !is_build
# This is the only way to dictate XML ordering when building
# from scratch, otherwise elements appear in the order they
# are set. It's just a style issue but annoying nonetheless
2013-09-11 02:32:10 +04:00
if not obj . _xmlstate . is_build :
2018-02-14 23:35:53 +03:00
use_xpath = obj . _xmlstate . abs_xpath ( ) . rsplit ( " / " , 1 ) [ 0 ]
indent = 2 * obj . _xmlstate . abs_xpath ( ) . count ( " / " )
self . _xmlstate . xmlapi . node_add_xml (
2019-06-07 00:59:51 +03:00
textwrap . indent ( xml , indent * " " ) , use_xpath )
2017-03-06 02:32:53 +03:00
obj . _parse_with_children ( None , self . _xmlstate )
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 )
2018-02-14 23:35:53 +03:00
xpath = obj . _xmlstate . abs_xpath ( )
2018-08-31 23:52:02 +03:00
xml = obj . get_xml ( )
2018-02-14 23:35:53 +03:00
obj . _set_xpaths ( None , None )
2014-01-28 04:54:41 +04:00
obj . _parse_with_children ( xml , None )
2018-02-14 23:35:53 +03:00
self . _xmlstate . xmlapi . node_force_remove ( xpath )
2013-09-11 02:32:10 +04:00
self . _set_child_xpaths ( )
2019-05-09 01:59:12 +03:00
def replace_child ( self , origobj , newobj ) :
"""
Replace the origobj child with the newobj . For is_build , this
replaces the objects , but for ! is_build this only replaces the
XML and keeps the object references in place . This is hacky and
it ' s fixable but at time or writing it doesn ' t matter for
our usecases .
"""
if not self . _xmlstate . is_build :
xpath = origobj . get_xml_id ( )
indent = 2 * xpath . count ( " / " )
2019-06-07 00:59:51 +03:00
xml = textwrap . indent ( newobj . get_xml ( ) , indent * " " ) . strip ( )
2019-05-09 01:59:12 +03:00
self . _xmlstate . xmlapi . node_replace_xml ( xpath , xml )
else :
origidx = origobj . get_xml_idx ( )
self . remove_child ( origobj )
self . add_child ( newobj , idx = origidx )
2018-09-02 16:02:32 +03:00
def _prop_is_unset ( self , propname ) :
"""
Return True if the property name has never had a value set
"""
if getattr ( self , propname ) :
return False
return propname not in self . _propstore
2013-07-25 19:02:56 +04:00
#################################
# Private XML building routines #
#################################
2013-07-24 22:37:07 +04:00
2018-02-14 18:48:53 +03:00
def _add_parse_bits ( self , xmlapi ) :
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
origpropstore = self . _propstore . copy ( )
2018-02-14 18:48:53 +03:00
origapi = self . _xmlstate . xmlapi
2013-07-25 19:02:56 +04:00
try :
2018-02-14 18:48:53 +03:00
self . _xmlstate . xmlapi = xmlapi
return self . _do_add_parse_bits ( )
2013-07-25 19:02:56 +04:00
finally :
2018-02-14 18:48:53 +03:00
self . _xmlstate . xmlapi = origapi
2013-07-25 19:02:56 +04:00
self . _propstore = origpropstore
2013-03-18 01:06:52 +04:00
2018-02-14 18:48:53 +03:00
def _do_add_parse_bits ( self ) :
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-15 01:02:42 +04:00
# Set up preferred XML ordering
2018-03-20 18:06:23 +03:00
do_order = [ p for p in self . _propstore if p not in childprops ]
2013-07-15 17:49:46 +04:00
for key in reversed ( self . _XML_PROP_ORDER ) :
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
2018-02-20 19:41:04 +03:00
for key in sorted ( list ( childprops . keys ( ) ) ) :
2013-09-20 19:17:11 +04:00
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 :
2018-02-14 17:57:22 +03:00
xmlprops [ key ] . _set_xml ( self , self . _propstore [ key ] )
2013-09-20 19:17:11 +04:00
elif key in childprops :
2019-06-08 01:21:24 +03:00
for obj in xmlutil . listify ( getattr ( self , key ) ) :
2018-02-14 18:48:53 +03:00
obj . _add_parse_bits ( self . _xmlstate . xmlapi )
2020-09-10 20:52:07 +03:00
2022-02-27 19:38:13 +03:00
for manualaction in self . xml_actions :
2020-09-10 20:52:07 +03:00
manualaction . perform ( self . _xmlstate )