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

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> 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): Add two function to set and get domain xml metadata 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 bug fix: call a method should be followed by "()" add method to test libvirt api are available manually manage the metadata element src/kimchi/config.py.in | 14 ++++++- src/kimchi/featuretests.py | 44 ++++++++++++++++++- src/kimchi/i18n.py | 1 + src/kimchi/model/utils.py | 97 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 102 ++++++++++++++++++++++++++++----------------- tests/test_model.py | 13 ++++++ 6 files changed, 229 insertions(+), 42 deletions(-) -- 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 | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 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..be5e526 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from kimchi.exception import OperationFailed +import libvirt +from lxml import etree + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +33,39 @@ 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 set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag, + flags=get_vm_config_flag(dom, mode)) + + +def get_vm_metadata_element(dom, element, mode="current"): + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message}) -- 1.9.0

On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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 | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 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..be5e526 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +import libvirt +from lxml import etree + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/"
def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +33,39 @@ 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 set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag, + flags=get_vm_config_flag(dom, mode)) + + +def get_vm_metadata_element(dom, element, mode="current"): + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message})
I am not sure we should raise here. The metadata element should not impact the user to get vm information. Maybe just warn user about the error and return empty string there?

On 04/25/2014 03:06 AM, Aline Manera wrote:
On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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 | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 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..be5e526 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +import libvirt +from lxml import etree + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/"
def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +33,39 @@ 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 set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag, + flags=get_vm_config_flag(dom, mode)) + + +def get_vm_metadata_element(dom, element, mode="current"): + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message})
I am not sure we should raise here. The metadata element should not impact the user to get vm information. Maybe just warn user about the error and return empty string there?
ACK.
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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 | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 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..be5e526 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +import libvirt +from lxml import etree + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/"
def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +33,39 @@ 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 set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag,
Why are you append element.tag to namespace? Use only https://github.com/kimchi-project/kimchi/ as namespace
+ flags=get_vm_config_flag(dom, mode)) + + +def get_vm_metadata_element(dom, element, mode="current"): + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message})

