From a96bc0642b896a673c640eec2c4935634d0e8080 Mon Sep 17 00:00:00 2001 From: Ifat Afek Date: Thu, 6 Dec 2018 17:48:38 +0000 Subject: [PATCH] Support Nova versioned notifications In Stein release, Nova will deprecate their legacy (unversioned) notifications that were used by Vitrage. Vitrage should support both the versioned notifications and the legacy notifications. In another change, we will switch the default behavior to prefer the versioned notifications (in case both are sent by Nova). More info: http://lists.openstack.org/pipermail/openstack-dev/2018-September/134721.html Change-Id: Ia5fece6e07766b68af316034736bd7a51b84f38b Story: 2004052 Task: 27058 --- ...sioned-notifications-3c5ff450b9fe69f0.yaml | 3 + vitrage/datasources/nova/instance/driver.py | 117 ++++++++++++--- .../nova/instance/field_extractor.py | 92 ++++++++++++ .../datasources/nova/instance/transformer.py | 79 ++++++---- vitrage/os_clients.py | 9 +- vitrage/tests/functional/base.py | 6 +- vitrage/tests/mocks/mock_driver.py | 10 +- vitrage/tests/mocks/trace_generator.py | 38 ++++- ...=> driver_inst_update_legacy_dynamic.json} | 0 .../driver_inst_update_versioned_dynamic.json | 103 +++++++++++++ ...r.py => base_nova_instance_transformer.py} | 142 ++++-------------- ...stance_transformer_legacy_notifications.py | 62 ++++++++ ...st_instance_transformer_snapshot_events.py | 130 ++++++++++++++++ ...nce_transformer_versioned_notifications.py | 62 ++++++++ .../nova/test_nova_instance_driver.py | 98 ++++++++++++ vitrage/tests/unit/entity_graph/base.py | 16 +- .../entity_graph/processor/test_processor.py | 4 +- 17 files changed, 788 insertions(+), 183 deletions(-) create mode 100644 releasenotes/notes/nova-versioned-notifications-3c5ff450b9fe69f0.yaml create mode 100644 vitrage/datasources/nova/instance/field_extractor.py rename vitrage/tests/resources/mock_configurations/driver/{driver_inst_update_dynamic.json => driver_inst_update_legacy_dynamic.json} (100%) create mode 100644 vitrage/tests/resources/mock_configurations/driver/driver_inst_update_versioned_dynamic.json rename vitrage/tests/unit/datasources/nova/{test_nova_instance_transformer.py => base_nova_instance_transformer.py} (61%) create mode 100644 vitrage/tests/unit/datasources/nova/test_instance_transformer_legacy_notifications.py create mode 100644 vitrage/tests/unit/datasources/nova/test_instance_transformer_snapshot_events.py create mode 100644 vitrage/tests/unit/datasources/nova/test_instance_transformer_versioned_notifications.py create mode 100644 vitrage/tests/unit/datasources/nova/test_nova_instance_driver.py diff --git a/releasenotes/notes/nova-versioned-notifications-3c5ff450b9fe69f0.yaml b/releasenotes/notes/nova-versioned-notifications-3c5ff450b9fe69f0.yaml new file mode 100644 index 000000000..1c42d1d52 --- /dev/null +++ b/releasenotes/notes/nova-versioned-notifications-3c5ff450b9fe69f0.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added support for Nova versioned notifications. diff --git a/vitrage/datasources/nova/instance/driver.py b/vitrage/datasources/nova/instance/driver.py index 142a3c8d4..8a507826a 100644 --- a/vitrage/datasources/nova/instance/driver.py +++ b/vitrage/datasources/nova/instance/driver.py @@ -19,8 +19,87 @@ from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.datasources.nova.nova_driver_base import NovaDriverBase +# versioned notifications +VERSIONED_NOTIFICATIONS = { + 'instance.create.end', + 'instance.create.error', + 'instance.delete.end', + 'instance.delete.start', + 'instance.evacuate', + 'instance.interface_attach.end', + 'instance.interface_attach.error', + 'instance.interface_detach.end', + 'instance.live_migration_abort.end', + 'instance.live_migration_force_complete.end', + 'instance.live_migration_post.end', + 'instance.live_migration_post_dest.end', + 'instance.live_migration_rollback.end', + 'instance.live_migration_rollback_dest.end', + 'instance.lock', + 'instance.pause.end', + 'instance.power_off.end', + 'instance.power_on.end', + 'instance.reboot.end', + 'instance.reboot.error', + 'instance.rebuild.end', + 'instance.rebuild.error', + 'instance.rescue.end', + 'instance.resize.end', + 'instance.resize.error', + 'instance.resize_confirm.end', + 'instance.resize_finish.end', + 'instance.resize_prep.end', + 'instance.resize_revert.end', + 'instance.restore.end', + 'instance.resume.end', + 'instance.shelve.end', + 'instance.shelve_offload.end', + 'instance.shutdown.end', + 'instance.soft_delete.end', + 'instance.snapshot.end', + 'instance.suspend.end', + 'instance.unlock', + 'instance.unpause.end', + 'instance.unrescue.end', + 'instance.unshelve.end', + 'instance.update', + 'instance.volume_attach.end', + 'instance.volume_attach.error', + 'instance.volume_detach.end', + 'instance.volume_swap.end', + 'instance.volume_swap.error', +} + +# legacy (unversioned) notifications +LEGACY_NOTIFICATIONS = { + 'compute.instance.create.error', + 'compute.instance.create.end', + 'compute.instance.delete.start', + 'compute.instance.delete.end', + 'compute.instance.finish_resize.end', + 'compute.instance.live_migration.post.dest.end', + 'compute.instance.live_migration._post.end', + 'compute.instance.power_off.end', + 'compute.instance.power_on.end', + 'compute.instance.reboot.end', + 'compute.instance.rebuild.end', + 'compute.instance.resize.end', + 'compute.instance.resize.revert.end', + 'compute.instance.resume.end', + 'compute.instance.shutdown.end', + 'compute.instance.suspend.end', + 'compute.instance.volume.attach', + 'compute.instance.volume.detach', + 'compute.instance.pause.end', + 'compute.instance.unpause.end' +} + + class InstanceDriver(NovaDriverBase): + def __init__(self, conf): + super(InstanceDriver, self).__init__(conf) + @staticmethod def extract_events(instances): events = [instance.__dict__ for instance in instances] @@ -38,11 +117,19 @@ class InstanceDriver(NovaDriverBase): *self.properties_to_filter_out()) def enrich_event(self, event, event_type): - event[DSProps.EVENT_TYPE] = event_type + use_versioned = self.conf.use_nova_versioned_notifications - return InstanceDriver.make_pickleable([event], - NOVA_INSTANCE_DATASOURCE, - DatasourceAction.UPDATE)[0] + # Send to the processor only events of the matching types. Nova may + # send both versioned and legacy notifications, and we don't want to + # handle a similar event twice. + if (use_versioned and event_type in VERSIONED_NOTIFICATIONS) or \ + ((not use_versioned) and event_type in LEGACY_NOTIFICATIONS): + event[DSProps.EVENT_TYPE] = event_type + return InstanceDriver.make_pickleable([event], + NOVA_INSTANCE_DATASOURCE, + DatasourceAction.UPDATE)[0] + + return [] @staticmethod def properties_to_filter_out(): @@ -50,27 +137,7 @@ class InstanceDriver(NovaDriverBase): @staticmethod def get_event_types(): - # Add event_types to receive notifications about - return ['compute.instance.create.error', - 'compute.instance.create.end', - 'compute.instance.delete.start', - 'compute.instance.delete.end', - 'compute.instance.finish_resize.end', - 'compute.instance.live_migration.post.dest.end', - 'compute.instance.live_migration._post.end', - 'compute.instance.power_off.end', - 'compute.instance.power_on.end', - 'compute.instance.reboot.end', - 'compute.instance.rebuild.end', - 'compute.instance.resize.end', - 'compute.instance.resize.revert.end', - 'compute.instance.resume.end', - 'compute.instance.shutdown.end', - 'compute.instance.suspend.end', - 'compute.instance.volume.attach', - 'compute.instance.volume.detach', - 'compute.instance.pause.end', - 'compute.instance.unpause.end'] + return list(VERSIONED_NOTIFICATIONS | LEGACY_NOTIFICATIONS) @staticmethod def should_delete_outdated_entities(): diff --git a/vitrage/datasources/nova/instance/field_extractor.py b/vitrage/datasources/nova/instance/field_extractor.py new file mode 100644 index 000000000..e204054ff --- /dev/null +++ b/vitrage/datasources/nova/instance/field_extractor.py @@ -0,0 +1,92 @@ +# Copyright 2018 - Nokia +# +# 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 abc +from vitrage.datasources.transformer_base import extract_field_value + + +class EventFieldExtractor(object): + """Extract field values from a Nova instance event""" + @abc.abstractmethod + def name(self, event): + pass + + @abc.abstractmethod + def entity_id(self, event): + pass + + @abc.abstractmethod + def state(self, event): + pass + + @abc.abstractmethod + def host(self, event): + pass + + @abc.abstractmethod + def tenant_id(self, event): + pass + + +class SnapshotEventFieldExtractor(EventFieldExtractor): + """Extract field values from an event generated by nova.list API call""" + def name(self, event): + return extract_field_value(event, 'name') + + def entity_id(self, event): + return extract_field_value(event, 'id') + + def state(self, event): + return extract_field_value(event, 'status') + + def host(self, event): + return extract_field_value(event, 'OS-EXT-SRV-ATTR:host') + + def tenant_id(self, event): + return extract_field_value(event, 'tenant_id') + + +class LegacyNotificationFieldExtractor(EventFieldExtractor): + """Extract field values from a Nova legacy notification""" + def name(self, event): + return extract_field_value(event, 'hostname') + + def entity_id(self, event): + return extract_field_value(event, 'instance_id') + + def state(self, event): + return extract_field_value(event, 'state') + + def host(self, event): + return extract_field_value(event, 'host') + + def tenant_id(self, event): + return extract_field_value(event, 'tenant_id') + + +class VersionedNotificationFieldExtractor(EventFieldExtractor): + """Extract field values from a Nova versioned notification""" + def name(self, event): + return extract_field_value(event, 'nova_object.data', 'host_name') + + def entity_id(self, event): + return extract_field_value(event, 'nova_object.data', 'uuid') + + def state(self, event): + return extract_field_value(event, 'nova_object.data', 'state') + + def host(self, event): + return extract_field_value(event, 'nova_object.data', 'host') + + def tenant_id(self, event): + return extract_field_value(event, 'nova_object.data', 'tenant_id') diff --git a/vitrage/datasources/nova/instance/transformer.py b/vitrage/datasources/nova/instance/transformer.py index e4704302e..2db4d5828 100644 --- a/vitrage/datasources/nova/instance/transformer.py +++ b/vitrage/datasources/nova/instance/transformer.py @@ -19,6 +19,12 @@ from vitrage.common.constants import EntityCategory from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE +from vitrage.datasources.nova.instance.field_extractor import \ + LegacyNotificationFieldExtractor +from vitrage.datasources.nova.instance.field_extractor import \ + SnapshotEventFieldExtractor +from vitrage.datasources.nova.instance.field_extractor import \ + VersionedNotificationFieldExtractor from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.datasources.resource_transformer_base import \ ResourceTransformerBase @@ -31,38 +37,38 @@ LOG = logging.getLogger(__name__) class InstanceTransformer(ResourceTransformerBase): + snapshot_extractor = SnapshotEventFieldExtractor() + legacy_notifications_extractor = LegacyNotificationFieldExtractor() + versioned_notifications_extractor = VersionedNotificationFieldExtractor() + # graph actions which need to refer them differently GRAPH_ACTION_MAPPING = { 'compute.instance.delete.end': GraphAction.DELETE_ENTITY, + 'instance.delete.end': GraphAction.DELETE_ENTITY, } def __init__(self, transformers, conf): super(InstanceTransformer, self).__init__(transformers, conf) def _create_snapshot_entity_vertex(self, entity_event): - - name = extract_field_value(entity_event, 'name') - entity_id = extract_field_value(entity_event, 'id') - state = extract_field_value(entity_event, 'status') - host = extract_field_value(entity_event, 'OS-EXT-SRV-ATTR:host') - - return self._create_vertex(entity_event, name, entity_id, state, host) + LOG.debug('got snapshot') + return self._create_vertex(entity_event) def _create_update_entity_vertex(self, entity_event): + LOG.debug('got event: %s', entity_event[DSProps.EVENT_TYPE]) + return self._create_vertex(entity_event) - name = extract_field_value(entity_event, 'hostname') - entity_id = extract_field_value(entity_event, 'instance_id') - state = extract_field_value(entity_event, 'state') - host = extract_field_value(entity_event, 'host') - - return self._create_vertex(entity_event, name, entity_id, state, host) - - def _create_vertex(self, entity_event, name, entity_id, state, host): + def _create_vertex(self, entity_event): + field_extractor = self._get_field_extractor(entity_event) + if not field_extractor: + LOG.warning('Failed to identify event type for event: %s', + entity_event) + return metadata = { - VProps.NAME: name, - VProps.PROJECT_ID: entity_event.get('tenant_id', None), - 'host_id': host + VProps.NAME: field_extractor.name(entity_event), + VProps.PROJECT_ID: field_extractor.tenant_id(entity_event), + 'host_id': field_extractor.host(entity_event) } vitrage_sample_timestamp = entity_event[DSProps.SAMPLE_DATE] @@ -78,21 +84,26 @@ class InstanceTransformer(ResourceTransformerBase): vitrage_category=EntityCategory.RESOURCE, vitrage_type=NOVA_INSTANCE_DATASOURCE, vitrage_sample_timestamp=vitrage_sample_timestamp, - entity_id=entity_id, - entity_state=state, + entity_id=field_extractor.entity_id(entity_event), + entity_state=field_extractor.state(entity_event), update_timestamp=update_timestamp, metadata=metadata) def _create_snapshot_neighbors(self, entity_event): - return self._create_instance_neighbors(entity_event, - 'OS-EXT-SRV-ATTR:host') + return self._create_instance_neighbors(entity_event) def _create_update_neighbors(self, entity_event): - return self._create_instance_neighbors(entity_event, - 'host') + return self._create_instance_neighbors(entity_event) + + def _create_instance_neighbors(self, entity_event): + field_extractor = self._get_field_extractor(entity_event) + if not field_extractor: + LOG.warning('Failed to identify event type for event: %s', + entity_event) + return [] + + host_name = field_extractor.host(entity_event) - def _create_instance_neighbors(self, entity_event, host_property_name): - host_name = entity_event.get(host_property_name) host_neighbor = self._create_neighbor(entity_event, host_name, NOVA_HOST_DATASOURCE, @@ -104,14 +115,22 @@ class InstanceTransformer(ResourceTransformerBase): def _create_entity_key(self, event): LOG.debug('Creating key for instance event: %s', str(event)) - instance_id = 'instance_id' if tbase.is_update_event(event) else 'id' - key_fields = self._key_values(NOVA_INSTANCE_DATASOURCE, - extract_field_value(event, - instance_id)) + instance_id = self._get_field_extractor(event).entity_id(event) + key_fields = self._key_values(NOVA_INSTANCE_DATASOURCE, instance_id) key = tbase.build_key(key_fields) + LOG.debug('Created key: %s', key) return key def get_vitrage_type(self): return NOVA_INSTANCE_DATASOURCE + + def _get_field_extractor(self, event): + """Return an object that extracts the field values from the event""" + if tbase.is_update_event(event): + return self.versioned_notifications_extractor if \ + self.conf.use_nova_versioned_notifications is True else \ + self.legacy_notifications_extractor + else: + return self.snapshot_extractor diff --git a/vitrage/os_clients.py b/vitrage/os_clients.py index d339d09ce..9a4160b18 100644 --- a/vitrage/os_clients.py +++ b/vitrage/os_clients.py @@ -28,7 +28,14 @@ OPTS = [ cfg.StrOpt('glance_version', default='2', help='Glance version'), cfg.StrOpt('heat_version', default='1', help='Heat version'), cfg.StrOpt('mistral_version', default='2', help='Mistral version'), - cfg.StrOpt('gnocchi_version', default='1', help='Gnocchi version') + cfg.StrOpt('gnocchi_version', default='1', help='Gnocchi version'), + cfg.BoolOpt('use_nova_versioned_notifications', + default=False, + help='Indicates whether to use Nova versioned notifications.' + 'The default is True. If False, the deprecated Nova ' + 'legacy notifications will be used.' + 'This flag must be set to False if notification_format ' + 'is set to "unversioned" in nova.conf'), ] _client_modules = { diff --git a/vitrage/tests/functional/base.py b/vitrage/tests/functional/base.py index fca317d80..359139356 100644 --- a/vitrage/tests/functional/base.py +++ b/vitrage/tests/functional/base.py @@ -45,9 +45,9 @@ class TestFunctionalBase(TestEntityGraphUnitBase): snap_vals={DSProps.DATASOURCE_ACTION: DatasourceAction.INIT_SNAPSHOT}) gen_list += mock_driver.simple_instance_generators( - self.NUM_HOSTS, - self.NUM_INSTANCES, - self.NUM_INSTANCES, + host_num=self.NUM_HOSTS, + vm_num=self.NUM_INSTANCES, + snapshot_events=self.NUM_INSTANCES, snap_vals={DSProps.DATASOURCE_ACTION: DatasourceAction.INIT_SNAPSHOT}) return mock_driver.generate_sequential_events_list(gen_list) diff --git a/vitrage/tests/mocks/mock_driver.py b/vitrage/tests/mocks/mock_driver.py index c6b9552c2..966379b19 100644 --- a/vitrage/tests/mocks/mock_driver.py +++ b/vitrage/tests/mocks/mock_driver.py @@ -88,6 +88,7 @@ def generate_sequential_events_list(generator_spec_list): def simple_instance_generators(host_num, vm_num, snapshot_events=0, update_events=0, + use_nova_versioned_format=True, snap_vals=None, update_vals=None): """A function for returning vm event generators. @@ -98,6 +99,8 @@ def simple_instance_generators(host_num, vm_num, :param vm_num: number of vms :param snapshot_events: number of snapshot events per instance :param update_events: number of update events per instance + :param use_nova_versioned_format: use the format of Nova versioned + notifications for the update events :param snap_vals: preset vals for ALL snapshot events :param update_vals: preset vals for ALL update events :return: generators for vm_num vms as specified @@ -118,9 +121,13 @@ def simple_instance_generators(host_num, vm_num, tg.NUM_EVENTS: snapshot_events } ) + + dynamic_info = tg.DRIVER_INST_UPDATE_VERSIONED_D \ + if use_nova_versioned_format else tg.DRIVER_INST_UPDATE_LEGACY_D + if update_events: test_entity_spec_list.append( - {tg.DYNAMIC_INFO_FKEY: tg.DRIVER_INST_UPDATE_D, + {tg.DYNAMIC_INFO_FKEY: dynamic_info, tg.STATIC_INFO_FKEY: None, tg.EXTERNAL_INFO_KEY: update_vals, tg.MAPPING_KEY: mapping, @@ -128,6 +135,7 @@ def simple_instance_generators(host_num, vm_num, tg.NUM_EVENTS: update_events } ) + return tg.get_trace_generators(test_entity_spec_list) diff --git a/vitrage/tests/mocks/trace_generator.py b/vitrage/tests/mocks/trace_generator.py index 63ee34633..9da2d3132 100644 --- a/vitrage/tests/mocks/trace_generator.py +++ b/vitrage/tests/mocks/trace_generator.py @@ -53,7 +53,8 @@ DRIVER_COLLECTD_UPDATE_D = 'driver_collectd_update_dynamic.json' DRIVER_HOST_SNAPSHOT_D = 'driver_host_snapshot_dynamic.json' DRIVER_INST_SNAPSHOT_D = 'driver_inst_snapshot_dynamic.json' DRIVER_INST_SNAPSHOT_S = 'driver_inst_snapshot_static.json' -DRIVER_INST_UPDATE_D = 'driver_inst_update_dynamic.json' +DRIVER_INST_UPDATE_LEGACY_D = 'driver_inst_update_legacy_dynamic.json' +DRIVER_INST_UPDATE_VERSIONED_D = 'driver_inst_update_versioned_dynamic.json' DRIVER_NAGIOS_SNAPSHOT_D = 'driver_nagios_snapshot_dynamic.json' DRIVER_NAGIOS_SNAPSHOT_S = 'driver_nagios_snapshot_static.json' DRIVER_PROMETHEUS_UPDATE_D = 'driver_prometheus_update_dynamic.json' @@ -123,7 +124,9 @@ class EventTraceGenerator(object): DRIVER_COLLECTD_UPDATE_D: _get_simple_update_driver_values, DRIVER_KUBE_SNAPSHOT_D: _get_k8s_node_snapshot_driver_values, DRIVER_INST_SNAPSHOT_D: _get_vm_snapshot_driver_values, - DRIVER_INST_UPDATE_D: _get_vm_update_driver_values, + DRIVER_INST_UPDATE_LEGACY_D: _get_vm_update_legacy_driver_values, + DRIVER_INST_UPDATE_VERSIONED_D: + _get_vm_update_versioned_driver_values, DRIVER_HOST_SNAPSHOT_D: _get_host_snapshot_driver_values, DRIVER_ZONE_SNAPSHOT_D: _get_zone_snapshot_driver_values, DRIVER_VOLUME_SNAPSHOT_D: _get_volume_snapshot_driver_values, @@ -487,7 +490,7 @@ def _get_trans_vm_snapshot_values(spec): return static_values -def _get_vm_update_driver_values(spec): +def _get_vm_update_legacy_driver_values(spec): """Generates the static driver values for each vm, for updates. :param spec: specification of event generation. @@ -511,6 +514,35 @@ def _get_vm_update_driver_values(spec): return static_values +def _get_vm_update_versioned_driver_values(spec): + """Generates the static driver values for each vm, for updates. + + :param spec: specification of event generation. + :type spec: dict + :return: list of static driver values for each vm updates. + :rtype: list + """ + + vm_host_mapping = spec[MAPPING_KEY] + static_info = None + if spec[STATIC_INFO_FKEY] is not None: + static_info = utils.load_specs(spec[STATIC_INFO_FKEY]) + static_values = [] + for vm_name, host_name in vm_host_mapping: + mapping = { + 'nova_object.data': { + 'host': host_name, + 'display_name': vm_name + } + + } + static_values.append(combine_data( + static_info, mapping, spec.get(EXTERNAL_INFO_KEY, None) + )) + + return static_values + + def _get_static_snapshot_driver_values(spec): """Generates the static driver values for static datasource. diff --git a/vitrage/tests/resources/mock_configurations/driver/driver_inst_update_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_inst_update_legacy_dynamic.json similarity index 100% rename from vitrage/tests/resources/mock_configurations/driver/driver_inst_update_dynamic.json rename to vitrage/tests/resources/mock_configurations/driver/driver_inst_update_legacy_dynamic.json diff --git a/vitrage/tests/resources/mock_configurations/driver/driver_inst_update_versioned_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_inst_update_versioned_dynamic.json new file mode 100644 index 000000000..8e0366ea6 --- /dev/null +++ b/vitrage/tests/resources/mock_configurations/driver/driver_inst_update_versioned_dynamic.json @@ -0,0 +1,103 @@ +{ + "vitrage_datasource_action": "update", + "vitrage_sample_date": "2015-12-01T12:46:41Z", + "vitrage_event_type": "instance.pause.end|instance.delete.end|instance.create.end", + "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" +} diff --git a/vitrage/tests/unit/datasources/nova/test_nova_instance_transformer.py b/vitrage/tests/unit/datasources/nova/base_nova_instance_transformer.py similarity index 61% rename from vitrage/tests/unit/datasources/nova/test_nova_instance_transformer.py rename to vitrage/tests/unit/datasources/nova/base_nova_instance_transformer.py index 276d2373a..c597c7bed 100644 --- a/vitrage/tests/unit/datasources/nova/test_nova_instance_transformer.py +++ b/vitrage/tests/unit/datasources/nova/base_nova_instance_transformer.py @@ -12,13 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. +import abc import datetime from oslo_config import cfg from oslo_log import log as logging from testtools import matchers -from vitrage.common.constants import DatasourceAction from vitrage.common.constants import DatasourceOpts as DSOpts from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import EdgeLabel @@ -39,7 +39,7 @@ LOG = logging.getLogger(__name__) # noinspection PyProtectedMember -class NovaInstanceTransformerTest(base.BaseTest): +class BaseNovaInstanceTransformerTest(base.BaseTest): OPTS = [ cfg.StrOpt(DSOpts.UPDATE_METHOD, @@ -49,16 +49,17 @@ class NovaInstanceTransformerTest(base.BaseTest): # noinspection PyAttributeOutsideInit,PyPep8Naming @classmethod def setUpClass(cls): - super(NovaInstanceTransformerTest, cls).setUpClass() + super(BaseNovaInstanceTransformerTest, cls).setUpClass() cls.transformers = {} cls.conf = cfg.ConfigOpts() cls.conf.register_opts(cls.OPTS, group=NOVA_INSTANCE_DATASOURCE) + cls.conf.register_opts(cls._get_default_group_opts()) cls.transformers[NOVA_HOST_DATASOURCE] = HostTransformer( cls.transformers, cls.conf) cls.transformers[NOVA_INSTANCE_DATASOURCE] = \ InstanceTransformer(cls.transformers, cls.conf) - def test_create_placeholder_vertex(self): + def _test_create_placeholder_vertex(self): LOG.debug('Test create placeholder vertex') # Tests setup @@ -100,53 +101,14 @@ class NovaInstanceTransformerTest(base.BaseTest): vitrage_is_placeholder = placeholder.get(VProps.VITRAGE_IS_PLACEHOLDER) self.assertTrue(vitrage_is_placeholder) - def test_snapshot_event_transform(self): - LOG.debug('Test tactual transform action for ' - 'snapshot and snapshot init events') - - # Test setup - spec_list = mock_sync.simple_instance_generators(host_num=1, - vm_num=1, - snapshot_events=10, - update_events=0) - instance_events = mock_sync.generate_random_events_list(spec_list) - + def _test_update_event_transform(self, instance_events): for event in instance_events: # Test action - wrapper = self.transformers[NOVA_INSTANCE_DATASOURCE].transform( - event) + transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] + wrapper = transformer.transform(event) # Test assertions - self._validate_vertex_props(wrapper.vertex, event) - - self.assertThat(wrapper.neighbors, matchers.HasLength(1), - 'Instance has only one host neighbor') - host_neighbor = wrapper.neighbors[0] - self._validate_host_neighbor(host_neighbor, event) - - datasource_action = event[DSProps.DATASOURCE_ACTION] - if datasource_action == DatasourceAction.INIT_SNAPSHOT: - self.assertEqual(GraphAction.CREATE_ENTITY, wrapper.action) - elif datasource_action == DatasourceAction.SNAPSHOT: - self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) - - def test_update_event_transform(self): - LOG.debug('Test tactual transform action for update events') - - # Test setup - spec_list = mock_sync.simple_instance_generators(host_num=1, - vm_num=1, - snapshot_events=0, - update_events=10) - instance_events = mock_sync.generate_random_events_list(spec_list) - - for event in instance_events: - # Test action - wrapper = self.transformers[NOVA_INSTANCE_DATASOURCE].transform( - event) - - # Test assertions - self._validate_vertex_props(wrapper.vertex, event) + self._validate_vertex_props(transformer, wrapper.vertex, event) # Validate the neighbors: only one valid host neighbor neighbors = wrapper.neighbors @@ -161,16 +123,12 @@ class NovaInstanceTransformerTest(base.BaseTest): else: self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) - def _validate_vertex_props(self, vertex, event): + def _validate_vertex_props(self, transformer, vertex, event): self.assertThat(vertex.properties, matchers.HasLength(14)) - is_update_event = tbase.is_update_event(event) - - extract_value = tbase.extract_field_value - - instance_id = 'instance_id' if is_update_event else 'id' - expected_id = extract_value(event, instance_id) + field_extractor = transformer._get_field_extractor(event) + expected_id = field_extractor.entity_id(event) observed_id = vertex[VProps.ID] self.assertEqual(expected_id, observed_id) @@ -181,12 +139,11 @@ class NovaInstanceTransformerTest(base.BaseTest): self.assertEqual(NOVA_INSTANCE_DATASOURCE, vertex[VProps.VITRAGE_TYPE]) - expected_project = extract_value(event, 'tenant_id') + expected_project = field_extractor.tenant_id(event) observed_project = vertex[VProps.PROJECT_ID] self.assertEqual(expected_project, observed_project) - state = 'state' if is_update_event else 'status' - expected_state = extract_value(event, state) + expected_state = field_extractor.state(event) observed_state = vertex[VProps.STATE] self.assertEqual(expected_state, observed_state) @@ -194,8 +151,7 @@ class NovaInstanceTransformerTest(base.BaseTest): observed_timestamp = vertex[VProps.VITRAGE_SAMPLE_TIMESTAMP] self.assertEqual(expected_timestamp, observed_timestamp) - name = 'hostname' if is_update_event else 'name' - expected_name = extract_value(event, name) + expected_name = field_extractor.name(event) observed_name = vertex[VProps.NAME] self.assertEqual(expected_name, observed_name) @@ -207,14 +163,13 @@ class NovaInstanceTransformerTest(base.BaseTest): def _validate_host_neighbor(self, h_neighbor, event): - it = self.transformers[NOVA_INSTANCE_DATASOURCE] + inst_transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] + field_extractor = inst_transformer._get_field_extractor(event) - name = 'host' if tbase.is_update_event(event) \ - else 'OS-EXT-SRV-ATTR:host' - host_name = tbase.extract_field_value(event, name) + host_name = field_extractor.host(event) time = event[DSProps.SAMPLE_DATE] - ht = self.transformers[NOVA_HOST_DATASOURCE] + host_transformer = self.transformers[NOVA_HOST_DATASOURCE] properties = { VProps.ID: host_name, VProps.VITRAGE_TYPE: NOVA_HOST_DATASOURCE, @@ -222,28 +177,24 @@ class NovaInstanceTransformerTest(base.BaseTest): VProps.VITRAGE_SAMPLE_TIMESTAMP: time } expected_neighbor = \ - ht.create_neighbor_placeholder_vertex(**properties) + host_transformer.create_neighbor_placeholder_vertex(**properties) self.assertEqual(expected_neighbor, h_neighbor.vertex) # Validate neighbor edge edge = h_neighbor.edge - entity_key = it._create_entity_key(event) + entity_key = inst_transformer._create_entity_key(event) entity_uuid = \ TransformerBase.uuid_from_deprecated_vitrage_id(entity_key) self.assertEqual(edge.source_id, h_neighbor.vertex.vertex_id) self.assertEqual(edge.target_id, entity_uuid) self.assertEqual(edge.label, EdgeLabel.CONTAINS) - def test_create_entity_key(self): + def _test_create_entity_key(self): LOG.debug('Test get key from nova instance transformer') # Test setup spec_list = mock_sync.simple_instance_generators( - host_num=1, - vm_num=1, - snapshot_events=1, - update_events=0 - ) + host_num=1, vm_num=1, snapshot_events=1) instance_events = mock_sync.generate_random_events_list(spec_list) instance_transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] @@ -272,7 +223,7 @@ class NovaInstanceTransformerTest(base.BaseTest): self.assertEqual(expected_key, observed_key) - def test_build_instance_key(self): + def _test_build_instance_key(self): LOG.debug('Test build instance key') # Test setup @@ -289,44 +240,7 @@ class NovaInstanceTransformerTest(base.BaseTest): observed_key = tbase.build_key(key_fields) self.assertEqual(expected_key, observed_key) - def test_create_host_neighbor(self): - LOG.debug('Test create host neighbor') - - # Test setup - host_name = 'host123' - vertex_key = 'RESOURCE:nova.instance:instance321' - vertex_id = \ - TransformerBase.uuid_from_deprecated_vitrage_id(vertex_key) - time = datetime.datetime.utcnow() - entity_event = { - '_info': { - 'host_name': host_name - }, - DSProps.DATASOURCE_ACTION: 'SNAPSHOT', - 'id': 'instance321', - DSProps.SAMPLE_DATE: time - } - - # Test action - instance_transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] - neighbor = \ - instance_transformer._create_neighbor(entity_event, - host_name, - NOVA_HOST_DATASOURCE, - EdgeLabel.CONTAINS, - is_entity_source=False) - - # Test assertions - host_vertex_id = \ - TransformerBase.uuid_from_deprecated_vitrage_id( - 'RESOURCE:nova.host:host123') - self.assertEqual(host_vertex_id, neighbor.vertex.vertex_id) - self.assertEqual( - time, - neighbor.vertex.get(VProps.VITRAGE_SAMPLE_TIMESTAMP) - ) - - # test relation edge - self.assertEqual(host_vertex_id, neighbor.edge.source_id) - self.assertEqual(vertex_id, neighbor.edge.target_id) - self.assertEqual(EdgeLabel.CONTAINS, neighbor.edge.label) + @classmethod + @abc.abstractmethod + def _get_default_group_opts(cls): + pass diff --git a/vitrage/tests/unit/datasources/nova/test_instance_transformer_legacy_notifications.py b/vitrage/tests/unit/datasources/nova/test_instance_transformer_legacy_notifications.py new file mode 100644 index 000000000..22113a69e --- /dev/null +++ b/vitrage/tests/unit/datasources/nova/test_instance_transformer_legacy_notifications.py @@ -0,0 +1,62 @@ +# Copyright 2018 - Nokia +# +# 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 oslo_config import cfg +from oslo_log import log as logging + +from vitrage.tests.mocks import mock_driver as mock_sync +from vitrage.tests.unit.datasources.nova.base_nova_instance_transformer \ + import BaseNovaInstanceTransformerTest + +LOG = logging.getLogger(__name__) + + +# noinspection PyProtectedMember +class NovaInstanceTransformerLegacyNotifTest( + BaseNovaInstanceTransformerTest): + + DEFAULT_GROUP_OPTS = [ + cfg.BoolOpt('use_nova_versioned_notifications', + default=False), + ] + + # noinspection PyAttributeOutsideInit,PyPep8Naming + @classmethod + def setUpClass(cls): + super(NovaInstanceTransformerLegacyNotifTest, cls).setUpClass() + + def test_update_event_transform(self): + LOG.debug('Test actual transform action for update events') + + # Test setup + spec_list = mock_sync.simple_instance_generators( + host_num=1, vm_num=1, update_events=10, + use_nova_versioned_format=False + ) + instance_events = mock_sync.generate_random_events_list(spec_list) + + self._test_update_event_transform(instance_events) + + def test_create_placeholder_vertex(self): + self._test_create_placeholder_vertex() + + def test_create_entity_key(self): + self._test_create_entity_key() + + def test_build_instance_key(self): + self._test_build_instance_key() + + @classmethod + def _get_default_group_opts(cls): + return cls.DEFAULT_GROUP_OPTS diff --git a/vitrage/tests/unit/datasources/nova/test_instance_transformer_snapshot_events.py b/vitrage/tests/unit/datasources/nova/test_instance_transformer_snapshot_events.py new file mode 100644 index 000000000..bdbda48c0 --- /dev/null +++ b/vitrage/tests/unit/datasources/nova/test_instance_transformer_snapshot_events.py @@ -0,0 +1,130 @@ +# Copyright 2018 - Nokia +# +# 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 datetime +from oslo_config import cfg +from oslo_log import log as logging +from testtools import matchers + +from vitrage.common.constants import DatasourceAction +from vitrage.common.constants import DatasourceProperties as DSProps +from vitrage.common.constants import EdgeLabel +from vitrage.common.constants import GraphAction +from vitrage.common.constants import VertexProperties as VProps +from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE +from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE +from vitrage.datasources.transformer_base import TransformerBase +from vitrage.tests.mocks import mock_driver as mock_sync +from vitrage.tests.unit.datasources.nova.base_nova_instance_transformer \ + import BaseNovaInstanceTransformerTest + +LOG = logging.getLogger(__name__) + + +# noinspection PyProtectedMember +class NovaInstanceTransformerSnapshotTest( + BaseNovaInstanceTransformerTest): + + DEFAULT_GROUP_OPTS = [ + cfg.BoolOpt('use_nova_versioned_notifications', + default=False), + ] + + # noinspection PyAttributeOutsideInit,PyPep8Naming + @classmethod + def setUpClass(cls): + super(NovaInstanceTransformerSnapshotTest, cls).setUpClass() + + def test_snapshot_event_transform(self): + LOG.debug('Test tactual transform action for ' + 'snapshot and snapshot init events') + + # Test setup + spec_list = mock_sync.simple_instance_generators( + host_num=1, vm_num=1, snapshot_events=10) + instance_events = mock_sync.generate_random_events_list(spec_list) + + for event in instance_events: + # Test action + transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] + wrapper = transformer.transform(event) + + # Test assertions + self._validate_vertex_props(transformer, wrapper.vertex, event) + + self.assertThat(wrapper.neighbors, matchers.HasLength(1), + 'Instance has only one host neighbor') + host_neighbor = wrapper.neighbors[0] + self._validate_host_neighbor(host_neighbor, event) + + datasource_action = event[DSProps.DATASOURCE_ACTION] + if datasource_action == DatasourceAction.INIT_SNAPSHOT: + self.assertEqual(GraphAction.CREATE_ENTITY, wrapper.action) + elif datasource_action == DatasourceAction.SNAPSHOT: + self.assertEqual(GraphAction.UPDATE_ENTITY, wrapper.action) + + def test_create_host_neighbor(self): + LOG.debug('Test create host neighbor') + + # Test setup + host_name = 'host123' + vertex_key = 'RESOURCE:nova.instance:instance321' + vertex_id = \ + TransformerBase.uuid_from_deprecated_vitrage_id(vertex_key) + time = datetime.datetime.utcnow() + entity_event = { + '_info': { + 'host_name': host_name + }, + DSProps.DATASOURCE_ACTION: 'SNAPSHOT', + 'id': 'instance321', + DSProps.SAMPLE_DATE: time + } + + # Test action + instance_transformer = self.transformers[NOVA_INSTANCE_DATASOURCE] + neighbor = \ + instance_transformer._create_neighbor(entity_event, + host_name, + NOVA_HOST_DATASOURCE, + EdgeLabel.CONTAINS, + is_entity_source=False) + + # Test assertions + host_vertex_id = \ + TransformerBase.uuid_from_deprecated_vitrage_id( + 'RESOURCE:nova.host:host123') + self.assertEqual(host_vertex_id, neighbor.vertex.vertex_id) + self.assertEqual( + time, + neighbor.vertex.get(VProps.VITRAGE_SAMPLE_TIMESTAMP) + ) + + # test relation edge + self.assertEqual(host_vertex_id, neighbor.edge.source_id) + self.assertEqual(vertex_id, neighbor.edge.target_id) + self.assertEqual(EdgeLabel.CONTAINS, neighbor.edge.label) + + def test_create_placeholder_vertex(self): + self._test_create_placeholder_vertex() + + def test_create_entity_key(self): + self._test_create_entity_key() + + def test_build_instance_key(self): + self._test_build_instance_key() + + @classmethod + def _get_default_group_opts(cls): + return cls.DEFAULT_GROUP_OPTS diff --git a/vitrage/tests/unit/datasources/nova/test_instance_transformer_versioned_notifications.py b/vitrage/tests/unit/datasources/nova/test_instance_transformer_versioned_notifications.py new file mode 100644 index 000000000..f4523879c --- /dev/null +++ b/vitrage/tests/unit/datasources/nova/test_instance_transformer_versioned_notifications.py @@ -0,0 +1,62 @@ +# Copyright 2018 - Nokia +# +# 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 oslo_config import cfg +from oslo_log import log as logging + +from vitrage.tests.mocks import mock_driver as mock_sync +from vitrage.tests.unit.datasources.nova.base_nova_instance_transformer \ + import BaseNovaInstanceTransformerTest + +LOG = logging.getLogger(__name__) + + +# noinspection PyProtectedMember +class NovaInstanceTransformerVersionedNotifTest( + BaseNovaInstanceTransformerTest): + + DEFAULT_GROUP_OPTS = [ + cfg.BoolOpt('use_nova_versioned_notifications', + default=True), + ] + + # noinspection PyAttributeOutsideInit,PyPep8Naming + @classmethod + def setUpClass(cls): + super(NovaInstanceTransformerVersionedNotifTest, cls).setUpClass() + + def test_update_event_transform(self): + LOG.debug('Test tactual transform action for update events') + + # Test setup + spec_list = mock_sync.simple_instance_generators( + host_num=1, vm_num=1, update_events=10, + use_nova_versioned_format=True + ) + instance_events = mock_sync.generate_random_events_list(spec_list) + + self._test_update_event_transform(instance_events) + + def test_create_placeholder_vertex(self): + self._test_create_placeholder_vertex() + + def test_create_entity_key(self): + self._test_create_entity_key() + + def test_build_instance_key(self): + self._test_build_instance_key() + + @classmethod + def _get_default_group_opts(cls): + return cls.DEFAULT_GROUP_OPTS diff --git a/vitrage/tests/unit/datasources/nova/test_nova_instance_driver.py b/vitrage/tests/unit/datasources/nova/test_nova_instance_driver.py new file mode 100644 index 000000000..b50d53c09 --- /dev/null +++ b/vitrage/tests/unit/datasources/nova/test_nova_instance_driver.py @@ -0,0 +1,98 @@ +# Copyright 2018 - Nokia +# +# 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 oslo_config import cfg +from oslo_log import log as logging + +from vitrage.common.constants import DatasourceOpts as DSOpts +from vitrage.common.constants import UpdateMethod +from vitrage.datasources.nova.instance.driver import InstanceDriver +from vitrage.tests import base +from vitrage.tests.mocks import mock_driver as mock_sync + +LOG = logging.getLogger(__name__) + + +# noinspection PyProtectedMember +class NovaHostTransformerTest(base.BaseTest): + + OPTS = [ + cfg.StrOpt(DSOpts.UPDATE_METHOD, + default=UpdateMethod.PUSH), + ] + + # noinspection PyAttributeOutsideInit,PyPep8Naming + @classmethod + def setUpClass(cls): + super(NovaHostTransformerTest, cls).setUpClass() + + def test_use_versioned_notifications(self): + LOG.debug('Nova instance driver test') + + # Test setup + driver = InstanceDriver(cfg.ConfigOpts()) + driver.conf.register_opts([ + cfg.BoolOpt('use_nova_versioned_notifications', + default=True, required=True), + ]) + update_versioned_event, update_legacy_event = self._create_events() + + # Test action + events = driver.enrich_event(update_versioned_event, + 'instance.create.end') + self.assert_is_not_empty(events) + + # Test action + events = driver.enrich_event(update_legacy_event, + 'compute.instance.create.end') + self.assert_is_empty(events) + + def test_use_legacy_notifications(self): + LOG.debug('Nova instance driver test') + + # Test setup + driver = InstanceDriver(cfg.ConfigOpts()) + driver.conf.register_opts([ + cfg.BoolOpt('use_nova_versioned_notifications', + default=False, required=True), + ]) + update_versioned_event, update_legacy_event = self._create_events() + + # Test action + events = driver.enrich_event(update_versioned_event, + 'instance.create.end') + self.assert_is_empty(events) + + # Test action + events = driver.enrich_event(update_legacy_event, + 'compute.instance.create.end') + self.assert_is_not_empty(events) + + @staticmethod + def _create_events(): + spec_list = mock_sync.simple_instance_generators( + host_num=1, vm_num=1, update_events=1, + use_nova_versioned_format=True + ) + update_versioned_event = \ + mock_sync.generate_random_events_list(spec_list)[0] + + spec_list = mock_sync.simple_instance_generators( + host_num=1, vm_num=1, update_events=1, + use_nova_versioned_format=False + ) + update_legacy_event = \ + mock_sync.generate_random_events_list(spec_list)[0] + + return update_versioned_event, update_legacy_event diff --git a/vitrage/tests/unit/entity_graph/base.py b/vitrage/tests/unit/entity_graph/base.py index cf60a400f..dc9dba3ec 100644 --- a/vitrage/tests/unit/entity_graph/base.py +++ b/vitrage/tests/unit/entity_graph/base.py @@ -63,6 +63,11 @@ class TestEntityGraphUnitBase(base.BaseTest): min=1) ] + OS_CLIENTS_OPTS = [ + cfg.BoolOpt('use_nova_versioned_notifications', + default=False, required=True), + ] + NUM_CLUSTERS = 1 NUM_ZONES = 2 NUM_HOSTS = 4 @@ -98,9 +103,9 @@ class TestEntityGraphUnitBase(base.BaseTest): snap_vals={DSProps.DATASOURCE_ACTION: DatasourceAction.INIT_SNAPSHOT}) gen_list += mock_sync.simple_instance_generators( - self.NUM_HOSTS, - self.NUM_INSTANCES, - self.NUM_INSTANCES, + host_num=self.NUM_HOSTS, + vm_num=self.NUM_INSTANCES, + snapshot_events=self.NUM_INSTANCES, snap_vals={DSProps.DATASOURCE_ACTION: DatasourceAction.INIT_SNAPSHOT}) return mock_sync.generate_sequential_events_list(gen_list) @@ -138,7 +143,8 @@ class TestEntityGraphUnitBase(base.BaseTest): event_type=None, properties=None): # generate event - spec_list = mock_sync.simple_instance_generators(1, 1, 1) + spec_list = mock_sync.simple_instance_generators(host_num=1, vm_num=1, + snapshot_events=1) events_list = mock_sync.generate_random_events_list( spec_list) @@ -161,7 +167,7 @@ class TestEntityGraphUnitBase(base.BaseTest): project_id=None, vitrage_resource_project_id=None, metadata=None, - vitrage_sample_timestamp=None, + vitrage_sample_timestamp='', datasource_name=None, is_deleted=False): return graph_utils.create_vertex( diff --git a/vitrage/tests/unit/entity_graph/processor/test_processor.py b/vitrage/tests/unit/entity_graph/processor/test_processor.py index bbf58626c..f5cd437c4 100644 --- a/vitrage/tests/unit/entity_graph/processor/test_processor.py +++ b/vitrage/tests/unit/entity_graph/processor/test_processor.py @@ -46,6 +46,7 @@ class TestProcessor(TestEntityGraphUnitBase): cls.conf = cfg.ConfigOpts() cls.conf.register_opts(cls.PROCESSOR_OPTS, group='entity_graph') cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources') + cls.conf.register_opts(cls.OS_CLIENTS_OPTS) cls.load_datasources(cls.conf) def test_process_event(self): @@ -57,13 +58,14 @@ class TestProcessor(TestEntityGraphUnitBase): self._check_graph(processor, self.NUM_VERTICES_AFTER_CREATION, self.NUM_EDGES_AFTER_CREATION) - # check update instance even + # check update instance event event[DSProps.DATASOURCE_ACTION] = DSAction.UPDATE event[DSProps.EVENT_TYPE] = 'compute.instance.volume.attach' event['hostname'] = 'new_host' event['instance_id'] = event['id'] event['state'] = event['status'] event['host'] = event['OS-EXT-SRV-ATTR:host'] + processor.process_event(event) self._check_graph(processor, self.NUM_VERTICES_AFTER_CREATION, self.NUM_EDGES_AFTER_CREATION)