[PATCH V11 0/7] bug fix: get user and group when VM is running

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> V10 -> V11 fix typo V9 -> V10 metadata xml will be changed to hierarchical structure rather than flat. V8 -> V9 move meta feature test to src/kimchi/model/config.py use ElementMaker to construct a metanode. V7 -> V8 add a feature test to probe libvirt support metadata. add namespace for manully metadata. V6 -> V7: After V6 rebase, still find one error code "KCHVM0029E" does not change. V5 -> V6: rebase V4 -> V5: it is wrong to call dom.isPersistent in V4, fix it. V3 -> V4: work around if libvirt do not support metadata API well. V2 -> V3: move the virDomain.metadata and virDomain.setMetadata to model/utils.py add testcase V1 -> V2: libvirt also support virDomain.metadata and virDomain.setMetadata two api. use virDomain.metadata to get the user and group. use virDomain.setMetadata to set the user and group. ShaoHe Feng (7): bug fix: call a method should be followed by "()" add method to test libvirt metadata api are available Add two function to set and get domain xml metadata manually manage the metadata element bug fix: get user and group when vm is living. update test case to set/get user and group when VM is running write the template OS info to vm metadata src/kimchi/featuretests.py | 33 +++++++++++- src/kimchi/i18n.py | 1 + src/kimchi/model/config.py | 2 + src/kimchi/model/utils.py | 124 +++++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 105 +++++++++++++++++++++++--------------- tests/test_model.py | 13 +++++ 6 files changed, 236 insertions(+), 42 deletions(-) -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> it should be: FeatureTests.enable_screen_error_logging() instead of: FeatureTests.enable_screen_error_logging Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/featuretests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py index 07d5676..b1001ea 100644 --- a/src/kimchi/featuretests.py +++ b/src/kimchi/featuretests.py @@ -171,7 +171,7 @@ class FeatureTests(object): # Libvirt requires adapter name, not needed when supports to FC return False finally: - FeatureTests.enable_screen_error_logging + FeatureTests.enable_screen_error_logging() pool is None or pool.undefine() conn is None or conn.close() return True -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> same mechanism with other feature tests. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/featuretests.py | 31 +++++++++++++++++++++++++++++++ src/kimchi/model/config.py | 2 ++ 2 files changed, 33 insertions(+) diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py index b1001ea..5192361 100644 --- a/src/kimchi/featuretests.py +++ b/src/kimchi/featuretests.py @@ -28,6 +28,7 @@ import threading from lxml.builder import E +from kimchi.rollbackcontext import RollbackContext from kimchi.utils import kimchi_log @@ -53,6 +54,16 @@ ISO_STREAM_XML = """ </devices> </domain>""" +SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>A_SIMPLE_VM</name> + <memory unit='KiB'>10240</memory> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> +</domain>""" + SCSI_FC_XML = """ <pool type='scsi'> <name>TEST_SCSI_FC_POOL</name> @@ -175,3 +186,23 @@ class FeatureTests(object): pool is None or pool.undefine() conn is None or conn.close() return True + + @staticmethod + def has_metadata_support(): + KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/" + KIMCHI_NAMESPACE = "kimchi" + with RollbackContext() as rollback: + FeatureTests.disable_screen_error_logging() + rollback.prependDefer(FeatureTests.enable_screen_error_logging) + conn = libvirt.open('qemu:///system') + rollback.prependDefer(conn.close) + dom = conn.defineXML(SIMPLE_VM_XML) + rollback.prependDefer(dom.undefine) + try: + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + "<metatest/>", KIMCHI_NAMESPACE, + KIMCHI_META_URL, + flags=libvirt.VIR_DOMAIN_AFFECT_CURRENT) + return True + except libvirt.libvirtError: + return False diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py index c9e3e9d..0ef0855 100644 --- a/src/kimchi/model/config.py +++ b/src/kimchi/model/config.py @@ -53,6 +53,7 @@ class CapabilitiesModel(object): self.qemu_stream_dns = False self.libvirt_stream_protocols = [] self.fc_host_support = False + self.metadata_support = False # Subscribe function to set host capabilities to be run when cherrypy # server is up @@ -65,6 +66,7 @@ class CapabilitiesModel(object): self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns() self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe() self.fc_host_support = FeatureTests.libvirt_support_fc_host() + self.metadata_support = FeatureTests.has_metadata_support() self.libvirt_stream_protocols = [] for p in ['http', 'https', 'ftp', 'ftps', 'tftp']: -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Libvirt support two API to set and get domain xml metadata. These two function wrap them. Put them in model/utils.py, so vm_storage and vm_iface can make use of them. For set domain xml metadata, we set live and config xml by default. For get domain xml metadata, we get the current xml by default. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/i18n.py | 1 + src/kimchi/model/utils.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index a1af1f7..e28f689 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -87,6 +87,7 @@ messages = { "KCHVM0027E": _("User(s) '%(users)s' do not exist"), "KCHVM0028E": _("Group(s) '%(groups)s' do not exist"), "KCHVM0029E": _("Unable to shutdown virtual machine %(name)s. Details: %(err)s"), + "KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"), "KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"), "KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"), diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 80e1801..3136402 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from kimchi.exception import OperationFailed +import libvirt +from lxml import etree +from lxml.builder import E + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" +KIMCHI_NAMESPACE = "kimchi" def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +35,55 @@ def get_vm_name(vm_name, t_name, name_list): if vm_name not in name_list: return vm_name raise OperationFailed("KCHUTILS0003E") + + +def get_vm_config_flag(dom, mode="persistent"): + # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 + # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 + flag = {"live": libvirt.VIR_DOMAIN_AFFECT_LIVE, + "persistent": libvirt.VIR_DOMAIN_AFFECT_CONFIG, + "current": libvirt.VIR_DOMAIN_AFFECT_CURRENT, + "all": libvirt.VIR_DOMAIN_AFFECT_CONFIG + + libvirt.VIR_DOMAIN_AFFECT_LIVE if dom.isActive() and + dom.isPersistent() else libvirt.VIR_DOMAIN_AFFECT_CURRENT} + + return flag[mode] + + +def libvirt_get_kimchi_metadata_node(dom, mode="current"): + try: + xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + return etree.fromstring(xml) + except libvirt.libvirtError: + return None + + +def set_metadata_node(dom, node_xml, mode="all"): + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element)) + + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + + +def get_metadata_node(dom, tag, mode="current"): + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> virDomain.metadata and virDomain.setMetadata does not work in all livirt versions used in the supported distros. So if libvirt do not support these two API. let kimchi manually manage the metadata element Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/utils.py | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 3136402..2614008 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,9 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from kimchi.exception import OperationFailed +from kimchi.model.config import CapabilitiesModel import libvirt -from lxml import etree -from lxml.builder import E +from lxml import etree, objectify +from lxml.builder import E, ElementMaker KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" @@ -37,6 +38,15 @@ def get_vm_name(vm_name, t_name, name_list): raise OperationFailed("KCHUTILS0003E") +def create_metadata_node(tag): + if CapabilitiesModel().metadata_support: + return E(tag) + else: + EM = ElementMaker(namespace=KIMCHI_META_URL, + nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) + return EM(tag) + + def get_vm_config_flag(dom, mode="persistent"): # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 @@ -50,6 +60,30 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode] +def _kimchi_set_metadata_node(dom, node_xml): + node = etree.fromstring(node_xml) + # some other tools will not let libvirt create a persistent + # configuration, raise exception. + if not dom.isPersistent(): + msg = 'The VM has not a persistent configuration' + raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + metadata = root.find("metadata") + if metadata is None: + metadata = E.metadata() + root.append(metadata) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + if kimchi is None: + kimchi = create_metadata_node("kimchi") + metadata.append(kimchi) + old_node = kimchi.find(node.tag) + (kimchi.replace(old_node, node) if old_node is not None + else kimchi.append(node)) + dom.connect().defineXML(etree.tostring(root)) + + def libvirt_get_kimchi_metadata_node(dom, mode="current"): try: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, @@ -61,29 +95,60 @@ def libvirt_get_kimchi_metadata_node(dom, mode="current"): def set_metadata_node(dom, node_xml, mode="all"): - element = etree.fromstring(node_xml) - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) - old_node = None - if kimchi is not None: - old_node = kimchi.find(element.tag) - else: - kimchi = E.kimchi() - (kimchi.replace(old_node, element) if old_node is not None - else kimchi.append(element)) + if CapabilitiesModel().metadata_support: + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element)) - node_xml = etree.tostring(kimchi) - # From libvirt doc, Passing None for @metadata says to remove that - # element from the domain XML (passing the empty string leaves the - # element present). Do not support remove the old metadata. - dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, - KIMCHI_NAMESPACE, KIMCHI_META_URL, - flags=get_vm_config_flag(dom, mode)) + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_metadata_node(dom, node_xml) -def get_metadata_node(dom, tag, mode="current"): - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) +def _kimchi_get_metadata_node(dom, tag): + # some other tools will not let libvirt create a persistent + # configuration, just return empty + if not dom.isPersistent(): + return "" + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + #remove the "kimchi" prifix of xml if kimchi is not None: + for child in kimchi.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(kimchi, cleanup_namespaces=True) + kimchi_xml = etree.tostring(kimchi) + kimchi = etree.fromstring(kimchi_xml) node = kimchi.find(tag) if node is not None: return etree.tostring(node) return "" + + +def get_metadata_node(dom, tag, mode="current"): + if CapabilitiesModel().metadata_support: + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" + else: + # FIXME remove this code when all distro libvirt supports metadata element + return _kimchi_get_metadata_node(dom, tag) -- 1.9.0

