Browse Source

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
Grzegorz Grasza 4 months ago
parent
commit
609f6e2b2b

+ 19
- 4
README.rst View File

@@ -117,6 +117,8 @@ novajoin REST service and enable notifications in
117 117
 
118 118
 .. note::
119 119
    Notifications have to be also enabled and configured on nova computes!
120
+   See also information about enabling versioned and neutron notifications
121
+   in the `Notification listener Configuration`_ section below.
120 122
 
121 123
 Novajoin enables keystone authentication by default, as seen in
122 124
 **/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::
217 219
 
218 220
     [oslo_messaging_notifications]
219 221
     ...
220
-    topics=notifications,novajoin_notifications
222
+    topics = notifications,novajoin_notifications
223
+
224
+In case of versioned notifications the configuration will look differently::
225
+
226
+    [notifications]
227
+    notify_on_state_change = vm_state
228
+    notification_format = versioned
229
+    versioned_notifications_topics = versioned_notifications,novajoin_notifications
221 230
 
222 231
 .. note::
223 232
    Notifications have to be also enabled and configured on nova computes!
224 233
 
225
-If you simply use notifications and ceilometer is running then the
226
-notifications will be roughly split between the two services in a
227
-round-robin format.
234
+To enable neutron notifications, in /etc/neutron/neutron.conf::
235
+
236
+    [oslo_messaging_notifications]
237
+    driver = messagingv2
238
+    topics = notifications,novajoin_notifications
239
+
240
+If you use notifications without changing the topic and ceilometer is
241
+running, then the notifications will be roughly split between the two
242
+services in a round-robin fashion.
228 243
 
229 244
 Usage
230 245
 =====

+ 8
- 0
novajoin/config.py View File

@@ -51,6 +51,14 @@ service_opts = [
51 51
                help='Number retries when downloading an image from glance'),
52 52
     cfg.StrOpt('auth_strategy', default='keystone',
53 53
                help='Strategy to use for authentication.'),
54
+    cfg.StrOpt('notification_format', default='unversioned',
55
+               choices=[
56
+                   ('versioned',
57
+                    'Only the new versioned notifications are read'),
58
+                   ('unversioned',
59
+                    'Only the legacy unversioned notifications are read'),
60
+               ],
61
+               help='The format of notifications to read.'),
54 62
     cfg.StrOpt('notifications_topic', default='novajoin_notifications',
55 63
                help='Topic on which to listen to notifications.'),
56 64
 ]

+ 6
- 0
novajoin/exception.py View File

@@ -151,3 +151,9 @@ class ImageNotFound(NotFound):
151 151
 
152 152
 class PolicyNotAuthorized(NotAuthorized):
153 153
     message = "Policy doesn't allow %(action)s to be performed."
154
+
155
+
156
+class NotificationVersionMismatch(JoinException):
157
+    message = ("Provided notification version "
158
+               "%(provided_maj)s.%(provided_min)s did not match expected "
159
+               "%(expected_maj)s.%(expected_min)s for %(type)s")

+ 112
- 24
novajoin/notifications.py View File

@@ -21,9 +21,11 @@ import json
21 21
 import sys
22 22
 import time
23 23
 
24
+import glanceclient as glance_client
24 25
 from neutronclient.v2_0 import client as neutron_client
25 26
 from novaclient import client as nova_client
26 27
 from novajoin import config
28
+from novajoin import exception
27 29
 from novajoin.ipa import IPAClient
28 30
 from novajoin import join
29 31
 from novajoin.keystone_client import get_session
@@ -57,12 +59,53 @@ def neutronclient():
57 59
     return neutron_client.Client(session=session)
58 60
 
59 61
 
62
+def glanceclient():
63
+    session = get_session()
64
+    return glance_client.Client('2', session=session)
65
+
66
+
60 67
 class Registry(dict):
