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.
17 ****************************************************************************/
18 /**************************************************************************//**
20 * Wrap the OpenStack metadata service.
21 ****************************************************************************/
27 #include <curl/curl.h>
30 #include "evel_internal.h"
34 /**************************************************************************//**
35 * URL on the link-local IP address where we can get the metadata in
36 * machine-friendly format.
37 *****************************************************************************/
38 static const char * OPENSTACK_METADATA_URL =
39 "http://169.254.169.254/openstack/latest/meta_data.json";
41 /**************************************************************************//**
42 * How long we're prepared to wait for the metadata service to respond in
44 *****************************************************************************/
45 static const int OPENSTACK_METADATA_TIMEOUT = 2;
47 /**************************************************************************//**
48 * Size of fields extracted from metadata service.
49 *****************************************************************************/
50 #define MAX_METADATA_STRING 64
52 /**************************************************************************//**
53 * UUID of the VM extracted from the OpenStack metadata service.
54 *****************************************************************************/
55 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
57 /**************************************************************************//**
58 * Name of the VM extracted from the OpenStack metadata service.
59 *****************************************************************************/
60 static char vm_name[MAX_METADATA_STRING+1] = {0};
62 /**************************************************************************//**
63 * How many metadata elements we allow for in the retrieved JSON.
64 *****************************************************************************/
65 static const int MAX_METADATA_TOKENS = 128;
67 /*****************************************************************************/
68 /* Local prototypes. */
69 /*****************************************************************************/
70 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
71 const jsmntok_t *tokens,
75 static EVEL_ERR_CODES json_get_string(const char * json_string,
76 const jsmntok_t *tokens,
80 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
82 /**************************************************************************//**
83 * Download metadata from the OpenStack metadata service.
85 * @param verbosity Controls whether to generate debug to stdout. Zero:
86 * none. Non-zero: generate debug.
87 * @returns Status code
88 * @retval EVEL_SUCCESS On success
89 * @retval ::EVEL_ERR_CODES On failure.
90 *****************************************************************************/
91 EVEL_ERR_CODES openstack_metadata(int verbosity)
93 int rc = EVEL_SUCCESS;
94 CURLcode curl_rc = CURLE_OK;
95 CURL * curl_handle = NULL;
96 MEMORY_CHUNK rx_chunk;
97 char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
98 jsmn_parser json_parser;
99 jsmntok_t tokens[MAX_METADATA_TOKENS];
100 int json_token_count = 0;
104 /***************************************************************************/
105 /* Initialize dummy values for the metadata - needed for test */
107 /***************************************************************************/
108 openstack_metadata_initialize();
110 /***************************************************************************/
111 /* Get a curl handle which we'll use for accessing the metadata service. */
112 /***************************************************************************/
113 curl_handle = curl_easy_init();
114 if (curl_handle == NULL)
116 rc = EVEL_CURL_LIBRARY_FAIL;
117 EVEL_ERROR("Failed to get libcurl handle");
121 /***************************************************************************/
122 /* Prime the library to give friendly error codes. */
123 /***************************************************************************/
124 curl_rc = curl_easy_setopt(curl_handle,
127 if (curl_rc != CURLE_OK)
129 rc = EVEL_CURL_LIBRARY_FAIL;
130 EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
131 "Error code=%d", curl_rc);
135 /***************************************************************************/
136 /* Set the URL for the metadata API. */
137 /***************************************************************************/
138 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
139 if (curl_rc != CURLE_OK)
141 rc = EVEL_CURL_LIBRARY_FAIL;
142 EVEL_ERROR("Failed to initialize libcurl with the API URL. "
143 "Error code=%d (%s)", curl_rc, curl_err_string);
147 /***************************************************************************/
148 /* send all data to this function. */
149 /***************************************************************************/
150 curl_rc = curl_easy_setopt(curl_handle,
151 CURLOPT_WRITEFUNCTION,
152 evel_write_callback);
153 if (curl_rc != CURLE_OK)
155 rc = EVEL_CURL_LIBRARY_FAIL;
156 EVEL_ERROR("Failed to initialize libcurl with the write callback. "
157 "Error code=%d (%s)", curl_rc, curl_err_string);
161 /***************************************************************************/
162 /* some servers don't like requests that are made without a user-agent */
163 /* field, so we provide one. */
164 /***************************************************************************/
165 curl_rc = curl_easy_setopt(curl_handle,
167 "libcurl-agent/1.0");
168 if (curl_rc != CURLE_OK)
170 rc = EVEL_CURL_LIBRARY_FAIL;
171 EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
172 curl_rc, curl_err_string);
176 /***************************************************************************/
177 /* Set the timeout for the operation. */
178 /***************************************************************************/
179 curl_rc = curl_easy_setopt(curl_handle,
181 OPENSTACK_METADATA_TIMEOUT);
182 if (curl_rc != CURLE_OK)
184 rc = EVEL_NO_METADATA;
185 EVEL_ERROR("Failed to initialize libcurl to set timeout. "
186 "Error code=%d (%s)", curl_rc, curl_err_string);
190 /***************************************************************************/
191 /* Create the memory chunk to be used for the response to the post. The */
192 /* will be realloced. */
193 /***************************************************************************/
194 rx_chunk.memory = malloc(1);
195 assert(rx_chunk.memory != NULL);
198 /***************************************************************************/
199 /* Point to the data to be received. */
200 /***************************************************************************/
201 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
202 if (curl_rc != CURLE_OK)
204 rc = EVEL_CURL_LIBRARY_FAIL;
205 EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
206 "Error code=%d (%s)", curl_rc, curl_err_string);
209 EVEL_DEBUG("Initialized data to receive");
211 /***************************************************************************/
212 /* If running in verbose mode generate more output. */
213 /***************************************************************************/
216 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
217 if (curl_rc != CURLE_OK)
219 rc = EVEL_CURL_LIBRARY_FAIL;
220 log_error_state("Failed to initialize libcurl to be verbose. "
221 "Error code=%d", curl_rc);
226 /***************************************************************************/
227 /* Now run off and do what you've been told! */
228 /***************************************************************************/
229 curl_rc = curl_easy_perform(curl_handle);
230 if (curl_rc != CURLE_OK)
232 rc = EVEL_CURL_LIBRARY_FAIL;
233 EVEL_ERROR("Failed to transfer the data from metadata service. "
234 "Error code=%d (%s)", curl_rc, curl_err_string);
238 /*************************************************************************/
239 /* We have some metadata available, so break it out into tokens. */
240 /*************************************************************************/
241 EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
242 EVEL_INFO("Received metadata = %s", rx_chunk.memory);
243 jsmn_init(&json_parser);
244 json_token_count = jsmn_parse(&json_parser,
245 rx_chunk.memory, rx_chunk.size,
246 tokens, MAX_METADATA_TOKENS);
248 /*************************************************************************/
249 /* Check that we parsed some data and that the top level is as expected. */
250 /*************************************************************************/
251 if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
253 rc = EVEL_BAD_METADATA;
254 EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
255 "Error code=%d", json_token_count);
260 EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
264 /*************************************************************************/
265 /* Find the keys we want from the metadata. */
266 /*************************************************************************/
267 if (json_get_string(rx_chunk.memory,
271 vm_uuid) != EVEL_SUCCESS)
273 rc = EVEL_BAD_METADATA;
274 EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
278 EVEL_DEBUG("UUID: %s", vm_uuid);
280 if (json_get_top_level_string(rx_chunk.memory,
284 vm_name) != EVEL_SUCCESS)
286 rc = EVEL_BAD_METADATA;
287 EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
291 EVEL_DEBUG("VM Name: %s", vm_name);
297 /***************************************************************************/
298 /* Shut down the cURL library in a tidy manner. */
299 /***************************************************************************/
300 if (curl_handle != NULL)
302 curl_easy_cleanup(curl_handle);
305 free(rx_chunk.memory);
311 /**************************************************************************//**
312 * Initialize default values for vm_name and vm_uuid - for testing purposes.
313 *****************************************************************************/
314 void openstack_metadata_initialize()
317 "Dummy VM UUID - No Metadata available",
318 MAX_METADATA_STRING);
320 "Dummy VM name - No Metadata available",
321 MAX_METADATA_STRING);
324 /**************************************************************************//**
325 * Get a string value from supplied JSON by matching the key.
327 * As the structure of the metadata we're looking at is pretty straightforward
328 * we don't do anything complex (a la XPath) to extract nested keys with the
329 * same leaf name, for example. Simply walk the structure until we find a
330 * string with the correct value.
332 * @param[in] json_string The string which contains the JSON and has already
334 * @param[in] tokens The tokens which the JSON parser found in the JSON.
335 * @param[in] json_token_count How many tokens were found.
336 * @param[in] key The key we're looking for.
337 * @param[out] value The string we found at @p key.
339 * @returns Status code
340 * @retval EVEL_SUCCESS On success - contents of @p value updated.
341 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
342 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
344 *****************************************************************************/
345 static EVEL_ERR_CODES json_get_string(const char * json_string,
346 const jsmntok_t * tokens,
347 int json_token_count,
351 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
357 /***************************************************************************/
358 /* Check assumptions. */
359 /***************************************************************************/
360 assert(json_string != NULL);
361 assert(tokens != NULL);
362 assert(json_token_count >= 0);
364 assert(value != NULL);
366 for (token_num = 0; token_num < json_token_count; token_num++)
368 switch(tokens[token_num].type)
371 EVEL_DEBUG("Skipping object");
375 EVEL_DEBUG("Skipping array");
379 /***********************************************************************/
380 /* This is a string, so may be what we want. Compare keys. */
381 /***********************************************************************/
382 if (jsoneq(json_string, &tokens[token_num], key) == 0)
384 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
385 EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
386 tokens[token_num + 1].start,
387 tokens[token_num + 1].end);
388 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
389 value[token_len] = '\0';
390 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
396 EVEL_DEBUG("String key did not match");
399 /***********************************************************************/
400 /* Step over the value, whether we used it or not. */
401 /***********************************************************************/
406 EVEL_INFO("Skipping primitive");
411 rc = EVEL_BAD_JSON_FORMAT;
412 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
414 tokens[token_num].type);
424 /**************************************************************************//**
425 * Get a top-level string value from supplied JSON by matching the key.
427 * Unlike json_get_string, this only returns a value that is in the top-level
430 * @param[in] json_string The string which contains the JSON and has already
432 * @param[in] tokens The tokens which the JSON parser found in the JSON.
433 * @param[in] json_token_count How many tokens were found.
434 * @param[in] key The key we're looking for.
435 * @param[out] value The string we found at @p key.
437 * @returns Status code
438 * @retval EVEL_SUCCESS On success - contents of @p value updated.
439 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
440 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
442 *****************************************************************************/
443 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
444 const jsmntok_t * tokens,
445 int json_token_count,
449 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
452 int bracket_count = 0;
453 int string_index = 0;
458 /***************************************************************************/
459 /* Check assumptions. */
460 /***************************************************************************/
461 assert(json_string != NULL);
462 assert(tokens != NULL);
463 assert(json_token_count >= 0);
465 assert(value != NULL);
467 for (token_num = 0; token_num < json_token_count; token_num++)
469 switch(tokens[token_num].type)
472 EVEL_DEBUG("Skipping object");
476 EVEL_DEBUG("Skipping array");
480 /***********************************************************************/
481 /* This is a string, so may be what we want. Compare keys. */
482 /***********************************************************************/
483 if (jsoneq(json_string, &tokens[token_num], key) == 0)
485 /*********************************************************************/
486 /* Count the difference in the number of opening and closing */
487 /* brackets up to this token. This needs to be 1 for a top-level */
488 /* string. Let's just hope we don't have any strings containing */
490 /*********************************************************************/
491 increment = ((string_index < tokens[token_num].start) ? 1 : -1);
493 while (string_index != tokens[token_num].start)
495 if (json_string[string_index] == '{')
497 bracket_count += increment;
499 else if (json_string[string_index] == '}')
501 bracket_count -= increment;
504 string_index += increment;
507 if (bracket_count == 1)
509 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
510 EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
512 tokens[token_num + 1].start,
513 tokens[token_num + 1].end);
514 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
515 value[token_len] = '\0';
516 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
522 EVEL_DEBUG("String key did match, but not at top level");
527 EVEL_DEBUG("String key did not match");
530 /***********************************************************************/
531 /* Step over the value, whether we used it or not. */
532 /***********************************************************************/
537 EVEL_INFO("Skipping primitive");
542 rc = EVEL_BAD_JSON_FORMAT;
543 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
545 tokens[token_num].type);
555 /**************************************************************************//**
556 * Compare a JSON string token with a value.
558 * @param[in] json The string which contains the JSON and has already been
560 * @param[in] tok The token which the JSON parser found in the JSON.
561 * @param[in] s The string we're looking for.
563 * @returns Whether the token matches the string or not.
564 * @retval 0 Value matches
565 * @retval -1 Value does not match.
566 *****************************************************************************/
567 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
568 if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
569 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
575 /**************************************************************************//**
576 * Get the VM name provided by the metadata service.
579 *****************************************************************************/
580 const char *openstack_vm_name()
585 /**************************************************************************//**
586 * Get the VM UUID provided by the metadata service.
589 *****************************************************************************/
590 const char *openstack_vm_uuid()