[PATCH V3 00/11] bug fix and enhancement patch rebase for 6.2

These are the patches rebased which fix bugs reported for 6.1 or potential problem. Last patch is an enhancement for migration usage. Each patch is stand alone can can be applied independendly. All code change for libvirt-cim are in this serial, Please use it for 6.2, ignoring V2 serials. Wenchao Xia (11): remove script for bridge network do not deregister virt classes in yum upgrade CSI, fix debug print crash CSI, add lock to protect shared data DevicePool, fix debug print crash DevicePool, reimplement get_diskpool_config with libvirt RASDIndication, fix debug print crash device parsing, add debug print CSI Discard libvirt event by default VSSD, fix a error report issue in VSSD enum allow ssh based migration with non root's key file libvirt-cim.conf | 14 + libvirt-cim.spec.in | 12 +- libxkutil/device_parsing.c | 11 +- libxkutil/misc_util.c | 119 ++++- libxkutil/misc_util.h | 3 + libxkutil/xmlgen.c | 9 +- src/Virt_ComputerSystem.c | 46 ++- src/Virt_ComputerSystemIndication.c | 581 +++++++++++++++++++- src/Virt_DevicePool.c | 41 +- src/Virt_ResourceAllocationSettingDataIndication.c | 6 +- src/Virt_VSMigrationService.c | 114 ++++- src/Virt_VSSD.c | 72 ++- src/Virt_VirtualSystemManagementService.c | 36 ++- 13 files changed, 996 insertions(+), 68 deletions(-)

These are the patches rebased which fix bugs reported for 6.1 or potential problem. Last patch is an enhancement for migration usage. Each patch is stand alone can can be applied independendly. All code change for libvirt-cim are in this serial, Please use it for 6.2, ignoring V2 serials. Wenchao Xia (11): remove script for bridge network do not deregister virt classes in yum upgrade CSI, fix debug print crash CSI, add lock to protect shared data DevicePool, fix debug print crash DevicePool, reimplement get_diskpool_config with libvirt RASDIndication, fix debug print crash device parsing, add debug print CSI Discard libvirt event by default VSSD, fix a error report issue in VSSD enum allow ssh based migration with non root's key file libvirt-cim.conf | 14 + libvirt-cim.spec.in | 12 +- libxkutil/device_parsing.c | 11 +- libxkutil/misc_util.c | 119 ++++- libxkutil/misc_util.h | 3 + libxkutil/xmlgen.c | 9 +- src/Virt_ComputerSystem.c | 46 ++- src/Virt_ComputerSystemIndication.c | 581 +++++++++++++++++++- src/Virt_DevicePool.c | 41 +- src/Virt_ResourceAllocationSettingDataIndication.c | 6 +- src/Virt_VSMigrationService.c | 114 ++++- src/Virt_VSSD.c | 72 ++- src/Virt_VirtualSystemManagementService.c | 36 ++- 13 files changed, 996 insertions(+), 68 deletions(-)