61
-    def __call__(self, name):
62
-        def decorator(fun):
68
+    def __call__(self, name, version=None, service='nova'):
69
+        def register_event(fun):
70
+            if version:
71
+                def check_event(sself, payload):
72
+                    self.check_version(payload, version, service)
73
+                    return fun(sself, payload[service + '_object.data'])
74
+                self[name] = check_event
75
+                return check_event
63 76
             self[name] = fun
64 77
             return fun
65
-        return decorator
78
+        return register_event
79
+
80
+    @staticmethod
81
+    def check_version(payload, expected_version, service):
82
+        """Check nova notification version
83
+
84
+        If actual's major version is different from expected, a
85
+        NotificationVersionMismatch error is raised.
86
+        If the minor versions are different, a DEBUG level log
87
+        message is output
88
+        """
89
+        notification_version = payload[service + '_object.version']
90
+        notification_name = payload[service + '_object.name']
91
+
92
+        maj_ver, min_ver = map(int, notification_version.split('.'))
93
+        expected_maj, expected_min = map(int, expected_version.split('.'))
94
+        if maj_ver != expected_maj:
95
+            raise exception.NotificationVersionMismatch(
96
+                provided_maj=maj_ver, provided_min=min_ver,
97
+                expected_maj=expected_maj, expected_min=expected_min,
98
+                type=notification_name)
99
+
100
+        if min_ver != expected_min:
101
+            LOG.debug(
102
+                "Notification %(type)s minor version mismatch, "
103
+                "provided: %(provided_maj)s.%(provided_min)s, "
104
+                "expected: %(expected_maj)s.%(expected_min)s.",
105
+                {"type": notification_name,
106
+                 "provided_maj": maj_ver, "provided_min": min_ver,
107
+                 "expected_maj": expected_maj, "expected_min": expected_min}
108
+            )
66 109
 
67 110
 
68 111
 class NotificationEndpoint(object):
@@ -90,19 +133,19 @@ class NotificationEndpoint(object):
90 133
         event_handler(self, payload)
91 134
 
92 135
     @event_handlers('compute.instance.create.end')
93
-    def instance_create(self, payload):
136
+    def compute_instance_create(self, payload):
94 137
         hostname = self._generate_hostname(payload.get('hostname'))
95
-        instance_id = payload.get('instance_id')
138
+        instance_id = payload['instance_id']
96 139
         LOG.info("Add new host %s (%s)", instance_id, hostname)
97 140
 
98 141
     @event_handlers('compute.instance.update')
99
-    def instance_update(self, payload):
142
+    def compute_instance_update(self, payload):
100 143
         ipa = ipaclient()
101 144
         join_controller = join.JoinController(ipa)
102
-        hostname_short = payload.get('hostname')
103
-        instance_id = payload.get('instance_id')
104
-        payload_metadata = payload.get('metadata')
105
-        image_metadata = payload.get('image_meta')
145
+        hostname_short = payload['hostname']
146
+        instance_id = payload['instance_id']
147
+        payload_metadata = payload['metadata']
148
+        image_metadata = payload['image_meta']
106 149
 
107 150
         hostname = self._generate_hostname(hostname_short)
108 151
 
@@ -133,11 +176,11 @@ class NotificationEndpoint(object):
133 176
         ipa.flush_batch_operation()
134 177
 
135 178
     @event_handlers('compute.instance.delete.end')
136
-    def instance_delete(self, payload):
137
-        hostname_short = payload.get('hostname')
138
-        instance_id = payload.get('instance_id')
139
-        payload_metadata = payload.get('metadata')
140
-        image_metadata = payload.get('image_meta')
179
+    def compute_instance_delete(self, payload):
180
+        hostname_short = payload['hostname']
181
+        instance_id = payload['instance_id']
182
+        payload_metadata = payload['metadata']
183
+        image_metadata = payload['image_meta']
141 184
 
142 185
         hostname = self._generate_hostname(hostname_short)
143 186
 
@@ -156,20 +199,20 @@ class NotificationEndpoint(object):
156 199
 
157 200
     @event_handlers('network.floating_ip.associate')
158 201
     def floaitng_ip_associate(self, payload):
