From d3eae6bf3df995566cad90c96a1dea49652b5dd2 Mon Sep 17 00:00:00 2001 From: Mehdi Abaakouk Date: Tue, 5 Jun 2018 16:09:30 +0200 Subject: [PATCH] virt: allow read Hostname from libvirt metadata This change allows to read Hostname from Libvirt metadata API. Applications like OVirt or Openstack Nova uses this metadata API to store additional informations like the 'user facing' Guest name. To do so, a new choice 'metadata' is added for 'HostnameFormat' and 'PluginInstanceFormat'. And two new options are also added to localize the hostname into the Guest metadata: HostnameMetadataNS and HostnameMetadataXPath. Closes: #2805 --- src/collectd.conf.in | 2 + src/collectd.conf.pod | 20 ++++++- src/virt.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 160 insertions(+), 6 deletions(-) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index b7c1b278..066e5a93 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -1670,6 +1670,8 @@ # InterfaceDevice "name:device" # IgnoreSelected false # HostnameFormat name +# HostnameMetadataXPath "/instance/name/text()" +# HostnameMetadataNS "http://openstack.org/xmlns/libvirt/nova/1.0" # InterfaceFormat name # PluginInstanceFormat name # Instances 1 diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 03b163ef..8960597d 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -9287,7 +9287,7 @@ be set to C. Setting C will cause the I to be set to C. -=item B B +=item B B When the virt plugin logs data, it sets the hostname of the collected data according to this setting. The default is to use the guest name as provided by @@ -9300,6 +9300,9 @@ B means to use the global B setting, which is probably not useful on its own because all guests will appear to have the same name. This is useful in conjunction with B though. +B means use information from guest's metadata. Use +B and B to localize this information. + You can also specify combinations of these fields. For example B means to concatenate the guest name and UUID (with a literal colon character between, thus I<"foo:1234-1234-1234-1234">). @@ -9318,18 +9321,31 @@ setting B. B
means use the interface's mac address. This is useful since the interface path might change between reboots of a guest or across migrations. -=item B B +=item B B When the virt plugin logs data, it sets the plugin_instance of the collected data according to this setting. The default is to not set the plugin_instance. B means use the guest's name as provided by the hypervisor. B means use the guest's UUID. +B means use information from guest's metadata. You can also specify combinations of the B and B fields. For example B means to concatenate the guest name and UUID (with a literal colon character between, thus I<"foo:1234-1234-1234-1234">). +=item B B + +When B is used in B or B, this +selects in which metadata namespace we will pick the hostname. The default is +I. + +=item B B + +When B is used in B or B, this +describes where the hostname is located in the libvirt metadata. The default is +I. + =item B B How many read instances you want to use for this plugin. The default is one, diff --git a/src/virt.c b/src/virt.c index c6ac5905..d955bcd6 100644 --- a/src/virt.c +++ b/src/virt.c @@ -129,6 +129,8 @@ static const char *config_keys[] = {"Connection", "IgnoreSelected", "HostnameFormat", + "HostnameMetadataNS", + "HostnameMetadataXPath", "InterfaceFormat", "PluginInstanceFormat", @@ -557,20 +559,29 @@ static int nr_instances = NR_INSTANCES_DEFAULT; static struct lv_user_data lv_read_user_data[NR_INSTANCES_MAX]; /* HostnameFormat. */ -#define HF_MAX_FIELDS 3 +#define HF_MAX_FIELDS 4 -enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid }; +enum hf_field { hf_none = 0, hf_hostname, hf_name, hf_uuid, hf_metadata }; static enum hf_field hostname_format[HF_MAX_FIELDS] = {hf_name}; /* PluginInstanceFormat */ -#define PLGINST_MAX_FIELDS 2 +#define PLGINST_MAX_FIELDS 3 -enum plginst_field { plginst_none = 0, plginst_name, plginst_uuid }; +enum plginst_field { + plginst_none = 0, + plginst_name, + plginst_uuid, + plginst_metadata +}; static enum plginst_field plugin_instance_format[PLGINST_MAX_FIELDS] = { plginst_none}; +/* HostnameMetadataNS && HostnameMetadataXPath */ +static char *hm_xpath; +static char *hm_ns; + /* BlockDeviceFormat */ enum bd_field { target, source }; @@ -705,6 +716,95 @@ static int get_block_info(struct lv_block_info *binfo, ERROR(PLUGIN_NAME " plugin: %s failed: %s", (s), err->message); \ } while (0) +char *metadata_get_hostname(virDomainPtr dom) { + const char *xpath_str = NULL; + if (hm_xpath == NULL) + xpath_str = "/instance/name/text()"; + else + xpath_str = hm_xpath; + + const char *namespace = NULL; + if (hm_ns == NULL) { + namespace = "http://openstack.org/xmlns/libvirt/nova/1.0"; + } else { + namespace = hm_ns; + } + + char *metadata_str = virDomainGetMetadata( + dom, VIR_DOMAIN_METADATA_ELEMENT, namespace, VIR_DOMAIN_AFFECT_CURRENT); + if (metadata_str == NULL) { + return NULL; + } else { + char *hostname = NULL; + xmlDocPtr xml_doc = NULL; + xmlXPathContextPtr xpath_ctx = NULL; + xmlXPathObjectPtr xpath_obj = NULL; + xmlNodePtr xml_node = NULL; + + xml_doc = xmlReadDoc((xmlChar *)metadata_str, NULL, NULL, XML_PARSE_NONET); + if (xml_doc == NULL) { + ERROR(PLUGIN_NAME " plugin: xmlReadDoc failed to read metadata"); + goto metadata_end; + } + + xpath_ctx = xmlXPathNewContext(xml_doc); + if (xpath_ctx == NULL) { + ERROR(PLUGIN_NAME " plugin: xmlXPathNewContext(%s) failed for metadata", + metadata_str); + goto metadata_end; + } + xpath_obj = xmlXPathEval((xmlChar *)xpath_str, xpath_ctx); + if (xpath_obj == NULL) { + ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) failed for metadata", + xpath_str); + goto metadata_end; + } + + if (xpath_obj->type != XPATH_NODESET) { + ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unexpected return type %d " + "(wanted %d) for metadata", + xpath_str, xpath_obj->type, XPATH_NODESET); + goto metadata_end; + } + + // TODO(sileht): We can support || operator by looping on nodes here + if (xpath_obj->nodesetval == NULL || xpath_obj->nodesetval->nodeNr != 1) { + WARNING(PLUGIN_NAME " plugin: xmlXPathEval(%s) return nodeset size=%i " + "expected=1 for metadata", + xpath_str, + (xpath_obj->nodesetval == NULL) ? 0 + : xpath_obj->nodesetval->nodeNr); + goto metadata_end; + } + + xml_node = xpath_obj->nodesetval->nodeTab[0]; + if (xml_node->type == XML_TEXT_NODE) { + hostname = strdup((const char *)xml_node->content); + } else if (xml_node->type == XML_ATTRIBUTE_NODE) { + hostname = strdup((const char *)xml_node->children->content); + } else { + ERROR(PLUGIN_NAME " plugin: xmlXPathEval(%s) unsupported node type %d", + xpath_str, xml_node->type); + goto metadata_end; + } + + if (hostname == NULL) { + ERROR(PLUGIN_NAME " plugin: strdup(%s) hostname failed", xpath_str); + goto metadata_end; + } + + metadata_end: + if (xpath_obj) + xmlXPathFreeObject(xpath_obj); + if (xpath_ctx) + xmlXPathFreeContext(xpath_ctx); + if (xml_doc) + xmlFreeDoc(xml_doc); + sfree(metadata_str); + return hostname; + } +} + static void init_value_list(value_list_t *vl, virDomainPtr dom) { const char *name; char uuid[VIR_UUID_STRING_BUFLEN]; @@ -736,6 +836,11 @@ static void init_value_list(value_list_t *vl, virDomainPtr dom) { if (virDomainGetUUIDString(dom, uuid) == 0) SSTRNCAT(vl->host, uuid, sizeof(vl->host)); break; + case hf_metadata: + name = metadata_get_hostname(dom); + if (name) + SSTRNCAT(vl->host, name, sizeof(vl->host)); + break; } } @@ -759,6 +864,11 @@ static void init_value_list(value_list_t *vl, virDomainPtr dom) { if (virDomainGetUUIDString(dom, uuid) == 0) SSTRNCAT(vl->plugin_instance, uuid, sizeof(vl->plugin_instance)); break; + case plginst_metadata: + name = metadata_get_hostname(dom); + if (name) + SSTRNCAT(vl->plugin_instance, name, sizeof(vl->plugin_instance)); + break; } } @@ -1083,6 +1193,28 @@ static int lv_config(const char *key, const char *value) { return 0; } + if (strcasecmp(key, "HostnameMetadataNS") == 0) { + char *tmp = strdup(value); + if (tmp == NULL) { + ERROR(PLUGIN_NAME " plugin: HostnameMetadataNS strdup failed."); + return 1; + } + sfree(hm_ns); + hm_ns = tmp; + return 0; + } + + if (strcasecmp(key, "HostnameMetadataXPath") == 0) { + char *tmp = strdup(value); + if (tmp == NULL) { + ERROR(PLUGIN_NAME " plugin: HostnameMetadataXPath strdup failed."); + return 1; + } + sfree(hm_xpath); + hm_xpath = tmp; + return 0; + } + if (strcasecmp(key, "HostnameFormat") == 0) { char *value_copy = strdup(value); if (value_copy == NULL) { @@ -1105,6 +1237,8 @@ static int lv_config(const char *key, const char *value) { hostname_format[i] = hf_name; else if (strcasecmp(fields[i], "uuid") == 0) hostname_format[i] = hf_uuid; + else if (strcasecmp(fields[i], "metadata") == 0) + hostname_format[i] = hf_metadata; else { ERROR(PLUGIN_NAME " plugin: unknown HostnameFormat field: %s", fields[i]); @@ -1143,6 +1277,8 @@ static int lv_config(const char *key, const char *value) { plugin_instance_format[i] = plginst_name; else if (strcasecmp(fields[i], "uuid") == 0) plugin_instance_format[i] = plginst_uuid; + else if (strcasecmp(fields[i], "metadata") == 0) + plugin_instance_format[i] = plginst_metadata; else { ERROR(PLUGIN_NAME " plugin: unknown PluginInstanceFormat field: %s", fields[i]); -- 2.11.0