From 609f6e2b2b61c40203869a80bd918130479cc4d4 Mon Sep 17 00:00:00 2001 From: Grzegorz Grasza Date: Fri, 23 Nov 2018 17:14:47 +0100 Subject: [PATCH] Support versioned notifications Support nova versioned notifications. Unversioned notifications are still supported and the default. The CI is configured to test versioned notifications, and both implementations use the same methods. Because of this, testing versioned notifications also covers unversioned notifications, since the execution path flows through both. Change-Id: If028afa9e9fbcb344786cd287605e0d9af5d3c01 --- README.rst | 23 ++- novajoin/config.py | 8 ++ novajoin/exception.py | 6 + novajoin/notifications.py | 136 ++++++++++++++---- novajoin/tests/unit/notifications/__init__.py | 0 .../floatingip.update.end_associate.json | 18 +++ .../floatingip.update.end_disassociate.json | 18 +++ .../notifications/instance.create.end.json | 105 ++++++++++++++ .../notifications/instance.delete.end.json | 82 +++++++++++ .../unit/notifications/instance.update.json | 91 ++++++++++++ .../tests/unit/notifications/test_formats.py | 94 ++++++++++++ scripts/novajoin-install | 30 +++- 12 files changed, 577 insertions(+), 34 deletions(-) create mode 100644 novajoin/tests/unit/notifications/__init__.py create mode 100644 novajoin/tests/unit/notifications/floatingip.update.end_associate.json create mode 100644 novajoin/tests/unit/notifications/floatingip.update.end_disassociate.json create mode 100644 novajoin/tests/unit/notifications/instance.create.end.json create mode 100644 novajoin/tests/unit/notifications/instance.delete.end.json create mode 100644 novajoin/tests/unit/notifications/instance.update.json create mode 100644 novajoin/tests/unit/notifications/test_formats.py diff --git a/README.rst b/README.rst index 9c9d2a8..2d3e72b 100644 --- a/README.rst +++ b/README.rst @@ -117,6 +117,8 @@ novajoin REST service and enable notifications in .. note:: Notifications have to be also enabled and configured on nova computes! + See also information about enabling versioned and neutron notifications + in the `Notification listener Configuration`_ section below. Novajoin enables keystone authentication by default, as seen in **/etc/novajoin/join-api-paste.ini**. So credentials need to be set for @@ -217,14 +219,27 @@ send notifications to the novajoin topic in /etc/nova/nova.conf:: [oslo_messaging_notifications] ... - topics=notifications,novajoin_notifications + topics = notifications,novajoin_notifications + +In case of versioned notifications the configuration will look differently:: + + [notifications] + notify_on_state_change = vm_state + notification_format = versioned + versioned_notifications_topics = versioned_notifications,novajoin_notifications .. note:: Notifications have to be also enabled and configured on nova computes! -If you simply use notifications and ceilometer is running then the -notifications will be roughly split between the two services in a -round-robin format. +To enable neutron notifications, in /etc/neutron/neutron.conf:: + + [oslo_messaging_notifications] + driver = messagingv2 + topics = notifications,novajoin_notifications + +If you use notifications without changing the topic and ceilometer is +running, then the notifications will be roughly split between the two +services in a round-robin fashion. Usage ===== diff --git a/novajoin/config.py b/novajoin/config.py index aa1044e..b62876c 100644 --- a/novajoin/config.py +++ b/novajoin/config.py @@ -51,6 +51,14 @@ service_opts = [ help='Number retries when downloading an image from glance'), cfg.StrOpt('auth_strategy', default='keystone', help='Strategy to use for authentication.'), + cfg.StrOpt('notification_format', default='unversioned', + choices=[ + ('versioned', + 'Only the new versioned notifications are read'), + ('unversioned', + 'Only the legacy unversioned notifications are read'), + ], + help='The format of notifications to read.'), cfg.StrOpt('notifications_topic', default='novajoin_notifications', help='Topic on which to listen to notifications.'), ] diff --git a/novajoin/exception.py b/novajoin/exception.py index c809a3a..c10744a 100644 --- a/novajoin/exception.py +++ b/novajoin/exception.py @@ -151,3 +151,9 @@ class ImageNotFound(NotFound): class PolicyNotAuthorized(NotAuthorized): message = "Policy doesn't allow %(action)s to be performed." + + +class NotificationVersionMismatch(JoinException): + message = ("Provided notification version " + "%(provided_maj)s.%(provided_min)s did not match expected " + "%(expected_maj)s.%(expected_min)s for %(type)s") diff --git a/novajoin/notifications.py b/novajoin/notifications.py index 5047f13..bfd6829 100644 --- a/novajoin/notifications.py +++ b/novajoin/notifications.py @@ -21,9 +21,11 @@ import json import sys import time +import glanceclient as glance_client from neutronclient.v2_0 import client as neutron_client from novaclient import client as nova_client from novajoin import config +from novajoin import exception from novajoin.ipa import IPAClient from novajoin import join from novajoin.keystone_client import get_session @@ -57,12 +59,53 @@ def neutronclient(): return neutron_client.Client(session=session) +def glanceclient(): + session = get_session() + return glance_client.Client('2', session=session) + + class Registry(dict): - def __call__(self, name): - def decorator(fun): + def __call__(self, name, version=None, service='nova'): + def register_event(fun): + if version: + def check_event(sself, payload): + self.check_version(payload, version, service) + return fun(sself, payload[service + '_object.data']) + self[name] = check_event + return check_event self[name] = fun return fun - return decorator + return register_event + + @staticmethod + def check_version(payload, expected_version, service): + """Check nova notification version + + If actual's major version is different from expected, a + NotificationVersionMismatch error is raised. + If the minor versions are different, a DEBUG level log + message is output + """ + notification_version = payload[service + '_object.version'] + notification_name = payload[service + '_object.name'] + + maj_ver, min_ver = map(int, notification_version.split('.')) + expected_maj, expected_min = map(int, expected_version.split('.')) + if maj_ver != expected_maj: + raise exception.NotificationVersionMismatch( + provided_maj=maj_ver, provided_min=min_ver, + expected_maj=expected_maj, expected_min=expected_min, + type=notification_name) + + if min_ver != expected_min: + LOG.debug( + "Notification %(type)s minor version mismatch, " + "provided: %(provided_maj)s.%(provided_min)s, " + "expected: %(expected_maj)s.%(expected_min)s.", + {"type": notification_name, + "provided_maj": maj_ver, "provided_min": min_ver, + "expected_maj": expected_maj, "expected_min": expected_min} + ) class NotificationEndpoint(object): @@ -90,19 +133,19 @@ class NotificationEndpoint(object): event_handler(self, payload) @event_handlers('compute.instance.create.end') - def instance_create(self, payload): + def compute_instance_create(self, payload): hostname = self._generate_hostname(payload.get('hostname')) - instance_id = payload.get('instance_id') + instance_id = payload['instance_id'] LOG.info("Add new host %s (%s)", instance_id, hostname) @event_handlers('compute.instance.update') - def instance_update(self, payload): + def compute_instance_update(self, payload): ipa = ipaclient() join_controller = join.JoinController(ipa) - hostname_short = payload.get('hostname') - instance_id = payload.get('instance_id') - payload_metadata = payload.get('metadata') - image_metadata = payload.get('image_meta') + hostname_short = payload['hostname'] + instance_id = payload['instance_id'] + payload_metadata = payload['metadata'] + image_metadata = payload['image_meta'] hostname = self._generate_hostname(hostname_short) @@ -133,11 +176,11 @@ class NotificationEndpoint(object): ipa.flush_batch_operation() @event_handlers('compute.instance.delete.end') - def instance_delete(self, payload): - hostname_short = payload.get('hostname') - instance_id = payload.get('instance_id') - payload_metadata = payload.get('metadata') - image_metadata = payload.get('image_meta') + def compute_instance_delete(self, payload): + hostname_short = payload['hostname'] + instance_id = payload['instance_id'] + payload_metadata = payload['metadata'] + image_metadata = payload['image_meta'] hostname = self._generate_hostname(hostname_short) @@ -156,20 +199,20 @@ class NotificationEndpoint(object): @event_handlers('network.floating_ip.associate') def floaitng_ip_associate(self, payload): - floating_ip = payload.get('floating_ip') + floating_ip = payload['floating_ip'] LOG.info("Associate floating IP %s" % floating_ip) ipa = ipaclient() nova = novaclient() - server = nova.servers.get(payload.get('instance_id')) + server = nova.servers.get(payload['instance_id']) if server: - ipa.add_ip(server.get, floating_ip) + ipa.add_ip(server.name, floating_ip) else: LOG.error("Could not resolve %s into a hostname", - payload.get('instance_id')) + payload['instance_id']) @event_handlers('network.floating_ip.disassociate') def floating_ip_disassociate(self, payload): - floating_ip = payload.get('floating_ip') + floating_ip = payload['floating_ip'] LOG.info("Disassociate floating IP %s" % floating_ip) ipa = ipaclient() ipa.remove_ip(floating_ip) @@ -177,9 +220,9 @@ class NotificationEndpoint(object): @event_handlers('floatingip.update.end') def floating_ip_update(self, payload): """Neutron event""" - floatingip = payload.get('floatingip') - floating_ip = floatingip.get('floating_ip_address') - port_id = floatingip.get('port_id') + floatingip = payload['floatingip'] + floating_ip = floatingip['floating_ip_address'] + port_id = floatingip['port_id'] ipa = ipaclient() if port_id: LOG.info("Neutron floating IP associate: %s" % floating_ip) @@ -295,6 +338,48 @@ class NotificationEndpoint(object): return host +class VersionedNotificationEndpoint(NotificationEndpoint): + + filter_rule = oslo_messaging.notify.filter.NotificationFilter( + publisher_id='^nova-compute.*|^network.*', + event_type='^instance.create.end|' + '^instance.delete.end|' + '^instance.update|' + '^floatingip.update.end') + + event_handlers = Registry(NotificationEndpoint.event_handlers) + + @event_handlers('instance.create.end', '1.10') + def instance_create(self, payload): + newpayload = { + 'hostname': payload['host_name'], + 'instance_id': payload['uuid'], + } + self.compute_instance_create(newpayload) + + @event_handlers('instance.update', '1.8') + def instance_update(self, payload): + glance = glanceclient() + newpayload = { + 'hostname': payload['host_name'], + 'instance_id': payload['uuid'], + 'metadata': payload['metadata'], + 'image_meta': glance.images.get(payload['image_uuid']) + } + self.compute_instance_update(newpayload) + + @event_handlers('instance.delete.end', '1.7') + def instance_delete(self, payload): + glance = glanceclient() + newpayload = { + 'hostname': payload['host_name'], + 'instance_id': payload['uuid'], + 'metadata': payload['metadata'], + 'image_meta': glance.images.get(payload['image_uuid']) + } + self.compute_instance_delete(newpayload) + + def main(): register_keystoneauth_opts(CONF) CONF(sys.argv[1:], version='1.0.21', @@ -303,7 +388,10 @@ def main(): transport = oslo_messaging.get_notification_transport(CONF) targets = [oslo_messaging.Target(topic=CONF.notifications_topic)] - endpoints = [NotificationEndpoint()] + if CONF.notification_format == 'unversioned': + endpoints = [NotificationEndpoint()] + elif CONF.notification_format == 'versioned': + endpoints = [VersionedNotificationEndpoint()] server = oslo_messaging.get_notification_listener(transport, targets, diff --git a/novajoin/tests/unit/notifications/__init__.py b/novajoin/tests/unit/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/novajoin/tests/unit/notifications/floatingip.update.end_associate.json b/novajoin/tests/unit/notifications/floatingip.update.end_associate.json new file mode 100644 index 0000000..04c7a27 --- /dev/null +++ b/novajoin/tests/unit/notifications/floatingip.update.end_associate.json @@ -0,0 +1,18 @@ +{ + "priority" : "INFO", + "message_id" : "281218d3-0764-4397-b844-936c93fb89e6", + "event_type" : "floatingip.update.end", + "timestamp" : "2012-11-18 01:29:29.497899", + "payload" : { + "floatingip" : { + "floating_network_id" : "d9edfcd5-f245-4f45-be26-4383942fd74c", + "tenant_id" : "c97027dd880d4c129ae7a4ba7edade05", + "fixed_ip_address" : "172.16.59.10", + "router_id" : "62c1fd2b-8149-4222-8d6b-e581c55e5264", + "port_id" : "289ed46b-274c-444d-9fd4-bddf8acc7d7c", + "floating_ip_address" : "192.168.5.201", + "id" : "f38ff2b6-cd4d-433e-8a9c-9e00dfc05b1e" + } + }, + "publisher_id" : "network.svc02.os.lan" +} diff --git a/novajoin/tests/unit/notifications/floatingip.update.end_disassociate.json b/novajoin/tests/unit/notifications/floatingip.update.end_disassociate.json new file mode 100644 index 0000000..8110cd8 --- /dev/null +++ b/novajoin/tests/unit/notifications/floatingip.update.end_disassociate.json @@ -0,0 +1,18 @@ +{ + "priority" : "INFO", + "message_id" : "e9667b80-d2dc-4687-b2c6-2e648520157c", + "event_type" : "floatingip.update.end", + "timestamp" : "2012-11-18 01:35:08.312766", + "payload" : { + "floatingip" : { + "floating_network_id" : "d9edfcd5-f245-4f45-be26-4383942fd74c", + "tenant_id" : "c97027dd880d4c129ae7a4ba7edade05", + "fixed_ip_address" : null, + "router_id" : null, + "port_id" : null, + "floating_ip_address" : "192.168.5.201", + "id" : "f38ff2b6-cd4d-433e-8a9c-9e00dfc05b1e" + } + }, + "publisher_id" : "network.svc02.os.lan" +} diff --git a/novajoin/tests/unit/notifications/instance.create.end.json b/novajoin/tests/unit/notifications/instance.create.end.json new file mode 100644 index 0000000..baec56a --- /dev/null +++ b/novajoin/tests/unit/notifications/instance.create.end.json @@ -0,0 +1,105 @@ +{ + "event_type": "instance.create.end", + "payload": { + "nova_object.data": { + "action_initiator_project": "6f70656e737461636b20342065766572", + "action_initiator_user": "fake", + "architecture": "x86_64", + "auto_disk_config": "MANUAL", + "availability_zone": "nova", + "block_devices": [], + "created_at": "2012-10-29T13:42:11Z", + "deleted_at": null, + "display_description": "some-server", + "display_name": "some-server", + "fault": null, + "flavor": { + "nova_object.data": { + "description": null, + "disabled": false, + "ephemeral_gb": 0, + "extra_specs": { + "hw:watchdog_action": "disabled" + }, + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "is_public": true, + "memory_mb": 512, + "name": "test_flavor", + "projects": null, + "root_gb": 1, + "rxtx_factor": 1.0, + "swap": 0, + "vcpu_weight": 0, + "vcpus": 1 + }, + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.4" + }, + "host": "compute", + "host_name": "some-server", + "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6", + "ip_addresses": [ + { + "nova_object.data": { + "address": "192.168.1.3", + "device_name": "tapce531f90-19", + "label": "private-network", + "mac": "fa:16:3e:4c:2c:30", + "meta": {}, + "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442", + "version": 4 + }, + "nova_object.name": "IpPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + } + ], + "kernel_id": "", + "key_name": "my-key", + "keypairs": [ + { + "nova_object.data": { + "fingerprint": "1e:2c:9b:56:79:4b:45:77:f9:ca:7a:98:2c:b0:d5:3c", + "name": "my-key", + "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated-by-Nova", + "type": "ssh", + "user_id": "fake" + }, + "nova_object.name": "KeypairPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + } + ], + "launched_at": "2012-10-29T13:42:11Z", + "locked": false, + "metadata": {}, + "node": "fake-mini", + "os_type": null, + "power_state": "running", + "progress": 0, + "ramdisk_id": "", + "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d", + "reservation_id": "r-npxv0e40", + "state": "active", + "tags": [ + "tag" + ], + "task_state": null, + "tenant_id": "6f70656e737461636b20342065766572", + "terminated_at": null, + "trusted_image_certificates": [ + "cert-id-1", + "cert-id-2" + ], + "updated_at": "2012-10-29T13:42:11Z", + "user_id": "fake", + "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c" + }, + "nova_object.name": "InstanceCreatePayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.10" + }, + "priority": "INFO", + "publisher_id": "nova-compute:compute" +} diff --git a/novajoin/tests/unit/notifications/instance.delete.end.json b/novajoin/tests/unit/notifications/instance.delete.end.json new file mode 100644 index 0000000..cb137db --- /dev/null +++ b/novajoin/tests/unit/notifications/instance.delete.end.json @@ -0,0 +1,82 @@ +{ + "event_type": "instance.delete.end", + "payload": { + "nova_object.data": { + "action_initiator_project": "6f70656e737461636b20342065766572", + "action_initiator_user": "fake", + "architecture": "x86_64", + "auto_disk_config": "MANUAL", + "availability_zone": "nova", + "block_devices": [ + { + "nova_object.data": { + "boot_index": null, + "delete_on_termination": false, + "device_name": "/dev/sdb", + "tag": null, + "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113" + }, + "nova_object.name": "BlockDevicePayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + } + ], + "created_at": "2012-10-29T13:42:11Z", + "deleted_at": "2012-10-29T13:42:11Z", + "display_description": "some-server", + "display_name": "some-server", + "fault": null, + "flavor": { + "nova_object.data": { + "description": null, + "disabled": false, + "ephemeral_gb": 0, + "extra_specs": { + "hw:watchdog_action": "disabled" + }, + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "is_public": true, + "memory_mb": 512, + "name": "test_flavor", + "projects": null, + "root_gb": 1, + "rxtx_factor": 1.0, + "swap": 0, + "vcpu_weight": 0, + "vcpus": 1 + }, + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.4" + }, + "host": "compute", + "host_name": "some-server", + "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6", + "ip_addresses": [], + "kernel_id": "", + "key_name": "my-key", + "launched_at": "2012-10-29T13:42:11Z", + "locked": false, + "metadata": {}, + "node": "fake-mini", + "os_type": null, + "power_state": "pending", + "progress": 0, + "ramdisk_id": "", + "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d", + "reservation_id": "r-npxv0e40", + "state": "deleted", + "task_state": null, + "tenant_id": "6f70656e737461636b20342065766572", + "terminated_at": "2012-10-29T13:42:11Z", + "updated_at": "2012-10-29T13:42:11Z", + "user_id": "fake", + "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c" + }, + "nova_object.name": "InstanceActionPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.7" + }, + "priority": "INFO", + "publisher_id": "nova-compute:compute" +} diff --git a/novajoin/tests/unit/notifications/instance.update.json b/novajoin/tests/unit/notifications/instance.update.json new file mode 100644 index 0000000..c1dde21 --- /dev/null +++ b/novajoin/tests/unit/notifications/instance.update.json @@ -0,0 +1,91 @@ +{ + "event_type": "instance.update", + "payload": { + "nova_object.data": { + "action_initiator_project": "6f70656e737461636b20342065766572", + "action_initiator_user": "fake", + "architecture": "x86_64", + "audit_period": { + "nova_object.data": { + "audit_period_beginning": "2012-10-01T00:00:00Z", + "audit_period_ending": "2012-10-29T13:42:11Z" + }, + "nova_object.name": "AuditPeriodPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "auto_disk_config": "MANUAL", + "availability_zone": "nova", + "bandwidth": [], + "block_devices": [], + "created_at": "2012-10-29T13:42:11Z", + "deleted_at": null, + "display_description": "some-server", + "display_name": "some-server", + "flavor": { + "nova_object.data": { + "description": null, + "disabled": false, + "ephemeral_gb": 0, + "extra_specs": { + "hw:watchdog_action": "disabled" + }, + "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3", + "is_public": true, + "memory_mb": 512, + "name": "test_flavor", + "projects": null, + "root_gb": 1, + "rxtx_factor": 1.0, + "swap": 0, + "vcpu_weight": 0, + "vcpus": 1 + }, + "nova_object.name": "FlavorPayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.4" + }, + "host": "compute", + "host_name": "some-server", + "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6", + "ip_addresses": [], + "kernel_id": "", + "key_name": "my-key", + "launched_at": null, + "locked": false, + "metadata": {}, + "node": "fake-mini", + "old_display_name": null, + "os_type": null, + "power_state": "pending", + "progress": 0, + "ramdisk_id": "", + "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d", + "reservation_id": "r-npxv0e40", + "state": "active", + "state_update": { + "nova_object.data": { + "new_task_state": null, + "old_state": "building", + "old_task_state": null, + "state": "building" + }, + "nova_object.name": "InstanceStateUpdatePayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.0" + }, + "tags": [], + "task_state": "scheduling", + "tenant_id": "6f70656e737461636b20342065766572", + "terminated_at": null, + "updated_at": null, + "user_id": "fake", + "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c" + }, + "nova_object.name": "InstanceUpdatePayload", + "nova_object.namespace": "nova", + "nova_object.version": "1.8" + }, + "priority": "INFO", + "publisher_id": "nova-compute:fake-mini" +} diff --git a/novajoin/tests/unit/notifications/test_formats.py b/novajoin/tests/unit/notifications/test_formats.py new file mode 100644 index 0000000..6b40368 --- /dev/null +++ b/novajoin/tests/unit/notifications/test_formats.py @@ -0,0 +1,94 @@ +# Copyright 2018 Red Hat, Inc. +# +# 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 mock +import os + +from oslo_messaging.notify import dispatcher as notify_dispatcher +from oslo_messaging.notify import NotificationResult +from oslo_serialization import jsonutils + +from novajoin import notifications +from novajoin import test + + +SAMPLES_DIR = os.path.dirname(os.path.realpath(__file__)) + + +class NotificationFormatsTest(test.TestCase): + + def _get_event(self, filename): + json_sample = os.path.join(SAMPLES_DIR, filename) + with open(json_sample) as sample_file: + return jsonutils.loads(sample_file.read()) + + def _run_dispatcher(self, event): + dispatcher = notify_dispatcher.NotificationDispatcher( + [notifications.VersionedNotificationEndpoint()], None) + return dispatcher.dispatch(mock.Mock(ctxt={}, message=event)) + + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_instance_create(self, generate_hostname): + event = self._get_event('instance.create.end.json') + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.HANDLED) + + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_instance_create_wrong_version(self, generate_hostname): + event = self._get_event('instance.create.end.json') + event['payload']['nova_object.version'] = '999.999' + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.REQUEUE) + + @mock.patch('novajoin.notifications.glanceclient') + @mock.patch('novajoin.notifications.ipaclient') + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_instance_update(self, glanceclient, ipaclient, gen_hostname): + event = self._get_event('instance.update.json') + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.HANDLED) + + @mock.patch('novajoin.notifications.glanceclient') + @mock.patch('novajoin.notifications.ipaclient') + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_instance_delete(self, glanceclient, ipaclient, gen_hostname): + event = self._get_event('instance.delete.end.json') + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.HANDLED) + + @mock.patch('novajoin.notifications.neutronclient') + @mock.patch('novajoin.notifications.novaclient') + @mock.patch('novajoin.notifications.ipaclient') + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_floatingip_associate(self, neutronclient, novaclient, + ipaclient, generate_hostname): + event = self._get_event('floatingip.update.end_associate.json') + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.HANDLED) + + @mock.patch('novajoin.notifications.neutronclient') + @mock.patch('novajoin.notifications.novaclient') + @mock.patch('novajoin.notifications.ipaclient') + @mock.patch('novajoin.notifications.NotificationEndpoint' + '._generate_hostname') + def test_floatingip_disassociate(self, neutronclient, novaclient, + ipaclient, generate_hostname): + event = self._get_event('floatingip.update.end_disassociate.json') + result = self._run_dispatcher(event) + self.assertEqual(result, NotificationResult.HANDLED) diff --git a/scripts/novajoin-install b/scripts/novajoin-install index c01d77f..74786c4 100755 --- a/scripts/novajoin-install +++ b/scripts/novajoin-install @@ -138,6 +138,12 @@ def install(opts): config.set('keystone_authtoken', 'project_domain_name', 'default') config.set('keystone_authtoken', 'user_domain_id', 'default') + if opts.notification_format == 'versioned': + config.set('DEFAULT', 'notification_format', 'versioned') + else: + config.set('DEFAULT', 'notification_format', 'unversioned') + + config.set('DEFAULT', 'notifications_topic', 'novajoin_notifications') with open(opts.novajoin_conf, 'w') as f: config.write(f) @@ -193,13 +199,21 @@ def install(opts): 'notify_on_state_change', 'vm_state') - config.set('notifications', - 'notification_format', - 'unversioned') + if opts.notification_format == 'versioned': + config.set('notifications', + 'notification_format', + 'versioned') + config.set('notifications', + 'versioned_notifications_topics', + 'versioned_notifications,novajoin_notifications') + else: + config.set('notifications', + 'notification_format', + 'unversioned') + config.set('oslo_messaging_notifications', + 'topics', + 'notifications,novajoin_notifications') - config.set('oslo_messaging_notifications', - 'topics', - 'notifications,novajoin_notifications') with open(conf, 'w') as f: config.write(f) @@ -259,6 +273,10 @@ def parse_args(): parser.add_argument('--novajoin-conf', dest='novajoin_conf', help='novajoin configuration file', default=JOINCONF) + parser.add_argument('--notification-format', dest='notification_format', + help='The format of notifications to emit and read.', + choices=['versioned', 'unversioned'], + default='versioned') parser = configure_ipa.ipa_options(parser) opts = parser.parse_args()