AT&T ECOMP Vendor Event Listener library  0.1
metadata.c
Go to the documentation of this file.
1 /**************************************************************************/
37 #include <string.h>
38 #include <assert.h>
39 #include <malloc.h>
40 
41 #include <curl/curl.h>
42 
43 #include "evel.h"
44 #include "evel_internal.h"
45 #include "jsmn.h"
46 #include "metadata.h"
47 
48 /**************************************************************************/
52 static const char * OPENSTACK_METADATA_URL =
53  "http://169.254.169.254/openstack/latest/meta_data.json";
54 
55 /**************************************************************************/
59 static const int OPENSTACK_METADATA_TIMEOUT = 2;
60 
61 /**************************************************************************/
64 #define MAX_METADATA_STRING 64
65 
66 /**************************************************************************/
69 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
70 
71 /**************************************************************************/
74 static char vm_name[MAX_METADATA_STRING+1] = {0};
75 
76 /**************************************************************************/
79 static const int MAX_METADATA_TOKENS = 128;
80 
81 /*****************************************************************************/
82 /* Local prototypes. */
83 /*****************************************************************************/
84 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
85  const jsmntok_t *tokens,
86  int json_token_count,
87  const char * key,
88  char * value);
89 static EVEL_ERR_CODES json_get_string(const char * json_string,
90  const jsmntok_t *tokens,
91  int json_token_count,
92  const char * key,
93  char * value);
94 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
95 
96 /**************************************************************************/
106 {
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;
115 
116  EVEL_ENTER();
117 
118  /***************************************************************************/
119  /* Initialize dummy values for the metadata - needed for test */
120  /* environments. */
121  /***************************************************************************/
123 
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)
129  {
131  EVEL_ERROR("Failed to get libcurl handle");
132  goto exit_label;
133  }
134 
135  /***************************************************************************/
136  /* Prime the library to give friendly error codes. */
137  /***************************************************************************/
138  curl_rc = curl_easy_setopt(curl_handle,
139  CURLOPT_ERRORBUFFER,
140  curl_err_string);
141  if (curl_rc != CURLE_OK)
142  {
144  EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
145  "Error code=%d", curl_rc);
146  goto exit_label;
147  }
148 
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)
154  {
156  EVEL_ERROR("Failed to initialize libcurl with the API URL. "
157  "Error code=%d (%s)", curl_rc, curl_err_string);
158  goto exit_label;
159  }
160 
161  /***************************************************************************/
162  /* send all data to this function. */
163  /***************************************************************************/
164  curl_rc = curl_easy_setopt(curl_handle,
165  CURLOPT_WRITEFUNCTION,
167  if (curl_rc != CURLE_OK)
168  {
170  EVEL_ERROR("Failed to initialize libcurl with the write callback. "
171  "Error code=%d (%s)", curl_rc, curl_err_string);
172  goto exit_label;
173  }
174 
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,
180  CURLOPT_USERAGENT,
181  "libcurl-agent/1.0");
182  if (curl_rc != CURLE_OK)
183  {
185  EVEL_ERROR("Failed to initialize libcurl to upload. Error code=%d (%s)",
186  curl_rc, curl_err_string);
187  goto exit_label;
188  }
189 
190  /***************************************************************************/
191  /* Set the timeout for the operation. */
192  /***************************************************************************/
193  curl_rc = curl_easy_setopt(curl_handle,
194  CURLOPT_TIMEOUT,
195  OPENSTACK_METADATA_TIMEOUT);
196  if (curl_rc != CURLE_OK)
197  {
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);
201  goto exit_label;
202  }
203 
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);
210  rx_chunk.size = 0;
211 
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)
217  {
219  EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
220  "Error code=%d (%s)", curl_rc, curl_err_string);
221  goto exit_label;
222  }
223  EVEL_DEBUG("Initialized data to receive");
224 
225  /***************************************************************************/
226  /* If running in verbose mode generate more output. */
227  /***************************************************************************/
228  if (verbosity > 0)
229  {
230  curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
231  if (curl_rc != CURLE_OK)
232  {
234  log_error_state("Failed to initialize libcurl to be verbose. "
235  "Error code=%d", curl_rc);
236  goto exit_label;
237  }
238  }
239 
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)
245  {
247  EVEL_ERROR("Failed to transfer the data from metadata service. "
248  "Error code=%d (%s)", curl_rc, curl_err_string);
249  }
250  else
251  {
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);
261 
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)
266  {
267  rc = EVEL_BAD_METADATA;
268  EVEL_ERROR("Failed to parse received JSON OpenStack metadata. "
269  "Error code=%d", json_token_count);
270  goto exit_label;
271  }
272  else
273  {
274  EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata. ",
275  json_token_count);
276  }
277 
278  /*************************************************************************/
279  /* Find the keys we want from the metadata. */
280  /*************************************************************************/
281  if (json_get_string(rx_chunk.memory,
282  tokens,
283  json_token_count,
284  "uuid",
285  vm_uuid) != EVEL_SUCCESS)
286  {
287  rc = EVEL_BAD_METADATA;
288  EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
289  }
290  else
291  {
292  EVEL_DEBUG("UUID: %s", vm_uuid);
293  }
294  if (json_get_top_level_string(rx_chunk.memory,
295  tokens,
296  json_token_count,
297  "name",
298  vm_name) != EVEL_SUCCESS)
299  {
300  rc = EVEL_BAD_METADATA;
301  EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
302  }
303  else
304  {
305  EVEL_DEBUG("VM Name: %s", vm_name);
306  }
307  }
308 
309 exit_label:
310 
311  /***************************************************************************/
312  /* Shut down the cURL library in a tidy manner. */
313  /***************************************************************************/
314  if (curl_handle != NULL)
315  {
316  curl_easy_cleanup(curl_handle);
317  curl_handle = NULL;
318  }
319  free(rx_chunk.memory);
320 
321  EVEL_EXIT();
322  return rc;
323 }
324 
325 /**************************************************************************/
329 {
330  strncpy(vm_uuid,
331  "Dummy VM UUID - No Metadata available",
333  strncpy(vm_name,
334  "Dummy VM name - No Metadata available",
336 }
337 
338 /**************************************************************************/
359 static EVEL_ERR_CODES json_get_string(const char * json_string,
360  const jsmntok_t * tokens,
361  int json_token_count,
362  const char * key,
363  char * value)
364 {
366  int token_num = 0;
367  int token_len = 0;
368 
369  EVEL_ENTER();
370 
371  /***************************************************************************/
372  /* Check assumptions. */
373  /***************************************************************************/
374  assert(json_string != NULL);
375  assert(tokens != NULL);
376  assert(json_token_count >= 0);
377  assert(key != NULL);
378  assert(value != NULL);
379 
380  for (token_num = 0; token_num < json_token_count; token_num++)
381  {
382  switch(tokens[token_num].type)
383  {
384  case JSMN_OBJECT:
385  EVEL_DEBUG("Skipping object");
386  break;
387 
388  case JSMN_ARRAY:
389  EVEL_DEBUG("Skipping array");
390  break;
391 
392  case JSMN_STRING:
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)
397  {
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);
405  rc = EVEL_SUCCESS;
406  goto exit_label;
407  }
408  else
409  {
410  EVEL_DEBUG("String key did not match");
411  }
412 
413  /***********************************************************************/
414  /* Step over the value, whether we used it or not. */
415  /***********************************************************************/
416  token_num++;
417  break;
418 
419  case JSMN_PRIMITIVE:
420  EVEL_INFO("Skipping primitive");
421  break;
422 
423  case JSMN_UNDEFINED:
424  default:
426  EVEL_ERROR("Unexpected JSON format at token %d (%d)",
427  token_num,
428  tokens[token_num].type);
429  goto exit_label;
430  }
431  }
432 
433 exit_label:
434  EVEL_EXIT();
435  return rc;
436 }
437 
438 /**************************************************************************/
457 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
458  const jsmntok_t * tokens,
459  int json_token_count,
460  const char * key,
461  char * value)
462 {
464  int token_num = 0;
465  int token_len = 0;
466  int bracket_count = 0;
467  int string_index = 0;
468  int increment = 0;
469 
470  EVEL_ENTER();
471 
472  /***************************************************************************/
473  /* Check assumptions. */
474  /***************************************************************************/
475  assert(json_string != NULL);
476  assert(tokens != NULL);
477  assert(json_token_count >= 0);
478  assert(key != NULL);
479  assert(value != NULL);
480 
481  for (token_num = 0; token_num < json_token_count; token_num++)
482  {
483  switch(tokens[token_num].type)
484  {
485  case JSMN_OBJECT:
486  EVEL_DEBUG("Skipping object");
487  break;
488 
489  case JSMN_ARRAY:
490  EVEL_DEBUG("Skipping array");
491  break;
492 
493  case JSMN_STRING:
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)
498  {
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 */
503  /* brackets. */
504  /*********************************************************************/
505  increment = ((string_index < tokens[token_num].start) ? 1 : -1);
506 
507  while (string_index != tokens[token_num].start)
508  {
509  if (json_string[string_index] == '{')
510  {
511  bracket_count += increment;
512  }
513  else if (json_string[string_index] == '}')
514  {
515  bracket_count -= increment;
516  }
517 
518  string_index += increment;
519  }
520 
521  if (bracket_count == 1)
522  {
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",
525  token_num,
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);
531  rc = EVEL_SUCCESS;
532  goto exit_label;
533  }
534  else
535  {
536  EVEL_DEBUG("String key did match, but not at top level");
537  }
538  }
539  else
540  {
541  EVEL_DEBUG("String key did not match");
542  }
543 
544  /***********************************************************************/
545  /* Step over the value, whether we used it or not. */
546  /***********************************************************************/
547  token_num++;
548  break;
549 
550  case JSMN_PRIMITIVE:
551  EVEL_INFO("Skipping primitive");
552  break;
553 
554  case JSMN_UNDEFINED:
555  default:
557  EVEL_ERROR("Unexpected JSON format at token %d (%d)",
558  token_num,
559  tokens[token_num].type);
560  goto exit_label;
561  }
562  }
563 
564 exit_label:
565  EVEL_EXIT();
566  return rc;
567 }
568 
569 /**************************************************************************/
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) {
584  return 0;
585  }
586  return -1;
587 }
588 
589 /**************************************************************************/
594 const char *openstack_vm_name()
595 {
596  return vm_name;
597 }
598 
599 /**************************************************************************/
604 const char *openstack_vm_uuid()
605 {
606  return vm_uuid;
607 }
JSON token description.
Definition: jsmn.h:40
#define EVEL_DEBUG(FMT,...)
Definition: evel.h:3621
OpenStack metadata invalid format.
Definition: evel.h:78
#define EVEL_INFO(FMT,...)
Definition: evel.h:3622
void openstack_metadata_initialize()
Initialize default values for vm_name and vm_uuid - for testing purposes.
Definition: metadata.c:328
A chunk of memory used in the cURL functions.
Definition: evel_internal.h:77
Non-specific failure.
Definition: evel.h:71
size_t evel_write_callback(void *contents, size_t size, size_t nmemb, void *userp)
Callback function to provide returned data.
Wrap the OpenStack metadata service.
#define EVEL_EXIT()
Definition: evel.h:3631
#define EVEL_ENTER()
Definition: evel.h:3626
#define MAX_METADATA_STRING
Size of fields extracted from metadata service.
Definition: metadata.c:64
Header for EVEL library.
jsmntype_t type
Definition: jsmn.h:41
JSON parser.
Definition: jsmn.h:54
EVEL_ERR_CODES openstack_metadata(int verbosity)
Download metadata from the OpenStack metadata service.
Definition: metadata.c:105
#define EVEL_ERROR(FMT,...)
Definition: evel.h:3624
int start
Definition: jsmn.h:42
EVEL_ERR_CODES
Error codes.
Definition: evel.h:68
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens)
Parse JSON string and fill tokens.
Definition: jsmn.c:151
int end
Definition: jsmn.h:43
const char * openstack_vm_name()
Get the VM name provided by the metadata service.
Definition: metadata.c:594
void log_error_state(char *format,...)
Definition: evel_logging.c:98
void jsmn_init(jsmn_parser *parser)
Creates a new parser based over a given buffer with an array of tokens available. ...
Definition: jsmn.c:306
Failed to retrieve OpenStack metadata.
Definition: evel.h:77
const char * openstack_vm_uuid()
Get the VM UUID provided by the metadata service.
Definition: metadata.c:604
EVEL internal definitions.
Attempt to raise event when inactive.
Definition: evel.h:76
JSON failed to parse correctly.
Definition: evel.h:79