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);
312 /**************************************************************************//**
313 * Initialize default values for vm_name and vm_uuid - for testing purposes.
314 *****************************************************************************/
315 void openstack_metadata_initialize()
317 char hostname[MAX_METADATA_STRING];
319 FILE * f = fopen ("/proc/sys/kernel/random/uuid", "r");
322 "Dummy VM UUID - No Metadata available",
323 MAX_METADATA_STRING);
325 "Dummy VM name - No Metadata available",
326 MAX_METADATA_STRING);
328 if( gethostname(hostname, 1024) != -1 )
329 strcpy(vm_name,hostname);
333 if (fgets(vm_uuid,MAX_METADATA_STRING, f)!=NULL)
335 vm_uuid[strlen( vm_uuid ) - 1 ] = '\0';
336 EVEL_DEBUG("VM UUID: %s", vm_uuid);
343 /**************************************************************************//**
344 * Get a string value from supplied JSON by matching the key.
346 * As the structure of the metadata we're looking at is pretty straightforward
347 * we don't do anything complex (a la XPath) to extract nested keys with the
348 * same leaf name, for example. Simply walk the structure until we find a
349 * string with the correct value.
351 * @param[in] json_string The string which contains the JSON and has already
353 * @param[in] tokens The tokens which the JSON parser found in the JSON.
354 * @param[in] json_token_count How many tokens were found.
355 * @param[in] key The key we're looking for.
356 * @param[out] value The string we found at @p key.
358 * @returns Status code
359 * @retval EVEL_SUCCESS On success - contents of @p value updated.
360 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
361 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
363 *****************************************************************************/
364 static EVEL_ERR_CODES json_get_string(const char * json_string,
365 const jsmntok_t * tokens,
366 int json_token_count,
370 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
376 /***************************************************************************/
377 /* Check assumptions. */
378 /***************************************************************************/
379 assert(json_string != NULL);
380 assert(tokens != NULL);
381 assert(json_token_count >= 0);
383 assert(value != NULL);
385 for (token_num = 0; token_num < json_token_count; token_num++)
387 switch(tokens[token_num].type)
390 EVEL_DEBUG("Skipping object");
394 EVEL_DEBUG("Skipping array");
398 /***********************************************************************/
399 /* This is a string, so may be what we want. Compare keys. */
400 /***********************************************************************/
401 if (jsoneq(json_string, &tokens[token_num], key) == 0)
403 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
404 EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
405 tokens[token_num + 1].start,
406 tokens[token_num + 1].end);
407 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
408 value[token_len] = '\0';
409 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
415 EVEL_DEBUG("String key did not match");
418 /***********************************************************************/
419 /* Step over the value, whether we used it or not. */
420 /***********************************************************************/
425 EVEL_INFO("Skipping primitive");
430 rc = EVEL_BAD_JSON_FORMAT;
431 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
433 tokens[token_num].type);
443 /**************************************************************************//**
444 * Get a top-level string value from supplied JSON by matching the key.
446 * Unlike json_get_string, this only returns a value that is in the top-level
449 * @param[in] json_string The string which contains the JSON and has already
451 * @param[in] tokens The tokens which the JSON parser found in the JSON.
452 * @param[in] json_token_count How many tokens were found.
453 * @param[in] key The key we're looking for.
454 * @param[out] value The string we found at @p key.
456 * @returns Status code
457 * @retval EVEL_SUCCESS On success - contents of @p value updated.
458 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
459 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
461 *****************************************************************************/
462 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
463 const jsmntok_t * tokens,
464 int json_token_count,
468 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
471 int bracket_count = 0;
472 int string_index = 0;
477 /***************************************************************************/
478 /* Check assumptions. */
479 /***************************************************************************/
480 assert(json_string != NULL);
481 assert(tokens != NULL);
482 assert(json_token_count >= 0);
484 assert(value != NULL);
486 for (token_num = 0; token_num < json_token_count; token_num++)
488 switch(tokens[token_num].type)
491 EVEL_DEBUG("Skipping object");
495 EVEL_DEBUG("Skipping array");
499 /***********************************************************************/
500 /* This is a string, so may be what we want. Compare keys. */
501 /***********************************************************************/
502 if (jsoneq(json_string, &tokens[token_num], key) == 0)
504 /*********************************************************************/
505 /* Count the difference in the number of opening and closing */
506 /* brackets up to this token. This needs to be 1 for a top-level */
507 /* string. Let's just hope we don't have any strings containing */
509 /*********************************************************************/
510 increment = ((string_index < tokens[token_num].start) ? 1 : -1);
512 while (string_index != tokens[token_num].start)
514 if (json_string[string_index] == '{')
516 bracket_count += increment;
518 else if (json_string[string_index] == '}')
520 bracket_count -= increment;
523 string_index += increment;
526 if (bracket_count == 1)
528 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
529 EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
531 tokens[token_num + 1].start,
532 tokens[token_num + 1].end);
533 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
534 value[token_len] = '\0';
535 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
541 EVEL_DEBUG("String key did match, but not at top level");
546 EVEL_DEBUG("String key did not match");
549 /***********************************************************************/
550 /* Step over the value, whether we used it or not. */
551 /***********************************************************************/
556 EVEL_INFO("Skipping primitive");
561 rc = EVEL_BAD_JSON_FORMAT;
562 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
564 tokens[token_num].type);
574 /**************************************************************************//**
575 * Compare a JSON string token with a value.
577 * @param[in] json The string which contains the JSON and has already been
579 * @param[in] tok The token which the JSON parser found in the JSON.
580 * @param[in] s The string we're looking for.
582 * @returns Whether the token matches the string or not.
583 * @retval 0 Value matches
584 * @retval -1 Value does not match.
585 *****************************************************************************/
586 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
587 if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
588 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
594 /**************************************************************************//**
595 * Get the VM name provided by the metadata service.
598 *****************************************************************************/
599 const char *openstack_vm_name()
604 /**************************************************************************//**
605 * Get the VM UUID provided by the metadata service.
608 *****************************************************************************/
609 const char *openstack_vm_uuid()