Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-15-SP1:GA
salt.10902
add-engine-relaying-libvirt-events.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File add-engine-relaying-libvirt-events.patch of Package salt.10902
From 8192d69c83f9470ac74ac66cef9f5d629d619b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= <cbosdonnat@suse.com> Date: Fri, 9 Mar 2018 15:46:12 +0100 Subject: [PATCH] Add engine relaying libvirt events Libvirt API offers clients to register callbacks for various events. libvirt_events engine will listen on a libvirt URI (local or remote) for events and send them to the salt event bus. Special thanks to @isbm for the code cleanup help --- salt/engines/libvirt_events.py | 702 ++++++++++++++++++++++ tests/unit/engines/test_libvirt_events.py | 159 +++++ 2 files changed, 861 insertions(+) create mode 100644 salt/engines/libvirt_events.py create mode 100644 tests/unit/engines/test_libvirt_events.py diff --git a/salt/engines/libvirt_events.py b/salt/engines/libvirt_events.py new file mode 100644 index 0000000000..a1c9d09067 --- /dev/null +++ b/salt/engines/libvirt_events.py @@ -0,0 +1,702 @@ +# -*- coding: utf-8 -*- + +''' +An engine that listens for libvirt events and resends them to the salt event bus. + +The minimal configuration is the following and will listen to all events on the +local hypervisor and send them with a tag starting with ``salt/engines/libvirt_events``: + +.. code-block:: yaml + + engines: + - libvirt_events + +Note that the automatically-picked libvirt connection will depend on the value +of ``uri_default`` in ``/etc/libvirt/libvirt.conf``. To force using another +connection like the local LXC libvirt driver, set the ``uri`` property as in the +following example configuration. + +.. code-block:: yaml + + engines: + - libvirt_events: + uri: lxc:/// + tag_prefix: libvirt + filters: + - domain/lifecycle + - domain/reboot + - pool + +Filters is a list of event types to relay to the event bus. Items in this list +can be either one of the main types (``domain``, ``network``, ``pool``, +``nodedev``, ``secret``), ``all`` or a more precise filter. These can be done +with values like <main_type>/<subtype>. The possible values are in the +CALLBACK_DEFS constant. If the filters list contains ``all``, all +events will be relayed. + +Be aware that the list of events increases with libvirt versions, for example +network events have been added in libvirt 1.2.1. + +Running the engine on non-root +------------------------------ + +Running this engine as non-root requires a special attention, which is surely +the case for the master running as user `salt`. The engine is likely to fail +to connect to libvirt with an error like this one: + + [ERROR ] authentication unavailable: no polkit agent available to authenticate action 'org.libvirt.unix.monitor' + + +To fix this, the user running the engine, for example the salt-master, needs +to have the rights to connect to libvirt in the machine polkit config. +A polkit rule like the following one will allow `salt` user to connect to libvirt: + +.. code-block:: javascript + + polkit.addRule(function(action, subject) { + if (action.id.indexOf("org.libvirt") == 0 && + subject.user == "salt") { + return polkit.Result.YES; + } + }); + +:depends: libvirt 1.0.0+ python binding + +.. versionadded:: Fluorine +''' + +from __future__ import absolute_import, unicode_literals, print_function +import logging + +# Import salt libs +import salt.utils.event + +# pylint: disable=no-name-in-module,import-error +from salt.ext.six.moves.urllib.parse import urlparse +# pylint: enable=no-name-in-module,import-error + +log = logging.getLogger(__name__) + + +try: + import libvirt +except ImportError: + libvirt = None # pylint: disable=invalid-name + + +def __virtual__(): + ''' + Only load if libvirt python binding is present + ''' + if libvirt is None: + msg = 'libvirt module not found' + elif libvirt.getVersion() < 1000000: + msg = 'libvirt >= 1.0.0 required' + else: + msg = '' + return not bool(msg), msg + + +REGISTER_FUNCTIONS = { + 'domain': 'domainEventRegisterAny', + 'network': 'networkEventRegisterAny', + 'pool': 'storagePoolEventRegisterAny', + 'nodedev': 'nodeDeviceEventRegisterAny', + 'secret': 'secretEventRegisterAny' +} + +# Handle either BLOCK_JOB or BLOCK_JOB_2, but prefer the latter +if hasattr(libvirt, 'VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2'): + BLOCK_JOB_ID = 'VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2' +else: + BLOCK_JOB_ID = 'VIR_DOMAIN_EVENT_ID_BLOCK_JOB' + +CALLBACK_DEFS = { + 'domain': (('lifecycle', None), + ('reboot', None), + ('rtc_change', None), + ('watchdog', None), + ('graphics', None), + ('io_error', 'VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON'), + ('control_error', None), + ('disk_change', None), + ('tray_change', None), + ('pmwakeup', None), + ('pmsuspend', None), + ('balloon_change', None), + ('pmsuspend_disk', None), + ('device_removed', None), + ('block_job', BLOCK_JOB_ID), + ('tunable', None), + ('agent_lifecycle', None), + ('device_added', None), + ('migration_iteration', None), + ('job_completed', None), + ('device_removal_failed', None), + ('metadata_change', None), + ('block_threshold', None)), + 'network': (('lifecycle', None),), + 'pool': (('lifecycle', None), + ('refresh', None)), + 'nodedev': (('lifecycle', None), + ('update', None)), + 'secret': (('lifecycle', None), + ('value_changed', None)) +} + + +def _compute_subprefix(attr): + ''' + Get the part before the first '_' or the end of attr including + the potential '_' + ''' + return ''.join((attr.split('_')[0], '_' if len(attr.split('_')) > 1 else '')) + + +def _get_libvirt_enum_string(prefix, value): + ''' + Convert the libvirt enum integer value into a human readable string. + + :param prefix: start of the libvirt attribute to look for. + :param value: integer to convert to string + ''' + attributes = [attr[len(prefix):] for attr in libvirt.__dict__ if attr.startswith(prefix)] + + # Filter out the values starting with a common base as they match another enum + prefixes = [_compute_subprefix(p) for p in attributes] + counts = {p: prefixes.count(p) for p in prefixes} + sub_prefixes = [p for p, count in counts.items() if count > 1] + filtered = [attr for attr in attributes if _compute_subprefix(attr) not in sub_prefixes] + + for candidate in filtered: + if value == getattr(libvirt, ''.join((prefix, candidate))): + name = candidate.lower().replace('_', ' ') + return name + return 'unknown' + + +def _get_domain_event_detail(event, detail): + ''' + Convert event and detail numeric values into a tuple of human readable strings + ''' + event_name = _get_libvirt_enum_string('VIR_DOMAIN_EVENT_', event) + if event_name == 'unknown': + return event_name, 'unknown' + + prefix = 'VIR_DOMAIN_EVENT_{0}_'.format(event_name.upper()) + detail_name = _get_libvirt_enum_string(prefix, detail) + + return event_name, detail_name + + +def _salt_send_event(opaque, conn, data): + ''' + Convenience function adding common data to the event and sending it + on the salt event bus. + + :param opaque: the opaque data that is passed to the callback. + This is a dict with 'prefix', 'object' and 'event' keys. + :param conn: libvirt connection + :param data: additional event data dict to send + ''' + tag_prefix = opaque['prefix'] + object_type = opaque['object'] + event_type = opaque['event'] + + # Prepare the connection URI to fit in the tag + # qemu+ssh://user@host:1234/system -> qemu+ssh/user@host:1234/system + uri = urlparse(conn.getURI()) + uri_tag = [uri.scheme] + if uri.netloc: + uri_tag.append(uri.netloc) + path = uri.path.strip('/') + if path: + uri_tag.append(path) + uri_str = "/".join(uri_tag) + + # Append some common data + all_data = { + 'uri': conn.getURI() + } + all_data.update(data) + + tag = '/'.join((tag_prefix, uri_str, object_type, event_type)) + + # Actually send the event in salt + if __opts__.get('__role') == 'master': + salt.utils.event.get_master_event( + __opts__, + __opts__['sock_dir']).fire_event(all_data, tag) + else: + __salt__['event.send'](tag, all_data) + + +def _salt_send_domain_event(opaque, conn, domain, event, event_data): + ''' + Helper function send a salt event for a libvirt domain. + + :param opaque: the opaque data that is passed to the callback. + This is a dict with 'prefix', 'object' and 'event' keys. + :param conn: libvirt connection + :param domain: name of the domain related to the event + :param event: name of the event + :param event_data: additional event data dict to send + ''' + data = { + 'domain': { + 'name': domain.name(), + 'id': domain.ID(), + 'uuid': domain.UUIDString() + }, + 'event': event + } + data.update(event_data) + _salt_send_event(opaque, conn, data) + + +def _domain_event_lifecycle_cb(conn, domain, event, detail, opaque): + ''' + Domain lifecycle events handler + ''' + event_str, detail_str = _get_domain_event_detail(event, detail) + + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'event': event_str, + 'detail': detail_str + }) + + +def _domain_event_reboot_cb(conn, domain, opaque): + ''' + Domain reboot events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], {}) + + +def _domain_event_rtc_change_cb(conn, domain, utcoffset, opaque): + ''' + Domain RTC change events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'utcoffset': utcoffset + }) + + +def _domain_event_watchdog_cb(conn, domain, action, opaque): + ''' + Domain watchdog events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'action': _get_libvirt_enum_string('VIR_DOMAIN_EVENT_WATCHDOG_', action) + }) + + +def _domain_event_io_error_cb(conn, domain, srcpath, devalias, action, reason, opaque): + ''' + Domain I/O Error events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'srcPath': srcpath, + 'dev': devalias, + 'action': _get_libvirt_enum_string('VIR_DOMAIN_EVENT_IO_ERROR_', action), + 'reason': reason + }) + + +def _domain_event_graphics_cb(conn, domain, phase, local, remote, auth, subject, opaque): + ''' + Domain graphics events handler + ''' + prefix = 'VIR_DOMAIN_EVENT_GRAPHICS_' + + def get_address(addr): + ''' + transform address structure into event data piece + ''' + data = {'family': _get_libvirt_enum_string('{0}_ADDRESS_'.format(prefix), addr['family']), + 'node': addr['node'], + 'service': addr['service']} + return addr + + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'phase': _get_libvirt_enum_string(prefix, phase), + 'local': get_address(local), + 'remote': get_address(remote), + 'authScheme': auth, + 'subject': [{'type': item[0], 'name': item[1]} for item in subject] + }) + + +def _domain_event_control_error_cb(conn, domain, opaque): + ''' + Domain control error events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], {}) + + +def _domain_event_disk_change_cb(conn, domain, old_src, new_src, dev, reason, opaque): + ''' + Domain disk change events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'oldSrcPath': old_src, + 'newSrcPath': new_src, + 'dev': dev, + 'reason': _get_libvirt_enum_string('VIR_DOMAIN_EVENT_DISK_', reason) + }) + + +def _domain_event_tray_change_cb(conn, domain, dev, reason, opaque): + ''' + Domain tray change events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'dev': dev, + 'reason': _get_libvirt_enum_string('VIR_DOMAIN_EVENT_TRAY_CHANGE_', reason) + }) + + +def _domain_event_pmwakeup_cb(conn, domain, reason, opaque): + ''' + Domain wakeup events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'reason': 'unknown' # currently unused + }) + + +def _domain_event_pmsuspend_cb(conn, domain, reason, opaque): + ''' + Domain suspend events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'reason': 'unknown' # currently unused + }) + + +def _domain_event_balloon_change_cb(conn, domain, actual, opaque): + ''' + Domain balloon change events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'actual': actual + }) + + +def _domain_event_pmsuspend_disk_cb(conn, domain, reason, opaque): + ''' + Domain disk suspend events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'reason': 'unknown' # currently unused + }) + + +def _domain_event_block_job_cb(conn, domain, disk, job_type, status, opaque): + ''' + Domain block job events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'disk': disk, + 'type': _get_libvirt_enum_string('VIR_DOMAIN_BLOCK_JOB_TYPE_', job_type), + 'status': _get_libvirt_enum_string('VIR_DOMAIN_BLOCK_JOB_', status) + }) + + +def _domain_event_device_removed_cb(conn, domain, dev, opaque): + ''' + Domain device removal events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'dev': dev + }) + + +def _domain_event_tunable_cb(conn, domain, params, opaque): + ''' + Domain tunable events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'params': params + }) + + +# pylint: disable=invalid-name +def _domain_event_agent_lifecycle_cb(conn, domain, state, reason, opaque): + ''' + Domain agent lifecycle events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'state': _get_libvirt_enum_string('VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_', state), + 'reason': _get_libvirt_enum_string('VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_', reason) + }) + + +def _domain_event_device_added_cb(conn, domain, dev, opaque): + ''' + Domain device addition events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'dev': dev + }) + + +# pylint: disable=invalid-name +def _domain_event_migration_iteration_cb(conn, domain, iteration, opaque): + ''' + Domain migration iteration events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'iteration': iteration + }) + + +def _domain_event_job_completed_cb(conn, domain, params, opaque): + ''' + Domain job completion events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'params': params + }) + + +def _domain_event_device_removal_failed_cb(conn, domain, dev, opaque): + ''' + Domain device removal failure events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'dev': dev + }) + + +def _domain_event_metadata_change_cb(conn, domain, mtype, nsuri, opaque): + ''' + Domain metadata change events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'type': _get_libvirt_enum_string('VIR_DOMAIN_METADATA_', mtype), + 'nsuri': nsuri + }) + + +def _domain_event_block_threshold_cb(conn, domain, dev, path, threshold, excess, opaque): + ''' + Domain block threshold events handler + ''' + _salt_send_domain_event(opaque, conn, domain, opaque['event'], { + 'dev': dev, + 'path': path, + 'threshold': threshold, + 'excess': excess + }) + + +def _network_event_lifecycle_cb(conn, net, event, detail, opaque): + ''' + Network lifecycle events handler + ''' + + _salt_send_event(opaque, conn, { + 'network': { + 'name': net.name(), + 'uuid': net.UUIDString() + }, + 'event': _get_libvirt_enum_string('VIR_NETWORK_EVENT_', event), + 'detail': 'unknown' # currently unused + }) + + +def _pool_event_lifecycle_cb(conn, pool, event, detail, opaque): + ''' + Storage pool lifecycle events handler + ''' + _salt_send_event(opaque, conn, { + 'pool': { + 'name': pool.name(), + 'uuid': pool.UUIDString() + }, + 'event': _get_libvirt_enum_string('VIR_STORAGE_POOL_EVENT_', event), + 'detail': 'unknown' # currently unused + }) + + +def _pool_event_refresh_cb(conn, pool, opaque): + ''' + Storage pool refresh events handler + ''' + _salt_send_event(opaque, conn, { + 'pool': { + 'name': pool.name(), + 'uuid': pool.UUIDString() + }, + 'event': opaque['event'] + }) + + +def _nodedev_event_lifecycle_cb(conn, dev, event, detail, opaque): + ''' + Node device lifecycle events handler + ''' + _salt_send_event(opaque, conn, { + 'nodedev': { + 'name': dev.name() + }, + 'event': _get_libvirt_enum_string('VIR_NODE_DEVICE_EVENT_', event), + 'detail': 'unknown' # currently unused + }) + + +def _nodedev_event_update_cb(conn, dev, opaque): + ''' + Node device update events handler + ''' + _salt_send_event(opaque, conn, { + 'nodedev': { + 'name': dev.name() + }, + 'event': opaque['event'] + }) + + +def _secret_event_lifecycle_cb(conn, secret, event, detail, opaque): + ''' + Secret lifecycle events handler + ''' + _salt_send_event(opaque, conn, { + 'secret': { + 'uuid': secret.UUIDString() + }, + 'event': _get_libvirt_enum_string('VIR_SECRET_EVENT_', event), + 'detail': 'unknown' # currently unused + }) + + +def _secret_event_value_changed_cb(conn, secret, opaque): + ''' + Secret value change events handler + ''' + _salt_send_event(opaque, conn, { + 'secret': { + 'uuid': secret.UUIDString() + }, + 'event': opaque['event'] + }) + + +def _cleanup(cnx): + ''' + Close the libvirt connection + + :param cnx: libvirt connection + ''' + log.debug('Closing libvirt connection: %s', cnx.getURI()) + cnx.close() + + +def _callbacks_cleanup(cnx, callback_ids): + ''' + Unregister all the registered callbacks + + :param cnx: libvirt connection + :param callback_ids: dictionary mapping a libvirt object type to an ID list + of callbacks to deregister + ''' + for obj, ids in callback_ids.items(): + register_name = REGISTER_FUNCTIONS[obj] + deregister_name = register_name.replace('Reg', 'Dereg') + deregister = getattr(cnx, deregister_name) + for callback_id in ids: + deregister(callback_id) + + +def _register_callback(cnx, tag_prefix, obj, event, real_id): + ''' + Helper function registering a callback + + :param cnx: libvirt connection + :param tag_prefix: salt event tag prefix to use + :param obj: the libvirt object name for the event. Needs to + be one of the REGISTER_FUNCTIONS keys. + :param event: the event type name. + :param real_id: the libvirt name of an alternative event id to use or None + + :rtype integer value needed to deregister the callback + ''' + libvirt_name = real_id + if real_id is None: + libvirt_name = 'VIR_{0}_EVENT_ID_{1}'.format(obj, event).upper() + + if not hasattr(libvirt, libvirt_name): + log.warning('Skipping "%s/%s" events: libvirt too old', obj, event) + return None + + libvirt_id = getattr(libvirt, libvirt_name) + callback_name = "_{0}_event_{1}_cb".format(obj, event) + callback = globals().get(callback_name, None) + if callback is None: + log.error('Missing function %s in engine', callback_name) + return None + + register = getattr(cnx, REGISTER_FUNCTIONS[obj]) + return register(None, libvirt_id, callback, + {'prefix': tag_prefix, + 'object': obj, + 'event': event}) + + +def _append_callback_id(ids, obj, callback_id): + ''' + Helper function adding a callback ID to the IDs dict. + The callback ids dict maps an object to event callback ids. + + :param ids: dict of callback IDs to update + :param obj: one of the keys of REGISTER_FUNCTIONS + :param callback_id: the result of _register_callback + ''' + if obj not in ids: + ids[obj] = [] + ids[obj].append(callback_id) + + +def start(uri=None, + tag_prefix='salt/engines/libvirt_events', + filters=None): + ''' + Listen to libvirt events and forward them to salt. + + :param uri: libvirt URI to listen on. + Defaults to None to pick the first available local hypervisor + :param tag_prefix: the begining of the salt event tag to use. + Defaults to 'salt/engines/libvirt_events' + :param filters: the list of event of listen on. Defaults to 'all' + ''' + if filters is None: + filters = ['all'] + try: + libvirt.virEventRegisterDefaultImpl() + + cnx = libvirt.openReadOnly(uri) + log.debug('Opened libvirt uri: %s', cnx.getURI()) + + callback_ids = {} + all_filters = "all" in filters + + for obj, event_defs in CALLBACK_DEFS.items(): + for event, real_id in event_defs: + event_filter = "/".join((obj, event)) + if event_filter not in filters and obj not in filters and not all_filters: + continue + registered_id = _register_callback(cnx, tag_prefix, + obj, event, real_id) + if registered_id: + _append_callback_id(callback_ids, obj, registered_id) + + exit_loop = False + while not exit_loop: + exit_loop = libvirt.virEventRunDefaultImpl() < 0 + + except Exception as err: # pylint: disable=broad-except + log.exception(err) + finally: + _callbacks_cleanup(cnx, callback_ids) + _cleanup(cnx) diff --git a/tests/unit/engines/test_libvirt_events.py b/tests/unit/engines/test_libvirt_events.py new file mode 100644 index 0000000000..6608aaf648 --- /dev/null +++ b/tests/unit/engines/test_libvirt_events.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +''' +unit tests for the libvirt_events engine +''' +# Import Python libs +from __future__ import absolute_import, print_function, unicode_literals + +# Import Salt Testing Libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.unit import skipIf, TestCase +from tests.support.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch) + +# Import Salt Libs +import salt.engines.libvirt_events as libvirt_events + + +# pylint: disable=protected-access,attribute-defined-outside-init,invalid-name,unused-argument,no-self-use + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class EngineLibvirtEventTestCase(TestCase, LoaderModuleMockMixin): + ''' + Test cases for salt.engine.libvirt_events + ''' + + def setup_loader_modules(self): + patcher = patch('salt.engines.libvirt_events.libvirt') + self.mock_libvirt = patcher.start() + self.mock_libvirt.getVersion.return_value = 2000000 + self.mock_libvirt.virEventRunDefaultImpl.return_value = -1 # Don't loop for ever + self.mock_libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE = 0 + self.mock_libvirt.VIR_DOMAIN_EVENT_ID_REBOOT = 1 + self.addCleanup(patcher.stop) + self.addCleanup(delattr, self, 'mock_libvirt') + return {libvirt_events: {}} + + @patch('salt.engines.libvirt_events.libvirt', + VIR_PREFIX_NONE=0, + VIR_PREFIX_ONE=1, + VIR_PREFIX_TWO=2, + VIR_PREFIX_SUB_FOO=0, + VIR_PREFIX_SUB_BAR=1, + VIR_PREFIX_SUB_FOOBAR=2) + def test_get_libvirt_enum_string_subprefix(self, libvirt_mock): + ''' + Make sure the libvirt enum value to string works reliably with + elements with a sub prefix, eg VIR_PREFIX_SUB_* in this case. + ''' + # Test case with a sub prefix + + assert libvirt_events._get_libvirt_enum_string('VIR_PREFIX_', 2) == 'two' + + @patch('salt.engines.libvirt_events.libvirt', + VIR_PREFIX_FOO=0, + VIR_PREFIX_FOO_BAR=1, + VIR_PREFIX_BAR_FOO=2) + def test_get_libvirt_enum_string_underscores(self, libvirt_mock): + ''' + Make sure the libvirt enum value to string works reliably and items + with an underscore aren't confused with sub prefixes. + ''' + assert libvirt_events._get_libvirt_enum_string('VIR_PREFIX_', 1) == 'foo bar' + + @patch('salt.engines.libvirt_events.libvirt', + VIR_DOMAIN_EVENT_DEFINED=0, + VIR_DOMAIN_EVENT_UNDEFINED=1, + VIR_DOMAIN_EVENT_DEFINED_ADDED=0, + VIR_DOMAIN_EVENT_DEFINED_UPDATED=1) + def test_get_domain_event_detail(self, mock_libvirt): + ''' + Test get_domain_event_detail function + ''' + assert libvirt_events._get_domain_event_detail(1, 2) == ('undefined', 'unknown') + assert libvirt_events._get_domain_event_detail(0, 1) == ('defined', 'updated') + assert libvirt_events._get_domain_event_detail(4, 2) == ('unknown', 'unknown') + + @patch('salt.engines.libvirt_events.libvirt', VIR_NETWORK_EVENT_ID_LIFECYCLE=1000) + def test_event_register(self, mock_libvirt): + ''' + Test that the libvirt_events engine actually registers events catch them and cleans + before leaving the place. + ''' + mock_cnx = MagicMock() + mock_libvirt.openReadOnly.return_value = mock_cnx + + mock_cnx.networkEventRegisterAny.return_value = 10000 + + libvirt_events.start('test:///', 'test/prefix') + + # Check that the connection has been opened + mock_libvirt.openReadOnly.assert_called_once_with('test:///') + + # Check that the connection has been closed + mock_cnx.close.assert_called_once() + + # Check events registration and deregistration + mock_cnx.domainEventRegisterAny.assert_any_call( + None, mock_libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, + libvirt_events._domain_event_lifecycle_cb, + {'prefix': 'test/prefix', 'object': 'domain', 'event': 'lifecycle'}) + mock_cnx.networkEventRegisterAny.assert_any_call( + None, mock_libvirt.VIR_NETWORK_EVENT_ID_LIFECYCLE, + libvirt_events._network_event_lifecycle_cb, + {'prefix': 'test/prefix', 'object': 'network', 'event': 'lifecycle'}) + + # Check that the deregister events are called with the result of register + mock_cnx.networkEventDeregisterAny.assert_called_with( + mock_cnx.networkEventRegisterAny.return_value) + + # Check that the default 'all' filter actually worked + counts = {obj: len(callback_def) for obj, callback_def in libvirt_events.CALLBACK_DEFS.items()} + for obj, count in counts.items(): + register = libvirt_events.REGISTER_FUNCTIONS[obj] + assert getattr(mock_cnx, register).call_count == count + + def test_event_skipped(self): + ''' + Test that events are skipped if their ID isn't defined in the libvirt + module (older libvirt) + ''' + self.mock_libvirt.mock_add_spec([ + 'openReadOnly', + 'virEventRegisterDefaultImpl', + 'virEventRunDefaultImpl', + 'VIR_DOMAIN_EVENT_ID_LIFECYCLE'], spec_set=True) + + libvirt_events.start('test:///', 'test/prefix') + + # Check events registration and deregistration + mock_cnx = self.mock_libvirt.openReadOnly.return_value + + mock_cnx.domainEventRegisterAny.assert_any_call( + None, self.mock_libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, + libvirt_events._domain_event_lifecycle_cb, + {'prefix': 'test/prefix', 'object': 'domain', 'event': 'lifecycle'}) + + # Network events should have been skipped + mock_cnx.networkEventRegisterAny.assert_not_called() + + def test_event_filtered(self): + ''' + Test that events are skipped if their ID isn't defined in the libvirt + module (older libvirt) + ''' + libvirt_events.start('test', 'test/prefix', 'domain/lifecycle') + + # Check events registration and deregistration + mock_cnx = self.mock_libvirt.openReadOnly.return_value + + mock_cnx.domainEventRegisterAny.assert_any_call( + None, 0, libvirt_events._domain_event_lifecycle_cb, + {'prefix': 'test/prefix', 'object': 'domain', 'event': 'lifecycle'}) + + # Network events should have been filtered out + mock_cnx.networkEventRegisterAny.assert_not_called() -- 2.17.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor