From: Mozejko, MarcinX Date: Mon, 16 Jul 2018 12:33:41 +0000 (+0100) Subject: New redfish plugin X-Git-Tag: collectd-5.11.0~21^2~1^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96e7a063911a4cda0045481f8ad04173db69eeeb;p=thirdparty%2Fcollectd.git New redfish plugin - Collect data from Redfish interface - Use libredfish C library redfish plugin: addressing comments from PR #2926 Add unit tests. Fix race condition. Fix segmentation fault. Fix memory leaks. ChangeLog: Redfish plugin: Collect sensor data using Redfish protocol. Signed-off-by: Marcin Mozejko Signed-off-by: Adrian Boczkowski Signed-off-by: Zoltan Szabo Signed-off-by: Michal Kobylinski --- diff --git a/Makefile.am b/Makefile.am index 2e16e5c54..df7ee2de6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1653,6 +1653,26 @@ protocols_la_LDFLAGS = $(PLUGIN_LDFLAGS) protocols_la_LIBADD = libignorelist.la endif +if BUILD_PLUGIN_REDFISH +pkglib_LTLIBRARIES += redfish.la +redfish_la_SOURCES = src/redfish.c +redfish_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBREDFISH_CPPFLAGS) +redfish_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBREDFISH_LDFLAGS) +redfish_la_LIBADD = $(BUILD_WITH_LIBREDFISH_LIBS) -lredfish + +test_plugin_redfish_SOURCES = src/redfish_test.c \ + src/utils/avltree/avltree.c \ + src/daemon/utils_llist.c \ + src/daemon/configfile.c \ + src/daemon/types_list.c +test_plugin_redfish_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBREDFISH_CPPFLAGS) +test_plugin_redfish_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBREDFISH_LDFLAGS) +test_plugin_redfish_LDADD = liboconfig.la libplugin_mock.la \ + $(BUILD_WITH_LIBREDFISH_LIBS) -lredfish -ljansson +check_PROGRAMS += test_plugin_redfish +TESTS += test_plugin_redfish +endif + if BUILD_PLUGIN_REDIS pkglib_LTLIBRARIES += redis.la redis_la_SOURCES = src/redis.c diff --git a/configure.ac b/configure.ac index 18314e2ac..d150cd1a6 100644 --- a/configure.ac +++ b/configure.ac @@ -2187,6 +2187,61 @@ AC_SUBST([BUILD_WITH_LIBHIREDIS_CPPFLAGS]) AC_SUBST([BUILD_WITH_LIBHIREDIS_LDFLAGS]) # }}} +# --with-libredfish {{{ +AC_ARG_WITH([libredfish], + [AS_HELP_STRING([--with-libredfish@<:@=PREFIX@:>@], [Path to libredfish.])], + [ + if test "x$withval" = "xyes"; then + with_libredfish="yes" + else if test "x$withval" = "xno"; then + with_libredfish="no" + else + with_libredfish="yes" + LIBREDFISH_CPPFLAGS="$LIBREDFISH_CPPFLAGS -I$withval/include" + LIBREDFISH_LDFLAGS="$LIBREDFISH_LDFLAGS -L$withval/lib" + fi; fi + ], + [with_libredfish="yes"] +) + +SAVE_CPPFLAGS="$CPPFLAGS" +SAVE_LDFLAGS="$LDFLAGS" +CPPFLAGS="$CPPFLAGS $LIBREDFISH_CPPFLAGS" +LDFLAGS="$LDFLAGS $LIBREDFISH_LDFLAGS" + +if test "x$with_libredfish" = "xyes"; then + if test "x$LIBREDFISH_CPPFLAGS" != "x"; then + AC_MSG_NOTICE([libredfish CPPFLAGS: $LIBHIREDFISH_CPPFLAGS]) + fi + AC_CHECK_HEADERS([redfish.h], + [with_libredfish="yes"], + [with_libredfish="no (redfish.h not found)"] + ) +fi + +if test "x$with_libredfish" = "xyes"; then + if test "x$LIBREDFISH_LDFLAGS" != "x"; then + AC_MSG_NOTICE([libredfish LDFLAGS: $LIBREDFISH_LDFLAGS]) + fi + AC_CHECK_LIB([redfish], [createServiceEnumerator], + [with_libredfish="yes"], + [with_libredfish="no (symbol 'createServiceEnumerator' not found)"] + ) +fi + +CPPFLAGS="$SAVE_CPPFLAGS" +LDFLAGS="$SAVE_LDFLAGS" + +if test "x$with_libredfish" = "xyes"; then + BUILD_WITH_LIBREDFISH_CPPFLAGS="$LIBREDFISH_CPPFLAGS" + BUILD_WITH_LIBREDFISH_LDFLAGS="$LIBREDFISH_LDFLAGS" +fi + +AC_SUBST([BUILD_WITH_LIBREDFISH_CPPFLAGS]) +AC_SUBST([BUILD_WITH_LIBREDFISH_LDFLAGS]) + +# }}} + # --with-libcurl {{{ with_curl_config="curl-config" with_curl_cflags="" @@ -6876,6 +6931,7 @@ AC_PLUGIN([powerdns], [yes], [PowerDNS statisti AC_PLUGIN([processes], [$plugin_processes], [Process statistics]) AC_PLUGIN([protocols], [$plugin_protocols], [Protocol (IP, TCP, ...) statistics]) AC_PLUGIN([python], [$plugin_python], [Embed a Python interpreter]) +AC_PLUGIN([redfish], [$with_libredfish], [Redfish plugin]) AC_PLUGIN([redis], [$with_libhiredis], [Redis plugin]) AC_PLUGIN([routeros], [$with_librouteros], [RouterOS plugin]) AC_PLUGIN([rrdcached], [$librrd_rrdc_update], [RRDTool output plugin]) @@ -7304,6 +7360,7 @@ AC_MSG_RESULT([ powerdns . . . . . . $enable_powerdns]) AC_MSG_RESULT([ processes . . . . . . $enable_processes]) AC_MSG_RESULT([ protocols . . . . . . $enable_protocols]) AC_MSG_RESULT([ python . . . . . . . $enable_python]) +AC_MSG_RESULT([ redfish . . . . . . . $enable_redfish]) AC_MSG_RESULT([ redis . . . . . . . . $enable_redis]) AC_MSG_RESULT([ routeros . . . . . . $enable_routeros]) AC_MSG_RESULT([ rrdcached . . . . . . $enable_rrdcached]) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index f09f373d3..4746e1774 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -182,6 +182,7 @@ #@BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes #@BUILD_PLUGIN_PROTOCOLS_TRUE@LoadPlugin protocols #@BUILD_PLUGIN_PYTHON_TRUE@LoadPlugin python +#@BUILD_PLUGIN_REDFISH_TRUE@LoadPlugin redfish #@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis #@BUILD_PLUGIN_ROUTEROS_TRUE@LoadPlugin routeros #@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached @@ -1300,6 +1301,43 @@ # # +# +# +# Endpoint "/redfish/v1/Chassis/Chassis-1/Thermal" +# +# +# PluginInstance "chassis-1" +# Type "rpm" +# +# +# +# +# Endpoint "/redfish/v1/Chassis/Chassis-1/Thermal" +# +# +# PluginInstance "chassis-1" +# Type "degrees" +# +# +# +# +# Endpoint "/redfish/v1/Chassis/Chassis-1/Power" +# +# +# PluginInstance "chassis-1" +# Type "volts" +# +# +# +# +# Host "127.0.0.1:5000" +# User "user" +# Passwd "passwd" +# Queries "fans" "voltages" "temperatures" +# +# +# + # # # Host "router.example.com" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index ed49195e4..af9d3da60 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -7354,6 +7354,97 @@ matching values will be ignored. This plugin embeds a Python-interpreter into collectd and provides an interface to collectd's plugin system. See L for its documentation. +=head2 Plugin C + +The C plugin collects sensor data using REST protocol called +Redfish. + +B + + + + Endpoint "/redfish/v1/Chassis/Chassis-1/Thermal" + + + PluginInstance "chassis-1" + Type "rpm" + + + + + Endpoint "/redfish/v1/Chassis/Chassis-1/Thermal" + + + PluginInstance "chassis-1" + Type "degrees" + + + + + Endpoint "/redfish/v1/Chassis/Chassis-1/Power" + + + PluginInstance "chassis-1" + Type "volts" + + + + + Host "127.0.0.1:5000" + User "user" + Passwd "passwd" + Queries "fans" "voltages" "temperatures" + + + +=over 4 + +=item B + +Section defining a query performed on Redfish interface + +=item B + +URI of the REST API Endpoint for accessing the BMC + +=item B + +Selects single resource or array to collect data. + +=item B + +Selects property from which data is gathered + +=item B + +Plugin instance of dispatched collectd metric + +=item B + +Type of dispatched collectd metric + +=item B + +Type instance of collectd metric + +=item B + +Section defining service to be sent requests + +=item B + +BMC username + +=item B + +BMC password + +=item B + +Queries to run + +=back + =head2 Plugin C The C plugin connects to a device running I, the diff --git a/src/redfish.c b/src/redfish.c new file mode 100644 index 000000000..98c1c2194 --- /dev/null +++ b/src/redfish.c @@ -0,0 +1,983 @@ +/** + * collectd - src/redfish.c + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Marcin Mozejko + * Martin Kennelly + * Adrian Boczkowski + **/ + +#include "collectd.h" + +#include "utils/avltree/avltree.h" +#include "utils/common/common.h" +#include "utils/deq/deq.h" +#include "utils_llist.h" + +#include +#define PLUGIN_NAME "redfish" +#define MAX_STR_LEN 128 + +struct redfish_property_s { + char *name; + char *plugin_inst; + char *type; + char *type_inst; +}; +typedef struct redfish_property_s redfish_property_t; + +struct redfish_resource_s { + char *name; + llist_t *properties; +}; +typedef struct redfish_resource_s redfish_resource_t; + +struct redfish_query_s { + char *name; + char *endpoint; + llist_t *resources; +}; +typedef struct redfish_query_s redfish_query_t; + +struct redfish_service_s { + char *name; + char *host; + char *user; + char *passwd; + char *token; + unsigned int flags; + char **queries; /* List of queries */ + llist_t *query_ptrs; /* Pointers to query structs */ + size_t queries_num; + enumeratorAuthentication auth; + redfishService *redfish; +}; +typedef struct redfish_service_s redfish_service_t; + +struct redfish_payload_ctx_s { + redfish_service_t *service; + redfish_query_t *query; +}; +typedef struct redfish_payload_ctx_s redfish_payload_ctx_t; + +enum redfish_value_type_e { VAL_TYPE_STR = 0, VAL_TYPE_INT, VAL_TYPE_REAL }; +typedef enum redfish_value_type_e redfish_value_type_t; + +union redfish_value_u { + double real; + int integer; + char *string; +}; +typedef union redfish_value_u redfish_value_t; + +typedef struct redfish_job_s { + DEQ_LINKS(struct redfish_job_s); + redfish_payload_ctx_t *service_query; +} redfish_job_t; + +DEQ_DECLARE(redfish_job_t, redfish_job_list_t); + +struct redfish_ctx_s { + llist_t *services; + c_avl_tree_t *queries; + pthread_t worker_thread; + redfish_job_list_t jobs; +}; +typedef struct redfish_ctx_s redfish_ctx_t; + +/* Globals */ +static redfish_ctx_t ctx; + +static int redfish_cleanup(void); +static int redfish_validate_config(void); +static void *redfish_worker_thread(void __attribute__((unused)) * args); + +#if COLLECT_DEBUG +static void redfish_print_config(void) { + DEBUG(PLUGIN_NAME ": ====================CONFIGURATION===================="); + DEBUG(PLUGIN_NAME ": SERVICES: %d", llist_size(ctx.services)); + for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) { + redfish_service_t *s = (redfish_service_t *)le->value; + char queries_str[MAX_STR_LEN]; + + strjoin(queries_str, MAX_STR_LEN, s->queries, s->queries_num, ", "); + + DEBUG(PLUGIN_NAME ": --------------------"); + DEBUG(PLUGIN_NAME ": Service: %s", s->name); + DEBUG(PLUGIN_NAME ": Host: %s", s->host); + + if (s->user && s->passwd) { + DEBUG(PLUGIN_NAME ": User: %s", s->user); + DEBUG(PLUGIN_NAME ": Passwd: %s", s->passwd); + } else if (s->token) + DEBUG(PLUGIN_NAME ": Token: %s", s->token); + + DEBUG(PLUGIN_NAME ": Queries[%" PRIsz "]: (%s)", s->queries_num, + queries_str); + } + + DEBUG(PLUGIN_NAME ": ====================================================="); + + c_avl_iterator_t *i = c_avl_get_iterator(ctx.queries); + char *key; + redfish_query_t *q; + + DEBUG(PLUGIN_NAME ": QUERIES: %d", c_avl_size(ctx.queries)); + + while (c_avl_iterator_next(i, (void *)&key, (void *)&q) == 0) { + DEBUG(PLUGIN_NAME ": --------------------"); + DEBUG(PLUGIN_NAME ": Query: %s", q->name); + DEBUG(PLUGIN_NAME ": Endpoint: %s", q->endpoint); + for (llentry_t *le = llist_head(q->resources); le != NULL; le = le->next) { + redfish_resource_t *r = (redfish_resource_t *)le->value; + DEBUG(PLUGIN_NAME ": Resource: %s", r->name); + for (llentry_t *le = llist_head(r->properties); le != NULL; + le = le->next) { + redfish_property_t *p = (redfish_property_t *)le->value; + DEBUG(PLUGIN_NAME ": Property: %s", p->name); + DEBUG(PLUGIN_NAME ": PluginInstance: %s", p->plugin_inst); + DEBUG(PLUGIN_NAME ": Type: %s", p->type); + DEBUG(PLUGIN_NAME ": TypeInstance: %s", p->type_inst); + } + } + } + + c_avl_iterator_destroy(i); + DEBUG(PLUGIN_NAME ": ====================================================="); +} +#endif + +static void redfish_service_destroy(redfish_service_t *service) { + /* This is checked internally by cleanupServiceEnumerator() also, + * but as long as it's a third-party library let's be a little 'defensive' */ + if (service->redfish != NULL) + cleanupServiceEnumerator(service->redfish); + + /* Destroy all service members, sfree() as well as strarray_free() + * and llist_destroy() are safe to call on NULL argument */ + sfree(service->name); + sfree(service->host); + sfree(service->user); + sfree(service->passwd); + sfree(service->token); + strarray_free(service->queries, (size_t)service->queries_num); + llist_destroy(service->query_ptrs); + + sfree(service); +} + +static void redfish_job_destroy(redfish_job_t *job) { + sfree(job->service_query); + sfree(job); +} + +static int redfish_init(void) { +#if COLLECT_DEBUG + redfish_print_config(); +#endif + int ret = redfish_validate_config(); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Validation of configuration file failed"); + return ret; + } + + DEQ_INIT(ctx.jobs); + ret = pthread_create(&ctx.worker_thread, NULL, redfish_worker_thread, NULL); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Creation of thread failed"); + return ret; + } + + for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) { + redfish_service_t *service = (redfish_service_t *)le->value; + /* Ignore redfish version */ + service->flags |= REDFISH_FLAG_SERVICE_NO_VERSION_DOC; + + /* Preparing struct for authentication */ + if (service->user && service->passwd) { + service->auth.authCodes.userPass.username = service->user; + service->auth.authCodes.userPass.password = service->passwd; + service->redfish = createServiceEnumerator( + service->host, NULL, &service->auth, service->flags); + } else if (service->token) { + service->auth.authCodes.authToken.token = service->token; + service->auth.authType = REDFISH_AUTH_BEARER_TOKEN; + service->redfish = createServiceEnumerator( + service->host, NULL, &service->auth, service->flags); + } else { + service->redfish = + createServiceEnumerator(service->host, NULL, NULL, service->flags); + } + + service->query_ptrs = llist_create(); + if (service->query_ptrs == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for service query list"); + goto error; + } + + /* Preparing query pointers list for every service */ + for (size_t i = 0; i < service->queries_num; i++) { + redfish_query_t *ptr; + if (c_avl_get(ctx.queries, (void *)service->queries[i], (void *)&ptr) != + 0) { + ERROR(PLUGIN_NAME ": Cannot find a service query in a context"); + goto error; + } + + llentry_t *entry = llentry_create(ptr->name, ptr); + if (entry != NULL) + llist_append(service->query_ptrs, entry); + else { + ERROR(PLUGIN_NAME ": Failed to allocate memory for a query list entry"); + goto error; + } + } + } + + return 0; + +error: + /* Freeing libredfish resources & llists */ + for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) { + redfish_service_t *service = (redfish_service_t *)le->value; + + redfish_service_destroy(service); + } + return -ENOMEM; +} + +static int redfish_preconfig(void) { + /* Creating placeholder for services */ + ctx.services = llist_create(); + if (ctx.services == NULL) + goto error; + + /* Creating placeholder for queries */ + ctx.queries = c_avl_create((void *)strcmp); + if (ctx.services == NULL) + goto free_services; + + return 0; + +free_services: + llist_destroy(ctx.services); +error: + ERROR(PLUGIN_NAME ": Failed to allocate memory for plugin context"); + return -ENOMEM; +} + +static int redfish_config_property(redfish_resource_t *resource, + oconfig_item_t *cfg_item) { + assert(resource != NULL); + assert(cfg_item != NULL); + + redfish_property_t *property = calloc(1, sizeof(*property)); + + if (property == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for property"); + return -ENOMEM; + } + + int ret = cf_util_get_string(cfg_item, &property->name); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Could not get property argument in resource section " + "named \"%s\"", + resource->name); + ret = -EINVAL; + goto free_all; + } + + for (int i = 0; i < cfg_item->children_num; i++) { + oconfig_item_t *opt = cfg_item->children + i; + if (strcasecmp("PluginInstance", opt->key) == 0) + ret = cf_util_get_string(opt, &property->plugin_inst); + else if (strcasecmp("Type", opt->key) == 0) + ret = cf_util_get_string(opt, &property->type); + else if (strcasecmp("TypeInstance", opt->key) == 0) + ret = cf_util_get_string(opt, &property->type_inst); + else { + ERROR(PLUGIN_NAME ": Invalid option \"%s\" in property \"%s\" " + "in resource \"%s\"", + opt->key, property->name, resource->name); + ret = -EINVAL; + goto free_all; + } + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Something went wrong going through attributes in " + "property named \"%s\" in resource named \"%s\"", + property->name, resource->name); + goto free_all; + } + } + + llentry_t *entry = llentry_create(property->name, property); + if (entry == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for property"); + ret = -ENOMEM; + goto free_all; + } + llist_append(resource->properties, entry); + + return 0; + +free_all: + sfree(property->name); + sfree(property->plugin_inst); + sfree(property->type); + sfree(property->type_inst); + sfree(property); + return ret; +} + +static int redfish_config_resource(redfish_query_t *query, + oconfig_item_t *cfg_item) { + assert(query != NULL); + assert(cfg_item != NULL); + + redfish_resource_t *resource = calloc(1, sizeof(*resource)); + + if (resource == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for resource"); + return -ENOMEM; + } + + resource->properties = llist_create(); + + if (resource->properties == NULL) + goto free_memory; + + int ret = cf_util_get_string(cfg_item, &resource->name); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Could not get resource name for query named \"%s\"", + query->name); + goto free_memory; + } + for (int i = 0; i < cfg_item->children_num; i++) { + oconfig_item_t *opt = cfg_item->children + i; + if (strcasecmp("Property", opt->key) != 0) { + WARNING(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key); + continue; + } + + ret = redfish_config_property(resource, opt); + + if (ret != 0) { + goto free_memory; + } + } + + llentry_t *entry = llentry_create(resource->name, resource); + if (entry == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for resource list entry"); + goto free_memory; + } + llist_append(query->resources, entry); + + return 0; + +free_memory: + sfree(resource->name); + llist_destroy(resource->properties); + sfree(resource); + return -1; +} + +static int redfish_config_query(oconfig_item_t *cfg_item, + c_avl_tree_t *queries) { + redfish_query_t *query = calloc(1, sizeof(*query)); + + if (query == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for query"); + return -ENOMEM; + } + + query->resources = llist_create(); + + int ret; + if (query->resources == NULL) { + ret = -ENOMEM; + goto free_all; + } + + ret = cf_util_get_string(cfg_item, &query->name); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Unable to get query name. Query ignored"); + ret = -EINVAL; + goto free_all; + } + + for (int i = 0; i < cfg_item->children_num; i++) { + oconfig_item_t *opt = cfg_item->children + i; + + if (strcasecmp("Endpoint", opt->key) == 0) + ret = cf_util_get_string(opt, &query->endpoint); + else if (strcasecmp("Resource", opt->key) == 0) + ret = redfish_config_resource(query, opt); + else { + ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key); + ret = -EINVAL; + goto free_all; + } + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Something went wrong processing query \"%s\"", + query->name); + ret = -EINVAL; + goto free_all; + } + } + + ret = c_avl_insert(queries, query->name, query); + + if (ret != 0) + goto free_all; + + return 0; + +free_all: + sfree(query->name); + sfree(query->endpoint); + llist_destroy(query->resources); + sfree(query); + return ret; +} + +static int redfish_read_queries(oconfig_item_t *cfg_item, char ***queries_ptr) { + char **queries = NULL; + size_t queries_num = 0; + + for (int i = 0; i < cfg_item->values_num; ++i) { + strarray_add(&queries, &queries_num, cfg_item->values[i].value.string); + } + + if (queries_num != (size_t)cfg_item->values_num) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for query list"); + strarray_free(queries, queries_num); + return -ENOMEM; + } + + *queries_ptr = queries; + return 0; +} + +static int redfish_config_service(oconfig_item_t *cfg_item) { + redfish_service_t *service = calloc(1, sizeof(*service)); + + if (service == NULL) { + ERROR(PLUGIN_NAME ": Failed to allocate memory for service"); + return -ENOMEM; + } + + int ret = cf_util_get_string(cfg_item, &service->name); + if (ret != 0) { + ERROR(PLUGIN_NAME ": A service was defined without an argument"); + goto free_service; + } + + for (int i = 0; i < cfg_item->children_num; i++) { + oconfig_item_t *opt = cfg_item->children + i; + + if (strcasecmp("Host", opt->key) == 0) + ret = cf_util_get_string(opt, &service->host); + else if (strcasecmp("User", opt->key) == 0) + ret = cf_util_get_string(opt, &service->user); + else if (strcasecmp("Passwd", opt->key) == 0) + ret = cf_util_get_string(opt, &service->passwd); + else if (strcasecmp("Token", opt->key) == 0) + ret = cf_util_get_string(opt, &service->token); + else if (strcasecmp("Queries", opt->key) == 0) { + ret = redfish_read_queries(opt, &service->queries); + service->queries_num = opt->values_num; + } else { + ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", opt->key); + } + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Something went wrong processing the service named \ + \"%s\"", + service->name); + goto free_service; + } + } + + llentry_t *entry = llentry_create(service->name, service); + + if (entry != NULL) + llist_append(ctx.services, entry); + else { + ERROR(PLUGIN_NAME ": Failed to create list for service name \"%s\"", + service->name); + goto free_service; + } + + return 0; + +free_service: + redfish_service_destroy(service); + return -1; +} + +static int redfish_config(oconfig_item_t *cfg_item) { + int ret = redfish_preconfig(); + + if (ret != 0) + return ret; + + for (int i = 0; i < cfg_item->children_num; i++) { + oconfig_item_t *child = cfg_item->children + i; + + if (strcasecmp("Query", child->key) == 0) + ret = redfish_config_query(child, ctx.queries); + else if (strcasecmp("Service", child->key) == 0) + ret = redfish_config_service(child); + else { + ERROR(PLUGIN_NAME ": Invalid configuration option \"%s\".", child->key); + } + + if (ret != 0) { + redfish_cleanup(); + return ret; + } + } + + return 0; +} + +static int redfish_validate_config(void) { + /* Service validation */ + for (llentry_t *llserv = llist_head(ctx.services); llserv != NULL; + llserv = llserv->next) { + redfish_service_t *service = llserv->value; + if (service->name == NULL) { + ERROR(PLUGIN_NAME ": A service has no name"); + return -EINVAL; + } + if (service->host == NULL) { + ERROR(PLUGIN_NAME ": Service \"%s\" has no host attribute", + service->name); + return -EINVAL; + } + if ((service->user == NULL) ^ (service->passwd == NULL)) { + ERROR(PLUGIN_NAME ": Service \"%s\" does not have user and/or password " + "defined", + service->name); + return -EINVAL; + } + if (service->user == NULL && service->token == NULL) { + ERROR(PLUGIN_NAME ": Service \"%s\" does not have an user/pass or " + "token defined", + service->name); + return -EINVAL; + } + if (service->queries_num == 0) + WARNING(PLUGIN_NAME ": Service \"%s\" does not have queries", + service->name); + + for (int i = 0; i < service->queries_num; i++) { + redfish_query_t *query_query; + bool found = false; + char *key; + c_avl_iterator_t *query_iter = c_avl_get_iterator(ctx.queries); + while (c_avl_iterator_next(query_iter, (void *)&key, + (void *)&query_query) == 0 && + !found) { + if (query_query->name != NULL && service->queries[i] != NULL && + strcmp(query_query->name, service->queries[i]) == 0) { + found = true; + } + } + + if (!found) { + ERROR(PLUGIN_NAME ": Query named \"%s\" in service \"%s\" not found", + service->queries[i], service->name); + c_avl_iterator_destroy(query_iter); + return -EINVAL; + } + + c_avl_iterator_destroy(query_iter); + } + } + + c_avl_iterator_t *queries_iter = c_avl_get_iterator(ctx.queries); + char *key; + redfish_query_t *query; + + /* Query validation */ + while (c_avl_iterator_next(queries_iter, (void *)&key, (void *)&query) == 0) { + if (query->name == NULL) { + ERROR(PLUGIN_NAME ": A query does not have a name"); + goto error; + } + if (query->endpoint == NULL) { + ERROR(PLUGIN_NAME ": Query \"%s\" does not have a valid endpoint", + query->name); + goto error; + } + for (llentry_t *llres = llist_head(query->resources); llres != NULL; + llres = llres->next) { + redfish_resource_t *resource = (redfish_resource_t *)llres->value; + /* Resource validation */ + if (resource->name == NULL) { + WARNING(PLUGIN_NAME ": A resource in query \"%s\" is not named", + query->name); + } + /* Property validation */ + for (llentry_t *llprop = llist_head(resource->properties); llprop != NULL; + llprop = llprop->next) { + redfish_property_t *prop = (redfish_property_t *)llprop->value; + if (prop->name == NULL) { + ERROR(PLUGIN_NAME ": A property has no name in query \"%s\"", + query->name); + goto error; + } + if (prop->plugin_inst == NULL) { + ERROR(PLUGIN_NAME ": A plugin instance is not defined in property " + "\"%s\" in query \"%s\"", + prop->name, query->name); + goto error; + } + if (prop->type == NULL) { + ERROR(PLUGIN_NAME ": Type is not defined in property \"%s\" in " + "query \"%s\"", + prop->name, query->name); + goto error; + } + } + } + } + + c_avl_iterator_destroy(queries_iter); + + return 0; + +error: + c_avl_iterator_destroy(queries_iter); + return -EINVAL; +} + +static int redfish_convert_val(redfish_value_t *value, + redfish_value_type_t src_type, value_t *vl, + int dst_type) { + switch (dst_type) { + case DS_TYPE_GAUGE: + if (src_type == VAL_TYPE_STR) + vl->gauge = strtod(value->string, NULL); + else if (src_type == VAL_TYPE_INT) + vl->gauge = (gauge_t)value->integer; + else if (src_type == VAL_TYPE_REAL) + vl->gauge = value->real; + break; + case DS_TYPE_DERIVE: + if (src_type == VAL_TYPE_STR) + vl->derive = strtoll(value->string, NULL, 0); + else if (src_type == VAL_TYPE_INT) + vl->derive = (derive_t)value->integer; + else if (src_type == VAL_TYPE_REAL) + vl->derive = (derive_t)value->real; + break; + case DS_TYPE_COUNTER: + if (src_type == VAL_TYPE_STR) + vl->derive = strtoull(value->string, NULL, 0); + else if (src_type == VAL_TYPE_INT) + vl->derive = (derive_t)value->integer; + else if (src_type == VAL_TYPE_REAL) + vl->derive = (derive_t)value->real; + break; + case DS_TYPE_ABSOLUTE: + if (src_type == VAL_TYPE_STR) + vl->absolute = strtoull(value->string, NULL, 0); + else if (src_type == VAL_TYPE_INT) + vl->absolute = (absolute_t)value->integer; + else if (src_type == VAL_TYPE_REAL) + vl->absolute = (absolute_t)value->real; + break; + default: + ERROR(PLUGIN_NAME ": Invalid data set type. Cannot convert value"); + return -EINVAL; + } + + return 0; +} + +static int redfish_json_get_string(char *const value, const size_t value_len, + const json_t *const json) { + if (json_is_string(json)) { + const char *str_val = json_string_value(json); + sstrncpy(value, str_val, value_len); + return 0; + } else if (json_is_integer(json)) { + snprintf(value, value_len, "%d", (int)json_integer_value(json)); + return 0; + } + + ERROR(PLUGIN_NAME ": Expected JSON value to be a string or an integer"); + return -EINVAL; +} + +static void redfish_process_payload_property(const redfish_property_t *prop, + const json_t *json_array, + const redfish_resource_t *res, + const redfish_service_t *serv) { + /* Iterating through array of sensor(s) */ + for (int i = 0; i < json_array_size(json_array); i++) { + json_t *item = json_array_get(json_array, i); + if (item == NULL) { + ERROR(PLUGIN_NAME ": Failure retrieving array member for resource \"%s\"", + res->name); + continue; + } + json_t *object = json_object_get(item, prop->name); + if (object == NULL) { + ERROR(PLUGIN_NAME + ": Failure retreiving property \"%s\" from resource \"%s\"", + prop->name, res->name); + continue; + } + value_list_t v1 = VALUE_LIST_INIT; + v1.values_len = 1; + if (prop->plugin_inst != NULL) + sstrncpy(v1.plugin_instance, prop->plugin_inst, + sizeof(v1.plugin_instance)); + if (prop->type_inst != NULL) + sstrncpy(v1.type_instance, prop->type_inst, sizeof(v1.type_instance)); + else { + /* Default TypeInstance is MemberId */ + char type_inst[40] = "MemberId"; + if (prop->type_inst != NULL) + sstrncpy(type_inst, prop->type_inst, sizeof(type_inst)); + + /* Retrieving sensor ID and setting TypeInstance */ + json_t *sensor_id = json_object_get(item, prop->type_inst); + if (sensor_id == NULL) { + ERROR(PLUGIN_NAME + ": Failed to get \"%s\" for property \"%s\" in resource " + "\"%s\"", + prop->type_inst, prop->name, res->name); + continue; + } + + int ret = redfish_json_get_string(v1.type_instance, + sizeof(v1.type_instance), sensor_id); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Cannot convert \"%s\" to a type instance", + prop->type_inst); + continue; + } + } + + /* Checking whether real or integer value */ + redfish_value_t value; + redfish_value_type_t type = VAL_TYPE_STR; + if (json_is_string(object)) { + value.string = (char *)json_string_value(object); + } else if (json_is_integer(object)) { + type = VAL_TYPE_INT; + value.integer = json_integer_value(object); + } else if (json_is_real(object)) { + type = VAL_TYPE_REAL; + value.real = json_real_value(object); + } + const data_set_t *ds = plugin_get_ds(prop->type); + + /* Check if data set found */ + if (ds == NULL) + continue; + + v1.values = &(value_t){0}; + redfish_convert_val(&value, type, v1.values, ds->ds[0].type); + + sstrncpy(v1.host, serv->name, sizeof(v1.host)); + sstrncpy(v1.plugin, PLUGIN_NAME, sizeof(v1.plugin)); + sstrncpy(v1.type, prop->type, sizeof(v1.type)); + plugin_dispatch_values(&v1); + /* Clear values assigned in case of leakage */ + v1.values = NULL; + v1.values_len = 0; + } +} + +static void redfish_process_payload(bool success, unsigned short http_code, + redfishPayload *payload, void *context) { + redfish_job_t *job = (redfish_job_t *)context; + + if (!success) { + WARNING(PLUGIN_NAME ": Query has failed, HTTP code = %u\n", http_code); + goto free_job; + } + + redfish_service_t *serv = job->service_query->service; + + if (!payload) { + WARNING(PLUGIN_NAME ": Failed to get payload for service name \"%s\"", + serv->name); + goto free_job; + } + + for (llentry_t *llres = llist_head(job->service_query->query->resources); + llres != NULL; llres = llres->next) { + redfish_resource_t *res = (redfish_resource_t *)llres->value; + json_t *json_array = json_object_get(payload->json, res->name); + + if (json_array == NULL) { + WARNING(PLUGIN_NAME ": Could not find resource \"%s\"", res->name); + continue; + } + + for (llentry_t *llprop = llist_head(res->properties); llprop != NULL; + llprop = llprop->next) { + redfish_property_t *prop = (redfish_property_t *)llprop->value; + + redfish_process_payload_property(prop, json_array, res, serv); + } + } + +free_job: + cleanupPayload(payload); + redfish_job_destroy(job); +} + +static void *redfish_worker_thread(void __attribute__((unused)) * args) { + INFO(PLUGIN_NAME ": Worker is running"); + + for (;;) { + usleep(10); + + if (DEQ_IS_EMPTY(ctx.jobs)) + continue; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + redfish_job_t *job = DEQ_HEAD(ctx.jobs); + getPayloadByPathAsync(job->service_query->service->redfish, + job->service_query->query->endpoint, NULL, + redfish_process_payload, job); + DEQ_REMOVE_HEAD(ctx.jobs); + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + } + + pthread_exit(NULL); + return NULL; +} + +static int redfish_read(__attribute__((unused)) user_data_t *ud) { + for (llentry_t *le = llist_head(ctx.services); le != NULL; le = le->next) { + redfish_service_t *service = (redfish_service_t *)le->value; + + for (llentry_t *le = llist_head(service->query_ptrs); le != NULL; + le = le->next) { + redfish_query_t *query = (redfish_query_t *)le->value; + redfish_job_t *job = calloc(1, sizeof(*job)); + + if (job == NULL) { + WARNING(PLUGIN_NAME ": Failed to allocate memory for task"); + continue; + } + + DEQ_ITEM_INIT(job); + + redfish_payload_ctx_t *serv_res = calloc(1, sizeof(*serv_res)); + + if (serv_res == NULL) { + WARNING(PLUGIN_NAME ": Failed to allocate memory for task's context"); + sfree(job); + continue; + } + + serv_res->query = query; + serv_res->service = service; + job->service_query = serv_res; + + DEQ_INSERT_TAIL(ctx.jobs, job); + } + } + return 0; +} + +static int redfish_cleanup(void) { + INFO(PLUGIN_NAME ": Cleaning up"); + /* Shutting down a worker thread */ + if (pthread_cancel(ctx.worker_thread) != 0) + ERROR(PLUGIN_NAME ": Failed to cancel the worker thread"); + + if (pthread_join(ctx.worker_thread, NULL) != 0) + ERROR(PLUGIN_NAME ": Failed to join the worker thread"); + + /* Cleaning worker's queue */ + while (!DEQ_IS_EMPTY(ctx.jobs)) { + redfish_job_t *job = DEQ_HEAD(ctx.jobs); + DEQ_REMOVE_HEAD(ctx.jobs); + redfish_job_destroy(job); + } + + for (llentry_t *le = llist_head(ctx.services); le; le = le->next) { + redfish_service_t *service = (redfish_service_t *)le->value; + + redfish_service_destroy(service); + } + llist_destroy(ctx.services); + + c_avl_iterator_t *i = c_avl_get_iterator(ctx.queries); + + char *key; + redfish_query_t *query; + + while (c_avl_iterator_next(i, (void *)&key, (void *)&query) == 0) { + for (llentry_t *le = llist_head(query->resources); le != NULL; + le = le->next) { + redfish_resource_t *resource = (redfish_resource_t *)le->value; + for (llentry_t *le = llist_head(resource->properties); le != NULL; + le = le->next) { + redfish_property_t *property = (redfish_property_t *)le->value; + sfree(property->name); + sfree(property->plugin_inst); + sfree(property->type); + sfree(property->type_inst); + sfree(property); + } + sfree(resource->name); + llist_destroy(resource->properties); + sfree(resource); + } + sfree(query->name); + sfree(query->endpoint); + llist_destroy(query->resources); + sfree(query); + } + + c_avl_iterator_destroy(i); + c_avl_destroy(ctx.queries); + + return 0; +} + +void module_register(void) { + plugin_register_init(PLUGIN_NAME, redfish_init); + plugin_register_complex_config(PLUGIN_NAME, redfish_config); + plugin_register_complex_read(NULL, PLUGIN_NAME, redfish_read, 0, NULL); + plugin_register_shutdown(PLUGIN_NAME, redfish_cleanup); +} diff --git a/src/redfish_test.c b/src/redfish_test.c new file mode 100644 index 000000000..1decc9ef8 --- /dev/null +++ b/src/redfish_test.c @@ -0,0 +1,616 @@ +/** + * collectd - src/redfish_test.c + * + * Copyright(c) 2018 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Martin Kennelly + * Marcin Mozejko + * Adrian Boczkowski + **/ + +#define plugin_dispatch_values redfish_test_plugin_dispatch_values_mock +#include "redfish.c" +#include "testing.h" + +#define VALUE_CACHE_SIZE (1) + +static value_list_t last_dispatched_value_list; +static value_t last_dispatched_values[VALUE_CACHE_SIZE]; +int redfish_test_plugin_dispatch_values_mock(value_list_t const *vl) { + last_dispatched_value_list = *vl; + size_t len = MIN(vl->values_len, VALUE_CACHE_SIZE); + for (size_t i = 0; i < len; ++i) { + last_dispatched_values[i] = vl->values[i]; + } + last_dispatched_value_list.values = last_dispatched_values; + return 0; +} + +static value_list_t *redfish_test_get_last_dispatched_value_list() { + return &last_dispatched_value_list; +} + +DEF_TEST(read_queries) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + + assert(ci != NULL); + ci->values = calloc(3, sizeof(*ci->values)); + assert(ci->values != NULL); + ci->values_num = 3; + ci->values[0].value.string = "temperatures"; + ci->values[0].type = OCONFIG_TYPE_STRING; + ci->values[1].value.string = "fans"; + ci->values[1].type = OCONFIG_TYPE_STRING; + ci->values[2].value.string = "power"; + ci->values[2].type = OCONFIG_TYPE_STRING; + + char **queries; + int ret = redfish_read_queries(ci, &queries); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("temperatures", queries[0]); + EXPECT_EQ_STR("fans", queries[1]); + EXPECT_EQ_STR("power", queries[2]); + + sfree(ci->values); + sfree(ci); + + for (int j = 0; j < 3; j++) + sfree(queries[j]); + sfree(queries); + return 0; +} + +DEF_TEST(convert_val) { + redfish_value_t val = {.string = "1"}; + redfish_value_type_t src_type = VAL_TYPE_STR; + int dst_type = DS_TYPE_GAUGE; + value_t vl = {0}; + int ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.gauge == 1.0); + + val.integer = 1; + src_type = VAL_TYPE_INT; + dst_type = DS_TYPE_GAUGE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.gauge == 1.0); + + val.real = 1.0; + src_type = VAL_TYPE_REAL; + dst_type = DS_TYPE_GAUGE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.gauge == 1.0); + + val.string = "-1"; + src_type = VAL_TYPE_STR; + dst_type = DS_TYPE_DERIVE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.derive == -1); + + val.integer = -1; + src_type = VAL_TYPE_INT; + dst_type = DS_TYPE_DERIVE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.derive == -1); + + val.real = -1.0; + src_type = VAL_TYPE_REAL; + dst_type = DS_TYPE_DERIVE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.derive == -1); + + val.string = "1"; + src_type = VAL_TYPE_STR; + dst_type = DS_TYPE_COUNTER; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.counter == 1); + + val.integer = 1; + src_type = VAL_TYPE_INT; + dst_type = DS_TYPE_COUNTER; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.counter == 1); + + val.real = 1.0; + src_type = VAL_TYPE_REAL; + dst_type = DS_TYPE_COUNTER; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.counter == 1); + + val.string = "1"; + src_type = VAL_TYPE_STR; + dst_type = DS_TYPE_ABSOLUTE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.absolute == 1); + + val.integer = 1; + src_type = VAL_TYPE_INT; + dst_type = DS_TYPE_ABSOLUTE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.absolute == 1); + + val.real = 1.0; + src_type = VAL_TYPE_REAL; + dst_type = DS_TYPE_ABSOLUTE; + ret = redfish_convert_val(&val, src_type, &vl, dst_type); + EXPECT_EQ_INT(0, ret); + OK(vl.absolute == 1); + + return 0; +} + +/* Testing allocation of memory for ctx struct. Creation of services list + * & queries avl tree */ +DEF_TEST(redfish_preconfig) { + int ret = redfish_preconfig(); + + EXPECT_EQ_INT(0, ret); + CHECK_NOT_NULL(ctx.queries); + CHECK_NOT_NULL(ctx.services); + + llist_destroy(ctx.services); + c_avl_destroy(ctx.queries); + + return 0; +} + +/* Testing correct input of properties from conf file */ +DEF_TEST(config_property) { + redfish_resource_t *resource = calloc(1, sizeof(*resource)); + assert(resource != NULL); + resource->name = "test property"; + resource->properties = llist_create(); + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + + assert(ci != NULL); + ci->values_num = 1; + ci->values = calloc(1, sizeof(*ci->values)); + ci->values[0].type = OCONFIG_TYPE_STRING; + ci->values[0].value.string = "ReadingRPM"; + + ci->children_num = 3; + ci->children = calloc(1, sizeof(*ci->children) * ci->children_num); + + ci->children[0].key = "PluginInstance"; + ci->children[0].parent = ci; + ci->children[0].values = calloc(1, sizeof(*ci->children[0].values)); + assert(ci->children[0].values != NULL); + ci->children[0].values_num = 1; + ci->children[0].values->value.string = "chassis1"; + ci->children[0].values->type = OCONFIG_TYPE_STRING; + + ci->children[1].key = "Type"; + ci->children[1].parent = ci; + ci->children[1].values = calloc(1, sizeof(*ci->children[1].values)); + assert(ci->children[1].values != NULL); + ci->children[1].values_num = 1; + ci->children[1].values->value.string = "degrees"; + ci->children[1].values->type = OCONFIG_TYPE_STRING; + + ci->children[2].key = "TypeInstance"; + ci->children[2].parent = ci; + ci->children[2].values = calloc(1, sizeof(*ci->children[2].values)); + assert(ci->children[2].values != NULL); + ci->children[2].values_num = 1; + ci->children[2].values->value.string = "0"; + ci->children[2].values->type = OCONFIG_TYPE_STRING; + + int ret = redfish_config_property(resource, ci); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, llist_size(resource->properties)); + + sfree(ci->children[0].values); + sfree(ci->children[1].values); + sfree(ci->children[2].values); + + sfree(ci->children); + sfree(ci->values); + sfree(ci); + + for (llentry_t *llprop = llist_head(resource->properties); llprop != NULL; + llprop = llprop->next) { + redfish_property_t *property = (redfish_property_t *)llprop->value; + sfree(property->name); + sfree(property->plugin_inst); + sfree(property->type); + sfree(property->type_inst); + sfree(property); + } + llist_destroy(resource->properties); + free(resource); + return 0; +} + +DEF_TEST(config_resource) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + assert(ci != NULL); + + ci->values_num = 1; + ci->values = calloc(1, sizeof(*ci->values)); + assert(ci->values != NULL); + ci->values[0].value.string = "Temperatures"; + + ci->children_num = 1; + ci->children = calloc(1, sizeof(*ci->children)); + assert(ci->children != NULL); + ci->children[0].children_num = 1; + ci->children[0].parent = ci; + ci->children[0].key = "Property"; + ci->children[0].values_num = 1; + ci->children[0].values = calloc(1, sizeof(*ci->children[0].values)); + ci->children[0].values->value.string = "ReadingRPM"; + assert(ci->children[0].values != NULL); + + oconfig_item_t *ci_prop = calloc(1, sizeof(*ci_prop)); + assert(ci_prop != NULL); + + ci->children[0].children = ci_prop; + ci->children_num = 1; + + ci_prop->key = "PluginInstance"; + ci_prop->parent = ci; + ci_prop->values_num = 1; + ci_prop->values = calloc(1, sizeof(*ci_prop->values)); + assert(ci_prop->values != NULL); + ci_prop->values[0].type = OCONFIG_TYPE_STRING; + ci_prop->values[0].value.string = "chassis-1"; + + redfish_query_t *query = calloc(1, sizeof(*query)); + query->endpoint = "/redfish/v1/Chassis/Chassis-1/Thermal"; + query->name = "fans"; + query->resources = llist_create(); + + int ret = redfish_config_resource(query, ci); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, llist_size(query->resources)); + + sfree(ci_prop->values); + sfree(ci_prop); + + sfree(ci->values); + sfree(ci->children[0].values); + sfree(ci->children); + sfree(ci); + + for (llentry_t *llres = llist_head(query->resources); llres != NULL; + llres = llres->next) { + redfish_resource_t *resource = (redfish_resource_t *)llres->value; + for (llentry_t *llprop = llist_head(resource->properties); llprop != NULL; + llprop = llprop->next) { + redfish_property_t *property = (redfish_property_t *)llprop->value; + sfree(property->name); + sfree(property->plugin_inst); + sfree(property->type); + sfree(property->type_inst); + free(property); + } + llist_destroy(resource->properties); + free(resource->name); + free(resource); + } + llist_destroy(query->resources); + sfree(query); + return 0; +} + +DEF_TEST(config_query) { + oconfig_item_t *qci = calloc(1, sizeof(*qci)); + assert(qci != NULL); + qci->key = "Query"; + qci->values_num = 1; + qci->values = calloc(1, sizeof(*qci->values)); + assert(qci->values != NULL); + qci->values->type = OCONFIG_TYPE_STRING; + qci->values->value.string = "fans"; + + qci->children_num = 2; + qci->children = calloc(1, sizeof(*qci->children) * qci->children_num); + assert(qci->children != NULL); + + qci->children[0].key = "Endpoint"; + qci->children[0].values = calloc(1, sizeof(*qci->children[0].values)); + assert(qci->children[0].values != NULL); + qci->children[0].values->type = OCONFIG_TYPE_STRING; + qci->children[0].values_num = 1; + qci->children[0].values->value.string = + "/redfish/v1/Chassis/Chassis-1/Thermal"; + + qci->children[1].key = "Resource"; + qci->children[1].values_num = 1; + qci->children[1].values = calloc(1, sizeof(*qci->children[1].values)); + assert(qci->children[1].values != NULL); + qci->children[1].values->type = OCONFIG_TYPE_STRING; + qci->children[1].values->value.string = "Temperature"; + qci->children[1].children_num = 1; + + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + assert(ci != NULL); + + qci->children[1].children = ci; + + ci->key = "Property"; + ci->values_num = 1; + ci->values = calloc(1, sizeof(*ci->values)); + assert(ci->values != NULL); + ci->values->type = OCONFIG_TYPE_STRING; + ci->values->value.string = "ReadingRPM"; + + oconfig_item_t *ci_prop = calloc(1, sizeof(*ci_prop)); + assert(ci_prop != NULL); + + ci->children = ci_prop; + ci->children_num = 1; + + ci_prop->key = "PluginInstance"; + ci_prop->parent = ci; + ci_prop->values_num = 1; + ci_prop->values = calloc(1, sizeof(*ci_prop->values)); + assert(ci_prop->values != NULL); + ci_prop->values[0].type = OCONFIG_TYPE_STRING; + ci_prop->values[0].value.string = "chassis-1"; + + c_avl_tree_t *queries = + c_avl_create((int (*)(const void *, const void *))strcmp); + + int ret = redfish_config_query(qci, queries); + EXPECT_EQ_INT(0, ret); + + sfree(ci_prop->values); + sfree(ci_prop); + + sfree(ci->values); + sfree(ci); + sfree(qci->children[0].values); + sfree(qci->children[1].values); + sfree(qci->children); + sfree(qci->values); + sfree(qci); + + redfish_query_t *query; + char *key; + c_avl_iterator_t *query_iter = c_avl_get_iterator(queries); + while (c_avl_iterator_next(query_iter, (void **)&key, (void **)&query) == 0) { + for (llentry_t *llres = llist_head(query->resources); llres != NULL; + llres = llres->next) { + redfish_resource_t *resource = (redfish_resource_t *)llres->value; + for (llentry_t *llprop = llist_head(resource->properties); llprop != NULL; + llprop = llprop->next) { + redfish_property_t *property = (redfish_property_t *)llprop->value; + sfree(property->name); + sfree(property->plugin_inst); + sfree(property->type); + sfree(property->type_inst); + sfree(property); + } + llist_destroy(resource->properties); + sfree(resource->name); + sfree(resource); + } + llist_destroy(query->resources); + sfree(query->name); + sfree(query->endpoint); + } + sfree(query_iter); + c_avl_destroy(queries); + return 0; +} + +DEF_TEST(config_service) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + assert(ci != NULL); + ci->key = "Service"; + ci->values_num = 1; + ci->values = calloc(1, sizeof(*ci->values)); + ci->values->type = OCONFIG_TYPE_STRING; + ci->values->value.string = "Server 5"; + ci->children_num = 4; + ci->children = calloc(1, sizeof(*ci->children) * ci->children_num); + ci->children[0].key = "Host"; + ci->children[0].values_num = 1; + ci->children[0].values = calloc(1, sizeof(*ci->children[0].values)); + ci->children[0].values->type = OCONFIG_TYPE_STRING; + ci->children[0].values->value.string = "127.0.0.1:5000"; + ci->children[1].key = "User"; + ci->children[1].values_num = 1; + ci->children[1].values = calloc(1, sizeof(*ci->children[1].values)); + ci->children[1].values->type = OCONFIG_TYPE_STRING; + ci->children[1].values->value.string = "user"; + ci->children[2].key = "Passwd"; + ci->children[2].values_num = 1; + ci->children[2].values = calloc(1, sizeof(*ci->children[2].values)); + ci->children[2].values->type = OCONFIG_TYPE_STRING; + ci->children[2].values->value.string = "passwd"; + ci->children[3].key = "Queries"; + ci->children[3].values_num = 1; + ci->children[3].values = calloc(1, sizeof(*ci->children[2].values)); + ci->children[3].values->type = OCONFIG_TYPE_STRING; + ci->children[3].values->value.string = "fans"; + + ctx.services = llist_create(); + + int ret = redfish_config_service(ci); + + EXPECT_EQ_INT(0, ret); + + for (llentry_t *llserv = llist_head(ctx.services); llserv != NULL; + llserv = llserv->next) { + redfish_service_t *serv = (redfish_service_t *)llserv->value; + sfree(serv->name); + sfree(serv->host); + sfree(serv->user); + sfree(serv->passwd); + for (int i = 0; i < serv->queries_num; i++) + sfree(serv->queries[i]); + sfree(serv->queries); + sfree(serv); + } + llist_destroy(ctx.services); + + sfree(ci->children[3].values); + sfree(ci->children[2].values); + sfree(ci->children[1].values); + sfree(ci->children[0].values); + sfree(ci->children); + sfree(ci->values); + sfree(ci); + return 0; +} + +DEF_TEST(process_payload_property) { + redfish_property_t property; + property.name = "Abc"; + property.plugin_inst = "TestPluginInstance"; + property.type = "MAGIC"; + property.type_inst = "TestTypeInstance"; + + redfish_resource_t resource; + resource.name = "ResourceName"; + + redfish_service_t service; + service.name = "localhost"; + + const char *json_text = "[" + " { \"Abc\": 4567 }" + "]"; + json_error_t error; + json_t *root = json_loads(json_text, 0, &error); + + if (!root) { + return -1; + } + + redfish_process_payload_property(&property, root, &resource, &service); + + json_decref(root); + + value_list_t *v = redfish_test_get_last_dispatched_value_list(); + EXPECT_EQ_INT(1, v->values_len); + EXPECT_EQ_STR("MAGIC", v->type); + EXPECT_EQ_INT(4567, v->values->derive); + EXPECT_EQ_STR("TestPluginInstance", v->plugin_instance); + EXPECT_EQ_STR("TestTypeInstance", v->type_instance); + EXPECT_EQ_STR("localhost", v->host); + EXPECT_EQ_STR("redfish", v->plugin); + return 0; +} + +DEF_TEST(service_destroy) { + /* Check for memory leaks when a service is destroyed */ + redfish_service_t *service = calloc(1, sizeof(*service)); + + service->name = strdup("Name"); + service->host = strdup("http://localhost:1234"); + service->user = strdup("User"); + service->passwd = strdup("Password"); + service->token = strdup("Token"); + + service->queries = calloc(2, sizeof(*service->queries)); + service->queries[0] = strdup("Query1"); + service->queries[1] = strdup("Query2"); + service->queries_num = 2; + + service->query_ptrs = llist_create(); + + service->flags |= REDFISH_FLAG_SERVICE_NO_VERSION_DOC; + service->auth.authCodes.userPass.username = service->user; + service->auth.authCodes.userPass.password = service->passwd; + service->redfish = createServiceEnumerator(service->host, NULL, + &service->auth, service->flags); + + redfish_service_destroy(service); + return 0; +} + +DEF_TEST(job_destroy) { + /* Check for memory leaks when a job is destroyed */ + redfish_job_t *job = calloc(1, sizeof(*job)); + redfish_job_destroy(job); + return 0; +} + +DEF_TEST(json_get_string_1) { + const char *json_text = "{ \"MemberId\": \"1234\" }"; + + json_error_t error; + json_t *root = json_loads(json_text, 0, &error); + + if (!root) { + return -1; + } + + char str[20]; + json_t *json = json_object_get(root, "MemberId"); + redfish_json_get_string(str, sizeof(str), json); + + json_decref(root); + + EXPECT_EQ_STR("1234", str); + return 0; +} + +DEF_TEST(json_get_string_2) { + const char *json_text = "{ \"MemberId\": 9876 }"; + + json_error_t error; + json_t *root = json_loads(json_text, 0, &error); + + if (!root) { + return -1; + } + + char str[20]; + json_t *json = json_object_get(root, "MemberId"); + redfish_json_get_string(str, sizeof(str), json); + + json_decref(root); + + EXPECT_EQ_STR("9876", str); + return 0; +} + +int main(void) { + RUN_TEST(read_queries); + RUN_TEST(convert_val); + RUN_TEST(redfish_preconfig); + RUN_TEST(config_property); + RUN_TEST(config_resource); + RUN_TEST(config_query); + RUN_TEST(config_service); + RUN_TEST(process_payload_property); + RUN_TEST(service_destroy); + RUN_TEST(job_destroy); + RUN_TEST(json_get_string_1); + RUN_TEST(json_get_string_2); + END_TEST; +}