From abd1972f9db972cfda12b61e2998d1300b1c1a0b Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Sun, 21 Jun 2015 17:59:46 +0200 Subject: [PATCH] write_gcm plugin: New plugin for Google Cloud Monitoring. --- Makefile.am | 9 + configure.ac | 3 + src/collectd.conf.in | 11 + src/collectd.conf.pod | 77 ++++++ src/write_gcm.c | 637 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 737 insertions(+) create mode 100644 src/write_gcm.c diff --git a/Makefile.am b/Makefile.am index aa8c59f9..07ecafe5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1976,6 +1976,15 @@ write_http_la_LDFLAGS = $(PLUGIN_LDFLAGS) write_http_la_LIBADD = libformat_json.la $(BUILD_WITH_LIBCURL_LIBS) endif +if BUILD_PLUGIN_WRITE_GCM +pkglib_LTLIBRARIES += write_gcm.la +write_gcm_la_SOURCES = src/write_gcm.c +write_gcm_la_LDFLAGS = $(PLUGIN_LDFLAGS) +write_gcm_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBCURL_CFLAGS) +write_gcm_la_LIBADD = libformat_gcm.la libgce.la liboauth.la \ + $(BUILD_WITH_LIBCURL_LIBS) +endif + if BUILD_PLUGIN_WRITE_KAFKA pkglib_LTLIBRARIES += write_kafka.la write_kafka_la_SOURCES = src/write_kafka.c diff --git a/configure.ac b/configure.ac index cae848f9..b32af6e2 100644 --- a/configure.ac +++ b/configure.ac @@ -6561,6 +6561,7 @@ fi if test "x$with_libcurl" = "xyes" && test "x$with_libyajl" = "xyes"; then plugin_curl_json="yes" + plugin_write_gcm="yes" fi if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"; then @@ -6895,6 +6896,7 @@ AC_PLUGIN([vserver], [$plugin_vserver], [Linux VServer stati AC_PLUGIN([wireless], [$plugin_wireless], [Wireless statistics]) AC_PLUGIN([write_graphite], [yes], [Graphite / Carbon output plugin]) AC_PLUGIN([write_http], [$with_libcurl], [HTTP output plugin]) +AC_PLUGIN([write_gcm], [$plugin_write_gcm], [Google cloud monitoring output plugin]) AC_PLUGIN([write_kafka], [$with_librdkafka], [Kafka output plugin]) AC_PLUGIN([write_log], [yes], [Log output plugin]) AC_PLUGIN([write_mongodb], [$with_libmongoc], [MongoDB output plugin]) @@ -7317,6 +7319,7 @@ AC_MSG_RESULT([ vserver . . . . . . . $enable_vserver]) AC_MSG_RESULT([ wireless . . . . . . $enable_wireless]) AC_MSG_RESULT([ write_graphite . . . $enable_write_graphite]) AC_MSG_RESULT([ write_http . . . . . $enable_write_http]) +AC_MSG_RESULT([ write_gcm . . . . . . $enable_write_gcm]) AC_MSG_RESULT([ write_kafka . . . . . $enable_write_kafka]) AC_MSG_RESULT([ write_log . . . . . . $enable_write_log]) AC_MSG_RESULT([ write_mongodb . . . . $enable_write_mongodb]) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index af652145..8880904c 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -213,6 +213,7 @@ #@BUILD_PLUGIN_VMEM_TRUE@LoadPlugin vmem #@BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver #@BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless +#@BUILD_PLUGIN_WRITE_GCM_TRUE@LoadPlugin write_gcm #@BUILD_PLUGIN_WRITE_GRAPHITE_TRUE@LoadPlugin write_graphite #@BUILD_PLUGIN_WRITE_HTTP_TRUE@LoadPlugin write_http #@BUILD_PLUGIN_WRITE_KAFKA_TRUE@LoadPlugin write_kafka @@ -1674,6 +1675,16 @@ # Verbose false # +# +# Project "gcp-project-id" +# CredentialFile "/path/to/gcp-project-id-12345.json" +# Email "123456789012@developer.gserviceaccount.com" +# +# project_id "gcp-project-id" +# +# Url "https://www.googleapis.com/cloudmonitoring/v2beta2" +# + # # # Host "localhost" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 6e6d6eaf..667e488e 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -9337,6 +9337,83 @@ traffic (e.Eg. due to headers and retransmission). If you want to collect on-wire traffic you could, for example, use the logging facilities of iptables to feed data for the guest IPs into the iptables plugin. +=head2 Plugin C + +The C plugin writes metrics to the I (GCM) +service. + +This plugin supports two authentication methods: When configured, credentials +are read from the JSON credentials file specified with B. +Alternatively, when running on +I (GCE), an I token is retrieved from the +I and used to authenticate to GCM. + +B + + + Project "123456789012" + + +=over 4 + +=item B I + +Path to a JSON credentials file holding the credentials for a GCP service +account. + +If not specified, I. If running on GCE, +B may be set to chose a different service account associated with the +instance. + +=item B I + +The I or the I of the I. The +I is a string identifying the GCP project, which you can chose +freely when creating a new project. The I is a 12-digit decimal +number. You can look up both on the I. + +This setting is optional. If not set, the project ID is read from the +credentials file or determined from the GCE's metadata service. + +=item B I + +Email address of an GCE I. This setting is only effective when +running on GCE and using I (see +B above). + +=item B I + +Configures the I to use when storing metrics. This option +takes a I and arbitrary string options which are used as labels. + +On GCE, defaults to the equivalent of this config: + + + project_id "${meta/project/project-id}" + instance_id "${meta/instance/id}" + zone "${meta/instance/zone}" + + +Where C<${meta/...}> are values read from the meta data service. + +When not running on GCE, defaults to the equivalent of this config: + + + project_id "${Project}" + + +Where C<${Project}> refers to the B option. + +See L for more information +on I. + +=item B I + +URL of the I API. Defaults to +C. + +=back + =head2 Plugin C The C plugin writes data to I, an open-source metrics diff --git a/src/write_gcm.c b/src/write_gcm.c new file mode 100644 index 00000000..8ab1eec4 --- /dev/null +++ b/src/write_gcm.c @@ -0,0 +1,637 @@ +/** + * collectd - src/write_gcm.c + * ISC license + * + * Copyright (C) 2017 Florian Forster + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Florian Forster + **/ + +#include "collectd.h" + +#include "common.h" +#include "configfile.h" +#include "plugin.h" +#include "utils_format_gcm.h" +#include "utils_gce.h" +#include "utils_oauth.h" + +#include +#include + +/* + * Private variables + */ +#ifndef GCM_API_URL +#define GCM_API_URL "https://monitoring.googleapis.com/v3" +#endif + +#ifndef MONITORING_SCOPE +#define MONITORING_SCOPE "https://www.googleapis.com/auth/monitoring" +#endif + +struct wg_callback_s { + /* config */ + char *email; + char *project; + char *url; + gcm_resource_t *resource; + + /* runtime */ + oauth_t *auth; + gcm_output_t *formatter; + CURL *curl; + char curl_errbuf[CURL_ERROR_SIZE]; + /* used by flush */ + size_t timeseries_count; + cdtime_t send_buffer_init_time; + + pthread_mutex_t lock; +}; +typedef struct wg_callback_s wg_callback_t; + +struct wg_memory_s { + char *memory; + size_t size; +}; +typedef struct wg_memory_s wg_memory_t; + +static size_t wg_write_memory_cb(void *contents, size_t size, + size_t nmemb, /* {{{ */ + void *userp) { + size_t realsize = size * nmemb; + wg_memory_t *mem = (wg_memory_t *)userp; + + if (0x7FFFFFF0 < mem->size || 0x7FFFFFF0 - mem->size < realsize) { + ERROR("integer overflow"); + return 0; + } + + mem->memory = (char *)realloc((void *)mem->memory, mem->size + realsize + 1); + if (mem->memory == NULL) { + /* out of memory! */ + ERROR("wg_write_memory_cb: not enough memory (realloc returned NULL)"); + return 0; + } + + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + return realsize; +} /* }}} size_t wg_write_memory_cb */ + +static char *wg_get_authorization_header(wg_callback_t *cb) { /* {{{ */ + int status = 0; + char access_token[256]; + char authorization_header[256]; + + assert((cb->auth != NULL) || gce_check()); + if (cb->auth != NULL) + status = oauth_access_token(cb->auth, access_token, sizeof(access_token)); + else + status = gce_access_token(cb->email, access_token, sizeof(access_token)); + if (status != 0) { + ERROR("write_gcm plugin: Failed to get access token"); + return NULL; + } + + status = snprintf(authorization_header, sizeof(authorization_header), + "Authorization: Bearer %s", access_token); + if ((status < 1) || ((size_t)status >= sizeof(authorization_header))) + return NULL; + + return strdup(authorization_header); +} /* }}} char *wg_get_authorization_header */ + +static int wg_call_metricdescriptor_create(wg_callback_t *cb, + char const *payload) { + /* {{{ */ + char final_url[1024]; + int status = + snprintf(final_url, sizeof(final_url), "%s/projects/%s/metricDescriptors", + cb->url, cb->project); + if ((status < 1) || ((size_t)status >= sizeof(final_url))) + return -1; + + char *authorization_header = wg_get_authorization_header(cb); + if (authorization_header == NULL) + return -1; + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, authorization_header); + + CURL *curl = curl_easy_init(); + if (!curl) { + ERROR("write_gcm plugin: curl_easy_init failed."); + curl_slist_free_all(headers); + sfree(authorization_header); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(cb->curl, CURLOPT_USERAGENT, + PACKAGE_NAME "/" PACKAGE_VERSION); + char curl_errbuf[CURL_ERROR_SIZE]; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf); + curl_easy_setopt(curl, CURLOPT_URL, final_url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); + + wg_memory_t res = { + .memory = NULL, .size = 0, + }; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wg_write_memory_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res); + + status = curl_easy_perform(curl); + if (status != CURLE_OK) { + ERROR("write_gcm plugin: curl_easy_perform failed with status %d: %s", + status, curl_errbuf); + sfree(res.memory); + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + sfree(authorization_header); + return -1; + } + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if ((http_code < 200) || (http_code >= 300)) { + ERROR("write_gcm plugin: POST request to %s failed: HTTP error %ld", + final_url, http_code); + INFO("write_gcm plugin: Server replied: %s", res.memory); + sfree(res.memory); + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + sfree(authorization_header); + return -1; + } + + sfree(res.memory); + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + sfree(authorization_header); + return 0; +} /* }}} int wg_call_metricdescriptor_create */ + +static void wg_reset_buffer(wg_callback_t *cb) /* {{{ */ +{ + cb->timeseries_count = 0; + cb->send_buffer_init_time = cdtime(); +} /* }}} wg_reset_buffer */ + +static int wg_call_timeseries_write(wg_callback_t *cb, + char const *payload) /* {{{ */ +{ + char final_url[1024]; + int status = snprintf(final_url, sizeof(final_url), + "%s/projects/%s/timeSeries", cb->url, cb->project); + if ((status < 1) || ((size_t)status >= sizeof(final_url))) + return -1; + + char *authorization_header = wg_get_authorization_header(cb); + if (authorization_header == NULL) + return -1; + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, authorization_header); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + curl_easy_setopt(cb->curl, CURLOPT_URL, final_url); + curl_easy_setopt(cb->curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(cb->curl, CURLOPT_POST, 1L); + curl_easy_setopt(cb->curl, CURLOPT_POSTFIELDS, payload); + + wg_memory_t res = { + .memory = NULL, .size = 0, + }; + curl_easy_setopt(cb->curl, CURLOPT_WRITEFUNCTION, wg_write_memory_cb); + curl_easy_setopt(cb->curl, CURLOPT_WRITEDATA, &res); + + status = curl_easy_perform(cb->curl); + if (status != CURLE_OK) { + ERROR("write_gcm plugin: curl_easy_perform failed with status %d: %s", + status, cb->curl_errbuf); + sfree(res.memory); + curl_slist_free_all(headers); + sfree(authorization_header); + return -1; + } + + long http_code = 0; + curl_easy_getinfo(cb->curl, CURLINFO_RESPONSE_CODE, &http_code); + if ((http_code < 200) || (http_code >= 300)) { + ERROR("write_gcm plugin: POST request to %s failed: HTTP error %ld", + final_url, http_code); + INFO("write_gcm plugin: Server replied: %s", res.memory); + sfree(res.memory); + curl_slist_free_all(headers); + sfree(authorization_header); + return -1; + } + + sfree(res.memory); + curl_slist_free_all(headers); + sfree(authorization_header); + return status; +} /* }}} wg_call_timeseries_write */ + +static int wg_callback_init(wg_callback_t *cb) /* {{{ */ +{ + if (cb->curl != NULL) + return 0; + + cb->formatter = gcm_output_create(cb->resource); + if (cb->formatter == NULL) { + ERROR("write_gcm plugin: gcm_output_create failed."); + return -1; + } + + cb->curl = curl_easy_init(); + if (cb->curl == NULL) { + ERROR("write_gcm plugin: curl_easy_init failed."); + return -1; + } + + curl_easy_setopt(cb->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(cb->curl, CURLOPT_USERAGENT, + PACKAGE_NAME "/" PACKAGE_VERSION); + curl_easy_setopt(cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf); + wg_reset_buffer(cb); + + return 0; +} /* }}} int wg_callback_init */ + +static int wg_flush_nolock(cdtime_t timeout, wg_callback_t *cb) /* {{{ */ +{ + if (cb->timeseries_count == 0) { + cb->send_buffer_init_time = cdtime(); + return 0; + } + + /* timeout == 0 => flush unconditionally */ + if (timeout > 0) { + cdtime_t now = cdtime(); + + if ((cb->send_buffer_init_time + timeout) > now) + return 0; + } + + char *payload = gcm_output_reset(cb->formatter); + int status = wg_call_timeseries_write(cb, payload); + if (status != 0) { + ERROR("write_gcm plugin: Sending buffer failed with status %d.", status); + } + + wg_reset_buffer(cb); + return status; +} /* }}} wg_flush_nolock */ + +static int wg_flush(cdtime_t timeout, /* {{{ */ + const char *identifier __attribute__((unused)), + user_data_t *user_data) { + wg_callback_t *cb; + int status; + + if (user_data == NULL) + return -EINVAL; + + cb = user_data->data; + + pthread_mutex_lock(&cb->lock); + + if (cb->curl == NULL) { + status = wg_callback_init(cb); + if (status != 0) { + ERROR("write_gcm plugin: wg_callback_init failed."); + pthread_mutex_unlock(&cb->lock); + return -1; + } + } + + status = wg_flush_nolock(timeout, cb); + pthread_mutex_unlock(&cb->lock); + + return status; +} /* }}} int wg_flush */ + +static void wg_callback_free(void *data) /* {{{ */ +{ + wg_callback_t *cb = data; + if (cb == NULL) + return; + + gcm_output_destroy(cb->formatter); + cb->formatter = NULL; + + sfree(cb->email); + sfree(cb->project); + sfree(cb->url); + + oauth_destroy(cb->auth); + if (cb->curl) { + curl_easy_cleanup(cb->curl); + } + + sfree(cb); +} /* }}} void wg_callback_free */ + +static int wg_metric_descriptors_create(wg_callback_t *cb, const data_set_t *ds, + const value_list_t *vl) { + /* {{{ */ + for (size_t i = 0; i < ds->ds_num; i++) { + char buffer[4096]; + + int status = + gcm_format_metric_descriptor(buffer, sizeof(buffer), ds, vl, i); + if (status != 0) { + ERROR("write_gcm plugin: gcm_format_metric_descriptor failed with status " + "%d", + status); + return status; + } + + status = wg_call_metricdescriptor_create(cb, buffer); + if (status != 0) { + ERROR("write_gcm plugin: wg_call_metricdescriptor_create failed with " + "status %d", + status); + return status; + } + } + + return gcm_output_register_metric(cb->formatter, ds, vl); +} /* }}} int wg_metric_descriptors_create */ + +static int wg_write(const data_set_t *ds, const value_list_t *vl, /* {{{ */ + user_data_t *user_data) { + wg_callback_t *cb = user_data->data; + if (cb == NULL) + return EINVAL; + + pthread_mutex_lock(&cb->lock); + + if (cb->curl == NULL) { + int status = wg_callback_init(cb); + if (status != 0) { + ERROR("write_gcm plugin: wg_callback_init failed."); + pthread_mutex_unlock(&cb->lock); + return status; + } + } + + int status; + while (42) { + status = gcm_output_add(cb->formatter, ds, vl); + if (status == 0) { /* success */ + break; + } else if (status == ENOBUFS) { /* success, flush */ + wg_flush_nolock(0, cb); + status = 0; + break; + } else if (status == EEXIST) { + /* metric already in the buffer; flush and retry */ + wg_flush_nolock(0, cb); + continue; + } else if (status == ENOENT) { + /* new metric, create metric descriptor first */ + status = wg_metric_descriptors_create(cb, ds, vl); + if (status != 0) { + break; + } + continue; + } else { + break; + } + } + + if (status == 0) { + cb->timeseries_count++; + } + + pthread_mutex_unlock(&cb->lock); + return status; +} /* }}} int wg_write */ + +static void wg_check_scope(char const *email) /* {{{ */ +{ + char *scope = gce_scope(email); + if (scope == NULL) { + WARNING("write_gcm plugin: Unable to determine scope of this instance."); + return; + } + + if (strstr(scope, MONITORING_SCOPE) == NULL) { + size_t scope_len; + + /* Strip trailing newline characers for printing. */ + scope_len = strlen(scope); + while ((scope_len > 0) && (iscntrl((int)scope[scope_len - 1]))) + scope[--scope_len] = 0; + + WARNING("write_gcm plugin: The determined scope of this instance " + "(\"%s\") does not contain the monitoring scope (\"%s\"). You need " + "to add this scope to the list of scopes passed to gcutil with " + "--service_account_scopes when creating the instance. " + "Alternatively, to use this plugin on an instance which does not " + "have this scope, use a Service Account.", + scope, MONITORING_SCOPE); + } + + sfree(scope); +} /* }}} void wg_check_scope */ + +static int wg_config_resource(oconfig_item_t *ci, wg_callback_t *cb) /* {{{ */ +{ + if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) { + ERROR("write_gcm plugin: The \"%s\" option requires exactly one string " + "argument.", + ci->key); + return EINVAL; + } + char *resource_type = ci->values[0].value.string; + + if (cb->resource != NULL) { + gcm_resource_destroy(cb->resource); + } + + cb->resource = gcm_resource_create(resource_type); + if (cb->resource == NULL) { + ERROR("write_gcm plugin: gcm_resource_create(\"%s\") failed.", + resource_type); + return ENOMEM; + } + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + + if ((child->values_num != 1) || + (child->values[0].type != OCONFIG_TYPE_STRING)) { + ERROR("write_gcm plugin: Resource labels must have exactly one string " + "value. Ignoring label \"%s\".", + child->key); + continue; + } + + gcm_resource_add_label(cb->resource, child->key, + child->values[0].value.string); + } + + return 0; +} /* }}} int wg_config_resource */ + +static int wg_config(oconfig_item_t *ci) /* {{{ */ +{ + if (ci == NULL) { + return EINVAL; + } + + wg_callback_t *cb = calloc(1, sizeof(*cb)); + if (cb == NULL) { + ERROR("write_gcm plugin: calloc failed."); + return ENOMEM; + } + cb->url = strdup(GCM_API_URL); + pthread_mutex_init(&cb->lock, /* attr = */ NULL); + + char *credential_file = NULL; + + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + if (strcasecmp("Project", child->key) == 0) + cf_util_get_string(child, &cb->project); + else if (strcasecmp("Email", child->key) == 0) + cf_util_get_string(child, &cb->email); + else if (strcasecmp("Url", child->key) == 0) + cf_util_get_string(child, &cb->url); + else if (strcasecmp("CredentialFile", child->key) == 0) + cf_util_get_string(child, &credential_file); + else if (strcasecmp("Resource", child->key) == 0) + wg_config_resource(child, cb); + else { + ERROR("write_gcm plugin: Invalid configuration option: %s.", child->key); + wg_callback_free(cb); + return EINVAL; + } + } + + /* Set up authentication */ + /* Option 1: Credentials file given => use service account */ + if (credential_file != NULL) { + oauth_google_t cfg = + oauth_create_google_file(credential_file, MONITORING_SCOPE); + if (cfg.oauth == NULL) { + ERROR("write_gcm plugin: oauth_create_google_file failed"); + wg_callback_free(cb); + return EINVAL; + } + cb->auth = cfg.oauth; + + if (cb->project == NULL) { + cb->project = cfg.project_id; + INFO("write_gcm plugin: Automatically detected project ID: \"%s\"", + cb->project); + } else { + sfree(cfg.project_id); + } + } + /* Option 2: Look for credentials in well-known places */ + if (cb->auth == NULL) { + oauth_google_t cfg = oauth_create_google_default(MONITORING_SCOPE); + cb->auth = cfg.oauth; + + if (cb->project == NULL) { + cb->project = cfg.project_id; + INFO("write_gcm plugin: Automatically detected project ID: \"%s\"", + cb->project); + } else { + sfree(cfg.project_id); + } + } + + if ((cb->auth != NULL) && (cb->email != NULL)) { + NOTICE("write_gcm plugin: A service account email was configured but is " + "not used for authentication because %s used instead.", + (credential_file != NULL) ? "a credential file was" + : "application default credentials were"); + } + + /* Option 3: Running on GCE => use metadata service */ + if ((cb->auth == NULL) && gce_check()) { + wg_check_scope(cb->email); + } else if (cb->auth == NULL) { + ERROR("write_gcm plugin: Unable to determine credentials. Please either " + "specify the \"Credentials\" option or set up Application Default " + "Credentials."); + wg_callback_free(cb); + return EINVAL; + } + + if ((cb->project == NULL) && gce_check()) { + cb->project = gce_project_id(); + } + if (cb->project == NULL) { + ERROR("write_gcm plugin: Unable to determine the project number. " + "Please specify the \"Project\" option manually."); + wg_callback_free(cb); + return EINVAL; + } + + if ((cb->resource == NULL) && gce_check()) { + /* TODO(octo): add error handling */ + cb->resource = gcm_resource_create("gce_instance"); + gcm_resource_add_label(cb->resource, "project_id", gce_project_id()); + gcm_resource_add_label(cb->resource, "instance_id", gce_instance_id()); + gcm_resource_add_label(cb->resource, "zone", gce_zone()); + } + if (cb->resource == NULL) { + /* TODO(octo): add error handling */ + cb->resource = gcm_resource_create("global"); + gcm_resource_add_label(cb->resource, "project_id", cb->project); + } + + DEBUG("write_gcm plugin: Registering write callback with URL %s", cb->url); + assert((cb->auth != NULL) || gce_check()); + + user_data_t user_data = { + .data = cb, + }; + plugin_register_flush("write_gcm", wg_flush, &user_data); + + user_data.free_func = wg_callback_free; + plugin_register_write("write_gcm", wg_write, &user_data); + + return 0; +} /* }}} int wg_config */ + +static int wg_init(void) { + /* {{{ */ + /* Call this while collectd is still single-threaded to avoid + * initialization issues in libgcrypt. */ + curl_global_init(CURL_GLOBAL_SSL); + + return 0; +} /* }}} int wg_init */ + +void module_register(void) /* {{{ */ +{ + plugin_register_complex_config("write_gcm", wg_config); + plugin_register_init("write_gcm", wg_init); +} /* }}} void module_register */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ -- 2.11.0