[PATCH 0/2] Create a xmlutils module to hold all the XML manipulation

It is the first step to do a VMTemplate refactor, as all the XML manipulation will be in a single place. Aline Manera (2): Create a xmlutils module to hold all the XML manipulation Move networkxml.py to xmlutils module and update it to use lxml.builder src/kimchi/config.py.in | 2 +- src/kimchi/model/host.py | 4 +- src/kimchi/model/hostdev.py | 2 +- src/kimchi/model/networks.py | 30 ++++----- 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/networkxml.py | 127 ------------------------------------- src/kimchi/xmlutils.py | 63 ------------------ src/kimchi/xmlutils/__init__.py | 18 ++++++ src/kimchi/xmlutils/network.py | 123 +++++++++++++++++++++++++++++++++++ src/kimchi/xmlutils/utils.py | 63 ++++++++++++++++++ tests/test_networkxml.py | 29 ++++----- tests/test_vmtemplate.py | 2 +- 15 files changed, 261 insertions(+), 252 deletions(-) delete mode 100644 src/kimchi/networkxml.py delete mode 100644 src/kimchi/xmlutils.py create mode 100644 src/kimchi/xmlutils/__init__.py create mode 100644 src/kimchi/xmlutils/network.py create mode 100644 src/kimchi/xmlutils/utils.py -- 1.9.3

Also move former xmlutils.py to xmlutils/utils.py and adjust the imports accordingly to it. Signed-off-by: Aline Manera <alinefm@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

