Initial OpenECOMP Demo commit
[demo.git] / vnfs / VES / code / evel_library / metadata.c
1 /**************************************************************************//**
2  * @file
3  * Wrap the OpenStack metadata service.
4  *
5  * License
6  * -------
7  *
8  * Copyright(c) <2016>, AT&T Intellectual Property.  All other rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions are met:
12  *
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.
24  *
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  *****************************************************************************/
36
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 /**************************************************************************//**
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";
54
55 /**************************************************************************//**
56  * How long we're prepared to wait for the metadata service to respond in
57  * seconds.
58  *****************************************************************************/
59 static const int OPENSTACK_METADATA_TIMEOUT = 2;
60
61 /**************************************************************************//**
62  * Size of fields extracted from metadata service.
63  *****************************************************************************/
64 #define MAX_METADATA_STRING  64
65
66 /**************************************************************************//**
67  * UUID of the VM extracted from the OpenStack metadata service.
68  *****************************************************************************/
69 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
70
71 /**************************************************************************//**
72  * Name of the VM extracted from the OpenStack metadata service.
73  *****************************************************************************/
74 static char vm_name[MAX_METADATA_STRING+1] = {0};
75
76 /**************************************************************************//**
77  * How many metadata elements we allow for in the retrieved JSON.
78  *****************************************************************************/
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 /**************************************************************************//**
97  * Download metadata from the OpenStack metadata service.
98  *
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)
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   /***************************************************************************/
122   openstack_metadata_initialize();
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   {
130     rc = EVEL_CURL_LIBRARY_FAIL;
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   {
143     rc = EVEL_CURL_LIBRARY_FAIL;
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   {
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);
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,
166                              evel_write_callback);
167   if (curl_rc != CURLE_OK)
168   {
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);
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   {
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);
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   {
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);
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     {
233       rc = EVEL_CURL_LIBRARY_FAIL;
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   {
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);
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 /**************************************************************************//**
326  * Initialize default values for vm_name and vm_uuid - for testing purposes.
327  *****************************************************************************/
328 void openstack_metadata_initialize()
329 {
330   strncpy(vm_uuid,
331           "Dummy VM UUID - No Metadata available",
332           MAX_METADATA_STRING);
333   strncpy(vm_name,
334           "Dummy VM name - No Metadata available",
335           MAX_METADATA_STRING);
336 }
337
338 /**************************************************************************//**
339  * Get a string value from supplied JSON by matching the key.
340  *
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.
345  *
346  * @param[in] json_string   The string which contains the JSON and has already
347  *                          been parsed.
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.
352  *
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
357  *                            updated.
358  *****************************************************************************/
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 {
365   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
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:
425       rc = EVEL_BAD_JSON_FORMAT;
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 /**************************************************************************//**
439  * Get a top-level string value from supplied JSON by matching the key.
440  *
441  * Unlike json_get_string, this only returns a value that is in the top-level
442  * JSON object.
443  *
444  * @param[in] json_string   The string which contains the JSON and has already
445  *                          been parsed.
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.
450  *
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
455  *                            updated.
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,
460                                                 const char * key,
461                                                 char * value)
462 {
463   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
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:
556       rc = EVEL_BAD_JSON_FORMAT;
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 /**************************************************************************//**
570  * Compare a JSON string token with a value.
571  *
572  * @param[in] json The string which contains the JSON and has already been
573  *                 parsed.
574  * @param[in] tok  The token which the JSON parser found in the JSON.
575  * @param[in] s    The string we're looking for.
576  *
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) {
584     return 0;
585   }
586   return -1;
587 }
588
589 /**************************************************************************//**
590  * Get the VM name provided by the metadata service.
591  *
592  * @returns VM name
593  *****************************************************************************/
594 const char *openstack_vm_name()
595 {
596   return vm_name;
597 }
598
599 /**************************************************************************//**
600  * Get the VM UUID provided by the metadata service.
601  *
602  * @returns VM UUID
603  *****************************************************************************/
604 const char *openstack_vm_uuid()
605 {
606   return vm_uuid;
607 }