Merge pull request #2701 from elfiesmelfie/feat_virt_notify_lifecycle
authorRuben Kerkhof <ruben@rubenkerkhof.com>
Fri, 18 May 2018 16:44:38 +0000 (18:44 +0200)
committerGitHub <noreply@github.com>
Fri, 18 May 2018 16:44:38 +0000 (18:44 +0200)
virt: Fix tracking of VM state changes

Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/daemon/plugin_mock.c
src/valgrind.suppress [new file with mode: 0644]
src/virt.c
src/virt_test.c

index b944b41..b7d5448 100644 (file)
@@ -61,6 +61,7 @@ EXTRA_DIST = \
        src/types.db \
        src/types.db.pod \
        src/valgrind.FreeBSD.suppress \
+       src/valgrind.suppress \
        testwrapper.sh \
        version-gen.sh
 
@@ -1759,18 +1760,15 @@ virt_la_CFLAGS = $(AM_CFLAGS) \
 virt_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 virt_la_LIBADD = libignorelist.la $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
 
-# TODO: enable once we support only modern libvirts which depends on libnl-3
-# the libvirt on wheezy is linked in libnl v1, and there is a small leak here,
-# triggered by the library initialization. There are no means to avoid it,
-# and libvirt switched to libnl3 anyway
-#test_plugin_virt_SOURCES = src/virt_test.c
-#test_plugin_virt_CPPFLAGS = $(AM_CPPFLAGS) \
-#      $(BUILD_WITH_LIBVIRT_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
-#test_plugin_virt_LDFLAGS = $(PLUGIN_LDFLAGS)
-#test_plugin_virt_LDADD = libplugin_mock.la \
-#      $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
-#check_PROGRAMS += test_plugin_virt
-#TESTS += test_plugin_virt
+test_plugin_virt_SOURCES = src/virt_test.c
+test_plugin_virt_CPPFLAGS = $(AM_CPPFLAGS) \
+       $(BUILD_WITH_LIBVIRT_CPPFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+test_plugin_virt_LDFLAGS = $(PLUGIN_LDFLAGS) \
+       $(BUILD_WITH_LIBVIRT_LDFLAGS) $(BUILD_WITH_LIBXML2_LDFLAGS)
+test_plugin_virt_LDADD = libplugin_mock.la \
+       $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+check_PROGRAMS += test_plugin_virt
+TESTS += test_plugin_virt
 endif
 
 if BUILD_PLUGIN_VMEM
index 14e8e98..c1681f1 100644 (file)
 #      PluginInstanceFormat name
 #      Instances 1
 #      ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpupin"
+#      PersistentNotification false
 #</Plugin>
 
 #<Plugin vmem>
index ef79f88..bbeddf2 100644 (file)
@@ -8923,6 +8923,12 @@ B<Note>: I<perf> metrics can't be collected if I<intel_rdt> plugin is enabled.
 
 =back
 
+=item B<PersistentNotification> B<true>|B<false>
+Override default configuration to only send notifications when there is a change
+in the lifecycle state of a domain. When set to true notifications will be sent
+for every read cycle. Default is false. Does not affect the stats being
+dispatched.
+
 =back
 
 =head2 Plugin C<vmem>
index f47b24b..8f8334e 100644 (file)
@@ -75,6 +75,65 @@ int plugin_register_data_set(const data_set_t *ds) { return ENOTSUP; }
 
 int plugin_dispatch_values(value_list_t const *vl) { return ENOTSUP; }
 
+int plugin_dispatch_notification(__attribute__((unused))
+                                 const notification_t *notif) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_add_string(__attribute__((unused))
+                                        notification_t *n,
+                                        __attribute__((unused))
+                                        const char *name,
+                                        __attribute__((unused))
+                                        const char *value) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_add_signed_int(__attribute__((unused))
+                                            notification_t *n,
+                                            __attribute__((unused))
+                                            const char *name,
+                                            __attribute__((unused))
+                                            int64_t value) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_add_unsigned_int(__attribute__((unused))
+                                              notification_t *n,
+                                              __attribute__((unused))
+                                              const char *name,
+                                              __attribute__((unused))
+                                              uint64_t value) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_add_double(__attribute__((unused))
+                                        notification_t *n,
+                                        __attribute__((unused))
+                                        const char *name,
+                                        __attribute__((unused)) double value) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_add_boolean(__attribute__((unused))
+                                         notification_t *n,
+                                         __attribute__((unused))
+                                         const char *name,
+                                         __attribute__((unused)) _Bool value) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_copy(__attribute__((unused)) notification_t *dst,
+                                  __attribute__((unused))
+                                  const notification_t *src) {
+  return ENOTSUP;
+}
+
+int plugin_notification_meta_free(__attribute__((unused))
+                                  notification_meta_t *n) {
+  return ENOTSUP;
+}
+
 int plugin_flush(const char *plugin, cdtime_t timeout, const char *identifier) {
   return ENOTSUP;
 }
diff --git a/src/valgrind.suppress b/src/valgrind.suppress
new file mode 100644 (file)
index 0000000..f4c3f34
--- /dev/null
@@ -0,0 +1,7 @@
+{
+   libnl1_virt_initialization_unpreventable_leak
+   Memcheck:Leak
+   ...
+   obj:*libnl.so.1.*
+   ...
+}
\ No newline at end of file
index e97fd84..3785385 100644 (file)
 #include <libxml/tree.h>
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
+#include <stdbool.h>
 
 /* Plugin name */
 #define PLUGIN_NAME "virt"
 
+/* Secure strcat macro assuring null termination. Parameter (n) is the size of
+   buffer (d), allowing this macro to be safe for static and dynamic buffers */
+#define SSTRNCAT(d, s, n)                                                      \
+  do {                                                                         \
+    size_t _l = strlen(d);                                                     \
+    sstrncpy((d) + _l, (s), (n)-_l);                                           \
+  } while (0)
+
 #ifdef LIBVIR_CHECK_VERSION
 
 #if LIBVIR_CHECK_VERSION(0, 9, 2)
 #define HAVE_DOM_REASON_RUNNING_WAKEUP 1
 #endif
 
+/*
+  virConnectListAllDomains() appeared in 0.10.2
+  Note that LIBVIR_CHECK_VERSION appeared a year later, so
+  in some systems which actually have virConnectListAllDomains()
+  we can't detect this.
+ */
+#if LIBVIR_CHECK_VERSION(0, 10, 2)
+#define HAVE_LIST_ALL_DOMAINS 1
+#endif
+
 #if LIBVIR_CHECK_VERSION(1, 0, 1)
 #define HAVE_DOM_REASON_PAUSED_SNAPSHOT 1
 #endif
 
 #endif /* LIBVIR_CHECK_VERSION */
 
+/* structure used for aggregating notification-thread data*/
+typedef struct virt_notif_thread_s {
+  pthread_t event_loop_tid;
+  int domain_event_cb_id;
+  pthread_mutex_t active_mutex; /* protects 'is_active' member access*/
+  bool is_active;
+} virt_notif_thread_t;
+
 static const char *config_keys[] = {"Connection",
 
                                     "RefreshInterval",
@@ -108,8 +135,15 @@ static const char *config_keys[] = {"Connection",
 
                                     "Instances",
                                     "ExtraStats",
+                                    "PersistentNotification",
                                     NULL};
 
+/* PersistentNotification is false by default */
+static bool persistent_notification = false;
+
+/* Thread used for handling libvirt notifications events */
+static virt_notif_thread_t notif_thread;
+
 const char *domain_states[] = {
         [VIR_DOMAIN_NOSTATE] = "no state",
         [VIR_DOMAIN_RUNNING] = "the domain is running",
@@ -124,7 +158,202 @@ const char *domain_states[] = {
 #endif
 };
 
+static int map_domain_event_to_state(int event) {
+  int ret;
+  switch (event) {
+  case VIR_DOMAIN_EVENT_STARTED:
+    ret = VIR_DOMAIN_RUNNING;
+    break;
+  case VIR_DOMAIN_EVENT_SUSPENDED:
+    ret = VIR_DOMAIN_PAUSED;
+    break;
+  case VIR_DOMAIN_EVENT_RESUMED:
+    ret = VIR_DOMAIN_RUNNING;
+    break;
+  case VIR_DOMAIN_EVENT_STOPPED:
+    ret = VIR_DOMAIN_SHUTOFF;
+    break;
+  case VIR_DOMAIN_EVENT_SHUTDOWN:
+    ret = VIR_DOMAIN_SHUTDOWN;
+    break;
+#ifdef HAVE_DOM_STATE_PMSUSPENDED
+  case VIR_DOMAIN_EVENT_PMSUSPENDED:
+    ret = VIR_DOMAIN_PMSUSPENDED;
+    break;
+#endif
+#ifdef HAVE_DOM_REASON_CRASHED
+  case VIR_DOMAIN_EVENT_CRASHED:
+    ret = VIR_DOMAIN_CRASHED;
+    break;
+#endif
+  default:
+    ret = VIR_DOMAIN_NOSTATE;
+  }
+  return ret;
+}
+
 #ifdef HAVE_DOM_REASON
+static int map_domain_event_detail_to_reason(int event, int detail) {
+  int ret;
+  switch (event) {
+  case VIR_DOMAIN_EVENT_STARTED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_STARTED_BOOTED: /* Normal startup from boot */
+      ret = VIR_DOMAIN_RUNNING_BOOTED;
+      break;
+    case VIR_DOMAIN_EVENT_STARTED_MIGRATED: /* Incoming migration from another
+                                               host */
+      ret = VIR_DOMAIN_RUNNING_MIGRATED;
+      break;
+    case VIR_DOMAIN_EVENT_STARTED_RESTORED: /* Restored from a state file */
+      ret = VIR_DOMAIN_RUNNING_RESTORED;
+      break;
+    case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT: /* Restored from snapshot */
+      ret = VIR_DOMAIN_RUNNING_FROM_SNAPSHOT;
+      break;
+#ifdef HAVE_DOM_REASON_RUNNING_WAKEUP
+    case VIR_DOMAIN_EVENT_STARTED_WAKEUP: /* Started due to wakeup event */
+      ret = VIR_DOMAIN_RUNNING_WAKEUP;
+      break;
+#endif
+    default:
+      ret = VIR_DOMAIN_RUNNING_UNKNOWN;
+    }
+    break;
+  case VIR_DOMAIN_EVENT_SUSPENDED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED: /* Normal suspend due to admin
+                                               pause */
+      ret = VIR_DOMAIN_PAUSED_USER;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED: /* Suspended for offline
+                                                 migration */
+      ret = VIR_DOMAIN_PAUSED_MIGRATION;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR: /* Suspended due to a disk I/O
+                                                error */
+      ret = VIR_DOMAIN_PAUSED_IOERROR;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG: /* Suspended due to a watchdog
+                                                 firing */
+      ret = VIR_DOMAIN_PAUSED_WATCHDOG;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED: /* Restored from paused state
+                                                 file */
+      ret = VIR_DOMAIN_PAUSED_UNKNOWN;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT: /* Restored from paused
+                                                      snapshot */
+      ret = VIR_DOMAIN_PAUSED_FROM_SNAPSHOT;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR: /* Suspended after failure during
+                                                  libvirt API call */
+      ret = VIR_DOMAIN_PAUSED_UNKNOWN;
+      break;
+#ifdef HAVE_DOM_REASON_POSTCOPY
+    case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY: /* Suspended for post-copy
+                                                 migration */
+      ret = VIR_DOMAIN_PAUSED_POSTCOPY;
+      break;
+    case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED: /* Suspended after failed
+                                                        post-copy */
+      ret = VIR_DOMAIN_PAUSED_POSTCOPY_FAILED;
+      break;
+#endif
+    default:
+      ret = VIR_DOMAIN_PAUSED_UNKNOWN;
+    }
+    break;
+  case VIR_DOMAIN_EVENT_RESUMED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED: /* Normal resume due to admin
+                                               unpause */
+      ret = VIR_DOMAIN_RUNNING_UNPAUSED;
+      break;
+    case VIR_DOMAIN_EVENT_RESUMED_MIGRATED: /* Resumed for completion of
+                                               migration */
+      ret = VIR_DOMAIN_RUNNING_MIGRATED;
+      break;
+    case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: /* Resumed from snapshot */
+      ret = VIR_DOMAIN_RUNNING_FROM_SNAPSHOT;
+      break;
+#ifdef HAVE_DOM_REASON_POSTCOPY
+    case VIR_DOMAIN_EVENT_RESUMED_POSTCOPY: /* Resumed, but migration is still
+                                               running in post-copy mode */
+      ret = VIR_DOMAIN_RUNNING_POSTCOPY;
+      break;
+#endif
+    default:
+      ret = VIR_DOMAIN_RUNNING_UNKNOWN;
+    }
+    break;
+  case VIR_DOMAIN_EVENT_STOPPED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN: /* Normal shutdown */
+      ret = VIR_DOMAIN_SHUTOFF_SHUTDOWN;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_DESTROYED: /* Forced poweroff from host */
+      ret = VIR_DOMAIN_SHUTOFF_DESTROYED;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_CRASHED: /* Guest crashed */
+      ret = VIR_DOMAIN_SHUTOFF_CRASHED;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_MIGRATED: /* Migrated off to another host */
+      ret = VIR_DOMAIN_SHUTOFF_MIGRATED;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_SAVED: /* Saved to a state file */
+      ret = VIR_DOMAIN_SHUTOFF_SAVED;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_FAILED: /* Host emulator/mgmt failed */
+      ret = VIR_DOMAIN_SHUTOFF_FAILED;
+      break;
+    case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT: /* Offline snapshot loaded */
+      ret = VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT;
+      break;
+    default:
+      ret = VIR_DOMAIN_SHUTOFF_UNKNOWN;
+    }
+    break;
+  case VIR_DOMAIN_EVENT_SHUTDOWN:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: /* Guest finished shutdown
+                                                sequence */
+      ret = VIR_DOMAIN_SHUTDOWN_USER;
+      break;
+    default:
+      ret = VIR_DOMAIN_SHUTDOWN_UNKNOWN;
+    }
+    break;
+#ifdef HAVE_DOM_STATE_PMSUSPENDED
+  case VIR_DOMAIN_EVENT_PMSUSPENDED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY: /* Guest was PM suspended to
+                                                 memory */
+      ret = VIR_DOMAIN_PMSUSPENDED_UNKNOWN;
+      break;
+    case VIR_DOMAIN_EVENT_PMSUSPENDED_DISK: /* Guest was PM suspended to disk */
+      ret = VIR_DOMAIN_PMSUSPENDED_DISK_UNKNOWN;
+      break;
+    default:
+      ret = VIR_DOMAIN_PMSUSPENDED_UNKNOWN;
+    }
+    break;
+#endif
+  case VIR_DOMAIN_EVENT_CRASHED:
+    switch (detail) {
+    case VIR_DOMAIN_EVENT_CRASHED_PANICKED: /* Guest was panicked */
+      ret = VIR_DOMAIN_CRASHED_PANICKED;
+      break;
+    default:
+      ret = VIR_DOMAIN_CRASHED_UNKNOWN;
+    }
+    break;
+  default:
+    ret = VIR_DOMAIN_NOSTATE_UNKNOWN;
+  }
+  return ret;
+}
+
 #define DOMAIN_STATE_REASON_MAX_SIZE 20
 const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
         [VIR_DOMAIN_NOSTATE][VIR_DOMAIN_NOSTATE_UNKNOWN] =