159
-        floating_ip = payload.get('floating_ip')
202
+        floating_ip = payload['floating_ip']
160 203
         LOG.info("Associate floating IP %s" % floating_ip)
161 204
         ipa = ipaclient()
162 205
         nova = novaclient()
163
-        server = nova.servers.get(payload.get('instance_id'))
206
+        server = nova.servers.get(payload['instance_id'])
164 207
         if server:
165
-            ipa.add_ip(server.get, floating_ip)
208
+            ipa.add_ip(server.name, floating_ip)
166 209
         else:
167 210
             LOG.error("Could not resolve %s into a hostname",
168
-                      payload.get('instance_id'))
211
+                      payload['instance_id'])
169 212
 
170 213
     @event_handlers('network.floating_ip.disassociate')
171 214
     def floating_ip_disassociate(self, payload):
172
-        floating_ip = payload.get('floating_ip')
215
+        floating_ip = payload['floating_ip']
173 216
         LOG.info("Disassociate floating IP %s" % floating_ip)
174 217
         ipa = ipaclient()
175 218
         ipa.remove_ip(floating_ip)
@@ -177,9 +220,9 @@ class NotificationEndpoint(object):
177 220
     @event_handlers('floatingip.update.end')
178 221
     def floating_ip_update(self, payload):
179 222
         """Neutron event"""
180
-        floatingip = payload.get('floatingip')
181
-        floating_ip = floatingip.get('floating_ip_address')
182
-        port_id = floatingip.get('port_id')
223
+        floatingip = payload['floatingip']
224
+        floating_ip = floatingip['floating_ip_address']
225
+        port_id = floatingip['port_id']
183 226
         ipa = ipaclient()
184 227
         if port_id:
185 228
             LOG.info("Neutron floating IP associate: %s" % floating_ip)
@@ -295,6 +338,48 @@ class NotificationEndpoint(object):
295 338
         return host
296 339
 
297 340
 
341
+class VersionedNotificationEndpoint(NotificationEndpoint):
342
+
343
+    filter_rule = oslo_messaging.notify.filter.NotificationFilter(
344
+        publisher_id='^nova-compute.*|^network.*',
345
+        event_type='^instance.create.end|'
346
+                   '^instance.delete.end|'
347
+                   '^instance.update|'
348
+                   '^floatingip.update.end')
349
+
350
+    event_handlers = Registry(NotificationEndpoint.event_handlers)
351
+
352
+    @event_handlers('instance.create.end', '1.10')
353
+    def instance_create(self, payload):
354
+        newpayload = {
355
+            'hostname': payload['host_name'],
356
+            'instance_id': payload['uuid'],
357
+        }
358
+        self.compute_instance_create(newpayload)
359
+
360
+    @event_handlers('instance.update', '1.8')
361
+    def instance_update(self, payload):
362
+        glance = glanceclient()
363
+        newpayload = {
364
+            'hostname': payload['host_name'],
365
+            'instance_id': payload['uuid'],
366
+            'metadata': payload['metadata'],
367
+            'image_meta': glance.images.get(payload['image_uuid'])
368
+        }
369
+        self.compute_instance_update(newpayload)
370
+
371
+    @event_handlers('instance.delete.end', '1.7')
372
+    def instance_delete(self, payload):
373
+        glance = glanceclient()
374
+        newpayload = {
375
+            'hostname': payload['host_name'],
376
+            'instance_id': payload['uuid'],
377
+            'metadata': payload['metadata'],
378
+            'image_meta': glance.images.get(payload['image_uuid'])
379
+        }
380
+        self.compute_instance_delete(newpayload)
381
+
382
+
298 383
 def main():
299 384
     register_keystoneauth_opts(CONF)
