Implement batching of VES events
[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 #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  * Initialize default values for vm_name and vm_uuid - for testing purposes.
314  *****************************************************************************/
315 void openstack_metadata_initialize()
316 {
317   char hostname[MAX_METADATA_STRING];
318
319   FILE * f = fopen ("/proc/sys/kernel/random/uuid", "r");
320
321   strncpy(vm_uuid,
322           "Dummy VM UUID - No Metadata available",
323           MAX_METADATA_STRING);
324   strncpy(vm_name,
325           "Dummy VM name - No Metadata available",
326           MAX_METADATA_STRING);
327
328   if( gethostname(hostname, 1024) != -1 )
329       strcpy(vm_name,hostname);
330
331   if (f)
332   {
333     if (fgets(vm_uuid,MAX_METADATA_STRING, f)!=NULL)
334     {
335       vm_uuid[strlen( vm_uuid ) - 1 ] = '\0';
336       EVEL_DEBUG("VM UUID: %s", vm_uuid);
337     }
338     fclose (f);
339   }
340
341 }
342
343 /**************************************************************************//**
344  * Get a string value from supplied JSON by matching the key.
345  *
346  * As the structure of the metadata we're looking at is pretty straightforward
347  * we don't do anything complex (a la XPath) to extract nested keys with the
348  * same leaf name, for example.  Simply walk the structure until we find a
349  * string with the correct value.
350  *
351  * @param[in] json_string   The string which contains the JSON and has already
352  *                          been parsed.
353  * @param[in] tokens        The tokens which the JSON parser found in the JSON.
354  * @param[in] json_token_count  How many tokens were found.
355  * @param[in] key           The key we're looking for.
356  * @param[out] value        The string we found at @p key.
357  *
358  * @returns Status code
359  * @retval  EVEL_SUCCESS      On success - contents of @p value updated.
360  * @retval  EVEL_JSON_KEY_NOT_FOUND  Key not found - @p value not updated.
361  * @retval  EVEL_BAD_JSON     Parser hit unexpected data - @p value not
362  *                            updated.
363  *****************************************************************************/
364 static EVEL_ERR_CODES json_get_string(const char * json_string,
365                                       const jsmntok_t * tokens,
366                                       int json_token_count,
367                                       const char * key,
368                                       char * value)
369 {
370   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
371   int token_num = 0;
372   int token_len = 0;
373
374   EVEL_ENTER();
375
376   /***************************************************************************/
377   /* Check assumptions.                                                      */
378   /***************************************************************************/
379   assert(json_string != NULL);
380   assert(tokens != NULL);
381   assert(json_token_count >= 0);
382   assert(key != NULL);
383   assert(value != NULL);
384
385   for (token_num = 0; token_num < json_token_count; token_num++)
386   {
387     switch(tokens[token_num].type)
388     {
389     case JSMN_OBJECT:
390       EVEL_DEBUG("Skipping object");
391       break;
392
393     case JSMN_ARRAY:
394       EVEL_DEBUG("Skipping array");
395       break;
396
397     case JSMN_STRING:
398       /***********************************************************************/
399       /* This is a string, so may be what we want.  Compare keys.            */
400       /***********************************************************************/
401       if (jsoneq(json_string, &tokens[token_num], key) == 0)
402       {
403         token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
404         EVEL_DEBUG("Token %d len %d matches at %d to %d", token_num,
405                                                    tokens[token_num + 1].start,
406                                                    tokens[token_num + 1].end);
407         strncpy(value, json_string + tokens[token_num + 1].start, token_len);
408         value[token_len] = '\0';
409         EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
410         rc = EVEL_SUCCESS;
411         goto exit_label;
412       }
413       else
414       {
415         EVEL_DEBUG("String key did not match");
416       }
417
418       /***********************************************************************/
419       /* Step over the value, whether we used it or not.                     */
420       /***********************************************************************/
421       token_num++;
422       break;
423
424     case JSMN_PRIMITIVE:
425       EVEL_INFO("Skipping primitive");
426       break;
427
428     case JSMN_UNDEFINED:
429     default:
430       rc = EVEL_BAD_JSON_FORMAT;
431       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
432                   token_num,
433                   tokens[token_num].type);
434       goto exit_label;
435     }
436   }
437
438 exit_label:
439   EVEL_EXIT();
440   return rc;
441 }
442
443 /**************************************************************************//**
444  * Get a top-level string value from supplied JSON by matching the key.
445  *
446  * Unlike json_get_string, this only returns a value that is in the top-level
447  * JSON object.
448  *
449  * @param[in] json_string   The string which contains the JSON and has already
450  *                          been parsed.
451  * @param[in] tokens        The tokens which the JSON parser found in the JSON.
452  * @param[in] json_token_count  How many tokens were found.
453  * @param[in] key           The key we're looking for.
454  * @param[out] value        The string we found at @p key.
455  *
456  * @returns Status code
457  * @retval  EVEL_SUCCESS      On success - contents of @p value updated.
458  * @retval  EVEL_JSON_KEY_NOT_FOUND  Key not found - @p value not updated.
459  * @retval  EVEL_BAD_JSON     Parser hit unexpected data - @p value not
460  *                            updated.
461  *****************************************************************************/
462 static EVEL_ERR_CODES json_get_top_level_string(const char * json_string,
463                                                 const jsmntok_t * tokens,
464                                                 int json_token_count,
465                                                 const char * key,
466                                                 char * value)
467 {
468   EVEL_ERR_CODES rc = EVEL_JSON_KEY_NOT_FOUND;
469   int token_num = 0;
470   int token_len = 0;
471   int bracket_count = 0;
472   int string_index = 0;
473   int increment = 0;
474
475   EVEL_ENTER();
476
477   /***************************************************************************/
478   /* Check assumptions.                                                      */
479   /***************************************************************************/
480   assert(json_string != NULL);
481   assert(tokens != NULL);
482   assert(json_token_count >= 0);
483   assert(key != NULL);
484   assert(value != NULL);
485
486   for (token_num = 0; token_num < json_token_count; token_num++)
487   {
488     switch(tokens[token_num].type)
489     {
490     case JSMN_OBJECT:
491       EVEL_DEBUG("Skipping object");
492       break;
493
494     case JSMN_ARRAY:
495       EVEL_DEBUG("Skipping array");
496       break;
497
498     case JSMN_STRING:
499       /***********************************************************************/
500       /* This is a string, so may be what we want.  Compare keys.            */
501       /***********************************************************************/
502       if (jsoneq(json_string, &tokens[token_num], key) == 0)
503       {
504         /*********************************************************************/
505         /* Count the difference in the number of opening and closing         */
506         /* brackets up to this token.  This needs to be 1 for a top-level    */
507         /* string.  Let's just hope we don't have any strings containing     */
508         /* brackets.                                                         */
509         /*********************************************************************/
510         increment = ((string_index < tokens[token_num].start) ? 1 : -1);
511
512         while (string_index != tokens[token_num].start)
513         {
514           if (json_string[string_index] == '{')
515           {
516             bracket_count += increment;
517           }
518           else if (json_string[string_index] == '}')
519           {
520             bracket_count -= increment;
521           }
522
523           string_index += increment;
524         }
525
526         if (bracket_count == 1)
527         {
528           token_len = tokens[token_num + 1].end - tokens[token_num + 1].start;
529           EVEL_DEBUG("Token %d len %d matches at top level at %d to %d",
530                      token_num,
531                      tokens[token_num + 1].start,
532                      tokens[token_num + 1].end);
533           strncpy(value, json_string + tokens[token_num + 1].start, token_len);
534           value[token_len] = '\0';
535           EVEL_DEBUG("Extracted key: \"%s\" Value: \"%s\"", key, value);
536           rc = EVEL_SUCCESS;
537           goto exit_label;
538         }
539         else
540         {
541           EVEL_DEBUG("String key did match, but not at top level");
542         }
543       }
544       else
545       {
546         EVEL_DEBUG("String key did not match");
547       }
548
549       /***********************************************************************/
550       /* Step over the value, whether we used it or not.                     */
551       /***********************************************************************/
552       token_num++;
553       break;
554
555     case JSMN_PRIMITIVE:
556       EVEL_INFO("Skipping primitive");
557       break;
558
559     case JSMN_UNDEFINED:
560     default:
561       rc = EVEL_BAD_JSON_FORMAT;
562       EVEL_ERROR("Unexpected JSON format at token %d (%d)",
563                   token_num,
564                   tokens[token_num].type);
565       goto exit_label;
566     }
567   }
568
569 exit_label:
570   EVEL_EXIT();
571   return rc;
572 }
573
574 /**************************************************************************//**
575  * Compare a JSON string token with a value.
576  *
577  * @param[in] json The string which contains the JSON and has already been
578  *                 parsed.
579  * @param[in] tok  The token which the JSON parser found in the JSON.
580  * @param[in] s    The string we're looking for.
581  *
582  * @returns        Whether the token matches the string or not.
583  * @retval  0      Value matches
584  * @retval  -1     Value does not match.
585  *****************************************************************************/
586 static int jsoneq(const char *json, const jsmntok_t *tok, const char *s) {
587   if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
588       strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
589     return 0;
590   }
591   return -1;
592 }
593
594 /**************************************************************************//**
595  * Get the VM name provided by the metadata service.
596  *
597  * @returns VM name
598  *****************************************************************************/
599 const char *openstack_vm_name()
600 {
601   return vm_name;
602 }
603
604 /**************************************************************************//**
605  * Get the VM UUID provided by the metadata service.
606  *
607  * @returns VM UUID
608  *****************************************************************************/
609 const char *openstack_vm_uuid()
610 {
611   return vm_uuid;
612 }