networkxml.py was moved to xmlutils/network.py and it was also updated to use lxml.builder instead of plan text while generating the XML. Update the imports accordinly to this changes and also the test cases. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/model/networks.py | 8 +-- src/kimchi/networkxml.py | 127 ----------------------------------------- src/kimchi/xmlutils/network.py | 123 +++++++++++++++++++++++++++++++++++++++ tests/test_networkxml.py | 27 +++++---- 4 files changed, 140 insertions(+), 145 deletions(-) delete mode 100644 src/kimchi/networkxml.py create mode 100644 src/kimchi/xmlutils/network.py diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py index 92fae4d..6d21e9e 100644 --- a/src/kimchi/model/networks.py +++ b/src/kimchi/model/networks.py @@ -27,11 +27,12 @@ from xml.sax.saxutils import escape from kimchi import netinfo from kimchi import network as knetwork -from kimchi import networkxml 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.network import create_vlan_tagged_bridge_xml +from kimchi.xmlutils.network import to_network_xml from kimchi.xmlutils.utils import xpath_get_text @@ -100,7 +101,7 @@ class NetworksModel(object): self._set_network_bridge(params) params['name'] = escape(params['name']) - xml = networkxml.to_network_xml(**params) + xml = to_network_xml(**params) try: network = conn.networkDefineXML(xml.encode("utf-8")) @@ -206,8 +207,7 @@ class NetworksModel(object): # Truncate the interface name if it exceeds 8 characters to make sure # the length of bridge name is less than 15 (its maximum value). br_name = KIMCHI_BRIDGE_PREFIX + interface[-8:] + '-' + vlan_id - br_xml = networkxml.create_vlan_tagged_bridge_xml(br_name, interface, - vlan_id) + br_xml = create_vlan_tagged_bridge_xml(br_name, interface, vlan_id) conn = self.conn.get() if br_name in [net.bridgeName() for net in conn.listAllNetworks()]: diff --git a/src/kimchi/networkxml.py b/src/kimchi/networkxml.py deleted file mode 100644 index ff30f16..0000000 --- a/src/kimchi/networkxml.py +++ /dev/null @@ -1,127 +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 ipaddr -import lxml.etree as ET - - -from lxml.builder import E - - -# FIXME, do not support ipv6 - -def _get_dhcp_xml(**kwargs): - """ - <dhcp> - <range start="192.168.122.100" end="192.168.122.254" /> - <host mac="00:16:3e:77:e2:ed" name="foo.test.com" ip="192.168.122.10" /> - <host mac="00:16:3e:3e:a9:1a" name="bar.test.com" ip="192.168.122.11" /> - </dhcp> - """ - xml = '' - dhcp_range = " <range start='%(start)s' end='%(end)s' />" - ipv4host = " <host mac='%(mac)s' name='%(name)s' ip='%(ip)s' />" - dhcp = [] - if 'range' in kwargs.keys(): - dhcp.append(dhcp_range % kwargs['range']) - if 'hosts' in kwargs.keys(): - dhcp.extend([ipv4host % host for host in kwargs['hosts']]) - if dhcp: - xml = "\n".join(["<dhcp>"] + dhcp + ["</dhcp>"]) - return xml - - -def _get_ip_xml(**kwargs): - """ - <ip address="192.168.152.1" netmask="255.255.255.0"> - <dhcp> - <range start="192.168.152.2" end="192.168.152.254" /> - </dhcp> - </ip> - """ - xml = "" - if 'net' in kwargs.keys(): - net = ipaddr.IPNetwork(kwargs['net']) - address = str(net.ip) - netmask = str(net.netmask) - dhcp_params = kwargs.get('dhcp', {}) - dhcp = _get_dhcp_xml(**dhcp_params) - xml = """ - <ip address='%s' netmask='%s'>" - %s - </ip>""" % (address, netmask, dhcp) - return xml - - -def _get_forward_xml(**kwargs): - """ - <forward mode='hostdev' dev='eth0' managed='yes'> - </forward> - """ - - if "mode" in kwargs.keys() and kwargs['mode'] is None: - return "" - mode = " mode='%s'" % kwargs['mode'] if 'mode' in kwargs.keys() else "" - dev = " dev='%s'" % kwargs['dev'] if 'dev' in kwargs.keys() else "" - managed = (" managed='%s'" % kwargs['managed'] - if 'managed' in kwargs.keys() else "") - xml = """ - <forward %s%s%s> - </forward> - """ % (mode, dev, managed) - return xml - - -def to_network_xml(**kwargs): - - params = {'name': kwargs['name']} - # None means is Isolated network, {} means default mode nat - forward = kwargs.get('forward', {"mode": None}) - ip = {'net': kwargs['net']} if 'net' in kwargs else {} - ip['dhcp'] = kwargs.get('dhcp', {}) - bridge = kwargs.get('bridge') - params = {'name': kwargs['name'], - 'forward': _get_forward_xml(**forward), - 'bridge': "<bridge name='%s' />" % bridge if bridge else "", - 'ip': _get_ip_xml(**ip)} - - xml = """ - <network> - <name>%(name)s</name> - %(bridge)s - %(forward)s - %(ip)s - </network> - """ % params - return xml - - -def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id): - vlan = E.vlan(E.interface(name=interface)) - vlan.set('tag', vlan_id) - m = E.interface( - E.start(mode='onboot'), - E.bridge( - E.interface( - vlan, - type='vlan', - name='.'.join([interface, vlan_id]))), - type='bridge', - name=bridge) - return ET.tostring(m) diff --git a/src/kimchi/xmlutils/network.py b/src/kimchi/xmlutils/network.py new file mode 100644 index 0000000..e78779c --- /dev/null +++ b/src/kimchi/xmlutils/network.py @@ -0,0 +1,123 @@ +# +# 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 ipaddr +import lxml.etree as ET + +from lxml.builder import E + + +# FIXME, do not support ipv6 +def _get_dhcp_elem(**kwargs): + """ + <dhcp> + <range start="192.168.122.100" end="192.168.122.254" /> + <host mac="00:16:3e:77:e2:ed" name="foo.test.com" ip="192.168.122.10" /> + <host mac="00:16:3e:3e:a9:1a" name="bar.test.com" ip="192.168.122.11" /> + </dhcp> + """ + dhcp = E.dhcp() + if 'range' in kwargs.keys(): + dhcp_range = E.range(start=kwargs['range']['start'], + end=kwargs['range']['end']) + dhcp.append(dhcp_range) + + if 'hosts' in kwargs.keys(): + for host in kwargs['hosts']: + dhcp.append(E.host(mac=host['mac'], + name=host['name'], + ip=host['ip'])) + + return dhcp if len(dhcp) > 0 else None + + +def _get_ip_elem(**kwargs): + """ + <ip address="192.168.152.1" netmask="255.255.255.0"> + <dhcp> + <range start="192.168.152.2" end="192.168.152.254" /> + </dhcp> + </ip> + """ + if 'net' not in kwargs.keys(): + return None + + net = ipaddr.IPNetwork(kwargs['net']) + ip = E.ip(address=str(net.ip), netmask=str(net.netmask)) + + dhcp_params = kwargs.get('dhcp', {}) + dhcp = _get_dhcp_elem(**dhcp_params) + if dhcp is not None: + ip.append(dhcp) + + return ip + + +def _get_forward_elem(**kwargs): + """ + <forward mode='hostdev' dev='eth0' managed='yes'> + </forward> + """ + if "mode" in kwargs.keys() and kwargs['mode'] is None: + return None + + forward = E.forward() + if 'mode' in kwargs.keys(): + forward.set('mode', kwargs['mode']) + + if 'dev' in kwargs.keys(): + forward.set('dev', kwargs['dev']) + + if 'managed' in kwargs.keys(): + forward.set('managed', kwargs['managed']) + + return forward + + +def to_network_xml(**kwargs): + network = E.network(E.name(kwargs['name'])) + bridge = kwargs.get('bridge') + if bridge: + network.append(E.bridge(name=bridge)) + + # None means is Isolated network, {} means default mode nat + params = kwargs.get('forward', {"mode": None}) + forward = _get_forward_elem(**params) + if forward is not None: + network.append(forward) + + if 'net' in kwargs: + network.append(_get_ip_elem(**kwargs)) + + return ET.tostring(network) + + +def create_vlan_tagged_bridge_xml(bridge, interface, vlan_id): + vlan = E.vlan(E.interface(name=interface)) + vlan.set('tag', vlan_id) + m = E.interface( + E.start(mode='onboot'), + E.bridge( + E.interface( + vlan, + type='vlan', + name='.'.join([interface, vlan_id]))), + type='bridge', + name=bridge) + return ET.tostring(m) diff --git a/tests/test_networkxml.py b/tests/test_networkxml.py index 674008d..3706a71 100644 --- a/tests/test_networkxml.py +++ b/tests/test_networkxml.py @@ -19,12 +19,11 @@ import ipaddr import unittest +import lxml.etree as ET - -import kimchi.networkxml as nxml import utils - +from kimchi.xmlutils import network as nxml from kimchi.xmlutils.utils import xpath_get_text @@ -42,18 +41,18 @@ class NetworkXmlTests(unittest.TestCase): "ip": "192.168.122.11"} params = {} - xml = nxml._get_dhcp_xml(**params) - self.assertEquals("", xml) + dhcp = nxml._get_dhcp_elem(**params) + self.assertEquals(None, dhcp) params["range"] = dhcp_range - xml = nxml._get_dhcp_xml(**params) + xml = ET.tostring(nxml._get_dhcp_elem(**params)) start = xpath_get_text(xml, "/dhcp/range/@start") end = xpath_get_text(xml, "/dhcp/range/@end") self.assertEquals(dhcp_range['start'], start[0]) self.assertEquals(dhcp_range['end'], end[0]) params["hosts"] = [host1, host2] - xml = nxml._get_dhcp_xml(**params) + xml = ET.tostring(nxml._get_dhcp_elem(**params)) ip = xpath_get_text(xml, "/dhcp/host/@ip") self.assertEquals(ip, [host1['ip'], host2['ip']]) @@ -64,12 +63,12 @@ class NetworkXmlTests(unittest.TestCase): dhcp_range = {"start": "192.168.122.100", "end": "192.168.122.254"} params = {} - xml = nxml._get_dhcp_xml(**params) - self.assertEquals("", xml) + dhcp = nxml._get_dhcp_elem(**params) + self.assertEquals(None, dhcp) params["net"] = "192.168.122.0/255.255.255.0" params["dhcp"] = {'range': dhcp_range} - xml = nxml._get_ip_xml(**params) + xml = ET.tostring(nxml._get_ip_elem(**params)) start = xpath_get_text(xml, "/ip/dhcp/range/@start")[0] end = xpath_get_text(xml, "/ip/dhcp/range/@end")[0] self.assertEquals(dhcp_range['start'], start) @@ -83,7 +82,7 @@ class NetworkXmlTests(unittest.TestCase): # test _get_ip_xml can accepts strings: '192.168.122.0/24', # which is same as "192.168.122.0/255.255.255.0" params["net"] = "192.168.122.0/24" - xml = nxml._get_ip_xml(**params) + xml = ET.tostring(nxml._get_ip_elem(**params)) netmask = xpath_get_text(xml, "/ip/@netmask")[0] self.assertEquals(netmask, str(ipaddr.IPNetwork(params["net"]).netmask)) @@ -94,12 +93,12 @@ class NetworkXmlTests(unittest.TestCase): """ params = {"mode": None} - xml = nxml._get_forward_xml(**params) - self.assertEquals("", xml) + forward = nxml._get_forward_elem(**params) + self.assertEquals(None, forward) params["mode"] = 'nat' params["dev"] = 'eth0' - xml = nxml._get_forward_xml(**params) + xml = ET.tostring(nxml._get_forward_elem(**params)) mode = xpath_get_text(xml, "/forward/@mode")[0] dev = xpath_get_text(xml, "/forward/@dev")[0] self.assertEquals(params['mode'], mode) -- 1.9.3

