Initial VES for DANOS vRouter
[demo.git] / vnfs / VESreporting_vFW5.0_DANOS / 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 #include <unistd.h>
27
28 #include <curl/curl.h>
29
30 #include "evel.h"
31 #include "evel_internal.h"
32 #include "jsmn.h"
33 #include "metadata.h"
34
35 /**************************************************************************//**
36  * URL on the link-local IP address where we can get the metadata in
37  * machine-friendly format.
38  *****************************************************************************/
39 static const char * OPENSTACK_METADATA_URL =
40                       "http://169.254.169.254/openstack/latest/meta_data.json";
41
42 /**************************************************************************//**
43  * How long we're prepared to wait for the metadata service to respond in
44  * seconds.
45  *****************************************************************************/
46 static const int OPENSTACK_METADATA_TIMEOUT = 2;
47
48 /**************************************************************************//**
49  * Size of fields extracted from metadata service.
50  *****************************************************************************/
51 #define MAX_METADATA_STRING  64
52
53 /**************************************************************************//**
54  * UUID of the VM extracted from the OpenStack metadata service.
55  *****************************************************************************/
56 static char vm_uuid[MAX_METADATA_STRING+1] = {0};
57
58 /**************************************************************************//**
59  * Name of the VM extracted from the OpenStack metadata service.
60  *****************************************************************************/
61 static char vm_name[MAX_METADATA_STRING+1] = {0};
62
63 /**************************************************************************//**
64  * How many metadata elements we allow for in the retrieved JSON.
65  *****************************************************************************/
66 static const int MAX_METADATA_TOKENS = 128;
67
68 /*****************************************************************************/
69 /* Local prototypes.                                                         */
70 /*****************************************************************************/
71 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
72                                                 const jsmntok_t *tokens,
73                                                 int json_token_count,
74                                                 const char * key,
75                                                 char * value);
76 static EVEL_ERR_CODES json_get_string(const char * json_string,
77                                       const jsmntok_t *tokens,
78                                       int json_token_count,
79                                       const char * key,
80                                       char * value);
81 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s);
82
83 /**************************************************************************//**
84  * Download metadata from the OpenStack metadata service.
85  *
86  * @param verbosity   Controls whether to generate debug to stdout.  Zero:
87  *                    none.  Non-zero: generate debug.
88  * @returns Status code
89  * @retval  EVEL_SUCCESS      On success
90  * @retval  ::EVEL_ERR_CODES  On failure.
91  *****************************************************************************/
92 EVEL_ERR_CODES openstack_metadata(int verbosity)
93 {
94   int rc = EVEL_SUCCESS;
95   CURLcode curl_rc = CURLE_OK;
96   CURL * curl_handle = NULL;
97   MEMORY_CHUNK rx_chunk;
98   char curl_err_string[CURL_ERROR_SIZE] = "<NULL>";
99   jsmn_parser json_parser;
100   jsmntok_t tokens[MAX_METADATA_TOKENS];
101   int json_token_count = 0;
102
103   EVEL_ENTER();
104
105   /***************************************************************************/
106   /* Initialize dummy values for the metadata - needed for test              */
107   /* environments.                                                           */
108   /***************************************************************************/
109   openstack_metadata_initialize();
110
111   /***************************************************************************/
112   /* Get a curl handle which we'll use for accessing the metadata service.   */
113   /***************************************************************************/
114   curl_handle = curl_easy_init();
115   if (curl_handle == NULL)
116   {
117     rc = EVEL_CURL_LIBRARY_FAIL;
118     EVEL_ERROR("Failed to get libcurl handle");
119     goto exit_label;
120   }
121
122   /***************************************************************************/
123   /* Prime the library to give friendly error codes.                         */
124   /***************************************************************************/
125   curl_rc = curl_easy_setopt(curl_handle,
126                              CURLOPT_ERRORBUFFER,
127                              curl_err_string);
128   if (curl_rc != CURLE_OK)
129   {
130     rc = EVEL_CURL_LIBRARY_FAIL;
131     EVEL_ERROR("Failed to initialize libcurl to provide friendly errors. "
132                "Error code=%d", curl_rc);
133     goto exit_label;
134   }
135
136   /***************************************************************************/
137   /* Set the URL for the metadata API.                                       */
138   /***************************************************************************/
139   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_URL, OPENSTACK_METADATA_URL);
140   if (curl_rc != CURLE_OK)
141   {
142     rc = EVEL_CURL_LIBRARY_FAIL;
143     EVEL_ERROR("Failed to initialize libcurl with the API URL. "
144                "Error code=%d (%s)", curl_rc, curl_err_string);
145     goto exit_label;
146   }
147
148   /***************************************************************************/
149   /* send all data to this function.                                         */
150   /***************************************************************************/
151   curl_rc = curl_easy_setopt(curl_handle,
152                              CURLOPT_WRITEFUNCTION,
153                              evel_write_callback);
154   if (curl_rc != CURLE_OK)
155   {
156     rc = EVEL_CURL_LIBRARY_FAIL;
157     EVEL_ERROR("Failed to initialize libcurl with the write callback. "
158              "Error code=%d (%s)", curl_rc, curl_err_string);
159     goto exit_label;
160   }
161
162   /***************************************************************************/
163   /* some servers don't like requests that are made without a user-agent     */
164   /* field, so we provide one.                                               */
165   /***************************************************************************/
166   curl_rc = curl_easy_setopt(curl_handle,
167                              CURLOPT_USERAGENT,
168                              "libcurl-agent/1.0");
169   if (curl_rc != CURLE_OK)
170   {
171     rc = EVEL_CURL_LIBRARY_FAIL;
172     EVEL_ERROR("Failed to initialize libcurl to upload.  Error code=%d (%s)",
173                curl_rc, curl_err_string);
174     goto exit_label;
175   }
176
177   /***************************************************************************/
178   /* Set the timeout for the operation.                                      */
179   /***************************************************************************/
180   curl_rc = curl_easy_setopt(curl_handle,
181                              CURLOPT_TIMEOUT,
182                              OPENSTACK_METADATA_TIMEOUT);
183   if (curl_rc != CURLE_OK)
184   {
185     rc = EVEL_NO_METADATA;
186     EVEL_ERROR("Failed to initialize libcurl to set timeout. "
187                "Error code=%d (%s)", curl_rc, curl_err_string);
188     goto exit_label;
189   }
190
191   /***************************************************************************/
192   /* Create the memory chunk to be used for the response to the post.  The   */
193   /* will be realloced.                                                      */
194   /***************************************************************************/
195   rx_chunk.memory = malloc(1);
196   assert(rx_chunk.memory != NULL);
197   rx_chunk.size = 0;
198
199   /***************************************************************************/
200   /* Point to the data to be received.                                       */
201   /***************************************************************************/
202   curl_rc = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&rx_chunk);
203   if (curl_rc != CURLE_OK)
204   {
205     rc = EVEL_CURL_LIBRARY_FAIL;
206     EVEL_ERROR("Failed to initialize libcurl to receive metadata. "
207                "Error code=%d (%s)", curl_rc, curl_err_string);
208     goto exit_label;
209   }
210   EVEL_DEBUG("Initialized data to receive");
211
212   /***************************************************************************/
213   /* If running in verbose mode generate more output.                        */
214   /***************************************************************************/
215   if (verbosity > 0)
216   {
217     curl_rc = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
218     if (curl_rc != CURLE_OK)
219     {
220       rc = EVEL_CURL_LIBRARY_FAIL;
221       log_error_state("Failed to initialize libcurl to be verbose. "
222                       "Error code=%d", curl_rc);
223       goto exit_label;
224     }
225   }
226
227   /***************************************************************************/
228   /* Now run off and do what you've been told!                               */
229   /***************************************************************************/
230   curl_rc = curl_easy_perform(curl_handle);
231   if (curl_rc != CURLE_OK)
232   {
233     rc = EVEL_CURL_LIBRARY_FAIL;
234     EVEL_ERROR("Failed to transfer the data from metadata service. "
235                "Error code=%d (%s)", curl_rc, curl_err_string);
236   }
237   else
238   {
239     /*************************************************************************/
240     /* We have some metadata available, so break it out into tokens.         */
241     /*************************************************************************/
242     EVEL_DEBUG("Received metadata size = %d", rx_chunk.size);
243     EVEL_INFO("Received metadata = %s", rx_chunk.memory);
244     jsmn_init(&json_parser);
245     json_token_count = jsmn_parse(&json_parser,
246                                   rx_chunk.memory, rx_chunk.size,
247                                   tokens, MAX_METADATA_TOKENS);
248
249     /*************************************************************************/
250     /* Check that we parsed some data and that the top level is as expected. */
251     /*************************************************************************/
252     if (json_token_count < 0 || tokens[0].type != JSMN_OBJECT)
253     {
254       rc = EVEL_BAD_METADATA;
255       EVEL_ERROR("Failed to parse received JSON OpenStack metadata.  "
256                  "Error code=%d", json_token_count);
257       goto exit_label;
258     }
259     else
260     {
261       EVEL_DEBUG("Extracted %d tokens from the JSON OpenStack metadata.  ",
262                                                              json_token_count);
263     }
264
265     /*************************************************************************/
266     /* Find the keys we want from the metadata.                              */
267     /*************************************************************************/
268     if (json_get_string(rx_chunk.memory,
269                         tokens,
270                         json_token_count,
271                         "uuid",
272                         vm_uuid) != EVEL_SUCCESS)
273     {
274       rc = EVEL_BAD_METADATA;
275       EVEL_ERROR("Failed to extract UUID from OpenStack metadata");
276     }
277     else
278     {
279       EVEL_DEBUG("UUID: %s", vm_uuid);
280     }
281     if (json_get_top_level_string(rx_chunk.memory,
282                                   tokens,
283                                   json_token_count,
284                                   "name",
285                                   vm_name) != EVEL_SUCCESS)
286     {
287       rc = EVEL_BAD_METADATA;
288       EVEL_ERROR("Failed to extract VM Name from OpenStack metadata");
289     }
290     else
291     {
292       EVEL_DEBUG("VM Name: %s", vm_name);
293     }
294   }
295
296 exit_label:
297
298   /***************************************************************************/
299   /* Shut down the cURL library in a tidy manner.                            */
300   /***************************************************************************/
301   if (curl_handle != NULL)
302   {
303     curl_easy_cleanup(curl_handle);
304     curl_handle = NULL;
305   }
306   free(rx_chunk.memory);
307
308   EVEL_EXIT();
309   return rc;
310 }
311
312
313 /**************************************************************************//**
314  * Initialize default values for vm_name and vm_uuid - for testing purposes.
315  *****************************************************************************/
316 void openstack_metadata_initialize()
317 {
318   char hostname[MAX_METADATA_STRING];
319
320   FILE * f = fopen ("/proc/sys/kernel/random/uuid", "r");
321
322   strncpy(vm_uuid,
323           "Dummy VM UUID - No Metadata available",
324           MAX_METADATA_STRING);
325   strncpy(vm_name,
326           "Dummy VM name - No Metadata available",
327           MAX_METADATA_STRING);
328
329   if( gethostname(hostname, 1024) != -1 )
330       strcpy(vm_name,hostname);
331
332   if (f)
333   {
334     if (fgets(vm_uuid,MAX_METADATA_STRING, f)!=NULL)
335     {
336       vm_uuid[strlen( vm_uuid ) - 1 ] = '\0';
337       EVEL_DEBUG("VM UUID: %s", vm_uuid);
338     }
339     fclose (f);
340   }
341
342 }
343
344 /**************************************************************************//**
345  * Initialize value for vm_name for all coming events
346  * @param  source_name  Source name string.
347  *                      Must confirm with EVEL source name standard
348  * @returns Status code
349  * @retval  EVEL_SUCCESS      On success
350  * @retval  ::EVEL_ERR_CODES  On failure.
351  *****************************************************************************/
352 EVEL_ERR_CODES evel_set_source_name(char * src_name)
353 {
354   if( src_name && src_name[0] )
355   {
356       if( strlen(src_name) < MAX_METADATA_STRING ){
357           strcpy(vm_name,src_name);
358           return EVEL_SUCCESS;
359        } else 
360           EVEL_DEBUG("Event Source Name too long");
361   }
362   else
363      EVEL_DEBUG("Invalid Event Source Name string");
364   return EVEL_ERR_GEN_FAIL;
365 }
366
367 /**************************************************************************//**
368  * Get a string value from supplied JSON by matching the key.
369  *
370  * As the structure of the metadata we're looking at is pretty straightforward
371  * we don't do anything complex (a la XPath) to extract nested keys with the
372  * same leaf name, for example.  Simply walk the structure until we find a
373  * string with the correct value.
374  *
375  * @param[in] json_string   The string which contains the JSON and has already
376  *                          been parsed.
377  * @param[in] tokens        The tokens which the JSON parser found in the JSON.
378  * @param[in] json_token_count  How many tokens were found.
379  * @param[in] key           The key we're looking for.
380  * @param[out] value        The string we found at @p key.
381  *
382  * @returns Status code
383  * @retval  EVEL_SUCCESS      On success - contents of @p value updated.
384  * @retval  EVEL_JSON_KEY_NOT_FOUND  Key not found - @p value not updated.
385  * @retval  EVEL_BAD_JSON     Parser hit unexpected data - @p value not
386  *                            updated.
387  *****************************************************************************/
388 static EVEL_ERR_CODES json_get_string(const char * json_string,
389                                       const jsmntok_t * tokens,
390                                       int json_token_count,
391                                       const char * key,
392                                       char * value)
393 {
394   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
395   int token_num = 0;
396   int token_len = 0;
397
398   EVEL_ENTER();
399
400   /***************************************************************************/
401   /* Check assumptions.                                                      */
402   /***************************************************************************/
403   assert(json_string != NULL);
404   assert(tokens != NULL);
405   assert(json_token_count >= 0);
406   assert(key != NULL);
407   assert(value != NULL);
408
409   for (token_num = 0; token_num < json_token_count; token_num++)
410   {
411     switch(tokens[token_num].type)
412     {
413     case JSMN_OBJECT:
414       EVEL_DEBUG("Skipping object");
415       break;
416
417     case JSMN_ARRAY:
418       EVEL_DEBUG("Skipping array");
419       break;
420
421     case JSMN_STRING:
422       /***********************************************************************/
423       /* This is a string, so may be what we want.  Compare keys.            */
424       /***********************************************************************/
425       if (jsoneq(json_string, &tokens[token_num], key) == 0)
426       {
427         token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
428         EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
429                                                    tokens[token_num + 1].start,
430                                                    tokens[token_num + 1].end);
431         strncpy(value, json_string + tokens[token_num + 1].start, token_len);
432         value[token_len] = '\0';
433         EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
434         rc = EVEL_SUCCESS;
435         goto exit_label;
436       }
437       else
438       {
439         EVEL_DEBUG("String key did not match");
440       }
441
442       /***********************************************************************/
443       /* Step over the value, whether we used it or not.                     */
444       /***********************************************************************/
445       token_num++;
446       break;
447
448     case JSMN_PRIMITIVE:
449       EVEL_INFO("Skipping primitive");
450       break;
451
452     case JSMN_UNDEFINED:
453     default:
454       rc = EVEL_BAD_JSON_FORMAT;
455       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
456                   token_num,
457                   tokens[token_num].type);
458       goto exit_label;
459     }
460   }
461
462 exit_label:
463   EVEL_EXIT();
464   return rc;
465 }
466
467 /**************************************************************************//**
468  * Get a top-level string value from supplied JSON by matching the key.
469  *
470  * Unlike json_get_string, this only returns a value that is in the top-level
471  * JSON object.
472  *
473  * @param[in] json_string   The string which contains the JSON and has already
474  *                          been parsed.
475  * @param[in] tokens        The tokens which the JSON parser found in the JSON.
476  * @param[in] json_token_count  How many tokens were found.
477  * @param[in] key           The key we're looking for.
478  * @param[out] value        The string we found at @p key.
479  *
480  * @returns Status code
481  * @retval  EVEL_SUCCESS      On success - contents of @p value updated.
482  * @retval  EVEL_JSON_KEY_NOT_FOUND  Key not found - @p value not updated.
483  * @retval  EVEL_BAD_JSON     Parser hit unexpected data - @p value not
484  *                            updated.
485  *****************************************************************************/
486 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
487                                                 const jsmntok_t * tokens,
488                                                 int json_token_count,
489                                                 const char * key,
490                                                 char * value)
491 {
492   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
493   int token_num = 0;
494   int token_len = 0;
495   int bracket_count = 0;
496   int string_index = 0;
497   int increment = 0;
498
499   EVEL_ENTER();
500
501   /***************************************************************************/
502   /* Check assumptions.                                                      */
503   /***************************************************************************/
504   assert(json_string != NULL);
505   assert(tokens != NULL);
506   assert(json_token_count >= 0);
507   assert(key != NULL);
508   assert(value != NULL);
509
510   for (token_num = 0; token_num < json_token_count; token_num++)
511   {
512     switch(tokens[token_num].type)
513     {
514     case JSMN_OBJECT:
515       EVEL_DEBUG("Skipping object");
516       break;
517
518     case JSMN_ARRAY:
519       EVEL_DEBUG("Skipping array");
520       break;
521
522     case JSMN_STRING:
523       /***********************************************************************/
524       /* This is a string, so may be what we want.  Compare keys.            */
525       /***********************************************************************/
526       if (jsoneq(json_string, &tokens[token_num], key) == 0)
527       {
528         /*********************************************************************/
529         /* Count the difference in the number of opening and closing         */
530         /* brackets up to this token.  This needs to be 1 for a top-level    */
531         /* string.  Let's just hope we don't have any strings containing     */
532         /* brackets.                                                         */
533         /*********************************************************************/
534         increment = ((string_index < tokens[token_num].start) ? 1 : -1);
535
536         while (string_index != tokens[token_num].start)
537         {
538           if (json_string[string_index] == '{')
539           {
540             bracket_count += increment;
541           }
542           else if (json_string[string_index] == '}')
543           {
544             bracket_count -= increment;
545           }
546
547           string_index += increment;
548         }
549
550         if (bracket_count == 1)
551         {
552           token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
553           EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
554                      token_num,
555                      tokens[token_num + 1].start,
556                      tokens[token_num + 1].end);
557           strncpy(value, json_string + tokens[token_num + 1].start, token_len);
558           value[token_len] = '\0';
559           EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
560           rc = EVEL_SUCCESS;
561           goto exit_label;
562         }
563         else
564         {
565           EVEL_DEBUG("String key did match, but not at top level");
566         }
567       }
568       else
569       {
570         EVEL_DEBUG("String key did not match");
571       }
572
573       /***********************************************************************/
574       /* Step over the value, whether we used it or not.                     */
575       /***********************************************************************/
576       token_num++;
577       break;
578
579     case JSMN_PRIMITIVE:
580       EVEL_INFO("Skipping primitive");
581       break;
582
583     case JSMN_UNDEFINED:
584     default:
585       rc = EVEL_BAD_JSON_FORMAT;
586       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
587                   token_num,
588                   tokens[token_num].type);
589       goto exit_label;
590     }
591   }
592
593 exit_label:
594   EVEL_EXIT();
595   return rc;
596 }
597
598 /**************************************************************************//**
599  * Compare a JSON string token with a value.
600  *
601  * @param[in] json The string which contains the JSON and has already been
602  *                 parsed.
603  * @param[in] tok  The token which the JSON parser found in the JSON.
604  * @param[in] s    The string we're looking for.
605  *
606  * @returns        Whether the token matches the string or not.
607  * @retval  0      Value matches
608  * @retval  -1     Value does not match.
609  *****************************************************************************/
610 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
611   if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
612       strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
613     return 0;
614   }
615   return -1;
616 }
617
618 /**************************************************************************//**
619  * Get the VM name provided by the metadata service.
620  *
621  * @returns VM name
622  *****************************************************************************/
623 const char *openstack_vm_name()
624 {
625   return vm_name;
626 }
627
628 /**************************************************************************//**
629  * Get the VM UUID provided by the metadata service.
630  *
631  * @returns VM UUID
632  *****************************************************************************/
633 const char *openstack_vm_uuid()
634 {
635   return vm_uuid;
636 }