]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
This plugin, named mdevents, is responsible for gathering the events from RAID arrays...
authorKobylinski, Michal <michal.kobylinski@intel.com>
Mon, 13 Jan 2020 08:50:15 +0000 (09:50 +0100)
committerroot <root@gklab-16-023.igk.intel.com>
Mon, 13 Jan 2020 17:42:53 +0000 (18:42 +0100)
Mdevents needs the syslog and mdadm to be present on a platform that collectd is launched.

Based on the naming of existing dpdk plugins (dpdkstat for metrics, dpdkevents for notifications) and the fact that there is already plugin called md for gathering metrics from RAIDs, giving this plugin name mdevents felt like the best option.

A slight change to ignorelist API was introduced - it is now possible to retrieve the count of nodes in array and the current ignore setting for given array.

ChangeLog: New plugin to read RAID events

Makefile.am
configure.ac
src/collectd.conf.in
src/collectd.conf.pod
src/mdevents.c [new file with mode: 0644]
src/mdevents_test.c [new file with mode: 0644]

index 258603f91fe1c9d3480fe50796fb9703d44dc261..b4573d78140583fe16cfe97fc48081cb07fa5461 100644 (file)
@@ -1288,6 +1288,21 @@ md_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 md_la_LIBADD = libignorelist.la
 endif
 
+if BUILD_PLUGIN_MDEVENTS
+pkglib_LTLIBRARIES += mdevents.la
+mdevents_la_SOURCES = src/mdevents.c
+mdevents_la_CFLAGS = $(AM_FLAGS)
+mdevents_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+mdevents_la_LIBADD = libignorelist.la
+
+test_plugin_mdevents_SOURCES = src/mdevents_test.c
+test_plugin_mdevents_CFLAGS = $(AM_FLAGS) -fprofile-arcs -ftest-coverage -O0 -g
+test_plugin_mdevents_LDFLAGS = $(PLUGIN_LDFLAGS) -fprofile-arcs
+test_plugin_mdevents_LDADD = libplugin_mock.la
+check_PROGRAMS += test_plugin_mdevents
+TESTS += test_plugin_mdevents
+endif
+
 if BUILD_PLUGIN_MEMCACHEC
 pkglib_LTLIBRARIES += memcachec.la
 memcachec_la_SOURCES = \
index 00e1f6a53e9d3c3e1b08830adf978b74481dffd5..207fe697e151a501d7f41af4e8c39c63addf471a 100644 (file)
@@ -6344,6 +6344,7 @@ plugin_irq="no"
 plugin_load="no"
 plugin_log_logstash="no"
 plugin_mcelog="no"
+plugin_mdevents="no"
 plugin_memory="no"
 plugin_multimeter="no"
 plugin_nfs="no"
@@ -6399,6 +6400,7 @@ if test "x$ac_system" = "xLinux"; then
   plugin_irq="yes"
   plugin_load="yes"
   plugin_mcelog="yes"
+  plugin_mdevents="yes"
   plugin_memory="yes"
   plugin_nfs="yes"
   plugin_numa="yes"
@@ -6799,6 +6801,7 @@ AC_PLUGIN([match_value],         [yes],                       [The value match])
 AC_PLUGIN([mbmon],               [yes],                       [Query mbmond])
 AC_PLUGIN([mcelog],              [$plugin_mcelog],            [Machine Check Exceptions notifications])
 AC_PLUGIN([md],                  [$have_linux_raid_md_u_h],   [md (Linux software RAID) devices])
+AC_PLUGIN([mdevents],            [$plugin_mdevents],          [Events from md (Linux Software RAID) devices])
 AC_PLUGIN([memcachec],           [$with_libmemcached],        [memcachec statistics])
 AC_PLUGIN([memcached],           [yes],                       [memcached statistics])
 AC_PLUGIN([memory],              [$plugin_memory],            [Memory usage])
@@ -7230,6 +7233,7 @@ AC_MSG_RESULT([    match_value . . . . . $enable_match_value])
 AC_MSG_RESULT([    mbmon . . . . . . . . $enable_mbmon])
 AC_MSG_RESULT([    mcelog  . . . . . . . $enable_mcelog])
 AC_MSG_RESULT([    md  . . . . . . . . . $enable_md])
