62ea6b5165319968e582b5530011b58f64405a08
[demo.git] / vnfs / VES5.0 / evel / evel-library / code / evel_library / metadata.c
1 /*************************************************************************//**
2  *
3  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
4  *
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
10  *
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 /**************************************************************************//**
19  * @file
20  * Wrap the OpenStack metadata service.
21  ****************************************************************************/
22
23 #include <string.h>
24 #include <assert.h>
25 #include <malloc.h>
26
27 #include <curl/curl.h>
28
29 #include "evel.h"
30 #include "evel_internal.h"
31 #include "jsmn.h"
32 #include "metadata.h"
33
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";
40
41 /**************************************************************************//**
42  * How long we're prepared to wait for the metadata service to respond in
43  * seconds.
44  *****************************************************************************/
45 static const int OPENSTACK_METADATA_TIMEOUT = 2;
46
47 /**************************************************************************//**
48  * Size of fields extracted from metadata service.
49  *****************************************************************************/
50 #define MAX_METADATA_STRING  64
51
52 /**************************************************************************//**
53  * UUID of the VM extracted from the OpenStack metadata service.
54  *****************************************************************************/
55 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
56
57 /**************************************************************************//**
58  * Name of the VM extracted from the OpenStack metadata service.
59  *****************************************************************************/
60 static char vm_name[MAX_METADATA_STRING+1] = {0};
61
62 /**************************************************************************//**
63  * How many metadata elements we allow for in the retrieved JSON.
64  *****************************************************************************/
65 static const int MAX_METADATA_TOKENS = 128;
66
67 /*****************************************************************************/
68 /* Local prototypes.                                                         */
69 /*****************************************************************************/
70 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
71                                                 const jsmntok_t *tokens,
72                                                 int json_token_count,
73                                                 const char * key,
74                                                 char * value);
75 static EVEL_ERR_CODES json_get_string(const char * json_string,
76                                       const jsmntok_t *tokens,
77                                       int json_token_count,
78                                       const char * key,
79                                       char * value);
80 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
81
82 /**************************************************************************//**
83  * Download metadata from the OpenStack metadata service.
84  *
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)
92 {
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;
101
102   EVEL_ENTER();
103
104   /***************************************************************************/
105   /* Initialize dummy values for the metadata - needed for test              */
106   /* environments.                                                           */
107   /***************************************************************************/
108   openstack_metadata_initialize();
109
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)
115   {
116     rc = EVEL_CURL_LIBRARY_FAIL;
117     EVEL_ERROR("Failed to get libcurl handle");
118     goto exit_label;
119   }
120
121   /***************************************************************************/
122   /* Prime the library to give friendly error codes.                         */
123   /***************************************************************************/
124   curl_rc = curl_easy_setopt(curl_handle,
125                              CURLOPT_ERRORBUFFER,
126                              curl_err_string);
127   if (curl_rc != CURLE_OK)
128   {
129     rc = EVEL_CURL_LIBRARY_FAIL;
130     EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
131                "Error code=%d", curl_rc);
132     goto exit_label;
133   }
134
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)
140   {
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);
144     goto exit_label;
145   }
146
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)
154   {
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);
158     goto exit_label;
159   }
160
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,
166                              CURLOPT_USERAGENT,
167                              "libcurl-agent/1.0");
168   if (curl_rc != CURLE_OK)
169   {
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);
173     goto exit_label;
174   }
175
176   /***************************************************************************/
177   /* Set the timeout for the operation.                                      */
178   /***************************************************************************/
179   curl_rc = curl_easy_setopt(curl_handle,
180                              CURLOPT_TIMEOUT,
181                              OPENSTACK_METADATA_TIMEOUT);
182   if (curl_rc != CURLE_OK)
183   {
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);
187     goto exit_label;
188   }
189
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);
196   rx_chunk.size = 0;
197
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)
203   {
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);
207     goto exit_label;
208   }
209   EVEL_DEBUG("Initialized data to receive");
210
211   /***************************************************************************/
212   /* If running in verbose mode generate more output.                        */
213   /***************************************************************************/
214   if (verbosity > 0)
215   {
216     curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
217     if (curl_rc != CURLE_OK)
218     {
219       rc = EVEL_CURL_LIBRARY_FAIL;
220       log_error_state("Failed to initialize libcurl to be verbose. "
221                       "Error code=%d", curl_rc);
222       goto exit_label;
223     }
224   }
225
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)
231   {
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);
235   }
236   else
237   {
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);
247
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)
252     {
253       rc = EVEL_BAD_METADATA;
254       EVEL_ERROR("Failed to parse received JSON OpenStack metadata.  "
255                  "Error code=%d", json_token_count);
256       goto exit_label;
257     }
258     else
259     {
260       EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata.  ",
261                                                              json_token_count);
262     }
263
264     /*************************************************************************/
265     /* Find the keys we want from the metadata.                              */
266     /*************************************************************************/
267     if (json_get_string(rx_chunk.memory,
268                         tokens,
269                         json_token_count,
270                         "uuid",
271                         vm_uuid) != EVEL_SUCCESS)
272     {
273       rc = EVEL_BAD_METADATA;
274       EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
275     }
276     else
277     {
278       EVEL_DEBUG("UUID: %s", vm_uuid);
279     }
280     if (json_get_top_level_string(rx_chunk.memory,
281                                   tokens,
282                                   json_token_count,
283                                   "name",
284                                   vm_name) != EVEL_SUCCESS)
285     {
286       rc = EVEL_BAD_METADATA;
287       EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
288     }
289     else
290     {
291       EVEL_DEBUG("VM Name: %s", vm_name);
292     }
293   }
294
295 exit_label:
296
297   /***************************************************************************/
298   /* Shut down the cURL library in a tidy manner.                            */
299   /***************************************************************************/
300   if (curl_handle != NULL)
301   {
302     curl_easy_cleanup(curl_handle);
303     curl_handle = NULL;
304   }
305   free(rx_chunk.memory);
306
307   EVEL_EXIT();
308   return rc;
309 }
310
311 /**************************************************************************//**
312  * Initialize default values for vm_name and vm_uuid - for testing purposes.
313  *****************************************************************************/
314 void openstack_metadata_initialize()
315 {
316   strncpy(vm_uuid,
317           "Dummy VM UUID - No Metadata available",
318           MAX_METADATA_STRING);
319   strncpy(vm_name,
320           "Dummy VM name - No Metadata available",
321           MAX_METADATA_STRING);
322 }
323
324 /**************************************************************************//**
325  * Get a string value from supplied JSON by matching the key.
326  *
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.
331  *
332  * @param[in] json_string   The string which contains the JSON and has already
333  *                          been parsed.
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.
338  *
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
343  *                            updated.
344  *****************************************************************************/
345 static EVEL_ERR_CODES json_get_string(const char * json_string,
346                                       const jsmntok_t * tokens,
347                                       int json_token_count,
348                                       const char * key,
349                                       char * value)
350 {
351   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
352   int token_num = 0;
353   int token_len = 0;
354
355   EVEL_ENTER();
356
357   /***************************************************************************/
358   /* Check assumptions.                                                      */
359   /***************************************************************************/
360   assert(json_string != NULL);
361   assert(tokens != NULL);
362   assert(json_token_count >= 0);
363   assert(key != NULL);
364   assert(value != NULL);
365
366   for (token_num = 0; token_num < json_token_count; token_num++)
367   {
368     switch(tokens[token_num].type)
369     {
370     case JSMN_OBJECT:
371       EVEL_DEBUG("Skipping object");
372       break;
373
374     case JSMN_ARRAY:
375       EVEL_DEBUG("Skipping array");
376       break;
377
378     case JSMN_STRING:
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)
383       {
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);
391         rc = EVEL_SUCCESS;
392         goto exit_label;
393       }
394       else
395       {
396         EVEL_DEBUG("String key did not match");
397       }
398
399       /***********************************************************************/
400       /* Step over the value, whether we used it or not.                     */
401       /***********************************************************************/
402       token_num++;
403       break;
404
405     case JSMN_PRIMITIVE:
406       EVEL_INFO("Skipping primitive");
407       break;
408
409     case JSMN_UNDEFINED:
410     default:
411       rc = EVEL_BAD_JSON_FORMAT;
412       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
413                   token_num,
414                   tokens[token_num].type);
415       goto exit_label;
416     }
417   }
418
419 exit_label:
420   EVEL_EXIT();
421   return rc;
422 }
423
424 /**************************************************************************//**
425  * Get a top-level string value from supplied JSON by matching the key.
426  *
427  * Unlike json_get_string, this only returns a value that is in the top-level
428  * JSON object.
429  *
430  * @param[in] json_string   The string which contains the JSON and has already
431  *                          been parsed.
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.
436  *
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
441  *                            updated.
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,
446                                                 const char * key,
447                                                 char * value)
448 {
449   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
450   int token_num = 0;
451   int token_len = 0;
452   int bracket_count = 0;
453   int string_index = 0;
454   int increment = 0;
455
456   EVEL_ENTER();
457
458   /***************************************************************************/
459   /* Check assumptions.                                                      */
460   /***************************************************************************/
461   assert(json_string != NULL);
462   assert(tokens != NULL);
463   assert(json_token_count >= 0);
464   assert(key != NULL);
465   assert(value != NULL);
466
467   for (token_num = 0; token_num < json_token_count; token_num++)
468   {
469     switch(tokens[token_num].type)
470     {
471     case JSMN_OBJECT:
472       EVEL_DEBUG("Skipping object");
473       break;
474
475     case JSMN_ARRAY:
476       EVEL_DEBUG("Skipping array");
477       break;
478
479     case JSMN_STRING:
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)
484       {
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     */
489         /* brackets.                                                         */
490         /*********************************************************************/
491         increment = ((string_index < tokens[token_num].start) ? 1 : -1);
492
493         while (string_index != tokens[token_num].start)
494         {
495           if (json_string[string_index] == '{')
496           {
497             bracket_count += increment;
498           }
499           else if (json_string[string_index] == '}')
500           {
501             bracket_count -= increment;
502           }
503
504           string_index += increment;
505         }
506
507         if (bracket_count == 1)
508         {
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",
511                      token_num,
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);
517           rc = EVEL_SUCCESS;
518           goto exit_label;
519         }
520         else
521         {
522           EVEL_DEBUG("String key did match, but not at top level");
523         }
524       }
525       else
526       {
527         EVEL_DEBUG("String key did not match");
528       }
529
530       /***********************************************************************/
531       /* Step over the value, whether we used it or not.                     */
532       /***********************************************************************/
533       token_num++;
534       break;
535
536     case JSMN_PRIMITIVE:
537       EVEL_INFO("Skipping primitive");
538       break;
539
540     case JSMN_UNDEFINED:
541     default:
542       rc = EVEL_BAD_JSON_FORMAT;
543       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
544                   token_num,
545                   tokens[token_num].type);
546       goto exit_label;
547     }
548   }
549
550 exit_label:
551   EVEL_EXIT();
552   return rc;
553 }
554
555 /**************************************************************************//**
556  * Compare a JSON string token with a value.
557  *
558  * @param[in] json The string which contains the JSON and has already been
559  *                 parsed.
560  * @param[in] tok  The token which the JSON parser found in the JSON.
561  * @param[in] s    The string we're looking for.
562  *
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) {
570     return 0;
571   }
572   return -1;
573 }
574
575 /**************************************************************************//**
576  * Get the VM name provided by the metadata service.
577  *
578  * @returns VM name
579  *****************************************************************************/
580 const char *openstack_vm_name()
581 {
582   return vm_name;
583 }
584
585 /**************************************************************************//**
586  * Get the VM UUID provided by the metadata service.
587  *
588  * @returns VM UUID
589  *****************************************************************************/
590 const char *openstack_vm_uuid()
591 {
592   return vm_uuid;
593 }