Reviewed-by: Daniel Barboza <danielhb@linux.vnet.ibm.com> On 10/16/2014 11:01 AM, Aline Manera wrote:
It is the first step to do a VMTemplate refactor, as all the XML manipulation will be in a single place.
Aline Manera (2): Create a xmlutils module to hold all the XML manipulation Move networkxml.py to xmlutils module and update it to use lxml.builder
src/kimchi/config.py.in | 2 +- src/kimchi/model/host.py | 4 +- src/kimchi/model/hostdev.py | 2 +- src/kimchi/model/networks.py | 30 ++++----- 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/networkxml.py | 127 ------------------------------------- src/kimchi/xmlutils.py | 63 ------------------ src/kimchi/xmlutils/__init__.py | 18 ++++++ src/kimchi/xmlutils/network.py | 123 +++++++++++++++++++++++++++++++++++ src/kimchi/xmlutils/utils.py | 63 ++++++++++++++++++ tests/test_networkxml.py | 29 ++++----- tests/test_vmtemplate.py | 2 +- 15 files changed, 261 insertions(+), 252 deletions(-) delete mode 100644 src/kimchi/networkxml.py delete mode 100644 src/kimchi/xmlutils.py create mode 100644 src/kimchi/xmlutils/__init__.py create mode 100644 src/kimchi/xmlutils/network.py create mode 100644 src/kimchi/xmlutils/utils.py
participants (2)
-
Aline Manera
-
Daniel H Barboza