[Kimchi-devel] [PATCH 1/2] Create a xmlutils module to hold all the XML manipulation

Aline Manera alinefm at linux.vnet.ibm.com
Thu Oct 16 14:01:35 UTC 2014


Also move former xmlutils.py to xmlutils/utils.py and adjust the imports
accordingly to it.

Signed-off-by: Aline Manera <alinefm at linux.vnet.ibm.com>
---
 src/kimchi/config.py.in            |  2 +-
 src/kimchi/model/host.py           |  4 +--
 src/kimchi/model/hostdev.py        |  2 +-
 src/kimchi/model/networks.py       | 22 ++++++-------
 src/kimchi/model/storagepools.py   | 16 +++++-----
 src/kimchi/model/storagevolumes.py |  5 ++-
 src/kimchi/model/templates.py      |  8 ++---
 src/kimchi/model/vms.py            | 21 ++++++-------
 src/kimchi/xmlutils.py             | 63 --------------------------------------
 src/kimchi/xmlutils/__init__.py    | 18 +++++++++++
 src/kimchi/xmlutils/utils.py       | 63 ++++++++++++++++++++++++++++++++++++++
 tests/test_networkxml.py           |  2 +-
 tests/test_vmtemplate.py           |  2 +-
 13 files changed, 121 insertions(+), 107 deletions(-)
 delete mode 100644 src/kimchi/xmlutils.py
 create mode 100644 src/kimchi/xmlutils/__init__.py
 create mode 100644 src/kimchi/xmlutils/utils.py

diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in
index 097c017..4792421 100644
--- a/src/kimchi/config.py.in
+++ b/src/kimchi/config.py.in
@@ -27,7 +27,7 @@ import threading
 from ConfigParser import SafeConfigParser
 
 
-from kimchi.xmlutils import xpath_get_text
+from kimchi.xmlutils.utils import xpath_get_text
 
 __version__ = "@kimchiversion@"
 __release__ = "@kimchirelease@"
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
index 5d31809..1bc3ca2 100644
--- a/src/kimchi/model/host.py
+++ b/src/kimchi/model/host.py
@@ -30,7 +30,6 @@ from cherrypy.process.plugins import BackgroundTask
 
 from kimchi import disks
 from kimchi import netinfo
-from kimchi import xmlutils
 from kimchi.basemodel import Singleton
 from kimchi.model import hostdev
 from kimchi.exception import InvalidOperation, InvalidParameter
@@ -41,6 +40,7 @@ from kimchi.model.vms import DOM_STATE_MAP
 from kimchi.repositories import Repositories
 from kimchi.swupdate import SoftwareUpdate
 from kimchi.utils import add_task, kimchi_log
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 HOST_STATS_INTERVAL = 1
@@ -348,7 +348,7 @@ class DevicesModel(object):
             for host in scsi_hosts:
                 xml = conn.nodeDeviceLookupByName(host).XMLDesc(0)
                 path = '/device/capability/capability/@type'
-                if 'fc_host' in xmlutils.xpath_get_text(xml, path):
+                if 'fc_host' in xpath_get_text(xml, path):
                     ret.append(host)
             return ret
         # Double verification to catch the case where the libvirt
diff --git a/src/kimchi/model/hostdev.py b/src/kimchi/model/hostdev.py
index 58ca92e..2a4a311 100644
--- a/src/kimchi/model/hostdev.py
+++ b/src/kimchi/model/hostdev.py
@@ -23,7 +23,7 @@ from pprint import pprint
 
 from kimchi.model.libvirtconnection import LibvirtConnection
 from kimchi.utils import kimchi_log
-from kimchi.xmlutils import dictize
+from kimchi.xmlutils.utils import dictize
 
 
 def _get_all_host_dev_infos(libvirt_conn):
diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py
index 43f9d50..92fae4d 100644
--- a/src/kimchi/model/networks.py
+++ b/src/kimchi/model/networks.py
@@ -28,11 +28,11 @@ from xml.sax.saxutils import escape
 from kimchi import netinfo
 from kimchi import network as knetwork
 from kimchi import networkxml