300 385
     CONF(sys.argv[1:], version='1.0.21',
@@ -303,7 +388,10 @@ def main():
303 388
 
304 389
     transport = oslo_messaging.get_notification_transport(CONF)
305 390
     targets = [oslo_messaging.Target(topic=CONF.notifications_topic)]
306
-    endpoints = [NotificationEndpoint()]
391
+    if CONF.notification_format == 'unversioned':
392
+        endpoints = [NotificationEndpoint()]
393
+    elif CONF.notification_format == 'versioned':
394
+        endpoints = [VersionedNotificationEndpoint()]
307 395
 
308 396
     server = oslo_messaging.get_notification_listener(transport,
309 397
                                                       targets,

+ 0
- 0
novajoin/tests/unit/notifications/__init__.py View File


+ 18
- 0
novajoin/tests/unit/notifications/floatingip.update.end_associate.json View File

@@ -0,0 +1,18 @@
1
+{
2
+   "priority" : "INFO",
3
+   "message_id" : "281218d3-0764-4397-b844-936c93fb89e6",
4
+   "event_type" : "floatingip.update.end",
5
+   "timestamp" : "2012-11-18 01:29:29.497899",
6
+   "payload" : {
7
+      "floatingip" : {
8
+         "floating_network_id" : "d9edfcd5-f245-4f45-be26-4383942fd74c",
9
+         "tenant_id" : "c97027dd880d4c129ae7a4ba7edade05",
10
+         "fixed_ip_address" : "172.16.59.10",
11
+         "router_id" : "62c1fd2b-8149-4222-8d6b-e581c55e5264",
12
+         "port_id" : "289ed46b-274c-444d-9fd4-bddf8acc7d7c",
13
+         "floating_ip_address" : "192.168.5.201",
14
+         "id" : "f38ff2b6-cd4d-433e-8a9c-9e00dfc05b1e"
15
+      }
16
+   },
17
+   "publisher_id" : "network.svc02.os.lan"
18
+}

+ 18
- 0
novajoin/tests/unit/notifications/floatingip.update.end_disassociate.json View File

@@ -0,0 +1,18 @@
1
+{
2
+   "priority" : "INFO",
3
+   "message_id" : "e9667b80-d2dc-4687-b2c6-2e648520157c",
4
+   "event_type" : "floatingip.update.end",
5
+   "timestamp" : "2012-11-18 01:35:08.312766",
6
+   "payload" : {
7
+      "floatingip" : {
8
+         "floating_network_id" : "d9edfcd5-f245-4f45-be26-4383942fd74c",
9
+         "tenant_id" : "c97027dd880d4c129ae7a4ba7edade05",
10
+         "fixed_ip_address" : null,
11
+         "router_id" : null,
12
+         "port_id" : null,
13
+         "floating_ip_address" : "192.168.5.201",
14
+         "id" : "f38ff2b6-cd4d-433e-8a9c-9e00dfc05b1e"
15
+      }
16
+   },
17
+   "publisher_id" : "network.svc02.os.lan"
18
+}

+ 105
- 0
novajoin/tests/unit/notifications/instance.create.end.json View File

@@ -0,0 +1,105 @@
1
+{
2
+    "event_type": "instance.create.end",
3
+    "payload": {
4
+        "nova_object.data": {
5
+            "action_initiator_project": "6f70656e737461636b20342065766572",
6
+            "action_initiator_user": "fake",
7
+            "architecture": "x86_64",
8
+            "auto_disk_config": "MANUAL",
9
+            "availability_zone": "nova",
10
+            "block_devices": [],
11
+            "created_at": "2012-10-29T13:42:11Z",
12
+            "deleted_at": null,
13
+            "display_description": "some-server",
14
+            "display_name": "some-server",
15
+            "fault": null,
16
+            "flavor": {
17
+                "nova_object.data": {
18
+                    "description": null,
19
+                    "disabled": false,
20
+                    "ephemeral_gb": 0,
21
+                    "extra_specs": {
22
+                        "hw:watchdog_action": "disabled"
23
+                    },
24
+                    "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
25
+                    "is_public": true,
26
+                    "memory_mb": 512,
27
+                    "name": "test_flavor",
28
+                    "projects": null,
29
+                    "root_gb": 1,
30
+                    "rxtx_factor": 1.0,
31
+                    "swap": 0,
32
+                    "vcpu_weight": 0,
33
+                    "vcpus": 1
34
+                },
35
+                "nova_object.name": "FlavorPayload",
36
+                "nova_object.namespace": "nova",
37
+                "nova_object.version": "1.4"
38
+            },
39
+            "host": "compute",
40
+            "host_name": "some-server",
41
+            "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
42
+            "ip_addresses": [
43
+                {
44
+                    "nova_object.data": {
45
+                        "address": "192.168.1.3",
46
+                        "device_name": "tapce531f90-19",
47
+                        "label": "private-network",
48
+                        "mac": "fa:16:3e:4c:2c:30",
49
+                        "meta": {},
50
+                        "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
51
+                        "version": 4
52
+                    },
53
+                    "nova_object.name": "IpPayload",
54
+                    "nova_object.namespace": "nova",
55
+                    "nova_object.version": "1.0"
56
+                }
57
+            ],
58
+            "kernel_id": "",
59
+            "key_name": "my-key",
60
+            "keypairs": [
61
+                {
62
+                    "nova_object.data": {
63
+                        "fingerprint": "1e:2c:9b:56:79:4b:45:77:f9:ca:7a:98:2c:b0:d5:3c",
64
+                        "name": "my-key",
65
+                        "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated-by-Nova",
66
+                        "type": "ssh",
67
+                        "user_id": "fake"
68
+                    },
69
+                    "nova_object.name": "KeypairPayload",
70
+                    "nova_object.namespace": "nova",
71
+                    "nova_object.version": "1.0"
72
+                }
73
+            ],
74
+            "launched_at": "2012-10-29T13:42:11Z",
75
+            "locked": false,
76
+            "metadata": {},
77
+            "node": "fake-mini",
78
+            "os_type": null,
79
+            "power_state": "running",
80
+            "progress": 0,
81
+            "ramdisk_id": "",
82
+            "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d",
83
+            "reservation_id": "r-npxv0e40",
84
+            "state": "active",
85
+            "tags": [
86
+                "tag"
87
+            ],
88
+            "task_state": null,
89
+            "tenant_id": "6f70656e737461636b20342065766572",
90
+            "terminated_at": null,
91
+            "trusted_image_certificates": [
92
+                "cert-id-1",
93
+                "cert-id-2"
94
+            ],
95
+            "updated_at": "2012-10-29T13:42:11Z",
96
+            "user_id": "fake",
97
+            "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c"
98
+        },
99
+        "nova_object.name": "InstanceCreatePayload",
100
+        "nova_object.namespace": "nova",
101
+        "nova_object.version": "1.10"
102
+    },
103
+    "priority": "INFO",
104
+    "publisher_id": "nova-compute:compute"
105
+}

+ 82
- 0
novajoin/tests/unit/notifications/instance.delete.end.json View File

@@ -0,0 +1,82 @@
1
+{
2
+    "event_type": "instance.delete.end",
3
+    "payload": {
4
+        "nova_object.data": {
5
+            "action_initiator_project": "6f70656e737461636b20342065766572",
6
+            "action_initiator_user": "fake",
7
+            "architecture": "x86_64",
8
+            "auto_disk_config": "MANUAL",
9
+            "availability_zone": "nova",
10
+            "block_devices": [
11
+                {
12
+                    "nova_object.data": {
13
+                        "boot_index": null,
14
+                        "delete_on_termination": false,
15
+                        "device_name": "/dev/sdb",
16
+                        "tag": null,
17
+                        "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113"
18
+                    },
19
+                    "nova_object.name": "BlockDevicePayload",
20
+                    "nova_object.namespace": "nova",
21
+                    "nova_object.version": "1.0"
22
+                }
23
+            ],
24
+            "created_at": "2012-10-29T13:42:11Z",
25
+            "deleted_at": "2012-10-29T13:42:11Z",
26
+            "display_description": "some-server",
27
+            "display_name": "some-server",
28
+            "fault": null,
29
+            "flavor": {
30
+                "nova_object.data": {
31
+                    "description": null,
32
+                    "disabled": false,
33
+                    "ephemeral_gb": 0,
34
+                    "extra_specs": {
35
+                        "hw:watchdog_action": "disabled"
36
+                    },
37
+                    "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
38
+                    "is_public": true,
39
+                    "memory_mb": 512,
40
+                    "name": "test_flavor",
41
+                    "projects": null,
42
+                    "root_gb": 1,
43
+                    "rxtx_factor": 1.0,
44
+                    "swap": 0,
45
+                    "vcpu_weight": 0,
46
+                    "vcpus": 1
47
+                },
48
+                "nova_object.name": "FlavorPayload",
49
+                "nova_object.namespace": "nova",
50
+                "nova_object.version": "1.4"
51
+            },
52
+            "host": "compute",
53
+            "host_name": "some-server",
54
+            "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
55
+            "ip_addresses": [],
56
+            "kernel_id": "",
57
+            "key_name": "my-key",
58
+            "launched_at": "2012-10-29T13:42:11Z",
59
+            "locked": false,
60
+            "metadata": {},
61
+            "node": "fake-mini",
62
+            "os_type": null,
63
+            "power_state": "pending",
64
+            "progress": 0,
65
+            "ramdisk_id": "",
66
+            "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d",
67
+            "reservation_id": "r-npxv0e40",
68
+            "state": "deleted",
69
+            "task_state": null,
70
+            "tenant_id": "6f70656e737461636b20342065766572",
71
+            "terminated_at": "2012-10-29T13:42:11Z",
72
+            "updated_at": "2012-10-29T13:42:11Z",
73
+            "user_id": "fake",
74
+            "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c"
75
+        },
76
+        "nova_object.name": "InstanceActionPayload",
77
+        "nova_object.namespace": "nova",
78
+        "nova_object.version": "1.7"
79
+    },
80
+    "priority": "INFO",
81
+    "publisher_id": "nova-compute:compute"
82
+}

+ 91
- 0
novajoin/tests/unit/notifications/instance.update.json View File

@@ -0,0 +1,91 @@
1
+{
2
+    "event_type": "instance.update",
3
+    "payload": {
4
+        "nova_object.data": {
5
+            "action_initiator_project": "6f70656e737461636b20342065766572",
6
+            "action_initiator_user": "fake",
7
+            "architecture": "x86_64",
8
+            "audit_period": {
9
+                "nova_object.data": {
10
+                    "audit_period_beginning": "2012-10-01T00:00:00Z",
11
+                    "audit_period_ending": "2012-10-29T13:42:11Z"
12
+                },
13
+                "nova_object.name": "AuditPeriodPayload",
14
+                "nova_object.namespace": "nova",
15
+                "nova_object.version": "1.0"
16
+            },
17
+            "auto_disk_config": "MANUAL",
18
+            "availability_zone": "nova",
19
+            "bandwidth": [],
20
+            "block_devices": [],
21
+            "created_at": "2012-10-29T13:42:11Z",
22
+            "deleted_at": null,
23
+            "display_description": "some-server",
24
+            "display_name": "some-server",
25
+            "flavor": {
26
+                "nova_object.data": {
27
+                    "description": null,
28
+                    "disabled": false,
29
+                    "ephemeral_gb": 0,
30
+                    "extra_specs": {
31
+                        "hw:watchdog_action": "disabled"
32
+                    },
33
+                    "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
34
+                    "is_public": true,
35
+                    "memory_mb": 512,
36
+                    "name": "test_flavor",
37
+                    "projects": null,
38
+                    "root_gb": 1,
39
+                    "rxtx_factor": 1.0,
40
+                    "swap": 0,
41
+                    "vcpu_weight": 0,
42
+                    "vcpus": 1
43
+                },
44
+                "nova_object.name": "FlavorPayload",
45
+                "nova_object.namespace": "nova",
46
+                "nova_object.version": "1.4"
47
+            },
48
+            "host": "compute",
49
+            "host_name": "some-server",
50
+            "image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
51
+            "ip_addresses": [],
52
+            "kernel_id": "",
53
+            "key_name": "my-key",
54
+            "launched_at": null,
55
+            "locked": false,
56
+            "metadata": {},
57
+            "node": "fake-mini",
58
+            "old_display_name": null,
59
+            "os_type": null,
60
+            "power_state": "pending",
61
+            "progress": 0,
62
+            "ramdisk_id": "",
63
+            "request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d",
64
+            "reservation_id": "r-npxv0e40",
65
+            "state": "active",
66
+            "state_update": {
67
+                "nova_object.data": {
68
+                    "new_task_state": null,
69
+                    "old_state": "building",
70
+                    "old_task_state": null,
71
+                    "state": "building"
72
+                },
73
+                "nova_object.name": "InstanceStateUpdatePayload",
74
+                "nova_object.namespace": "nova",
75
+                "nova_object.version": "1.0"
76
+            },
77
+            "tags": [],
78
+            "task_state": "scheduling",
79
+            "tenant_id": "6f70656e737461636b20342065766572",
80
+            "terminated_at": null,
81
+            "updated_at": null,
82
+            "user_id": "fake",
83
+            "uuid": "178b0921-8f85-4257-88b6-2e743b5a975c"
84
+        },
85
+        "nova_object.name": "InstanceUpdatePayload",
86
+        "nova_object.namespace": "nova",
87
+        "nova_object.version": "1.8"
88
+    },
89
+    "priority": "INFO",
90
+    "publisher_id": "nova-compute:fake-mini"
91
+}

+ 94
- 0
novajoin/tests/unit/notifications/test_formats.py View File

@@ -0,0 +1,94 @@
1
+# Copyright 2018 Red Hat, Inc.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+import mock
16
+import os
17
+
18
+from oslo_messaging.notify import dispatcher as notify_dispatcher
19
+from oslo_messaging.notify import NotificationResult
20
+from oslo_serialization import jsonutils
21
+
22
+from novajoin import notifications
23
+from novajoin import test
24
+
25
+
26
+SAMPLES_DIR = os.path.dirname(os.path.realpath(__file__))
27
+
28
+
29
+class NotificationFormatsTest(test.TestCase):
30
+
31
+    def _get_event(self, filename):
32
+        json_sample = os.path.join(SAMPLES_DIR, filename)
33
+        with open(json_sample) as sample_file:
34
+            return jsonutils.loads(sample_file.read())
35
+
36
+    def _run_dispatcher(self, event):
37
+        dispatcher = notify_dispatcher.NotificationDispatcher(
38
+            [notifications.VersionedNotificationEndpoint()], None)
39
+        return dispatcher.dispatch(mock.Mock(ctxt={}, message=event))
40
+
41
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
42
+                '._generate_hostname')
43
+    def test_instance_create(self, generate_hostname):
44
+        event = self._get_event('instance.create.end.json')
45
+        result = self._run_dispatcher(event)
46
+        self.assertEqual(result, NotificationResult.HANDLED)
47
+
48
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
49
+                '._generate_hostname')
50
+    def test_instance_create_wrong_version(self, generate_hostname):
51
+        event = self._get_event('instance.create.end.json')
52
+        event['payload']['nova_object.version'] = '999.999'
53
+        result = self._run_dispatcher(event)
54
+        self.assertEqual(result, NotificationResult.REQUEUE)
55
+
56
+    @mock.patch('novajoin.notifications.glanceclient')
57
+    @mock.patch('novajoin.notifications.ipaclient')
58
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
59
+                '._generate_hostname')
60
+    def test_instance_update(self, glanceclient, ipaclient, gen_hostname):
61
+        event = self._get_event('instance.update.json')
62
+        result = self._run_dispatcher(event)
63
+        self.assertEqual(result, NotificationResult.HANDLED)
64
+
65
+    @mock.patch('novajoin.notifications.glanceclient')
66
+    @mock.patch('novajoin.notifications.ipaclient')
67
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
68
+                '._generate_hostname')
69
+    def test_instance_delete(self, glanceclient, ipaclient, gen_hostname):
70
+        event = self._get_event('instance.delete.end.json')
71
+        result = self._run_dispatcher(event)
72
+        self.assertEqual(result, NotificationResult.HANDLED)
73
+
74
+    @mock.patch('novajoin.notifications.neutronclient')
75
+    @mock.patch('novajoin.notifications.novaclient')
76
+    @mock.patch('novajoin.notifications.ipaclient')
77
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
78
+                '._generate_hostname')
79
+    def test_floatingip_associate(self, neutronclient, novaclient,
80
+                                  ipaclient, generate_hostname):
81
+        event = self._get_event('floatingip.update.end_associate.json')
82
+        result = self._run_dispatcher(event)
83
+        self.assertEqual(result, NotificationResult.HANDLED)
84
+
85
+    @mock.patch('novajoin.notifications.neutronclient')
86
+    @mock.patch('novajoin.notifications.novaclient')
87
+    @mock.patch('novajoin.notifications.ipaclient')
88
+    @mock.patch('novajoin.notifications.NotificationEndpoint'
89
+                '._generate_hostname')
90
+    def test_floatingip_disassociate(self, neutronclient, novaclient,
91
+                                     ipaclient, generate_hostname):
92
+        event = self._get_event('floatingip.update.end_disassociate.json')
93
+        result = self._run_dispatcher(event)
94
+        self.assertEqual(result, NotificationResult.HANDLED)