On 04/28/2014 12:44 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
virDomain.metadata and virDomain.setMetadata does not work in all livirt versions used in the supported distros.
So if libvirt do not support these two API.
let kimchi manually manage the metadata element
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/utils.py | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-)
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 3136402..2614008 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,9 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +from kimchi.model.config import CapabilitiesModel import libvirt -from lxml import etree -from lxml.builder import E +from lxml import etree, objectify +from lxml.builder import E, ElementMaker
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" @@ -37,6 +38,15 @@ def get_vm_name(vm_name, t_name, name_list): raise OperationFailed("KCHUTILS0003E")
+def create_metadata_node(tag): + if CapabilitiesModel().metadata_support: + return E(tag) + else: + EM = ElementMaker(namespace=KIMCHI_META_URL, + nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) + return EM(tag) + + def get_vm_config_flag(dom, mode="persistent"): # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 @@ -50,6 +60,30 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode]
+def _kimchi_set_metadata_node(dom, node_xml): + node = etree.fromstring(node_xml) + # some other tools will not let libvirt create a persistent + # configuration, raise exception. + if not dom.isPersistent(): + msg = 'The VM has not a persistent configuration' + raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + metadata = root.find("metadata") + if metadata is None: + metadata = E.metadata() + root.append(metadata) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + if kimchi is None: + kimchi = create_metadata_node("kimchi") + metadata.append(kimchi) + old_node = kimchi.find(node.tag) + (kimchi.replace(old_node, node) if old_node is not None + else kimchi.append(node)) + dom.connect().defineXML(etree.tostring(root)) + +
There is a some duplicated code here and in set_metadata_node() We should have special function only for special matters. See below
def libvirt_get_kimchi_metadata_node(dom, mode="current"): try: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, @@ -61,29 +95,60 @@ def libvirt_get_kimchi_metadata_node(dom, mode="current"):
def set_metadata_node(dom, node_xml, mode="all"):
Why node_xml? Use node directly as we use ET to create the element in model/vms.py That way we avoid ET node -> string -> ET node
- element = etree.fromstring(node_xml) - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) - old_node = None - if kimchi is not None: - old_node = kimchi.find(element.tag) - else: - kimchi = E.kimchi() - (kimchi.replace(old_node, element) if old_node is not None - else kimchi.append(element)) + if CapabilitiesModel().metadata_support: + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element))
There is already an if/else statement above, use the same one to append or replace the new node if kimchi is None: node = kimchi.find(element.tag) kimchi.replace(node, element) else: kimchi = E.kimchi() kimchi.append(element) Joining common code in one place: def set_metadata_node(dom, node, mode="all") kimchi = get_kimchi_metadata_node(dom, mode) if kimchi is None: kimchi = create_metadata_node("kimchi") else: old_node = kimchi.find(node.tag) kimchi.replace(old_node, node) if CapabilitiesModel().metadata_support: return dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, KIMCHI_NAMESPACE, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode)) # Without metadata support # some other tools will not let libvirt create a persistent # configuration, raise exception. if not dom.isPersistent(): msg = 'The VM has not a persistent configuration' raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") if metadtaa is None: metadata = E.metadata() root.append(metadata) old_node = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) metadata.replace(old_node, kimchi) if old_node else metadata.append(kimchi) dom.connect().defineXML(etree.tostring(root)) def get_kimchi_metadata_node(dom, mode): if CapabilitiesModel().metadata_support: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode)) return etree.fromstring(xml) # Without metadata support xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") return root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
- node_xml = etree.tostring(kimchi) - # From libvirt doc, Passing None for @metadata says to remove that - # element from the domain XML (passing the empty string leaves the - # element present). Do not support remove the old metadata. - dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, - KIMCHI_NAMESPACE, KIMCHI_META_URL, - flags=get_vm_config_flag(dom, mode)) + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_metadata_node(dom, node_xml)
-def get_metadata_node(dom, tag, mode="current"): - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) +def _kimchi_get_metadata_node(dom, tag): + # some other tools will not let libvirt create a persistent + # configuration, just return empty + if not dom.isPersistent(): + return "" + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
+ #remove the "kimchi" prifix of xml if kimchi is not None: + for child in kimchi.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(kimchi, cleanup_namespaces=True) + kimchi_xml = etree.tostring(kimchi) + kimchi = etree.fromstring(kimchi_xml) node = kimchi.find(tag) if node is not None: return etree.tostring(node) return ""
What about? if kimchi is None: return "" kimchi_xml = etree.tostring(kimchi) Kimchi = kimchi_xml.replace("{%s}" % KIMCHI_META_URL, "") node = kimchi.find(tag) if node is not None: return etree.tostring(node) return ""
+ + +def get_metadata_node(dom, tag, mode="current"): + if CapabilitiesModel().metadata_support: + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" + else: + # FIXME remove this code when all distro libvirt supports metadata element + return _kimchi_get_metadata_node(dom, tag)

