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 enabled True
|
||||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_nova_flavor notifications_topics_exchanges versioned_notifications,nova
|
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
|
# 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_account enabled False
|
||||||
iniset $SEARCHLIGHT_CONF resource_plugin:os_swift_container 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]
|
[resource_plugin:os_nova_server]
|
||||||
enabled = true
|
enabled = true
|
||||||
resource_group_name = searchlight
|
resource_group_name = searchlight
|
||||||
|
notifications_topics_exchanges = versioned_notifications,nova
|
||||||
|
use_versioned_notifications = true
|
||||||
|
|
||||||
Plugin: OS::Nova::Hypervisor
|
Plugin: OS::Nova::Hypervisor
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -125,6 +127,7 @@ incremental updates. Enable notifications using the following::
|
||||||
|
|
||||||
[notifications]
|
[notifications]
|
||||||
notify_on_state_change = vm_and_task_state
|
notify_on_state_change = vm_and_task_state
|
||||||
|
# notification_format = versioned
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -132,6 +135,10 @@ incremental updates. Enable notifications using the following::
|
||||||
See :ref:`plugin_notifications` for more information on
|
See :ref:`plugin_notifications` for more information on
|
||||||
notification topics.
|
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)
|
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, "
|
message = _("The provided API version is not supported, "
|
||||||
"the current available version range for %(service)s "
|
"the current available version range for %(service)s "
|
||||||
"is: from %(min_version)s to %(max_version)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']
|
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):
|
def _get_flavor_access(flavor):
|
||||||
if flavor.is_public:
|
if flavor.is_public:
|
||||||
|
@ -80,9 +86,107 @@ def serialize_nova_server(server):
|
||||||
|
|
||||||
utils.normalize_date_fields(serialized)
|
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
|
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):
|
def serialize_nova_hypervisor(hypervisor, updated_at=None):
|
||||||
serialized = hypervisor.to_dict()
|
serialized = hypervisor.to_dict()
|
||||||
# The id for hypervisor is an integer, should be changed to
|
# The id for hypervisor is an integer, should be changed to
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import novaclient.exceptions
|
import novaclient.exceptions
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from elasticsearch import helpers
|
from elasticsearch import helpers
|
||||||
from searchlight.elasticsearch.plugins import base
|
from searchlight.elasticsearch.plugins import base
|
||||||
from searchlight.elasticsearch.plugins.nova import serialize_nova_flavor
|
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_nova_server
|
||||||
|
from searchlight.elasticsearch.plugins.nova import serialize_server_versioned
|
||||||
from searchlight.elasticsearch.plugins import utils
|
from searchlight.elasticsearch.plugins import utils
|
||||||
from searchlight import pipeline
|
from searchlight import pipeline
|
||||||
|
|
||||||
|
@ -67,6 +69,14 @@ class InstanceHandler(base.NotificationBase):
|
||||||
'spawning': 'unshelving'
|
'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
|
@classmethod
|
||||||
def _get_notification_exchanges(cls):
|
def _get_notification_exchanges(cls):
|
||||||
return ['nova']
|
return ['nova']
|
||||||
|
@ -78,38 +88,68 @@ class InstanceHandler(base.NotificationBase):
|
||||||
('old_task_state', payload.get('old_task_state')),
|
('old_task_state', payload.get('old_task_state')),
|
||||||
('new_task_state', payload.get('new_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):
|
def get_event_handlers(self):
|
||||||
return {
|
if not self._use_versioned_notifications():
|
||||||
# compute.instance.update seems to be the event set as a
|
return {
|
||||||
# result of a state change etc
|
# compute.instance.update seems to be the event set as a
|
||||||
'compute.instance.update': self.index_from_update,
|
# result of a state change etc
|
||||||
|
'compute.instance.update': self.index_from_update,
|
||||||
|
|
||||||
'compute.instance.create.start': self.index_from_api,
|
'compute.instance.create.start': self.index_from_api,
|
||||||
'compute.instance.create.end': self.index_from_api,
|
'compute.instance.create.end': self.index_from_api,
|
||||||
|
|
||||||
'compute.instance.power_on.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.power_off.end': self.index_from_api,
|
||||||
'compute.instance.resume.end': self.index_from_api,
|
'compute.instance.resume.end': self.index_from_api,
|
||||||
'compute.instance.suspend.end': self.index_from_api,
|
'compute.instance.suspend.end': self.index_from_api,
|
||||||
'compute.instance.pause.end': self.index_from_api,
|
'compute.instance.pause.end': self.index_from_api,
|
||||||
'compute.instance.unpause.end': self.index_from_api,
|
'compute.instance.unpause.end': self.index_from_api,
|
||||||
|
|
||||||
'compute.instance.shutdown.end': self.index_from_api,
|
'compute.instance.shutdown.end': self.index_from_api,
|
||||||
'compute.instance.reboot.end': self.index_from_api,
|
'compute.instance.reboot.end': self.index_from_api,
|
||||||
'compute.instance.delete.end': self.delete,
|
'compute.instance.delete.end': self.delete,
|
||||||
|
|
||||||
'compute.instance.shelve.end': self.index_from_api,
|
'compute.instance.shelve.end': self.index_from_api,
|
||||||
'compute.instance.shelve_offload.end': self.index_from_api,
|
'compute.instance.shelve_offload.end': self.index_from_api,
|
||||||
'compute.instance.unshelve.end': self.index_from_api,
|
'compute.instance.unshelve.end': self.index_from_api,
|
||||||
|
|
||||||
'compute.instance.volume.attach': self.index_from_api,
|
'compute.instance.volume.attach': self.index_from_api,
|
||||||
'compute.instance.volume.detach': self.index_from_api,
|
'compute.instance.volume.detach': self.index_from_api,
|
||||||
|
|
||||||
# Removing neutron port events for now; waiting on nova
|
# Removing neutron port events for now; waiting on nova
|
||||||
# to implement interface notifications as with volumes
|
# to implement interface notifications as with volumes
|
||||||
# https://launchpad.net/bugs/1567525
|
# https://launchpad.net/bugs/1567525
|
||||||
# https://blueprints.launchpad.net/nova/+spec/interface-notifications
|
# 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):
|
def index_from_update(self, event_type, payload, timestamp):
|
||||||
"""Determine whether or not to process a full update. The updates, and
|
"""Determine whether or not to process a full update. The updates, and
|
||||||
|
@ -325,6 +365,49 @@ class InstanceHandler(base.NotificationBase):
|
||||||
'from index: %(exc)s' %
|
'from index: %(exc)s' %
|
||||||
{'instance_id': instance_id, 'exc': exc})
|
{'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):
|
class ServerGroupHandler(base.NotificationBase):
|
||||||
"""Handles nova server group notifications.
|
"""Handles nova server group notifications.
|
||||||
|
|
|
@ -29,6 +29,7 @@ class ServerIndex(base.IndexBase):
|
||||||
NotificationHandlerCls = notification_handler.InstanceHandler
|
NotificationHandlerCls = notification_handler.InstanceHandler
|
||||||
|
|
||||||
# Will be combined with 'admin_only_fields' from config
|
# 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']
|
ADMIN_ONLY_FIELDS = ['OS-EXT-SRV-ATTR:*', 'host_status']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -90,6 +91,22 @@ class ServerIndex(base.IndexBase):
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'index': 'not_analyzed'
|
'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
|
# Nova gives security group names, where neutron ports
|
||||||
# give ids in the same field. There's no solution that
|
# give ids in the same field. There's no solution that
|
||||||
# maintains compatibility with both
|
# maintains compatibility with both
|
||||||
|
@ -163,7 +180,8 @@ class ServerIndex(base.IndexBase):
|
||||||
only be available to administrators.
|
only be available to administrators.
|
||||||
"""
|
"""
|
||||||
return {'tenant_id': True, 'project_id': True, 'host_status': True,
|
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
|
@property
|
||||||
def resource_allowed_policy_target(self):
|
def resource_allowed_policy_target(self):
|
||||||
|
|
|
@ -23,6 +23,7 @@ import six
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
|
||||||
|
from searchlight.common import exception as sl_exc
|
||||||
from searchlight.common import utils
|
from searchlight.common import utils
|
||||||
from searchlight.context import RequestContext
|
from searchlight.context import RequestContext
|
||||||
import searchlight.elasticsearch
|
import searchlight.elasticsearch
|
||||||
|
@ -526,3 +527,28 @@ def normalize_es_document(es_doc, plugin):
|
||||||
admin_context = RequestContext()
|
admin_context = RequestContext()
|
||||||
plugin.filter_result({'_source': es_doc}, admin_context)
|
plugin.filter_result({'_source': es_doc}, admin_context)
|
||||||
return es_doc
|
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
|
workers = 0
|
||||||
bind_host = 127.0.0.1
|
bind_host = 127.0.0.1
|
||||||
bind_port = %(bind_port)s
|
bind_port = %(bind_port)s
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.paste_conf_base = """[pipeline:searchlight]
|
self.paste_conf_base = """[pipeline:searchlight]
|
||||||
pipeline = versionnegotiation unauthenticated-context rootapp
|
pipeline = versionnegotiation unauthenticated-context rootapp
|
||||||
|
|
|
@ -329,8 +329,8 @@ class TestSearchApi(functional.FunctionalTest):
|
||||||
expected = {
|
expected = {
|
||||||
u'name': u'status',
|
u'name': u'status',
|
||||||
u'options': [
|
u'options': [
|
||||||
{u'doc_count': 2, u'key': u'ACTIVE'},
|
{u'doc_count': 2, u'key': u'active'},
|
||||||
{u'doc_count': 1, u'key': u'RESUMING'},
|
{u'doc_count': 1, u'key': u'resuming'},
|
||||||
],
|
],
|
||||||
u'type': u'string'
|
u'type': u'string'
|
||||||
}
|
}
|
||||||
|
@ -967,6 +967,7 @@ class TestSearchApi(functional.FunctionalTest):
|
||||||
u'id': 'abcdef',
|
u'id': 'abcdef',
|
||||||
u'tenant_id': TENANT1,
|
u'tenant_id': TENANT1,
|
||||||
u'user_id': USER1,
|
u'user_id': USER1,
|
||||||
|
u'status': 'ACTIVE',
|
||||||
u'image': {u'id': u'a'},
|
u'image': {u'id': u'a'},
|
||||||
u'flavor': {u'id': u'1'},
|
u'flavor': {u'id': u'1'},
|
||||||
u'created_at': u'2016-04-07T15:49:35Z',
|
u'created_at': u'2016-04-07T15:49:35Z',
|
||||||
|
@ -977,6 +978,7 @@ class TestSearchApi(functional.FunctionalTest):
|
||||||
u'id': '12341234',
|
u'id': '12341234',
|
||||||
u'tenant_id': TENANT2,
|
u'tenant_id': TENANT2,
|
||||||
u'user_id': USER1,
|
u'user_id': USER1,
|
||||||
|
u'status': 'ACTIVE',
|
||||||
u'image': {u'id': u'a'},
|
u'image': {u'id': u'a'},
|
||||||
u'flavor': {u'id': u'1'},
|
u'flavor': {u'id': u'1'},
|
||||||
u'created_at': u'2016-04-07T15:49:35Z',
|
u'created_at': u'2016-04-07T15:49:35Z',
|
||||||
|
|
|
@ -126,9 +126,8 @@ class TestNovaPlugins(functional.FunctionalTest):
|
||||||
TENANT_ID)
|
TENANT_ID)
|
||||||
self.assertEqual(200, response.status)
|
self.assertEqual(200, response.status)
|
||||||
self.assertEqual(1, json_content['hits']['total'])
|
self.assertEqual(1, json_content['hits']['total'])
|
||||||
|
|
||||||
hits = json_content['hits']['hits']
|
|
||||||
host_id = u'41d7069823d74c9ea8debda9a3a02bb00b2f7d53a0accd1f79429407'
|
host_id = u'41d7069823d74c9ea8debda9a3a02bb00b2f7d53a0accd1f79429407'
|
||||||
|
hits = json_content['hits']['hits']
|
||||||
expected_sources = [{
|
expected_sources = [{
|
||||||
u'OS-DCF:diskConfig': u'MANUAL',
|
u'OS-DCF:diskConfig': u'MANUAL',
|
||||||
u'OS-EXT-AZ:availability_zone': u'nova',
|
u'OS-EXT-AZ:availability_zone': u'nova',
|
||||||
|
@ -163,7 +162,7 @@ class TestNovaPlugins(functional.FunctionalTest):
|
||||||
u'owner': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
u'owner': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||||
u'project_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
u'project_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||||
u'security_groups': [{u'name': u'default'}],
|
u'security_groups': [{u'name': u'default'}],
|
||||||
u'status': u'ACTIVE',
|
u'status': u'active',
|
||||||
u'tenant_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
u'tenant_id': u'1dd2c5280b4e45fc9d7d08a81228c891',
|
||||||
u'updated': u'2016-03-08T08:40:22Z',
|
u'updated': u'2016-03-08T08:40:22Z',
|
||||||
u'user_id': u'7c97202cf58d43a9ab33016fc403f093'}]
|
u'user_id': u'7c97202cf58d43a9ab33016fc403f093'}]
|
||||||
|
@ -269,31 +268,6 @@ class TestNovaListeners(test_listener.TestSearchListenerBase):
|
||||||
)
|
)
|
||||||
self.listener_alias = self.servers_plugin.alias_name_listener
|
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):
|
def test_server_group_create_delete(self):
|
||||||
# Test #1: Create a server group.
|
# Test #1: Create a server group.
|
||||||
create_event = self.server_group_events["servergroup.create"]
|
create_event = self.server_group_events["servergroup.create"]
|
||||||
|
@ -393,3 +367,53 @@ class TestNovaListeners(test_listener.TestSearchListenerBase):
|
||||||
|
|
||||||
self.assertEqual(200, response.status)
|
self.assertEqual(200, response.status)
|
||||||
self.assertEqual(0, json_content['hits']['total'])
|
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'hostId': u'd86d2c042a1f233227f70c5e9d2c5829de98d222d0922f469054ac17',
|
||||||
|
u'host_name': u'devstack',
|
||||||
u'id': instance_id,
|
u'id': instance_id,
|
||||||
u'image': {
|
u'image': {
|
||||||
u'id': u'46b77e67-ce40-44ca-823d-e6f83489f21e',
|
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'os-extended-volumes:volumes_attached': [],
|
||||||
u'progress': 0,
|
u'progress': 0,
|
||||||
u'security_groups': [{u'name': u'default'}],
|
u'security_groups': [{u'name': u'default'}],
|
||||||
u'status': u'ACTIVE',
|
u'status': u'active',
|
||||||
u'tenant_id': tenant_id,
|
u'tenant_id': tenant_id,
|
||||||
u'updated': updated_now,
|
u'updated': updated_now,
|
||||||
u'user_id': USER1}
|
u'user_id': USER1}
|
||||||
|
@ -181,6 +182,15 @@ def _instance_fixture(instance_id, name, tenant_id, **kwargs):
|
||||||
class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestServerLoaderPlugin, self).setUp()
|
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.plugin = servers_plugin.ServerIndex()
|
||||||
self._create_fixtures()
|
self._create_fixtures()
|
||||||
|
|
||||||
|
@ -233,6 +243,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||||
u'config_drive': u'True',
|
u'config_drive': u'True',
|
||||||
u'flavor': {u'id': u'1'},
|
u'flavor': {u'id': u'1'},
|
||||||
u'hostId': u'host1',
|
u'hostId': u'host1',
|
||||||
|
u'host_name': u'devstack',
|
||||||
u'id': u'6c41b4d1-f0fa-42d6-9d8d-e3b99695aa69',
|
u'id': u'6c41b4d1-f0fa-42d6-9d8d-e3b99695aa69',
|
||||||
u'image': {u'id': u'a'},
|
u'image': {u'id': u'a'},
|
||||||
u'key_name': u'key',
|
u'key_name': u'key',
|
||||||
|
@ -241,7 +252,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||||
u'os-extended-volumes:volumes_attached': [],
|
u'os-extended-volumes:volumes_attached': [],
|
||||||
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'security_groups': [u'default'],
|
u'security_groups': [u'default'],
|
||||||
u'status': u'ACTIVE',
|
u'status': u'active',
|
||||||
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'updated': updated_now,
|
u'updated': updated_now,
|
||||||
|
@ -301,6 +312,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||||
u'config_drive': u'True',
|
u'config_drive': u'True',
|
||||||
u'flavor': {u'id': u'1'},
|
u'flavor': {u'id': u'1'},
|
||||||
u'hostId': u'host1',
|
u'hostId': u'host1',
|
||||||
|
u'host_name': u'devstack',
|
||||||
u'id': u'a380287d-1f61-4887-959c-8c5ab8f75f8f',
|
u'id': u'a380287d-1f61-4887-959c-8c5ab8f75f8f',
|
||||||
u'key_name': u'key',
|
u'key_name': u'key',
|
||||||
u'metadata': {},
|
u'metadata': {},
|
||||||
|
@ -309,7 +321,7 @@ class TestServerLoaderPlugin(test_utils.BaseTestCase):
|
||||||
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'owner': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'project_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'security_groups': [u'default'],
|
u'security_groups': [u'default'],
|
||||||
u'status': u'ACTIVE',
|
u'status': u'active',
|
||||||
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
u'tenant_id': u'4d64ac83-87af-4d2a-b884-cc42c3e8f2c0',
|
||||||
u'updated': updated_now,
|
u'updated': updated_now,
|
||||||
u'user_id': u'27f4d76b-be62-4e4e-aa33bb11cc55',
|
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',
|
network_facets = ('name', 'version', 'ipv6_addr', 'ipv4_addr',
|
||||||
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
||||||
expected_facet_names = [
|
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',
|
'flavor.id', 'id', 'image.id', 'locked', 'name',
|
||||||
'owner', 'security_groups', 'status', 'tags', 'updated_at',
|
'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])
|
expected_facet_names.extend(['networks.' + f for f in network_facets])
|
||||||
|
|
||||||
self.assertEqual(set(expected_facet_names), set(facet_names))
|
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',
|
network_facets = ('name', 'version', 'ipv6_addr', 'ipv4_addr',
|
||||||
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type')
|
||||||
expected_facet_names = [
|
expected_facet_names = [
|
||||||
|
'OS-EXT-SRV-ATTR:hypervisor_hostname',
|
||||||
'OS-EXT-AZ:availability_zone', 'created_at', 'description',
|
'OS-EXT-AZ:availability_zone', 'created_at', 'description',
|
||||||
'flavor.id', 'host_status', 'id', 'image.id', 'locked',
|
'flavor.id', 'host_status', 'id', 'image.id', 'locked',
|
||||||
'name', 'owner', 'project_id', 'security_groups', 'status',
|
'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])
|
expected_facet_names.extend(['networks.' + f for f in network_facets])
|
||||||
|
|
||||||
self.assertEqual(set(expected_facet_names), set(facet_names))
|
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:
|
return_value=self.instance1) as nova_getter:
|
||||||
|
|
||||||
type_handler('compute.instance.update', update_event,
|
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)
|
nova_getter.assert_called_with(instance_id)
|
||||||
self.assertEqual(1, mock_save.call_count)
|
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