+ 24
- 6
scripts/novajoin-install View File

@@ -138,6 +138,12 @@ def install(opts):
138 138
     config.set('keystone_authtoken', 'project_domain_name', 'default')
139 139
     config.set('keystone_authtoken', 'user_domain_id', 'default')
140 140
 
141
+    if opts.notification_format == 'versioned':
142
+        config.set('DEFAULT', 'notification_format', 'versioned')
143
+    else:
144
+        config.set('DEFAULT', 'notification_format', 'unversioned')
145
+
146
+    config.set('DEFAULT', 'notifications_topic', 'novajoin_notifications')
141 147
 
142 148
     with open(opts.novajoin_conf, 'w') as f:
143 149
         config.write(f)
@@ -193,13 +199,21 @@ def install(opts):
193 199
                    'notify_on_state_change',
194 200
                    'vm_state')
195 201
 
196
-        config.set('notifications',
197
-                   'notification_format',
198
-                   'unversioned')
202
+        if opts.notification_format == 'versioned':
203
+            config.set('notifications',
204
+                       'notification_format',
205
+                       'versioned')
206
+            config.set('notifications',
207
+                       'versioned_notifications_topics',
208
+                       'versioned_notifications,novajoin_notifications')
209
+        else:
210
+            config.set('notifications',
211
+                       'notification_format',
212
+                       'unversioned')
213
+            config.set('oslo_messaging_notifications',
214
+                       'topics',
215
+                       'notifications,novajoin_notifications')
199 216
 
200
-        config.set('oslo_messaging_notifications',
201
-                   'topics',
202
-                   'notifications,novajoin_notifications')
203 217
         with open(conf, 'w') as f:
204 218
             config.write(f)
205 219
 
@@ -259,6 +273,10 @@ def parse_args():
259 273
     parser.add_argument('--novajoin-conf', dest='novajoin_conf',
260 274
                         help='novajoin configuration file',
261 275
                         default=JOINCONF)
276
+    parser.add_argument('--notification-format', dest='notification_format',
277
+                        help='The format of notifications to emit and read.',
278
+                        choices=['versioned', 'unversioned'],
279
+                        default='versioned')
262 280
     parser = configure_ipa.ipa_options(parser)
263 281
 
264 282
     opts = parser.parse_args()

Loading…
Cancel
Save