On 04/29/2014 06:04 AM, Aline Manera wrote:
On 04/28/2014 12:44 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
virDomain.metadata and virDomain.setMetadata does not work in all livirt versions used in the supported distros.
So if libvirt do not support these two API.
let kimchi manually manage the metadata element
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/utils.py | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-)
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 3136402..2614008 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,9 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +from kimchi.model.config import CapabilitiesModel import libvirt -from lxml import etree -from lxml.builder import E +from lxml import etree, objectify +from lxml.builder import E, ElementMaker
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" @@ -37,6 +38,15 @@ def get_vm_name(vm_name, t_name, name_list): raise OperationFailed("KCHUTILS0003E")
+def create_metadata_node(tag): + if CapabilitiesModel().metadata_support: + return E(tag) + else: + EM = ElementMaker(namespace=KIMCHI_META_URL, + nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) + return EM(tag) + + def get_vm_config_flag(dom, mode="persistent"): # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 @@ -50,6 +60,30 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode]
+def _kimchi_set_metadata_node(dom, node_xml): + node = etree.fromstring(node_xml) + # some other tools will not let libvirt create a persistent + # configuration, raise exception. + if not dom.isPersistent(): + msg = 'The VM has not a persistent configuration' + raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + metadata = root.find("metadata") + if metadata is None: + metadata = E.metadata() + root.append(metadata) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + if kimchi is None: + kimchi = create_metadata_node("kimchi") + metadata.append(kimchi) + old_node = kimchi.find(node.tag) + (kimchi.replace(old_node, node) if old_node is not None + else kimchi.append(node)) + dom.connect().defineXML(etree.tostring(root)) + +
There is a some duplicated code here and in set_metadata_node() We should have special function only for special matters. See below
def libvirt_get_kimchi_metadata_node(dom, mode="current"): try: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, @@ -61,29 +95,60 @@ def libvirt_get_kimchi_metadata_node(dom, mode="current"):
def set_metadata_node(dom, node_xml, mode="all"):
Why node_xml? Use node directly as we use ET to create the element in model/vms.py That way we avoid ET node -> string -> ET node
- element = etree.fromstring(node_xml) - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) - old_node = None - if kimchi is not None: - old_node = kimchi.find(element.tag) - else: - kimchi = E.kimchi() - (kimchi.replace(old_node, element) if old_node is not None - else kimchi.append(element)) + if CapabilitiesModel().metadata_support: + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element))
There is already an if/else statement above, use the same one to append or replace the new node
if kimchi is None: node = kimchi.find(element.tag) kimchi.replace(node, element) For node maybe None. In [145]: kimchi.replace(node, element)
TypeError Traceback (most recent call last) <ipython-input-145-bb05d26e1b86> in <module>() ----> 1 metadata.replace(node, element) TypeError: Argument 'old_element' has incorrect type (expected lxml.etree._Element, got NoneType)
else: kimchi = E.kimchi() kimchi.append(element)
Joining common code in one place:
def set_metadata_node(dom, node, mode="all") kimchi = get_kimchi_metadata_node(dom, mode) if kimchi is None: kimchi = create_metadata_node("kimchi") else: old_node = kimchi.find(node.tag) kimchi.replace(old_node, node)
if CapabilitiesModel().metadata_support: return dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, KIMCHI_NAMESPACE, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode))
# Without metadata support # some other tools will not let libvirt create a persistent # configuration, raise exception. if not dom.isPersistent(): msg = 'The VM has not a persistent configuration' raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg})
xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") if metadtaa is None: metadata = E.metadata() root.append(metadata)
old_node = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) metadata.replace(old_node, kimchi) if old_node else metadata.append(kimchi) dom.connect().defineXML(etree.tostring(root))
def get_kimchi_metadata_node(dom, mode): if CapabilitiesModel().metadata_support: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode)) return etree.fromstring(xml)
# Without metadata support xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") return root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
- node_xml = etree.tostring(kimchi) - # From libvirt doc, Passing None for @metadata says to remove that - # element from the domain XML (passing the empty string leaves the - # element present). Do not support remove the old metadata. - dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, - KIMCHI_NAMESPACE, KIMCHI_META_URL, - flags=get_vm_config_flag(dom, mode)) + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_metadata_node(dom, node_xml)
-def get_metadata_node(dom, tag, mode="current"): - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) +def _kimchi_get_metadata_node(dom, tag): + # some other tools will not let libvirt create a persistent + # configuration, just return empty + if not dom.isPersistent(): + return "" + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
+ #remove the "kimchi" prifix of xml if kimchi is not None: + for child in kimchi.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(kimchi, cleanup_namespaces=True) + kimchi_xml = etree.tostring(kimchi) + kimchi = etree.fromstring(kimchi_xml) node = kimchi.find(tag) if node is not None: return etree.tostring(node) return ""
What about?
if kimchi is None: return ""
kimchi_xml = etree.tostring(kimchi) Kimchi = kimchi_xml.replace("{%s}" % KIMCHI_META_URL, "")
it should be kimchi_xml.replace(xmlns:* ="https://github.com/kimchi-project/kimchi, "") also it should remove the ”kimchi:“ <kimchi:access "> <kimchi:user>user1</kimchi:user> <kimchi:user>user2</kimchi:user> <kimchi:group>group1</kimchi:group> <kimchi:group>group2</kimchi:group> </kimchi:access> or it will report: In [149]: etree.fromstring(kimchi_xml) File "<string>", line unknown XMLSyntaxError: Namespace prefix kimchi on access is not defined, line 2, column 19 I can use RE to handle the xml directly if you do not like handle the ET node directly.
node = kimchi.find(tag) if node is not None: return etree.tostring(node)
return ""
+ + +def get_metadata_node(dom, tag, mode="current"): + if CapabilitiesModel().metadata_support: + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" + else: + # FIXME remove this code when all distro libvirt supports metadata element + return _kimchi_get_metadata_node(dom, tag)
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 04/28/2014 09:13 PM, Sheldon wrote:
On 04/29/2014 06:04 AM, Aline Manera wrote:
On 04/28/2014 12:44 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
virDomain.metadata and virDomain.setMetadata does not work in all livirt versions used in the supported distros.
So if libvirt do not support these two API.
let kimchi manually manage the metadata element
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/utils.py | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-)
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 3136402..2614008 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,9 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +from kimchi.model.config import CapabilitiesModel import libvirt -from lxml import etree -from lxml.builder import E +from lxml import etree, objectify +from lxml.builder import E, ElementMaker
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" @@ -37,6 +38,15 @@ def get_vm_name(vm_name, t_name, name_list): raise OperationFailed("KCHUTILS0003E")
+def create_metadata_node(tag): + if CapabilitiesModel().metadata_support: + return E(tag) + else: + EM = ElementMaker(namespace=KIMCHI_META_URL, + nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) + return EM(tag) + + def get_vm_config_flag(dom, mode="persistent"): # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 @@ -50,6 +60,30 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode]
+def _kimchi_set_metadata_node(dom, node_xml): + node = etree.fromstring(node_xml) + # some other tools will not let libvirt create a persistent + # configuration, raise exception. + if not dom.isPersistent(): + msg = 'The VM has not a persistent configuration' + raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + metadata = root.find("metadata") + if metadata is None: + metadata = E.metadata() + root.append(metadata) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + if kimchi is None: + kimchi = create_metadata_node("kimchi") + metadata.append(kimchi) + old_node = kimchi.find(node.tag) + (kimchi.replace(old_node, node) if old_node is not None + else kimchi.append(node)) + dom.connect().defineXML(etree.tostring(root)) + +
There is a some duplicated code here and in set_metadata_node() We should have special function only for special matters. See below
def libvirt_get_kimchi_metadata_node(dom, mode="current"): try: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, @@ -61,29 +95,60 @@ def libvirt_get_kimchi_metadata_node(dom, mode="current"):
def set_metadata_node(dom, node_xml, mode="all"):
Why node_xml? Use node directly as we use ET to create the element in model/vms.py That way we avoid ET node -> string -> ET node
- element = etree.fromstring(node_xml) - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) - old_node = None - if kimchi is not None: - old_node = kimchi.find(element.tag) - else: - kimchi = E.kimchi() - (kimchi.replace(old_node, element) if old_node is not None - else kimchi.append(element)) + if CapabilitiesModel().metadata_support: + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element))
There is already an if/else statement above, use the same one to append or replace the new node
if kimchi is None: node = kimchi.find(element.tag) kimchi.replace(node, element) For node maybe None. In [145]: kimchi.replace(node, element)
TypeError Traceback (most recent call last) <ipython-input-145-bb05d26e1b86> in <module>() ----> 1 metadata.replace(node, element)
TypeError: Argument 'old_element' has incorrect type (expected lxml.etree._Element, got NoneType)
OK. I thought all "node" would be valid
else: kimchi = E.kimchi() kimchi.append(element)
Joining common code in one place:
def set_metadata_node(dom, node, mode="all") kimchi = get_kimchi_metadata_node(dom, mode) if kimchi is None: kimchi = create_metadata_node("kimchi") else: old_node = kimchi.find(node.tag) kimchi.replace(old_node, node)
if CapabilitiesModel().metadata_support: return dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, KIMCHI_NAMESPACE, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode))
# Without metadata support # some other tools will not let libvirt create a persistent # configuration, raise exception. if not dom.isPersistent(): msg = 'The VM has not a persistent configuration' raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg})
xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") if metadtaa is None: metadata = E.metadata() root.append(metadata)
old_node = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) metadata.replace(old_node, kimchi) if old_node else metadata.append(kimchi) dom.connect().defineXML(etree.tostring(root))
def get_kimchi_metadata_node(dom, mode): if CapabilitiesModel().metadata_support: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode)) return etree.fromstring(xml)
# Without metadata support xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") return root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
- node_xml = etree.tostring(kimchi) - # From libvirt doc, Passing None for @metadata says to remove that - # element from the domain XML (passing the empty string leaves the - # element present). Do not support remove the old metadata. - dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, - KIMCHI_NAMESPACE, KIMCHI_META_URL, - flags=get_vm_config_flag(dom, mode)) + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_metadata_node(dom, node_xml)
-def get_metadata_node(dom, tag, mode="current"): - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) +def _kimchi_get_metadata_node(dom, tag): + # some other tools will not let libvirt create a persistent + # configuration, just return empty + if not dom.isPersistent(): + return "" + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
+ #remove the "kimchi" prifix of xml if kimchi is not None: + for child in kimchi.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(kimchi, cleanup_namespaces=True) + kimchi_xml = etree.tostring(kimchi) + kimchi = etree.fromstring(kimchi_xml) node = kimchi.find(tag) if node is not None: return etree.tostring(node) return ""
What about?
if kimchi is None: return ""
kimchi_xml = etree.tostring(kimchi) Kimchi = kimchi_xml.replace("{%s}" % KIMCHI_META_URL, "")
it should be kimchi_xml.replace(xmlns:* ="https://github.com/kimchi-project/kimchi, "") also it should remove the ”kimchi:“ <kimchi:access "> <kimchi:user>user1</kimchi:user> <kimchi:user>user2</kimchi:user> <kimchi:group>group1</kimchi:group> <kimchi:group>group2</kimchi:group> </kimchi:access>
or it will report:
In [149]: etree.fromstring(kimchi_xml) File "<string>", line unknown XMLSyntaxError: Namespace prefix kimchi on access is not defined, line 2, column 19
I can use RE to handle the xml directly if you do not like handle the ET node directly.
Ok. Keep it as you have done.
node = kimchi.find(tag) if node is not None: return etree.tostring(node)
return ""
+ + +def get_metadata_node(dom, tag, mode="current"): + if CapabilitiesModel().metadata_support: + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" + else: + # FIXME remove this code when all distro libvirt supports metadata element + return _kimchi_get_metadata_node(dom, tag)

On 04/28/2014 12:44 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
virDomain.metadata and virDomain.setMetadata does not work in all livirt versions used in the supported distros.
So if libvirt do not support these two API.
let kimchi manually manage the metadata element
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/utils.py | 105 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 20 deletions(-)
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index 3136402..2614008 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,9 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +from kimchi.model.config import CapabilitiesModel import libvirt -from lxml import etree -from lxml.builder import E +from lxml import etree, objectify +from lxml.builder import E, ElementMaker
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi" @@ -37,6 +38,15 @@ def get_vm_name(vm_name, t_name, name_list): raise OperationFailed("KCHUTILS0003E")
+def create_metadata_node(tag): + if CapabilitiesModel().metadata_support: + return E(tag) + else: + EM = ElementMaker(namespace=KIMCHI_META_URL, + nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) + return EM(tag) + + def get_vm_config_flag(dom, mode="persistent"): # libvirt.VIR_DOMAIN_AFFECT_CURRENT is 0 # VIR_DOMAIN_AFFECT_LIVE is 1, VIR_DOMAIN_AFFECT_CONFIG is 2 @@ -50,6 +60,30 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode]
+def _kimchi_set_metadata_node(dom, node_xml): + node = etree.fromstring(node_xml) + # some other tools will not let libvirt create a persistent + # configuration, raise exception. + if not dom.isPersistent(): + msg = 'The VM has not a persistent configuration' + raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg}) + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + metadata = root.find("metadata") + if metadata is None: + metadata = E.metadata() + root.append(metadata) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) + if kimchi is None: + kimchi = create_metadata_node("kimchi") + metadata.append(kimchi) + old_node = kimchi.find(node.tag) + (kimchi.replace(old_node, node) if old_node is not None + else kimchi.append(node)) + dom.connect().defineXML(etree.tostring(root)) + +
There is a some duplicated code here and in set_metadata_node() We should have special function only for special matters. See below I do not think this is better. only several line code. for my original thought, I make the logic of virDomain api and our kimchi code clearly. virDomain api can make xml parser more easily. And I give the comments tag with FIXME. we can easily remove the kimchi code when all distro libvirt supports
On 04/29/2014 06:04 AM, Aline Manera wrote: metadata elemen. also I have try your below code, the codes does not reduce. And we mix the logic of virDomain api and our kimchi code in two functions. But since we do not know how long will all distro libvirt support metadata elemen, I can change as your comments.
def libvirt_get_kimchi_metadata_node(dom, mode="current"): try: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, @@ -61,29 +95,60 @@ def libvirt_get_kimchi_metadata_node(dom, mode="current"):
def set_metadata_node(dom, node_xml, mode="all"):
Why node_xml? Use node directly as we use ET to create the element in model/vms.py That way we avoid ET node -> string -> ET node
- element = etree.fromstring(node_xml) - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) - old_node = None - if kimchi is not None: - old_node = kimchi.find(element.tag) - else: - kimchi = E.kimchi() - (kimchi.replace(old_node, element) if old_node is not None - else kimchi.append(element)) + if CapabilitiesModel().metadata_support: + element = etree.fromstring(node_xml) + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + old_node = None + if kimchi is not None: + old_node = kimchi.find(element.tag) + else: + kimchi = E.kimchi() + (kimchi.replace(old_node, element) if old_node is not None + else kimchi.append(element))
There is already an if/else statement above, use the same one to append or replace the new node
if kimchi is None: node = kimchi.find(element.tag) kimchi.replace(node, element) else: kimchi = E.kimchi() kimchi.append(element)
Joining common code in one place:
def set_metadata_node(dom, node, mode="all") kimchi = get_kimchi_metadata_node(dom, mode) if kimchi is None: kimchi = create_metadata_node("kimchi") else: old_node = kimchi.find(node.tag) kimchi.replace(old_node, node)
if CapabilitiesModel().metadata_support: return dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, KIMCHI_NAMESPACE, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode))
# Without metadata support # some other tools will not let libvirt create a persistent # configuration, raise exception. if not dom.isPersistent(): msg = 'The VM has not a persistent configuration' raise OperationFailed("KCHVM0030E", + {'name': dom.name(), "err": msg})
xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") if metadtaa is None: metadata = E.metadata() root.append(metadata)
old_node = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL) metadata.replace(old_node, kimchi) if old_node else metadata.append(kimchi) dom.connect().defineXML(etree.tostring(root))
def get_kimchi_metadata_node(dom, mode): if CapabilitiesModel().metadata_support: xml = dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, KIMCHI_META_URL, flags=get_vm_config_flag(dom, mode)) return etree.fromstring(xml)
# Without metadata support xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) root = etree.fromstring(xml) metadata = root.find("metadata") return root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
- node_xml = etree.tostring(kimchi) - # From libvirt doc, Passing None for @metadata says to remove that - # element from the domain XML (passing the empty string leaves the - # element present). Do not support remove the old metadata. - dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, - KIMCHI_NAMESPACE, KIMCHI_META_URL, - flags=get_vm_config_flag(dom, mode)) + node_xml = etree.tostring(kimchi) + # From libvirt doc, Passing None for @metadata says to remove that + # element from the domain XML (passing the empty string leaves the + # element present). Do not support remove the old metadata. + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, node_xml, + KIMCHI_NAMESPACE, KIMCHI_META_URL, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_metadata_node(dom, node_xml)
-def get_metadata_node(dom, tag, mode="current"): - kimchi = libvirt_get_kimchi_metadata_node(dom, mode) +def _kimchi_get_metadata_node(dom, tag): + # some other tools will not let libvirt create a persistent + # configuration, just return empty + if not dom.isPersistent(): + return "" + xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE) + root = etree.fromstring(xml) + kimchi = root.find("metadata/{%s}kimchi" % KIMCHI_META_URL)
+ #remove the "kimchi" prifix of xml if kimchi is not None: + for child in kimchi.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(kimchi, cleanup_namespaces=True) + kimchi_xml = etree.tostring(kimchi) + kimchi = etree.fromstring(kimchi_xml) node = kimchi.find(tag) if node is not None: return etree.tostring(node) return ""
What about?
if kimchi is None: return ""
kimchi_xml = etree.tostring(kimchi) Kimchi = kimchi_xml.replace("{%s}" % KIMCHI_META_URL, "") node = kimchi.find(tag) if node is not None: return etree.tostring(node)
return ""
+ + +def get_metadata_node(dom, tag, mode="current"): + if CapabilitiesModel().metadata_support: + kimchi = libvirt_get_kimchi_metadata_node(dom, mode) + if kimchi is not None: + node = kimchi.find(tag) + if node is not None: + return etree.tostring(node) + return "" + else: + # FIXME remove this code when all distro libvirt supports metadata element + return _kimchi_get_metadata_node(dom, tag)
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> define a domain just edit the persistent xml. So we should get user and group from persistent xml instead of live xml when domain is living. Refacor the code by libvirt metadata API and fix the bug. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 88 ++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 8a79f0f..be720f3 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -17,6 +17,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +from lxml.builder import E import lxml.etree as ET import os import time @@ -25,7 +26,6 @@ from xml.etree import ElementTree import libvirt from cherrypy.process.plugins import BackgroundTask -from lxml.builder import E from kimchi import vnc from kimchi import xmlutils @@ -35,6 +35,8 @@ from kimchi.exception import NotFoundError, OperationFailed from kimchi.model.config import CapabilitiesModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name +from kimchi.model.utils import get_metadata_node +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 @@ -247,7 +249,7 @@ class VMModel(object): self._live_vm_update(dom, params) return dom.name().decode('utf-8') - def _get_metadata_node(self, users, groups): + def _build_access_elem(self, users, groups): access = E.access() for user in users: access.append(E.user(user)) @@ -255,40 +257,50 @@ class VMModel(object): for group in groups: access.append(E.group(group)) - return E.metadata(E.kimchi(access)) + return access + + def _vm_update_access_metadata(self, dom, params): + users = groups = None + if "users" in params: + users = params["users"] + invalid_users = set(users) - set(self.users.get_list()) + if len(invalid_users) != 0: + raise InvalidParameter("KCHVM0027E", + {'users': ", ".join(invalid_users)}) + if "groups" in params: + groups = params["groups"] + invalid_groups = set(groups) - set(self.groups.get_list()) + if len(invalid_groups) != 0: + raise InvalidParameter("KCHVM0028E", + {'groups': ", ".join(invalid_groups)}) + + if users is None and groups is None: + return + + access_xml = (get_metadata_node(dom, "access") or + """<access></access>""") + old_users = xpath_get_text(access_xml, "/access/user") + old_groups = xpath_get_text(access_xml, "/access/group") + users = old_users if users is None else users + groups = old_groups if groups is None else groups + + node = self._build_access_elem(users, groups) + set_metadata_node(dom, ET.tostring(node)) def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) - metadata_xpath = "/domain/metadata/kimchi/access/%s" - users = xpath_get_text(old_xml, metadata_xpath % "user") - groups = xpath_get_text(old_xml, metadata_xpath % "group") - for key, val in params.items(): - if key == 'users': - invalid_users = set(val) - set(self.users.get_list()) - if len(invalid_users) != 0: - raise InvalidParameter("KCHVM0027E", - {'users': ", ".join(invalid_users)}) - users = val - elif key == 'groups': - invalid_groups = set(val) - set(self.groups.get_list()) - if len(invalid_groups) != 0: - raise InvalidParameter("KCHVM0028E", - {'groups': - ", ".join(invalid_groups)}) - groups = val - else: - if key in VM_STATIC_UPDATE_PARAMS: - if key == 'memory': - # Libvirt saves memory in KiB. Retrieved xml has memory - # in KiB too, so new valeu must be in KiB here - val = val * 1024 - if type(val) == int: - val = str(val) - xpath = VM_STATIC_UPDATE_PARAMS[key] - new_xml = xmlutils.xml_item_update(new_xml, xpath, val) + if key in VM_STATIC_UPDATE_PARAMS: + if key == 'memory': + # Libvirt saves memory in KiB. Retrieved xml has memory + # in KiB too, so new valeu must be in KiB here + val = val * 1024 + if type(val) == int: + val = str(val) + xpath = VM_STATIC_UPDATE_PARAMS[key] + new_xml = xmlutils.xml_item_update(new_xml, xpath, val) conn = self.conn.get() try: @@ -302,13 +314,6 @@ class VMModel(object): root = ET.fromstring(new_xml) root.remove(root.find('.currentMemory')) - # Update metadata element - current_metadata = root.find('metadata') - new_metadata = self._get_metadata_node(users, groups) - if current_metadata is not None: - root.replace(current_metadata, new_metadata) - else: - root.append(new_metadata) dom = conn.defineXML(ET.tostring(root, encoding="utf-8")) except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) @@ -317,7 +322,7 @@ class VMModel(object): return dom def _live_vm_update(self, dom, params): - pass + self._vm_update_access_metadata(dom, params) def _has_video(self, dom): dom = ElementTree.fromstring(dom.XMLDesc(0)) @@ -356,9 +361,10 @@ class VMModel(object): res['io_throughput'] = vm_stats.get('disk_io', 0) res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) - xml = dom.XMLDesc(0) - users = xpath_get_text(xml, "/domain/metadata/kimchi/access/user") - groups = xpath_get_text(xml, "/domain/metadata/kimchi/access/group") + access_xml = (get_metadata_node(dom, "access") or + """<access></access>""") + users = xpath_get_text(access_xml, "/access/user") + groups = xpath_get_text(access_xml, "/access/group") return {'name': name, 'state': state, -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> When vm is live, we should also get user and group correctly. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- tests/test_model.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index 05e5741..c9af745 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -603,6 +603,19 @@ class ModelTests(unittest.TestCase): self.assertRaises(InvalidParameter, inst.vm_update, 'kimchi-vm1', params) + # change VM users and groups, when wm is running. + inst.vm_update(u'kimchi-vm1', + {'users': ['root'], 'groups': ['root']}) + vm_info = inst.vm_lookup(u'kimchi-vm1') + self.assertEquals(['root'], vm_info['users']) + self.assertEquals(['root'], vm_info['groups']) + # change VM users and groups by removing all elements, + # when wm is running. + inst.vm_update(u'kimchi-vm1', {'users': [], 'groups': []}) + vm_info = inst.vm_lookup(u'kimchi-vm1') + self.assertEquals([], vm_info['users']) + self.assertEquals([], vm_info['groups']) + inst.vm_poweroff('kimchi-vm1') self.assertRaises(OperationFailed, inst.vm_update, 'kimchi-vm1', {'name': 'kimchi-vm2'}) -- 1.9.0

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Then kimchi can choose the divice mode depend on these info. Note, the info is the initial OS when install OS on a blank guest. OS may change after the install. So other device should still keep their model info in the vm metadata. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index be720f3..26ef0e8 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -223,6 +223,8 @@ class VMsModel(object): raise OperationFailed("KCHVM0007E", {'name': name, 'err': e.get_error_message()}) + VMModel.vm_update_os_metadata(VMModel.get_vm(name, self.conn), t.info) + return name def get_list(self): @@ -287,6 +289,21 @@ class VMModel(object): node = self._build_access_elem(users, groups) set_metadata_node(dom, ET.tostring(node)) + @staticmethod + def vm_get_os_metadata(dom): + os_xml = get_metadata_node(dom, "os") or """<os></os>""" + os_elem = ET.fromstring(os_xml) + return (os_elem.attrib.get("version"), os_elem.attrib.get("distro")) + + @staticmethod + def vm_update_os_metadata(dom, params): + distro = params.get("os_distro") + version = params.get("os_version") + if distro is None: + return + os_elem = E.os({"distro": distro, "version": version}) + set_metadata_node(dom, ET.tostring(os_elem)) + def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) -- 1.9.0

