From c0e4e162ce732086c64134ce31c3d5faa06c10d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Tue, 18 Jul 2017 17:28:24 +0200 Subject: [PATCH] Transform libvirt.error notification The libvirt.error notification has been transformed to the versioned notification framework. Co-Authored-By: Takashi Natsume Change-Id: I7d2287ce06d77c0afdef0ea8bdfb70f6c52d3c50 Implements: bp versioned-notification-transformation-stein --- .../libvirt-connect-error.json | 25 +++++++++ nova/compute/utils.py | 24 ++++++++ nova/notifications/objects/base.py | 3 +- nova/notifications/objects/libvirt.py | 40 +++++++++++++ nova/objects/fields.py | 4 +- .../notification_sample_tests/test_libvirt.py | 56 +++++++++++++++++++ .../objects/test_notification.py | 4 +- nova/tests/unit/virt/libvirt/test_driver.py | 21 +++++++ nova/virt/libvirt/host.py | 7 ++- 9 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 doc/notification_samples/libvirt-connect-error.json create mode 100644 nova/notifications/objects/libvirt.py create mode 100644 nova/tests/functional/notification_sample_tests/test_libvirt.py diff --git a/doc/notification_samples/libvirt-connect-error.json b/doc/notification_samples/libvirt-connect-error.json new file mode 100644 index 000000000000..a936d339ab21 --- /dev/null +++ b/doc/notification_samples/libvirt-connect-error.json @@ -0,0 +1,25 @@ +{ + "event_type": "libvirt.connect.error", + "payload": { + "nova_object.data": { + "reason": { + "nova_object.data": { + "exception": "libvirtError", + "exception_message": "Sample exception for versioned notification test.", + "function_name": "_get_connection", + "module_name": "nova.virt.libvirt.host", + "traceback": "Traceback (most recent call last):\n File \"nova/virt/libvirt/host.py\", line ..." + }, + "nova_object.name": "ExceptionPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.1" + }, + "ip": "10.0.2.15" + }, + "nova_object.name": "LibvirtErrorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "priority": "ERROR", + "publisher_id": "nova-compute:fake-mini" +} diff --git a/nova/compute/utils.py b/nova/compute/utils.py index f03dda8d9256..7ae7e5b6f74f 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -40,6 +40,7 @@ from nova.notifications.objects import exception as notification_exception from nova.notifications.objects import flavor as flavor_notification from nova.notifications.objects import instance as instance_notification from nova.notifications.objects import keypair as keypair_notification +from nova.notifications.objects import libvirt as libvirt_notification from nova.notifications.objects import metrics as metrics_notification from nova.notifications.objects import server_group as sg_notification from nova import objects @@ -810,6 +811,29 @@ def notify_about_metrics_update(context, host, host_ip, nodename, notification.emit(context) +@rpc.if_notifications_enabled +def notify_about_libvirt_connect_error(context, ip, exception, tb): + """Send a versioned notification about libvirt connect error. + + :param context: the request context + :param ip: the IP address of the host + :param exception: the thrown exception + :param tb: the traceback + """ + fault, _ = _get_fault_and_priority_from_exc_and_tb(exception, tb) + payload = libvirt_notification.LibvirtErrorPayload(ip=ip, reason=fault) + notification = libvirt_notification.LibvirtErrorNotification( + priority=fields.NotificationPriority.ERROR, + publisher=notification_base.NotificationPublisher( + host=CONF.host, source=fields.NotificationSource.COMPUTE), + event_type=notification_base.EventType( + object='libvirt', + action=fields.NotificationAction.CONNECT, + phase=fields.NotificationPhase.ERROR), + payload=payload) + notification.emit(context) + + def refresh_info_cache_for_instance(context, instance): """Refresh the info cache for an instance. diff --git a/nova/notifications/objects/base.py b/nova/notifications/objects/base.py index 4c9b548185b9..1678e5d714de 100644 --- a/nova/notifications/objects/base.py +++ b/nova/notifications/objects/base.py @@ -65,7 +65,8 @@ class EventType(NotificationObject): # enum # Version 1.15: LIVE_MIGRATION_FORCE_COMPLETE is added to the # NotificationActionField enum - VERSION = '1.15' + # Version 1.16: CONNECT is added to NotificationActionField enum + VERSION = '1.16' fields = { 'object': fields.StringField(nullable=False), diff --git a/nova/notifications/objects/libvirt.py b/nova/notifications/objects/libvirt.py new file mode 100644 index 000000000000..1deb47d7bdce --- /dev/null +++ b/nova/notifications/objects/libvirt.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.notifications.objects import base +from nova.objects import base as nova_base +from nova.objects import fields + + +@nova_base.NovaObjectRegistry.register_notification +class LibvirtErrorPayload(base.NotificationPayloadBase): + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'ip': fields.StringField(), + 'reason': fields.ObjectField('ExceptionPayload'), + } + + def __init__(self, ip, reason): + super(LibvirtErrorPayload, self).__init__() + self.ip = ip + self.reason = reason + + +@base.notification_sample('libvirt-connect-error.json') +@nova_base.NovaObjectRegistry.register_notification +class LibvirtErrorNotification(base.NotificationBase): + # Version 1.0: Initial version + VERSION = '1.0' + fields = { + 'payload': fields.ObjectField('LibvirtErrorPayload') + } diff --git a/nova/objects/fields.py b/nova/objects/fields.py index 0e03b5d0afa5..fdf963817479 100644 --- a/nova/objects/fields.py +++ b/nova/objects/fields.py @@ -831,6 +831,7 @@ class NotificationAction(BaseNovaEnum): LOCK = 'lock' UNLOCK = 'unlock' UPDATE_PROP = 'update_prop' + CONNECT = 'connect' ALL = (UPDATE, EXCEPTION, DELETE, PAUSE, UNPAUSE, RESIZE, VOLUME_SWAP, SUSPEND, POWER_ON, REBOOT, SHUTDOWN, SNAPSHOT, INTERFACE_ATTACH, @@ -842,7 +843,8 @@ class NotificationAction(BaseNovaEnum): RESIZE_CONFIRM, RESIZE_PREP, RESIZE_REVERT, SHELVE_OFFLOAD, SOFT_DELETE, TRIGGER_CRASH_DUMP, UNRESCUE, UNSHELVE, ADD_HOST, REMOVE_HOST, ADD_MEMBER, UPDATE_METADATA, LOCK, UNLOCK, - REBUILD_SCHEDULED, UPDATE_PROP, LIVE_MIGRATION_FORCE_COMPLETE) + REBUILD_SCHEDULED, UPDATE_PROP, LIVE_MIGRATION_FORCE_COMPLETE, + CONNECT) # TODO(rlrossit): These should be changed over to be a StateMachine enum from diff --git a/nova/tests/functional/notification_sample_tests/test_libvirt.py b/nova/tests/functional/notification_sample_tests/test_libvirt.py new file mode 100644 index 000000000000..5d046860805c --- /dev/null +++ b/nova/tests/functional/notification_sample_tests/test_libvirt.py @@ -0,0 +1,56 @@ +# Copyright 2018 NTT Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures +import mock + +import nova.conf +from nova import exception +from nova.tests.functional.notification_sample_tests \ + import notification_sample_base +from nova.tests.unit import fake_notifier +from nova.tests.unit.virt.libvirt import fakelibvirt +from nova.virt.libvirt import host + + +CONF = nova.conf.CONF + + +class TestLibvirtErrorNotificationSample( + notification_sample_base.NotificationSampleTestBase): + + def setUp(self): + self.flags(compute_driver='libvirt.LibvirtDriver') + self.useFixture(fakelibvirt.FakeLibvirtFixture()) + self.useFixture(fixtures.MockPatchObject(host.Host, 'initialize')) + super(TestLibvirtErrorNotificationSample, self).setUp() + + @mock.patch('nova.virt.libvirt.host.Host._get_connection') + def test_libvirt_connect_error(self, mock_get_conn): + mock_get_conn.side_effect = fakelibvirt.libvirtError( + 'Sample exception for versioned notification test.') + # restart the compute service + self.assertRaises(exception.HypervisorUnavailable, + self.restart_compute_service, self.compute) + + self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS)) + self._verify_notification( + 'libvirt-connect-error', + replacements={ + 'ip': CONF.my_ip, + 'reason.function_name': self.ANY, + 'reason.module_name': self.ANY, + 'reason.traceback': self.ANY + }, + actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py index 1dba3e140204..be96bea49c2c 100644 --- a/nova/tests/unit/notifications/objects/test_notification.py +++ b/nova/tests/unit/notifications/objects/test_notification.py @@ -370,7 +370,7 @@ notification_object_data = { 'AuditPeriodPayload': '1.0-2b429dd307b8374636703b843fa3f9cb', 'BandwidthPayload': '1.0-ee2616a7690ab78406842a2b68e34130', 'BlockDevicePayload': '1.0-29751e1b6d41b1454e36768a1e764df8', - 'EventType': '1.15-a93b5b3b54ebf6c5a158dfcd985d15c5', + 'EventType': '1.16-0da423d66218567962410921f2542c41', 'ExceptionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'ExceptionPayload': '1.1-6c43008bd81885a63bc7f7c629f0793b', 'FlavorNotification': '1.0-a73147b93b520ff0061865849d3dfa56', @@ -404,6 +404,8 @@ notification_object_data = { 'IpPayload': '1.0-8ecf567a99e516d4af094439a7632d34', 'KeypairNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'KeypairPayload': '1.0-6daebbbde0e1bf35c1556b1ecd9385c1', + 'LibvirtErrorNotification': '1.0-a73147b93b520ff0061865849d3dfa56', + 'LibvirtErrorPayload': '1.0-9e7a8f0b895dd15531d5a6f3aa95d58e', 'MetricPayload': '1.0-bcdbe85048f335132e4c82a1b8fa3da8', 'MetricsNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'MetricsPayload': '1.0-65c69b15b4de5a8c01971cb5bb9ab650', diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 81af44d22fb9..4e0fb05bc885 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1656,6 +1656,27 @@ class LibvirtConnTestCase(test.NoDBTestCase, mock_lookup.side_effect = lambda x: fakelibvirt.NodeDevice(conn) drvr._prepare_pci_devices_for_use(pci_devices) + @mock.patch('nova.context.get_admin_context') + @mock.patch('nova.compute.utils.notify_about_libvirt_connect_error') + def test_versioned_notification(self, mock_notify, mock_get): + mock_get.return_value = self.context + + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + + fake_error = fakelibvirt.make_libvirtError( + fakelibvirt.libvirtError, "Failed to connect to host", + error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR) + + with mock.patch('nova.virt.libvirt.host.Host._get_connection', + side_effect=fake_error): + self.assertRaises(exception.HypervisorUnavailable, + drvr._host.get_connection) + mock_get.assert_called_once_with() + mock_notify.assert_called_once_with(self.context, ip=CONF.my_ip, + exception=fake_error, tb=mock.ANY) + _, kwargs = mock_notify.call_args + self.assertIn('Traceback (most recent call last):', kwargs['tb']) + @mock.patch.object(fakelibvirt.virConnect, "nodeDeviceLookupByName") @mock.patch.object(fakelibvirt.virNodeDevice, "dettach") def test_prepare_pci_device_exception(self, mock_detach, mock_lookup): diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py index 65ae0ff2f12b..baf643496a39 100644 --- a/nova/virt/libvirt/host.py +++ b/nova/virt/libvirt/host.py @@ -32,6 +32,7 @@ import os import socket import sys import threading +import traceback from eventlet import greenio from eventlet import greenthread @@ -45,6 +46,7 @@ from oslo_utils import units from oslo_utils import versionutils import six +from nova.compute import utils as compute_utils import nova.conf from nova import context as nova_context from nova import exception @@ -463,9 +465,12 @@ class Host(object): payload = dict(ip=CONF.my_ip, method='_connect', reason=ex) - rpc.get_notifier('compute').error(nova_context.get_admin_context(), + ctxt = nova_context.get_admin_context() + rpc.get_notifier('compute').error(ctxt, 'compute.libvirt.error', payload) + compute_utils.notify_about_libvirt_connect_error( + ctxt, ip=CONF.my_ip, exception=ex, tb=traceback.format_exc()) raise exception.HypervisorUnavailable(host=CONF.host) return conn