+AC_MSG_RESULT([    mdevents  . . . . . . $enable_mdevents])
 AC_MSG_RESULT([    memcachec . . . . . . $enable_memcachec])
 AC_MSG_RESULT([    memcached . . . . . . $enable_memcached])
 AC_MSG_RESULT([    memory  . . . . . . . $enable_memory])
index 63db8b1a302bcd9ee2bf8a46a25ee5fd444d93d7..874f64c02a401b11fe8a165330fdf11f2a9d5362 100644 (file)
 #@BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon
 #@BUILD_PLUGIN_MCELOG_TRUE@LoadPlugin mcelog
 #@BUILD_PLUGIN_MD_TRUE@LoadPlugin md
+#@BUILD_PLUGIN_MDEVENTS_TRUE@LoadPlugin mdevents
 #@BUILD_PLUGIN_MEMCACHEC_TRUE@LoadPlugin memcachec
 #@BUILD_PLUGIN_MEMCACHED_TRUE@LoadPlugin memcached
 @BUILD_PLUGIN_MEMORY_TRUE@@BUILD_PLUGIN_MEMORY_TRUE@LoadPlugin memory
 #      IgnoreSelected false
 #</Plugin>
 
+#<Plugin mdevents>
+# Event ""
+# IgnoreEvent False
+# Array ""
+# IgnoreArray False
+#</Plugin>
+
 #<Plugin memcachec>
 #      <Page "plugin_instance">
 #              Server "localhost"
index cda1002c5de721ebde9d3ed6ada459ae1c255e0a..5601d0345a111c86a18add1c5950bad17c0bd1a8 100644 (file)
@@ -4068,6 +4068,94 @@ TCP-Port to connect to. Defaults to B<411>.
 
 =back
 
+=head2 Plugin C<mdevents >
+The I< mdevents > plugin collects status changes from md (Linux software RAID) devices.
+
+RAID arrays are meant to allow users/administrators to keep systems up and
+running, in case of common hardware problems (disk failure). Mdadm is the
+standard software RAID management tool for Linux. It provides the ability to 
+monitor "metadata event" occurring such as disk failures, clean-to-dirty 
+transitions, and etc. The kernel provides the ability to report such actions to 
+the userspace via sysfs, and mdadm takes action accordingly with the monitoring 
+capability. The mdmon polls the /sys looking for changes in the entries 
+array_state, sync_action, and per disk state attribute files. This is meaningful
+for RAID1, 5 and 10 only.
+
+Mdevents plugin is based on gathering RAID array events that are
+written to syslog by mdadm. After registering an event, it can send a collectd
+notification that contains mdadm event's data. Event consists of event type,
+raid array name and, for particular events, name of component device.
+
+Example message:
+
+C<Jan 17 05:24:27 pc1 mdadm[188]: NewArray event detected on md device /dev/md0>
+
+Plugin also classifies gathered event. This means that a notification will have
+a different severity {OKAY, WARNING, FAILURE} for particular mdadm event.
+
+For proper work, mdevents plugin needs syslog and mdadm utilities to be present on
+the running system. Otherwise it will not be compiled as a part of collectd.
+
+B<Synopsis:>
+
+  <Plugin mdevents>
+    Event ""
+    IgnoreEvent False
+    Array ""
+    IgnoreArray False
+  </Plugin>
+
+B<Plugin configuration:>
+
+Mdevents plugin's configuration is mostly based on IgnoreList, which is a collectd's
+utility. User can specify what particular events/RAID arrays lie in his interest.
+Setting of IgnoreEvent/IgnoreArray booleans won't take effect if Event/Array config
+lists are empty - plugin will accept entry anyway.
+B<Options:>
+=over 4
+
+=item B<Event> I<"EventName">
+
+Names of events to be monitored, separated by spaces. Possible events include:
+
+Event Name        | Class of event
+------------------+---------------
+DeviceDisappeared | FAILURE
+RebuildStarted    | OKAY
+RebuildNN         | OKAY
+RebuildFinished   | WARNING
+Fail              | FAILURE
+FailSpare         | WARNING
+SpareActive       | OKAY
+NewArray          | OKAY
+DegradedArray     | FAILURE
+MoveSpare         | WARNING
+SparesMissing     | WARNING
+TestMessage       | OKAY
+
+User should set the events that should be monitored as a strings separated by spaces,
+for example Events "DeviceDisappeared Fail DegradedArray".
+
+=item B<IgnoreEvent> I<false>|I<true>
+
+If I<IgnoreEvent> is set to true, events specified in I<Events> will be ignored.
+If it's false, only specified events will be monitored.
+
+=item B<Array> I<arrays>
+
+User can specify an array or a group of arrays using regexp. Plugin will accept
+only RAID arrays names that start with "/dev/md".
+
+=item B<IgnoreArray> I<false>|I<true>
+
+If I<IgnoreArray> is set to true, arrays specified in I<Array> will be ignored.
+If it's false, only specified events will be monitored.
+
+=back
+
 =head2 Plugin C<mcelog>
 
 The C<mcelog plugin> uses mcelog to retrieve machine check exceptions.
