From b934f1572cdb54cf485b600546a72dbf9929ba2c Mon Sep 17 00:00:00 2001 From: Kamil Wiatrowski Date: Fri, 17 May 2019 15:05:03 +0200 Subject: [PATCH] capabilities plugin: new plugin to read static platform data Read decoded SMBIOS data from dmidecode and expose it through httpd. Using libmicrohttpd and libjansson. Change-Id: I7bcbb8a0747fbaaa3d5e4affe78b828ca0d8837d Signed-off-by: Kamil Wiatrowski --- Makefile.am | 26 +++ README | 7 +- configure.ac | 7 + src/capabilities.c | 408 ++++++++++++++++++++++++++++++++++++++++ src/capabilities_test.c | 273 +++++++++++++++++++++++++++ src/collectd.conf.in | 7 +- src/collectd.conf.pod | 30 +++ src/utils/dmi/dmi.c | 189 +++++++++++++++++++ src/utils/dmi/dmi.h | 160 ++++++++++++++++ 9 files changed, 1105 insertions(+), 2 deletions(-) create mode 100644 src/capabilities.c create mode 100644 src/capabilities_test.c create mode 100644 src/utils/dmi/dmi.c create mode 100644 src/utils/dmi/dmi.h diff --git a/Makefile.am b/Makefile.am index 0b3408877..dabc08ecf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -757,6 +757,32 @@ buddyinfo_la_LDFLAGS = $(PLUGIN_LDFLAGS) buddyinfo_la_LIBADD = libignorelist.la endif +if BUILD_PLUGIN_CAPABILITIES +pkglib_LTLIBRARIES += capabilities.la +capabilities_la_SOURCES = src/capabilities.c \ + src/utils/dmi/dmi.c \ + src/utils/dmi/dmi.h +capabilities_la_CPPFLAGS = $(AM_CPPFLAGS) \ + $(BUILD_WITH_LIBMICROHTTPD_CPPFLAGS) $(BUILD_WITH_LIBJANSSON_CPPFLAGS) +capabilities_la_LDFLAGS = $(PLUGIN_LDFLAGS) \ + $(BUILD_WITH_LIBMICROHTTPD_LDFLAGS) $(BUILD_WITH_LIBJANSSON_LDFLAGS) +capabilities_la_LIBADD = $(BUILD_WITH_LIBMICROHTTPD_LIBS) \ + $(BUILD_WITH_LIBJANSSON_LIBS) + +test_plugin_capabilities_SOURCES = \ + src/capabilities_test.c \ + src/daemon/configfile.c \ + src/daemon/types_list.c +test_plugin_capabilities_CPPFLAGS = $(AM_CPPFLAGS) \ + $(BUILD_WITH_LIBJANSSON_CPPFLAGS) +test_plugin_capabilities_LDFLAGS = $(PLUGIN_LDFLAGS) \ + $(BUILD_WITH_LIBJANSSON_LDFLAGS) +test_plugin_capabilities_LDADD = liboconfig.la libplugin_mock.la \ + $(BUILD_WITH_LIBJANSSON_LIBS) +check_PROGRAMS += test_plugin_capabilities +TESTS += test_plugin_capabilities +endif + if BUILD_PLUGIN_CEPH pkglib_LTLIBRARIES += ceph.la ceph_la_SOURCES = src/ceph.c diff --git a/README b/README index bd75b071c..54babf6ac 100644 --- a/README +++ b/README @@ -48,6 +48,10 @@ Features - buddyinfo Statistics from buddyinfo file about memory fragmentation. + - capabilities + Platform static capabilities decoded from SMBIOS using dmidecode. + + - ceph Statistics from the Ceph distributed storage system. @@ -843,7 +847,8 @@ Prerequisites * libjansson (optional) - Used by the `dpdk_telemetry` plugin. + Parse JSON data. This is used for the `capabilities' and `dpdk_telemetry` plugins. + * libjevents (optional) The jevents library is used by the `intel_pmu' plugin to access the Linux diff --git a/configure.ac b/configure.ac index 6e886b94c..9c1d0461f 100644 --- a/configure.ac +++ b/configure.ac @@ -6425,6 +6425,7 @@ plugin_barometer="no" plugin_battery="no" plugin_bind="no" plugin_buddyinfo="no" +plugin_capabilities="no" plugin_ceph="no" plugin_cgroups="no" plugin_connectivity="no" @@ -6559,6 +6560,10 @@ if test "x$ac_system" = "xLinux"; then if test "x$have_pci_regs_h" = "xyes"; then plugin_pcie_errors="yes" fi + + if test "x$with_libmicrohttpd" = "xyes" && test "x$with_libjansson" = "xyes"; then + plugin_capabilities="yes" + fi fi if test "x$ac_system" = "xOpenBSD"; then @@ -6868,6 +6873,7 @@ AC_PLUGIN([barometer], [$plugin_barometer], [Barometer sensor AC_PLUGIN([battery], [$plugin_battery], [Battery statistics]) AC_PLUGIN([bind], [$plugin_bind], [ISC Bind nameserver statistics]) AC_PLUGIN([buddyinfo], [$plugin_buddyinfo], [buddyinfo statistics]) +AC_PLUGIN([capabilities], [$plugin_capabilities], [Platform static capabilities]) AC_PLUGIN([ceph], [$plugin_ceph], [Ceph daemon statistics]) AC_PLUGIN([cgroups], [$plugin_cgroups], [CGroups CPU usage accounting]) AC_PLUGIN([chrony], [yes], [Chrony statistics]) @@ -7306,6 +7312,7 @@ AC_MSG_RESULT([ barometer . . . . . . $enable_barometer]) AC_MSG_RESULT([ battery . . . . . . . $enable_battery]) AC_MSG_RESULT([ bind . . . . . . . . $enable_bind]) AC_MSG_RESULT([ buddyinfo . . . . . . $enable_buddyinfo]) +AC_MSG_RESULT([ capabilities . . . . $enable_capabilities]) AC_MSG_RESULT([ ceph . . . . . . . . $enable_ceph]) AC_MSG_RESULT([ cgroups . . . . . . . $enable_cgroups]) AC_MSG_RESULT([ chrony. . . . . . . . $enable_chrony]) diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 000000000..29bba0433 --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,408 @@ +/* + * MIT License + * + * Copyright(c) 2019 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: + * Kamil Wiatrowski + * + */ + +#include "collectd.h" + +#include "plugin.h" +#include "utils/common/common.h" +#include "utils/dmi/dmi.h" + +#include + +#include +#include + +#define CAP_PLUGIN "capabilities" +#define CONTENT_TYPE_JSON "application/json" +#define LISTEN_BACKLOG 16 + +typedef struct dmi_type_name_s { + dmi_type type; + const char *name; +} dmi_type_name_t; + +static char *g_cap_string = NULL; +static char *httpd_host = NULL; +static unsigned short httpd_port = 9104; +static struct MHD_Daemon *httpd; + +static dmi_type_name_t types_list[] = { + {BIOS, "BIOS"}, + {SYSTEM, "SYSTEM"}, + {BASEBOARD, "BASEBOARD"}, + {PROCESSOR, "PROCESSORS"}, + {CACHE, "CACHE"}, + {PHYSICAL_MEMORY_ARRAY, "PHYSICAL MEMORY ARRAYS"}, + {MEMORY_DEVICE, "MEMORY DEVICES"}, + {IPMI_DEVICE, "IPMI DEVICE"}, + {ONBOARD_DEVICES_EXTENDED_INFORMATION, + "ONBOARD DEVICES EXTENDED INFORMATION"}}; + +static int cap_get_dmi_variables(json_t *parent, const dmi_type type, + const char *json_name) { + DEBUG(CAP_PLUGIN ": cap_get_dmi_variables: %d/%s.", type, json_name); + dmi_reader_t reader; + if (dmi_reader_init(&reader, type) != DMI_OK) { + ERROR(CAP_PLUGIN ": dmi_reader_init failed."); + return -1; + } + + json_t *section = NULL; + json_t *entries = NULL; + json_t *attributes = NULL; + json_t *arr = json_array(); + if (arr == NULL) { + ERROR(CAP_PLUGIN ": Failed to allocate json array."); + dmi_reader_clean(&reader); + return -1; + } + if (json_object_set_new(parent, json_name, arr)) { + ERROR(CAP_PLUGIN ": Failed to set array to parent."); + dmi_reader_clean(&reader); + return -1; + } + + while (reader.current_type != DMI_ENTRY_END) { + int status = dmi_read_next(&reader); + if (status != DMI_OK) { + ERROR(CAP_PLUGIN ": dmi_read_next failed."); + return -1; + } + + switch (reader.current_type) { + + case DMI_ENTRY_NAME: + DEBUG("%s", reader.name); + attributes = NULL; + section = json_object(); + if (section == NULL) { + ERROR(CAP_PLUGIN ": Failed to allocate json object."); + dmi_reader_clean(&reader); + return -1; + } + if (json_array_append_new(arr, section)) { + ERROR(CAP_PLUGIN ": Failed to append json entry."); + dmi_reader_clean(&reader); + return -1; + } + entries = json_object(); + if (entries == NULL) { + ERROR(CAP_PLUGIN ": Failed to allocate json object."); + dmi_reader_clean(&reader); + return -1; + } + if (json_object_set_new(section, reader.name, entries)) { + ERROR(CAP_PLUGIN ": Failed to set json entry."); + dmi_reader_clean(&reader); + return -1; + } + break; + + case DMI_ENTRY_MAP: + DEBUG(" %s:%s", reader.name, reader.value); + attributes = NULL; + if (entries == NULL) { + ERROR(CAP_PLUGIN ": unexpected dmi output format"); + dmi_reader_clean(&reader); + return -1; + } + if (json_object_set_new(entries, reader.name, + json_string(reader.value))) { + ERROR(CAP_PLUGIN ": Failed to set json entry."); + dmi_reader_clean(&reader); + return -1; + } + break; + + case DMI_ENTRY_LIST_NAME: + DEBUG(" %s:", reader.name); + if (entries == NULL) { + ERROR(CAP_PLUGIN ": unexpected dmi output format"); + dmi_reader_clean(&reader); + return -1; + } + attributes = json_array(); + if (attributes == NULL) { + ERROR(CAP_PLUGIN ": Failed to allocate json array for attributes."); + dmi_reader_clean(&reader); + return -1; + } + if (json_object_set_new(entries, reader.name, attributes)) { + ERROR(CAP_PLUGIN ": Failed to set json entry."); + dmi_reader_clean(&reader); + return -1; + } + break; + + case DMI_ENTRY_LIST_VALUE: + DEBUG(" %s", reader.value); + if (attributes == NULL) { + ERROR(CAP_PLUGIN ": unexpected dmi output format"); + dmi_reader_clean(&reader); + return -1; + } + if (json_array_append_new(attributes, json_string(reader.value))) { + ERROR(CAP_PLUGIN ": Failed to append json attribute."); + dmi_reader_clean(&reader); + return -1; + } + break; + + default: + section = NULL; + entries = NULL; + attributes = NULL; + break; + } + } + + return 0; +} + +/* http_handler is the callback called by the microhttpd library. It essentially + * handles all HTTP request aspects and creates an HTTP response. */ +static int cap_http_handler(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **connection_state) { + if (strcmp(method, MHD_HTTP_METHOD_GET) != 0) { + return MHD_NO; + } + + /* On the first call for each connection, return without anything further. + * The first time only the headers are valid, do not respond in the first + * round. The docs are not very specific on the issue. */ + if (*connection_state == NULL) { + /* set to a random non-NULL pointer. */ + *connection_state = &(int){44}; + return MHD_YES; + } + DEBUG(CAP_PLUGIN ": formatted response: %s", g_cap_string); + +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090500 + struct MHD_Response *res = MHD_create_response_from_buffer( + strlen(g_cap_string), g_cap_string, MHD_RESPMEM_PERSISTENT); +#else + struct MHD_Response *res = + MHD_create_response_from_data(strlen(g_cap_string), g_cap_string, 0, 0); +#endif + + MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON); + int status = MHD_queue_response(connection, MHD_HTTP_OK, res); + + MHD_destroy_response(res); + + return status; +} + +static void cap_logger(__attribute__((unused)) void *arg, char const *fmt, + va_list ap) { + char errbuf[1024]; + vsnprintf(errbuf, sizeof(errbuf), fmt, ap); + + ERROR(CAP_PLUGIN ": libmicrohttpd: %s", errbuf); +} + +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 +static int cap_open_socket() { + char service[NI_MAXSERV]; + snprintf(service, sizeof(service), "%hu", httpd_port); + + struct addrinfo *res; + int status = getaddrinfo(httpd_host, service, + &(struct addrinfo){ + .ai_flags = AI_PASSIVE | AI_ADDRCONFIG, + .ai_family = PF_INET, + .ai_socktype = SOCK_STREAM, + }, + &res); + if (status != 0) { + return -1; + } + + int fd = -1; + for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) { + int flags = ai->ai_socktype; +#ifdef SOCK_CLOEXEC + flags |= SOCK_CLOEXEC; +#endif + + fd = socket(ai->ai_family, flags, 0); + if (fd == -1) + continue; + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) != 0) { + WARNING(CAP_PLUGIN ": setsockopt(SO_REUSEADDR) failed: %s", STRERRNO); + close(fd); + fd = -1; + continue; + } + + if (bind(fd, ai->ai_addr, ai->ai_addrlen) != 0) { + close(fd); + fd = -1; + continue; + } + + if (listen(fd, LISTEN_BACKLOG) != 0) { + close(fd); + fd = -1; + continue; + } + + char str_node[NI_MAXHOST]; + char str_service[NI_MAXSERV]; + + getnameinfo(ai->ai_addr, ai->ai_addrlen, str_node, sizeof(str_node), + str_service, sizeof(str_service), + NI_NUMERICHOST | NI_NUMERICSERV); + + INFO(CAP_PLUGIN ": Listening on [%s]:%s.", str_node, str_service); + break; + } + + freeaddrinfo(res); + + return fd; +} +#endif + +static struct MHD_Daemon *cap_start_daemon() { +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 + int fd = cap_open_socket(); + if (fd == -1) { + ERROR(CAP_PLUGIN ": Opening a listening socket for [%s]:%hu failed.", + (httpd_host != NULL) ? httpd_host : "0.0.0.0", httpd_port); + return NULL; + } +#endif + unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG; +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00095300 + flags |= MHD_USE_POLL_INTERNAL_THREAD; +#endif + + struct MHD_Daemon *d = MHD_start_daemon( + flags, httpd_port, NULL, NULL, cap_http_handler, NULL, +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 + MHD_OPTION_LISTEN_SOCKET, fd, +#endif + MHD_OPTION_EXTERNAL_LOGGER, cap_logger, NULL, MHD_OPTION_END); + + if (d == NULL) { + ERROR(CAP_PLUGIN ": MHD_start_daemon() failed."); + return NULL; + } + + return d; +} + +static int cap_config(oconfig_item_t *ci) { + int status = 0; + for (int i = 0; i < ci->children_num; i++) { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp("Host", child->key) == 0) { +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 + status = cf_util_get_string(child, &httpd_host); +#else + ERROR(CAP_PLUGIN ": Option `Host' not supported. Please upgrade " + "libmicrohttpd to at least 0.9.0"); + status = -1; +#endif + } else if (strcasecmp("Port", child->key) == 0) { + int port = cf_util_get_port_number(child); + if (port > 0) + httpd_port = (unsigned short)port; + else { + ERROR(CAP_PLUGIN ": Wrong port number, correct range is 1-65535."); + status = -1; + } + } else { + ERROR(CAP_PLUGIN ": Unknown configuration option \"%s\".", child->key); + status = -1; + } + + if (status) { + ERROR(CAP_PLUGIN ": Invalid configuration parameter \"%s\".", child->key); + sfree(httpd_host); + break; + } + } + + return status; +} + +static int cap_shutdown() { + if (httpd != NULL) { + MHD_stop_daemon(httpd); + httpd = NULL; + } + + sfree(httpd_host); + sfree(g_cap_string); + return 0; +} + +static int cap_init(void) { + json_t *root = json_object(); + if (root == NULL) { + ERROR(CAP_PLUGIN ": Failed to allocate json root."); + cap_shutdown(); + return -1; + } + + for (int i = 0; i < STATIC_ARRAY_SIZE(types_list); i++) + if (cap_get_dmi_variables(root, types_list[i].type, types_list[i].name)) { + json_decref(root); + cap_shutdown(); + return -1; + } + + g_cap_string = json_dumps(root, JSON_COMPACT); + json_decref(root); + + if (g_cap_string == NULL) { + ERROR(CAP_PLUGIN ": json_dumps() failed."); + cap_shutdown(); + return -1; + } + + httpd = cap_start_daemon(); + if (httpd == NULL) { + cap_shutdown(); + return -1; + } + + return 0; +} + +void module_register(void) { + plugin_register_complex_config(CAP_PLUGIN, cap_config); + plugin_register_init(CAP_PLUGIN, cap_init); + plugin_register_shutdown(CAP_PLUGIN, cap_shutdown); +} diff --git a/src/capabilities_test.c b/src/capabilities_test.c new file mode 100644 index 000000000..95ac66657 --- /dev/null +++ b/src/capabilities_test.c @@ -0,0 +1,273 @@ +/* + * collectd - src/capabilities_test.c + * + * Copyright(c) 2019 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: + * Kamil Wiatrowski + */ + +#include "capabilities.c" /* sic */ +#include "testing.h" + +#define RESULT_STRING_JSON \ + "{\"TEST_TYPE\":[{\"Name\":{\"MapName1\":\"MapValue1\",\"ListName1\":[" \ + "\"ListValue1\",\"ListValue2\"],\"MapName2\":\"MapValue2\"}},{\"Name\":{" \ + "\"MapName1\":\"MapValue1\"}}]}" + +static int idx = 0; +static char *test_dmi[][2] = {{NULL, NULL}, + {"Name", NULL}, + {"MapName1", "MapValue1"}, + {"ListName1", NULL}, + {NULL, "ListValue1"}, + {NULL, "ListValue2"}, + {"MapName2", "MapValue2"}, + {NULL, NULL}, + {"Name", NULL}, + {"MapName1", "MapValue1"}, + {NULL, NULL}}; +static entry_type entry[] = { + DMI_ENTRY_NONE, DMI_ENTRY_NAME, DMI_ENTRY_MAP, + DMI_ENTRY_LIST_NAME, DMI_ENTRY_LIST_VALUE, DMI_ENTRY_LIST_VALUE, + DMI_ENTRY_MAP, DMI_ENTRY_NONE, DMI_ENTRY_NAME, + DMI_ENTRY_MAP, DMI_ENTRY_END}; +static size_t len = STATIC_ARRAY_SIZE(entry); + +/* mock functions */ +int dmi_reader_init(dmi_reader_t *reader, const dmi_type type) { + reader->current_type = DMI_ENTRY_NONE; + return DMI_OK; +} + +void dmi_reader_clean(dmi_reader_t *reader) {} + +int dmi_read_next(dmi_reader_t *reader) { + if (idx >= len) + return DMI_ERROR; + reader->current_type = entry[idx]; + reader->name = test_dmi[idx][0]; + reader->value = test_dmi[idx][1]; + idx++; + return DMI_OK; +} + +struct MHD_Daemon *MHD_start_daemon(unsigned int flags, unsigned short port, + MHD_AcceptPolicyCallback apc, void *apc_cls, + MHD_AccessHandlerCallback dh, void *dh_cls, + ...) { + return NULL; +} + +void MHD_stop_daemon(struct MHD_Daemon *daemon) {} + +struct MHD_Response * +MHD_create_response_from_buffer(size_t size, void *data, + enum MHD_ResponseMemoryMode mode) { + return NULL; +} + +struct MHD_Response *MHD_create_response_from_data(size_t size, void *data, + int must_free, + int must_copy) { + return NULL; +} + +int MHD_add_response_header(struct MHD_Response *response, const char *header, + const char *content) { + return 0; +} + +int MHD_queue_response(struct MHD_Connection *connection, + unsigned int status_code, + struct MHD_Response *response) { + return MHD_HTTP_OK; +} + +void MHD_destroy_response(struct MHD_Response *response) {} +/* end mock functions */ + +DEF_TEST(plugin_config) { + oconfig_item_t test_cfg_parent = {"capabilities", NULL, 0, NULL, NULL, 0}; + char value_buff[256] = "1234"; + char key_buff[256] = "port"; + oconfig_value_t test_cfg_value = {{value_buff}, OCONFIG_TYPE_STRING}; + oconfig_item_t test_cfg = { + key_buff, &test_cfg_value, 1, &test_cfg_parent, NULL, 0}; + + test_cfg_parent.children = &test_cfg; + test_cfg_parent.children_num = 1; + + int ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1234, httpd_port); + OK(NULL == httpd_host); + + strncpy(value_buff, "1", STATIC_ARRAY_SIZE(value_buff)); + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, httpd_port); + + strncpy(value_buff, "65535", STATIC_ARRAY_SIZE(value_buff)); + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(65535, httpd_port); + +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 + strncpy(value_buff, "127.0.0.1", STATIC_ARRAY_SIZE(value_buff)); + strncpy(key_buff, "host", STATIC_ARRAY_SIZE(key_buff)); + + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("127.0.0.1", httpd_host); + EXPECT_EQ_INT(65535, httpd_port); + + free(httpd_host); + strncpy(key_buff, "port", STATIC_ARRAY_SIZE(key_buff)); +#endif + + double port_value = 65535; + oconfig_value_t test_cfg_value2 = {{.number = port_value}, + OCONFIG_TYPE_NUMBER}; + test_cfg.values = &test_cfg_value2; + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(65535, httpd_port); + + return 0; +} + +DEF_TEST(plugin_config_fail) { + oconfig_item_t test_cfg_parent = {"capabilities", NULL, 0, NULL, NULL, 0}; + char value_buff[256] = "1"; + char key_buff[256] = "aport"; + oconfig_value_t test_cfg_value = {{value_buff}, OCONFIG_TYPE_STRING}; + oconfig_item_t test_cfg = { + key_buff, &test_cfg_value, 1, &test_cfg_parent, NULL, 0}; + + test_cfg_parent.children = &test_cfg; + test_cfg_parent.children_num = 1; + + unsigned short default_port = httpd_port; + int ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(-1, ret); + EXPECT_EQ_INT(default_port, httpd_port); + OK(NULL == httpd_host); + + /* Correct port range is 1 - 65535 */ + strncpy(key_buff, "port", STATIC_ARRAY_SIZE(key_buff)); + strncpy(value_buff, "-1", STATIC_ARRAY_SIZE(value_buff)); + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(-1, ret); + EXPECT_EQ_INT(default_port, httpd_port); + OK(NULL == httpd_host); + + strncpy(value_buff, "65536", STATIC_ARRAY_SIZE(value_buff)); + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(-1, ret); + EXPECT_EQ_INT(default_port, httpd_port); + OK(NULL == httpd_host); + +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090000 + strncpy(value_buff, "127.0.0.1", STATIC_ARRAY_SIZE(value_buff)); + strncpy(key_buff, "host", STATIC_ARRAY_SIZE(key_buff)); + test_cfg_value.type = OCONFIG_TYPE_NUMBER; + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(-1, ret); + EXPECT_EQ_INT(default_port, httpd_port); + OK(NULL == httpd_host); + + strncpy(key_buff, "port", STATIC_ARRAY_SIZE(key_buff)); +#endif + + double port_value = 65536; + oconfig_value_t test_cfg_value2 = {{.number = port_value}, + OCONFIG_TYPE_NUMBER}; + test_cfg.values = &test_cfg_value2; + ret = cap_config(&test_cfg_parent); + EXPECT_EQ_INT(-1, ret); + EXPECT_EQ_INT(default_port, httpd_port); + OK(NULL == httpd_host); + + return 0; +} + +DEF_TEST(http_handler) { + void *state = NULL; + g_cap_string = "TEST"; + int ret = cap_http_handler(NULL, NULL, NULL, MHD_HTTP_METHOD_PUT, NULL, NULL, + NULL, &state); + EXPECT_EQ_INT(MHD_NO, ret); + OK(NULL == state); + + ret = cap_http_handler(NULL, NULL, NULL, MHD_HTTP_METHOD_GET, NULL, NULL, + NULL, &state); + EXPECT_EQ_INT(MHD_YES, ret); + CHECK_NOT_NULL(state); + + ret = cap_http_handler(NULL, NULL, NULL, MHD_HTTP_METHOD_GET, NULL, NULL, + NULL, &state); + EXPECT_EQ_INT(MHD_HTTP_OK, ret); + CHECK_NOT_NULL(state); + + g_cap_string = NULL; + + return 0; +} + +DEF_TEST(get_dmi_variables) { + json_t *root = json_object(); + CHECK_NOT_NULL(root); + + int ret = cap_get_dmi_variables(root, 0, "TEST_TYPE"); + EXPECT_EQ_INT(0, ret); + + char *test_str = json_dumps(root, JSON_COMPACT); + CHECK_NOT_NULL(test_str); + json_decref(root); + EXPECT_EQ_STR(RESULT_STRING_JSON, test_str); + + free(test_str); + + root = json_object(); + CHECK_NOT_NULL(root); + ret = cap_get_dmi_variables(root, 1, "TEST_TYPE2"); + EXPECT_EQ_INT(-1, ret); + + test_str = json_dumps(root, JSON_COMPACT); + CHECK_NOT_NULL(test_str); + json_decref(root); + EXPECT_EQ_STR("{\"TEST_TYPE2\":[]}", test_str); + + free(test_str); + + return 0; +} + +int main(void) { + RUN_TEST(plugin_config_fail); + RUN_TEST(plugin_config); + + RUN_TEST(http_handler); + RUN_TEST(get_dmi_variables); + + END_TEST; +} diff --git a/src/collectd.conf.in b/src/collectd.conf.in index f6a62060b..aae6ccdc4 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -100,6 +100,7 @@ #@BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery #@BUILD_PLUGIN_BIND_TRUE@LoadPlugin bind #@BUILD_PLUGIN_BUDDYINFO_TRUE@LoadPlugin buddyinfo +#@BUILD_PLUGIN_CAPABILITIES_TRUE@LoadPlugin capabilities #@BUILD_PLUGIN_CEPH_TRUE@LoadPlugin ceph #@BUILD_PLUGIN_CGROUPS_TRUE@LoadPlugin cgroups #@BUILD_PLUGIN_CHRONY_TRUE@LoadPlugin chrony @@ -367,7 +368,11 @@ # # -# Zone "Normal" +# Zone "Normal" +# + +# +# Port "9104" # # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 1e94a8c9d..92d6e3e4a 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -1469,6 +1469,36 @@ Zone to collect info about. Will collect all zones by default. =back +=head2 Plugin C + +The C plugin collects selected static platform data using +I and expose it through micro embedded webserver. The data +returned by plugin is in json format. + +B + + + Host "" + Port "9104" + + +Available configuration options for the C plugin: + +=over 4 + +=item B I + +Bind to the hostname / address I. By default, the plugin will bind to the +"any" address, i.e. accept packets sent to any of the hosts addresses. + +This option is supported only for libmicrohttpd newer than 0.9.0. + +=item B I + +Port the embedded webserver should listen on. Defaults to B<9104>. + +=back + =head2 Plugin C The ceph plugin collects values from JSON data to be parsed by B diff --git a/src/utils/dmi/dmi.c b/src/utils/dmi/dmi.c new file mode 100644 index 000000000..12e98845a --- /dev/null +++ b/src/utils/dmi/dmi.c @@ -0,0 +1,189 @@ +/* + * MIT License + * + * Copyright(c) 2019 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: + * Kamil Wiatrowski + * + */ + +/* + * The util helps to parse the output from dmidecode command. + * It expects that the output follows the given format, the size of + * tabulations does not matter: + * + * Handle 1(...) + * SMBIOS type name + * item1: value + * item2: value + * list name: + * list elem1 + * list elem2 + * (...) + * item3: value + * (...) + * + * Handle 2(...) + * SMBIOS type name + * (and so on ...) + * + */ + +#include "collectd.h" + +#include "utils/common/common.h" +#include "utils/dmi/dmi.h" + +#define UTIL_NAME "DMI_READER" + +#define DMIDECODE_CMD_FMT_LEN DMI_MAX_LEN + +static int dmi_look_for_handle(dmi_reader_t *r); + +static int dmi_read_entry(dmi_reader_t *r) { + while (fgets(r->_buff, STATIC_ARRAY_SIZE(r->_buff), r->_fd) != NULL) { + char *buff = r->_buff; + while (isspace(*buff)) + buff++; + if (*buff == '\0') { + r->current_type = DMI_ENTRY_NONE; + r->_read_callback = dmi_look_for_handle; + return DMI_OK; + } + + strstripnewline(buff); + char *sep = strchr(buff, ':'); + if (sep == NULL) { + r->value = buff; + r->current_type = DMI_ENTRY_LIST_VALUE; + return DMI_OK; + } + + sep[0] = '\0'; + r->name = buff; + if (sep[1] == '\0') { + r->current_type = DMI_ENTRY_LIST_NAME; + } else { + sep++; + while (isspace(*sep)) + sep++; + if (*sep == '\0') + INFO(UTIL_NAME ": value is empty for: \'%s\'.", r->_buff); + r->value = sep; + r->current_type = DMI_ENTRY_MAP; + } + return DMI_OK; + } + + r->current_type = DMI_ENTRY_END; + return DMI_OK; +} + +static int dmi_read_type_name(dmi_reader_t *r) { + while (fgets(r->_buff, STATIC_ARRAY_SIZE(r->_buff), r->_fd) != NULL) { + strstripnewline(r->_buff); + if (strlen(r->_buff) == 0) { + ERROR(UTIL_NAME ": unexpected format of dmidecode output."); + return DMI_ERROR; + } + + r->name = r->_buff; + r->current_type = DMI_ENTRY_NAME; + r->_read_callback = dmi_read_entry; + + return DMI_OK; + } + + r->current_type = DMI_ENTRY_END; + return DMI_OK; +} + +static int dmi_look_for_handle(dmi_reader_t *r) { + while (fgets(r->_buff, STATIC_ARRAY_SIZE(r->_buff), r->_fd) != NULL) { + const char *handle = "Handle"; + if (strncmp(handle, r->_buff, strlen(handle)) != 0) + continue; + + r->_read_callback = dmi_read_type_name; + return DMI_OK; + } + + r->current_type = DMI_ENTRY_END; + return DMI_OK; +} + +int dmi_reader_init(dmi_reader_t *reader, const dmi_type type) { + if (reader == NULL) { + ERROR(UTIL_NAME ".%s: NULL pointer.", __func__); + return DMI_ERROR; + } + char cmd[DMIDECODE_CMD_FMT_LEN]; + + if (type == DMI_TYPE_ALL) + strncpy(cmd, "dmidecode 2>/dev/null", STATIC_ARRAY_SIZE(cmd)); + else + snprintf(cmd, STATIC_ARRAY_SIZE(cmd), "dmidecode -t %d 2>/dev/null", type); + + DEBUG(UTIL_NAME ": dmidecode cmd=\'%s\'.", cmd); + + reader->_fd = popen(cmd, "r"); + if (reader->_fd == NULL) { + ERROR(UTIL_NAME ": popen failed."); + return DMI_ERROR; + } + + reader->name = NULL; + reader->value = NULL; + reader->_read_callback = dmi_look_for_handle; + reader->current_type = DMI_ENTRY_NONE; + + return DMI_OK; +} + +void dmi_reader_clean(dmi_reader_t *reader) { + if (reader == NULL) { + WARNING(UTIL_NAME ".%s: NULL pointer.", __func__); + return; + } + + if (reader->_fd) + pclose(reader->_fd); + + reader->_fd = NULL; + reader->_read_callback = NULL; +} + +int dmi_read_next(dmi_reader_t *reader) { + if (reader == NULL || reader->_read_callback == NULL || reader->_fd == NULL) { + ERROR(UTIL_NAME ".%s: NULL pointer.", __func__); + return DMI_ERROR; + } + int ret = reader->_read_callback(reader); + + if (reader->current_type == DMI_ENTRY_END || ret == DMI_ERROR) { + pclose(reader->_fd); + reader->_read_callback = NULL; + DEBUG(UTIL_NAME ": dmidecode reader finished, status=%d.", ret); + } + + return ret; +} diff --git a/src/utils/dmi/dmi.h b/src/utils/dmi/dmi.h new file mode 100644 index 000000000..181477f90 --- /dev/null +++ b/src/utils/dmi/dmi.h @@ -0,0 +1,160 @@ +/* + * MIT License + * + * Copyright(c) 2019 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: + * Kamil Wiatrowski + */ + +#ifndef UTILS_DMI_H +#define UTILS_DMI_H + +#define DMI_MAX_LEN 256 + +#define DMI_OK 0 +#define DMI_ERROR -1 + +/* Following values should match Types assigned in System Management BIOS + * (SMBIOS) Reference Specification + */ +typedef enum dmi_type_e { + BIOS = 0, + SYSTEM, + BASEBOARD, + CHASSIS, + PROCESSOR, + MEMORY_CONTROLLER, + MEMORY_MODULE, + CACHE, + PORT_CONNECTOR, + SYSTEM_SLOTS, + ON_BOARD_DEVICES, + OEM_STRINGS, + SYSTEM_CONFIGURATION_OPTIONS, + BIOS_LANGUAGE, + GROUP_ASSOCIATIONS, + SYSTEM_EVENT_LOG, + PHYSICAL_MEMORY_ARRAY, + MEMORY_DEVICE, + MEMORY_ERROR_32_BIT, + MEMORY_ARRAY_MAPPED_ADDRESS, + MEMORY_DEVICE_MAPPED_ADDRESS, + BUILT_IN_POINTING_DEVICE, + PORTABLE_BATTERY, + SYSTEM_RESET, + HARDWARE_SECURITY, + SYSTEM_POWER_CONTROLS, + VOLTAGE_PROBE, + COOLING_DEVICE, + TEMPERATURE_PROBE, + ELECTRICAL_CURRENT_PROBE, + OUT_OF_BAND_REMOTE_ACCESS, + BOOT_INTEGRITY_SERVICES, + SYSTEM_BOOT, + MEMORY_ERROR_64_BIT, + MANAGEMENT_DEVICE, + MANAGEMENT_DEVICE_COMPONENT, + MANAGEMENT_DEVICE_THRESHOLD_DATA, + MEMORY_CHANNEL, + IPMI_DEVICE, + POWER_SUPPLY, + ADDITIONAL_INFORMATION, + ONBOARD_DEVICES_EXTENDED_INFORMATION, + MANAGEMENT_CONTROLLER_HOST_INTERFACE, + DMI_TYPE_ALL /* special type to read all SMBIOS handles */ +} dmi_type; + +typedef enum entry_type_e { + DMI_ENTRY_NONE, + DMI_ENTRY_NAME, + DMI_ENTRY_MAP, + DMI_ENTRY_LIST_NAME, + DMI_ENTRY_LIST_VALUE, + DMI_ENTRY_END +} entry_type; + +typedef struct dmi_reader_s { + FILE *_fd; + char _buff[DMI_MAX_LEN]; + int (*_read_callback)(struct dmi_reader_s *reader); + /* Type of current entry */ + entry_type current_type; + /* Entry name, the pointer changes after every read. */ + char *name; + /* Entry value, the pointer changes after every read. */ + char *value; +} dmi_reader_t; + +/* + * NAME + * dmi_reader_init + * + * DESCRIPTION + * Initialize reader structure and use popen to read SMBIOS using dmidecode. + * + * PARAMETERS + * `reader' Pointer to dmi_reader structure, can't be NULL. + * `type' Type of DMI entries to read. + * + * RETURN VALUE + * DMI_OK upon success or DMI_ERROR if an error occurred. + * + * NOTES + * The dmi_reader_clean needs to be called to close pipe if + * reader hasn't finished yet. In case of error cleanup is + * done by the function itself. + */ +int dmi_reader_init(dmi_reader_t *reader, const dmi_type type); + +/* + * NAME + * dmi_reader_clean + * + * DESCRIPTION + * Clean up reader structure and close pipe. + * + * PARAMETERS + * `reader' Pointer to dmi_reader structure, can't be NULL. + */ +void dmi_reader_clean(dmi_reader_t *reader); + +/* + * NAME + * dmi_reader_next + * + * DESCRIPTION + * Read next entry and store values. + * + * PARAMETERS + * `reader' Pointer to dmi_reader structure, can't be NULL. + * + * RETURN VALUE + * DMI_OK upon success or DMI_ERROR if an error occurred. + * + * NOTES + * In case of error the function cleans the reader structure. + * After all entries are read the function sets state to DMI_ENTRY_END + * and no further clean-up is required. + */ +int dmi_read_next(dmi_reader_t *reader); + +#endif /* UTILS_DMI_H */ -- 2.39.5