-from kimchi import xmlutils
 from kimchi.exception import InvalidOperation, InvalidParameter
 from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
 from kimchi.rollbackcontext import RollbackContext
 from kimchi.utils import kimchi_log, run_command
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 KIMCHI_BRIDGE_PREFIX = 'kb'
@@ -301,7 +301,7 @@ class NetworkModel(object):
     def _vm_get_networks(self, dom):
         xml = dom.XMLDesc(0)
         xpath = "/domain/devices/interface[@type='network']/source/@network"
-        return xmlutils.xpath_get_text(xml, xpath)
+        return xpath_get_text(xml, xpath)
 
     def activate(self, name):
         network = self.get_network(self.conn.get(), name)
@@ -335,25 +335,23 @@ class NetworkModel(object):
 
     @staticmethod
     def get_network_from_xml(xml):
-        address = xmlutils.xpath_get_text(xml, "/network/ip/@address")
+        address = xpath_get_text(xml, "/network/ip/@address")
         address = address and address[0] or ''
-        netmask = xmlutils.xpath_get_text(xml, "/network/ip/@netmask")
+        netmask = xpath_get_text(xml, "/network/ip/@netmask")
         netmask = netmask and netmask[0] or ''
         net = address and netmask and "/".join([address, netmask]) or ''
 
-        dhcp_start = xmlutils.xpath_get_text(xml,
-                                             "/network/ip/dhcp/range/@start")
+        dhcp_start = xpath_get_text(xml, "/network/ip/dhcp/range/@start")
         dhcp_start = dhcp_start and dhcp_start[0] or ''
-        dhcp_end = xmlutils.xpath_get_text(xml, "/network/ip/dhcp/range/@end")
+        dhcp_end = xpath_get_text(xml, "/network/ip/dhcp/range/@end")
         dhcp_end = dhcp_end and dhcp_end[0] or ''
         dhcp = {'start': dhcp_start, 'end': dhcp_end}
 
-        forward_mode = xmlutils.xpath_get_text(xml, "/network/forward/@mode")
+        forward_mode = xpath_get_text(xml, "/network/forward/@mode")
         forward_mode = forward_mode and forward_mode[0] or ''
