Enable versioned nova notifications
Listen for versioned notifications from Nova servers. The expected versions for each type are coded into notification_handlers. The versioned notifications are not entirely compatible with the API or the old notifications; mostly notably in the network information. Needs some functional tests as well Change-Id: Iea3c2f19cca4b24f05b7edab4b307555fecdc1b3 Signed-off-by: Steve McLellan <steven.j.mclellan@gmail.com> Story: #2003676 Task: #26198
This commit is contained in:
parent
a9a146a62c
commit
665a106e25
|
@ -151,6 +151,10 @@ function configure_searchlight {
|
|||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_flavor enabled True
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_flavor notifications_topics_exchanges versioned_notifications,nova
|
||||
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_server enabled True
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_server notifications_topics_exchanges versioned_notifications,nova
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_server use_versioned_notifications True
|
||||
|
||||
# Plugin config - disable swift by default since it's not typically installed
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_swift_account enabled False
|
||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_swift_container enabled False
|
||||
|
|
|
@ -59,6 +59,8 @@ Plugin: OS::Nova::Server
|
|||
[resource_plugin:os_nova_server]
|
||||
enabled = true
|
||||
resource_group_name = searchlight
|
||||
notifications_topics_exchanges = versioned_notifications,nova
|
||||
use_versioned_notifications = true
|
||||
|
||||
Plugin: OS::Nova::Hypervisor
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -125,6 +127,7 @@ incremental updates. Enable notifications using the following::
|
|||
|
||||
[notifications]
|
||||
notify_on_state_change = vm_and_task_state
|
||||
# notification_format = versioned
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -132,6 +135,10 @@ incremental updates. Enable notifications using the following::
|
|||
See :ref:`plugin_notifications` for more information on
|
||||
notification topics.
|
||||
|
||||
The default setting for notification_format is 'both' which sends both
|
||||
versioned and unversioned notifications. Searchlight uses
|
||||
'use_versioned_notifications' to decide which to use.
|
||||
|
||||
local.conf (devstack)
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
prelude: >
|
||||
Adds support for versioned nova server notifications.
|
||||
features:
|
||||
- |
|
||||
Support for versioned nova server notifications (and related notifications
|
||||
where supported, like volume attach). This has to be enabled (it is not
|
||||
yet the default).
|
||||
|
||||
This reduces the callbacks to the nova API significantly.
|
||||
issues:
|
||||
- |
|
||||
Payload versioning is somewhat manual; a given release will support a
|
||||
maxmimum major payload version and warn on later minor versions.
|
||||
upgrade:
|
||||
- |
|
||||
Configuration needs changing to support versioned notifications; see docs.
|
|
@ -125,3 +125,9 @@ class InvalidAPIVersionProvided(SearchlightException):
|
|||
message = _("The provided API version is not supported, "
|
||||
"the current available version range for %(service)s "
|
||||
"is: from %(min_version)s to %(max_version)s.")
|
||||
|
||||
|
||||
class VersionedNotificationMismatch(SearchlightException):
|
||||
message = _("Provided notification version "
|
||||
"%(provided_maj)s.%(provided_min)s did not match expected "
|
||||
"%(expected_maj)s.%(expected_min)s for %(type)s")
|
||||
|
|
|
@ -39,6 +39,12 @@ FLAVOR_FIELDS_MAP = {
|
|||
}
|
||||
FLAVOR_BLACKLISTED_FIELDS = ['vcpu_weight', 'flavorid']
|
||||
|
||||
EXTENDED_FIELDS = {'OS-EXT-STS:task_state': 'task_state',
|
||||
'OS-EXT-STS:vm_state': 'state',
|
||||
'OS-EXT-AZ:availability_zone': 'availability_zone',
|
||||
'OS-EXT-SRV-ATTR:hypervisor_hostname': 'host_name',
|
||||
'OS-EXT-SRV-ATTR:host': 'host'}
|
||||
|
||||
|
||||
def _get_flavor_access(flavor):
|
||||
if flavor.is_public:
|
||||
|
@ -80,9 +86,107 @@ def serialize_nova_server(server):
|
|||
|
||||
utils.normalize_date_fields(serialized)
|
||||
|
||||
serialized['status'] = serialized['status'].lower()
|
||||
|
||||
# Pop the fault stracktrace if any - it's big
|
||||
fault = serialized.get('fault', None)
|
||||
if fault and isinstance(fault, dict):
|
||||
fault.pop('details', None)
|
||||
|
||||
return serialized
|
||||
|
||||
|
||||
# TODO(sjmc7) - if https://review.openstack.org/#/c/485525/ lands, remove this
|
||||
# If it doesn't, make it more accurate
|
||||
def _get_server_status(vm_state, task_state):
|
||||
# https://github.com/openstack/nova/blob/master/nova/api/openstack/common.py#L113
|
||||
# Simplified version of that
|
||||
if vm_state:
|
||||
vm_state = vm_state.lower()
|
||||
if task_state:
|
||||
task_state = task_state.lower()
|
||||
|
||||
return {
|
||||
'active': 'active',
|
||||
'building': 'build',
|
||||
'stopped': 'shutoff',
|
||||
'resized': 'verify_resize',
|
||||
'paused': 'paused',
|
||||
'suspended': 'suspended',
|
||||
'rescued': 'rescue',
|
||||
'error': 'error',
|
||||
'deleted': 'deleted',
|
||||
'soft-delete': 'soft_deleted',
|
||||
'shelved': 'shelved',
|
||||
'shelved_offloaded': 'shelved_offloaded',
|
||||
}.get(vm_state)
|
||||
|
||||
|
||||
def serialize_server_versioned(payload):
|
||||
# Based loosely on currently documented 1.1 InstanceActionPayload
|
||||
|
||||
# Some transforms - maybe these could be made the same in nova?
|
||||
transform_keys = [('display_description', 'description'),
|
||||
('display_name', 'name'), ('uuid', 'id')]
|
||||
for src, dest in transform_keys:
|
||||
payload[dest] = payload.pop(src)
|
||||
|
||||
copy_keys = [('tenant_id', 'project_id')]
|
||||
for src, dest in copy_keys:
|
||||
if src in payload:
|
||||
payload[dest] = payload.get(src)
|
||||
|
||||
delete_keys = ['audit_period', 'node']
|
||||
for key in delete_keys:
|
||||
payload.pop(key, None)
|
||||
|
||||
# We should denormalize this because it'd be better for searching
|
||||
flavor_id = payload.pop('flavor')['nova_object.data']['flavorid']
|
||||
payload['flavor'] = {'id': flavor_id}
|
||||
|
||||
image_id = payload.pop('image_uuid')
|
||||
payload['image'] = {'id': image_id}
|
||||
|
||||
# Translate the status, kind of. state and task_state will get
|
||||
# popped off shortly
|
||||
vm_state = payload.get('state', None)
|
||||
task_state = payload.get('task_state', None)
|
||||
payload['status'] = _get_server_status(vm_state, task_state)
|
||||
|
||||
# Map backwards to the OS-EXT- attributes
|
||||
for ext_attr, simple_attr in EXTENDED_FIELDS.items():
|
||||
attribute = payload.pop(simple_attr, None)
|
||||
if attribute:
|
||||
payload[ext_attr] = attribute
|
||||
|
||||
# Network information. This has to be transformed
|
||||
# TODO(sjmc7) Try to better reconcile this with the API format
|
||||
ip_addresses = [address['nova_object.data'] for address in
|
||||
payload.pop("ip_addresses", [])]
|
||||
|
||||
def map_address(addr):
|
||||
# TODO(sjmc7) Think this should be network name. Missing net type
|
||||
net = {
|
||||
"version": addr["version"],
|
||||
"name": addr["device_name"],
|
||||
"OS-EXT-IPS-MAC:mac_addr": addr["mac"],
|
||||
}
|
||||
if net["version"] == 4:
|
||||
net["ipv4_addr"] = addr["address"]
|
||||
else:
|
||||
net["ipv6_addr"] = addr["address"]
|
||||
return net
|
||||
|
||||
payload["networks"] = [map_address(address) for address in ip_addresses]
|
||||
|
||||
# Pop the fault stracktrace if any - it's big
|
||||
fault = payload.get('fault', None)
|
||||
if fault and isinstance(fault, dict):
|
||||
fault.pop('details', None)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def serialize_nova_hypervisor(hypervisor, updated_at=None):
|
||||
serialized = hypervisor.to_dict()
|
||||
# The id for hypervisor is an integer, should be changed to
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
|
||||
from copy import deepcopy
|
||||
import novaclient.exceptions
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from elasticsearch import helpers
|
||||
from searchlight.elasticsearch.plugins import base
|
||||
from searchlight.elasticsearch.plugins.nova import serialize_nova_flavor
|
||||
from searchlight.elasticsearch.plugins.nova import serialize_nova_server
|
||||
from searchlight.elasticsearch.plugins.nova import serialize_server_versioned
|
||||
from searchlight.elasticsearch.plugins import utils
|
||||
from searchlight import pipeline
|
||||
|
||||
|
@ -67,6 +69,14 @@ class InstanceHandler(base.NotificationBase):
|
|||
'spawning': 'unshelving'
|
||||
}
|
||||
|
||||
# Supported major/minor notification versions. Major changes will likely
|
||||
# require code changes.
|
||||
notification_versions = {
|
||||
'InstanceActionPayload': '1.2',
|
||||
'InstanceUpdatePayload': '1.3',
|
||||
'InstanceActionVolumeSwapPayload': '1.1',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _get_notification_exchanges(cls):
|
||||
return ['nova']
|
||||
|
@ -78,38 +88,68 @@ class InstanceHandler(base.NotificationBase):
|
|||
('old_task_state', payload.get('old_task_state')),
|
||||
('new_task_state', payload.get('new_task_state')))
|
||||
|
||||
@classmethod
|
||||
def get_plugin_opts(cls):
|
||||
opts = super(InstanceHandler, cls).get_plugin_opts()
|
||||
opts.extend([
|
||||
cfg.BoolOpt(
|
||||
'use_versioned_notifications',
|
||||
help='Expect versioned notifications and ignore unversioned',
|
||||
default=True)
|
||||
])
|
||||
return opts
|
||||
|
||||
def _use_versioned_notifications(self):
|
||||
return self.plugin_options.use_versioned_notifications
|
||||
|
||||
def get_event_handlers(self):
|
||||
return {
|
||||
# compute.instance.update seems to be the event set as a
|
||||
# result of a state change etc
|
||||
'compute.instance.update': self.index_from_update,
|
||||
if not self._use_versioned_notifications():
|
||||
return {
|
||||
# compute.instance.update seems to be the event set as a
|
||||
# result of a state change etc
|
||||
'compute.instance.update': self.index_from_update,
|
||||
|
||||
'compute.instance.create.start': self.index_from_api,
|
||||
'compute.instance.create.end': self.index_from_api,
|
||||
'compute.instance.create.start': self.index_from_api,
|
||||
'compute.instance.create.end': self.index_from_api,
|
||||
|
||||
'compute.instance.power_on.end': self.index_from_api,
|
||||
'compute.instance.power_off.end': self.index_from_api,
|
||||
'compute.instance.resume.end': self.index_from_api,
|
||||
'compute.instance.suspend.end': self.index_from_api,
|
||||
'compute.instance.pause.end': self.index_from_api,
|
||||
'compute.instance.unpause.end': self.index_from_api,
|
||||
'compute.instance.power_on.end': self.index_from_api,
|
||||
'compute.instance.power_off.end': self.index_from_api,
|
||||
'compute.instance.resume.end': self.index_from_api,
|
||||
'compute.instance.suspend.end': self.index_from_api,
|
||||
'compute.instance.pause.end': self.index_from_api,
|
||||
'compute.instance.unpause.end': self.index_from_api,
|
||||
|
||||
'compute.instance.shutdown.end': self.index_from_api,
|
||||
'compute.instance.reboot.end': self.index_from_api,
|
||||
'compute.instance.delete.end': self.delete,
|
||||
'compute.instance.shutdown.end': self.index_from_api,
|
||||
'compute.instance.reboot.end': self.index_from_api,
|
||||
'compute.instance.delete.end': self.delete,
|
||||
|
||||
'compute.instance.shelve.end': self.index_from_api,
|
||||
'compute.instance.shelve_offload.end': self.index_from_api,
|
||||
'compute.instance.unshelve.end': self.index_from_api,
|
||||
'compute.instance.shelve.end': self.index_from_api,
|
||||
'compute.instance.shelve_offload.end': self.index_from_api,
|
||||
'compute.instance.unshelve.end': self.index_from_api,
|
||||
|
||||
'compute.instance.volume.attach': self.index_from_api,
|
||||
'compute.instance.volume.detach': self.index_from_api,
|
||||
'compute.instance.volume.attach': self.index_from_api,
|
||||
'compute.instance.volume.detach': self.index_from_api,
|
||||
|
||||
# Removing neutron port events for now; waiting on nova
|
||||
# to implement interface notifications as with volumes
|
||||
# https://launchpad.net/bugs/1567525
|
||||
# https://blueprints.launchpad.net/nova/+spec/interface-notifications
|
||||
}
|
||||
# Removing neutron port events for now; waiting on nova
|
||||
# to implement interface notifications as with volumes
|
||||
# https://launchpad.net/bugs/1567525
|
||||
# bps/nova/+spec/interface-notifications
|
||||
}
|
||||
# Otherwise listen for versioned notifications!
|
||||
# Nova versioned notifications all include the entire payload
|
||||
end_events = ['create', 'pause', 'power_off', 'power_on',
|
||||
'reboot', 'rebuild', 'resize', 'restore', 'resume',
|
||||
'shelve', 'shutdown', 'snapshot', 'suspend', 'unpause',
|
||||
'unshelve', 'volume_attach', 'volume_detach']
|
||||
notifications = {('instance.%s.end' % ev): self.index_from_versioned
|
||||
for ev in end_events}
|
||||
|
||||
# instance.update has no start or end
|
||||
notifications['instance.update'] = self.index_from_versioned
|
||||
|
||||
# This should become soft delete once that is supported
|
||||
notifications['instance.delete.end'] = self.delete_from_versioned
|
||||
return notifications
|
||||
|
||||
def index_from_update(self, event_type, payload, timestamp):
|
||||
"""Determine whether or not to process a full update. The updates, and
|
||||
|
@ -325,6 +365,49 @@ class InstanceHandler(base.NotificationBase):
|
|||
'from index: %(exc)s' %
|
||||
{'instance_id': instance_id, 'exc': exc})
|
||||
|
||||
def index_from_versioned(self, event_type, payload, timestamp):
|
||||
notification_version = payload['nova_object.version']
|
||||
notification_name = payload['nova_object.name']
|
||||
expected_version = self.notification_versions.get(notification_name,
|
||||
None)
|
||||
if expected_version:
|
||||
utils.check_notification_version(
|
||||
expected_version, notification_version, notification_name)
|
||||
else:
|
||||
LOG.warning("No expected notification version for %s; "
|
||||
"processing anyway", notification_name)
|
||||
|
||||
versioned_payload = payload['nova_object.data']
|
||||
serialized = serialize_server_versioned(versioned_payload)
|
||||
self.index_helper.save_document(
|
||||
serialized,
|
||||
version=self.get_version(serialized, timestamp))
|
||||
return pipeline.IndexItem(self.index_helper.plugin,
|
||||
event_type,
|
||||
payload,
|
||||
serialized)
|
||||
|
||||
def delete_from_versioned(self, event_type, payload, timestamp):
|
||||
payload = payload['nova_object.data']
|
||||
instance_id = payload['uuid']
|
||||
version = self.get_version(payload, timestamp,
|
||||
preferred_date_field='deleted_at')
|
||||
try:
|
||||
version = self.get_version(payload, timestamp,
|
||||
preferred_date_field='deleted_at')
|
||||
self.index_helper.delete_document(
|
||||
{'_id': instance_id, '_version': version})
|
||||
return pipeline.DeleteItem(self.index_helper.plugin,
|
||||
event_type,
|
||||
payload,
|
||||
instance_id
|
||||
)
|
||||
except Exception as exc:
|
||||
LOG.error(
|
||||
'Error deleting instance %(instance_id)s '
|
||||
'from index: %(exc)s' %
|
||||
{'instance_id': instance_id, 'exc': exc})
|
||||
|
||||
|
||||
class ServerGroupHandler(base.NotificationBase):
|
||||
"""Handles nova server group notifications.
|
||||
|
|
|
@ -29,6 +29,7 @@ class ServerIndex(base.IndexBase):
|
|||
NotificationHandlerCls = notification_handler.InstanceHandler
|
||||
|
||||
# Will be combined with 'admin_only_fields' from config
|
||||
# https://developer.openstack.org/api-ref/compute/?expanded=show-server-details-detail
|
||||
ADMIN_ONLY_FIELDS = ['OS-EXT-SRV-ATTR:*', 'host_status']
|
||||
|
||||
@classmethod
|
||||
|
@ -90,6 +91,22 @@ class ServerIndex(base.IndexBase):
|
|||
'type': 'string',
|
||||
'index': 'not_analyzed'
|
||||
},
|
||||
'OS-EXT-SRV-ATTR:hypervisor_hostname': {
|
||||
'type': 'string',
|
||||
'index': 'not_analyzed'
|
||||
},
|
||||
'OS-EXT-STS:vm_state': {
|
||||
'type': 'string',
|
||||
'index': 'not_analyzed'
|
||||
},
|
||||
'fault': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'code': {'type': 'integer'},
|
||||
'created': {'type': 'date'},
|
||||
'message': {'type': 'string'},
|
||||
}
|
||||
},
|
||||
# Nova gives security group names, where neutron ports
|
||||
# give ids in the same field. There's no solution that
|
||||
# maintains compatibility with both
|
||||
|
@ -163,7 +180,8 @@ class ServerIndex(base.IndexBase):
|
|||
only be available to administrators.
|
||||
"""
|
||||
return {'tenant_id': True, 'project_id': True, 'host_status': True,
|
||||
'created': False, 'updated': False}
|
||||
'created': False, 'updated': False,
|
||||
'OS-EXT-SRV-ATTR:hypervisor_hostname': True, 'fault': False}
|
||||
|
||||
@property
|
||||
def resource_allowed_policy_target(self):
|
||||
|
|
|
@ -23,6 +23,7 @@ import six
|
|||
from oslo_config import cfg
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
from searchlight.common import exception as sl_exc
|
||||
from searchlight.common import utils
|
||||
from searchlight.context import RequestContext
|
||||
import searchlight.elasticsearch
|
||||
|
@ -526,3 +527,28 @@ def normalize_es_document(es_doc, plugin):
|
|||
admin_context = RequestContext()
|
||||
plugin.filter_result({'_source': es_doc}, admin_context)
|
||||
return es_doc
|
||||
|
||||
|
||||
def check_notification_version(expected, actual, notification_type):
|
||||
"""
|
||||
If actual's major version is different from expected, a
|
||||
VersionedNotificationMismatch error is raised.
|
||||
If the minor versions are different, a DEBUG level log
|
||||
message is output
|
||||
"""
|
||||
maj_ver, min_ver = map(int, actual.split('.'))
|
||||
expected_maj, expected_min = map(int, expected.split('.'))
|
||||
if maj_ver != expected_maj:
|
||||
raise sl_exc.VersionedNotificationMismatch(
|
||||
provided_maj=maj_ver, provided_min=min_ver,
|
||||
expected_maj=expected_maj, expected_min=expected_min,
|
||||
type=notification_type)
|
||||
|
||||
if min_ver != expected_min:
|
||||
LOG.debug(
|
||||
"Notification minor version mismatch. "
|
||||
"Provided: %(provided_maj)s, %(provided_min)s. "
|
||||
"Expected: %(expected_maj)s.%(expected_min)s." % {
|
||||
"provided_maj": maj_ver, "provided_min": min_ver,
|
||||
"expected_maj": expected_maj, "expected_min": expected_min}
|
||||
)
|
||||
|
|
|
@ -270,6 +270,7 @@ service_policy_path = %(service_policy_path)s
|
|||
workers = 0
|
||||
bind_host = 127.0.0.1
|
||||
bind_port = %(bind_port)s
|
||||
|
||||
"""
|
||||
self.paste_conf_base = """[pipeline:searchlight]
|
||||
pipeline = versionnegotiation unauthenticated-context rootapp
|
||||
|
|
|
@ -329,8 +329,8 @@ class TestSearchApi(functional.FunctionalTest):
|
|||
expected = {
|
||||
u'name': u'status',
|
||||
u'options': [
|
||||
{u'doc_count': 2, u'key': u'ACTIVE'},
|
||||
{u'doc_count': 1, u'key': u'RESUMING'},
|
||||
{u'doc_count': 2, u'key': u'active'},
|
||||
{u'doc_count': 1, u'key': u'resuming'},
|
||||
],
|
||||
u'type': u'string'
|
||||
}
|
||||
|
@ -967,6 +967,7 @@ class TestSearchApi(functional.FunctionalTest):
|
|||
u'id': 'abcdef',
|
||||
u'tenant_id': TENANT1,
|
||||
u'user_id': USER1,
|
||||
u'status': 'ACTIVE',
|
||||
u'image': {u'id': u'a'},
|
||||
u'flavor': {u'id': u'1'},
|
||||
u'created_at': u'2016-04-07T15:49:35Z',
|
||||
|
@ -977,6 +978,7 @@ class TestSearchApi(functional.FunctionalTest):
|
|||
u'id': '12341234',
|
||||
u'tenant_id': TENANT2,
|
||||
u'user_id': USER1,
|
||||
u'status': 'ACTIVE',
|
||||
u'image': {u'id': u'a'},
|
||||
u'flavor': {u'id': u'1'},
|
||||
u'created_at': u'2016-04-07T15:49:35Z',
|
||||
|
|
|
@ -126,9 +126,8 @@ class TestNovaPlugins(functional.FunctionalTest):
|
|||
TENANT_ID)
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual(1, json_content['hits']['total'])
|
||||
|
||||
hits = json_content['hits']['hits']
|
||||
host_id = u'41d7069823d74c9ea8debda9a3a02bb00b2f7d53a0accd1f79429407'
|
||||
hits = json_content['hits']['hits']
|
||||
expected_sources = [{
|
||||
u'OS-DCF:diskConfig': u'MANUAL',
|
||||
u'OS-EXT-AZ:availability_zone': u'nova',
|
||||
|
@ -163,7 +162,7 @@ class TestNovaPlugins(functional.FunctionalTest):
|
|||
u'owner': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||
u'project_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||
u'security_groups': [{u'name': u'default'}],
|
||||
u'status': u'ACTIVE',
|
||||
u'status': u'active',
|
||||
u'tenant_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||
u'updated': u'2016-03-08T08:40:22Z',
|
||||
u'user_id': u'7c97202cf58d43a9ab33016fc403f093'}]
|
||||
|
@ -269,31 +268,6 @@ class TestNovaListeners(test_listener.TestSearchListenerBase):
|
|||
)
|
||||
self.listener_alias = self.servers_plugin.alias_name_listener
|
||||
|
||||
@mock.patch(nova_version_getter, return_value=fake_version_list)
|
||||
@mock.patch(nova_server_getter)
|
||||
def test_error_state_transition(self, mock_nova, mock_version_list):
|
||||
inst_id = "4b86f534-16db-4de8-8ce0-f1ee68894835"
|
||||
|
||||
mock_nova.return_value = utils.DictObj(**{
|
||||
'id': inst_id,
|
||||
'name': 'test-error',
|
||||
'tenant_id': EV_TENANT,
|
||||
'addresses': {},
|
||||
'image': {'id': '1'},
|
||||
'flavor': {'id': 'a'},
|
||||
'created': '2016-08-31T23:32:11Z',
|
||||
'updated': '2016-08-31T23:32:11Z',
|
||||
})
|
||||
|
||||
error_update = self.server_events[
|
||||
'instance-update-error-final'
|
||||
]
|
||||
self._send_event_to_listener(error_update,
|
||||
self.listener_alias)
|
||||
result = self._verify_event_processing(error_update, owner=EV_TENANT)
|
||||
self._verify_result(error_update, ['tenant_id'], result)
|
||||
mock_nova.assert_called_with(inst_id)
|
||||
|
||||
def test_server_group_create_delete(self):
|
||||
# Test #1: Create a server group.
|
||||
create_event = self.server_group_events["servergroup.create"]
|
||||
|
@ -393,3 +367,53 @@ class TestNovaListeners(test_listener.TestSearchListenerBase):
|
|||
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual(0, json_content['hits']['total'])
|
||||
|
||||
|
||||
class TestNovaUnversionedListener(test_listener.TestSearchListenerBase):
|
||||
def setUp(self):
|
||||
version_notifications = \
|
||||
'searchlight.elasticsearch.plugins.nova.notification_handler'\
|
||||
'.InstanceHandler._use_versioned_notifications'
|
||||
mock_versioned = mock.patch(version_notifications,
|
||||
return_value=False)
|
||||
mock_versioned.start()
|
||||
self.addCleanup(mock_versioned.stop)
|
||||
|
||||
super(TestNovaUnversionedListener, self).setUp()
|
||||
self.servers_plugin = self.initialized_plugins['OS::Nova::Server']
|
||||
self.server_events = self._load_fixture_data('events/servers.json')
|
||||
|
||||
sp = self.servers_plugin
|
||||
notification_plugins = {sp.document_type: utils.StevedoreMock(sp)}
|
||||
|
||||
self.notification_endpoint = NotificationEndpoint(
|
||||
notification_plugins,
|
||||
PipelineManager(notification_plugins)
|
||||
)
|
||||
self.listener_alias = self.servers_plugin.alias_name_listener
|
||||
|
||||
@mock.patch(nova_version_getter, return_value=fake_version_list)
|
||||
@mock.patch(nova_server_getter)
|
||||
def test_error_state_transition(self, mock_nova, mock_version_list):
|
||||
inst_id = "4b86f534-16db-4de8-8ce0-f1ee68894835"
|
||||
|
||||
mock_nova.return_value = utils.DictObj(**{
|
||||
'id': inst_id,
|
||||
'name': 'test-error',
|
||||
'tenant_id': EV_TENANT,
|
||||
'addresses': {},
|
||||
'image': {'id': '1'},
|
||||
'flavor': {'id': 'a'},
|
||||
'status': 'ERROR',
|
||||
'created': '2016-08-31T23:32:11Z',
|
||||
'updated': '2016-08-31T23:32:11Z',
|
||||
})
|
||||
|
||||
error_update = self.server_events[
|
||||
'instance-update-error-final'
|
||||
]
|
||||
self._send_event_to_listener(error_update,
|
||||
self.listener_alias)
|
||||
result = self._verify_event_processing(error_update, owner=EV_TENANT)
|
||||
self._verify_result(error_update, ['tenant_id'], result)
|
||||
mock_nova.assert_called_with(inst_id)
|
||||
|
|
|
@ -143,6 +143,7 @@ def _instance_fixture(instance_id, name, tenant_id, **kwargs):
|
|||
}]
|
||||
},
|
||||
u'hostId': u'd86d2c042a1f233227f70c5e9d2c5829de98d222d0922f469054ac17',
|
||||
u'host_name': u'devstack',
|
||||
u'id': instance_id,
|
||||
u'image': {
|
||||
u'id': u'46b77e67-ce40-44ca-823d-e6f83489f21e',
|
||||
|
@ -167,7 +168,7 @@ def _instance_fixture(instance_id, name, tenant_id, **kwargs):
|
|||
u'os-extended-volumes:volumes_attached': [],
|
||||
u'progress': 0,
|
||||
u'security_groups': [{u'name': u'default'}],
|
||||
u'status': u'ACTIVE',
|
||||
u'status': u'active',
|
||||
u'tenant_id': tenant_id,
|
||||
u'updated': updated_now,
|
||||
u'user_id': USER1}
|
||||
|
@ -181,6 +182,15 @@ def _instance_fixture(instance_id, name, tenant_id, **kwargs):
|
|||
class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestServerLoaderPlugin, self).setUp()
|
||||
# Use unversioned notifications
|
||||
version_notifications = \
|
||||
'searchlight.elasticsearch.plugins.nova.notification_handler'\
|
||||
'.InstanceHandler._use_versioned_notifications'
|
||||
mock_versioned = mock.patch(version_notifications,
|
||||
return_value=False)
|
||||
mock_versioned.start()
|
||||
self.addCleanup(mock_versioned.stop)
|
||||
|
||||
self.plugin = servers_plugin.ServerIndex()
|
||||
self._create_fixtures()
|
||||
|
||||
|
@ -233,6 +243,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
u'config_drive': u'True',
|
||||
u'flavor': {u'id': u'1'},
|
||||
u'hostId': u'host1',
|
||||
u'host_name': u'devstack',
|
||||
u'id': u'6c41b4d1-f0fa-42d6-9d8d-e3b99695aa69',
|
||||
u'image': {u'id': u'a'},
|
||||
u'key_name': u'key',
|
||||
|
@ -241,7 +252,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
u'os-extended-volumes:volumes_attached': [],
|
||||
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'security_groups': [u'default'],
|
||||
u'status': u'ACTIVE',
|
||||
u'status': u'active',
|
||||
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'updated': updated_now,
|
||||
|
@ -301,6 +312,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
u'config_drive': u'True',
|
||||
u'flavor': {u'id': u'1'},
|
||||
u'hostId': u'host1',
|
||||
u'host_name': u'devstack',
|
||||
u'id': u'a380287d-1f61-4887-959c-8c5ab8f75f8f',
|
||||
u'key_name': u'key',
|
||||
u'metadata': {},
|
||||
|
@ -309,7 +321,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'security_groups': [u'default'],
|
||||
u'status': u'ACTIVE',
|
||||
u'status': u'active',
|
||||
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||
u'updated': updated_now,
|
||||
u'user_id': u'27f4d76b-be62-4e4e-aa33bb11cc55',
|
||||
|
@ -349,10 +361,11 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
network_facets = ('name', 'version', 'ipv6_addr', 'ipv4_addr',
|
||||
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
||||
expected_facet_names = [
|
||||
'OS-EXT-AZ:availability_zone', 'created_at', 'description',
|
||||
'OS-EXT-AZ:availability_zone',
|
||||
'created_at', 'description',
|
||||
'flavor.id', 'id', 'image.id', 'locked', 'name',
|
||||
'owner', 'security_groups', 'status', 'tags', 'updated_at',
|
||||
'user_id']
|
||||
'user_id', 'OS-EXT-STS:vm_state']
|
||||
expected_facet_names.extend(['networks.' + f for f in network_facets])
|
||||
|
||||
self.assertEqual(set(expected_facet_names), set(facet_names))
|
||||
|
@ -408,10 +421,12 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
network_facets = ('name', 'version', 'ipv6_addr', 'ipv4_addr',
|
||||
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
||||
expected_facet_names = [
|
||||
'OS-EXT-SRV-ATTR:hypervisor_hostname',
|
||||
'OS-EXT-AZ:availability_zone', 'created_at', 'description',
|
||||
'flavor.id', 'host_status', 'id', 'image.id', 'locked',
|
||||
'name', 'owner', 'project_id', 'security_groups', 'status',
|
||||
'tags', 'tenant_id', 'updated_at', 'user_id']
|
||||
'tags', 'tenant_id', 'updated_at', 'user_id',
|
||||
'OS-EXT-STS:vm_state']
|
||||
expected_facet_names.extend(['networks.' + f for f in network_facets])
|
||||
|
||||
self.assertEqual(set(expected_facet_names), set(facet_names))
|
||||
|
@ -1315,6 +1330,88 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
|||
return_value=self.instance1) as nova_getter:
|
||||
|
||||
type_handler('compute.instance.update', update_event,
|
||||
'2016-07-17 19:52:13.523135')
|
||||
'2016-03-17 19:52:13.523135')
|
||||
nova_getter.assert_called_with(instance_id)
|
||||
self.assertEqual(1, mock_save.call_count)
|
||||
|
||||
|
||||
class TestVersionedServerNotifications(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestVersionedServerNotifications, self).setUp()
|
||||
|
||||
self.plugin = servers_plugin.ServerIndex()
|
||||
|
||||
def test_versioned_create(self):
|
||||
create_event = {
|
||||
"nova_object.name": "InstanceActionPayload",
|
||||
"nova_object.namespace": "nova",
|
||||
"nova_object.version": "1.1",
|
||||
"nova_object.data": {
|
||||
"image_uuid": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"created_at": "2017-03-17T19:52:13Z",
|
||||
"display_name": "some-server",
|
||||
"display_description": "some-server",
|
||||
"state": "active",
|
||||
"flavor": {
|
||||
"nova_object.name": "FlavorPayload",
|
||||
"nova_object.data": {
|
||||
"flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
|
||||
"name": "test_flavor",
|
||||
"root_gb": 1,
|
||||
"vcpus": 1,
|
||||
"ephemeral_gb": 0,
|
||||
"memory_mb": 512
|
||||
}
|
||||
},
|
||||
"uuid": "178b0921-8f85-4257-88b6-2e743b5a975c",
|
||||
"power_state": "running",
|
||||
"ip_addresses": [{
|
||||
"nova_object.name": "IpPayload",
|
||||
"nova_object.data": {
|
||||
"mac": "fa:16:3e:4c:2c:30",
|
||||
"address": "192.168.1.3",
|
||||
"port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
|
||||
"version": 4,
|
||||
"label": "private-network",
|
||||
"device_name": "tapce531f90-19"
|
||||
}
|
||||
}],
|
||||
'OS-EXT-STS:vm_state': 'active',
|
||||
}
|
||||
}
|
||||
|
||||
handler = self.plugin.get_notification_handler()
|
||||
event_handlers = handler.get_event_handlers()
|
||||
|
||||
expected = {
|
||||
'image': {'id': "155d900f-4e14-4e4c-a73d-069cbf4541e6"},
|
||||
'tenant_id': "6f70656e737461636b20342065766572",
|
||||
'project_id': "6f70656e737461636b20342065766572",
|
||||
"created_at": "2017-03-17T19:52:13Z",
|
||||
"name": "some-server",
|
||||
"description": "some-server",
|
||||
"flavor": {"id": "a22d5517-147c-4147-a0d1-e698df5cd4e3"},
|
||||
"id": "178b0921-8f85-4257-88b6-2e743b5a975c",
|
||||
"power_state": "running",
|
||||
"status": "active",
|
||||
'OS-EXT-STS:vm_state': 'active',
|
||||
"networks": [{
|
||||
"version": 4,
|
||||
"ipv4_addr": "192.168.1.3",
|
||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:4c:2c:30",
|
||||
"name": "tapce531f90-19"
|
||||
}],
|
||||
}
|
||||
expected_version = 489780333780333818
|
||||
|
||||
with mock.patch.object(self.plugin.index_helper,
|
||||
'save_documents') as mock_save:
|
||||
handler = event_handlers.get('instance.create.end')
|
||||
self.assertIsNotNone(handler)
|
||||
handler(payload=create_event,
|
||||
event_type='instance.create.end',
|
||||
timestamp='2017-03-17 19:52:13.818362')
|
||||
|
||||
self.assertEqual(1, mock_save.call_count)
|
||||
mock_save.assert_called_with([expected], [expected_version])
|
||||
|
|
Loading…
Reference in New Issue