diff --git a/src/mdevents.c b/src/mdevents.c
new file mode 100644 (file)
index 0000000..7cbfb0a
--- /dev/null
@@ -0,0 +1,387 @@
+/**
+ * collectd - src/mdevents.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:
+ *   Krzysztof Kazimierczak <krzysztof.kazimierczak@intel.com>
+ *   Maciej Fijalkowski <maciej.fijalkowski@intel.com>
+ *   Michal Kobylinski <michal.kobylinski@intel.com>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "utils/common/common.h"
+#include "utils/ignorelist/ignorelist.h"
+
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MD_EVENTS_PLUGIN "mdevents"
+#define DAEMON_NAME "mdadm"
+
+#define MD_EVENTS_ERROR(err_msg, ...)                                          \
+  ERROR(MD_EVENTS_PLUGIN ": %s: " err_msg, __FUNCTION__, ##__VA_ARGS__)
+
+// Syslog can be located under different paths on various linux distros;
+// The following two cover the debian-based and redhat distros
+#define SYSLOG_PATH "/var/log/syslog"
+#define SYSLOG_MSG_PATH "/var/log/messages"
+
+#define MAX_SYSLOG_MESSAGE_LENGTH 1024
+#define MAX_ERROR_MSG 100
+#define MAX_MATCHES 4
+#define MD_ARRAY_NAME_PREFIX_LEN 7
+
+static FILE *syslog_file;
+static regex_t regex;
+static ignorelist_t *event_ignorelist;
+static ignorelist_t *array_ignorelist;
+
+static char regex_pattern[] =
+    "mdadm[\\[0-9]+\\]: ([a-zA-Z]+) event detected on md"
+    " device ([a-z0-9\\/\\.\\-]+)[^\\/\n]*([a-z0-9\\/\\.\\-]+)?";
+
+static const char *config_keys[] = {"Array", "Event", "IgnoreArray",
+                                    "IgnoreEvent"};
+static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
+
+enum md_events_regex_entries {
+  // matches of substrings in whole RE start from index 1
+  EVENT = 1,
+  MD_DEVICE = 2,
+  COMPONENT_DEVICE = 3,
+};
+
+typedef struct md_events_event_s {
+  char event_name[DATA_MAX_NAME_LEN];
+  char md_device[DATA_MAX_NAME_LEN];
+  char component_device[DATA_MAX_NAME_LEN];
+} md_events_event_t;
+
+static const char *md_events_critical_events[] = {"DeviceDisappeared",
+                                                  "DegradedArray", "Fail"};
+static const char *md_events_warning_events[] = {
+    "SparesMissing", "FailSpare", "MoveSpare", "RebuildFinished"};
+static const char *md_events_informative_events[] = {
+    "RebuildStarted", "RebuildNN", "SpareActive", "NewArray", "TestMessage"};
+
+static int md_events_classify_event(const char *event_name) {
+  int i;
+
+  for (i = 0; i < STATIC_ARRAY_SIZE(md_events_critical_events); i++) {
+    if (!strcmp(event_name, md_events_critical_events[i]))
+      return NOTIF_FAILURE;
+  }
+  for (i = 0; i < STATIC_ARRAY_SIZE(md_events_warning_events); i++) {
+    if (!strcmp(event_name, md_events_warning_events[i]))
+      return NOTIF_WARNING;
+  }
+  for (i = 0; i < STATIC_ARRAY_SIZE(md_events_informative_events); i++) {
+    if (!strcmp(event_name, md_events_informative_events[i]))
+      return NOTIF_OKAY;
+  }
+  // we do not support that event
+  return 0;
+}
+
+int md_events_parse_events(const char *events, size_t len) {
+  char *event_buf;
+  char *event;
+  char *save_ptr;
+
+  // have an additional byte for nul terminator
+  len++;
+
+  if ((event_buf = calloc(1, len)) == NULL) {
+    MD_EVENTS_ERROR("calloc failed for event_buf\n");
+    return -1;
+  }
+
+  // need a non-const copy so that strtok can work on this
+  strncpy(event_buf, events, len);
+  event_buf[len - 1] = '\0';
+  event = strtok_r(event_buf, " ", &save_ptr);
+  if (event == NULL) {
+    MD_EVENTS_ERROR("Couldn't parse events specified by user\n");
+    free(event_buf);
+    return -1;
+  }
+  // verify that user-defined event from config is the one that
+  // we/mdadm support
+  if (md_events_classify_event(event)) {
+    ignorelist_add(event_ignorelist, event);
+  } else {
+    MD_EVENTS_ERROR("Unclassified event \"%s\"; Ignoring.\n", event);
+    free(event_buf);
+    return -1;
+  }
+
+  while ((event = strtok_r(NULL, " ", &save_ptr)) != NULL) {
+    if (md_events_classify_event(event)) {
+      ignorelist_add(event_ignorelist, event);
+    } else {
+      MD_EVENTS_ERROR("Unclassified event \"%s\"; Ignoring.\n", event);
+      free(event_buf);
+      return -1;
+    }
+  }
+  free(event_buf);
+  return 0;
+}
+
+static int md_events_parse_boolean(const char *bool_setting,
+                                   ignorelist_t *list) {
+  if (IS_TRUE(bool_setting)) {
+    ignorelist_set_invert(list, 0);
+    return 0;
+  } else if (IS_FALSE(bool_setting)) {
+    ignorelist_set_invert(list, 1);
+    return 0;
+  }
+  return 1;
+}
+
+static int md_events_config(const char *key, const char *value) {
+  size_t len = strlen(value);
+
+  if (array_ignorelist == NULL) {
+    array_ignorelist = ignorelist_create(/* invert = */ 1);
+    if (array_ignorelist == NULL)
+      return -1;
+  }
+  if (event_ignorelist == NULL) {
+    event_ignorelist = ignorelist_create(/* invert = */ 1);
+    if (event_ignorelist == NULL)
+      return -1;
+  }
+
+  if (!strcasecmp("Event", key) && len) {
+    if (md_events_parse_events(value, len)) {
+      MD_EVENTS_ERROR(
+          "Failed while parsing events, please check your config file");
+      return -1;
+    }
+  }
+  if (!strcasecmp("Array", key) && len) {
+    if (strncmp("/dev/md", value, MD_ARRAY_NAME_PREFIX_LEN)) {
+      MD_EVENTS_ERROR("The array name/regex must start with '/dev/md';"
+                      " Ignoring %s\n",
+                      value);
+      return -1;
+    } else {
+      ignorelist_add(array_ignorelist, value);
+    }
+  }
+  if (!strcasecmp("IgnoreArray", key)) {
+    if (md_events_parse_boolean(value, array_ignorelist)) {
+      MD_EVENTS_ERROR("Error while checking 'IgnoreArray' value, "
+                      "is it boolean? Check the config file.");
+      return -1;
+    }
+  }
+  if (!strcasecmp("IgnoreEvent", key)) {
+    if (md_events_parse_boolean(value, event_ignorelist)) {
+      MD_EVENTS_ERROR("Error while checking 'IgnoreEvent' value, "
+                      "is it boolean? Check the config file.");
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+static void md_events_handle_regex_error(int rc, regex_t *regex,
+                                         const char *func_name) {
+  char buf[MAX_ERROR_MSG] = {};
+
+  regerror(rc, regex, buf, MAX_ERROR_MSG);
+  DEBUG("%s() failed with '%s'\n", func_name, buf);
+}
+
+static int md_events_compile_regex(regex_t *regex, const char *regex_pattern) {
+  int status = regcomp(regex, regex_pattern, REG_EXTENDED | REG_NEWLINE);
+
+  if (status) {
+    md_events_handle_regex_error(status, regex, "regcomp");
+    return -1;
+  }
+  return 0;
+}
+
+static int md_events_dispatch_notification(md_events_event_t *event,
+                                           notification_t *notif) {
+  int offset;
+  size_t len;
+
+  if (!notif || !event) {
+    MD_EVENTS_ERROR("Null pointer\n");
+    return -1;
+  }
+
+  len = strlen(hostname_g);
+  // we need to make sure that we don't overflow the notif->host buffer
+  // keep in mind that strlen(hostname_g) does not include the nul terminator
+  if (len > sizeof(notif->host) - 1)
+    len = sizeof(notif->host) - 1;
+  memcpy(notif->host, hostname_g, len);
+  notif->host[len] = '\0';
+
+  // with this string literal we are safe to copy
+  strncpy(notif->type, "gauge", sizeof(notif->type));
+  offset =
+      snprintf(notif->message, NOTIF_MAX_MSG_LEN, "event name %s, md array %s ",
+               event->event_name, event->md_device);
+  // this one is not present in every event;
+  if (event->component_device[0] != '\0') {
+    snprintf(notif->message + offset, NOTIF_MAX_MSG_LEN - offset,
+             "component device %s\n", event->component_device);
+  }
+  plugin_dispatch_notification(notif);
+
+  return 0;
+}
+
+// helper function to check whether regex match will fit onto buffer
+// check whether the difference of indexes is bigger than max allowed length
+static inline size_t md_events_get_max_len(regmatch_t match,
+                                           size_t max_name_len) {
+  size_t len;
+
+  if (match.rm_eo - match.rm_so > max_name_len - 1)
+    len = max_name_len - 1;
+  else
+    len = match.rm_eo - match.rm_so;
+  return len;
+}
+
+static void md_events_copy_match(char *buf, const char *line,
+                                 regmatch_t match) {
+  size_t bytes_to_copy = md_events_get_max_len(match, DATA_MAX_NAME_LEN);
+
+  memcpy(buf, &line[match.rm_so], bytes_to_copy);
+  buf[bytes_to_copy] = '\0';
+}
+
+static int md_events_match_regex(regex_t *regex, const char *to_match) {
+  regmatch_t matches[MAX_MATCHES] = {};
+  int status, severity;
+  md_events_event_t event = {};
+
+  status = regexec(regex, to_match, MAX_MATCHES, matches, 0);
+  if (status) {
+    md_events_handle_regex_error(status, regex, "regexec");
+    return -1;
+  }
+
+  // each element from matches array contains the indexes (start/end) within
+  // the string that we ran regexp on; use them to retrieve the substrings
+  md_events_copy_match(event.event_name, to_match, matches[EVENT]);
+  md_events_copy_match(event.md_device, to_match, matches[MD_DEVICE]);
+
+  // this one is not present in every event, regex API sets indexes to -1
+  // if the match wasn't found
+  if (matches[COMPONENT_DEVICE].rm_so != -1)
+    md_events_copy_match(event.component_device, to_match,
+                         matches[COMPONENT_DEVICE]);
+
+  if (ignorelist_match(event_ignorelist, event.event_name))
+    return -1;
+
+  if (ignorelist_match(array_ignorelist, event.md_device))
+    return -1;
+
+  severity = md_events_classify_event(event.event_name);
+  if (!severity) {
+    MD_EVENTS_ERROR("Unsupported event %s\n", event.event_name);
+    return -1;
+  }
+
+  md_events_dispatch_notification(&event,
+                                  &(notification_t){.severity = severity,
+                                                    .time = cdtime(),
+                                                    .plugin = MD_EVENTS_PLUGIN,
+                                                    .type_instance = ""});
+  return 0;
+}
+
+static int md_events_read(void) {
+  char syslog_line[MAX_SYSLOG_MESSAGE_LENGTH];
+  while (fgets(syslog_line, sizeof(syslog_line), syslog_file))
+    // don't check the return code here; exiting from read callback with
+    // nonzero status causes the suspension of next read call;
+    md_events_match_regex(&regex, syslog_line);
+
+  return 0;
+}
+
+static int md_events_shutdown(void) {
+  if (syslog_file)
+    fclose(syslog_file);
+
+  regfree(&regex);
+  ignorelist_free(event_ignorelist);
+  ignorelist_free(array_ignorelist);
+
+  plugin_unregister_config(MD_EVENTS_PLUGIN);
+  plugin_unregister_read(MD_EVENTS_PLUGIN);
+  plugin_unregister_shutdown(MD_EVENTS_PLUGIN);
+
+  return 0;
+}
+
+static int md_events_init(void) {
+  syslog_file = fopen(SYSLOG_PATH, "r");
+  if (!syslog_file) {
+    syslog_file = fopen(SYSLOG_MSG_PATH, "r");
+    if (!syslog_file) {
+      MD_EVENTS_ERROR(
+          "/var/log/syslog and /var/log/messages files are not present. Are "
+          "you sure that you have rsyslog utility installed on your system?\n");
+      return -1;
+    }
+  }
+
+  // monitor events only from point of collectd start
+  if (fseek(syslog_file, 0, SEEK_END)) {
+    MD_EVENTS_ERROR("fseek on syslog file failed, error: %s\n",
+                    strerror(errno));
+    fclose(syslog_file);
+    return -1;
+  }
+
+  if (md_events_compile_regex(&regex, regex_pattern)) {
+    fclose(syslog_file);
+    return -1;
+  }
+
+  return 0;
+}
+
+void module_register(void) {
+  plugin_register_init(MD_EVENTS_PLUGIN, md_events_init);
+  plugin_register_config(MD_EVENTS_PLUGIN, md_events_config, config_keys,
+                         config_keys_num);
+  plugin_register_read(MD_EVENTS_PLUGIN, md_events_read);
+  plugin_register_shutdown(MD_EVENTS_PLUGIN, md_events_shutdown);
+}
diff --git a/src/mdevents_test.c b/src/mdevents_test.c
new file mode 100644 (file)
index 0000000..f1e54c1
--- /dev/null
@@ -0,0 +1,224 @@
+/**
+ * collectd - src/intel_md_events_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:
+ *   Krzysztof Kazimierczak <krzysztof.kazimierczak@intel.com>
+ *   Maciej Fijalkowski <maciej.fijalkowski@intel.com>
+ **/
+
+#include "mdevents.c"
+#include "testing.h"
+
+DEF_TEST(classify_event) {
+  int ret;
+  ret = md_events_classify_event("Fail");
+  EXPECT_EQ_INT(NOTIF_FAILURE, ret);
+
+  ret = md_events_classify_event("SparesMissing");
+  EXPECT_EQ_INT(NOTIF_WARNING, ret);
+
+  ret = md_events_classify_event("NewArray");
+  EXPECT_EQ_INT(NOTIF_OKAY, ret);
+
+  ret = md_events_classify_event("UnsupportedEvent");
+  EXPECT_EQ_INT(0, ret);
+
+  return 0;
+}
+
+DEF_TEST(compile_regex) {
+  int ret;
+  regex_t r;
+
+  ret = md_events_compile_regex(&r, "^(test|example).+regex[0-9]$");
+  EXPECT_EQ_INT(ret, 0);
+
+  // Compiling invalid regex causes memory leak:
+  // https://bugzilla.redhat.com/show_bug.cgi?id=1049743
+  // ret = md_events_compile_regex(&r, "^$^(oooo|MmaA nnn,d[[[14-1]");
+  // EXPECT_EQ_INT(ret, -1);
+
+  regfree(&r);
+  return 0;
+}
+
+DEF_TEST(config) {
+  int ret;
+  ret = md_events_config("Event", "DeviceDisappeared");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("Event", "WrongEvent");
+  EXPECT_EQ_INT(ret, -1);
+  ret = md_events_config("Array", "/dev/md0");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("Array", "WrongArrayName");
+  EXPECT_EQ_INT(ret, -1);
+
+  ret = md_events_config("IgnoreArray", "True");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("IgnoreArray", "False");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("IgnoreArray", "Talse");
+  EXPECT_EQ_INT(ret, -1);
+
+  ret = md_events_config("IgnoreEvent", "True");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("IgnoreEvent", "False");
+  EXPECT_EQ_INT(ret, 0);
+  ret = md_events_config("IgnoreEvent", "Frue");
+  EXPECT_EQ_INT(ret, -1);
+
+  return 0;
+}
+
+DEF_TEST(copy_match) {
+  char *test_str = "This is a test string to be used as an input for "
+                   "md_events_copy_match";
+  regmatch_t match = {.rm_so = 10, .rm_eo = 20};
+  md_events_event_t event = {};
+
+  md_events_copy_match(event.event_name, test_str, match);
+  EXPECT_EQ_STR(event.event_name, "test strin");
+
+  // check the out of bounds
+
+  return 0;
+}
+
+DEF_TEST(dispatch_notification) {
+  int ret;
+  char buf[130];
+
+  memset(buf, 'a', 129);
+  buf[129] = '\0';
+
+  ret = md_events_dispatch_notification(NULL, NULL);
+  EXPECT_EQ_INT(ret, -1);
+
+  md_events_event_t e = {.event_name = "Fail",
+                         .md_device = "/dev/md1",
+                         .component_device = "/dev/sda1"};
+
+  ret = md_events_dispatch_notification(&e, NULL);
+  EXPECT_EQ_INT(ret, -1);
+
+  notification_t n = {.severity = NOTIF_FAILURE,
+                      .time = cdtime(),
+                      .plugin = MD_EVENTS_PLUGIN,
+                      .type_instance = ""};
+
+  ret = md_events_dispatch_notification(NULL, &n);
+  EXPECT_EQ_INT(ret, -1);
+
+  ret = md_events_dispatch_notification(&e, &n);
+  EXPECT_EQ_INT(ret, 0);
+
+  return 0;
+}
+
+DEF_TEST(get_max_len) {
+  size_t max_name_len = 4;
+  regmatch_t match = {.rm_eo = 2, .rm_so = 1};
+  int ret;
+
+  ret = md_events_get_max_len(match, max_name_len);
+  EXPECT_EQ_INT(ret, 1);
+
+  match.rm_eo *= 10;
+  ret = md_events_get_max_len(match, max_name_len);
+  EXPECT_EQ_INT(ret, max_name_len - 1);
+
+  return 0;
+}
+
+DEF_TEST(init) {
+  int ret = md_events_init();
+  EXPECT_EQ_INT(ret, 0);
+
+  return 0;
+}
+
+DEF_TEST(match_regex) {
+  regex_t r;
+  int ret;
+  static char regex_pattern[] =
+      "mdadm[\\[0-9]+\\]: ([a-zA-Z]+) event detected on md"
+      " device ([a-z0-9\\/\\.\\-]+)[^\\/\n]*([a-z0-9\\/\\.\\-]+)?";
+  const char matching_string[] =
+      "Jan 17 05:24:27 ubuntu-sadev02 mdadm[1848]: "
+      "DeviceDisappeared event detected on md device /dev/md0";
+  const char unmatching_string[] =
+      "mdm[4016] RebuildStarted event detected on md"
+      " device /dev/md127, component device $/dev/sdb";
+  const char unclass_event[] =
+      "Jan 17 05:24:27 ubuntu-sadev02 mdadm[1848]: "
+      "Unclassified event detected on md device /dev/md0";
+
+  md_events_compile_regex(&r, regex_pattern);
+
+  ret = md_events_match_regex(&r, matching_string);
+  EXPECT_EQ_INT(ret, 0);
+
+  ret = md_events_match_regex(&r, unmatching_string);
+  EXPECT_EQ_INT(ret, -1);
+
+  ret = md_events_match_regex(&r, unclass_event);
+  EXPECT_EQ_INT(ret, -1);
+
+  regfree(&r);
+
+  return 0;
+}
+
+DEF_TEST(parse_events) {
+  struct {
+    const char *event;
+    size_t len;
+    int ret;
+  } events[] = {{"Fail SpareActive RebuildFinished", 32, 0},
+                {"\0", 1, -1},
+                {"", 0, -1},
+                {"MoveSpare", 9, 0},
+                {"MoveSpare UnclassedEvent", 23, -1},
+                {"MvoeSpare Fail", 14, -1}};
+
+  int ret;
+  for (int i = 0; i < STATIC_ARRAY_SIZE(events); i++) {
+    ret = md_events_parse_events(events[i].event, events[i].len);
+    EXPECT_EQ_INT(ret, events[i].ret);
+  }
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(classify_event);
+  // RUN_TEST(compile_regex);
+  RUN_TEST(config);
+  RUN_TEST(copy_match);
+  RUN_TEST(dispatch_notification);
+  RUN_TEST(get_max_len);
+  RUN_TEST(init);
+  RUN_TEST(match_regex);
+  RUN_TEST(parse_events);
+  END_TEST;
+}