Read decoded SMBIOS data from dmidecode and expose it through httpd.
Using libmicrohttpd and libjansson.
Change-Id: I7bcbb8a0747fbaaa3d5e4affe78b828ca0d8837d
Signed-off-by: Kamil Wiatrowski <kamilx.wiatrowski@intel.com>
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
- buddyinfo
Statistics from buddyinfo file about memory fragmentation.
+ - capabilities
+ Platform static capabilities decoded from SMBIOS using dmidecode.
+ <https://www.nongnu.org/dmidecode/>
+
- ceph
Statistics from the Ceph distributed storage system.
<http://netfilter.org/>
* libjansson (optional)
- Used by the `dpdk_telemetry` plugin.
+ Parse JSON data. This is used for the `capabilities' and `dpdk_telemetry` plugins.
+ <http://www.digip.org/jansson/>
* libjevents (optional)
The jevents library is used by the `intel_pmu' plugin to access the Linux
plugin_battery="no"
plugin_bind="no"
plugin_buddyinfo="no"
+plugin_capabilities="no"
plugin_ceph="no"
plugin_cgroups="no"
plugin_connectivity="no"
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
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])
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])
--- /dev/null
+/*
+ * 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 <kamilx.wiatrowski@intel.com>
+ *
+ */
+
+#include "collectd.h"
+
+#include "plugin.h"
+#include "utils/common/common.h"
+#include "utils/dmi/dmi.h"
+
+#include <microhttpd.h>
+
+#include <jansson.h>
+#include <netdb.h>
+
+#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);
+}
--- /dev/null
+/*
+ * 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 <kamilx.wiatrowski@intel.com>
+ */
+
+#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;
+}
#@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
#</Plugin>
#<Plugin buddyinfo>
-# Zone "Normal"
+# Zone "Normal"
+#</Plugin>
+
+#<Plugin capabilities>
+# Port "9104"
#</Plugin>
#<Plugin ceph>
=back
+=head2 Plugin C<capabilities>
+
+The C<capabilities> plugin collects selected static platform data using
+I<dmidecode> and expose it through micro embedded webserver. The data
+returned by plugin is in json format.
+
+B<Synopsis:>
+
+ <Plugin capabilities>
+ Host "<hostname>"
+ Port "9104"
+ </Plugin>
+
+Available configuration options for the C<capabilities> plugin:
+
+=over 4
+
+=item B<Host> I<Hostname>
+
+Bind to the hostname / address I<Host>. 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<Port> I<Port>
+
+Port the embedded webserver should listen on. Defaults to B<9104>.
+
+=back
+
=head2 Plugin C<ceph>
The ceph plugin collects values from JSON data to be parsed by B<libyajl>
--- /dev/null
+/*
+ * 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 <kamilx.wiatrowski@intel.com>
+ *
+ */
+
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 <kamilx.wiatrowski@intel.com>
+ */
+
+#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 */