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
This commit is contained in:
Ifat Afek 2018-12-06 17:48:38 +00:00
parent dc48a3ecfa
commit a96bc0642b
17 changed files with 788 additions and 183 deletions

View File

@ -0,0 +1,3 @@
---
features:
- Added support for Nova versioned notifications.

View File

@ -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():

View File

@ -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')

View File

@ -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

View File

@ -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 = {

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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)