From: Ruben Kerkhof Date: Fri, 18 May 2018 16:44:38 +0000 (+0200) Subject: Merge pull request #2701 from elfiesmelfie/feat_virt_notify_lifecycle X-Git-Url: https://git.octo.it/?p=collectd.git;a=commitdiff_plain;h=38e94dcc796e5d00088826ffd5d43ca5a2e590a2;hp=06a86a60a7dabc685bdbd81ce3d36ea5f7e2c2d4 Merge pull request #2701 from elfiesmelfie/feat_virt_notify_lifecycle virt: Fix tracking of VM state changes --- diff --git a/Makefile.am b/Makefile.am index b944b412..b7d5448c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 14e8e983..c1681f1e 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -1608,6 +1608,7 @@ # PluginInstanceFormat name # Instances 1 # ExtraStats "cpu_util disk disk_err domain_state fs_info job_stats_background pcpu perf vcpupin" +# PersistentNotification false # # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index ef79f88f..bbeddf21 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -8923,6 +8923,12 @@ B: I metrics can't be collected if I plugin is enabled. =back +=item B B|B +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 diff --git a/src/daemon/plugin_mock.c b/src/daemon/plugin_mock.c index f47b24be..8f8334ea 100644 --- a/src/daemon/plugin_mock.c +++ b/src/daemon/plugin_mock.c @@ -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 index 00000000..f4c3f343 --- /dev/null +++ b/src/valgrind.suppress @@ -0,0 +1,7 @@ +{ + libnl1_virt_initialization_unpreventable_leak + Memcheck:Leak + ... + obj:*libnl.so.1.* + ... +} \ No newline at end of file diff --git a/src/virt.c b/src/virt.c index e97fd847..37853852 100644 --- a/src/virt.c +++ b/src/virt.c @@ -34,10 +34,19 @@ #include #include #include +#include /* 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) @@ -59,6 +68,16 @@ #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 @@ -90,6 +109,14 @@ #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(¬if_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(¬if_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(¬if_thread); + if (start_event_loop(¬if_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(¬if_thread); + lv_disconnect(); ignorelist_free(il_domains); diff --git a/src/virt_test.c b/src/virt_test.c index 489a367d..458facaf 100644 --- a/src/virt_test.c +++ b/src/virt_test.c @@ -25,183 +25,71 @@ #include "testing.h" #include "virt.c" /* sic */ -#include +static virDomainPtr *domains; +static int nr_domains; -static const char minimal_xml[] = - "" - "" - "" - " " - ""; - -static const char minimal_metadata_xml[] = - "" - "" - "" - " " - " virt-0" - " " - ""; - -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 : */