libvirt0.9.10 will report error if bridge network was defined with script. This is the fix for it, otherwise VM start would fail. v2: rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- libxkutil/xmlgen.c | 9 +-------- 1 files changed, 1 insertions(+), 8 deletions(-) diff --git a/libxkutil/xmlgen.c b/libxkutil/xmlgen.c index 2dcd0d2..31619d8 100644 --- a/libxkutil/xmlgen.c +++ b/libxkutil/xmlgen.c @@ -269,18 +269,11 @@ static const char *set_net_source(xmlNodePtr nic, return NULL; } - +/* libvirt 0.9.10 report error if script is set with brdige */ static const char *bridge_net_to_xml(xmlNodePtr nic, struct net_device *dev) { - const char *script = "vif-bridge"; - xmlNodePtr tmp; const char *msg = NULL; - tmp = xmlNewChild(nic, NULL, BAD_CAST "script", NULL); - if (tmp == NULL) - return XML_ERROR; - xmlNewProp(tmp, BAD_CAST "path", BAD_CAST script); - msg = set_net_source(nic, dev, "bridge"); return msg; -- 1.7.1

Now yum upgrade will deregister the virt classes, make libvirt-cim not workable. This patch fixed it from now on. v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- libvirt-cim.spec.in | 12 ++++++++---- 1 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libvirt-cim.spec.in b/libvirt-cim.spec.in index d78eee7..3def978 100644 --- a/libvirt-cim.spec.in +++ b/libvirt-cim.spec.in @@ -104,18 +104,22 @@ rm -fr $RPM_BUILD_ROOT -r %{CIMV2_REG} -m %{CIMV2_MOF} -v >/dev/null 2>&1 || true %preun -%{_datadir}/%{name}/provider-register.sh -d -t pegasus \ +#Deregister only in uninstall, do nothing in upgrade. +if [ "$1" = "0" ]; then + echo "Deleting registered classes in libvirt-cim..." + %{_datadir}/%{name}/provider-register.sh -d -t pegasus \ -n @CIM_VIRT_NS@ \ -r %{REGISTRATION} -m %{SCHEMA} >/dev/null 2>&1 || true -%{_datadir}/%{name}/provider-register.sh -d -t pegasus \ + %{_datadir}/%{name}/provider-register.sh -d -t pegasus \ -n root/interop \ -r %{INTEROP_REG} -m %{INTEROP_MOF} >/dev/null 2>&1 || true -%{_datadir}/%{name}/provider-register.sh -d -t pegasus \ + %{_datadir}/%{name}/provider-register.sh -d -t pegasus \ -n root/PG_InterOp \ -r %{PGINTEROP_REG} -m %{PGINTEROP_MOF} >/dev/null 2>&1 || true -%{_datadir}/%{name}/provider-register.sh -d -t pegasus \ + %{_datadir}/%{name}/provider-register.sh -d -t pegasus \ -n root/cimv2 \ -r %{CIMV2_REG} -m %{CIMV2_MOF} >/dev/null 2>&1 || true +fi %postun -p /sbin/ldconfig -- 1.7.1

v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_ComputerSystemIndication.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/Virt_ComputerSystemIndication.c b/src/Virt_ComputerSystemIndication.c index 2d0a94e..b1b9fa0 100644 --- a/src/Virt_ComputerSystemIndication.c +++ b/src/Virt_ComputerSystemIndication.c @@ -503,7 +503,7 @@ static void csi_domain_event_cb(virConnectPtr conn, CMPIStatus s = {CMPI_RC_OK, NULL}; if (lifecycle_enabled == false || thread->active_filters <= 0) { - CU_DEBUG("%s indications deactivated, return"); + CU_DEBUG("%s indications deactivated, return", prefix); return; } -- 1.7.1

This patch added lock on shared data without which may trigger incorrect data operation. Global lock is used so speed may be slowed down, it can be improved in the future. Original code have a small chance to free thread->args in child thread just after main thread malloc it, for that thread->id is set to zero allowing main thread to enter that code. v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_ComputerSystemIndication.c | 39 ++++++++++++++++++++++++++++++++-- 1 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Virt_ComputerSystemIndication.c b/src/Virt_ComputerSystemIndication.c index b1b9fa0..3159ca0 100644 --- a/src/Virt_ComputerSystemIndication.c +++ b/src/Virt_ComputerSystemIndication.c @@ -174,9 +174,11 @@ static void csi_free_thread_data(void *data) if (data == NULL) return; + pthread_mutex_lock(&lifecycle_mutex); list_free(thread->dom_list); thread->dom_list = NULL; stdi_free_ind_args(&thread->args); + pthread_mutex_unlock(&lifecycle_mutex); } void set_source_inst_props(const CMPIBroker *broker, @@ -490,6 +492,8 @@ static int update_domain_list(virConnectPtr conn, csi_thread_data_t *thread) return s.rc; } +/* following function will protect global data with lifecycle_mutex. + TODO: improve it with seperate lock later. */ static void csi_domain_event_cb(virConnectPtr conn, virDomainPtr dom, int event, @@ -502,10 +506,13 @@ static void csi_domain_event_cb(virConnectPtr conn, char *prefix = class_prefix_name(thread->args->classname); CMPIStatus s = {CMPI_RC_OK, NULL}; + pthread_mutex_lock(&lifecycle_mutex); if (lifecycle_enabled == false || thread->active_filters <= 0) { CU_DEBUG("%s indications deactivated, return", prefix); + pthread_mutex_unlock(&lifecycle_mutex); return; } + pthread_mutex_unlock(&lifecycle_mutex); CU_DEBUG("Event: Domain %s(%d) event: %d detail: %d\n", virDomainGetName(dom), virDomainGetID(dom), event, detail); @@ -537,7 +544,9 @@ static void csi_domain_event_cb(virConnectPtr conn, if (cs_event != CS_CREATED) { char uuid[VIR_UUID_STRING_BUFLEN] = {0}; virDomainGetUUIDString(dom, &uuid[0]); + pthread_mutex_lock(&lifecycle_mutex); dom_xml = list_find(thread->dom_list, uuid); + pthread_mutex_unlock(&lifecycle_mutex); } if (dom_xml == NULL) { @@ -545,12 +554,16 @@ static void csi_domain_event_cb(virConnectPtr conn, goto end; } + pthread_mutex_lock(&lifecycle_mutex); async_ind(thread->args, cs_event, dom_xml, prefix); + pthread_mutex_unlock(&lifecycle_mutex); /* Update the domain list accordingly */ if (event == VIR_DOMAIN_EVENT_DEFINED) { if (detail == VIR_DOMAIN_EVENT_DEFINED_ADDED) { + pthread_mutex_lock(&lifecycle_mutex); csi_thread_dom_list_append(thread, dom_xml); + pthread_mutex_unlock(&lifecycle_mutex); } else if (detail == VIR_DOMAIN_EVENT_DEFINED_UPDATED) { free(dom_xml->name); free(dom_xml->xml); @@ -558,7 +571,9 @@ static void csi_domain_event_cb(virConnectPtr conn, } } else if (event == VIR_DOMAIN_EVENT_DEFINED && detail == VIR_DOMAIN_EVENT_UNDEFINED_REMOVED) { + pthread_mutex_lock(&lifecycle_mutex); list_remove(thread->dom_list, dom_xml); + pthread_mutex_unlock(&lifecycle_mutex); } end: @@ -579,10 +594,12 @@ static CMPI_THREAD_RETURN lifecycle_thread(void *params) if (prefix == NULL) goto init_out; + pthread_mutex_lock(&lifecycle_mutex); conn = connect_by_classname(_BROKER, args->classname, &s); if (conn == NULL) { CU_DEBUG("Unable to start lifecycle thread: " "Failed to connect (cn: %s)", args->classname); + pthread_mutex_unlock(&lifecycle_mutex); goto conn_out; } @@ -594,33 +611,47 @@ static CMPI_THREAD_RETURN lifecycle_thread(void *params) if (cb_id == -1) { CU_DEBUG("Failed to register domain event watch for '%s'", - args->classname) + args->classname); + pthread_mutex_unlock(&lifecycle_mutex); goto cb_out; } CBAttachThread(_BROKER, args->context); /* Get currently defined domains */ - if (update_domain_list(conn, thread) != CMPI_RC_OK) + if (update_domain_list(conn, thread) != CMPI_RC_OK) { + pthread_mutex_unlock(&lifecycle_mutex); goto end; + } + pthread_mutex_unlock(&lifecycle_mutex); CU_DEBUG("Entering CSI event loop (%s)", prefix); - while (thread->active_filters > 0) { + while (1) { + pthread_mutex_lock(&lifecycle_mutex); + if (thread->active_filters <= 0) { + pthread_mutex_unlock(&lifecycle_mutex); + break; + } + pthread_mutex_unlock(&lifecycle_mutex); if (virEventRunDefaultImpl() < 0) { virErrorPtr err = virGetLastError(); CU_DEBUG("Failed to run event loop: %s\n", err && err->message ? err->message : "Unknown error"); } + usleep(1); } CU_DEBUG("Exiting CSI event loop (%s)", prefix); + pthread_mutex_lock(&lifecycle_mutex); CBDetachThread(_BROKER, args->context); + pthread_mutex_unlock(&lifecycle_mutex); end: virConnectDomainEventDeregisterAny(conn, cb_id); cb_out: + pthread_mutex_lock(&lifecycle_mutex); thread->id = 0; thread->active_filters = 0; @@ -628,6 +659,8 @@ static CMPI_THREAD_RETURN lifecycle_thread(void *params) if (thread->args != NULL) stdi_free_ind_args(&thread->args); + pthread_mutex_unlock(&lifecycle_mutex); + conn_out: virConnectClose(conn); -- 1.7.1

v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_DevicePool.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/Virt_DevicePool.c b/src/Virt_DevicePool.c index 202e509..79dc108 100644 --- a/src/Virt_DevicePool.c +++ b/src/Virt_DevicePool.c @@ -710,7 +710,7 @@ static bool mempool_set_consumed(CMPIInstance *inst, virConnectPtr conn) dom = virDomainLookupByID(conn, domain_ids[i]); if (dom == NULL) { - CU_DEBUG("Cannot connect to domain %n: excluding", + CU_DEBUG("Cannot connect to domain %d: excluding", domain_ids[i]); continue; } -- 1.7.1

Original implemetion may return pools with NULL name if some pool disappear between two libvirt pool API call. And caller of this function consider number = 0 as error only but it original return negative value when error before. This patch fix it. V2: Use for instead of while in clean up. Treat zero pool returned from libvirt as normal instead of error. Rebased. v3: fix wrong i++ in for while. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_DevicePool.c | 39 +++++++++++++++++++++++++++++---------- 1 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/Virt_DevicePool.c b/src/Virt_DevicePool.c index 79dc108..769de63 100644 --- a/src/Virt_DevicePool.c +++ b/src/Virt_DevicePool.c @@ -117,52 +117,71 @@ int get_disk_pool(virStoragePoolPtr poolptr, struct virt_pool **pool) return ret; } +/* This function returns the real number of pools, no negative value should be + returned, if error happens it returns zero, otherwise return at least 1. */ static int get_diskpool_config(virConnectPtr conn, struct tmp_disk_pool **_pools) { - int count = 0; + int count = 0, realcount = 0; int i; char ** names = NULL; struct tmp_disk_pool *pools = NULL; + int have_err = 0; count = virConnectNumOfStoragePools(conn); - if (count <= 0) + if (count < 0) { + have_err = 1; goto out; + } else if (count == 0) { + goto out; + } names = calloc(count, sizeof(char *)); if (names == NULL) { CU_DEBUG("Failed to alloc space for %i pool names", count); count = 0; + have_err = 1; goto out; } - if (virConnectListStoragePools(conn, names, count) == -1) { + realcount = virConnectListStoragePools(conn, names, count); + if (realcount == -1) { CU_DEBUG("Failed to get storage pools"); - count = 0; + realcount = 0; + have_err = 1; + goto out; + } + if (realcount == 0) { + CU_DEBUG("zero pools got, but prelist is %d.", count); goto out; } - pools = calloc(count, sizeof(*pools)); + pools = calloc(realcount, sizeof(*pools)); if (pools == NULL) { - CU_DEBUG("Failed to alloc space for %i pool structs", count); + CU_DEBUG("Failed to alloc space for %i pool structs", realcount); + realcount = 0; + have_err = 1; goto out; } - for (i = 0; i < count; i++) { + for (i = 0; i < realcount; i++) { pools[i].tag = strdup(names[i]); pools[i].primordial = false; } out: - for (i = 0; i < count; i++) + for (i = 0; i < count; i++) { free(names[i]); + } free(names); - get_disk_parent(&pools, &count); + if (have_err == 0) { + get_disk_parent(&pools, &realcount); + } *_pools = pools; - return count; + return realcount; } static bool diskpool_set_capacity(virConnectPtr conn, -- 1.7.1

v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_ResourceAllocationSettingDataIndication.c | 6 +++++- 1 files changed, 5 insertions(+), 1 deletions(-) diff --git a/src/Virt_ResourceAllocationSettingDataIndication.c b/src/Virt_ResourceAllocationSettingDataIndication.c index a386132..93fb563 100644 --- a/src/Virt_ResourceAllocationSettingDataIndication.c +++ b/src/Virt_ResourceAllocationSettingDataIndication.c @@ -122,7 +122,11 @@ static CMPIStatus raise_indication(const CMPIBroker *broker, if (s.rc == CMPI_RC_OK) { CU_DEBUG("Indication delivered"); } else { - CU_DEBUG("Not delivered: %s", CMGetCharPtr(s.msg)); + if (s.msg == NULL) { + CU_DEBUG("Not delivered: msg is NULL."); + } else { + CU_DEBUG("Not delivered: %s", CMGetCharPtr(s.msg)); + } } out: -- 1.7.1

Sometime libvirt will fail in these API when there are low level error, what we saw is libvirt can't got xml for some domain. This patch add debug log when met this error, so in future we can know where is wrong better. v2: Rebased. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- libxkutil/device_parsing.c | 11 +++++++++-- 1 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libxkutil/device_parsing.c b/libxkutil/device_parsing.c index ceb4552..2841662 100644 --- a/libxkutil/device_parsing.c +++ b/libxkutil/device_parsing.c @@ -1251,12 +1251,19 @@ int get_dominfo(virDomainPtr dom, struct domain **dominfo) xml = virDomainGetXMLDesc(dom, VIR_DOMAIN_XML_INACTIVE | VIR_DOMAIN_XML_SECURE); - if (xml == NULL) + if (xml == NULL) { + CU_DEBUG("Failed to get dom xml with libvirt API."); return 0; + } ret = get_dominfo_from_xml(xml, dominfo); - if (virDomainGetAutostart(dom, &start) != 0) + if (ret != 1) { + CU_DEBUG("Failed to translate xml into struct domain"); + } + if (virDomainGetAutostart(dom, &start) != 0) { + CU_DEBUG("Failed to get dom autostart with libvirt API."); return 0; + } (*dominfo)->autostrt = start; -- 1.7.1

From cimtest, Calling to virEventRegisterDefaultImpl() of libvirt API, resulting random fail in cases, which seems most likely tog-pegasus's internal data is damaged. The root cause may be: 1 libvirt event API have a bug, we called it from thread A and then do other things in thread B, maybe it did not handle this well. 2 tog-pegasus have confilict with libvirt's event. 3 Potential requirement in libvirt event API or tog-pegasus's thread, which is not document so we used them in a wrong way. To me, most possible is that tog-pegasus tries to manage all threads resulting the error. This patch bring back libvirt-cim's own old event implemention, which is by default used now. CSI from libvirt can still be activated with a macro. This patch also have changed some buglike code of old libvirt-cim's event implemention. Tested with cimtest on following Env, no more strange error found: RH6.3 libvirt-0.9.10-21.el6.x86_64 tog-pegasus-2.11.0-3.el6.x86_64 v2: Rebased. minor change for getting current vm status code in CSI child thread. Comment out the debug print in CSI child thread. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_ComputerSystem.c | 46 +++- src/Virt_ComputerSystemIndication.c | 540 ++++++++++++++++++++++++++++- src/Virt_VirtualSystemManagementService.c | 36 ++- 3 files changed, 616 insertions(+), 6 deletions(-) diff --git a/src/Virt_ComputerSystem.c b/src/Virt_ComputerSystem.c index e6c7e55..adef85e 100644 --- a/src/Virt_ComputerSystem.c +++ b/src/Virt_ComputerSystem.c @@ -45,8 +45,45 @@ #include "Virt_HostSystem.h" #include "Virt_VirtualSystemSnapshotService.h" +#include "config.h" + const static CMPIBroker *_BROKER; +#ifndef USE_LIBVIRT_EVENT +static bool trigger_mod_indication(const CMPIContext *context, + CMPIInstance *prev_inst, + const CMPIObjectPath *ref) +{ + CMPIStatus s = {CMPI_RC_OK, NULL}; + const char *ind_name = "ComputerSystemModifiedIndication"; + CMPIInstance *ind = NULL; + char *type = NULL; + + CU_DEBUG("Preparing ComputerSystem indication"); + + ind = get_typed_instance(_BROKER, + CLASSNAME(ref), + ind_name, + NAMESPACE(ref)); + if (ind == NULL) { + CU_DEBUG("Failed to create ind '%s'", ind_name); + goto out; + } + + CU_DEBUG("Setting PreviousInstance"); + CMSetProperty(ind, "PreviousInstance", + (CMPIValue *)&prev_inst, CMPI_instance); + + type = get_typed_class(CLASSNAME(ref), ind_name); + + s = stdi_raise_indication(_BROKER, context, type, NAMESPACE(ref), ind); + + out: + free(type); + return s.rc == CMPI_RC_OK; +} +#endif + /* Set the "Name" property of an instance from a domain */ static int set_name_from_dom(virDomainPtr dom, CMPIInstance *instance) { @@ -1256,8 +1293,15 @@ static CMPIStatus state_change(CMPIMethodMI *self, s = __state_change(name, state, reference); - if (s.rc == CMPI_RC_OK) + if (s.rc == CMPI_RC_OK) { +#ifndef USE_LIBVIRT_EVENT + bool ind_rc= trigger_mod_indication(context, prev_inst, + reference); + if (!ind_rc) + CU_DEBUG("Unable to trigger indication"); +#endif rc = 0; + } out: CMReturnData(results, &rc, CMPI_uint32); diff --git a/src/Virt_ComputerSystemIndication.c b/src/Virt_ComputerSystemIndication.c index 3159ca0..6b5cdd4 100644 --- a/src/Virt_ComputerSystemIndication.c +++ b/src/Virt_ComputerSystemIndication.c @@ -48,6 +48,8 @@ #include "Virt_ComputerSystemIndication.h" #include "Virt_HostSystem.h" +#define WAIT_TIME 60 +#define FAIL_WAIT_TIME 2 #define CSI_NUM_PLATFORMS 3 enum CSI_PLATFORMS { @@ -80,6 +82,7 @@ struct _csi_thread_data_t { }; static const CMPIBroker *_BROKER; +static pthread_cond_t lifecycle_cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t lifecycle_mutex = PTHREAD_MUTEX_INITIALIZER; static bool lifecycle_enabled = false; static csi_thread_data_t csi_thread_data[CSI_NUM_PLATFORMS] = {{0}, {0}, {0}}; @@ -580,6 +583,376 @@ static void csi_domain_event_cb(virConnectPtr conn, free(prefix); } +/* libvirt-cim's private CSI implement */ +struct dom_xml { + char uuid[VIR_UUID_STRING_BUFLEN]; + char *xml; + enum {DOM_OFFLINE, + DOM_ONLINE, + DOM_PAUSED, + DOM_CRASHED, + DOM_GONE, + } state; +}; + +static void free_dom_xml (struct dom_xml dom) +{ + free(dom.xml); + dom.xml = NULL; +} + +static char *sys_name_from_xml(char *xml) +{ + char *tmp = NULL; + char *name = NULL; + int rc; + + tmp = strstr(xml, "<name>"); + if (tmp == NULL) + goto out; + + rc = sscanf(tmp, "<name>%a[^<]s</name>", &name); + if (rc != 1) + name = NULL; + + out: + return name; +} + +static int dom_state(virDomainPtr dom) +{ + virDomainInfo info; + int ret; + + ret = virDomainGetInfo(dom, &info); + if (ret != 0) + return DOM_GONE; + + switch (info.state) { + case VIR_DOMAIN_NOSTATE: + case VIR_DOMAIN_RUNNING: + case VIR_DOMAIN_BLOCKED: + return DOM_ONLINE; + + case VIR_DOMAIN_PAUSED: + return DOM_PAUSED; + + case VIR_DOMAIN_SHUTOFF: + return DOM_OFFLINE; + + case VIR_DOMAIN_CRASHED: + return DOM_CRASHED; + + default: + return DOM_GONE; + }; +} + +static CMPIStatus doms_to_xml(struct dom_xml **dom_xml_list, + virDomainPtr *dom_ptr_list, + int dom_ptr_count) +{ + int i; + int rc; + CMPIStatus s = {CMPI_RC_OK, NULL}; + + if (dom_ptr_count <= 0) { + *dom_xml_list = NULL; + return s; + } + *dom_xml_list = calloc(dom_ptr_count, sizeof(struct dom_xml)); + for (i = 0; i < dom_ptr_count; i++) { + rc = virDomainGetUUIDString(dom_ptr_list[i], + (*dom_xml_list)[i].uuid); + if (rc == -1) { + cu_statusf(_BROKER, &s, + CMPI_RC_ERR_FAILED, + "Failed to get UUID"); + /* If any domain fails, we fail. */ + break; + } + + (*dom_xml_list)[i].xml = virDomainGetXMLDesc(dom_ptr_list[i], + VIR_DOMAIN_XML_INACTIVE | VIR_DOMAIN_XML_SECURE); + if ((*dom_xml_list)[i].xml == NULL) { + cu_statusf(_BROKER, &s, + CMPI_RC_ERR_FAILED, + "Failed to get xml desc"); + break; + } + + (*dom_xml_list)[i].state = dom_state(dom_ptr_list[i]); + } + + return s; +} + +static bool dom_changed(struct dom_xml prev_dom, + struct dom_xml *cur_xml, + int cur_count) +{ + int i; + bool ret = false; + + for (i = 0; i < cur_count; i++) { + if (strcmp(cur_xml[i].uuid, prev_dom.uuid) != 0) + continue; + + if (strcmp(cur_xml[i].xml, prev_dom.xml) != 0) { + CU_DEBUG("Domain config changed"); + ret = true; + } + + if (prev_dom.state != cur_xml[i].state) { + CU_DEBUG("Domain state changed"); + ret = true; + } + + break; + } + + return ret; +} + +static bool wait_for_event(int wait_time) +{ + struct timespec timeout; + int ret; + + + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += wait_time; + + ret = pthread_cond_timedwait(&lifecycle_cond, + &lifecycle_mutex, + &timeout); + + return true; +} + +static bool dom_in_list(char *uuid, int count, struct dom_xml *list) +{ + int i; + + for (i = 0; i < count; i++) { + if (STREQ(uuid, list[i].uuid)) + return true; + } + + return false; +} + +static bool async_ind_native(CMPIContext *context, + int ind_type, + struct dom_xml prev_dom, + char *prefix, + struct ind_args *args) +{ + bool rc = false; + char *name = NULL; + char *cn = NULL; + CMPIObjectPath *op; + CMPIInstance *prev_inst; + CMPIInstance *affected_inst; + CMPIStatus s = {CMPI_RC_OK, NULL}; + + CU_DEBUG("Entering native indication diliv with type %d.", ind_type) + if (!lifecycle_enabled) { + CU_DEBUG("CSI not enabled, skipping indication delivery"); + return false; + } + + name = sys_name_from_xml(prev_dom.xml); + CU_DEBUG("Name for system: '%s'", name); + if (name == NULL) { + rc = false; + goto out; + } + + cn = get_typed_class(prefix, "ComputerSystem"); + + op = CMNewObjectPath(_BROKER, args->ns, cn, &s); + if ((s.rc != CMPI_RC_OK) || CMIsNullObject(op)) { + CU_DEBUG("op error"); + goto out; + } + + if (ind_type == CS_CREATED || ind_type == CS_MODIFIED) { + s = get_domain_by_name(_BROKER, op, name, &affected_inst); + if (s.rc != CMPI_RC_OK) { + CU_DEBUG("domain by name error"); + goto out; + } + } else if (ind_type == CS_DELETED) { + rc = create_deleted_guest_inst(prev_dom.xml, + args->ns, + prefix, + &affected_inst); + if (!rc) { + CU_DEBUG("Could not recreate guest instance"); + goto out; + } + } else { + CU_DEBUG("Unrecognized indication type"); + goto out; + } + + /* FIXME: We are unable to get the previous CS instance after it has + been modified. Consider keeping track of the previous + state in the place we keep track of the requested state */ + prev_inst = affected_inst; + + CMSetProperty(affected_inst, "Name", + (CMPIValue *)name, CMPI_chars); + CMSetProperty(affected_inst, "UUID", + (CMPIValue *)prev_dom.uuid, CMPI_chars); + + rc = _do_indication(_BROKER, context, prev_inst, affected_inst, + ind_type, prefix, args); + + out: + free(cn); + free(name); + return rc; +} + +static CMPI_THREAD_RETURN lifecycle_thread_native(void *params) +{ + CU_DEBUG("Entering libvirtc-cim native CSI thread."); + csi_thread_data_t *thread = (csi_thread_data_t *) params; + struct ind_args *args = thread->args; + CMPIContext *context = args->context; + char *prefix = class_prefix_name(args->classname); + virConnectPtr conn; + CMPIStatus s; + int retry_time = FAIL_WAIT_TIME; + + struct dom_xml *cur_xml = NULL; + struct dom_xml *prev_xml = NULL; + int prev_count = 0; + int cur_count = 0; + virDomainPtr *tmp_list = NULL; + int CBAttached = 0; + + if (prefix == NULL) + goto init_out; + + pthread_mutex_lock(&lifecycle_mutex); + conn = connect_by_classname(_BROKER, args->classname, &s); + if (conn == NULL) { + CU_DEBUG("Unable to start lifecycle thread: " + "Failed to connect (cn: %s)", args->classname); + pthread_mutex_unlock(&lifecycle_mutex); + goto conn_out; + } + + CBAttachThread(_BROKER, args->context); + CBAttached = 1; + prev_count = get_domain_list(conn, &tmp_list); + s = doms_to_xml(&prev_xml, tmp_list, prev_count); + free_domain_list(tmp_list, prev_count); + free(tmp_list); + if (s.rc != CMPI_RC_OK) + CU_DEBUG("doms_to_xml failed. Attempting to continue."); + + CU_DEBUG("Entering libvirt-cim native CSI event loop (%s)", prefix); + + int i; + while (1) { + if (thread->active_filters <= 0) { + break; + } + + bool res; + bool failure = false; + + cur_count = get_domain_list(conn, &tmp_list); + s = doms_to_xml(&cur_xml, tmp_list, cur_count); + free_domain_list(tmp_list, cur_count); + free(tmp_list); + if (s.rc != CMPI_RC_OK) { + CU_DEBUG("doms_to_xml failed. retry in %d seconds", + retry_time); + failure = true; + goto fail; + } + + /* CU_DEBUG("cur_count %d, prev_count %d.", + cur_count, prev_count); */ + for (i = 0; i < cur_count; i++) { + res = dom_in_list(cur_xml[i].uuid, prev_count, prev_xml); + if (!res) { + async_ind_native(context, CS_CREATED, + cur_xml[i], prefix, args); + } + + } + + for (i = 0; i < prev_count; i++) { + res = dom_in_list(prev_xml[i].uuid, cur_count, cur_xml); + if (!res) { + async_ind_native(context, CS_DELETED, + prev_xml[i], prefix, args); + } + } + + for (i = 0; i < prev_count; i++) { + res = dom_changed(prev_xml[i], cur_xml, cur_count); + if (res) { + async_ind_native(context, CS_MODIFIED, + prev_xml[i], prefix, args); + } + free_dom_xml(prev_xml[i]); + } + + fail: + if (failure) { + wait_for_event(FAIL_WAIT_TIME); + } else { + free(prev_xml); + prev_xml = cur_xml; + cur_xml = NULL; + prev_count = cur_count; + cur_count = 0; + wait_for_event(WAIT_TIME); + } + } + + CU_DEBUG("Exiting libvirt-cim native CSI event loop (%s)", prefix); + + if (prev_xml != NULL) { + for (i = 0; i < prev_count; i++) { + free_dom_xml(prev_xml[i]); + } + free(prev_xml); + prev_xml = NULL; + } + + pthread_mutex_unlock(&lifecycle_mutex); + + virConnectClose(conn); + + conn_out: + free(prefix); + + init_out: + pthread_mutex_lock(&lifecycle_mutex); + thread->id = 0; + thread->active_filters = 0; + + /* it seems tog-pegasus try kill this thread after detached, use this + flag to delay detach as much as possible. */ + if (CBAttached > 0) { + CBDetachThread(_BROKER, args->context); + } + if (thread->args != NULL) + stdi_free_ind_args(&thread->args); + + pthread_mutex_unlock(&lifecycle_mutex); + + return (CMPI_THREAD_RETURN) 0; +} + static CMPI_THREAD_RETURN lifecycle_thread(void *params) { csi_thread_data_t *thread = (csi_thread_data_t *) params; @@ -696,13 +1069,24 @@ static CMPIStatus ActivateFilter(CMPIIndicationMI* mi, csi_thread_data_t *thread = NULL; static int events_registered = 0; +#ifndef USE_LIBVIRT_EVENT + int use_libvirt_event = 0; +#else + int use_libvirt_event = 1; +#endif + CU_DEBUG("ActivateFilter for %s", CLASSNAME(op)); pthread_mutex_lock(&lifecycle_mutex); - if (events_registered == 0) { - events_registered = 1; - virEventRegisterDefaultImpl(); + if (use_libvirt_event == 1) { + if (events_registered == 0) { + events_registered = 1; + CU_DEBUG("Registering libvirt event."); + virEventRegisterDefaultImpl(); + } + } else { + CU_DEBUG("Using libvirt-cim's event implemention."); } _ctx = (struct std_indication_ctx *)mi->hdl; @@ -753,7 +1137,20 @@ static CMPIStatus ActivateFilter(CMPIIndicationMI* mi, args->_ctx = _ctx; thread->args = args; - thread->id = _BROKER->xft->newThread(lifecycle_thread, thread, 0); + + if (use_libvirt_event == 0) { + thread->id = _BROKER->xft->newThread( + lifecycle_thread_native, + thread, 0); + } else { + thread->id = _BROKER->xft->newThread(lifecycle_thread, + thread, 0); + } + + if (thread->id <= 0) { + CU_DEBUG("Error, failed to create new thread."); + error = true; + } out: if (error == true) { @@ -791,6 +1188,10 @@ static CMPIStatus DeActivateFilter(CMPIIndicationMI* mi, csi_thread_data[platform].active_filters -= 1; pthread_mutex_unlock(&lifecycle_mutex); +#ifndef USE_LIBVIRT_EVENT + pthread_cond_signal(&lifecycle_cond); +#endif + out: return s; } @@ -817,6 +1218,7 @@ static _EI_RTYPE DisableIndications(CMPIIndicationMI* mi, _EI_RET(); } + DECLARE_FILTER(xen_created, "Xen_ComputerSystemCreatedIndication"); DECLARE_FILTER(xen_deleted, "Xen_ComputerSystemDeletedIndication"); DECLARE_FILTER(xen_modified, "Xen_ComputerSystemModifiedIndication"); @@ -840,7 +1242,137 @@ static struct std_ind_filter *filters[] = { NULL, }; +#ifndef USE_LIBVIRT_EVENT +static CMPIStatus trigger_indication(const CMPIContext *context) +{ + CU_DEBUG("triggered"); + pthread_cond_signal(&lifecycle_cond); + return(CMPIStatus){CMPI_RC_OK, NULL}; +} + +static CMPIInstance *get_prev_inst(const CMPIBroker *broker, + const CMPIInstance *ind, + CMPIStatus *s) +{ + CMPIData data; + CMPIInstance *prev_inst = NULL; + + data = CMGetProperty(ind, "PreviousInstance", s); + if (s->rc != CMPI_RC_OK || CMIsNullValue(data)) { + cu_statusf(broker, s, + CMPI_RC_ERR_NO_SUCH_PROPERTY, + "Unable to get PreviousInstance of the indication"); + goto out; + } + + if (data.type != CMPI_instance) { + cu_statusf(broker, s, + CMPI_RC_ERR_TYPE_MISMATCH, + "Indication SourceInstance is of unexpected type"); + goto out; + } + + prev_inst = data.value.inst; + + out: + return prev_inst; +} + +static CMPIStatus raise_indication(const CMPIBroker *broker, + const CMPIContext *ctx, + const CMPIObjectPath *ref, + const CMPIInstance *ind) +{ + CMPIStatus s = {CMPI_RC_OK, NULL}; + CMPIInstance *prev_inst; + CMPIInstance *src_inst; + CMPIObjectPath *_ref = NULL; + struct std_indication_ctx *_ctx = NULL; + struct ind_args *args = NULL; + char *prefix = NULL; + bool rc; + + if (!lifecycle_enabled) { + cu_statusf(_BROKER, &s, + CMPI_RC_ERR_FAILED, + "CSI not enabled, skipping indication delivery"); + goto out; + } + + prev_inst = get_prev_inst(broker, ind, &s); + if (s.rc != CMPI_RC_OK || CMIsNullObject(prev_inst)) + goto out; + + _ref = CMGetObjectPath(prev_inst, &s); + if (s.rc != CMPI_RC_OK) { + cu_statusf(broker, &s, + CMPI_RC_ERR_FAILED, + "Unable to get a reference to the guest"); + goto out; + } + + /* FIXME: This is a Pegasus work around. Pegsus loses the namespace + when an ObjectPath is pulled from an instance */ + if (STREQ(NAMESPACE(_ref), "")) + CMSetNameSpace(_ref, "root/virt"); + + s = get_domain_by_ref(broker, _ref, &src_inst); + if (s.rc != CMPI_RC_OK || CMIsNullObject(src_inst)) + goto out; + + _ctx = malloc(sizeof(struct std_indication_ctx)); + if (_ctx == NULL) { + cu_statusf(broker, &s, + CMPI_RC_ERR_FAILED, + "Unable to allocate indication context"); + goto out; + } + + _ctx->brkr = broker; + _ctx->handler = NULL; + _ctx->filters = filters; + _ctx->enabled = lifecycle_enabled; + + args = malloc(sizeof(struct ind_args)); + if (args == NULL) { + cu_statusf(broker, &s, + CMPI_RC_ERR_FAILED, + "Unable to allocate ind_args"); + goto out; + } + + args->ns = strdup(NAMESPACE(_ref)); + args->classname = strdup(CLASSNAME(_ref)); + args->_ctx = _ctx; + + prefix = class_prefix_name(args->classname); + + rc = _do_indication(broker, ctx, prev_inst, src_inst, + CS_MODIFIED, prefix, args); + + if (!rc) { + cu_statusf(_BROKER, &s, + CMPI_RC_ERR_FAILED, + "Unable to generate indication"); + } + + out: + if (args != NULL) + stdi_free_ind_args(&args); + + if (_ctx != NULL) + free(_ctx); + + free(prefix); + return s; +} +#endif + static struct std_indication_handler csi = { +#ifndef USE_LIBVIRT_EVENT + .raise_fn = raise_indication, + .trigger_fn = trigger_indication, +#endif .activate_fn = ActivateFilter, .deactivate_fn = DeActivateFilter, .enable_fn = EnableIndications, diff --git a/src/Virt_VirtualSystemManagementService.c b/src/Virt_VirtualSystemManagementService.c index 10adf8b..059b852 100644 --- a/src/Virt_VirtualSystemManagementService.c +++ b/src/Virt_VirtualSystemManagementService.c @@ -107,6 +107,24 @@ enum ResourceAction { RESOURCE_MOD, }; +#ifndef USE_LIBVIRT_EVENT +static bool trigger_indication(const CMPIContext *context, + const char *base_type, + const CMPIObjectPath *ref) +{ + char *type; + CMPIStatus s; + + type = get_typed_class(CLASSNAME(ref), base_type); + + s = stdi_trigger_indication(_BROKER, context, type, NAMESPACE(ref)); + + free(type); + + return s.rc == CMPI_RC_OK; +} +#endif + #if LIBVIR_VERSION_NUMBER < 9000 /* Network QoS support */ static CMPIStatus add_qos_for_mac(const uint64_t qos, @@ -2166,6 +2184,11 @@ static CMPIStatus define_system(CMPIMethodMI *self, CMAddArg(argsout, "ResultingSystem", &result, CMPI_ref); } +#ifndef USE_LIBVIRT_EVENT + trigger_indication(context, + "ComputerSystemCreatedIndication", + reference); +#endif out: if (s.rc == CMPI_RC_OK) rc = CIM_SVPC_RETURN_COMPLETED; @@ -2268,6 +2291,11 @@ error: NULL, reference, &list); +#ifndef USE_LIBVIRT_EVENT + trigger_indication(context, + "ComputerSystemDeletedIndication", + reference); +#endif } virDomainFree(dom); @@ -2349,8 +2377,14 @@ static CMPIStatus update_system_settings(const CMPIContext *context, connect_and_create(xml, ref, &s); } - if (s.rc == CMPI_RC_OK) + if (s.rc == CMPI_RC_OK) { set_autostart(vssd, ref, dom); +#ifndef USE_LIBVIRT_EVENT + trigger_indication(context, + "ComputerSystemModifiedIndication", + ref); +#endif + } out: free(xml); -- 1.7.1

If user delete a VS and enum VSSD at the sametime, it may have chance to report error, but actually it succeed just with some VS not found. Root cause is race condition between libvirt API for get domain's name&count and the API for get domain's xml. This Patch fixed it. It will continue if found one VS is missing in enum. v2: Rebased and better document. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- src/Virt_VSSD.c | 72 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/Virt_VSSD.c b/src/Virt_VSSD.c index 499b157..bbf45e8 100644 --- a/src/Virt_VSSD.c +++ b/src/Virt_VSSD.c @@ -36,6 +36,8 @@ #include "Virt_VSSD.h" +#define VSSD_ERROR_RECOVERABLE (2) + const static CMPIBroker *_BROKER; static CMPIStatus _set_fv_prop(const CMPIBroker *broker, @@ -181,8 +183,19 @@ static int instance_from_dom(const CMPIBroker *broker, struct domain *dominfo = NULL; ret = get_dominfo(dom, &dominfo); - if (!ret) + if (!ret) { + int active = virDomainIsActive(dom); + if (active < 0) { + /* ret > 1 indicate that it is recoverable */ + CU_DEBUG("Failed to get dominfo, " + "it may not present now."); + ret = VSSD_ERROR_RECOVERABLE; + } else { + CU_DEBUG("Failed to get dominfo, " + "libvirt level error."); + } goto out; + } op = CMGetObjectPath(inst, NULL); pfx = class_prefix_name(CLASSNAME(op)); @@ -246,6 +259,8 @@ static int instance_from_dom(const CMPIBroker *broker, s = _set_fv_prop(broker, dominfo, inst); if (s.rc != CMPI_RC_OK) { ret = 0; + CU_DEBUG("Failed to set full virtual prop for %d dom.", + dominfo->type); goto out; } } @@ -260,6 +275,7 @@ static int instance_from_dom(const CMPIBroker *broker, if (asprintf(&vsid, "%s:%s", pfx, dominfo->name) == -1) { ret = 0; + CU_DEBUG("Failed in asprintf vsid."); goto out; } @@ -278,7 +294,8 @@ static CMPIInstance *_get_vssd(const CMPIBroker *broker, const CMPIObjectPath *reference, virConnectPtr conn, virDomainPtr dom, - CMPIStatus *s) + CMPIStatus *s, + int *error_below) { CMPIInstance *inst = NULL; @@ -294,10 +311,18 @@ static CMPIInstance *_get_vssd(const CMPIBroker *broker, goto out; } - if (instance_from_dom(broker, dom, inst) != 1) { - cu_statusf(broker, s, - CMPI_RC_ERR_FAILED, - "Unable to get VSSD instance from Domain"); + *error_below = instance_from_dom(broker, dom, inst); + if (*error_below != 1) { + if (*error_below == VSSD_ERROR_RECOVERABLE) { + cu_statusf(broker, s, + CMPI_RC_ERR_FAILED, + "Unable to get VSSD instance from Domain, " + "Domain may not exist at this moment."); + } else { + cu_statusf(broker, s, + CMPI_RC_ERR_FAILED, + "Unable to get VSSD instance from Domain"); + } } out: @@ -312,6 +337,7 @@ static CMPIStatus return_enum_vssd(const CMPIObjectPath *reference, virDomainPtr *list; int count; int i; + int error_below; CMPIStatus s = {CMPI_RC_OK, NULL}; conn = connect_by_classname(_BROKER, CLASSNAME(reference), &s); @@ -327,14 +353,33 @@ static CMPIStatus return_enum_vssd(const CMPIObjectPath *reference, } else if (count == 0) goto out; + int fail_count = 0; for (i = 0; i < count; i++) { CMPIInstance *inst = NULL; - - inst = _get_vssd(_BROKER, reference, conn, list[i], &s); - + + inst = _get_vssd(_BROKER, reference, conn, list[i], + &s, &error_below); + virDomainFree(list[i]); - if (inst == NULL) - continue; + + if ((inst == NULL) || (s.rc != CMPI_RC_OK)) { + fail_count++; + CU_DEBUG("VSSD got %d fail instance, total %d," + " error %d.", + fail_count, count, error_below); + if (error_below == VSSD_ERROR_RECOVERABLE) { + cu_statusf(_BROKER, &s, + CMPI_RC_OK, + "%d/%d VSSD not got," + " some VS may not exist now", + fail_count, count); + CU_DEBUG("Continue to next VSSD for enum."); + continue; + } else { + CU_DEBUG("Met unrecoverable error."); + break; + } + } if (names_only) cu_return_instance_name(results, inst); @@ -358,7 +403,8 @@ CMPIStatus get_vssd_by_name(const CMPIBroker *broker, virDomainPtr dom; CMPIStatus s = {CMPI_RC_OK, NULL}; CMPIInstance *inst = NULL; - + int error_below; + conn = connect_by_classname(broker, CLASSNAME(reference), &s); if (conn == NULL) { cu_statusf(broker, &s, @@ -377,7 +423,7 @@ CMPIStatus get_vssd_by_name(const CMPIBroker *broker, goto out; } - inst = _get_vssd(broker, reference, conn, dom, &s); + inst = _get_vssd(broker, reference, conn, dom, &s, &error_below); virDomainFree(dom); -- 1.7.1

This patch allow libvirt-cim to use non-root's ssh key in migration to avoid exposing root's ssh login on server. In some case server are forbidden to expose or provide any root ssh login, and still use ssh encryption between two migration nodes with key of special account created for virtual machine management. Usage: 1 set temp file in libvirt-cim.conf or not: migrate_ssh_temp_key=[DIR]/[FILE] Note that [DIR] must be full owned by root user and exist, otherwise libvirt will reject the request later. For example set it to /root/VControll_id_rsa If it is set, it tells libvirt-cim a copy is needed to make keyfile in a directory owned by root. If not set, that means keyfile is already in a valid place can be used directly. 2 call migrate API with ssh destination as before, but add one additonal parameter for example: SSH_Key="/home/VController/.ssh/id_rsa" Then that key would be used intead of default root's ssh key. Details: This patch contains two parts, first is adding common code to read config include non-root ssh migration configuration, second is migration logic code. For first part, now config reading is done only once instead improving performance, so change config now needs restart the service. Also abstracted the common config reading code to make each property access more gracefully. For second part, libvirt-cim would run shell command if needed: "cp -f [SSH_Key] [migrate_ssh_temp_key]", then use [migrate_ssh_temp_key] to generate uri suffix for remote connection to migration destination. If copy is not needed, then directly use [SSH_Key]. Note: Test was done when SELinux was shutdown. V2: Rebased and Better documentation. v3: Added some common code to read configuration, Added an option which can disable this function, use just one parameter SSH_Key in API, forbid copy any file to avoid security problem for that now config file controls it. Signed-off-by: Wenchao Xia <xiawenc@linux.vnet.ibm.com> --- libvirt-cim.conf | 14 +++++ libxkutil/misc_util.c | 119 ++++++++++++++++++++++++++++++++++++----- libxkutil/misc_util.h | 3 + src/Virt_VSMigrationService.c | 114 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 231 insertions(+), 19 deletions(-) diff --git a/libvirt-cim.conf b/libvirt-cim.conf index d3cb2c3..97e6918 100644 --- a/libvirt-cim.conf +++ b/libvirt-cim.conf @@ -11,3 +11,17 @@ # Default value: false # # readonly = false; + +# migrate_ssh_temp_key (string) +# Defines a temp key file which would be used as ssh key in ssh migration, +# Only valid when SSH_Key is set in migration call. +# If SSH_Key is not in a directory owned by root, set this value to a path +# owned by root, tells libvirt-cim copy it there before use it. The directory +# in the path must exist and totally owned by root. +# If SSH_Key is already in a valid place, don't set it to to tell libvirt-cim +# use SSH_Key directly. +# +# Possible values: {any path plus filename on host} +# Default value: NULL, that is not set. +# +# migrate_ssh_temp_key = "/root/vm_migrate_tmp_id_rsa"; diff --git a/libxkutil/misc_util.c b/libxkutil/misc_util.c index 2d149ae..4a16ec2 100644 --- a/libxkutil/misc_util.c +++ b/libxkutil/misc_util.c @@ -106,14 +106,28 @@ static const char *cn_to_uri(const char *classname) return NULL; } -static int is_read_only(void) -{ - int readonly = 0; +typedef enum LibvirtcimConfigType { + CONFIG_BOOL, + CONFIG_STRING, +} LibvirtcimConfigType; + +typedef struct LibvirtcimConfigProperty { + const char *name; + LibvirtcimConfigType value_type; + union { + int value_bool; + char *value_string; + }; + int have_read; +} LibvirtcimConfigProperty; +static int libvirt_cim_config_get(LibvirtcimConfigProperty *prop) +{ #ifdef HAVE_LIBCONFIG config_t conf; - int ret; - const char *readonly_str = "readonly"; + int ret = 0; + int error = 0; + const char *value_string = NULL; config_init(&conf); @@ -121,24 +135,101 @@ static int is_read_only(void) if (ret == CONFIG_FALSE) { CU_DEBUG("Error reading config file at line %d: '%s'\n", conf.error_line, conf.error_text); + error = -1; goto out; } - ret = config_lookup_bool(&conf, readonly_str, &readonly); - if (ret == CONFIG_FALSE) { - CU_DEBUG("'%s' not found in config file, assuming false\n", - readonly_str); - goto out; + switch (prop->value_type) { + case CONFIG_BOOL: + ret = config_lookup_bool(&conf, + prop->name, &prop->value_bool); + if (ret == CONFIG_FALSE) { + CU_DEBUG("Bool property '%s' in config file '%s' " + "not found.", + prop->name, LIBVIRTCIM_CONF); + error = -1; + goto out; + } + + CU_DEBUG("Bool property '%s' in config file '%s' is '%d'.", + prop->name, LIBVIRTCIM_CONF, prop->value_bool); + break; + case CONFIG_STRING: + ret = config_lookup_string(&conf, + prop->name, &value_string); + if (ret == CONFIG_FALSE) { + CU_DEBUG("String property '%s' in config file '%s' " + "not found.", + prop->name, LIBVIRTCIM_CONF); + error = -1; + goto out; + } + + CU_DEBUG("String property '%s' in config file '%s' is '%s'.", + prop->name, LIBVIRTCIM_CONF, value_string); + + if (prop->value_string) { + free(prop->value_string); + } + prop->value_string = strdup(value_string); + if (!prop->value_string) { + CU_DEBUG("Failed in duplicate value '%s'", + value_string); + error = -1; + goto out; + } + break; + default: + CU_DEBUG("Got invalid property type request %d.", + prop->value_type); + error = -1; + break; } - CU_DEBUG("'%s' value in '%s' config file: %d\n", readonly_str, - LIBVIRTCIM_CONF, readonly); out: config_destroy(&conf); + return error; +#else + CU_DEBUG("Built without libconfig, can't read '%s'.",prop->name); + return -2; #endif +} + +static int is_read_only(void) +{ + static LibvirtcimConfigProperty prop; + + if (prop.have_read == 1) { + goto ret; + } + + prop.name = "readonly"; + prop.value_type = CONFIG_BOOL; + prop.value_bool = 0; + libvirt_cim_config_get(&prop); + /* try only once */ + prop.have_read = 1; + +ret: + return prop.value_bool; +} + +const char *get_mig_ssh_tmp_key(void) +{ + static LibvirtcimConfigProperty prop; + + if (prop.have_read == 1) { + goto ret; + } + + prop.name = "migrate_ssh_temp_key"; + prop.value_type = CONFIG_STRING; + libvirt_cim_config_get(&prop); + /* try only once */ + prop.have_read = 1; - /* Default value is 0 (false) */ - return readonly; +ret: + return prop.value_string; } virConnectPtr connect_by_classname(const CMPIBroker *broker, diff --git a/libxkutil/misc_util.h b/libxkutil/misc_util.h index c7a2122..6ce60a0 100644 --- a/libxkutil/misc_util.h +++ b/libxkutil/misc_util.h @@ -151,6 +151,9 @@ int virt_set_status(const CMPIBroker *broker, #define REF2STR(r) CMGetCharPtr(CMObjectPathToString(r, NULL)) +/* get libvirt-cim config */ +const char *get_mig_ssh_tmp_key(void); + /* * Local Variables: * mode: C diff --git a/src/Virt_VSMigrationService.c b/src/Virt_VSMigrationService.c index 76e3d25..14a3004 100644 --- a/src/Virt_VSMigrationService.c +++ b/src/Virt_VSMigrationService.c @@ -150,6 +150,7 @@ static CMPIStatus get_migration_uri(CMPIInstance *msd, static char *dest_uri(const char *cn, const char *dest, + const char *dest_params, uint16_t transport) { const char *prefix; @@ -157,6 +158,7 @@ static char *dest_uri(const char *cn, const char *param = ""; char *uri = NULL; int rc; + int param_labeled = 0; if (STARTS_WITH(cn, "Xen")) prefix = "xen"; @@ -197,16 +199,79 @@ static char *dest_uri(const char *cn, goto out; } - if (!STREQC(param, "")) + if (!STREQC(param, "")) { rc = asprintf(&uri, "%s/%s", uri, param); + param_labeled = 1; + } - if (rc == -1) + if (rc == -1) { uri = NULL; + goto out; + } + if (dest_params) { + if (param_labeled == 0) { + rc = asprintf(&uri, "%s?%s", uri, dest_params); + } else { + /* ? is already added */ + rc = asprintf(&uri, "%s%s", uri, dest_params); + } + if (rc == -1) { + uri = NULL; + goto out; + } + } out: return uri; } +/* libvirt need private key specified must be placed in a directory owned by + root, because libvirt-cim now runs as root. So here the key would be copied, + up layer need to delete that key after migration. This method could allow + libvirt-cim borrow a non-root ssh private key, instead of using root's private + key, avoid security risk. */ +static int ssh_key_cp(const char *src, const char *dest) +{ + char *cmd = NULL; + int rc; + int ret = 0; + FILE *stream = NULL; + char buf[256]; + + rc = asprintf(&cmd, "cp -f %s %s", src, dest); + if (rc == -1) { + cmd = NULL; + ret = -1; + goto out; + } + + CU_DEBUG("executing system cmd [%s].", cmd); + /* Todo: We need a better popen, currently stdout have been closed + and SIGCHILD is handled by tog-pegasus, so fgets always got NULL + making error detection not possible. */ + stream = popen(cmd, "r"); + if (stream == NULL) { + CU_DEBUG("Failed to open pipe to run command"); + ret = -2; + goto out; + } + usleep(10000); + + buf[255] = 0; + while (fgets(buf, sizeof(buf), stream) != NULL) { + CU_DEBUG("Exception got: %s.", buf); + ret = -3; + goto out; + } + + out: + if (stream != NULL) { + pclose(stream); + } + free(cmd); + return ret; +} + static CMPIStatus get_msd_values(const CMPIObjectPath *ref, const char *destination, const CMPIArgs *argsin, @@ -217,6 +282,11 @@ static CMPIStatus get_msd_values(const CMPIObjectPath *ref, CMPIInstance *msd; uint16_t uri_type; char *uri = NULL; + const char *ssh_key = NULL; + char *dest_params = NULL; + int ret; + + cu_get_str_arg(argsin, "SSH_Key", &ssh_key); s = get_msd(ref, argsin, &msd); if (s.rc != CMPI_RC_OK) @@ -230,7 +300,38 @@ static CMPIStatus get_msd_values(const CMPIObjectPath *ref, if (s.rc != CMPI_RC_OK) goto out; - uri = dest_uri(CLASSNAME(ref), destination, uri_type); + if (ssh_key) { + CU_DEBUG("Trying migrate with specified ssh key file '%s'.", + ssh_key); + const char * tmp_keyfile = get_mig_ssh_tmp_key(); + const char * final_keyfile = NULL; + const char *errstr; + if (tmp_keyfile) { + CU_DEBUG("Copying ssh keys src '%s', dest '%s'.", + ssh_key, tmp_keyfile); + ret = ssh_key_cp(ssh_key, tmp_keyfile); + if (ret < 0) { + errstr = "Failed to copy ssh key files"; + CU_DEBUG("%s", errstr); + cu_statusf(_BROKER, &s, CMPI_RC_ERR_FAILED, + "%s", errstr); + goto out; + } + final_keyfile = tmp_keyfile; + } else { + final_keyfile = ssh_key; + CU_DEBUG("Skip key copy."); + } + CU_DEBUG("Generating param string with key '%s'.", + final_keyfile); + ret = asprintf(&dest_params, "keyfile=%s", final_keyfile); + if (ret < 0) { + CU_DEBUG("Failed in generating param string."); + goto out; + } + } + + uri = dest_uri(CLASSNAME(ref), destination, dest_params, uri_type); if (uri == NULL) { cu_statusf(_BROKER, &s, CMPI_RC_ERR_FAILED, @@ -238,6 +339,7 @@ static CMPIStatus get_msd_values(const CMPIObjectPath *ref, goto out; } + CU_DEBUG("Migrate tring to connect remote host with uri %s.", uri); *conn = virConnectOpen(uri); if (*conn == NULL) { CU_DEBUG("Failed to connect to remote host (%s)", uri); @@ -249,7 +351,7 @@ static CMPIStatus get_msd_values(const CMPIObjectPath *ref, out: free(uri); - + free(dest_params); return s; } @@ -1537,7 +1639,7 @@ static CMPIStatus migrate_vs_host(CMPIMethodMI *self, const char *dhost = NULL; CMPIObjectPath *system; const char *name = NULL; - + cu_get_str_arg(argsin, "DestinationHost", &dhost); cu_get_ref_arg(argsin, "ComputerSystem", &system); @@ -1608,6 +1710,7 @@ static struct method_handler vsimth = { .handler = vs_migratable_host, .args = {{"ComputerSystem", CMPI_ref, false}, {"DestinationHost", CMPI_string, false}, + {"SSH_Key", CMPI_string, true}, {"MigrationSettingData", CMPI_instance, true}, {"NewSystemSettingData", CMPI_instance, true}, {"NewResourceSettingData", CMPI_instanceA, true}, @@ -1632,6 +1735,7 @@ static struct method_handler mvsth = { .handler = migrate_vs_host, .args = {{"ComputerSystem", CMPI_ref, false}, {"DestinationHost", CMPI_string, false}, + {"SSH_Key", CMPI_string, true}, {"MigrationSettingData", CMPI_instance, true}, {"NewSystemSettingData", CMPI_instance, true}, {"NewResourceSettingData", CMPI_instanceA, true}, -- 1.7.1
participants (1)
-
Wenchao Xia