-        forward_if = xmlutils.xpath_get_text(xml,
-                                             "/network/forward/interface/@dev")
-        forward_pf = xmlutils.xpath_get_text(xml, "/network/forward/pf/@dev")
-        bridge = xmlutils.xpath_get_text(xml, "/network/bridge/@name")
+        forward_if = xpath_get_text(xml, "/network/forward/interface/@dev")
+        forward_pf = xpath_get_text(xml, "/network/forward/pf/@dev")
+        bridge = xpath_get_text(xml, "/network/bridge/@name")
         bridge = bridge and bridge[0] or ''
         return {'subnet': net, 'dhcp': dhcp, 'bridge': bridge,
                 'forward': {'mode': forward_mode,
diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py
index 49b2f6a..d44e079 100644
--- a/src/kimchi/model/storagepools.py
+++ b/src/kimchi/model/storagepools.py
@@ -19,7 +19,6 @@
 
 import libvirt
 
-from kimchi import xmlutils
 from kimchi.scan import Scanner
 from kimchi.exception import InvalidOperation, MissingParameter
 from kimchi.exception import NotFoundError, OperationFailed
@@ -27,6 +26,7 @@ from kimchi.model.config import CapabilitiesModel
 from kimchi.model.host import DeviceModel
 from kimchi.model.libvirtstoragepool import StoragePoolDef
 from kimchi.utils import add_task, kimchi_log, pool_name_from_uri, run_command
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 ISO_POOL_NAME = u'kimchi_isos'
@@ -212,7 +212,7 @@ class StoragePoolModel(object):
             return source
 
         for key, val in STORAGE_SOURCES[pool_type].items():
-            res = xmlutils.xpath_get_text(pool_xml, val)
+            res = xpath_get_text(pool_xml, val)
             if len(res) == 1:
                 source[key] = res[0]
             elif len(res) == 0:
@@ -224,7 +224,7 @@ class StoragePoolModel(object):
     def _nfs_status_online(self, pool, poolArgs=None):
         if not poolArgs:
             xml = pool.XMLDesc(0)
-            pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+            pool_type = xpath_get_text(xml, "/pool/@type")[0]
             source = self._get_storage_source(pool_type, xml)
             poolArgs = {}
             poolArgs['name'] = pool.name()
@@ -245,8 +245,8 @@ class StoragePoolModel(object):
         autostart = True if pool.autostart() else False
         persistent = True if pool.isPersistent() else False
         xml = pool.XMLDesc(0)
-        path = xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
-        pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+        path = xpath_get_text(xml, "/pool/target/path")[0]
+        pool_type = xpath_get_text(xml, "/pool/@type")[0]
         source = self._get_storage_source(pool_type, xml)
         # FIXME: nfs workaround - prevent any libvirt operation
         # for a nfs if the corresponding NFS server is down.
@@ -319,7 +319,7 @@ class StoragePoolModel(object):
         if 'disks' in params:
             # check if pool is type 'logical'
             xml = pool.XMLDesc(0)
-            pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+            pool_type = xpath_get_text(xml, "/pool/@type")[0]
             if pool_type != 'logical':
                 raise InvalidOperation('KCHPOOL0029E')
             self._update_lvm_disks(name, params['disks'])
@@ -331,7 +331,7 @@ class StoragePoolModel(object):
         # FIXME: nfs workaround - do not activate a NFS pool
         # if the NFS server is not reachable.
         xml = pool.XMLDesc(0)
-        pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+        pool_type = xpath_get_text(xml, "/pool/@type")[0]
         if pool_type == 'netfs' and not self._nfs_status_online(pool):
             # block the user from activating the pool.
             source = self._get_storage_source(pool_type, xml)
@@ -362,7 +362,7 @@ class StoragePoolModel(object):
         # FIXME: nfs workaround - do not try to deactivate a NFS pool
         # if the NFS server is not reachable.
         xml = pool.XMLDesc(0)
-        pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+        pool_type = xpath_get_text(xml, "/pool/@type")[0]
         if pool_type == 'netfs' and not self._nfs_status_online(pool):
             # block the user from dactivating the pool.
             source = self._get_storage_source(pool_type, xml)
diff --git a/src/kimchi/model/storagevolumes.py b/src/kimchi/model/storagevolumes.py
index 23927e3..1ee8d0a 100644
--- a/src/kimchi/model/storagevolumes.py
+++ b/src/kimchi/model/storagevolumes.py
@@ -24,7 +24,6 @@ import urllib2
 
 import libvirt
 
-from kimchi import xmlutils
 from kimchi.config import READONLY_POOL_TYPE
 from kimchi.exception import InvalidOperation, InvalidParameter, IsoFormatError
 from kimchi.exception import MissingParameter, NotFoundError, OperationFailed
@@ -34,6 +33,7 @@ from kimchi.model.tasks import TaskModel
 from kimchi.model.vms import VMsModel, VMModel
 from kimchi.utils import add_task, kimchi_log
 from kimchi.vmdisks import get_vm_disk, get_vm_disk_list
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 VOLUME_TYPE_MAP = {0: 'file',
@@ -286,8 +286,7 @@ class StorageVolumeModel(object):
         info = vol.info()
         xml = vol.XMLDesc(0)
         try:
-            fmt = xmlutils.xpath_get_text(
-                xml, "/volume/target/format/@type")[0]
+            fmt = xpath_get_text(xml, "/volume/target/format/@type")[0]
         except IndexError:
             # Not all types of libvirt storage can provide volume format
             # infomation. When there is no format information, we assume
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
index 9278cdc..bb5bd3a 100644
--- a/src/kimchi/model/templates.py
+++ b/src/kimchi/model/templates.py
@@ -22,13 +22,13 @@ import os
 
 import libvirt
 
-from kimchi import xmlutils
 from kimchi.exception import InvalidOperation, InvalidParameter
 from kimchi.exception import NotFoundError, OperationFailed
 from kimchi.kvmusertests import UserTests
 from kimchi.utils import pool_name_from_uri
 from kimchi.utils import probe_file_permission_as_user
 from kimchi.vmtemplate import VMTemplate
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 class TemplatesModel(object):
@@ -88,7 +88,7 @@ class TemplatesModel(object):
 
     def template_volume_validate(self, tmp_volumes, pool):
         kwargs = {'conn': self.conn, 'objstore': self.objstore}
-        pool_type = xmlutils.xpath_get_text(pool.XMLDesc(0), "/pool/@type")[0]
+        pool_type = xpath_get_text(pool.XMLDesc(0), "/pool/@type")[0]
         pool_name = pool.name()
 
         # as we discussion, we do not mix disks from 2 different types of
@@ -235,12 +235,12 @@ class LibvirtVMTemplate(VMTemplate):
     def _get_storage_path(self):
         pool = self._storage_validate()
         xml = pool.XMLDesc(0)
-        return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
+        return xpath_get_text(xml, "/pool/target/path")[0]
 
     def _get_storage_type(self):
         pool = self._storage_validate()
         xml = pool.XMLDesc(0)
-        return xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+        return xpath_get_text(xml, "/pool/@type")[0]
 
     def _get_volume_path(self, pool, vol):
         pool = self._storage_validate()
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index 58686cd..1089464 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -31,7 +31,6 @@ import libvirt
 from cherrypy.process.plugins import BackgroundTask
 
 from kimchi import vnc
-from kimchi import xmlutils
 from kimchi.config import READONLY_POOL_TYPE
 from kimchi.exception import InvalidOperation, InvalidParameter
 from kimchi.exception import NotFoundError, OperationFailed
@@ -43,7 +42,7 @@ from kimchi.model.utils import set_metadata_node
 from kimchi.screenshot import VMScreenshot
 from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr
 from kimchi.utils import template_name_from_uri
-from kimchi.xmlutils import xpath_get_text
+from kimchi.xmlutils.utils import xpath_get_text, xml_item_update
 
 
 DOM_STATE_MAP = {0: 'nostate',
@@ -352,7 +351,7 @@ class VMModel(object):
                 if type(val) == int:
                     val = str(val)
                 xpath = VM_STATIC_UPDATE_PARAMS[key]
-                new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
+                new_xml = xml_item_update(new_xml, xpath, val)
 
         if 'graphics' in params:
             new_xml = self._update_graphics(dom, new_xml, params)
@@ -445,7 +444,7 @@ class VMModel(object):
     def _vm_get_disk_paths(self, dom):
         xml = dom.XMLDesc(0)
         xpath = "/domain/devices/disk[@device='disk']/source/@file"
-        return xmlutils.xpath_get_text(xml, xpath)
+        return xpath_get_text(xml, xpath)
 
     @staticmethod
     def get_vm(name, conn):
@@ -480,7 +479,7 @@ class VMModel(object):
             vol = conn.storageVolLookupByPath(path)
             pool = vol.storagePoolLookupByVolume()
             xml = pool.XMLDesc(0)
-            pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+            pool_type = xpath_get_text(xml, "/pool/@type")[0]
             if pool_type not in READONLY_POOL_TYPE:
                 vol.delete(0)
         try:
@@ -498,7 +497,7 @@ class VMModel(object):
         dom = self.get_vm(name, self.conn)
         xml = dom.XMLDesc(0)
         xpath = "/domain/devices/disk[@device='cdrom']/source/@file"
-        isofiles = xmlutils.xpath_get_text(xml, xpath)
+        isofiles = xpath_get_text(xml, xpath)
         for iso in isofiles:
             run_setfacl_set_attr(iso)
 
@@ -538,25 +537,25 @@ class VMModel(object):
         xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
 
         expr = "/domain/devices/graphics/@type"
-        res = xmlutils.xpath_get_text(xml, expr)
+        res = xpath_get_text(xml, expr)
         graphics_type = res[0] if res else None
 
         expr = "/domain/devices/graphics/@listen"
-        res = xmlutils.xpath_get_text(xml, expr)
+        res = xpath_get_text(xml, expr)
         graphics_listen = res[0] if res else None
 
         graphics_port = graphics_passwd = graphics_passwdValidTo = None
         if graphics_type:
             expr = "/domain/devices/graphics[@type='%s']/@port"
-            res = xmlutils.xpath_get_text(xml, expr % graphics_type)
+            res = xpath_get_text(xml, expr % graphics_type)
             graphics_port = int(res[0]) if res else None
 
             expr = "/domain/devices/graphics[@type='%s']/@passwd"
-            res = xmlutils.xpath_get_text(xml, expr % graphics_type)
+            res = xpath_get_text(xml, expr % graphics_type)
             graphics_passwd = res[0] if res else None
 
             expr = "/domain/devices/graphics[@type='%s']/@passwdValidTo"
-            res = xmlutils.xpath_get_text(xml, expr % graphics_type)
+            res = xpath_get_text(xml, expr % graphics_type)
             if res:
                 to = time.mktime(time.strptime(res[0], '%Y-%m-%dT%H:%M:%S'))
                 graphics_passwdValidTo = to - time.mktime(time.gmtime())
diff --git a/src/kimchi/xmlutils.py b/src/kimchi/xmlutils.py
deleted file mode 100644
index 00a9d55..0000000
--- a/src/kimchi/xmlutils.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Project Kimchi
-#
-# Copyright IBM, Corp. 2013-2014
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library 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
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
-
-import libxml2
-from lxml import objectify
-
-
-from xml.etree import ElementTree
-
-
-def xpath_get_text(xml, expr):
-    doc = libxml2.parseDoc(xml)
-    res = doc.xpathEval(expr)
-    ret = [None if x.children is None else x.children.content for x in res]
-
-    doc.freeDoc()
-    return ret
-
-
-def xml_item_update(xml, xpath, value):
-    root = ElementTree.fromstring(xml)
-    item = root.find(xpath)
-    item.text = value
-    return ElementTree.tostring(root, encoding="utf-8")
-
-
-def dictize(xmlstr):
-    root = objectify.fromstring(xmlstr)
-    return {root.tag: _dictize(root)}
-
-
-def _dictize(e):
-    d = {}
-    if e.text is not None:
-        if not e.attrib and e.countchildren() == 0:
-            return e.pyval
-        d['pyval'] = e.pyval
-    d.update(e.attrib)
-    for child in e.iterchildren():
-        if child.tag in d:
-            continue
-        if len(child) > 1:
-            d[child.tag] = [
-                _dictize(same_tag_child) for same_tag_child in child]
-        else:
-            d[child.tag] = _dictize(child)
-    return d
diff --git a/src/kimchi/xmlutils/__init__.py b/src/kimchi/xmlutils/__init__.py
new file mode 100644
index 0000000..ca7ede4
--- /dev/null
+++ b/src/kimchi/xmlutils/__init__.py
@@ -0,0 +1,18 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
diff --git a/src/kimchi/xmlutils/utils.py b/src/kimchi/xmlutils/utils.py
new file mode 100644
index 0000000..2af1c2c
--- /dev/null
+++ b/src/kimchi/xmlutils/utils.py
@@ -0,0 +1,63 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2014
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+
+import libxml2
+from lxml import objectify
+
+
+from xml.etree import ElementTree
+
+
+def xpath_get_text(xml, expr):
+    doc = libxml2.parseDoc(xml)
+    res = doc.xpathEval(expr)
+    ret = [None if x.children is None else x.children.content for x in res]
+
+    doc.freeDoc()
+    return ret
+
+
+def xml_item_update(xml, xpath, value):
+    root = ElementTree.fromstring(xml)
+    item = root.find(xpath)
+    item.text = value
+    return ElementTree.tostring(root, encoding="utf-8")
+
+
+def dictize(xmlstr):
+    root = objectify.fromstring(xmlstr)
+    return {root.tag: _dictize(root)}
+
+
+def _dictize(e):
+    d = {}
+    if e.text is not None:
+        if not e.attrib and e.countchildren() == 0:
+            return e.pyval
+        d['pyval'] = e.pyval
+    d.update(e.attrib)
+    for child in e.iterchildren():
+        if child.tag in d:
+            continue
+        if len(child) > 1:
+            d[child.tag] = [
+                _dictize(same_tag_child) for same_tag_child in child]
+        else:
+            d[child.tag] = _dictize(child)
+    return d
diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py
index d714413..674008d 100644
--- a/tests/test_networkxml.py
+++ b/tests/test_networkxml.py
@@ -25,7 +25,7 @@ import kimchi.networkxml as nxml
 import utils
 
 
-from kimchi.xmlutils import xpath_get_text
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 class NetworkXmlTests(unittest.TestCase):
diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py
index 2a6fb8e..550bb2a 100644
--- a/tests/test_vmtemplate.py
+++ b/tests/test_vmtemplate.py
@@ -23,7 +23,7 @@ import uuid
 
 
 from kimchi.vmtemplate import VMTemplate
-from kimchi.xmlutils import xpath_get_text
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 class VMTemplateTests(unittest.TestCase):
-- 
1.9.3




More information about the Kimchi-devel mailing list