On 04/28/2014 12:44 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
Then kimchi can choose the divice mode depend on these info.
Note, the info is the initial OS when install OS on a blank guest.
OS may change after the install.
So other device should still keep their model info in the vm metadata.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/model/vms.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index be720f3..26ef0e8 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -223,6 +223,8 @@ class VMsModel(object): raise OperationFailed("KCHVM0007E", {'name': name, 'err': e.get_error_message()})
+ VMModel.vm_update_os_metadata(VMModel.get_vm(name, self.conn), t.info) + return name
def get_list(self): @@ -287,6 +289,21 @@ class VMModel(object): node = self._build_access_elem(users, groups) set_metadata_node(dom, ET.tostring(node))
+ @staticmethod + def vm_get_os_metadata(dom): + os_xml = get_metadata_node(dom, "os") or """<os></os>""" + os_elem = ET.fromstring(os_xml) + return (os_elem.attrib.get("version"), os_elem.attrib.get("distro")) + + @staticmethod + def vm_update_os_metadata(dom, params): + distro = params.get("os_distro") + version = params.get("os_version") + if distro is None: + return + os_elem = E.os({"distro": distro, "version": version})
+ set_metadata_node(dom, ET.tostring(os_elem))
As we will use the node in set_metadat_node(), pass the node to the function instead of the XML So we avoid node -> xml -> node The same while calling the function to set users and groups
+ def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0)
participants (3)
-
Aline Manera
-
shaohef@linux.vnet.ibm.com
-
Sheldon