1 /*************************************************************************//**
3 * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 ****************************************************************************/
17 /**************************************************************************//**
19 * Wrap the OpenStack metadata service.
20 ****************************************************************************/
26 #include <curl/curl.h>
29 #include "evel_internal.h"
33 /**************************************************************************//**
34 * URL on the link-local IP address where we can get the metadata in
35 * machine-friendly format.
36 *****************************************************************************/
37 static const char * OPENSTACK_METADATA_URL =
38 "http://169.254.169.254/openstack/latest/meta_data.json";
40 /**************************************************************************//**
41 * How long we're prepared to wait for the metadata service to respond in
43 *****************************************************************************/
44 static const int OPENSTACK_METADATA_TIMEOUT = 2;
46 /**************************************************************************//**
47 * Size of fields extracted from metadata service.
48 *****************************************************************************/
49 #define MAX_METADATA_STRING 64
51 /**************************************************************************//**
52 * UUID of the VM extracted from the OpenStack metadata service.
53 *****************************************************************************/
54 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
56 /**************************************************************************//**
57 * Name of the VM extracted from the OpenStack metadata service.
58 *****************************************************************************/
59 static char vm_name[MAX_METADATA_STRING+1] = {0};
61 /**************************************************************************//**
62 * How many metadata elements we allow for in the retrieved JSON.
63 *****************************************************************************/
64 static const int MAX_METADATA_TOKENS = 128;
66 /*****************************************************************************/
67 /* Local prototypes. */
68 /*****************************************************************************/
69 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
70 const jsmntok_t *tokens,
74 static EVEL_ERR_CODES json_get_string(const char * json_string,
75 const jsmntok_t *tokens,
79 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
81 /**************************************************************************//**
82 * Download metadata from the OpenStack metadata service.
84 * @param verbosity Controls whether to generate debug to stdout. Zero:
85 * none. Non-zero: generate debug.
86 * @returns Status code
87 * @retval EVEL_SUCCESS On success
88 * @retval ::EVEL_ERR_CODES On failure.
89 *****************************************************************************/
90 EVEL_ERR_CODES openstack_metadata(int verbosity)
92 int rc = EVEL_SUCCESS;
93 CURLcode curl_rc = CURLE_OK;
94 CURL * curl_handle = NULL;
95 MEMORY_CHUNK rx_chunk;
96 char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
97 jsmn_parser json_parser;
98 jsmntok_t tokens[MAX_METADATA_TOKENS];
99 int json_token_count = 0;
103 /***************************************************************************/
104 /* Initialize dummy values for the metadata - needed for test */
106 /***************************************************************************/
107 openstack_metadata_initialize();
109 /***************************************************************************/
110 /* Get a curl handle which we'll use for accessing the metadata service. */
111 /***************************************************************************/
112 curl_handle = curl_easy_init();
113 if (curl_handle == NULL)
115 rc = EVEL_CURL_LIBRARY_FAIL;
116 EVEL_ERROR("Failed to get libcurl handle");
120 /***************************************************************************/
121 /* Prime the library to give friendly error codes. */
122 /***************************************************************************/
123 curl_rc = curl_easy_setopt(curl_handle,
126 if (curl_rc != CURLE_OK)
128 rc = EVEL_CURL_LIBRARY_FAIL;
129 EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
130 "Error code=%d", curl_rc);
134 /***************************************************************************/
135 /* Set the URL for the metadata API. */
136 /***************************************************************************/
137 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
138 if (curl_rc != CURLE_OK)
140 rc = EVEL_CURL_LIBRARY_FAIL;
141 EVEL_ERROR("Failed to initialize libcurl with the API URL. "
142 "Error code=%d (%s)", curl_rc, curl_err_string);
146 /***************************************************************************/
147 /* send all data to this function. */
148 /***************************************************************************/
149 curl_rc = curl_easy_setopt(curl_handle,
150 CURLOPT_WRITEFUNCTION,
151 evel_write_callback);
152 if (curl_rc != CURLE_OK)
154 rc = EVEL_CURL_LIBRARY_FAIL;
155 EVEL_ERROR("Failed to initialize libcurl with the write callback. "
156 "Error code=%d (%s)", curl_rc, curl_err_string);
160 /***************************************************************************/
161 /* some servers don't like requests that are made without a user-agent */
162 /* field, so we provide one. */
163 /***************************************************************************/
164 curl_rc = curl_easy_setopt(curl_handle,
166 "libcurl-agent/1.0");
167 if (curl_rc != CURLE_OK)
169 rc = EVEL_CURL_LIBRARY_FAIL;
170 EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
171 curl_rc, curl_err_string);
175 /***************************************************************************/
176 /* Set the timeout for the operation. */
177 /***************************************************************************/
178 curl_rc = curl_easy_setopt(curl_handle,
180 OPENSTACK_METADATA_TIMEOUT);
181 if (curl_rc != CURLE_OK)
183 rc = EVEL_NO_METADATA;
184 EVEL_ERROR("Failed to initialize libcurl to set timeout. "
185 "Error code=%d (%s)", curl_rc, curl_err_string);
189 /***************************************************************************/
190 /* Create the memory chunk to be used for the response to the post. The */
191 /* will be realloced. */
192 /***************************************************************************/
193 rx_chunk.memory = malloc(1);
194 assert(rx_chunk.memory != NULL);
197 /***************************************************************************/
198 /* Point to the data to be received. */
199 /***************************************************************************/
200 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
201 if (curl_rc != CURLE_OK)
203 rc = EVEL_CURL_LIBRARY_FAIL;
204 EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
205 "Error code=%d (%s)", curl_rc, curl_err_string);
208 EVEL_DEBUG("Initialized data to receive");
210 /***************************************************************************/
211 /* If running in verbose mode generate more output. */
212 /***************************************************************************/
215 curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
216 if (curl_rc != CURLE_OK)
218 rc = EVEL_CURL_LIBRARY_FAIL;
219 log_error_state("Failed to initialize libcurl to be verbose. "
220 "Error code=%d", curl_rc);
225 /***************************************************************************/
226 /* Now run off and do what you've been told! */
227 /***************************************************************************/
228 curl_rc = curl_easy_perform(curl_handle);
229 if (curl_rc != CURLE_OK)
231 rc = EVEL_CURL_LIBRARY_FAIL;
232 EVEL_ERROR("Failed to transfer the data from metadata service. "
233 "Error code=%d (%s)", curl_rc, curl_err_string);
237 /*************************************************************************/
238 /* We have some metadata available, so break it out into tokens. */
239 /*************************************************************************/
240 EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
241 EVEL_INFO("Received metadata = %s", rx_chunk.memory);
242 jsmn_init(&json_parser);
243 json_token_count = jsmn_parse(&json_parser,
244 rx_chunk.memory, rx_chunk.size,
245 tokens, MAX_METADATA_TOKENS);
247 /*************************************************************************/
248 /* Check that we parsed some data and that the top level is as expected. */
249 /*************************************************************************/
250 if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
252 rc = EVEL_BAD_METADATA;
253 EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
254 "Error code=%d", json_token_count);
259 EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
263 /*************************************************************************/
264 /* Find the keys we want from the metadata. */
265 /*************************************************************************/
266 if (json_get_string(rx_chunk.memory,
270 vm_uuid) != EVEL_SUCCESS)
272 rc = EVEL_BAD_METADATA;
273 EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
277 EVEL_DEBUG("UUID: %s", vm_uuid);
279 if (json_get_top_level_string(rx_chunk.memory,
283 vm_name) != EVEL_SUCCESS)
285 rc = EVEL_BAD_METADATA;
286 EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
290 EVEL_DEBUG("VM Name: %s", vm_name);
296 /***************************************************************************/
297 /* Shut down the cURL library in a tidy manner. */
298 /***************************************************************************/
299 if (curl_handle != NULL)
301 curl_easy_cleanup(curl_handle);
304 free(rx_chunk.memory);
310 /**************************************************************************//**
311 * Initialize default values for vm_name and vm_uuid - for testing purposes.
312 *****************************************************************************/
313 void openstack_metadata_initialize()
316 "Dummy VM UUID - No Metadata available",
317 MAX_METADATA_STRING);
319 "Dummy VM name - No Metadata available",
320 MAX_METADATA_STRING);
323 /**************************************************************************//**
324 * Get a string value from supplied JSON by matching the key.
326 * As the structure of the metadata we're looking at is pretty straightforward
327 * we don't do anything complex (a la XPath) to extract nested keys with the
328 * same leaf name, for example. Simply walk the structure until we find a
329 * string with the correct value.
331 * @param[in] json_string The string which contains the JSON and has already
333 * @param[in] tokens The tokens which the JSON parser found in the JSON.
334 * @param[in] json_token_count How many tokens were found.
335 * @param[in] key The key we're looking for.
336 * @param[out] value The string we found at @p key.
338 * @returns Status code
339 * @retval EVEL_SUCCESS On success - contents of @p value updated.
340 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
341 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
343 *****************************************************************************/
344 static EVEL_ERR_CODES json_get_string(const char * json_string,
345 const jsmntok_t * tokens,
346 int json_token_count,
350 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
356 /***************************************************************************/
357 /* Check assumptions. */
358 /***************************************************************************/
359 assert(json_string != NULL);
360 assert(tokens != NULL);
361 assert(json_token_count >= 0);
363 assert(value != NULL);
365 for (token_num = 0; token_num < json_token_count; token_num++)
367 switch(tokens[token_num].type)
370 EVEL_DEBUG("Skipping object");
374 EVEL_DEBUG("Skipping array");
378 /***********************************************************************/
379 /* This is a string, so may be what we want. Compare keys. */
380 /***********************************************************************/
381 if (jsoneq(json_string, &tokens[token_num], key) == 0)
383 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
384 EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
385 tokens[token_num + 1].start,
386 tokens[token_num + 1].end);
387 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
388 value[token_len] = '\0';
389 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
395 EVEL_DEBUG("String key did not match");
398 /***********************************************************************/
399 /* Step over the value, whether we used it or not. */
400 /***********************************************************************/
405 EVEL_INFO("Skipping primitive");
410 rc = EVEL_BAD_JSON_FORMAT;
411 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
413 tokens[token_num].type);
423 /**************************************************************************//**
424 * Get a top-level string value from supplied JSON by matching the key.
426 * Unlike json_get_string, this only returns a value that is in the top-level
429 * @param[in] json_string The string which contains the JSON and has already
431 * @param[in] tokens The tokens which the JSON parser found in the JSON.
432 * @param[in] json_token_count How many tokens were found.
433 * @param[in] key The key we're looking for.
434 * @param[out] value The string we found at @p key.
436 * @returns Status code
437 * @retval EVEL_SUCCESS On success - contents of @p value updated.
438 * @retval EVEL_JSON_KEY_NOT_FOUND Key not found - @p value not updated.
439 * @retval EVEL_BAD_JSON Parser hit unexpected data - @p value not
441 *****************************************************************************/
442 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
443 const jsmntok_t * tokens,
444 int json_token_count,
448 EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
451 int bracket_count = 0;
452 int string_index = 0;
457 /***************************************************************************/
458 /* Check assumptions. */
459 /***************************************************************************/
460 assert(json_string != NULL);
461 assert(tokens != NULL);
462 assert(json_token_count >= 0);
464 assert(value != NULL);
466 for (token_num = 0; token_num < json_token_count; token_num++)
468 switch(tokens[token_num].type)
471 EVEL_DEBUG("Skipping object");
475 EVEL_DEBUG("Skipping array");
479 /***********************************************************************/
480 /* This is a string, so may be what we want. Compare keys. */
481 /***********************************************************************/
482 if (jsoneq(json_string, &tokens[token_num], key) == 0)
484 /*********************************************************************/
485 /* Count the difference in the number of opening and closing */
486 /* brackets up to this token. This needs to be 1 for a top-level */
487 /* string. Let's just hope we don't have any strings containing */
489 /*********************************************************************/
490 increment = ((string_index < tokens[token_num].start) ? 1 : -1);
492 while (string_index != tokens[token_num].start)
494 if (json_string[string_index] == '{')
496 bracket_count += increment;
498 else if (json_string[string_index] == '}')
500 bracket_count -= increment;
503 string_index += increment;
506 if (bracket_count == 1)
508 token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
509 EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
511 tokens[token_num + 1].start,
512 tokens[token_num + 1].end);
513 strncpy(value, json_string + tokens[token_num + 1].start, token_len);
514 value[token_len] = '\0';
515 EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
521 EVEL_DEBUG("String key did match, but not at top level");
526 EVEL_DEBUG("String key did not match");
529 /***********************************************************************/
530 /* Step over the value, whether we used it or not. */
531 /***********************************************************************/
536 EVEL_INFO("Skipping primitive");
541 rc = EVEL_BAD_JSON_FORMAT;
542 EVEL_ERROR("Unexpected JSON format at token %d (%d)",
544 tokens[token_num].type);
554 /**************************************************************************//**
555 * Compare a JSON string token with a value.
557 * @param[in] json The string which contains the JSON and has already been
559 * @param[in] tok The token which the JSON parser found in the JSON.
560 * @param[in] s The string we're looking for.
562 * @returns Whether the token matches the string or not.
563 * @retval 0 Value matches
564 * @retval -1 Value does not match.
565 *****************************************************************************/
566 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
567 if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
568 strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
574 /**************************************************************************//**
575 * Get the VM name provided by the metadata service.
578 *****************************************************************************/
579 const char *openstack_vm_name()
584 /**************************************************************************//**
585 * Get the VM UUID provided by the metadata service.
588 *****************************************************************************/
589 const char *openstack_vm_uuid()