@@ -158,7 +387,6 @@ const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
         [VIR_DOMAIN_RUNNING][VIR_DOMAIN_RUNNING_POSTCOPY] =
             "running in post-copy migration mode",
 #endif
-
         [VIR_DOMAIN_BLOCKED][VIR_DOMAIN_BLOCKED_UNKNOWN] =
             "the reason is unknown",
 
@@ -198,7 +426,6 @@ const char *domain_reasons[][DOMAIN_STATE_REASON_MAX_SIZE] = {
         [VIR_DOMAIN_PAUSED][VIR_DOMAIN_PAUSED_POSTCOPY_FAILED] =
             "paused after failed post-copy",
 #endif
-
         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_UNKNOWN] =
             "the reason is unknown",
         [VIR_DOMAIN_SHUTDOWN][VIR_DOMAIN_SHUTDOWN_USER] =
@@ -278,6 +505,7 @@ struct interface_device {
 typedef struct domain_s {
   virDomainPtr ptr;
   virDomainInfo info;
+  bool active;
 } domain_t;
 
 struct lv_read_state {
@@ -293,7 +521,8 @@ struct lv_read_state {
 };
 
 static void free_domains(struct lv_read_state *state);
-static int add_domain(struct lv_read_state *state, virDomainPtr dom);
+static int add_domain(struct lv_read_state *state, virDomainPtr dom,
+                      bool active);
 
 static void free_block_devices(struct lv_read_state *state);
 static int add_block_device(struct lv_read_state *state, virDomainPtr dom,
@@ -533,7 +762,6 @@ static int lv_domain_info(virDomainPtr dom, struct lv_info *info) {
 }
 
 static void init_value_list(value_list_t *vl, virDomainPtr dom) {
-  int n;
   const char *name;
   char uuid[VIR_UUID_STRING_BUFLEN];
 
@@ -546,44 +774,34 @@ static void init_value_list(value_list_t *vl, virDomainPtr dom) {
     if (hostname_format[i] == hf_none)
       continue;
 
-    n = DATA_MAX_NAME_LEN - strlen(vl->host) - 2;
-
-    if (i > 0 && n >= 1) {
-      strncat(vl->host, ":", 1);
-      n--;
-    }
+    if (i > 0)
+      SSTRNCAT(vl->host, ":", sizeof(vl->host));
 
     switch (hostname_format[i]) {
     case hf_none:
       break;
     case hf_hostname:
-      strncat(vl->host, hostname_g, n);
+      SSTRNCAT(vl->host, hostname_g, sizeof(vl->host));
       break;
     case hf_name:
       name = virDomainGetName(dom);
       if (name)
-        strncat(vl->host, name, n);
+        SSTRNCAT(vl->host, name, sizeof(vl->host));
       break;
     case hf_uuid:
       if (virDomainGetUUIDString(dom, uuid) == 0)
-        strncat(vl->host, uuid, n);
+        SSTRNCAT(vl->host, uuid, sizeof(vl->host));
       break;
     }
   }
 
-  vl->host[sizeof(vl->host) - 1] = '\0';
-
   /* Construct the plugin instance field according to PluginInstanceFormat. */
   for (int i = 0; i < PLGINST_MAX_FIELDS; ++i) {
     if (plugin_instance_format[i] == plginst_none)
       continue;
 
-    n = sizeof(vl->plugin_instance) - strlen(vl->plugin_instance) - 2;
-
-    if (i > 0 && n >= 1) {
-      strncat(vl->plugin_instance, ":", 1);
-      n--;
-    }
+    if (i > 0)
+      SSTRNCAT(vl->plugin_instance, ":", sizeof(vl->plugin_instance));
 
     switch (plugin_instance_format[i]) {
     case plginst_none:
@@ -591,17 +809,15 @@ static void init_value_list(value_list_t *vl, virDomainPtr dom) {
     case plginst_name:
       name = virDomainGetName(dom);
       if (name)
-        strncat(vl->plugin_instance, name, n);
+        SSTRNCAT(vl->plugin_instance, name, sizeof(vl->plugin_instance));
       break;
     case plginst_uuid:
       if (virDomainGetUUIDString(dom, uuid) == 0)
-        strncat(vl->plugin_instance, uuid, n);
+        SSTRNCAT(vl->plugin_instance, uuid, sizeof(vl->plugin_instance));
       break;
     }
   }
 
-  vl->plugin_instance[sizeof(vl->plugin_instance) - 1] = '\0';
-
 } /* void init_value_list */
 
 static int init_notif(notification_t *notif, const virDomainPtr domain,
@@ -799,8 +1015,7 @@ static unsigned int parse_ex_stats_flags(char **exstats, int numexstats) {
   return ex_stats_flags;
 }
 
-static void domain_state_submit(virDomainPtr dom, int state, int reason) {
-
+static void domain_state_submit_notif(virDomainPtr dom, int state, int reason) {
   if ((state < 0) || (state >= STATIC_ARRAY_SIZE(domain_states))) {
     ERROR(PLUGIN_NAME ": Array index out of bounds: state=%d", state);
     return;
@@ -908,7 +1123,7 @@ static int lv_config(const char *key, const char *value) {
     return 0;
   }
   if (strcasecmp(key, "BlockDeviceFormatBasename") == 0) {
-    blockdevice_format_basename = IS_TRUE(value);
+    blockdevice_format_basename = IS_TRUE(value) ? true : false;
     return 0;
   }
   if (strcasecmp(key, "InterfaceDevice") == 0) {
@@ -1070,6 +1285,11 @@ static int lv_config(const char *key, const char *value) {
     }
   }
 
+  if (strcasecmp(key, "PersistentNotification") == 0) {
+    persistent_notification = IS_TRUE(value);
+    return 0;
+  }
+
   /* Unrecognised option. */
   return -1;
 }
@@ -1223,6 +1443,15 @@ static int get_vcpu_stats(virDomainPtr domain, unsigned short nr_virt_cpu) {
 }
 
 #ifdef HAVE_DOM_REASON
+
+static void domain_state_submit(virDomainPtr dom, int state, int reason) {
+  value_t values[] = {
+      {.gauge = (gauge_t)state}, {.gauge = (gauge_t)reason},
+  };
+
+  submit(dom, "domain_state", NULL, values, STATIC_ARRAY_SIZE(values));
+}
+
 static int get_domain_state(virDomainPtr domain) {
   int domain_state = 0;
   int domain_reason = 0;
@@ -1235,8 +1464,28 @@ static int get_domain_state(virDomainPtr domain) {
   }
 
   domain_state_submit(domain, domain_state, domain_reason);
+
   return status;
 }
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+static int get_domain_state_notify(virDomainPtr domain) {
+  int domain_state = 0;
+  int domain_reason = 0;
+
+  int status = virDomainGetState(domain, &domain_state, &domain_reason, 0);
+  if (status != 0) {
+    ERROR(PLUGIN_NAME " plugin: virDomainGetState failed with status %i.",
+          status);
+    return status;
+  }
+
+  if (persistent_notification)
+    domain_state_submit_notif(domain, domain_state, domain_reason);
+
+  return status;
+}
+#endif /* HAVE_LIST_ALL_DOMAINS */
 #endif /* HAVE_DOM_REASON */
 
 static int get_memory_stats(virDomainPtr domain) {
@@ -1488,10 +1737,6 @@ static int get_domain_metrics(domain_t *domain) {
      * We need to get it from virDomainGetState.
      */
     GET_STATS(get_domain_state, "domain reason", domain->ptr);
-#else
-    /* virDomainGetState is not available. Submit 0, which corresponds to
-     * unknown reason. */
-    domain_state_submit(domain->ptr, info.di.state, 0);
 #endif
   }
 
@@ -1530,6 +1775,7 @@ static int get_domain_metrics(domain_t *domain) {
 
   /* Update cached virDomainInfo. It has to be done after cpu_submit */
   memcpy(&domain->info, &info.di, sizeof(domain->info));
+
   return 0;
 }
 
@@ -1578,6 +1824,190 @@ static int get_if_dev_stats(struct interface_device *if_dev) {
   return 0;
 }
 
+static int domain_lifecycle_event_cb(__attribute__((unused)) virConnectPtr conn,
+                                     virDomainPtr dom, int event, int detail,
+                                     __attribute__((unused)) void *opaque) {
+  int domain_state = map_domain_event_to_state(event);
+  int domain_reason = 0; /* 0 means UNKNOWN reason for any state */
+#ifdef HAVE_DOM_REASON
+  domain_reason = map_domain_event_detail_to_reason(event, detail);
+#endif
+  domain_state_submit_notif(dom, domain_state, domain_reason);
+
+  return 0;
+}
+
+static int register_event_impl(void) {
+  if (virEventRegisterDefaultImpl() < 0) {
+    virErrorPtr err = virGetLastError();
+    ERROR(PLUGIN_NAME
+          " plugin: error while event implementation registering: %s",
+          err && err->message ? err->message : "Unknown error");
+    return -1;
+  }
+
+  return 0;
+}
+
+static void virt_notif_thread_set_active(virt_notif_thread_t *thread_data,
+                                         const bool active) {
+  assert(thread_data != NULL);
+  pthread_mutex_lock(&thread_data->active_mutex);
+  thread_data->is_active = active;
+  pthread_mutex_unlock(&thread_data->active_mutex);
+}
+
+static bool virt_notif_thread_is_active(virt_notif_thread_t *thread_data) {
+  bool active = false;
+
+  assert(thread_data != NULL);
+  pthread_mutex_lock(&thread_data->active_mutex);
+  active = thread_data->is_active;
+  pthread_mutex_unlock(&thread_data->active_mutex);
+
+  return active;
+}
+
+/* worker function running default event implementation */
+static void *event_loop_worker(void *arg) {
+  virt_notif_thread_t *thread_data = (virt_notif_thread_t *)arg;
+
+  while (virt_notif_thread_is_active(thread_data)) {
+    if (virEventRunDefaultImpl() < 0) {
+      virErrorPtr err = virGetLastError();
+      ERROR(PLUGIN_NAME " plugin: failed to run event loop: %s\n",
+            err && err->message ? err->message : "Unknown error");
+    }
+  }
+
+  return NULL;
+}
+
+static int virt_notif_thread_init(virt_notif_thread_t *thread_data) {
+  int ret;
+
+  assert(thread_data != NULL);
+  ret = pthread_mutex_init(&thread_data->active_mutex, NULL);
+  if (ret != 0) {
+    ERROR(PLUGIN_NAME ": Failed to initialize mutex, err %u", ret);
+    return ret;
+  }
+
+  /**
+   * '0' and positive integers are meaningful ID's, therefore setting
+   * domain_event_cb_id to '-1'
+   */
+  thread_data->domain_event_cb_id = -1;
+  thread_data->is_active = 0;
+
+  return 0;
+}
+
+/* register domain event callback and start event loop thread */
+static int start_event_loop(virt_notif_thread_t *thread_data) {
+  assert(thread_data != NULL);
+  thread_data->domain_event_cb_id = virConnectDomainEventRegisterAny(
+      conn, NULL, VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+      VIR_DOMAIN_EVENT_CALLBACK(domain_lifecycle_event_cb), NULL, NULL);
+  if (thread_data->domain_event_cb_id == -1) {
+    ERROR(PLUGIN_NAME " plugin: error while callback registering");
+    return -1;
+  }
+
+  virt_notif_thread_set_active(thread_data, 1);
+  if (pthread_create(&thread_data->event_loop_tid, NULL, event_loop_worker,
+                     thread_data)) {
+    ERROR(PLUGIN_NAME " plugin: failed event loop thread creation");
+    virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* stop event loop thread and deregister callback */
+static void stop_event_loop(virt_notif_thread_t *thread_data) {
+  /* stopping loop and de-registering event handler*/
+  virt_notif_thread_set_active(thread_data, 0);
+  if (conn != NULL && thread_data->domain_event_cb_id != -1)
+    virConnectDomainEventDeregisterAny(conn, thread_data->domain_event_cb_id);
+
+  if (pthread_join(notif_thread.event_loop_tid, NULL) != 0)
+    ERROR(PLUGIN_NAME " plugin: stopping notification thread failed");
+}
+
+static int persistent_domains_state_notification(void) {
+  int status = 0;
+  int n;
+#ifdef HAVE_LIST_ALL_DOMAINS
+  virDomainPtr *domains = NULL;
+  n = virConnectListAllDomains(conn, &domains,
+                               VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
+  if (n < 0) {
+    VIRT_ERROR(conn, "reading list of persistent domains");
+    status = -1;
+  } else {
+    DEBUG(PLUGIN_NAME " plugin: getting state of %i persistent domains", n);
+    /* Fetch each persistent domain's state and notify it */
+    int n_notified = n;
+    for (int i = 0; i < n; ++i) {
+      status = get_domain_state_notify(domains[i]);
+      if (status != 0) {
+        n_notified--;
+        ERROR(PLUGIN_NAME " plugin: could not notify state of domain %s",
+              virDomainGetName(domains[i]));
+      }
+      virDomainFree(domains[i]);
+    }
+
+    sfree(domains);
+    DEBUG(PLUGIN_NAME " plugin: notified state of %i persistent domains",
+          n_notified);
+  }
+#else
+  n = virConnectNumOfDomains(conn);
+  if (n > 0) {
+    int *domids;
+    /* Get list of domains. */
+    domids = calloc(n, sizeof(*domids));
+    if (domids == NULL) {
+      ERROR(PLUGIN_NAME " plugin: calloc failed.");
+      return -1;
+    }
+    n = virConnectListDomains(conn, domids, n);
+    if (n < 0) {
+      VIRT_ERROR(conn, "reading list of domains");
+      sfree(domids);
+      return -1;
+    }
+    /* Fetch info of each active domain and notify it */
+    for (int i = 0; i < n; ++i) {
+      virDomainInfo info;
+      virDomainPtr dom = NULL;
+      dom = virDomainLookupByID(conn, domids[i]);
+      if (dom == NULL) {
+        VIRT_ERROR(conn, "virDomainLookupByID");
+        /* Could be that the domain went away -- ignore it anyway. */
+        continue;
+      }
+      status = virDomainGetInfo(dom, &info);
+      if (status == 0)
+        /* virDomainGetState is not available. Submit 0, which corresponds to
+         * unknown reason. */
+        domain_state_submit_notif(dom, info.state, 0);
+      else
+        ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
+              status);
+
+      virDomainFree(dom);
+    }
+    sfree(domids);
+  }
+#endif
+
+  return status;
+}
+
 static int lv_read(user_data_t *ud) {
   time_t t;
   struct lv_read_instance *inst = NULL;
@@ -1591,9 +2021,19 @@ static int lv_read(user_data_t *ud) {
   inst = ud->data;
   state = &inst->read_state;
 
+  bool reconnect = conn == NULL ? true : false;
+  /* event implementation must be registered before connection is opened */
   if (inst->id == 0) {
+    if (!persistent_notification && reconnect)
+      if (register_event_impl() != 0)
+        return -1;
+
     if (lv_connect() < 0)
       return -1;
+
+    if (!persistent_notification && reconnect && conn != NULL)
+      if (start_event_loop(&notif_thread) != 0)
+        return -1;
   }
 
   time(&t);
@@ -1602,32 +2042,53 @@ static int lv_read(user_data_t *ud) {
   if ((last_refresh == (time_t)0) ||
       ((interval > 0) && ((last_refresh + interval) <= t))) {
     if (refresh_lists(inst) != 0) {
-      if (inst->id == 0)
+      if (inst->id == 0) {
+        if (!persistent_notification)
+          stop_event_loop(&notif_thread);
         lv_disconnect();
+      }
       return -1;
     }
     last_refresh = t;
   }
 
-#if 0
-    for (int i = 0; i < nr_domains; ++i)
-        fprintf (stderr, "domain %s\n", virDomainGetName (state->domains[i].ptr));
-    for (int i = 0; i < nr_block_devices; ++i)
-        fprintf  (stderr, "block device %d %s:%s\n",
-                  i, virDomainGetName (block_devices[i].dom),
-                  block_devices[i].path);
-    for (int i = 0; i < nr_interface_devices; ++i)
-        fprintf (stderr, "interface device %d %s:%s\n",
-                 i, virDomainGetName (interface_devices[i].dom),
-                 interface_devices[i].path);
+  /* persistent domains state notifications are handled by instance 0 */
+  if (inst->id == 0 && persistent_notification) {
+    int status = persistent_domains_state_notification();
+    if (status != 0)
+      DEBUG(PLUGIN_NAME " plugin: persistent_domains_state_notifications "
+                        "returned with status %i",
+            status);
+  }
+
+#if COLLECT_DEBUG
+  for (int i = 0; i < state->nr_domains; ++i)
+    DEBUG(PLUGIN_NAME " plugin: domain %s",
+          virDomainGetName(state->domains[i].ptr));
+  for (int i = 0; i < state->nr_block_devices; ++i)
+    DEBUG(PLUGIN_NAME " plugin: block device %d %s:%s", i,
+          virDomainGetName(state->block_devices[i].dom),
+          state->block_devices[i].path);
+  for (int i = 0; i < state->nr_interface_devices; ++i)
+    DEBUG(PLUGIN_NAME " plugin: interface device %d %s:%s", i,
+          virDomainGetName(state->interface_devices[i].dom),
+          state->interface_devices[i].path);
 #endif
 
   /* Get domains' metrics */
   for (int i = 0; i < state->nr_domains; ++i) {
-    int status = get_domain_metrics(&state->domains[i]);
+    domain_t *dom = &state->domains[i];
+    int status = 0;
+    if (dom->active)
+      status = get_domain_metrics(dom);
+#ifdef HAVE_DOM_REASON
+    else
+      status = get_domain_state(dom->ptr);
+#endif
+
     if (status != 0)
       ERROR(PLUGIN_NAME " failed to get metrics for domain=%s",
-            virDomainGetName(state->domains[i].ptr));
+            virDomainGetName(dom->ptr));
   }
 
   /* Get block device stats for each domain. */
@@ -1667,6 +2128,7 @@ static int lv_init_instance(size_t i, plugin_read_cb callback) {
   ud->free_func = NULL;
 
   INFO(PLUGIN_NAME " plugin: reader %s initialized", inst->tag);
+
   return plugin_register_complex_read(NULL, inst->tag, callback, 0, ud);
 }
 
@@ -1681,6 +2143,7 @@ static void lv_fini_instance(size_t i) {
   struct lv_read_state *state = &(inst->read_state);
 
   lv_clean_read_state(state);
+
   INFO(PLUGIN_NAME " plugin: reader %s finalized", inst->tag);
 }
 
@@ -1688,13 +2151,27 @@ static int lv_init(void) {
   if (virInitialize() != 0)
     return -1;
 
+  /* event implementation must be registered before connection is opened */
+  if (!persistent_notification)
+    if (register_event_impl() != 0)
+      return -1;
+
   if (lv_connect() != 0)
     return -1;
 
+  DEBUG(PLUGIN_NAME " plugin: starting event loop");
+
+  if (!persistent_notification) {
+    virt_notif_thread_init(&notif_thread);
+    if (start_event_loop(&notif_thread) != 0)
+      return -1;
+  }
+
   DEBUG(PLUGIN_NAME " plugin: starting %i instances", nr_instances);
 
   for (int i = 0; i < nr_instances; ++i)
-    lv_init_instance(i, lv_read);
+    if (lv_init_instance(i, lv_read) != 0)
+      return -1;
 
   return 0;
 }
@@ -1793,224 +2270,247 @@ static int lv_instance_include_domain(struct lv_read_instance *inst,
   return 0;
 }
 
-/*
-  virConnectListAllDomains() appeared in 0.10.2
-  Note that LIBVIR_CHECK_VERSION appeared a year later, so
-  in some systems which actually have virConnectListAllDomains()
-  we can't detect this.
- */
-#ifdef LIBVIR_CHECK_VERSION
-#if LIBVIR_CHECK_VERSION(0, 10, 2)
-#define HAVE_LIST_ALL_DOMAINS 1
-#endif
-#endif
-
 static int refresh_lists(struct lv_read_instance *inst) {
   struct lv_read_state *state = &inst->read_state;
   int n;
 
+#ifndef HAVE_LIST_ALL_DOMAINS
   n = virConnectNumOfDomains(conn);
   if (n < 0) {
     VIRT_ERROR(conn, "reading number of domains");
     return -1;
   }
+#endif
 
   lv_clean_read_state(state);
 
-  if (n > 0) {
+#ifndef HAVE_LIST_ALL_DOMAINS
+  if (n == 0)
+    goto end;
+#endif
+
 #ifdef HAVE_LIST_ALL_DOMAINS
-    virDomainPtr *domains;
-    n = virConnectListAllDomains(conn, &domains,
-                                 VIR_CONNECT_LIST_DOMAINS_ACTIVE);
+  virDomainPtr *domains, *domains_inactive;
+  int m = virConnectListAllDomains(conn, &domains_inactive,
+                                   VIR_CONNECT_LIST_DOMAINS_INACTIVE);
+  n = virConnectListAllDomains(conn, &domains, VIR_CONNECT_LIST_DOMAINS_ACTIVE);
 #else
-    int *domids;
+  int *domids;
 
-    /* Get list of domains. */
-    domids = malloc(sizeof(*domids) * n);
-    if (domids == NULL) {
-      ERROR(PLUGIN_NAME " plugin: malloc failed.");
-      return -1;
-    }
+  /* Get list of domains. */
+  domids = calloc(n, sizeof(*domids));
+  if (domids == NULL) {
+    ERROR(PLUGIN_NAME " plugin: calloc failed.");
+    return -1;
+  }
 
-    n = virConnectListDomains(conn, domids, n);
+  n = virConnectListDomains(conn, domids, n);
 #endif
 
-    if (n < 0) {
-      VIRT_ERROR(conn, "reading list of domains");
+  if (n < 0) {
+    VIRT_ERROR(conn, "reading list of domains");
 #ifndef HAVE_LIST_ALL_DOMAINS
-      sfree(domids);
+    sfree(domids);
+#else
+    for (int i = 0; i < m; ++i)
+      virDomainFree(domains_inactive[i]);
+    sfree(domains_inactive);
 #endif
-      return -1;
+    return -1;
+  }
+
+#ifdef HAVE_LIST_ALL_DOMAINS
+  for (int i = 0; i < m; ++i)
+    if (add_domain(state, domains_inactive[i], 0) < 0) {
+      ERROR(PLUGIN_NAME " plugin: malloc failed.");
+      virDomainFree(domains_inactive[i]);
+      domains_inactive[i] = NULL;
+      continue;
     }
+#endif
 
-    /* Fetch each domain and add it to the list, unless ignore. */
-    for (int i = 0; i < n; ++i) {
-      const char *name;
-      char *xml = NULL;
-      xmlDocPtr xml_doc = NULL;
-      xmlXPathContextPtr xpath_ctx = NULL;
-      xmlXPathObjectPtr xpath_obj = NULL;
-      char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
-      virDomainInfo info;
-      int status;
+  /* Fetch each domain and add it to the list, unless ignore. */
+  for (int i = 0; i < n; ++i) {
+    const char *name;
+    char *xml = NULL;
+    xmlDocPtr xml_doc = NULL;
+    xmlXPathContextPtr xpath_ctx = NULL;
+    xmlXPathObjectPtr xpath_obj = NULL;
+    char tag[PARTITION_TAG_MAX_LEN] = {'\0'};
+    virDomainInfo info;
+    int status;
 
 #ifdef HAVE_LIST_ALL_DOMAINS
-      virDomainPtr dom = domains[i];
+    virDomainPtr dom = domains[i];
 #else
-      virDomainPtr dom = NULL;
-      dom = virDomainLookupByID(conn, domids[i]);
-      if (dom == NULL) {
-        VIRT_ERROR(conn, "virDomainLookupByID");
-        /* Could be that the domain went away -- ignore it anyway. */
-        continue;
-      }
+    virDomainPtr dom = NULL;
+    dom = virDomainLookupByID(conn, domids[i]);
+    if (dom == NULL) {
+      VIRT_ERROR(conn, "virDomainLookupByID");
+      /* Could be that the domain went away -- ignore it anyway. */
+      continue;
+    }
 #endif
 
-      name = virDomainGetName(dom);
-      if (name == NULL) {
-        VIRT_ERROR(conn, "virDomainGetName");
-        goto cont;
-      }
+    if (add_domain(state, dom, 1) < 0) {
+      /*
+       * When domain is already tracked, then there is
+       * no problem with memory handling (will be freed
+       * with the rest of domains cached data)
+       * But in case of error like this (error occurred
+       * before adding domain to track) we have to take
+       * care it ourselves and call virDomainFree
+       */
+      ERROR(PLUGIN_NAME " plugin: malloc failed.");
+      virDomainFree(dom);
+      goto cont;
+    }
 
-      status = virDomainGetInfo(dom, &info);
-      if (status != 0) {
-        ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
-              status);
-        continue;
-      }
+    name = virDomainGetName(dom);
+    if (name == NULL) {
+      VIRT_ERROR(conn, "virDomainGetName");
+      goto cont;
+    }
 
-      if (info.state != VIR_DOMAIN_RUNNING) {
-        DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", name);
-        continue;
-      }
+    status = virDomainGetInfo(dom, &info);
+    if (status != 0) {
+      ERROR(PLUGIN_NAME " plugin: virDomainGetInfo failed with status %i.",
+            status);
+      continue;
+    }
 
-      if (il_domains && ignorelist_match(il_domains, name) != 0)
-        goto cont;
+    if (info.state != VIR_DOMAIN_RUNNING) {
+      DEBUG(PLUGIN_NAME " plugin: skipping inactive domain %s", name);
+      continue;
+    }
 
-      /* Get a list of devices for this domain. */
-      xml = virDomainGetXMLDesc(dom, 0);
-      if (!xml) {
-        VIRT_ERROR(conn, "virDomainGetXMLDesc");
-        goto cont;
-      }
+    if (il_domains && ignorelist_match(il_domains, name) != 0)
+      goto cont;
 
-      /* Yuck, XML.  Parse out the devices. */
-      xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
-      if (xml_doc == NULL) {
-        VIRT_ERROR(conn, "xmlReadDoc");
-        goto cont;
-      }
+    /* Get a list of devices for this domain. */
+    xml = virDomainGetXMLDesc(dom, 0);
+    if (!xml) {
+      VIRT_ERROR(conn, "virDomainGetXMLDesc");
+      goto cont;
+    }
 
-      xpath_ctx = xmlXPathNewContext(xml_doc);
+    /* Yuck, XML.  Parse out the devices. */
+    xml_doc = xmlReadDoc((xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
+    if (xml_doc == NULL) {
+      VIRT_ERROR(conn, "xmlReadDoc");
+      goto cont;
+    }
 
-      if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
-        ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
-        goto cont;
-      }
+    xpath_ctx = xmlXPathNewContext(xml_doc);
 
-      if (!lv_instance_include_domain(inst, name, tag))
-        goto cont;
+    if (lv_domain_get_tag(xpath_ctx, name, tag) < 0) {
+      ERROR(PLUGIN_NAME " plugin: lv_domain_get_tag failed.");
+      goto cont;
+    }
 
-      if (add_domain(state, dom) < 0) {
-        ERROR(PLUGIN_NAME " plugin: malloc failed.");
-        goto cont;
-      }
+    if (!lv_instance_include_domain(inst, name, tag))
+      goto cont;
 
-      /* Block devices. */
-      const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
-      if (blockdevice_format == source)
-        bd_xmlpath = "/domain/devices/disk/source[@dev]";
-      xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
+    /* Block devices. */
+    const char *bd_xmlpath = "/domain/devices/disk/target[@dev]";
+    if (blockdevice_format == source)
+      bd_xmlpath = "/domain/devices/disk/source[@dev]";
+    xpath_obj = xmlXPathEval((const xmlChar *)bd_xmlpath, xpath_ctx);
 
-      if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
-          xpath_obj->nodesetval == NULL)
-        goto cont;
+    if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+        xpath_obj->nodesetval == NULL)
+      goto cont;
 
-      for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
-        xmlNodePtr node;
-        char *path = NULL;
+    for (int j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
+      xmlNodePtr node;
+      char *path = NULL;
 
-        node = xpath_obj->nodesetval->nodeTab[j];
-        if (!node)
-          continue;
-        path = (char *)xmlGetProp(node, (xmlChar *)"dev");
-        if (!path)
-          continue;
+      node = xpath_obj->nodesetval->nodeTab[j];
+      if (!node)
+        continue;
+      path = (char *)xmlGetProp(node, (xmlChar *)"dev");
+      if (!path)
+        continue;
 
-        if (il_block_devices &&
-            ignore_device_match(il_block_devices, name, path) != 0)
-          goto cont2;
+      if (il_block_devices &&
+          ignore_device_match(il_block_devices, name, path) != 0)
+        goto cont2;
 
-        add_block_device(state, dom, path);
-      cont2:
-        if (path)
-          xmlFree(path);
-      }
-      xmlXPathFreeObject(xpath_obj);
+      add_block_device(state, dom, path);
+    cont2:
+      if (path)
+        xmlFree(path);
+    }
+    xmlXPathFreeObject(xpath_obj);
+
+    /* Network interfaces. */
+    xpath_obj = xmlXPathEval(
+        (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
+    if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+        xpath_obj->nodesetval == NULL)
+      goto cont;
 
-      /* Network interfaces. */
-      xpath_obj = xmlXPathEval(
-          (xmlChar *)"/domain/devices/interface[target[@dev]]", xpath_ctx);
-      if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
-          xpath_obj->nodesetval == NULL)
-        goto cont;
+    xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
 
-      xmlNodeSetPtr xml_interfaces = xpath_obj->nodesetval;
+    for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
+      char *path = NULL;
+      char *address = NULL;
+      xmlNodePtr xml_interface;
 
-      for (int j = 0; j < xml_interfaces->nodeNr; ++j) {
-        char *path = NULL;
-        char *address = NULL;
-        xmlNodePtr xml_interface;
+      xml_interface = xml_interfaces->nodeTab[j];
+      if (!xml_interface)
+        continue;
 
-        xml_interface = xml_interfaces->nodeTab[j];
-        if (!xml_interface)
+      for (xmlNodePtr child = xml_interface->children; child;
+           child = child->next) {
+        if (child->type != XML_ELEMENT_NODE)
           continue;
 
-        for (xmlNodePtr child = xml_interface->children; child;
-             child = child->next) {
-          if (child->type != XML_ELEMENT_NODE)
+        if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
+          path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
+          if (!path)
+            continue;
+        } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
+          address = (char *)xmlGetProp(child, (const xmlChar *)"address");
+          if (!address)
             continue;
-
-          if (xmlStrEqual(child->name, (const xmlChar *)"target")) {
-            path = (char *)xmlGetProp(child, (const xmlChar *)"dev");
-            if (!path)
-              continue;
-          } else if (xmlStrEqual(child->name, (const xmlChar *)"mac")) {
-            address = (char *)xmlGetProp(child, (const xmlChar *)"address");
-            if (!address)
-              continue;
-          }
         }
-
-        if (il_interface_devices &&
-            (ignore_device_match(il_interface_devices, name, path) != 0 ||
-             ignore_device_match(il_interface_devices, name, address) != 0))
-          goto cont3;
-
-        add_interface_device(state, dom, path, address, j + 1);
-      cont3:
-        if (path)
-          xmlFree(path);
-        if (address)
-          xmlFree(address);
       }
 
-    cont:
-      if (xpath_obj)
-        xmlXPathFreeObject(xpath_obj);
-      if (xpath_ctx)
-        xmlXPathFreeContext(xpath_ctx);
-      if (xml_doc)
-        xmlFreeDoc(xml_doc);
-      sfree(xml);
+      if (il_interface_devices &&
+          (ignore_device_match(il_interface_devices, name, path) != 0 ||
+           ignore_device_match(il_interface_devices, name, address) != 0))
+        goto cont3;
+
+      add_interface_device(state, dom, path, address, j + 1);
+    cont3:
+      if (path)
+        xmlFree(path);
+      if (address)
+        xmlFree(address);
     }
 
+  cont:
+    if (xpath_obj)
+      xmlXPathFreeObject(xpath_obj);
+    if (xpath_ctx)
+      xmlXPathFreeContext(xpath_ctx);
+    if (xml_doc)
+      xmlFreeDoc(xml_doc);
+    sfree(xml);
+  }
+
 #ifdef HAVE_LIST_ALL_DOMAINS
-    sfree(domains);
+  /* NOTE: domains_active and domains_inactive data will be cleared during
+     refresh of all domains (inside lv_clean_read_state function) so we need
+     to free here only allocated arrays */
+  sfree(domains);
+  sfree(domains_inactive);
 #else
-    sfree(domids);
+  sfree(domids);
+
+end:
 #endif
-  }
 
   DEBUG(PLUGIN_NAME " plugin#%s: refreshing"
                     " domains=%i block_devices=%i iface_devices=%i",
@@ -2030,7 +2530,8 @@ static void free_domains(struct lv_read_state *state) {
   state->nr_domains = 0;
 }
 
-static int add_domain(struct lv_read_state *state, virDomainPtr dom) {
+static int add_domain(struct lv_read_state *state, virDomainPtr dom,
+                      bool active) {
   domain_t *new_ptr;
   int new_size = sizeof(state->domains[0]) * (state->nr_domains + 1);
 
@@ -2044,6 +2545,7 @@ static int add_domain(struct lv_read_state *state, virDomainPtr dom) {
 
   state->domains = new_ptr;
   state->domains[state->nr_domains].ptr = dom;
+  state->domains[state->nr_domains].active = active;
   memset(&state->domains[state->nr_domains].info, 0,
          sizeof(state->domains[state->nr_domains].info));
 
@@ -2166,6 +2668,11 @@ static int lv_shutdown(void) {
     lv_fini_instance(i);
   }
 
+  DEBUG(PLUGIN_NAME " plugin: stopping event loop");
+
+  if (!persistent_notification)
+    stop_event_loop(&notif_thread);
+
   lv_disconnect();
 
   ignorelist_free(il_domains);
index 489a367..458faca 100644 (file)
 #include "testing.h"
 #include "virt.c" /* sic */
 
-#include <unistd.h>
+static virDomainPtr *domains;
+static int nr_domains;
 
-static const char minimal_xml[] =
-    ""
-    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-    "<domain type=\"kvm\" xmlns:ovirt=\"http://ovirt.org/vm/tune/1.0\">"
-    "  <metadata/>"
-    "</domain>";
-
-static const char minimal_metadata_xml[] =
-    ""
-    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-    "<domain type=\"kvm\" xmlns:ovirt=\"http://ovirt.org/vm/tune/1.0\">"
-    "  <metadata>"
-    "    <ovirtmap:tag "
-    "xmlns:ovirtmap=\"http://ovirt.org/ovirtmap/tag/1.0\">virt-0</ovirtmap:tag>"
-    "  </metadata>"
-    "</domain>";
-
-struct xml_state {
-  xmlDocPtr xml_doc;
-  xmlXPathContextPtr xpath_ctx;
-  xmlXPathObjectPtr xpath_obj;
-  char tag[PARTITION_TAG_MAX_LEN];
-};
-
-static int init_state(struct xml_state *st, const char *xml) {
-  memset(st, 0, sizeof(*st));
-
-  st->xml_doc = xmlReadDoc((const xmlChar *)xml, NULL, NULL, XML_PARSE_NONET);
-  if (st->xml_doc == NULL) {
-    return -1;
-  }
-  st->xpath_ctx = xmlXPathNewContext(st->xml_doc);
-  if (st->xpath_ctx == NULL) {
+static int setup(void) {
+  if (virInitialize() != 0) {
+    printf("ERROR: virInitialize() != 0\n");
     return -1;
   }
-  return 0;
-}
 
-static void fini_state(struct xml_state *st) {
-  if (st->xpath_ctx) {
-    xmlXPathFreeContext(st->xpath_ctx);
-    st->xpath_ctx = NULL;
-  }
-  if (st->xml_doc) {
-    xmlFreeDoc(st->xml_doc);
-    st->xml_doc = NULL;
+  conn = virConnectOpen("test:///default");
+  if (conn == NULL) {
+    printf("ERROR: virConnectOpen == NULL\n");
+    return -1;
   }
-}
-
-#define TAG "virt-0"
-
-DEF_TEST(lv_domain_get_tag_no_metadata_xml) {
-  int err;
-  struct xml_state st;
-  err = init_state(&st, minimal_xml);
-  EXPECT_EQ_INT(0, err);
-
-  err = lv_domain_get_tag(st.xpath_ctx, "test", st.tag);
-
-  EXPECT_EQ_INT(0, err);
-  EXPECT_EQ_STR("", st.tag);
-
-  fini_state(&st);
-  return 0;
-}
-
-DEF_TEST(lv_domain_get_tag_valid_xml) {
-  int err;
-  struct xml_state st;
-  err = init_state(&st, minimal_metadata_xml);
-  EXPECT_EQ_INT(0, err);
-
-  err = lv_domain_get_tag(st.xpath_ctx, "test", st.tag);
-
-  EXPECT_EQ_INT(0, err);
-  EXPECT_EQ_STR(TAG, st.tag);
-
-  return 0;
-}
-
-DEF_TEST(lv_default_instance_include_domain_without_tag) {
-  struct lv_read_instance *inst = NULL;
-  int ret;
-
-  ret = lv_init_instance(0, lv_read);
-  inst = &(lv_read_user_data[0].inst);
-  EXPECT_EQ_STR("virt-0", inst->tag);
-
-  ret = lv_instance_include_domain(inst, "testing", "");
-  EXPECT_EQ_INT(1, ret);
-
-  lv_fini_instance(0);
-  return 0;
-}
-
-DEF_TEST(lv_regular_instance_skip_domain_without_tag) {
-  struct lv_read_instance *inst = NULL;
-  int ret;
-
-  ret = lv_init_instance(1, lv_read);
-  inst = &(lv_read_user_data[1].inst);
-  EXPECT_EQ_STR("virt-1", inst->tag);
 
-  ret = lv_instance_include_domain(inst, "testing", "");
-  EXPECT_EQ_INT(0, ret);
-
-  lv_fini_instance(0);
   return 0;
 }
 
-DEF_TEST(lv_include_domain_matching_tags) {
-  struct lv_read_instance *inst = NULL;
-  int ret;
-
-  ret = lv_init_instance(0, lv_read);
-  inst = &(lv_read_user_data[0].inst);
-  EXPECT_EQ_STR("virt-0", inst->tag);
-
-  ret = lv_instance_include_domain(inst, "testing", "virt-0");
-  EXPECT_EQ_INT(1, ret);
-
-  ret = lv_init_instance(1, lv_read);
-  inst = &(lv_read_user_data[1].inst);
-  EXPECT_EQ_STR("virt-1", inst->tag);
-
-  ret = lv_instance_include_domain(inst, "testing", "virt-1");
-  EXPECT_EQ_INT(1, ret);
+static int teardown(void) {
+  if (domains) {
+    for (int i = 0; i < nr_domains; ++i)
+      virDomainFree(domains[i]);
+    sfree(domains);
+  }
+  nr_domains = 0;
+  if (conn != NULL)
+    virConnectClose(conn);
 
-  lv_fini_instance(0);
-  lv_fini_instance(1);
   return 0;
 }
 
-DEF_TEST(lv_default_instance_include_domain_with_unknown_tag) {
-  struct lv_read_instance *inst = NULL;
-  int ret;
-
-  ret = lv_init_instance(0, lv_read);
-  inst = &(lv_read_user_data[0].inst);
-  EXPECT_EQ_STR("virt-0", inst->tag);
-
-  ret = lv_instance_include_domain(inst, "testing", "unknownFormat-tag");
-  EXPECT_EQ_INT(1, ret);
+#ifdef HAVE_LIST_ALL_DOMAINS
+DEF_TEST(get_domain_state_notify) {
+  if (setup() == 0) {
+    nr_domains = virConnectListAllDomains(conn, &domains,
+                                          VIR_CONNECT_LIST_DOMAINS_PERSISTENT);
+    if (nr_domains <= 0) {
+      printf("ERROR: virConnectListAllDomains: nr_domains <= 0\n");
+      return -1;
+    }
+
+    int ret = get_domain_state_notify(domains[0]);
+    EXPECT_EQ_INT(0, ret);
+  }
+  teardown();
 
-  lv_fini_instance(0);
   return 0;
 }
+#endif
 
-DEF_TEST(lv_regular_instance_skip_domain_with_unknown_tag) {
-  struct lv_read_instance *inst = NULL;
-  int ret;
-
-  ret = lv_init_instance(1, lv_read);
-  inst = &(lv_read_user_data[1].inst);
-  EXPECT_EQ_STR("virt-1", inst->tag);
-
-  ret = lv_instance_include_domain(inst, "testing", "unknownFormat-tag");
-  EXPECT_EQ_INT(0, ret);
+DEF_TEST(persistent_domains_state_notification) {
+  if (setup() == 0) {
+    int ret = persistent_domains_state_notification();
+    EXPECT_EQ_INT(0, ret);
+  }
+  teardown();
 
-  lv_fini_instance(0);
   return 0;
 }
-#undef TAG
 
 int main(void) {
-  RUN_TEST(lv_domain_get_tag_no_metadata_xml);
-  RUN_TEST(lv_domain_get_tag_valid_xml);
-
-  RUN_TEST(lv_default_instance_include_domain_without_tag);
-  RUN_TEST(lv_regular_instance_skip_domain_without_tag);
-  RUN_TEST(lv_include_domain_matching_tags);
-  RUN_TEST(lv_default_instance_include_domain_with_unknown_tag);
-  RUN_TEST(lv_regular_instance_skip_domain_with_unknown_tag);
+#ifdef HAVE_LIST_ALL_DOMAINS
+  RUN_TEST(get_domain_state_notify);
+#endif
+  RUN_TEST(persistent_domains_state_notification);
 
   END_TEST;
 }
-
-/* vim: set sw=2 sts=2 et : */