X-Git-Url: https://git.octo.it/?p=collectd.git;a=blobdiff_plain;f=src%2Fwrite_stackdriver.c;h=dfa1d7c0785a9db6ff5207f24aaa1b3728ae0b54;hp=bb24e23ea023b502a6b5dabbd488e6f91480c09b;hb=48efd3deb4c9139fd060ff3d289896e9031bcc7c;hpb=1c94793facfbcdade4cf5a9b3a4a65a1cf1a7d48 diff --git a/src/write_stackdriver.c b/src/write_stackdriver.c index bb24e23e..dfa1d7c0 100644 --- a/src/write_stackdriver.c +++ b/src/write_stackdriver.c @@ -22,15 +22,16 @@ #include "collectd.h" -#include "common.h" #include "configfile.h" #include "plugin.h" -#include "utils_format_stackdriver.h" -#include "utils_gce.h" -#include "utils_oauth.h" +#include "utils/common/common.h" +#include "utils/format_stackdriver/format_stackdriver.h" +#include "utils/gce/gce.h" +#include "utils/oauth/oauth.h" #include #include +#include /* * Private variables @@ -108,150 +109,195 @@ static char *wg_get_authorization_header(wg_callback_t *cb) { /* {{{ */ return NULL; } - status = snprintf(authorization_header, sizeof(authorization_header), - "Authorization: Bearer %s", access_token); + status = ssnprintf(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; +typedef struct { + int code; + char *message; +} api_error_t; - struct curl_slist *headers = NULL; - headers = curl_slist_append(headers, "Content-Type: application/json"); - headers = curl_slist_append(headers, authorization_header); +static api_error_t *parse_api_error(char const *body) { + char errbuf[1024]; + yajl_val root = yajl_tree_parse(body, errbuf, sizeof(errbuf)); + if (root == NULL) { + ERROR("write_stackdriver plugin: yajl_tree_parse failed: %s", errbuf); + return NULL; + } - CURL *curl = curl_easy_init(); - if (!curl) { - ERROR("write_stackdriver plugin: curl_easy_init failed."); - curl_slist_free_all(headers); - sfree(authorization_header); - return -1; + api_error_t *err = calloc(1, sizeof(*err)); + if (err == NULL) { + ERROR("write_stackdriver plugin: calloc failed"); + yajl_tree_free(root); + return NULL; } - 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); + yajl_val code = yajl_tree_get(root, (char const *[]){"error", "code", NULL}, + yajl_t_number); + if (YAJL_IS_INTEGER(code)) { + err->code = YAJL_GET_INTEGER(code); + } - status = curl_easy_perform(curl); - if (status != CURLE_OK) { - ERROR( - "write_stackdriver 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; + yajl_val message = yajl_tree_get( + root, (char const *[]){"error", "message", NULL}, yajl_t_string); + if (YAJL_IS_STRING(message)) { + char const *m = YAJL_GET_STRING(message); + if (m != NULL) { + err->message = strdup(m); + } } - long http_code = 0; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - if ((http_code < 200) || (http_code >= 300)) { - ERROR("write_stackdriver plugin: POST request to %s failed: HTTP error %ld", - final_url, http_code); - INFO("write_stackdriver plugin: Server replied: %s", res.memory); - sfree(res.memory); - curl_easy_cleanup(curl); - curl_slist_free_all(headers); - sfree(authorization_header); - return -1; + return err; +} + +static char *api_error_string(api_error_t *err, char *buffer, + size_t buffer_size) { + if (err == NULL) { + strncpy(buffer, "Unknown error (API error is NULL)", buffer_size); + } else if (err->message == NULL) { + ssnprintf(buffer, buffer_size, "API error %d", err->code); + } else { + ssnprintf(buffer, buffer_size, "API error %d: %s", err->code, err->message); } - sfree(res.memory); - curl_easy_cleanup(curl); - curl_slist_free_all(headers); - sfree(authorization_header); - return 0; -} /* }}} int wg_call_metricdescriptor_create */ + return buffer; +} +#define API_ERROR_STRING(err) api_error_string(err, (char[1024]){""}, 1024) -static void wg_reset_buffer(wg_callback_t *cb) /* {{{ */ -{ - cb->timeseries_count = 0; - cb->send_buffer_init_time = cdtime(); -} /* }}} wg_reset_buffer */ +// do_post does a HTTP POST request, assuming a JSON payload and using OAuth +// authentication. Returns -1 on error and the HTTP status code otherwise. +// ret_content, if not NULL, will contain the server's response. +// If ret_content is provided and the server responds with a 4xx or 5xx error, +// an appropriate message will be logged. +static int do_post(wg_callback_t *cb, char const *url, void const *payload, + wg_memory_t *ret_content) { + if (cb->curl == NULL) { + cb->curl = curl_easy_init(); + if (cb->curl == NULL) { + ERROR("write_stackdriver plugin: curl_easy_init() failed"); + return -1; + } -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; + curl_easy_setopt(cb->curl, CURLOPT_ERRORBUFFER, cb->curl_errbuf); + curl_easy_setopt(cb->curl, CURLOPT_NOSIGNAL, 1L); + } - char *authorization_header = wg_get_authorization_header(cb); - if (authorization_header == NULL) - return -1; + curl_easy_setopt(cb->curl, CURLOPT_POST, 1L); + curl_easy_setopt(cb->curl, CURLOPT_URL, url); - struct curl_slist *headers = NULL; - headers = curl_slist_append(headers, authorization_header); - headers = curl_slist_append(headers, "Content-Type: application/json"); + long timeout_ms = 2 * CDTIME_T_TO_MS(plugin_get_interval()); + if (timeout_ms < 10000) { + timeout_ms = 10000; + } + curl_easy_setopt(cb->curl, CURLOPT_TIMEOUT_MS, timeout_ms); - curl_easy_setopt(cb->curl, CURLOPT_URL, final_url); + /* header */ + char *auth_header = wg_get_authorization_header(cb); + if (auth_header == NULL) { + ERROR("write_stackdriver plugin: getting access token failed with"); + return -1; + } + + struct curl_slist *headers = + curl_slist_append(NULL, "Content-Type: application/json"); + headers = curl_slist_append(headers, auth_header); 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); + curl_easy_setopt(cb->curl, CURLOPT_WRITEFUNCTION, + ret_content ? wg_write_memory_cb : NULL); + curl_easy_setopt(cb->curl, CURLOPT_WRITEDATA, ret_content); + + int status = curl_easy_perform(cb->curl); + + /* clean up that has to happen in any case */ + curl_slist_free_all(headers); + sfree(auth_header); + curl_easy_setopt(cb->curl, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(cb->curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(cb->curl, CURLOPT_WRITEDATA, NULL); - status = curl_easy_perform(cb->curl); if (status != CURLE_OK) { - ERROR( - "write_stackdriver plugin: curl_easy_perform failed with status %d: %s", - status, cb->curl_errbuf); - sfree(res.memory); - curl_slist_free_all(headers); - sfree(authorization_header); + ERROR("write_stackdriver plugin: POST %s failed: %s", url, cb->curl_errbuf); + if (ret_content != NULL) { + sfree(ret_content->memory); + ret_content->size = 0; + } 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_stackdriver plugin: POST request to %s failed: HTTP error %ld", - final_url, http_code); - INFO("write_stackdriver plugin: Server replied: %s", res.memory); - sfree(res.memory); - curl_slist_free_all(headers); - sfree(authorization_header); + + if (ret_content != NULL) { + if ((http_code >= 400) && (http_code < 500)) { + ERROR("write_stackdriver plugin: POST %s: %s", url, + API_ERROR_STRING(parse_api_error(ret_content->memory))); + } else if (http_code >= 500) { + WARNING("write_stackdriver plugin: POST %s: %s", url, + ret_content->memory); + } + } + + return (int)http_code; +} /* int do_post */ + +static int wg_call_metricdescriptor_create(wg_callback_t *cb, + char const *payload) { + char url[1024]; + ssnprintf(url, sizeof(url), "%s/projects/%s/metricDescriptors", cb->url, + cb->project); + wg_memory_t response = {0}; + + int status = do_post(cb, url, payload, &response); + if (status == -1) { + ERROR("write_stackdriver plugin: POST %s failed", url); return -1; } + sfree(response.memory); - sfree(res.memory); - curl_slist_free_all(headers); - sfree(authorization_header); - return status; -} /* }}} wg_call_timeseries_write */ + if (status != 200) { + ERROR("write_stackdriver plugin: POST %s: unexpected response code: got " + "%d, want 200", + url, status); + return -1; + } + return 0; +} /* int wg_call_metricdescriptor_create */ + +static int wg_call_timeseries_write(wg_callback_t *cb, char const *payload) { + char url[1024]; + ssnprintf(url, sizeof(url), "%s/projects/%s/timeSeries", cb->url, + cb->project); + wg_memory_t response = {0}; + + int status = do_post(cb, url, payload, &response); + if (status == -1) { + ERROR("write_stackdriver plugin: POST %s failed", url); + return -1; + } + sfree(response.memory); + + if (status != 200) { + ERROR("write_stackdriver plugin: POST %s: unexpected response code: got " + "%d, want 200", + url, status); + return -1; + } + return 0; +} /* int wg_call_timeseries_write */ + +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_callback_init(wg_callback_t *cb) /* {{{ */ { @@ -296,11 +342,6 @@ static int wg_flush_nolock(cdtime_t timeout, wg_callback_t *cb) /* {{{ */ char *payload = sd_output_reset(cb->formatter); int status = wg_call_timeseries_write(cb, payload); - if (status != 0) { - ERROR("write_stackdriver plugin: Sending buffer failed with status %d.", - status); - } - wg_reset_buffer(cb); return status; } /* }}} wg_flush_nolock */ @@ -553,9 +594,9 @@ static int wg_config(oconfig_item_t *ci) /* {{{ */ if (cb->project == NULL) { cb->project = cfg.project_id; - INFO( - "write_stackdriver plugin: Automatically detected project ID: \"%s\"", - cb->project); + INFO("write_stackdriver plugin: Automatically detected project ID: " + "\"%s\"", + cb->project); } else { sfree(cfg.project_id); } @@ -567,9 +608,9 @@ static int wg_config(oconfig_item_t *ci) /* {{{ */ if (cb->project == NULL) { cb->project = cfg.project_id; - INFO( - "write_stackdriver plugin: Automatically detected project ID: \"%s\"", - cb->project); + INFO("write_stackdriver plugin: Automatically detected project ID: " + "\"%s\"", + cb->project); } else { sfree(cfg.project_id); }