summaryrefslogtreecommitdiffstats
path: root/VES5.0/evel/evel-library/code/evel_library/metadata.c
diff options
context:
space:
mode:
Diffstat (limited to 'VES5.0/evel/evel-library/code/evel_library/metadata.c')
-rw-r--r--VES5.0/evel/evel-library/code/evel_library/metadata.c607
1 files changed, 607 insertions, 0 deletions
diff --git a/VES5.0/evel/evel-library/code/evel_library/metadata.c b/VES5.0/evel/evel-library/code/evel_library/metadata.c
new file mode 100644
index 00000000..dfdca052
--- /dev/null
+++ b/VES5.0/evel/evel-library/code/evel_library/metadata.c
@@ -0,0 +1,607 @@
+/**************************************************************************//**
+ * @file
+ * Wrap the OpenStack metadata service.
+ *
+ * License
+ * -------
+ *
+ * Copyright(c) <2016>, AT&T Intellectual Property. All other rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement: This product includes
+ * software developed by the AT&T.
+ * 4. Neither the name of AT&T nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AT&T INTELLECTUAL PROPERTY ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AT&T INTELLECTUAL PROPERTY BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <string.h>
+#include <assert.h>
+#include <malloc.h>
+
+#include <curl/curl.h>
+
+#include "evel.h"
+#include "evel_internal.h"
+#include "jsmn.h"
+#include "metadata.h"
+
+/**************************************************************************//**
+ * URL on the link-local IP address where we can get the metadata in
+ * machine-friendly format.
+ *****************************************************************************/
+static const char * OPENSTACK_METADATA_URL =
+ "http://169.254.169.254/openstack/latest/meta_data.json";
+
+/**************************************************************************//**
+ * How long we're prepared to wait for the metadata service to respond in
+ * seconds.
+ *****************************************************************************/
+static const int OPENSTACK_METADATA_TIMEOUT = 2;
+
+/**************************************************************************//**
+ * Size of fields extracted from metadata service.
+ *****************************************************************************/
+#define MAX_METADATA_STRING 64
+
+/**************************************************************************//**
+ * UUID of the VM extracted from the OpenStack metadata service.
+ *****************************************************************************/
+static char vm_uuid[MAX_METADATA_STRING+1] = {0};
+
+/**************************************************************************//**
+ * Name of the VM extracted from the OpenStack metadata service.
+ *****************************************************************************/
+static char vm_name[MAX_METADATA_STRING+1] = {0};
+
+/**************************************************************************//**
+ * How many metadata elements we allow for in the retrieved JSON.
+ *****************************************************************************/
+static const int MAX_METADATA_TOKENS = 128;
+
+/*****************************************************************************/
+/* Local prototypes. */
+/*****************************************************************************/
+static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
+ const jsmntok_t *tokens,
+ int json_token_count,
+ const char * key,
+ char * value);
+static EVEL_ERR_CODES json_get_string(const char * json_string,
+ const jsmntok_t *tokens,
+ int json_token_count,
+ const char * key,
+ char * value);
+static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
+
+/**************************************************************************//**
+ * Download metadata from the OpenStack metadata service.
+ *
+ * @param verbosity Controls whether to generate debug to stdout. Zero:
+ * none. Non-zero: generate debug.
+ * @returns Status code
+ * @retval EVEL_SUCCESS On success
+ * @retval ::EVEL_ERR_CODES On failure.
+ *****************************************************************************/
+EVEL_ERR_CODES openstack_metadata(int verbosity)
+{
+ int rc = EVEL_SUCCESS;
+ CURLcode curl_rc = CURLE_OK;
+ CURL * curl_handle = NULL;
+ MEMORY_CHUNK rx_chunk;
+ char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
+ jsmn_parser json_parser;
+ jsmntok_t tokens[MAX_METADATA_TOKENS];
+ int json_token_count = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Initialize dummy values for the metadata - needed for test */
+ /* environments. */
+ /***************************************************************************/
+ openstack_metadata_initialize();
+
+ /***************************************************************************/
+ /* Get a curl handle which we'll use for accessing the metadata service. */
+ /***************************************************************************/
+ curl_handle = curl_easy_init();
+ if (curl_handle == NULL)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to get libcurl handle");
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Prime the library to give friendly error codes. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_ERRORBUFFER,
+ curl_err_string);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
+ "Error code=%d", curl_rc);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Set the URL for the metadata API. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to initialize libcurl with the API URL. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* send all data to this function. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_WRITEFUNCTION,
+ evel_write_callback);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to initialize libcurl with the write callback. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* some servers don't like requests that are made without a user-agent */
+ /* field, so we provide one. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_USERAGENT,
+ "libcurl-agent/1.0");
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
+ curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Set the timeout for the operation. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle,
+ CURLOPT_TIMEOUT,
+ OPENSTACK_METADATA_TIMEOUT);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_NO_METADATA;
+ EVEL_ERROR("Failed to initialize libcurl to set timeout. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+
+ /***************************************************************************/
+ /* Create the memory chunk to be used for the response to the post. The */
+ /* will be realloced. */
+ /***************************************************************************/
+ rx_chunk.memory = malloc(1);
+ assert(rx_chunk.memory != NULL);
+ rx_chunk.size = 0;
+
+ /***************************************************************************/
+ /* Point to the data to be received. */
+ /***************************************************************************/
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ goto exit_label;
+ }
+ EVEL_DEBUG("Initialized data to receive");
+
+ /***************************************************************************/
+ /* If running in verbose mode generate more output. */
+ /***************************************************************************/
+ if (verbosity > 0)
+ {
+ curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ log_error_state("Failed to initialize libcurl to be verbose. "
+ "Error code=%d", curl_rc);
+ goto exit_label;
+ }
+ }
+
+ /***************************************************************************/
+ /* Now run off and do what you've been told! */
+ /***************************************************************************/
+ curl_rc = curl_easy_perform(curl_handle);
+ if (curl_rc != CURLE_OK)
+ {
+ rc = EVEL_CURL_LIBRARY_FAIL;
+ EVEL_ERROR("Failed to transfer the data from metadata service. "
+ "Error code=%d (%s)", curl_rc, curl_err_string);
+ }
+ else
+ {
+ /*************************************************************************/
+ /* We have some metadata available, so break it out into tokens. */
+ /*************************************************************************/
+ EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
+ EVEL_INFO("Received metadata = %s", rx_chunk.memory);
+ jsmn_init(&json_parser);
+ json_token_count = jsmn_parse(&json_parser,
+ rx_chunk.memory, rx_chunk.size,
+ tokens, MAX_METADATA_TOKENS);
+
+ /*************************************************************************/
+ /* Check that we parsed some data and that the top level is as expected. */
+ /*************************************************************************/
+ if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
+ {
+ rc = EVEL_BAD_METADATA;
+ EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
+ "Error code=%d", json_token_count);
+ goto exit_label;
+ }
+ else
+ {
+ EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
+ json_token_count);
+ }
+
+ /*************************************************************************/
+ /* Find the keys we want from the metadata. */
+ /*************************************************************************/
+ if (json_get_string(rx_chunk.memory,
+ tokens,
+ json_token_count,
+ "uuid",
+ vm_uuid) != EVEL_SUCCESS)
+ {
+ rc = EVEL_BAD_METADATA;
+ EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
+ }
+ else
+ {
+ EVEL_DEBUG("UUID: %s", vm_uuid);
+ }
+ if (json_get_top_level_string(rx_chunk.memory,
+ tokens,
+ json_token_count,
+ "name",
+ vm_name) != EVEL_SUCCESS)
+ {
+ rc = EVEL_BAD_METADATA;
+ EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
+ }
+ else
+ {
+ EVEL_DEBUG("VM Name: %s", vm_name);
+ }
+ }
+
+exit_label:
+
+ /***************************************************************************/
+ /* Shut down the cURL library in a tidy manner. */
+ /***************************************************************************/
+ if (curl_handle != NULL)
+ {
+ curl_easy_cleanup(curl_handle);
+ curl_handle = NULL;
+ }
+ free(rx_chunk.memory);
+
+ EVEL_EXIT();
+ return rc;
+}
+
+/**************************************************************************//**
+ * Initialize default values for vm_name and vm_uuid - for testing purposes.
+ *****************************************************************************/
+void openstack_metadata_initialize()
+{
+ strncpy(vm_uuid,
+ "Dummy VM UUID - No Metadata available",
+ MAX_METADATA_STRING);
+ strncpy(vm_name,
+ "Dummy VM name - No Metadata available",
+ MAX_METADATA_STRING);
+}
+
+/**************************************************************************//**
+ * Get a string value from supplied JSON by matching the key.
+ *
+ * As the structure of the metadata we're looking at is pretty straightforward
+ * we don't do anything complex (a la XPath) to extract nested keys with the
+ * same leaf name, for example. Simply walk the structure until we find a
+ * string with the correct value.
+ *
+ * @param[in] json_string The string which contains the JSON and has already
+ * been parsed.
+ * @param[in] tokens The tokens which the JSON parser found in the JSON.
+ * @param[in] json_token_count How many tokens were found.
+ * @param[in] key The key we're looking for.
+ * @param[out] value The string we found at @p key.
+ *
+ * @returns Status code
+ * @retval EVEL_SUCCESS On success - contents of @p value updated.
+ * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
+ * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
+ * updated.
+ *****************************************************************************/
+static EVEL_ERR_CODES json_get_string(const char * json_string,
+ const jsmntok_t * tokens,
+ int json_token_count,
+ const char * key,
+ char * value)
+{
+ EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
+ int token_num = 0;
+ int token_len = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check assumptions. */
+ /***************************************************************************/
+ assert(json_string != NULL);
+ assert(tokens != NULL);
+ assert(json_token_count >= 0);
+ assert(key != NULL);
+ assert(value != NULL);
+
+ for (token_num = 0; token_num < json_token_count; token_num++)
+ {
+ switch(tokens[token_num].type)
+ {
+ case JSMN_OBJECT:
+ EVEL_DEBUG("Skipping object");
+ break;
+
+ case JSMN_ARRAY:
+ EVEL_DEBUG("Skipping array");
+ break;
+
+ case JSMN_STRING:
+ /***********************************************************************/
+ /* This is a string, so may be what we want. Compare keys. */
+ /***********************************************************************/
+ if (jsoneq(json_string, &tokens[token_num], key) == 0)
+ {
+ token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
+ EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
+ tokens[token_num + 1].start,
+ tokens[token_num + 1].end);
+ strncpy(value, json_string + tokens[token_num + 1].start, token_len);
+ value[token_len] = '\0';
+ EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
+ rc = EVEL_SUCCESS;
+ goto exit_label;
+ }
+ else
+ {
+ EVEL_DEBUG("String key did not match");
+ }
+
+ /***********************************************************************/
+ /* Step over the value, whether we used it or not. */
+ /***********************************************************************/
+ token_num++;
+ break;
+
+ case JSMN_PRIMITIVE:
+ EVEL_INFO("Skipping primitive");
+ break;
+
+ case JSMN_UNDEFINED:
+ default:
+ rc = EVEL_BAD_JSON_FORMAT;
+ EVEL_ERROR("Unexpected JSON format at token %d (%d)",
+ token_num,
+ tokens[token_num].type);
+ goto exit_label;
+ }
+ }
+
+exit_label:
+ EVEL_EXIT();
+ return rc;
+}
+
+/**************************************************************************//**
+ * Get a top-level string value from supplied JSON by matching the key.
+ *
+ * Unlike json_get_string, this only returns a value that is in the top-level
+ * JSON object.
+ *
+ * @param[in] json_string The string which contains the JSON and has already
+ * been parsed.
+ * @param[in] tokens The tokens which the JSON parser found in the JSON.
+ * @param[in] json_token_count How many tokens were found.
+ * @param[in] key The key we're looking for.
+ * @param[out] value The string we found at @p key.
+ *
+ * @returns Status code
+ * @retval EVEL_SUCCESS On success - contents of @p value updated.
+ * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
+ * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
+ * updated.
+ *****************************************************************************/
+static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
+ const jsmntok_t * tokens,
+ int json_token_count,
+ const char * key,
+ char * value)
+{
+ EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
+ int token_num = 0;
+ int token_len = 0;
+ int bracket_count = 0;
+ int string_index = 0;
+ int increment = 0;
+
+ EVEL_ENTER();
+
+ /***************************************************************************/
+ /* Check assumptions. */
+ /***************************************************************************/
+ assert(json_string != NULL);
+ assert(tokens != NULL);
+ assert(json_token_count >= 0);
+ assert(key != NULL);
+ assert(value != NULL);
+
+ for (token_num = 0; token_num < json_token_count; token_num++)
+ {
+ switch(tokens[token_num].type)
+ {
+ case JSMN_OBJECT:
+ EVEL_DEBUG("Skipping object");
+ break;
+
+ case JSMN_ARRAY:
+ EVEL_DEBUG("Skipping array");
+ break;
+
+ case JSMN_STRING:
+ /***********************************************************************/
+ /* This is a string, so may be what we want. Compare keys. */
+ /***********************************************************************/
+ if (jsoneq(json_string, &tokens[token_num], key) == 0)
+ {
+ /*********************************************************************/
+ /* Count the difference in the number of opening and closing */
+ /* brackets up to this token. This needs to be 1 for a top-level */
+ /* string. Let's just hope we don't have any strings containing */
+ /* brackets. */
+ /*********************************************************************/
+ increment = ((string_index < tokens[token_num].start) ? 1 : -1);
+
+ while (string_index != tokens[token_num].start)
+ {
+ if (json_string[string_index] == '{')
+ {
+ bracket_count += increment;
+ }
+ else if (json_string[string_index] == '}')
+ {
+ bracket_count -= increment;
+ }
+
+ string_index += increment;
+ }
+
+ if (bracket_count == 1)
+ {
+ token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
+ EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
+ token_num,
+ tokens[token_num + 1].start,
+ tokens[token_num + 1].end);
+ strncpy(value, json_string + tokens[token_num + 1].start, token_len);
+ value[token_len] = '\0';
+ EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
+ rc = EVEL_SUCCESS;
+ goto exit_label;
+ }
+ else
+ {
+ EVEL_DEBUG("String key did match, but not at top level");
+ }
+ }
+ else
+ {
+ EVEL_DEBUG("String key did not match");
+ }
+
+ /***********************************************************************/
+ /* Step over the value, whether we used it or not. */
+ /***********************************************************************/
+ token_num++;
+ break;
+
+ case JSMN_PRIMITIVE:
+ EVEL_INFO("Skipping primitive");
+ break;
+
+ case JSMN_UNDEFINED:
+ default:
+ rc = EVEL_BAD_JSON_FORMAT;
+ EVEL_ERROR("Unexpected JSON format at token %d (%d)",
+ token_num,
+ tokens[token_num].type);
+ goto exit_label;
+ }
+ }
+
+exit_label:
+ EVEL_EXIT();
+ return rc;
+}
+
+/**************************************************************************//**
+ * Compare a JSON string token with a value.
+ *
+ * @param[in] json The string which contains the JSON and has already been
+ * parsed.
+ * @param[in] tok The token which the JSON parser found in the JSON.
+ * @param[in] s The string we're looking for.
+ *
+ * @returns Whether the token matches the string or not.
+ * @retval 0 Value matches
+ * @retval -1 Value does not match.
+ *****************************************************************************/
+static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
+ if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
+ strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+ return 0;
+ }
+ return -1;
+}
+
+/**************************************************************************//**
+ * Get the VM name provided by the metadata service.
+ *
+ * @returns VM name
+ *****************************************************************************/
+const char *openstack_vm_name()
+{
+ return vm_name;
+}
+
+/**************************************************************************//**
+ * Get the VM UUID provided by the metadata service.
+ *
+ * @returns VM UUID
+ *****************************************************************************/
+const char *openstack_vm_uuid()
+{
+ return vm_uuid;
+}