1 /**
2  * collectd - src/redfish.c
3  *
4  * Copyright(c) 2018 Intel Corporation. All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * Authors:
25  *   Marcin Mozejko <marcinx.mozejko@intel.com>
26  *   Martin Kennelly <martin.kennelly@intel.com>
27  *   Adrian Boczkowski <adrianx.boczkowski@intel.com>
28  **/
29 
30 #include "collectd.h"
31 
32 #include "utils/avltree/avltree.h"
33 #include "utils/common/common.h"
34 #include "utils/deq/deq.h"
35 #include "utils_llist.h"
36 
37 #include <redfish.h>
38 #define PLUGIN_NAME "redfish"
39 #define MAX_STR_LEN 128
40 
41 struct redfish_property_s {
42   char *name;
43   char *plugin_inst;
44   char *type;
45   char *type_inst;
46 };
47 typedef struct redfish_property_s redfish_property_t;
48 
49 struct redfish_resource_s {
50   char *name;
51   llist_t *properties;
52 };
53 typedef struct redfish_resource_s redfish_resource_t;
54 
55 struct redfish_query_s {
56   char *name;
57   char *endpoint;
58   llist_t *resources;
59 };
60 typedef struct redfish_query_s redfish_query_t;
61 
62 struct redfish_service_s {
63   char *name;
64   char *host;
65   char *user;
66   char *passwd;
67   char *token;
68   unsigned int flags;
69   char **queries;      /* List of queries */
70   llist_t *query_ptrs; /* Pointers to query structs */
71   size_t queries_num;
72   enumeratorAuthentication auth;
73   redfishService *redfish;
74 };
75 typedef struct redfish_service_s redfish_service_t;
76 
77 struct redfish_payload_ctx_s {
78   redfish_service_t *service;
79   redfish_query_t *query;
80 };
81 typedef struct redfish_payload_ctx_s redfish_payload_ctx_t;
82 
83 enum redfish_value_type_e { VAL_TYPE_STR = 0, VAL_TYPE_INT, VAL_TYPE_REAL };
84 typedef enum redfish_value_type_e redfish_value_type_t;
85 
86 union redfish_value_u {
87   double real;
88   int integer;
89   char *string;
90 };
91 typedef union redfish_value_u redfish_value_t;
92 
93 typedef struct redfish_job_s {
94   DEQ_LINKS(struct redfish_job_s);
95   redfish_payload_ctx_t *service_query;
96 } redfish_job_t;
97 
98 DEQ_DECLARE(redfish_job_t, redfish_job_list_t);
99 
100 struct redfish_ctx_s {
101   llist_t *services;
102   c_avl_tree_t *queries;
103   pthread_t worker_thread;
104   redfish_job_list_t jobs;
105 };
106 typedef struct redfish_ctx_s redfish_ctx_t;
107 
108 /* Globals */
109 static redfish_ctx_t ctx;
110 
111 static int redfish_cleanup(void);
112 static int redfish_validate_config(void);
113 static void *redfish_worker_thread(void __attribute__((unused)) * args);
114 
115 #if COLLECT_DEBUG
redfish_print_config(void)116 static void redfish_print_config(void) {
117   DEBUG(PLUGIN_NAME ": ====================CONFIGURATION====================");
118   DEBUG(PLUGIN_NAME ": SERVICES: %d", llist_size(ctx.services));
119   for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) {
120     redfish_service_t *s = (redfish_service_t *)le->value;
121     char queries_str[MAX_STR_LEN];
122 
123     strjoin(queries_str, MAX_STR_LEN, s->queries, s->queries_num, ", ");
124 
125     DEBUG(PLUGIN_NAME ": --------------------");
126     DEBUG(PLUGIN_NAME ": Service: %s", s->name);
127     DEBUG(PLUGIN_NAME ":   Host: %s", s->host);
128 
129     if (s->user && s->passwd) {
130       DEBUG(PLUGIN_NAME ":   User: %s", s->user);
131       DEBUG(PLUGIN_NAME ":   Passwd: %s", s->passwd);
132     } else if (s->token)
133       DEBUG(PLUGIN_NAME ":   Token: %s", s->token);
134 
135     DEBUG(PLUGIN_NAME ":   Queries[%" PRIsz "]: (%s)", s->queries_num,
136           queries_str);
137   }
138 
139   DEBUG(PLUGIN_NAME ": =====================================================");
140 
141   c_avl_iterator_t *i = c_avl_get_iterator(ctx.queries);
142   char *key;
143   redfish_query_t *q;
144 
145   DEBUG(PLUGIN_NAME ": QUERIES: %d", c_avl_size(ctx.queries));
146 
147   while (c_avl_iterator_next(i, (void *)&key, (void *)&q) == 0) {
148     DEBUG(PLUGIN_NAME ": --------------------");
149     DEBUG(PLUGIN_NAME ": Query: %s", q->name);
150     DEBUG(PLUGIN_NAME ":   Endpoint: %s", q->endpoint);
151     for (llentry_t *le = llist_head(q->resources); le != NULL; le = le->next) {
152       redfish_resource_t *r = (redfish_resource_t *)le->value;
153       DEBUG(PLUGIN_NAME ":   Resource: %s", r->name);
154       for (llentry_t *le = llist_head(r->properties); le != NULL;
155            le = le->next) {
156         redfish_property_t *p = (redfish_property_t *)le->value;
157         DEBUG(PLUGIN_NAME ":     Property: %s", p->name);
158         DEBUG(PLUGIN_NAME ":       PluginInstance: %s", p->plugin_inst);
159         DEBUG(PLUGIN_NAME ":       Type: %s", p->type);
160         DEBUG(PLUGIN_NAME ":       TypeInstance: %s", p->type_inst);
161       }
162     }
163   }
164 
165   c_avl_iterator_destroy(i);
166   DEBUG(PLUGIN_NAME ": =====================================================");
167 }
168 #endif
169 
redfish_service_destroy(redfish_service_t * service)170 static void redfish_service_destroy(redfish_service_t *service) {
171   /* This is checked internally by cleanupServiceEnumerator() also,
172    * but as long as it's a third-party library let's be a little 'defensive' */
173   if (service->redfish != NULL)
174     cleanupServiceEnumerator(service->redfish);
175 
176   /* Destroy all service members, sfree() as well as strarray_free()
177    * and llist_destroy() are safe to call on NULL argument */
178   sfree(service->name);
179   sfree(service->host);
180   sfree(service->user);
181   sfree(service->passwd);
182   sfree(service->token);
183   strarray_free(service->queries, (size_t)service->queries_num);
184   llist_destroy(service->query_ptrs);
185 
186   sfree(service);
187 }
188 
redfish_job_destroy(redfish_job_t * job)189 static void redfish_job_destroy(redfish_job_t *job) {
190   sfree(job->service_query);
191   sfree(job);
192 }
193 
redfish_init(void)194 static int redfish_init(void) {
195 #if COLLECT_DEBUG
196   redfish_print_config();
197 #endif
198   int ret = redfish_validate_config();
199 
200   if (ret != 0) {
201     ERROR(PLUGIN_NAME ": Validation of configuration file failed");
202     return ret;
203   }
204 
205   DEQ_INIT(ctx.jobs);
206   ret = pthread_create(&ctx.worker_thread, NULL, redfish_worker_thread, NULL);
207 
208   if (ret != 0) {
209     ERROR(PLUGIN_NAME ": Creation of thread failed");
210     return ret;
211   }
212 
213   for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) {
214     redfish_service_t *service = (redfish_service_t *)le->value;
215     /* Ignore redfish version */
216     service->flags |= REDFISH_FLAG_SERVICE_NO_VERSION_DOC;
217 
218     /* Preparing struct for authentication */
219     if (service->user && service->passwd) {
220       service->auth.authCodes.userPass.username = service->user;
221       service->auth.authCodes.userPass.password = service->passwd;
222       service->redfish = createServiceEnumerator(
223           service->host, NULL, &service->auth, service->flags);
224     } else if (service->token) {
225       service->auth.authCodes.authToken.token = service->token;
226       service->auth.authType = REDFISH_AUTH_BEARER_TOKEN;
227       service->redfish = createServiceEnumerator(
228           service->host, NULL, &service->auth, service->flags);
229     } else {
230       service->redfish =
231           createServiceEnumerator(service->host, NULL, NULL, service->flags);
232     }
233 
234     service->query_ptrs = llist_create();
235     if (service->query_ptrs == NULL) {
236       ERROR(PLUGIN_NAME ": Failed to allocate memory for service query list");
237       goto error;
238     }
239 
240     /* Preparing query pointers list for every service */
241     for (size_t i = 0; i < service->queries_num; i++) {
242       redfish_query_t *ptr;
243       if (c_avl_get(ctx.queries, (void *)service->queries[i], (void *)&ptr) !=
244           0) {
245         ERROR(PLUGIN_NAME ": Cannot find a service query in a context");
246         goto error;
247       }
248 
249       llentry_t *entry = llentry_create(ptr->name, ptr);
250       if (entry != NULL)
251         llist_append(service->query_ptrs, entry);
252       else {
253         ERROR(PLUGIN_NAME ": Failed to allocate memory for a query list entry");
254         goto error;
255       }
256     }
257   }
258 
259   return 0;
260 
261 error:
262   /* Freeing libredfish resources & llists */
263   for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) {
264     redfish_service_t *service = (redfish_service_t *)le->value;
265 
266     redfish_service_destroy(service);
267   }
268   return -ENOMEM;
269 }
270 
redfish_preconfig(void)271 static int redfish_preconfig(void) {
272   /* Creating placeholder for services */
273   ctx.services = llist_create();
274   if (ctx.services == NULL)
275     goto error;
276 
277   /* Creating placeholder for queries */
278   ctx.queries = c_avl_create((void *)strcmp);
279   if (ctx.services == NULL)
280     goto free_services;
281 
282   return 0;
283 
284 free_services:
285   llist_destroy(ctx.services);
286 error:
287   ERROR(PLUGIN_NAME ": Failed to allocate memory for plugin context");
288   return -ENOMEM;
289 }
290 
redfish_config_property(redfish_resource_t * resource,oconfig_item_t * cfg_item)291 static int redfish_config_property(redfish_resource_t *resource,
292                                    oconfig_item_t *cfg_item) {
293   assert(resource != NULL);
294   assert(cfg_item != NULL);
295 
296   redfish_property_t *property = calloc(1, sizeof(*property));
297 
298   if (property == NULL) {
299     ERROR(PLUGIN_NAME ": Failed to allocate memory for property");
300     return -ENOMEM;
301   }
302 
303   int ret = cf_util_get_string(cfg_item, &property->name);
304   if (ret != 0) {
305     ERROR(PLUGIN_NAME ": Could not get property argument in resource section "
306                       "named \"%s\"",
307           resource->name);
308     ret = -EINVAL;
309     goto free_all;
310   }
311 
312   for (int i = 0; i < cfg_item->children_num; i++) {
313     oconfig_item_t *opt = cfg_item->children + i;
314     if (strcasecmp("PluginInstance", opt->key) == 0)
315       ret = cf_util_get_string(opt, &property->plugin_inst);
316     else if (strcasecmp("Type", opt->key) == 0)
317       ret = cf_util_get_string(opt, &property->type);
318     else if (strcasecmp("TypeInstance", opt->key) == 0)
319       ret = cf_util_get_string(opt, &property->type_inst);
320     else {
321       ERROR(PLUGIN_NAME ": Invalid option \"%s\" in property \"%s\" "
322                         "in resource \"%s\"",
323             opt->key, property->name, resource->name);
324       ret = -EINVAL;
325       goto free_all;
326     }
327 
328     if (ret != 0) {
329       ERROR(PLUGIN_NAME ": Something went wrong going through attributes in "
330                         "property named \"%s\" in resource named \"%s\"",
331             property->name, resource->name);
332       goto free_all;
333     }
334   }
335 
336   llentry_t *entry = llentry_create(property->name, property);
337   if (entry == NULL) {
338     ERROR(PLUGIN_NAME ": Failed to allocate memory for property");
339     ret = -ENOMEM;
340     goto free_all;
341   }
342   llist_append(resource->properties, entry);
343 
344   return 0;
345 
346 free_all:
347   sfree(property->name);
348   sfree(property->plugin_inst);
349   sfree(property->type);
350   sfree(property->type_inst);
351   sfree(property);
352   return ret;
353 }
354 
redfish_config_resource(redfish_query_t * query,oconfig_item_t * cfg_item)355 static int redfish_config_resource(redfish_query_t *query,
356                                    oconfig_item_t *cfg_item) {
357   assert(query != NULL);
358   assert(cfg_item != NULL);
359 
360   redfish_resource_t *resource = calloc(1, sizeof(*resource));
361 
362   if (resource == NULL) {
363     ERROR(PLUGIN_NAME ": Failed to allocate memory for resource");
364     return -ENOMEM;
365   }
366 
367   resource->properties = llist_create();
368 
369   if (resource->properties == NULL)
370     goto free_memory;
371 
372   int ret = cf_util_get_string(cfg_item, &resource->name);
373   if (ret != 0) {
374     ERROR(PLUGIN_NAME ": Could not get resource name for query named \"%s\"",
375           query->name);
376     goto free_memory;
377   }
378   for (int i = 0; i < cfg_item->children_num; i++) {
379     oconfig_item_t *opt = cfg_item->children + i;
380     if (strcasecmp("Property", opt->key) != 0) {
381       WARNING(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key);
382       continue;
383     }
384 
385     ret = redfish_config_property(resource, opt);
386 
387     if (ret != 0) {
388       goto free_memory;
389     }
390   }
391 
392   llentry_t *entry = llentry_create(resource->name, resource);
393   if (entry == NULL) {
394     ERROR(PLUGIN_NAME ": Failed to allocate memory for resource list entry");
395     goto free_memory;
396   }
397   llist_append(query->resources, entry);
398 
399   return 0;
400 
401 free_memory:
402   sfree(resource->name);
403   llist_destroy(resource->properties);
404   sfree(resource);
405   return -1;
406 }
407 
redfish_config_query(oconfig_item_t * cfg_item,c_avl_tree_t * queries)408 static int redfish_config_query(oconfig_item_t *cfg_item,
409                                 c_avl_tree_t *queries) {
410   redfish_query_t *query = calloc(1, sizeof(*query));
411 
412   if (query == NULL) {
413     ERROR(PLUGIN_NAME ": Failed to allocate memory for query");
414     return -ENOMEM;
415   }
416 
417   query->resources = llist_create();
418 
419   int ret;
420   if (query->resources == NULL) {
421     ret = -ENOMEM;
422     goto free_all;
423   }
424 
425   ret = cf_util_get_string(cfg_item, &query->name);
426   if (ret != 0) {
427     ERROR(PLUGIN_NAME ": Unable to get query name. Query ignored");
428     ret = -EINVAL;
429     goto free_all;
430   }
431 
432   for (int i = 0; i < cfg_item->children_num; i++) {
433     oconfig_item_t *opt = cfg_item->children + i;
434 
435     if (strcasecmp("Endpoint", opt->key) == 0)
436       ret = cf_util_get_string(opt, &query->endpoint);
437     else if (strcasecmp("Resource", opt->key) == 0)
438       ret = redfish_config_resource(query, opt);
439     else {
440       ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key);
441       ret = -EINVAL;
442       goto free_all;
443     }
444 
445     if (ret != 0) {
446       ERROR(PLUGIN_NAME ": Something went wrong processing query \"%s\"",
447             query->name);
448       ret = -EINVAL;
449       goto free_all;
450     }
451   }
452 
453   ret = c_avl_insert(queries, query->name, query);
454 
455   if (ret != 0)
456     goto free_all;
457 
458   return 0;
459 
460 free_all:
461   sfree(query->name);
462   sfree(query->endpoint);
463   llist_destroy(query->resources);
464   sfree(query);
465   return ret;
466 }
467 
redfish_read_queries(oconfig_item_t * cfg_item,char *** queries_ptr)468 static int redfish_read_queries(oconfig_item_t *cfg_item, char ***queries_ptr) {
469   char **queries = NULL;
470   size_t queries_num = 0;
471 
472   for (int i = 0; i < cfg_item->values_num; ++i) {
473     strarray_add(&queries, &queries_num, cfg_item->values[i].value.string);
474   }
475 
476   if (queries_num != (size_t)cfg_item->values_num) {
477     ERROR(PLUGIN_NAME ": Failed to allocate memory for query list");
478     strarray_free(queries, queries_num);
479     return -ENOMEM;
480   }
481 
482   *queries_ptr = queries;
483   return 0;
484 }
485 
redfish_config_service(oconfig_item_t * cfg_item)486 static int redfish_config_service(oconfig_item_t *cfg_item) {
487   redfish_service_t *service = calloc(1, sizeof(*service));
488 
489   if (service == NULL) {
490     ERROR(PLUGIN_NAME ": Failed to allocate memory for service");
491     return -ENOMEM;
492   }
493 
494   int ret = cf_util_get_string(cfg_item, &service->name);
495   if (ret != 0) {
496     ERROR(PLUGIN_NAME ": A service was defined without an argument");
497     goto free_service;
498   }
499 
500   for (int i = 0; i < cfg_item->children_num; i++) {
501     oconfig_item_t *opt = cfg_item->children + i;
502 
503     if (strcasecmp("Host", opt->key) == 0)
504       ret = cf_util_get_string(opt, &service->host);
505     else if (strcasecmp("User", opt->key) == 0)
506       ret = cf_util_get_string(opt, &service->user);
507     else if (strcasecmp("Passwd", opt->key) == 0)
508       ret = cf_util_get_string(opt, &service->passwd);
509     else if (strcasecmp("Token", opt->key) == 0)
510       ret = cf_util_get_string(opt, &service->token);
511     else if (strcasecmp("Queries", opt->key) == 0) {
512       ret = redfish_read_queries(opt, &service->queries);
513       service->queries_num = opt->values_num;
514     } else {
515       ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key);
516     }
517 
518     if (ret != 0) {
519       ERROR(PLUGIN_NAME ": Something went wrong processing the service named \
520             \"%s\"",
521             service->name);
522       goto free_service;
523     }
524   }
525 
526   llentry_t *entry = llentry_create(service->name, service);
527 
528   if (entry != NULL)
529     llist_append(ctx.services, entry);
530   else {
531     ERROR(PLUGIN_NAME ": Failed to create list for service name \"%s\"",
532           service->name);
533     goto free_service;
534   }
535 
536   return 0;
537 
538 free_service:
539   redfish_service_destroy(service);
540   return -1;
541 }
542 
redfish_config(oconfig_item_t * cfg_item)543 static int redfish_config(oconfig_item_t *cfg_item) {
544   int ret = redfish_preconfig();
545 
546   if (ret != 0)
547     return ret;
548 
549   for (int i = 0; i < cfg_item->children_num; i++) {
550     oconfig_item_t *child = cfg_item->children + i;
551 
552     if (strcasecmp("Query", child->key) == 0)
553       ret = redfish_config_query(child, ctx.queries);
554     else if (strcasecmp("Service", child->key) == 0)
555       ret = redfish_config_service(child);
556     else {
557       ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", child->key);
558     }
559 
560     if (ret != 0) {
561       redfish_cleanup();
562       return ret;
563     }
564   }
565 
566   return 0;
567 }
568 
redfish_validate_config(void)569 static int redfish_validate_config(void) {
570   /* Service validation */
571   for (llentry_t *llserv = llist_head(ctx.services); llserv != NULL;
572        llserv = llserv->next) {
573     redfish_service_t *service = llserv->value;
574     if (service->name == NULL) {
575       ERROR(PLUGIN_NAME ": A service has no name");
576       return -EINVAL;
577     }
578     if (service->host == NULL) {
579       ERROR(PLUGIN_NAME ": Service \"%s\" has no host attribute",
580             service->name);
581       return -EINVAL;
582     }
583     if ((service->user == NULL) ^ (service->passwd == NULL)) {
584       ERROR(PLUGIN_NAME ": Service \"%s\" does not have user and/or password "
585                         "defined",
586             service->name);
587       return -EINVAL;
588     }
589     if (service->user == NULL && service->token == NULL) {
590       ERROR(PLUGIN_NAME ": Service \"%s\" does not have an user/pass or "
591                         "token defined",
592             service->name);
593       return -EINVAL;
594     }
595     if (service->queries_num == 0)
596       WARNING(PLUGIN_NAME ": Service \"%s\" does not have queries",
597               service->name);
598 
599     for (int i = 0; i < service->queries_num; i++) {
600       redfish_query_t *query_query;
601       bool found = false;
602       char *key;
603       c_avl_iterator_t *query_iter = c_avl_get_iterator(ctx.queries);
604       while (c_avl_iterator_next(query_iter, (void *)&key,
605                                  (void *)&query_query) == 0 &&
606              !found) {
607         if (query_query->name != NULL && service->queries[i] != NULL &&
608             strcmp(query_query->name, service->queries[i]) == 0) {
609           found = true;
610         }
611       }
612 
613       if (!found) {
614         ERROR(PLUGIN_NAME ": Query named \"%s\" in service \"%s\" not found",
615               service->queries[i], service->name);
616         c_avl_iterator_destroy(query_iter);
617         return -EINVAL;
618       }
619 
620       c_avl_iterator_destroy(query_iter);
621     }
622   }
623 
624   c_avl_iterator_t *queries_iter = c_avl_get_iterator(ctx.queries);
625   char *key;
626   redfish_query_t *query;
627 
628   /* Query validation */
629   while (c_avl_iterator_next(queries_iter, (void *)&key, (void *)&query) == 0) {
630     if (query->name == NULL) {
631       ERROR(PLUGIN_NAME ": A query does not have a name");
632       goto error;
633     }
634     if (query->endpoint == NULL) {
635       ERROR(PLUGIN_NAME ": Query \"%s\" does not have a valid endpoint",
636             query->name);
637       goto error;
638     }
639     for (llentry_t *llres = llist_head(query->resources); llres != NULL;
640          llres = llres->next) {
641       redfish_resource_t *resource = (redfish_resource_t *)llres->value;
642       /* Resource validation */
643       if (resource->name == NULL) {
644         WARNING(PLUGIN_NAME ": A resource in query \"%s\" is not named",
645                 query->name);
646       }
647       /* Property validation */
648       for (llentry_t *llprop = llist_head(resource->properties); llprop != NULL;
649            llprop = llprop->next) {
650         redfish_property_t *prop = (redfish_property_t *)llprop->value;
651         if (prop->name == NULL) {
652           ERROR(PLUGIN_NAME ": A property has no name in query \"%s\"",
653                 query->name);
654           goto error;
655         }
656         if (prop->plugin_inst == NULL) {
657           ERROR(PLUGIN_NAME ": A plugin instance is not defined in property "
658                             "\"%s\" in query \"%s\"",
659                 prop->name, query->name);
660           goto error;
661         }
662         if (prop->type == NULL) {
663           ERROR(PLUGIN_NAME ": Type is not defined in property \"%s\" in "
664                             "query \"%s\"",
665                 prop->name, query->name);
666           goto error;
667         }
668       }
669     }
670   }
671 
672   c_avl_iterator_destroy(queries_iter);
673 
674   return 0;
675 
676 error:
677   c_avl_iterator_destroy(queries_iter);
678   return -EINVAL;
679 }
680 
redfish_convert_val(redfish_value_t * value,redfish_value_type_t src_type,value_t * vl,int dst_type)681 static int redfish_convert_val(redfish_value_t *value,
682                                redfish_value_type_t src_type, value_t *vl,
683                                int dst_type) {
684   switch (dst_type) {
685   case DS_TYPE_GAUGE:
686     if (src_type == VAL_TYPE_STR)
687       vl->gauge = strtod(value->string, NULL);
688     else if (src_type == VAL_TYPE_INT)
689       vl->gauge = (gauge_t)value->integer;
690     else if (src_type == VAL_TYPE_REAL)
691       vl->gauge = value->real;
692     break;
693   case DS_TYPE_DERIVE:
694     if (src_type == VAL_TYPE_STR)
695       vl->derive = strtoll(value->string, NULL, 0);
696     else if (src_type == VAL_TYPE_INT)
697       vl->derive = (derive_t)value->integer;
698     else if (src_type == VAL_TYPE_REAL)
699       vl->derive = (derive_t)value->real;
700     break;
701   case DS_TYPE_COUNTER:
702     if (src_type == VAL_TYPE_STR)
703       vl->derive = strtoull(value->string, NULL, 0);
704     else if (src_type == VAL_TYPE_INT)
705       vl->derive = (derive_t)value->integer;
706     else if (src_type == VAL_TYPE_REAL)
707       vl->derive = (derive_t)value->real;
708     break;
709   case DS_TYPE_ABSOLUTE:
710     if (src_type == VAL_TYPE_STR)
711       vl->absolute = strtoull(value->string, NULL, 0);
712     else if (src_type == VAL_TYPE_INT)
713       vl->absolute = (absolute_t)value->integer;
714     else if (src_type == VAL_TYPE_REAL)
715       vl->absolute = (absolute_t)value->real;
716     break;
717   default:
718     ERROR(PLUGIN_NAME ": Invalid data set type. Cannot convert value");
719     return -EINVAL;
720   }
721 
722   return 0;
723 }
724 
redfish_json_get_string(char * const value,const size_t value_len,const json_t * const json)725 static int redfish_json_get_string(char *const value, const size_t value_len,
726                                    const json_t *const json) {
727   if (json_is_string(json)) {
728     const char *str_val = json_string_value(json);
729     sstrncpy(value, str_val, value_len);
730     return 0;
731   } else if (json_is_integer(json)) {
732     snprintf(value, value_len, "%d", (int)json_integer_value(json));
733     return 0;
734   }
735 
736   ERROR(PLUGIN_NAME ": Expected JSON value to be a string or an integer");
737   return -EINVAL;
738 }
739 
redfish_process_payload_property(const redfish_property_t * prop,const json_t * json_array,const redfish_resource_t * res,const redfish_service_t * serv)740 static void redfish_process_payload_property(const redfish_property_t *prop,
741                                              const json_t *json_array,
742                                              const redfish_resource_t *res,
743                                              const redfish_service_t *serv) {
744   /* Iterating through array of sensor(s) */
745   for (int i = 0; i < json_array_size(json_array); i++) {
746     json_t *item = json_array_get(json_array, i);
747     if (item == NULL) {
748       ERROR(PLUGIN_NAME ": Failure retrieving array member for resource \"%s\"",
749             res->name);
750       continue;
751     }
752     json_t *object = json_object_get(item, prop->name);
753     if (object == NULL) {
754       ERROR(PLUGIN_NAME
755             ": Failure retreiving property \"%s\" from resource \"%s\"",
756             prop->name, res->name);
757       continue;
758     }
759     value_list_t v1 = VALUE_LIST_INIT;
760     v1.values_len = 1;
761     if (prop->plugin_inst != NULL)
762       sstrncpy(v1.plugin_instance, prop->plugin_inst,
763                sizeof(v1.plugin_instance));
764     if (prop->type_inst != NULL)
765       sstrncpy(v1.type_instance, prop->type_inst, sizeof(v1.type_instance));
766     else {
767       /* Retrieving MemberId of sensor */
768       json_t *member_id = json_object_get(item, "MemberId");
769       if (member_id == NULL) {
770         ERROR(PLUGIN_NAME
771               ": Failed to get MemberId for property \"%s\" in resource "
772               "\"%s\"",
773               prop->name, res->name);
774         continue;
775       }
776 
777       int ret = redfish_json_get_string(v1.type_instance,
778                                         sizeof(v1.type_instance), member_id);
779 
780       if (ret != 0) {
781         ERROR(PLUGIN_NAME ": Cannot convert \"%s\" to a type instance",
782               prop->type_inst);
783         continue;
784       }
785     }
786 
787     /* Checking whether real or integer value */
788     redfish_value_t value;
789     redfish_value_type_t type = VAL_TYPE_STR;
790     if (json_is_string(object)) {
791       value.string = (char *)json_string_value(object);
792     } else if (json_is_integer(object)) {
793       type = VAL_TYPE_INT;
794       value.integer = json_integer_value(object);
795     } else if (json_is_real(object)) {
796       type = VAL_TYPE_REAL;
797       value.real = json_real_value(object);
798     }
799     const data_set_t *ds = plugin_get_ds(prop->type);
800 
801     /* Check if data set found */
802     if (ds == NULL)
803       continue;
804 
805     value_t tmp = {0};
806     v1.values = &tmp;
807     redfish_convert_val(&value, type, v1.values, ds->ds[0].type);
808 
809     sstrncpy(v1.host, serv->name, sizeof(v1.host));
810     sstrncpy(v1.plugin, PLUGIN_NAME, sizeof(v1.plugin));
811     sstrncpy(v1.type, prop->type, sizeof(v1.type));
812     plugin_dispatch_values(&v1);
813     /* Clear values assigned in case of leakage */
814     v1.values = NULL;
815     v1.values_len = 0;
816   }
817 }
818 
redfish_process_payload(bool success,unsigned short http_code,redfishPayload * payload,void * context)819 static void redfish_process_payload(bool success, unsigned short http_code,
820                                     redfishPayload *payload, void *context) {
821   redfish_job_t *job = (redfish_job_t *)context;
822 
823   if (!success) {
824     WARNING(PLUGIN_NAME ": Query has failed, HTTP code = %u\n", http_code);
825     goto free_job;
826   }
827 
828   redfish_service_t *serv = job->service_query->service;
829 
830   if (!payload) {
831     WARNING(PLUGIN_NAME ": Failed to get payload for service name \"%s\"",
832             serv->name);
833     goto free_job;
834   }
835 
836   for (llentry_t *llres = llist_head(job->service_query->query->resources);
837        llres != NULL; llres = llres->next) {
838     redfish_resource_t *res = (redfish_resource_t *)llres->value;
839     json_t *json_array = json_object_get(payload->json, res->name);
840 
841     if (json_array == NULL) {
842       WARNING(PLUGIN_NAME ": Could not find resource \"%s\"", res->name);
843       continue;
844     }
845 
846     for (llentry_t *llprop = llist_head(res->properties); llprop != NULL;
847          llprop = llprop->next) {
848       redfish_property_t *prop = (redfish_property_t *)llprop->value;
849 
850       redfish_process_payload_property(prop, json_array, res, serv);
851     }
852   }
853 
854 free_job:
855   cleanupPayload(payload);
856   redfish_job_destroy(job);
857 }
858 
redfish_worker_thread(void * args)859 static void *redfish_worker_thread(void __attribute__((unused)) * args) {
860   INFO(PLUGIN_NAME ": Worker is running");
861 
862   for (;;) {
863     usleep(10);
864 
865     if (DEQ_IS_EMPTY(ctx.jobs))
866       continue;
867 
868     pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
869 
870     redfish_job_t *job = DEQ_HEAD(ctx.jobs);
871     getPayloadByPathAsync(job->service_query->service->redfish,
872                           job->service_query->query->endpoint, NULL,
873                           redfish_process_payload, job);
874     DEQ_REMOVE_HEAD(ctx.jobs);
875 
876     pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
877   }
878 
879   pthread_exit(NULL);
880   return NULL;
881 }
882 
redfish_read(user_data_t * ud)883 static int redfish_read(__attribute__((unused)) user_data_t *ud) {
884   for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) {
885     redfish_service_t *service = (redfish_service_t *)le->value;
886 
887     for (llentry_t *le = llist_head(service->query_ptrs); le != NULL;
888          le = le->next) {
889       redfish_query_t *query = (redfish_query_t *)le->value;
890       redfish_job_t *job = calloc(1, sizeof(*job));
891 
892       if (job == NULL) {
893         WARNING(PLUGIN_NAME ": Failed to allocate memory for task");
894         continue;
895       }
896 
897       DEQ_ITEM_INIT(job);
898 
899       redfish_payload_ctx_t *serv_res = calloc(1, sizeof(*serv_res));
900 
901       if (serv_res == NULL) {
902         WARNING(PLUGIN_NAME ": Failed to allocate memory for task's context");
903         sfree(job);
904         continue;
905       }
906 
907       serv_res->query = query;
908       serv_res->service = service;
909       job->service_query = serv_res;
910 
911       DEQ_INSERT_TAIL(ctx.jobs, job);
912     }
913   }
914   return 0;
915 }
916 
redfish_cleanup(void)917 static int redfish_cleanup(void) {
918   INFO(PLUGIN_NAME ": Cleaning up");
919   /* Shutting down a worker thread */
920   if (pthread_cancel(ctx.worker_thread) != 0)
921     ERROR(PLUGIN_NAME ": Failed to cancel the worker thread");
922 
923   if (pthread_join(ctx.worker_thread, NULL) != 0)
924     ERROR(PLUGIN_NAME ": Failed to join the worker thread");
925 
926   /* Cleaning worker's queue */
927   while (!DEQ_IS_EMPTY(ctx.jobs)) {
928     redfish_job_t *job = DEQ_HEAD(ctx.jobs);
929     DEQ_REMOVE_HEAD(ctx.jobs);
930     redfish_job_destroy(job);
931   }
932 
933   for (llentry_t *le = llist_head(ctx.services); le; le = le->next) {
934     redfish_service_t *service = (redfish_service_t *)le->value;
935 
936     redfish_service_destroy(service);
937   }
938   llist_destroy(ctx.services);
939 
940   c_avl_iterator_t *i = c_avl_get_iterator(ctx.queries);
941 
942   char *key;
943   redfish_query_t *query;
944 
945   while (c_avl_iterator_next(i, (void *)&key, (void *)&query) == 0) {
946     for (llentry_t *le = llist_head(query->resources); le != NULL;
947          le = le->next) {
948       redfish_resource_t *resource = (redfish_resource_t *)le->value;
949       for (llentry_t *le = llist_head(resource->properties); le != NULL;
950            le = le->next) {
951         redfish_property_t *property = (redfish_property_t *)le->value;
952         sfree(property->name);
953         sfree(property->plugin_inst);
954         sfree(property->type);
955         sfree(property->type_inst);
956         sfree(property);
957       }
958       sfree(resource->name);
959       llist_destroy(resource->properties);
960       sfree(resource);
961     }
962     sfree(query->name);
963     sfree(query->endpoint);
964     llist_destroy(query->resources);
965     sfree(query);
966   }
967 
968   c_avl_iterator_destroy(i);
969   c_avl_destroy(ctx.queries);
970 
971   return 0;
972 }
973 
module_register(void)974 void module_register(void) {
975   plugin_register_init(PLUGIN_NAME, redfish_init);
976   plugin_register_complex_config(PLUGIN_NAME, redfish_config);
977   plugin_register_complex_read(NULL, PLUGIN_NAME, redfish_read, 0, NULL);
978   plugin_register_shutdown(PLUGIN_NAME, redfish_cleanup);
979 }
980