1 /**************************************************************************//**
3 * Wrap the OpenStack metadata service.
8 * Copyright(c) <2016>, AT&T Intellectual Property. All other rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
13 * 1. Redistributions of source code must retain the above copyright notice,
14 * this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement: This product includes
20 * software developed by the AT&T.
21 * 4. Neither the name of AT&T nor the names of its contributors may be used to
22 * endorse or promote products derived from this software without specific
23 * prior written permission.
25 * THIS SOFTWARE IS PROVIDED BY AT&T INTELLECTUAL PROPERTY ''AS IS'' AND ANY
26 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28 * DISCLAIMED. IN NO EVENT SHALL AT&T INTELLECTUAL PROPERTY BE LIABLE FOR ANY
29 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
32 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *****************************************************************************/
41 #include <curl/curl.h>
44 #include "evel_internal.h"
48 /**************************************************************************//**
49 * URL on the link-local IP address where we can get the metadata in
50 * machine-friendly format.
51 *****************************************************************************/
52 static const char * OPENSTACK_METADATA_URL =
53 "http://169.254.169.254/openstack/latest/meta_data.json";
55 /**************************************************************************//**
56 * How long we're prepared to wait for the metadata service to respond in
58 *****************************************************************************/
59 static const int OPENSTACK_METADATA_TIMEOUT = 2;
61 /**************************************************************************//**
62 * Size of fields extracted from metadata service.
63 *****************************************************************************/
64 #define MAX_METADATA_STRING 64
66 /**************************************************************************//**
67 * UUID of the VM extracted from the OpenStack metadata service.
68 *****************************************************************************/
69 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
71 /**************************************************************************//**
72 * Name of the VM extracted from the OpenStack metadata service.
73 *****************************************************************************/
74 static char vm_name[MAX_METADATA_STRING+1] = {0};
76 /**************************************************************************//**
77 * How many metadata elements we allow for in the retrieved JSON.
78 *****************************************************************************/
79 static const int MAX_METADATA_TOKENS = 128;
81 /*****************************************************************************/
82 /* Local prototypes. */
83 /*****************************************************************************/
84 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
85 const jsmntok_t *tokens,
89 static EVEL_ERR_CODES json_get_string(const char * json_string,
90 const jsmntok_t *tokens,
94 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
96 /**************************************************************************//**
97 * Download metadata from the OpenStack metadata service.
99 * @param verbosity Controls whether to generate debug to stdout. Zero:
100 * none. Non-zero: generate debug.
101 * @returns Status code
102 * @retval EVEL_SUCCESS On success
103 * @retval ::EVEL_ERR_CODES On failure.
104 *****************************************************************************/
105 EVEL_ERR_CODES openstack_metadata(int verbosity)
107 int rc = EVEL_SUCCESS;
108 CURLcode curl_rc = CURLE_OK;
109 CURL * curl_handle = NULL;
110 MEMORY_CHUNK rx_chunk;
111 char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
112 jsmn_parser json_parser;
113 jsmntok_t tokens[MAX_METADATA_TOKENS];
114 int json_token_count = 0;
118 /***************************************************************************/
119 /* Initialize dummy values for the metadata - needed for test */
121 /***************************************************************************/
122 openstack_metadata_initialize();
124 /***************************************************************************/
125 /* Get a curl handle which we'll use for accessing the metadata service. */
126 /***************************************************************************/
127 curl_handle = curl_easy_init();
128 if (curl_handle == NULL)
130 rc = EVEL_CURL_LIBRARY_FAIL;
131 EVEL_ERROR("Failed to get libcurl handle");
135 /***************************************************************************/
136 /* Prime the library to give friendly error codes. */
137 /***************************************************************************/
138 curl_rc = curl_easy_setopt(curl_handle,
141 if (curl_rc != CURLE_OK)
143 rc = EVEL_CURL_LIBRARY_FAIL;
144 EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
145 "Error code=%d", curl_rc);
149 /***************************************************************************/
150 /* Set the URL for the metadata API. */
151 /***************************************************************************/
152 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
153 if (curl_rc != CURLE_OK)
155 rc = EVEL_CURL_LIBRARY_FAIL;
156 EVEL_ERROR("Failed to initialize libcurl with the API URL. "
157 "Error code=%d (%s)", curl_rc, curl_err_string);
161 /***************************************************************************/
162 /* send all data to this function. */
163 /***************************************************************************/
164 curl_rc = curl_easy_setopt(curl_handle,
165 CURLOPT_WRITEFUNCTION,
166 evel_write_callback);
167 if (curl_rc != CURLE_OK)
169 rc = EVEL_CURL_LIBRARY_FAIL;
170 EVEL_ERROR("Failed to initialize libcurl with the write callback. "
171 "Error code=%d (%s)", curl_rc, curl_err_string);
175 /***************************************************************************/
176 /* some servers don't like requests that are made without a user-agent */
177 /* field, so we provide one. */
178 /***************************************************************************/
179 curl_rc = curl_easy_setopt(curl_handle,
181 "libcurl-agent/1.0");
182 if (curl_rc != CURLE_OK)
184 rc = EVEL_CURL_LIBRARY_FAIL;
185 EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
186 curl_rc, curl_err_string);
190 /***************************************************************************/
191 /* Set the timeout for the operation. */
192 /***************************************************************************/
193 curl_rc = curl_easy_setopt(curl_handle,
195 OPENSTACK_METADATA_TIMEOUT);
196 if (curl_rc != CURLE_OK)
198 rc = EVEL_NO_METADATA;
199 EVEL_ERROR("Failed to initialize libcurl to set timeout. "
200 "Error code=%d (%s)", curl_rc, curl_err_string);
204 /***************************************************************************/
205 /* Create the memory chunk to be used for the response to the post. The */
206 /* will be realloced. */
207 /***************************************************************************/
208 rx_chunk.memory = malloc(1);
209 assert(rx_chunk.memory != NULL);
212 /***************************************************************************/
213 /* Point to the data to be received. */
214 /***************************************************************************/
215 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
216 if (curl_rc != CURLE_OK)
218 rc = EVEL_CURL_LIBRARY_FAIL;
219 EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
220 "Error code=%d (%s)", curl_rc, curl_err_string);
223 EVEL_DEBUG("Initialized data to receive");
225 /***************************************************************************/
226 /* If running in verbose mode generate more output. */
227 /***************************************************************************/
230 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
231 if (curl_rc != CURLE_OK)
233 rc = EVEL_CURL_LIBRARY_FAIL;
234 log_error_state("Failed to initialize libcurl to be verbose. "
235 "Error code=%d", curl_rc);
240 /***************************************************************************/
241 /* Now run off and do what you've been told! */
242 /***************************************************************************/
243 curl_rc = curl_easy_perform(curl_handle);
244 if (curl_rc != CURLE_OK)
246 rc = EVEL_CURL_LIBRARY_FAIL;
247 EVEL_ERROR("Failed to transfer the data from metadata service. "
248 "Error code=%d (%s)", curl_rc, curl_err_string);
252 /*************************************************************************/
253 /* We have some metadata available, so break it out into tokens. */
254 /*************************************************************************/
255 EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
256 EVEL_INFO("Received metadata = %s", rx_chunk.memory);
257 jsmn_init(&json_parser);
258 json_token_count = jsmn_parse(&json_parser,
259 rx_chunk.memory, rx_chunk.size,
260 tokens, MAX_METADATA_TOKENS);
262 /*************************************************************************/
263 /* Check that we parsed some data and that the top level is as expected. */
264 /*************************************************************************/
265 if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
267 rc = EVEL_BAD_METADATA;
268 EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
269 "Error code=%d", json_token_count);
274 EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
278 /*************************************************************************/
279 /* Find the keys we want from the metadata. */
280 /*************************************************************************/
281 if (json_get_string(rx_chunk.memory,
285 vm_uuid) != EVEL_SUCCESS)
287 rc = EVEL_BAD_METADATA;
288 EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
292 EVEL_DEBUG("UUID: %s", vm_uuid);
294 if (json_get_top_level_string(rx_chunk.memory,
298 vm_name) != EVEL_SUCCESS)
300 rc = EVEL_BAD_METADATA;
301 EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
305 EVEL_DEBUG("VM Name: %s", vm_name);
311 /***************************************************************************/
312 /* Shut down the cURL library in a tidy manner. */
313 /***************************************************************************/
314 if (curl_handle != NULL)
316 curl_easy_cleanup(curl_handle);
319 free(rx_chunk.memory);
325 /**************************************************************************//**
326 * Initialize default values for vm_name and vm_uuid - for testing purposes.
327 *****************************************************************************/
328 void openstack_metadata_initialize()
331 "Dummy VM UUID - No Metadata available",
332 MAX_METADATA_STRING);
334 "Dummy VM name - No Metadata available",
335 MAX_METADATA_STRING);
338 /**************************************************************************//**
339 * Get a string value from supplied JSON by matching the key.
341 * As the structure of the metadata we're looking at is pretty straightforward
342 * we don't do anything complex (a la XPath) to extract nested keys with the
343 * same leaf name, for example. Simply walk the structure until we find a
344 * string with the correct value.
346 * @param[in] json_string The string which contains the JSON and has already
348 * @param[in] tokens The tokens which the JSON parser found in the JSON.
349 * @param[in] json_token_count How many tokens were found.
350 * @param[in] key The key we're looking for.
351 * @param[out] value The string we found at @p key.
353 * @returns Status code
354 * @retval EVEL_SUCCESS On success - contents of @p value updated.
355 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
356 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
358 *****************************************************************************/
359 static EVEL_ERR_CODES json_get_string(const char * json_string,
360 const jsmntok_t * tokens,
361 int json_token_count,
365 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
371 /***************************************************************************/
372 /* Check assumptions. */
373 /***************************************************************************/
374 assert(json_string != NULL);
375 assert(tokens != NULL);
376 assert(json_token_count >= 0);
378 assert(value != NULL);
380 for (token_num = 0; token_num < json_token_count; token_num++)
382 switch(tokens[token_num].type)
385 EVEL_DEBUG("Skipping object");
389 EVEL_DEBUG("Skipping array");
393 /***********************************************************************/
394 /* This is a string, so may be what we want. Compare keys. */
395 /***********************************************************************/
396 if (jsoneq(json_string, &tokens[token_num], key) == 0)
398 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
399 EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
400 tokens[token_num + 1].start,
401 tokens[token_num + 1].end);
402 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
403 value[token_len] = '\0';
404 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
410 EVEL_DEBUG("String key did not match");
413 /***********************************************************************/
414 /* Step over the value, whether we used it or not. */
415 /***********************************************************************/
420 EVEL_INFO("Skipping primitive");
425 rc = EVEL_BAD_JSON_FORMAT;
426 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
428 tokens[token_num].type);
438 /**************************************************************************//**
439 * Get a top-level string value from supplied JSON by matching the key.
441 * Unlike json_get_string, this only returns a value that is in the top-level
444 * @param[in] json_string The string which contains the JSON and has already
446 * @param[in] tokens The tokens which the JSON parser found in the JSON.
447 * @param[in] json_token_count How many tokens were found.
448 * @param[in] key The key we're looking for.
449 * @param[out] value The string we found at @p key.
451 * @returns Status code
452 * @retval EVEL_SUCCESS On success - contents of @p value updated.
453 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
454 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
456 *****************************************************************************/
457 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
458 const jsmntok_t * tokens,
459 int json_token_count,
463 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
466 int bracket_count = 0;
467 int string_index = 0;
472 /***************************************************************************/
473 /* Check assumptions. */
474 /***************************************************************************/
475 assert(json_string != NULL);
476 assert(tokens != NULL);
477 assert(json_token_count >= 0);
479 assert(value != NULL);
481 for (token_num = 0; token_num < json_token_count; token_num++)
483 switch(tokens[token_num].type)
486 EVEL_DEBUG("Skipping object");
490 EVEL_DEBUG("Skipping array");
494 /***********************************************************************/
495 /* This is a string, so may be what we want. Compare keys. */
496 /***********************************************************************/
497 if (jsoneq(json_string, &tokens[token_num], key) == 0)
499 /*********************************************************************/
500 /* Count the difference in the number of opening and closing */
501 /* brackets up to this token. This needs to be 1 for a top-level */
502 /* string. Let's just hope we don't have any strings containing */
504 /*********************************************************************/
505 increment = ((string_index < tokens[token_num].start) ? 1 : -1);
507 while (string_index != tokens[token_num].start)
509 if (json_string[string_index] == '{')
511 bracket_count += increment;
513 else if (json_string[string_index] == '}')
515 bracket_count -= increment;
518 string_index += increment;
521 if (bracket_count == 1)
523 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
524 EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
526 tokens[token_num + 1].start,
527 tokens[token_num + 1].end);
528 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
529 value[token_len] = '\0';
530 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
536 EVEL_DEBUG("String key did match, but not at top level");
541 EVEL_DEBUG("String key did not match");
544 /***********************************************************************/
545 /* Step over the value, whether we used it or not. */
546 /***********************************************************************/
551 EVEL_INFO("Skipping primitive");
556 rc = EVEL_BAD_JSON_FORMAT;
557 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
559 tokens[token_num].type);
569 /**************************************************************************//**
570 * Compare a JSON string token with a value.
572 * @param[in] json The string which contains the JSON and has already been
574 * @param[in] tok The token which the JSON parser found in the JSON.
575 * @param[in] s The string we're looking for.
577 * @returns Whether the token matches the string or not.
578 * @retval 0 Value matches
579 * @retval -1 Value does not match.
580 *****************************************************************************/
581 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
582 if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
583 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
589 /**************************************************************************//**
590 * Get the VM name provided by the metadata service.
593 *****************************************************************************/
594 const char *openstack_vm_name()
599 /**************************************************************************//**
600 * Get the VM UUID provided by the metadata service.
603 *****************************************************************************/
604 const char *openstack_vm_uuid()