1 /*************************************************************************//**
3 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
5 * Unless otherwise specified, all software contained herein is
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
17 ****************************************************************************/
18 /**************************************************************************//**
20 * Wrap the OpenStack metadata service.
21 ****************************************************************************/
28 #include <curl/curl.h>
31 #include "evel_internal.h"
35 /**************************************************************************//**
36 * URL on the link-local IP address where we can get the metadata in
37 * machine-friendly format.
38 *****************************************************************************/
39 static const char * OPENSTACK_METADATA_URL =
40 "http://169.254.169.254/openstack/latest/meta_data.json";
42 /**************************************************************************//**
43 * How long we're prepared to wait for the metadata service to respond in
45 *****************************************************************************/
46 static const int OPENSTACK_METADATA_TIMEOUT = 2;
48 /**************************************************************************//**
49 * Size of fields extracted from metadata service.
50 *****************************************************************************/
51 #define MAX_METADATA_STRING 64
53 /**************************************************************************//**
54 * UUID of the VM extracted from the OpenStack metadata service.
55 *****************************************************************************/
56 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
58 /**************************************************************************//**
59 * Name of the VM extracted from the OpenStack metadata service.
60 *****************************************************************************/
61 static char vm_name[MAX_METADATA_STRING+1] = {0};
63 /**************************************************************************//**
64 * How many metadata elements we allow for in the retrieved JSON.
65 *****************************************************************************/
66 static const int MAX_METADATA_TOKENS = 128;
68 /*****************************************************************************/
69 /* Local prototypes. */
70 /*****************************************************************************/
71 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
72 const jsmntok_t *tokens,
76 static EVEL_ERR_CODES json_get_string(const char * json_string,
77 const jsmntok_t *tokens,
81 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
83 /**************************************************************************//**
84 * Download metadata from the OpenStack metadata service.
86 * @param verbosity Controls whether to generate debug to stdout. Zero:
87 * none. Non-zero: generate debug.
88 * @returns Status code
89 * @retval EVEL_SUCCESS On success
90 * @retval ::EVEL_ERR_CODES On failure.
91 *****************************************************************************/
92 EVEL_ERR_CODES openstack_metadata(int verbosity)
94 int rc = EVEL_SUCCESS;
95 CURLcode curl_rc = CURLE_OK;
96 CURL * curl_handle = NULL;
97 MEMORY_CHUNK rx_chunk;
98 char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
99 jsmn_parser json_parser;
100 jsmntok_t tokens[MAX_METADATA_TOKENS];
101 int json_token_count = 0;
105 /***************************************************************************/
106 /* Initialize dummy values for the metadata - needed for test */
108 /***************************************************************************/
109 openstack_metadata_initialize();
111 /***************************************************************************/
112 /* Get a curl handle which we'll use for accessing the metadata service. */
113 /***************************************************************************/
114 curl_handle = curl_easy_init();
115 if (curl_handle == NULL)
117 rc = EVEL_CURL_LIBRARY_FAIL;
118 EVEL_ERROR("Failed to get libcurl handle");
122 /***************************************************************************/
123 /* Prime the library to give friendly error codes. */
124 /***************************************************************************/
125 curl_rc = curl_easy_setopt(curl_handle,
128 if (curl_rc != CURLE_OK)
130 rc = EVEL_CURL_LIBRARY_FAIL;
131 EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
132 "Error code=%d", curl_rc);
136 /***************************************************************************/
137 /* Set the URL for the metadata API. */
138 /***************************************************************************/
139 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
140 if (curl_rc != CURLE_OK)
142 rc = EVEL_CURL_LIBRARY_FAIL;
143 EVEL_ERROR("Failed to initialize libcurl with the API URL. "
144 "Error code=%d (%s)", curl_rc, curl_err_string);
148 /***************************************************************************/
149 /* send all data to this function. */
150 /***************************************************************************/
151 curl_rc = curl_easy_setopt(curl_handle,
152 CURLOPT_WRITEFUNCTION,
153 evel_write_callback);
154 if (curl_rc != CURLE_OK)
156 rc = EVEL_CURL_LIBRARY_FAIL;
157 EVEL_ERROR("Failed to initialize libcurl with the write callback. "
158 "Error code=%d (%s)", curl_rc, curl_err_string);
162 /***************************************************************************/
163 /* some servers don't like requests that are made without a user-agent */
164 /* field, so we provide one. */
165 /***************************************************************************/
166 curl_rc = curl_easy_setopt(curl_handle,
168 "libcurl-agent/1.0");
169 if (curl_rc != CURLE_OK)
171 rc = EVEL_CURL_LIBRARY_FAIL;
172 EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
173 curl_rc, curl_err_string);
177 /***************************************************************************/
178 /* Set the timeout for the operation. */
179 /***************************************************************************/
180 curl_rc = curl_easy_setopt(curl_handle,
182 OPENSTACK_METADATA_TIMEOUT);
183 if (curl_rc != CURLE_OK)
185 rc = EVEL_NO_METADATA;
186 EVEL_ERROR("Failed to initialize libcurl to set timeout. "
187 "Error code=%d (%s)", curl_rc, curl_err_string);
191 /***************************************************************************/
192 /* Create the memory chunk to be used for the response to the post. The */
193 /* will be realloced. */
194 /***************************************************************************/
195 rx_chunk.memory = malloc(1);
196 assert(rx_chunk.memory != NULL);
199 /***************************************************************************/
200 /* Point to the data to be received. */
201 /***************************************************************************/
202 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
203 if (curl_rc != CURLE_OK)
205 rc = EVEL_CURL_LIBRARY_FAIL;
206 EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
207 "Error code=%d (%s)", curl_rc, curl_err_string);
210 EVEL_DEBUG("Initialized data to receive");
212 /***************************************************************************/
213 /* If running in verbose mode generate more output. */
214 /***************************************************************************/
217 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
218 if (curl_rc != CURLE_OK)
220 rc = EVEL_CURL_LIBRARY_FAIL;
221 log_error_state("Failed to initialize libcurl to be verbose. "
222 "Error code=%d", curl_rc);
227 /***************************************************************************/
228 /* Now run off and do what you've been told! */
229 /***************************************************************************/
230 curl_rc = curl_easy_perform(curl_handle);
231 if (curl_rc != CURLE_OK)
233 rc = EVEL_CURL_LIBRARY_FAIL;
234 EVEL_ERROR("Failed to transfer the data from metadata service. "
235 "Error code=%d (%s)", curl_rc, curl_err_string);
239 /*************************************************************************/
240 /* We have some metadata available, so break it out into tokens. */
241 /*************************************************************************/
242 EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
243 EVEL_INFO("Received metadata = %s", rx_chunk.memory);
244 jsmn_init(&json_parser);
245 json_token_count = jsmn_parse(&json_parser,
246 rx_chunk.memory, rx_chunk.size,
247 tokens, MAX_METADATA_TOKENS);
249 /*************************************************************************/
250 /* Check that we parsed some data and that the top level is as expected. */
251 /*************************************************************************/
252 if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
254 rc = EVEL_BAD_METADATA;
255 EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
256 "Error code=%d", json_token_count);
261 EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
265 /*************************************************************************/
266 /* Find the keys we want from the metadata. */
267 /*************************************************************************/
268 if (json_get_string(rx_chunk.memory,
272 vm_uuid) != EVEL_SUCCESS)
274 rc = EVEL_BAD_METADATA;
275 EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
279 EVEL_DEBUG("UUID: %s", vm_uuid);
281 if (json_get_top_level_string(rx_chunk.memory,
285 vm_name) != EVEL_SUCCESS)
287 rc = EVEL_BAD_METADATA;
288 EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
292 EVEL_DEBUG("VM Name: %s", vm_name);
298 /***************************************************************************/
299 /* Shut down the cURL library in a tidy manner. */
300 /***************************************************************************/
301 if (curl_handle != NULL)
303 curl_easy_cleanup(curl_handle);
306 free(rx_chunk.memory);
313 /**************************************************************************//**
314 * Initialize default values for vm_name and vm_uuid - for testing purposes.
315 *****************************************************************************/
316 void openstack_metadata_initialize()
318 char hostname[MAX_METADATA_STRING];
320 FILE * f = fopen ("/proc/sys/kernel/random/uuid", "r");
323 "Dummy VM UUID - No Metadata available",
324 MAX_METADATA_STRING);
326 "Dummy VM name - No Metadata available",
327 MAX_METADATA_STRING);
329 if( gethostname(hostname, 1024) != -1 )
330 strcpy(vm_name,hostname);
334 if (fgets(vm_uuid,MAX_METADATA_STRING, f)!=NULL)
336 vm_uuid[strlen( vm_uuid ) - 1 ] = '\0';
337 EVEL_DEBUG("VM UUID: %s", vm_uuid);
344 /**************************************************************************//**
345 * Initialize value for vm_name for all coming events
346 * @param source_name Source name string.
347 * Must confirm with EVEL source name standard
348 * @returns Status code
349 * @retval EVEL_SUCCESS On success
350 * @retval ::EVEL_ERR_CODES On failure.
351 *****************************************************************************/
352 EVEL_ERR_CODES evel_set_source_name(char * src_name)
354 if( src_name && src_name[0] )
356 if( strlen(src_name) < MAX_METADATA_STRING ){
357 strcpy(vm_name,src_name);
360 EVEL_DEBUG("Event Source Name too long");
363 EVEL_DEBUG("Invalid Event Source Name string");
364 return EVEL_ERR_GEN_FAIL;
367 /**************************************************************************//**
368 * Get a string value from supplied JSON by matching the key.
370 * As the structure of the metadata we're looking at is pretty straightforward
371 * we don't do anything complex (a la XPath) to extract nested keys with the
372 * same leaf name, for example. Simply walk the structure until we find a
373 * string with the correct value.
375 * @param[in] json_string The string which contains the JSON and has already
377 * @param[in] tokens The tokens which the JSON parser found in the JSON.
378 * @param[in] json_token_count How many tokens were found.
379 * @param[in] key The key we're looking for.
380 * @param[out] value The string we found at @p key.
382 * @returns Status code
383 * @retval EVEL_SUCCESS On success - contents of @p value updated.
384 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
385 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
387 *****************************************************************************/
388 static EVEL_ERR_CODES json_get_string(const char * json_string,
389 const jsmntok_t * tokens,
390 int json_token_count,
394 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
400 /***************************************************************************/
401 /* Check assumptions. */
402 /***************************************************************************/
403 assert(json_string != NULL);
404 assert(tokens != NULL);
405 assert(json_token_count >= 0);
407 assert(value != NULL);
409 for (token_num = 0; token_num < json_token_count; token_num++)
411 switch(tokens[token_num].type)
414 EVEL_DEBUG("Skipping object");
418 EVEL_DEBUG("Skipping array");
422 /***********************************************************************/
423 /* This is a string, so may be what we want. Compare keys. */
424 /***********************************************************************/
425 if (jsoneq(json_string, &tokens[token_num], key) == 0)
427 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
428 EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
429 tokens[token_num + 1].start,
430 tokens[token_num + 1].end);
431 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
432 value[token_len] = '\0';
433 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
439 EVEL_DEBUG("String key did not match");
442 /***********************************************************************/
443 /* Step over the value, whether we used it or not. */
444 /***********************************************************************/
449 EVEL_INFO("Skipping primitive");
454 rc = EVEL_BAD_JSON_FORMAT;
455 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
457 tokens[token_num].type);
467 /**************************************************************************//**
468 * Get a top-level string value from supplied JSON by matching the key.
470 * Unlike json_get_string, this only returns a value that is in the top-level
473 * @param[in] json_string The string which contains the JSON and has already
475 * @param[in] tokens The tokens which the JSON parser found in the JSON.
476 * @param[in] json_token_count How many tokens were found.
477 * @param[in] key The key we're looking for.
478 * @param[out] value The string we found at @p key.
480 * @returns Status code
481 * @retval EVEL_SUCCESS On success - contents of @p value updated.
482 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
483 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
485 *****************************************************************************/
486 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
487 const jsmntok_t * tokens,
488 int json_token_count,
492 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
495 int bracket_count = 0;
496 int string_index = 0;
501 /***************************************************************************/
502 /* Check assumptions. */
503 /***************************************************************************/
504 assert(json_string != NULL);
505 assert(tokens != NULL);
506 assert(json_token_count >= 0);
508 assert(value != NULL);
510 for (token_num = 0; token_num < json_token_count; token_num++)
512 switch(tokens[token_num].type)
515 EVEL_DEBUG("Skipping object");
519 EVEL_DEBUG("Skipping array");
523 /***********************************************************************/
524 /* This is a string, so may be what we want. Compare keys. */
525 /***********************************************************************/
526 if (jsoneq(json_string, &tokens[token_num], key) == 0)
528 /*********************************************************************/
529 /* Count the difference in the number of opening and closing */
530 /* brackets up to this token. This needs to be 1 for a top-level */
531 /* string. Let's just hope we don't have any strings containing */
533 /*********************************************************************/
534 increment = ((string_index < tokens[token_num].start) ? 1 : -1);
536 while (string_index != tokens[token_num].start)
538 if (json_string[string_index] == '{')
540 bracket_count += increment;
542 else if (json_string[string_index] == '}')
544 bracket_count -= increment;
547 string_index += increment;
550 if (bracket_count == 1)
552 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
553 EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
555 tokens[token_num + 1].start,
556 tokens[token_num + 1].end);
557 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
558 value[token_len] = '\0';
559 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
565 EVEL_DEBUG("String key did match, but not at top level");
570 EVEL_DEBUG("String key did not match");
573 /***********************************************************************/
574 /* Step over the value, whether we used it or not. */
575 /***********************************************************************/
580 EVEL_INFO("Skipping primitive");
585 rc = EVEL_BAD_JSON_FORMAT;
586 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
588 tokens[token_num].type);
598 /**************************************************************************//**
599 * Compare a JSON string token with a value.
601 * @param[in] json The string which contains the JSON and has already been
603 * @param[in] tok The token which the JSON parser found in the JSON.
604 * @param[in] s The string we're looking for.
606 * @returns Whether the token matches the string or not.
607 * @retval 0 Value matches
608 * @retval -1 Value does not match.
609 *****************************************************************************/
610 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
611 if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
612 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
618 /**************************************************************************//**
619 * Get the VM name provided by the metadata service.
622 *****************************************************************************/
623 const char *openstack_vm_name()
628 /**************************************************************************//**
629 * Get the VM UUID provided by the metadata service.
632 *****************************************************************************/
633 const char *openstack_vm_uuid()