virt: allow read Hostname from libvirt metadata
authorMehdi Abaakouk <sileht@sileht.net>
Tue, 5 Jun 2018 14:09:30 +0000 (16:09 +0200)
committerMehdi Abaakouk <sileht@sileht.net>
Tue, 27 Nov 2018 09:20:34 +0000 (10:20 +0100)
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
src/collectd.conf.pod
src/virt.c

index b7c1b27..066e5a9 100644 (file)
 #      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
index 03b163e..8960597 100644 (file)
@@ -9287,7 +9287,7 @@ be set to C<var_lib_libvirt_images_image1.qcow2>.
 Setting C<BlockDeviceFormatBasename true> will cause the I<type instance> to be
 set to C<image1.qcow2>.
 
-=item B<HostnameFormat> B<name|uuid|hostname|...>
+=item B<HostnameFormat> B<name|uuid|hostname|metadata...>
 
 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<hostname> means to use the global B<Hostname> 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<PluginInstanceFormat> though.
 
+B<metadata> means use information from guest's metadata. Use
+B<HostnameMetadataNS> and B<HostnameMetadataXPath> to localize this information.
+
 You can also specify combinations of these fields. For example B<name uuid>
 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<name>.
 B<address> 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<PluginInstanceFormat> B<name|uuid|none>
+=item B<PluginInstanceFormat> B<name|uuid|metadata|none>
 
 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<name> means use the guest's name as provided by the hypervisor.
 B<uuid> means use the guest's UUID.
+B<metadata> means use information from guest's metadata.
 
 You can also specify combinations of the B<name> and B<uuid> fields.
 For example B<name uuid> means to concatenate the guest name and UUID
 (with a literal colon character between, thus I<"foo:1234-1234-1234-1234">).
 
+=item B<HostnameMetadataNS> B<string>
+
+When B<metadata> is used in B<HostnameFormat> or B<PluginInstanceFormat>, this
+selects in which metadata namespace we will pick the hostname. The default is
+I<http://openstack.org/xmlns/libvirt/nova/1.0>.
+
+=item B<HostnameMetadataXPath> B<string>
+
+When B<metadata> is used in B<HostnameFormat> or B<PluginInstanceFormat>, this
+describes where the hostname is located in the libvirt metadata. The default is
+I</instance/name/text()>.
+
 =item B<Instances> B<integer>
 
 How many read instances you want to use for this plugin. The default is one,
index c6ac590..d955bcd 100644 (file)
@@ -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]);