On 04/25/2014 04:19 AM, Aline Manera wrote:
On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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 | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 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..be5e526 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,6 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +import libvirt +from lxml import etree + + +KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/"
def get_vm_name(vm_name, t_name, name_list): @@ -28,3 +33,39 @@ 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 set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag,
Why are you append element.tag to namespace? Use only https://github.com/kimchi-project/kimchi/ as namespace ACK
+ flags=get_vm_config_flag(dom, mode)) + + +def get_vm_metadata_element(dom, element, mode="current"): + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message})
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 04/25/2014 08:11 AM, Sheldon wrote:
+def set_vm_metadata_element(dom, meta_xml, mode="all"): + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag,
Why are you append element.tag to namespace? Use only https://github.com/kimchi-project/kimchi/ as namespace If we don not like it. I change it next version.
The reason as follow as libvirt docs(http://libvirt.org/formatdomain.html#elementsMetadata) says: <domain type='xen' id='3'> <name>fv0</name> <uuid>4dea22b31d52d8f32516782e98ab3fa0</uuid> <title>A short description - title - of the domain</title> <description>Some human readable description</description> <metadata> <app1:foo xmlns:app1="http://app1.org/app1/">..</app1:foo> <app2:bar xmlns:app2="http://app1.org/app2/">..</app2:bar> </metadata> ... |metadata| yes, one application use one namespace. append element.tag to namespace, make every "kimchi" element has an unique uri. this looks like all the "kimchi" prefix element are belong to one "kimchi" application. looks like "foo" are "bar" are all "kimchi", but they has different xmlns. <metadata> <kimchi:foo xmlns:kimchi="http://kimchi.org/kimchi/foo">..</kimchi:foo> <kimchi:bar xmlns:kimchi="http://kimchi.org/kimchi/bar">..</kimchi:bar> </metadata> The libvirt API can easy to set/get the every metadata element. get foo by: virDomain.metadata(@type, "https://kimchi.org/kimchi/foo/") get bar by: virDomain.metadata(@type, "https://kimchi.org/kimchi/bar/") set foo by: virDomain.setMetadata(@type, "<foo></foo>", "https://kimchi.org/kimchi/foo/") set bar by: virDomain.setMetadata(@type, "<bar></bar>", "https://kimchi.org/kimchi/bar/") looks like foo and bar belong to "kimchi" element, but they are independent. Their is no relationship between them. If we just want to handle foo, we do not need to care "bar". like we can call kimchi api, every api is unique uri. every api are independent. virDomain.metadata Type: instancemethod String Form:<unbound method virDomain.metadata> File: /usr/lib64/python2.7/site-packages/libvirt.py Definition: libvirt.virDomain.metadata(self, type, uri, flags=0) Docstring: Retrieves the appropriate domain element given by @type. If VIR_DOMAIN_METADATA_ELEMENT is requested parameter @uri must be set to the name of the namespace the requested elements belong to, otherwise must be None. If an element of the domain XML is not present, the resulting error will be VIR_ERR_NO_DOMAIN_METADATA. This method forms a shortcut for seeing information from virDomainSetMetadata() without having to go through virDomainGetXMLDesc(). @flags controls whether the live domain or persistent configuration will be queried. The|metadata|node can be used by applications to store custom metadata in the form of XML nodes/trees. Applications must use custom namespaces on their XML nodes/trees, with only one top-level element per namespace (if the application needs structure, they should have sub-elements to their namespace element).Since 0.9.10 -- 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 | 84 ++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 8a79f0f..7077900 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -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_vm_metadata_element +from kimchi.model.utils import set_vm_metadata_element 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 @@ -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_updata_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_vm_metadata_element(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 + + xml = self._get_metadata_node(users, groups) + set_vm_metadata_element(dom, ET.tostring(xml)) 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_updata_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_vm_metadata_element(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

On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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 | 84 ++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 39 deletions(-)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 8a79f0f..7077900 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -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_vm_metadata_element +from kimchi.model.utils import set_vm_metadata_element 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 @@ -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_updata_access_metadata(self, dom, params):
typo: updata
+ 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_vm_metadata_element(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 + + xml = self._get_metadata_node(users, groups) + set_vm_metadata_element(dom, ET.tostring(xml))
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_updata_access_metadata(dom, params)
typo: updata
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_vm_metadata_element(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,

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 b17ce00..ab22012 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 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 7077900..42a9c97 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_updata_os_metadata(VMModel.get_vm(name, self.conn), t.info) + return name def get_list(self): @@ -287,6 +289,22 @@ class VMModel(object): xml = self._get_metadata_node(users, groups) set_vm_metadata_element(dom, ET.tostring(xml)) + @staticmethod + def vm_get_os_metadata(dom): + os_xml = (get_vm_metadata_element(dom, "OS") or + """<OS></OS>""") + OS = ET.fromstring(os_xml) + return (OS.attrib.get("version"), OS.attrib.get("distro")) + + @staticmethod + def vm_updata_os_metadata(dom, params): + distro = params.get("os_distro") + version = params.get("os_version") + if distro is None: + return + OS = E.OS({"distro": distro, "version": version}) + set_vm_metadata_element(dom, ET.tostring(OS)) + 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/24/2014 02:19 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 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 7077900..42a9c97 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_updata_os_metadata(VMModel.get_vm(name, self.conn), t.info) +
typo: updata
return name
def get_list(self): @@ -287,6 +289,22 @@ class VMModel(object): xml = self._get_metadata_node(users, groups) set_vm_metadata_element(dom, ET.tostring(xml))
+ @staticmethod + def vm_get_os_metadata(dom): + os_xml = (get_vm_metadata_element(dom, "OS") or + """<OS></OS>""") + OS = ET.fromstring(os_xml) + return (OS.attrib.get("version"), OS.attrib.get("distro")) + + @staticmethod + def vm_updata_os_metadata(dom, params):
typo: updata
+ distro = params.get("os_distro") + version = params.get("os_version") + if distro is None: + return + OS = E.OS({"distro": distro, "version": version}) + set_vm_metadata_element(dom, ET.tostring(OS)) + def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0)

On 04/24/2014 02:19 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 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 7077900..42a9c97 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_updata_os_metadata(VMModel.get_vm(name, self.conn), t.info) + return name
def get_list(self): @@ -287,6 +289,22 @@ class VMModel(object): xml = self._get_metadata_node(users, groups) set_vm_metadata_element(dom, ET.tostring(xml))
+ @staticmethod + def vm_get_os_metadata(dom): + os_xml = (get_vm_metadata_element(dom, "OS") or + """<OS></OS>""") + OS = ET.fromstring(os_xml) + return (OS.attrib.get("version"), OS.attrib.get("distro")) + + @staticmethod + def vm_updata_os_metadata(dom, params): + distro = params.get("os_distro") + version = params.get("os_version") + if distro is None: + return + OS = E.OS({"distro": distro, "version": version})
+ set_vm_metadata_element(dom, ET.tostring(OS))
This will add the <os> element and remove all other metadata elements. While using setMetadata() you need to always provide the whole metadata xml
+ def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(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/config.py.in | 14 +++++++++++++- src/kimchi/featuretests.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in index f8a645a..bca15ef 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -26,7 +26,7 @@ import threading from ConfigParser import SafeConfigParser - +from kimchi.featuretests import FeatureTests from kimchi.xmlutils import xpath_get_text __version__ = "@kimchiversion@" @@ -125,6 +125,18 @@ class Paths(object): paths = Paths() +class LivirtSupportAPI(object): + def __init__(self): + self.apis = { + "meta_element": FeatureTests.libvirt_config_support_metadata()} + + def get_apis(self): + return self.apis + + +libvirt_support_apis = LivirtSupportAPI().get_apis() + + class PluginPaths(Paths): def __init__(self, name): diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py index b1001ea..956f328 100644 --- a/src/kimchi/featuretests.py +++ b/src/kimchi/featuretests.py @@ -23,14 +23,15 @@ import lxml.etree as ET import socket import subprocess import threading +import uuid from lxml.builder import E +from kimchi.rollbackcontext import RollbackContext from kimchi.utils import kimchi_log - ISO_STREAM_XML = """ <domain type='kvm'> <name>ISO_STREAMING</name> @@ -53,6 +54,17 @@ ISO_STREAM_XML = """ </devices> </domain>""" +SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>%s</name> + <uuid>%s</uuid> + <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 +187,31 @@ class FeatureTests(object): pool is None or pool.undefine() conn is None or conn.close() return True + + @staticmethod + def libvirt_config_support_metadata(): + vm_uuid = uuid.uuid1() + vm_name = "kimchi_test_%s" % vm_uuid + KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" + xml = SIMPLE_VM_XML % (vm_name, vm_uuid) + meta_support = False + 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(xml) + rollback.prependDefer(dom.undefine) + try: + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + "<metatest/>", "kimchi", + KIMCHI_META_URL + "metatest", + flags=libvirt.VIR_DOMAIN_AFFECT_CURRENT) + meta_support = True + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_ARGUMENT_UNSUPPORTED: + meta_support = False + else: + raise + + return meta_support -- 1.9.0

On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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/config.py.in | 14 +++++++++++++- src/kimchi/featuretests.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in
The better place to insert it is in src/kimchi/model/config.py as all the feature tests are made there. So we can have single place for it.
index f8a645a..bca15ef 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -26,7 +26,7 @@ import threading
from ConfigParser import SafeConfigParser
- +from kimchi.featuretests import FeatureTests from kimchi.xmlutils import xpath_get_text
__version__ = "@kimchiversion@" @@ -125,6 +125,18 @@ class Paths(object): paths = Paths()
+class LivirtSupportAPI(object): + def __init__(self): + self.apis = { + "meta_element": FeatureTests.libvirt_config_support_metadata()} + + def get_apis(self): + return self.apis + + +libvirt_support_apis = LivirtSupportAPI().get_apis() + + class PluginPaths(Paths):
def __init__(self, name): diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py index b1001ea..956f328 100644 --- a/src/kimchi/featuretests.py +++ b/src/kimchi/featuretests.py @@ -23,14 +23,15 @@ import lxml.etree as ET import socket import subprocess import threading +import uuid
from lxml.builder import E
+from kimchi.rollbackcontext import RollbackContext from kimchi.utils import kimchi_log
- ISO_STREAM_XML = """ <domain type='kvm'> <name>ISO_STREAMING</name> @@ -53,6 +54,17 @@ ISO_STREAM_XML = """ </devices> </domain>"""
+SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>%s</name>
+ <uuid>%s</uuid>
the uuid is not required to create a vm
+ <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 +187,31 @@ class FeatureTests(object): pool is None or pool.undefine() conn is None or conn.close() return True + + @staticmethod + def libvirt_config_support_metadata(): + vm_uuid = uuid.uuid1() + vm_name = "kimchi_test_%s" % vm_uuid + KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" + xml = SIMPLE_VM_XML % (vm_name, vm_uuid) + meta_support = False + 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(xml) + rollback.prependDefer(dom.undefine) + try: + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + "<metatest/>", "kimchi", + KIMCHI_META_URL + "metatest", + flags=libvirt.VIR_DOMAIN_AFFECT_CURRENT) + meta_support = True + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_ARGUMENT_UNSUPPORTED: + meta_support = False + else: + raise
You should not raise an exception during feature test. Independent of which error occurred you should only return meta_support = False and handle that info when using it except libvirt.libvirtError as e: return False
+ + return meta_support

On 04/24/2014 04:16 PM, Aline Manera wrote:
On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
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/config.py.in | 14 +++++++++++++- src/kimchi/featuretests.py | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-)
diff --git a/src/kimchi/config.py.in b/src/kimchi/config.py.in
The better place to insert it is in src/kimchi/model/config.py as all the feature tests are made there. So we can have single place for it.
In CapabilitiesModel()
index f8a645a..bca15ef 100644 --- a/src/kimchi/config.py.in +++ b/src/kimchi/config.py.in @@ -26,7 +26,7 @@ import threading
from ConfigParser import SafeConfigParser
- +from kimchi.featuretests import FeatureTests from kimchi.xmlutils import xpath_get_text
__version__ = "@kimchiversion@" @@ -125,6 +125,18 @@ class Paths(object): paths = Paths()
+class LivirtSupportAPI(object): + def __init__(self): + self.apis = { + "meta_element": FeatureTests.libvirt_config_support_metadata()} + + def get_apis(self): + return self.apis + + +libvirt_support_apis = LivirtSupportAPI().get_apis() + + class PluginPaths(Paths):
def __init__(self, name): diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py index b1001ea..956f328 100644 --- a/src/kimchi/featuretests.py +++ b/src/kimchi/featuretests.py @@ -23,14 +23,15 @@ import lxml.etree as ET import socket import subprocess import threading +import uuid
from lxml.builder import E
+from kimchi.rollbackcontext import RollbackContext from kimchi.utils import kimchi_log
- ISO_STREAM_XML = """ <domain type='kvm'> <name>ISO_STREAMING</name> @@ -53,6 +54,17 @@ ISO_STREAM_XML = """ </devices> </domain>"""
+SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>%s</name>
+ <uuid>%s</uuid>
the uuid is not required to create a vm
+ <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 +187,31 @@ class FeatureTests(object): pool is None or pool.undefine() conn is None or conn.close() return True + + @staticmethod + def libvirt_config_support_metadata(): + vm_uuid = uuid.uuid1() + vm_name = "kimchi_test_%s" % vm_uuid + KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" + xml = SIMPLE_VM_XML % (vm_name, vm_uuid) + meta_support = False + 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(xml) + rollback.prependDefer(dom.undefine) + try: + dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + "<metatest/>", "kimchi", + KIMCHI_META_URL + "metatest", + flags=libvirt.VIR_DOMAIN_AFFECT_CURRENT) + meta_support = True + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_ARGUMENT_UNSUPPORTED: + meta_support = False + else: + raise
You should not raise an exception during feature test. Independent of which error occurred you should only return meta_support = False and handle that info when using it
except libvirt.libvirtError as e: return False
+ + return meta_support
_______________________________________________ Kimchi-devel mailing list Kimchi-devel@ovirt.org http://lists.ovirt.org/mailman/listinfo/kimchi-devel

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 | 92 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index be5e526..6ddef8c 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,8 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from kimchi.exception import OperationFailed +from kimchi.config import libvirt_support_apis import libvirt -from lxml import etree +from lxml import etree, objectify +from lxml.builder import E KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" @@ -48,24 +50,78 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode] -def set_vm_metadata_element(dom, meta_xml, mode="all"): +def _kimchi_set_vm_metadata_element(dom, meta_xml): element = etree.fromstring(meta_xml) - # 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, meta_xml, - "kimchi", KIMCHI_META_URL + element.tag, - flags=get_vm_config_flag(dom, mode)) + # E = ElementMaker(namespace=KIMCHI_META_URL + element.tag, + # nsmap={'kimchi': KIMCHI_META_URL + element.tag}) + # 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) + element.tag = "{%s%s}%s" % (KIMCHI_META_URL, element.tag, element.tag) + old_elem = metadata.find(element.tag) + (metadata.replace(old_elem, element) if old_elem is not None + else metadata.append(element)) + + dom.connect().defineXML(etree.tostring(root)) + + +def set_vm_metadata_element(dom, meta_xml, mode="all"): + if libvirt_support_apis["meta_element"] is True: + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_vm_metadata_element(dom, meta_xml) + + +def _kimchi_get_vm_metadata_element(dom, element): + # 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) + metadata = root.find("metadata") + if metadata is None: + return "" + tag = "{%s%s}%s" % (KIMCHI_META_URL, element, element) + elem = metadata.find(tag) + if elem: + for child in elem.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(elem, cleanup_namespaces=True) + return etree.tostring(elem) + return "" def get_vm_metadata_element(dom, element, mode="current"): - try: - return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, - KIMCHI_META_URL + element, - flags=get_vm_config_flag(dom, mode)) - except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: - return "" - else: - raise OperationFailed("KCHVM0030E", {'name': dom.name(), - 'err': e.message}) + if libvirt_support_apis["meta_element"] is True: + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message}) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_get_vm_metadata_element(dom, element) -- 1.9.0

On 04/24/2014 02:19 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 | 92 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 18 deletions(-)
diff --git a/src/kimchi/model/utils.py b/src/kimchi/model/utils.py index be5e526..6ddef8c 100644 --- a/src/kimchi/model/utils.py +++ b/src/kimchi/model/utils.py @@ -18,8 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
from kimchi.exception import OperationFailed +from kimchi.config import libvirt_support_apis import libvirt -from lxml import etree +from lxml import etree, objectify +from lxml.builder import E
KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/metadata/" @@ -48,24 +50,78 @@ def get_vm_config_flag(dom, mode="persistent"): return flag[mode]
-def set_vm_metadata_element(dom, meta_xml, mode="all"): +def _kimchi_set_vm_metadata_element(dom, meta_xml): element = etree.fromstring(meta_xml) - # 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, meta_xml, - "kimchi", KIMCHI_META_URL + element.tag, - flags=get_vm_config_flag(dom, mode))
+ # E = ElementMaker(namespace=KIMCHI_META_URL + element.tag, + # nsmap={'kimchi': KIMCHI_META_URL + element.tag})
ElementMaker can make this code simpler. Why didn't you use it?
+ # 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) + element.tag = "{%s%s}%s" % (KIMCHI_META_URL, element.tag, element.tag) + old_elem = metadata.find(element.tag) + (metadata.replace(old_elem, element) if old_elem is not None + else metadata.append(element)) + + dom.connect().defineXML(etree.tostring(root)) + + +def set_vm_metadata_element(dom, meta_xml, mode="all"): + if libvirt_support_apis["meta_element"] is True: + element = etree.fromstring(meta_xml) + # 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, meta_xml, + "kimchi", KIMCHI_META_URL + element.tag, + flags=get_vm_config_flag(dom, mode)) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_set_vm_metadata_element(dom, meta_xml) + + +def _kimchi_get_vm_metadata_element(dom, element): + # 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) + metadata = root.find("metadata") + if metadata is None: + return "" + tag = "{%s%s}%s" % (KIMCHI_META_URL, element, element) + elem = metadata.find(tag)
+ if elem: + for child in elem.getiterator(): + i = child.tag.find('}') + if i >= 0: + child.tag = child.tag[i+1:] + objectify.deannotate(elem, cleanup_namespaces=True)
I didn't understand that code
+ return etree.tostring(elem) + return ""
def get_vm_metadata_element(dom, element, mode="current"): - try: - return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, - KIMCHI_META_URL + element, - flags=get_vm_config_flag(dom, mode)) - except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: - return "" - else: - raise OperationFailed("KCHVM0030E", {'name': dom.name(), - 'err': e.message}) + if libvirt_support_apis["meta_element"] is True: + try: + return dom.metadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, + KIMCHI_META_URL + element, + flags=get_vm_config_flag(dom, mode)) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN_METADATA: + return "" + else: + raise OperationFailed("KCHVM0030E", {'name': dom.name(), + 'err': e.message}) + else: + # FIXME remove this code when all distro libvirt supports metadata element + _kimchi_get_vm_metadata_element(dom, element)

This patchset breaks the tests. The first of the 12 failed tests is pasted below: ====================================================================== ERROR: test_storagepool (unittest.loader.ModuleImportFailure) ---------------------------------------------------------------------- ImportError: Failed to import test module: test_storagepool Traceback (most recent call last): File "/usr/lib64/python2.7/unittest/loader.py", line 252, in _find_tests module = self._get_module_from_name(name) File "/usr/lib64/python2.7/unittest/loader.py", line 230, in _get_module_from_name __import__(name) File "/home/vianac/LTC/kimchi/tests/test_storagepool.py", line 24, in <module> from kimchi.model.libvirtstoragepool import StoragePoolDef File "/home/vianac/LTC/kimchi/src/kimchi/model/libvirtstoragepool.py", line 26, in <module> from kimchi.exception import InvalidParameter, OperationFailed, TimeoutExpired File "/home/vianac/LTC/kimchi/src/kimchi/exception.py", line 25, in <module> from kimchi.template import get_lang, validate_language File "/home/vianac/LTC/kimchi/src/kimchi/template.py", line 25, in <module> from kimchi.config import paths File "/home/vianac/LTC/kimchi/src/kimchi/config.py", line 29, in <module> from kimchi.featuretests import FeatureTests File "/home/vianac/LTC/kimchi/src/kimchi/featuretests.py", line 33, in <module> from kimchi.utils import kimchi_log File "/home/vianac/LTC/kimchi/src/kimchi/utils.py", line 35, in <module> from kimchi.asynctask import AsyncTask File "/home/vianac/LTC/kimchi/src/kimchi/asynctask.py", line 25, in <module> from kimchi.exception import OperationFailed ImportError: cannot import name OperationFailed On 24-04-2014 14:19, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
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): Add two function to set and get domain xml metadata 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 bug fix: call a method should be followed by "()" add method to test libvirt api are available manually manage the metadata element
src/kimchi/config.py.in | 14 ++++++- src/kimchi/featuretests.py | 44 ++++++++++++++++++- src/kimchi/i18n.py | 1 + src/kimchi/model/utils.py | 97 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 102 ++++++++++++++++++++++++++++----------------- tests/test_model.py | 13 ++++++ 6 files changed, 229 insertions(+), 42 deletions(-)

Let me make a diagram about what this patch should cover: 1) Identify system has support for virDomain.metadata and virDomain.setMetadata FeatureTests.has_metadata_support() returns True or False It should not raise any exception Insert it in CapabilitiesModel (src/kimchi/model/config.py) that way we have all feature tests in one place 2) Use FeatureTests.has_metadata_support() result to SET metadata element KIMCHI_META_URL = "https://github.com/kimchi-project/kimchi/" KIMCHI_NAMESPACE = "kimchi" if FeatureTests.has_metadata_support(): dom.setMetadata(libvirt.VIR_DOMAIN_METADATA_ELEMENT, <meta_xml>, KIMCHI_NAMESPACE, KIMCHI_META_URL) The <meta_xml> should be something like, built in _get_metadata_node() <kimchi> <access> <user>...</user> ... <group>...</group> ... </access> <os> .... </os> </kimchi> # FeatureTests.has_metadata_support() is False else: # use ElementMaker (instead of original lxml.builder.E) to generate metadata xml E = lxml.builder.ElementMaker(namespace=KIMCHI_META_URL, nsmap={KIMCHI_NAMESPACE: KIMCHI_META_URL}) # follow the same build process made on _get_metadata_node() That way both xmls will be generated in same way <kimchi:kimchi xmlns:kimchi="http://github.com/kimchi-project/kimchi"> <kimchi:access> <kimchi:user>...</kimchi:user> ... <kimchi:group>...</kimchi:group> ... </kimchi:access> <kimchi:os> .... </kimchi:os> </kimchi> 3) Use FeatureTests.has_metadata_support() result to GET metadata element if FeatureTests.has_metadata_support(): return dom.metadata() else: # use lxml to return the metadata element IMPORTANT: While using setMetadata() you need to ALWAYS provide the whole metadata XML otherwise the metadata info will be override. On 04/24/2014 02:19 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
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): Add two function to set and get domain xml metadata 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 bug fix: call a method should be followed by "()" add method to test libvirt api are available manually manage the metadata element
src/kimchi/config.py.in | 14 ++++++- src/kimchi/featuretests.py | 44 ++++++++++++++++++- src/kimchi/i18n.py | 1 + src/kimchi/model/utils.py | 97 ++++++++++++++++++++++++++++++++++++++++++ src/kimchi/model/vms.py | 102 ++++++++++++++++++++++++++++----------------- tests/test_model.py | 13 ++++++ 6 files changed, 229 insertions(+), 42 deletions(-)
participants (4)
-
Aline Manera
-
Crístian Viana
-
shaohef@linux.vnet.ibm.com
-
Sheldon