Add neutron subnets and routers

Adds subnets and routers to supported neutron resources, in addition
to the networks and ports already merged. Subnets are configured as
a child plugin to networks; routers are independent.

This adds a listener for router.interface events on the Ports handler
because no separate notifications are received for those events.

Change-Id: If8efa7033bc99bebbea4e5b7564fc9ed150dbb37
Implements: blueprint neutron-subnet-router
This commit is contained in:
Steve McLellan 2016-03-01 16:05:30 -06:00
parent 8adf00a64b
commit 5efb890ca4
18 changed files with 1508 additions and 25 deletions

View File

@ -53,12 +53,26 @@ Plugin: OS::Neutron::Port
[resource_plugin:os_neutron_port] [resource_plugin:os_neutron_port]
enabled = true enabled = true
Plugin: OS::Neutron::Subnet
^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
[resource_plugin:os_neutron_subnet]
enabled = true
Plugin: OS::Neutron::Router
^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
[resource_plugin:os_neutron_router]
enabled = true
Neutron Configuration Neutron Configuration
===================== =====================
Neutron sends notifications on create/update/delete actions on the Neutron sends notifications on create/update/delete actions on the
concepts that it implements. Currently Searchlight supports indexing concepts that it implements. Currently Searchlight supports indexing
for networks and ports, with subnets and routers to follow. for networks, subnets, ports and routers.
neutron.conf neutron.conf
------------ ------------
@ -92,6 +106,8 @@ Release Notes
All provider:* properties of networks are exposed to administrators only. All provider:* properties of networks are exposed to administrators only.
All binding:* properties of ports are also visible only to administrators. All binding:* properties of ports are also visible only to administrators.
The 'distributed' and 'ha' router properties are available only to
administrators.
Additional properties can be protected similarly with the `admin_only_fields` Additional properties can be protected similarly with the `admin_only_fields`
under each plugin's configuration section. Glob-like patterns are supported. under each plugin's configuration section. Glob-like patterns are supported.
@ -103,3 +119,4 @@ For instance::
See: ADMIN_ONLY_FIELDS in: See: ADMIN_ONLY_FIELDS in:
* searchlight/elasticsearch/plugins/neutron/networks.py * searchlight/elasticsearch/plugins/neutron/networks.py
* searchlight/elasticsearch/plugins/neutron/ports.py * searchlight/elasticsearch/plugins/neutron/ports.py
* searchlight/elasticsearch/plugins/neutron/routers.py

View File

@ -14,5 +14,7 @@
"resource:OS::Designate::Zone:allow": "", "resource:OS::Designate::Zone:allow": "",
"resource:OS::Designate::RecordSet:allow": "", "resource:OS::Designate::RecordSet:allow": "",
"resource:OS::Neutron::Net:allow": "", "resource:OS::Neutron::Net:allow": "",
"resource:OS::Neutron::Port:allow": "" "resource:OS::Neutron::Port:allow": "",
"resource:OS::Neutron::Subnet:allow": "",
"resource:OS::Neutron::Router:allow": ""
} }

View File

@ -1,6 +1,7 @@
--- ---
features: features:
- Adds neutron plugins for networks and ports. - Adds neutron plugins for networks, subnets, ports
and routers.
issues: issues:
- Neutron resources do not provide dates (created_at - Neutron resources do not provide dates (created_at
or updated_at). created_at is left empty; updated_at or updated_at). created_at is left empty; updated_at

View File

@ -18,6 +18,14 @@ import copy
from searchlight.elasticsearch.plugins import utils from searchlight.elasticsearch.plugins import utils
def _add_dates(entity, updated_at=None):
"""Nothing in neutron appears to have any dates attached to it, or at
least not that we're able to get at.
"""
if not entity.get('updated_at'):
entity['updated_at'] = updated_at or utils.get_now_str()
def serialize_network(network, updated_at=None): def serialize_network(network, updated_at=None):
serialized = copy.deepcopy(network) serialized = copy.deepcopy(network)
# TODO(sjmc7): Once subnets are added, look at whether or not to # TODO(sjmc7): Once subnets are added, look at whether or not to
@ -33,3 +41,16 @@ def serialize_port(port, updated_at=None):
serialized = copy.deepcopy(port) serialized = copy.deepcopy(port)
serialized['updated_at'] = updated_at or utils.get_now_str() serialized['updated_at'] = updated_at or utils.get_now_str()
return serialized return serialized
def serialize_subnet(subnet):
serialized = copy.deepcopy(subnet)
serialized['project_id'] = serialized['tenant_id']
return serialized
def serialize_router(router, updated_at=None):
serialized = copy.deepcopy(router)
serialized['updated_at'] = updated_at or utils.get_now_str()
serialized['project_id'] = serialized['tenant_id']
return serialized

View File

@ -18,7 +18,11 @@ from oslo_log import log as logging
from searchlight.elasticsearch.plugins import base from searchlight.elasticsearch.plugins import base
from searchlight.elasticsearch.plugins.neutron import serialize_network from searchlight.elasticsearch.plugins.neutron import serialize_network
from searchlight.elasticsearch.plugins.neutron import serialize_port from searchlight.elasticsearch.plugins.neutron import serialize_port
from searchlight.elasticsearch.plugins.neutron import serialize_router
from searchlight.elasticsearch.plugins.neutron import serialize_subnet
from searchlight.elasticsearch.plugins import openstack_clients
from searchlight.elasticsearch.plugins import utils from searchlight.elasticsearch.plugins import utils
from searchlight import i18n from searchlight import i18n
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -73,7 +77,10 @@ class PortHandler(base.NotificationBase):
return { return {
'port.create.end': self.create_or_update, 'port.create.end': self.create_or_update,
'port.update.end': self.create_or_update, 'port.update.end': self.create_or_update,
'port.delete.end': self.delete 'port.delete.end': self.delete,
'router.interface.create': self.create_or_update_from_interface,
'router.interface.delete': self.delete_from_interface
} }
def create_or_update(self, payload, timestamp): def create_or_update(self, payload, timestamp):
@ -98,3 +105,90 @@ class PortHandler(base.NotificationBase):
'Error deleting port %(port_id)s ' 'Error deleting port %(port_id)s '
'from index. Error: %(exc)s') % 'from index. Error: %(exc)s') %
{'port_id': port_id, 'exc': exc}) {'port_id': port_id, 'exc': exc})
def create_or_update_from_interface(self, payload, timestamp):
"""Unfortunately there seems to be no notification for ports created
as part of a router interface creation, nor for DHCP ports. This
means we need to go to the API.
"""
port_id = payload['router_interface']['port_id']
LOG.debug("Retrieving port %s from API", port_id)
nc = openstack_clients.get_neutronclient()
port = nc.show_port(port_id)['port']
serialized = serialize_port(
port, updated_at=utils.timestamp_to_isotime(timestamp))
version = self.get_version(serialized, timestamp)
self.index_helper.save_document(serialized, version=version)
def delete_from_interface(self, payload, timestamp):
"""The partner of create_or_update_from_interface. There's no separate
port deletion notification.
"""
port_id = payload['router_interface']['port_id']
delete_payload = {'port_id': port_id}
self.delete(delete_payload, timestamp)
class SubnetHandler(base.NotificationBase):
@classmethod
def _get_notification_exchanges(cls):
return ['neutron']
def get_event_handlers(self):
return {
'subnet.create.end': self.create_or_update,
'subnet.update.end': self.create_or_update,
'subnet.delete.end': self.delete
}
def create_or_update(self, payload, timestamp):
subnet_id = payload['subnet']['id']
LOG.debug("Updating subnet information for %s", subnet_id)
subnet = serialize_subnet(payload['subnet'])
version = self.get_version(subnet, timestamp)
self.index_helper.save_document(subnet, version=version)
def delete(self, payload, timestamp):
subnet_id = payload['subnet_id']
LOG.debug("Deleting subnet information for %s", subnet_id)
try:
self.index_helper.delete_document({'_id': subnet_id})
except Exception as exc:
LOG.error(_LE(
'Error deleting subnet %(subnet_id)s '
'from index: %(exc)s') %
{'subnet_id': subnet_id, 'exc': exc})
class RouterHandler(base.NotificationBase):
@classmethod
def _get_notification_exchanges(cls):
return ['neutron']
def get_event_handlers(self):
return {
'router.create.end': self.create_or_update,
'router.update.end': self.create_or_update,
'router.delete.end': self.delete
}
def create_or_update(self, payload, timestamp):
router_id = payload['router']['id']
LOG.debug("Updating router information for %s", router_id)
router = serialize_router(
payload['router'],
updated_at=utils.timestamp_to_isotime(timestamp))
version = self.get_version(router, timestamp)
self.index_helper.save_document(router, version=version)
def delete(self, payload, timestamp):
router_id = payload['router_id']
LOG.debug("Deleting router information for %s", router_id)
try:
self.index_helper.delete_document({'_id': router_id})
except Exception as exc:
LOG.error(_LE(
'Error deleting router %(router)s '
'from index: %(exc)s') %
{'router': router_id, 'exc': exc})

View File

@ -0,0 +1,117 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# 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 searchlight.elasticsearch.plugins import base
from searchlight.elasticsearch.plugins.neutron import notification_handlers
from searchlight.elasticsearch.plugins.neutron import serialize_router
from searchlight.elasticsearch.plugins import openstack_clients
class RouterIndex(base.IndexBase):
NotificationHandlerCls = notification_handlers.RouterHandler
ADMIN_ONLY_FIELDS = ['distributed', 'ha']
@classmethod
def get_document_type(self):
return 'OS::Neutron::Router'
def get_mapping(self):
return {
'dynamic': 'false',
'properties': {
'admin_state_up': {'type': 'boolean'},
'availability_zone_hints': {'type': 'string',
'index': 'not_analyzed'},
'availability_zones': {'type': 'string',
'index': 'not_analyzed'},
# Routers don't have created_at
'description': {'type': 'string'},
'distributed': {'type': 'boolean'},
'external_gateway_info': {
'type': 'nested',
'properties': {
'enable_snat': {'type': 'boolean'},
'external_fixed_ips': {
'type': 'nested',
# TODO(sjmc7) Check we can deal with arbitrary
# levels of nesting with facets
'properties': {
'ip_address': {'type': 'string',
'index': 'not_analyzed'},
'subnet_id': {'type': 'string',
'index': 'not_analyzed'},
}
},
'network_id': {'type': 'string',
'index': 'not_analyzed'}
}
},
'ha': {'type': 'boolean'},
'id': {'type': 'string', 'index': 'not_analyzed'},
'name': {
'type': 'string',
'fields': {
'raw': {'type': 'string', 'index': 'not_analyzed'}
}
},
'project_id': {'type': 'string', 'index': 'not_analyzed'},
# TODO(sjmc7) Decide whether to keep these;
# they seem like trouble
'routes': {
'type': 'nested',
'properties': {
'destination': {'type': 'string',
'index': 'not_analyzed'},
'nexthop': {'type': 'string', 'index': 'not_analyzed'},
'action': {'type': 'string', 'index': 'not_analyzed'},
'source': {'type': 'string',
'index': 'not_analyzed'}
}
},
'status': {'type': 'string', 'index': 'not_analyzed'},
'tenant_id': {'type': 'string', 'index': 'not_analyzed'},
'updated_at': {'type': 'date'}
}
}
@property
def admin_only_fields(self):
from_conf = super(RouterIndex, self).admin_only_fields
return from_conf + RouterIndex.ADMIN_ONLY_FIELDS
@property
def facets_with_options(self):
return ('admin_state_up', 'availability_zones', 'status',
'distributed', 'ha', 'external_gateway_info.enable_snat')
@property
def facets_excluded(self):
return {'tenant_id': True, 'distributed': True, 'ha': True,
'project_id': False}
def _get_rbac_field_filters(self, request_context):
return [
{'term': {'tenant_id': request_context.owner}}
]
def get_objects(self):
neutron_client = openstack_clients.get_neutronclient()
for router in neutron_client.list_routers()['routers']:
yield router
def serialize(self, router):
return serialize_router(router)

View File

@ -0,0 +1,120 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# 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 searchlight.elasticsearch.plugins import base
from searchlight.elasticsearch.plugins.neutron import notification_handlers
from searchlight.elasticsearch.plugins.neutron import serialize_subnet
from searchlight.elasticsearch.plugins import openstack_clients
class SubnetIndex(base.IndexBase):
NotificationHandlerCls = notification_handlers.SubnetHandler
@classmethod
def get_document_type(self):
return 'OS::Neutron::Subnet'
@classmethod
def parent_plugin_type(cls):
return 'OS::Neutron::Net'
def get_parent_id_field(self):
return 'network_id'
def get_mapping(self):
return {
'dynamic': False,
'properties': {
'allocation_pools': {
'type': 'nested',
'properties': {
'start': {'type': 'string', 'index': 'not_analyzed'},
'end': {'type': 'string', 'index': 'not_analyzed'},
}
},
'cidr': {'type': 'string', 'index': 'not_analyzed'},
'created_at': {'type': 'date'},
'description': {'type': 'string'},
'dns_nameservers': {'type': 'string', 'index': 'not_analyzed'},
'enable_dhcp': {'type': 'boolean'},
'gateway_ip': {'type': 'string', 'index': 'not_analyzed'},
'host_routes': {
'type': 'nested',
'properties': {
'destination': {'type': 'string',
'index': 'not_analyzed'},
'next_hop': {'type': 'string',
'index': 'not_analyzed'}
}
},
'id': {'type': 'string', 'index': 'not_analyzed'},
'ip_version': {'type': 'short'},
'ipv6_address_mode': {'type': 'string',
'index': 'not_analyzed'},
'ipv6_ra_mode': {'type': 'string', 'index': 'not_analyzed'},
'name': {
'type': 'string',
'fields': {
'raw': {'type': 'string', 'index': 'not_analyzed'}
}
},
'network_id': {'type': 'string', 'index': 'not_analyzed'},
'project_id': {'type': 'string', 'index': 'not_analyzed'},
'subnetpool_id': {'type': 'string', 'index': 'not_analyzed'},
'tenant_id': {'type': 'string', 'index': 'not_analyzed'},
'updated_at': {'type': 'date'}
}
}
@property
def requires_role_separation(self):
return self.parent_plugin.requires_role_separation
@property
def facets_with_options(self):
return ('ip_version', 'dns_nameservers', 'network_id', 'enable_dhcp',
'ipv6_address_mode', 'ipv6_ra_mode')
@property
def facets_excluded(self):
"""A map of {name: allow_admin} that indicate which
fields should not be offered as facet options, or those that should
only be available to administrators.
"""
return {'tenant_id': True, 'project_id': False}
def _get_rbac_field_filters(self, request_context):
"""Subnets are visible to their owners and if they belong to networks
with the 'shared' property.
"""
return [{
"or": [
{'term': {'tenant_id': request_context.owner}},
{
'has_parent': {
'type': self.parent_plugin_type(),
'query': {'term': {'shared': True}}
}
}
]
}]
def get_objects(self):
neutron_client = openstack_clients.get_neutronclient()
for subnet in neutron_client.list_subnets()['subnets']:
yield subnet
def serialize(self, subnet):
return serialize_subnet(subnet)

View File

@ -374,19 +374,23 @@ class FunctionalTest(test_utils.BaseTestCase):
plugin_classes = { plugin_classes = {
'glance': {'images': 'ImageIndex', 'metadefs': 'MetadefIndex'}, 'glance': {'images': 'ImageIndex', 'metadefs': 'MetadefIndex'},
'nova': {'servers': 'ServerIndex'}, 'nova': {'servers': 'ServerIndex'},
'neutron': {'networks': 'NetworkIndex', 'ports': 'PortIndex'},
'cinder': {'volumes': 'VolumeIndex', 'snapshots': 'SnapshotIndex'}, 'cinder': {'volumes': 'VolumeIndex', 'snapshots': 'SnapshotIndex'},
'neutron': {'networks': 'NetworkIndex', 'ports': 'PortIndex',
'subnets': 'SubnetIndex', 'routers': 'RouterIndex'},
'swift': {'accounts': 'AccountIndex', 'swift': {'accounts': 'AccountIndex',
'containers': 'ContainerIndex', 'containers': 'ContainerIndex',
'objects': 'ObjectIndex'} 'objects': 'ObjectIndex'},
} }
plugins = include_plugins or ( plugins = include_plugins or (
('glance', 'images'), ('glance', 'metadefs'), ('glance', 'images'), ('glance', 'metadefs'),
('nova', 'servers'), ('nova', 'servers'),
('cinder', 'volumes'), ('cinder', 'snapshots'),
('neutron', 'networks'), ('neutron', 'ports'), ('neutron', 'networks'), ('neutron', 'ports'),
('neutron', 'subnets'), ('neutron', 'routers'),
('cinder', 'volumes'), ('cinder', 'snapshots'), ('cinder', 'volumes'), ('cinder', 'snapshots'),
('swift', 'accounts'), ('swift', 'containers'), ('swift', 'accounts'), ('swift', 'containers'),
('swift', 'objects') ('swift', 'objects'),
) )
plugins = filter(lambda plugin: plugin not in exclude_plugins, plugins) plugins = filter(lambda plugin: plugin not in exclude_plugins, plugins)

View File

@ -0,0 +1,281 @@
{
"router.create.end": {
"event_type": "router.create.end",
"payload": {
"router": {
"status": "ACTIVE",
"external_gateway_info": null,
"availability_zone_hints": [],
"availability_zones": [],
"name": "test-router",
"admin_state_up": true,
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"distributed": false,
"routes": [],
"ha": false,
"id": "0b143748-44d2-4545-9230-864c3abbc786"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "20cd67462bb540b98210f7dd118b7ce2",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-64f15ef0-c172-44da-bb7e-eafac7ebdee0",
"user_domain": null,
"timestamp": "2016-03-13 23:36:25.080945",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:36:25.145439",
"message_id": "0d408f9c-d996-40b5-98fe-738765ae093d"
}
},
"router.update.end": {
"event_type": "router.update.end",
"payload": {
"router": {
"status": "ACTIVE",
"external_gateway_info": null,
"availability_zone_hints": [],
"availability_zones": [],
"name": "renamed router",
"admin_state_up": true,
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"distributed": false,
"routes": [],
"ha": false,
"id": "0b143748-44d2-4545-9230-864c3abbc786"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "32846cb7f8524e13ae0b399e7dd19f03",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-17f689d2-0f35-4eb7-8074-bed59a2151f0",
"user_domain": null,
"timestamp": "2016-03-13 23:38:18.481114",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:38:18.651866",
"message_id": "e90ab0d3-2ba6-476b-b092-af44ecc29532"
}
},
"router.interface.create": {
"event_type": "router.interface.create",
"payload": {
"router_interface": {
"network_id": "60e80dca-302d-4460-8984-9b85dc782bca",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"subnet_id": "4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805",
"subnet_ids": [
"4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805"
],
"port_id": "a5324522-47d3-4547-85df-a01ef6bde4b1",
"id": "0b143748-44d2-4545-9230-864c3abbc786"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "b29f0dd426ca41af90ba36eb022ddbda",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-f9be76a5-dcf1-4296-9add-a06e8105413e",
"user_domain": null,
"timestamp": "2016-03-13 23:42:47.959774",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:42:48.273491",
"message_id": "a90d5a20-5206-42e3-ab9b-08309cf2d96c"
}
},
"router.interface.delete": {
"event_type": "router.interface.delete",
"payload": {
"router_interface": {
"network_id": "60e80dca-302d-4460-8984-9b85dc782bca",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"subnet_id": "4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805",
"subnet_ids": [
"4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805"
],
"port_id": "a5324522-47d3-4547-85df-a01ef6bde4b1",
"id": "0b143748-44d2-4545-9230-864c3abbc786"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "3d9c4d8a583f42ad84cf8f45d647937a",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": false,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-1c3bc795-43b1-4ad8-aa17-b6f2b5c3fe2b",
"user_domain": null,
"timestamp": "2016-03-17 17:29:32.388395",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:42:51.273491",
"message_id": "6d917992-07ef-471e-9720-6c5e3b60f2d4"
}
},
"router.gateway.set": {
"event_type": "router.update.end",
"payload": {
"router": {
"status": "ACTIVE",
"external_gateway_info": {
"network_id": "8891323e-bf5b-48d7-a75e-669af0608538",
"enable_snat": true,
"external_fixed_ips": [
{
"subnet_id": "8cbe9b71-ecf1-4355-b5ba-dee54ec88fa7",
"ip_address": "172.25.0.3"
},
{
"subnet_id": "d7774648-ba81-477a-9329-2acc9a810e50",
"ip_address": "2001:db8::3"
}
]
},
"availability_zone_hints": [],
"availability_zones": [
"nova"
],
"name": "renamed router",
"admin_state_up": true,
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"distributed": false,
"routes": [],
"ha": false,
"id": "0b143748-44d2-4545-9230-864c3abbc786"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "f55ae4544c6040358bad962ac20f725d",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-0f16f883-c413-480a-9d4b-4e79fc7fd476",
"user_domain": null,
"timestamp": "2016-03-13 23:46:39.283489",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:46:39.809556",
"message_id": "7c92b12c-1477-4e87-a76a-024518da354b"
}
},
"router.delete.end": {
"event_type": "router.delete.end",
"payload": {
"router_id": "0b143748-44d2-4545-9230-864c3abbc786"
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "ee110ae55ebc4ca0af695e106745833c",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-adba6854-8bcf-47a5-9710-7a7a7df33a37",
"user_domain": null,
"timestamp": "2016-03-14 00:44:56.857061",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-14 00:44:57.126816",
"message_id": "56428f7d-3f07-4b23-882d-612d8852e422"
}
}
}

View File

@ -0,0 +1,151 @@
{
"subnet.create.end": {
"event_type": "subnet.create.end",
"payload": {
"subnet": {
"name": "",
"enable_dhcp": true,
"network_id": "60e80dca-302d-4460-8984-9b85dc782bca",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"dns_nameservers": [],
"ipv6_ra_mode": null,
"allocation_pools": [
{
"start": "172.45.0.2",
"end": "172.45.255.254"
}
],
"gateway_ip": "172.45.0.1",
"ipv6_address_mode": null,
"ip_version": 4,
"host_routes": [],
"cidr": "172.45.0.0/16",
"id": "4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805",
"subnetpool_id": null,
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:41:32"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "2af46d5627ef422491be4400038b5709",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-8148c629-919d-4fac-b8ff-57220d7f10b3",
"user_domain": null,
"timestamp": "2016-03-13 23:41:32.473691",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:41:32.562400",
"message_id": "7e3277a1-68cb-4114-87bf-a622d0417ac7"
}
},
"subnet.update.end": {
"event_type": "subnet.update.end",
"payload": {
"subnet": {
"name": "Updated subnet",
"enable_dhcp": true,
"network_id": "60e80dca-302d-4460-8984-9b85dc782bca",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"dns_nameservers": [],
"ipv6_ra_mode": null,
"allocation_pools": [
{
"start": "172.45.0.2",
"end": "172.45.255.254"
}
],
"gateway_ip": "172.45.0.1",
"ipv6_address_mode": null,
"ip_version": 4,
"host_routes": [],
"cidr": "172.45.0.0/16",
"id": "4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805",
"subnetpool_id": null,
"updated_at": "2016-03-13T23:42:20",
"created_at": "2016-03-13T23:41:32"
}
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "cc90d47602134401802295b0b109ab26",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-0171556b-f68e-48ff-93a4-9214b286087b",
"user_domain": null,
"timestamp": "2016-03-13 23:42:20.408564",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-13 23:42:20.631587",
"message_id": "cf136c26-9aa6-40ed-80ac-afc468ca09d7"
}
},
"subnet.delete.end": {
"event_type": "subnet.delete.end",
"payload": {
"subnet_id": "4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805"
},
"publisher_id": "network.devstack",
"ctxt": {
"read_only": false,
"domain": null,
"project_name": "admin",
"user_id": "d2ac2752732444adac10fc4911fdac75",
"show_deleted": false,
"roles": [
"admin"
],
"user_identity": "d2ac2752732444adac10fc4911fdac75 75c31cdaa3604b76b7e279de50aec9f0 - - -",
"project_domain": null,
"tenant_name": "admin",
"auth_token": "f4611508539a43a6b3511e6f8bdd8492",
"resource_uuid": null,
"project_id": "75c31cdaa3604b76b7e279de50aec9f0",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"is_admin": true,
"user": "d2ac2752732444adac10fc4911fdac75",
"request_id": "req-0c5ccd33-730d-40ca-86d9-3b76b7dbc568",
"user_domain": null,
"timestamp": "2016-03-14 00:43:17.952733",
"tenant": "75c31cdaa3604b76b7e279de50aec9f0",
"user_name": "admin"
},
"metadata": {
"timestamp": "2016-03-14 00:43:18.143933",
"message_id": "adac0012-c79c-4905-9a24-1777f9675808"
}
}
}

View File

@ -25,7 +25,22 @@
"router:external": false, "router:external": false,
"shared": true, "shared": true,
"status": "ACTIVE", "status": "ACTIVE",
"subnets": ["63785b90-f3d1-4124-b2e9-1e62dd7c8dab"], "subnets": ["def"],
"tenant_id": "aaaaaabbbbbbccccc555552222255511"
},
{
"admin_state_up": true,
"id": "00000000-4808-4c88-8c3b-000000000000",
"mtu": 0,
"name": "test-not-shared",
"port_security_enabled": true,
"provider:network_type": "vxlan",
"provider:physical_network": null,
"provider:segmentation_id": 1078,
"router:external": false,
"shared": false,
"status": "ACTIVE",
"subnets": ["abc"],
"tenant_id": "aaaaaabbbbbbccccc555552222255511" "tenant_id": "aaaaaabbbbbbccccc555552222255511"
}, },
{ {

View File

@ -0,0 +1,60 @@
{
"routers": [
{
"admin_state_up": true,
"availability_zone_hints": [],
"availability_zones": [
"nova"
],
"distributed": false,
"external_gateway_info": {
"enable_snat": true,
"external_fixed_ips": [
{
"ip_address": "172.25.0.3",
"subnet_id": "8cbe9b71-ecf1-4355-b5ba-dee54ec88fa7"
},
{
"ip_address": "2001:db8::3",
"subnet_id": "d7774648-ba81-477a-9329-2acc9a810e50"
}
],
"network_id": "8891323e-bf5b-48d7-a75e-669af0608538"
},
"ha": false,
"id": "0b143748-44d2-4545-9230-864c3abbc786",
"name": "renamed router",
"routes": [],
"status": "ACTIVE",
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0"
},
{
"admin_state_up": true,
"availability_zone_hints": [],
"availability_zones": [
"nova"
],
"distributed": true,
"external_gateway_info": {
"enable_snat": true,
"external_fixed_ips": [
{
"ip_address": "172.25.0.2",
"subnet_id": "8cbe9b71-ecf1-4355-b5ba-dee54ec88fa7"
},
{
"ip_address": "2001:db8::1",
"subnet_id": "d7774648-ba81-477a-9329-2acc9a810e50"
}
],
"network_id": "8891323e-bf5b-48d7-a75e-669af0608538"
},
"ha": false,
"id": "324d16fc-c381-4ea4-8f77-feda17cea1d7",
"name": "router1",
"routes": [],
"status": "ACTIVE",
"tenant_id": "1816a16093df465dbc609cf638422a05"
}
]
}

View File

@ -0,0 +1,122 @@
{
"subnets": [
{
"allocation_pools": [
{
"end": "172.25.255.254",
"start": "172.25.0.2"
}
],
"cidr": "172.25.0.0/16",
"dns_nameservers": [],
"enable_dhcp": false,
"gateway_ip": "172.25.0.1",
"host_routes": [],
"id": "8cbe9b71-ecf1-4355-b5ba-dee54ec88fa7",
"ip_version": 4,
"ipv6_address_mode": null,
"ipv6_ra_mode": null,
"name": "public-subnet",
"network_id": "8891323e-bf5b-48d7-a75e-669af0608538",
"subnetpool_id": null,
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:45:32"
},
{
"allocation_pools": [
{
"end": "2001:db8::1",
"start": "2001:db8::1"
},
{
"end": "2001:db8::ffff:ffff:ffff:ffff",
"start": "2001:db8::3"
}
],
"cidr": "2001:db8::/64",
"dns_nameservers": [],
"enable_dhcp": false,
"gateway_ip": "2001:db8::2",
"host_routes": [],
"id": "d7774648-ba81-477a-9329-2acc9a810e50",
"ip_version": 6,
"ipv6_address_mode": null,
"ipv6_ra_mode": null,
"name": "ipv6-public-subnet",
"network_id": "8891323e-bf5b-48d7-a75e-669af0608538",
"subnetpool_id": null,
"tenant_id": "75c31cdaa3604b76b7e279de50aec9f0",
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:45:32"
},
{
"allocation_pools": [
{
"end": "fd2c:ac9d:23d1:0:ffff:ffff:ffff:ffff",
"start": "fd2c:ac9d:23d1::2"
}
],
"cidr": "fd2c:ac9d:23d1::/64",
"dns_nameservers": [],
"enable_dhcp": true,
"gateway_ip": "fd2c:ac9d:23d1::1",
"host_routes": [],
"id": "12e00c04-7699-4f5a-80e7-8b84c65b2ee3",
"ip_version": 6,
"ipv6_address_mode": "slaac",
"ipv6_ra_mode": "slaac",
"name": "ipv6-private-subnet",
"network_id": "60e80dca-302d-4460-8984-9b85dc782bca",
"subnetpool_id": null,
"tenant_id": "1816a16093df465dbc609cf638422a05",
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:45:32"
},
{
"allocation_pools": [
{
"end": "fd2c:ac9d:23d1:0:ffff:ffff:ffff:ffff",
"start": "fd2c:ac9d:23d1::2"
}
],
"cidr": "fd2c:ac9d:23d1::/64",
"dns_nameservers": [],
"enable_dhcp": true,
"gateway_ip": "4444:ac9d:54d1::1",
"host_routes": [],
"id": "fe6b1c72-2560-421d-bd4c-867e545d1234",
"ip_version": 6,
"ipv6_address_mode": "slaac",
"ipv6_ra_mode": "slaac",
"name": "shared-subnet",
"network_id": "deadbeef-4808-4c88-8c3b-deadbeefdead",
"subnetpool_id": null,
"tenant_id": "aaaaaabbbbbbccccc555552222255511",
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:45:32"
},
{
"allocation_pools": [
{
"end": "fd2c:ac9d:23d1:0:ffff:ffff:ffff:ffff",
"start": "fd2c:ac9d:23d1::2"
}
],
"cidr": "fd2c:ac9d:23d1::/64",
"dns_nameservers": [],
"enable_dhcp": true,
"gateway_ip": "4444:ac9d:54d1::1",
"host_routes": [],
"id": "0134324324-0000-aaaa-cccc-543534534",
"ip_version": 6,
"ipv6_address_mode": "slaac",
"ipv6_ra_mode": "slaac",
"name": "not-shared-subnet",
"network_id": "11112222-4808-4c88-8c3b-111122223333",
"subnetpool_id": null,
"tenant_id": "aaaaaabbbbbbccccc555552222255511",
"updated_at": "2016-03-13T23:41:32",
"created_at": "2016-03-13T23:45:32"
}
]}

View File

@ -26,6 +26,16 @@ from searchlight.tests.functional import test_listener
# This is in the load file # This is in the load file
TENANT1 = "8eaac046b2c44ab99246cb0850c7f06d" TENANT1 = "8eaac046b2c44ab99246cb0850c7f06d"
TENANT2 = "aaaaaabbbbbbccccc555552222255511" TENANT2 = "aaaaaabbbbbbccccc555552222255511"
# Subnets and routers use these
TENANT3 = "75c31cdaa3604b76b7e279de50aec9f0"
TENANT4 = "1816a16093df465dbc609cf638422a05"
NETID3 = "8891323e-bf5b-48d7-a75e-669af0608538"
NETID4 = "60e80dca-302d-4460-8984-9b85dc782bca"
SHARED_NET_ID = "deadbeef-4808-4c88-8c3b-deadbeefdead"
_now_str = timeutils.isotime(datetime.datetime.utcnow()) _now_str = timeutils.isotime(datetime.datetime.utcnow())
@ -33,8 +43,17 @@ class TestNeutronPlugins(functional.FunctionalTest):
def setUp(self): def setUp(self):
super(TestNeutronPlugins, self).setUp() super(TestNeutronPlugins, self).setUp()
self.networks_plugin = self.initialized_plugins['OS::Neutron::Net'] self.networks_plugin = self.initialized_plugins['OS::Neutron::Net']
self.subnets_plugin = self.initialized_plugins['OS::Neutron::Subnet']
self.routers_plugin = self.initialized_plugins['OS::Neutron::Router']
self.network_objects = self._load_fixture_data('load/networks.json') self.network_objects = self._load_fixture_data('load/networks.json')
self.subnet_objects = self._load_fixture_data('load/subnets.json')
self.subnet_objects = self.subnet_objects['subnets']
self.router_objects = self._load_fixture_data('load/routers.json')
self.router_objects = self.router_objects['routers']
@mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str') @mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str')
def test_network_rbac_tenant(self, mock_utcnow_str): def test_network_rbac_tenant(self, mock_utcnow_str):
mock_utcnow_str.return_value = _now_str mock_utcnow_str.return_value = _now_str
@ -50,10 +69,11 @@ class TestNeutronPlugins(functional.FunctionalTest):
response, json_content = self._search_request(test_api.MATCH_ALL, response, json_content = self._search_request(test_api.MATCH_ALL,
TENANT2) TENANT2)
self.assertEqual(200, response.status) self.assertEqual(200, response.status)
self.assertEqual(2, json_content['hits']['total']) self.assertEqual(3, json_content['hits']['total'])
hits = json_content['hits']['hits'] hits = json_content['hits']['hits']
expected_names = ['test-shared', 'test-external-router'] expected_names = ['test-shared', 'test-external-router',
'test-not-shared']
actual_names = [hit['_source']['name'] for hit in hits] actual_names = [hit['_source']['name'] for hit in hits]
self.assertEqual(set(expected_names), set(actual_names)) self.assertEqual(set(expected_names), set(actual_names))
@ -62,7 +82,7 @@ class TestNeutronPlugins(functional.FunctionalTest):
{"query": {"match_all": {}}, "all_projects": True}, {"query": {"match_all": {}}, "all_projects": True},
TENANT2, role="admin") TENANT2, role="admin")
self.assertEqual(200, response.status) self.assertEqual(200, response.status)
self.assertEqual(3, json_content['hits']['total']) self.assertEqual(4, json_content['hits']['total'])
@mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str') @mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str')
def test_network_rbac_shared_external(self, mock_utcnow_str): def test_network_rbac_shared_external(self, mock_utcnow_str):
@ -91,30 +111,207 @@ class TestNeutronPlugins(functional.FunctionalTest):
self.assertEqual(set(expected_names), set(actual_names)) self.assertEqual(set(expected_names), set(actual_names))
@mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str')
def test_subnet_rbac(self, mock_utcnow_str):
"""These tests intentionally don't set a parent network so that they're
just testing the tenant id check
"""
mock_utcnow_str.return_value = _now_str
serialized_subnets = [self.subnets_plugin.serialize(subnet)
for subnet in self.subnet_objects
if subnet["tenant_id"] == TENANT3]
self._index(self.subnets_plugin.alias_name_listener,
self.subnets_plugin.get_document_type(),
serialized_subnets,
TENANT3,
parent_id_field="network_id",
role_separation=True)
class TestNeutronListener(test_listener.TestSearchListenerBase): serialized_subnets = [self.subnets_plugin.serialize(subnet)
for subnet in self.subnet_objects
if subnet["tenant_id"] == TENANT4]
self._index(self.subnets_plugin.alias_name_listener,
self.subnets_plugin.get_document_type(),
serialized_subnets,
TENANT4,
parent_id_field="network_id",
role_separation=True)
response, json_content = self._search_request(
{"query": {"match_all": {}}, "type": "OS::Neutron::Subnet"},
TENANT3)
self.assertEqual(200, response.status)
self.assertEqual(2, json_content['hits']['total'])
self.assertEqual(set([NETID3]),
set(hit["_source"]["network_id"]
for hit in json_content['hits']['hits']))
@mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str')
def test_shared_network_subnet(self, mock_utcnow_str):
mock_utcnow_str.return_value = _now_str
role_sep = self.networks_plugin.requires_role_separation
tenant2_nets = [self.networks_plugin.serialize(net)
for net in self.network_objects
if net['tenant_id'] == TENANT2]
self._index(self.networks_plugin.alias_name_listener,
self.networks_plugin.get_document_type(),
tenant2_nets,
TENANT2,
role_separation=role_sep)
# Index the subnets that belongs to the shared network
tenant2_subnets = [self.subnets_plugin.serialize(subnet)
for subnet in self.subnet_objects
if subnet["tenant_id"] == TENANT2]
self._index(self.subnets_plugin.alias_name_listener,
self.subnets_plugin.get_document_type(),
tenant2_subnets,
TENANT2,
parent_id_field="network_id",
role_separation=True)
# There are now two networks each with one subnet. Both should
# show up for tenant 2 but only one for tenant3
response, json_content = self._search_request(
{"query": {"match_all": {}}, "type": "OS::Neutron::Subnet"},
TENANT2)
self.assertEqual(2, json_content['hits']['total'])
self.assertEqual(
set(['shared-subnet', 'not-shared-subnet']),
set([h['_source']['name'] for h in json_content['hits']['hits']]))
# Tenant 3 can see the shared network's subnet
response, json_content = self._search_request(
{"query": {"match_all": {}}, "type": "OS::Neutron::Subnet"},
TENANT3)
self.assertEqual(1, json_content['hits']['total'])
self.assertEqual(
'shared-subnet',
json_content['hits']['hits'][0]['_source']['name'])
@mock.patch('searchlight.elasticsearch.plugins.utils.get_now_str')
def test_router_rbac(self, mock_utcnow_str):
mock_utcnow_str.return_value = _now_str
serialized_routers = [self.routers_plugin.serialize(router)
for router in self.router_objects
if router["tenant_id"] == TENANT3]
role_sep = self.routers_plugin.requires_role_separation
self.assertTrue(role_sep)
self._index(self.routers_plugin.alias_name_listener,
self.routers_plugin.get_document_type(),
serialized_routers,
TENANT3,
role_separation=role_sep)
serialized_routers = [self.routers_plugin.serialize(router)
for router in self.router_objects
if router["tenant_id"] == TENANT4]
self._index(self.routers_plugin.alias_name_listener,
self.routers_plugin.get_document_type(),
serialized_routers,
TENANT4,
role_separation=role_sep)
query = {"query": {"match_all": {}}, "type": "OS::Neutron::Router"}
response, json_content = self._search_request(query,
TENANT3)
self.assertEqual(200, response.status)
self.assertEqual(1, json_content['hits']['total'])
self.assertEqual('0b143748-44d2-4545-9230-864c3abbc786',
json_content['hits']['hits'][0]['_source']['id'])
response, json_content = self._search_request(query,
TENANT4)
self.assertEqual(200, response.status)
self.assertEqual(1, json_content['hits']['total'])
self.assertEqual('324d16fc-c381-4ea4-8f77-feda17cea1d7',
json_content['hits']['hits'][0]['_source']['id'])
def test_router_fixed_ip(self):
serialized_routers = [self.routers_plugin.serialize(router)
for router in self.router_objects
if router["tenant_id"] == TENANT4]
role_sep = self.routers_plugin.requires_role_separation
self._index(self.routers_plugin.alias_name_listener,
self.routers_plugin.get_document_type(),
serialized_routers,
TENANT4,
role_separation=role_sep)
query = {
"query": {
"nested": {
"path": "external_gateway_info",
"query": {
"term": {"external_gateway_info.enable_snat": True}
}
}
},
"type": "OS::Neutron::Router"
}
response, json_content = self._search_request(query,
TENANT4)
self.assertEqual(200, response.status)
self.assertEqual(1, json_content['hits']['total'])
fixed_ip_path = "external_gateway_info.external_fixed_ips"
ip_addr = "%s.ip_address" % fixed_ip_path
net_id = "external_gateway_info.network_id"
query = {
"query": {
"nested": {
"path": "external_gateway_info",
"query": {
"bool": {
"must": [
{"term": {net_id: NETID3}},
{"nested": {
"path": fixed_ip_path,
"query": {
"term": {ip_addr: "2001:db8::1"}}
}}
]
}
}
}
}
}
response, json_content = self._search_request(query,
TENANT4)
self.assertEqual(200, response.status)
self.assertEqual(1, json_content['hits']['total'])
class TestNeutronListeners(test_listener.TestSearchListenerBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TestNeutronListener, self).__init__(*args, **kwargs) super(TestNeutronListeners, self).__init__(*args, **kwargs)
self.network_events = self._load_fixture_data('events/networks.json') self.network_events = self._load_fixture_data('events/networks.json')
self.port_events = self._load_fixture_data('events/ports.json') self.port_events = self._load_fixture_data('events/ports.json')
self.subnet_events = self._load_fixture_data('events/subnets.json')
self.router_events = self._load_fixture_data('events/routers.json')
def setUp(self): def setUp(self):
super(TestNeutronListener, self).setUp() super(TestNeutronListeners, self).setUp()
self.networks_plugin = self.initialized_plugins['OS::Neutron::Net'] self.networks_plugin = self.initialized_plugins['OS::Neutron::Net']
self.ports_plugin = self.initialized_plugins['OS::Neutron::Port'] self.ports_plugin = self.initialized_plugins['OS::Neutron::Port']
self.subnets_plugin = self.initialized_plugins['OS::Neutron::Subnet']
self.routers_plugin = self.initialized_plugins['OS::Neutron::Router']
notification_plugins = { notification_plugins = {
plugin.document_type: test_listener.StevedoreMock(plugin) plugin.document_type: test_listener.StevedoreMock(plugin)
for plugin in (self.networks_plugin, self.ports_plugin)} for plugin in (self.networks_plugin, self.ports_plugin,
self.subnets_plugin, self.routers_plugin)}
self.notification_endpoint = NotificationEndpoint(notification_plugins) self.notification_endpoint = NotificationEndpoint(notification_plugins)
self.index_name = self.networks_plugin.alias_name_listener self.listener_alias = self.networks_plugin.alias_name_listener
def test_network_create_update_delete(self): def test_network_create_update_delete(self):
'''Send network.create.end notification event to listener''' '''Send network.create.end notification event to listener'''
create_event = self.network_events['network.create.end'] create_event = self.network_events['network.create.end']
self._send_event_to_listener(create_event, self.index_name) self._send_event_to_listener(create_event, self.listener_alias)
result = self._verify_event_processing(create_event, owner=TENANT1) result = self._verify_event_processing(create_event, owner=TENANT1)
verification_keys = ['id', 'status', 'port_security_enabled', 'name'] verification_keys = ['id', 'status', 'port_security_enabled', 'name']
self._verify_result(create_event, verification_keys, result, self._verify_result(create_event, verification_keys, result,
@ -124,20 +321,20 @@ class TestNeutronListener(test_listener.TestSearchListenerBase):
self.assertEqual('2016-01-13T17:39:04Z', hit['updated_at']) self.assertEqual('2016-01-13T17:39:04Z', hit['updated_at'])
update_event = self.network_events['network.update.end'] update_event = self.network_events['network.update.end']
self._send_event_to_listener(update_event, self.index_name) self._send_event_to_listener(update_event, self.listener_alias)
result = self._verify_event_processing(update_event, owner=TENANT1) result = self._verify_event_processing(update_event, owner=TENANT1)
verification_keys = ['id', 'status', 'port_security_enabled', 'name'] verification_keys = ['id', 'status', 'port_security_enabled', 'name']
self._verify_result(update_event, verification_keys, result, self._verify_result(update_event, verification_keys, result,
inner_key='network') inner_key='network')
delete_event = self.network_events['network.delete.end'] delete_event = self.network_events['network.delete.end']
self._send_event_to_listener(delete_event, self.index_name) self._send_event_to_listener(delete_event, self.listener_alias)
self._verify_event_processing(delete_event, count=0, self._verify_event_processing(delete_event, count=0,
owner=TENANT1) owner=TENANT1)
def test_port_create_event(self): def test_port_create_event(self):
create_event = self.port_events['port.create.end'] create_event = self.port_events['port.create.end']
self._send_event_to_listener(create_event, self.index_name) self._send_event_to_listener(create_event, self.listener_alias)
result = self._verify_event_processing(create_event, owner=TENANT1) result = self._verify_event_processing(create_event, owner=TENANT1)
verification_keys = ['id', 'status', 'mac_address', 'status'] verification_keys = ['id', 'status', 'mac_address', 'status']
self._verify_result(create_event, verification_keys, result, self._verify_result(create_event, verification_keys, result,
@ -147,7 +344,7 @@ class TestNeutronListener(test_listener.TestSearchListenerBase):
def test_port_rename_event(self): def test_port_rename_event(self):
update_event = self.port_events['port_rename'] update_event = self.port_events['port_rename']
self._send_event_to_listener(update_event, self.index_name) self._send_event_to_listener(update_event, self.listener_alias)
result = self._verify_event_processing(update_event, owner=TENANT1) result = self._verify_event_processing(update_event, owner=TENANT1)
verification_keys = ['name'] verification_keys = ['name']
self._verify_result(update_event, verification_keys, result, self._verify_result(update_event, verification_keys, result,
@ -155,21 +352,21 @@ class TestNeutronListener(test_listener.TestSearchListenerBase):
def test_port_attach_detach_events(self): def test_port_attach_detach_events(self):
create_event = self.port_events['port.create.end'] create_event = self.port_events['port.create.end']
self._send_event_to_listener(create_event, self.index_name) self._send_event_to_listener(create_event, self.listener_alias)
result = self._verify_event_processing(create_event, owner=TENANT1) result = self._verify_event_processing(create_event, owner=TENANT1)
verification_keys = ['device_owner', 'device_id'] verification_keys = ['device_owner', 'device_id']
self._verify_result(create_event, verification_keys, result, self._verify_result(create_event, verification_keys, result,
inner_key='port') inner_key='port')
attach_event = self.port_events['port_attach'] attach_event = self.port_events['port_attach']
self._send_event_to_listener(attach_event, self.index_name) self._send_event_to_listener(attach_event, self.listener_alias)
result = self._verify_event_processing(attach_event, owner=TENANT1) result = self._verify_event_processing(attach_event, owner=TENANT1)
verification_keys = ['device_owner', 'device_id'] verification_keys = ['device_owner', 'device_id']
self._verify_result(attach_event, verification_keys, result, self._verify_result(attach_event, verification_keys, result,
inner_key='port') inner_key='port')
detach_event = self.port_events['port_detach'] detach_event = self.port_events['port_detach']
self._send_event_to_listener(detach_event, self.index_name) self._send_event_to_listener(detach_event, self.listener_alias)
result = self._verify_event_processing(attach_event, owner=TENANT1) result = self._verify_event_processing(attach_event, owner=TENANT1)
verification_keys = ['device_owner', 'device_id'] verification_keys = ['device_owner', 'device_id']
self._verify_result(detach_event, verification_keys, result, self._verify_result(detach_event, verification_keys, result,
@ -177,6 +374,98 @@ class TestNeutronListener(test_listener.TestSearchListenerBase):
def test_port_delete_event(self): def test_port_delete_event(self):
delete_event = self.port_events['port.delete.end'] delete_event = self.port_events['port.delete.end']
self._send_event_to_listener(delete_event, self.index_name) self._send_event_to_listener(delete_event, self.listener_alias)
self._verify_event_processing(None, count=0, self._verify_event_processing(None, count=0,
owner=TENANT1) owner=TENANT1)
def test_subnet_create_update_delete(self):
create_event = self.subnet_events['subnet.create.end']
self._send_event_to_listener(create_event, self.listener_alias)
result = self._verify_event_processing(create_event, owner=TENANT3)
verification_keys = ['network_id', 'name']
self._verify_result(create_event, verification_keys, result,
inner_key='subnet')
update_event = self.subnet_events['subnet.update.end']
self._send_event_to_listener(update_event, self.listener_alias)
result = self._verify_event_processing(update_event, owner=TENANT3)
verification_keys = ['network_id', 'name']
self._verify_result(update_event, verification_keys, result,
inner_key='subnet')
delete_event = self.subnet_events['subnet.delete.end']
self._send_event_to_listener(delete_event, self.listener_alias)
self._verify_event_processing(delete_event, count=0,
owner=TENANT3)
def test_router_create_update_delete(self):
create_event = self.router_events['router.create.end']
self._send_event_to_listener(create_event, self.listener_alias)
result = self._verify_event_processing(create_event, owner=TENANT3)
verification_keys = ['status', 'name', 'id']
self._verify_result(create_event, verification_keys, result,
inner_key='router')
update_event = self.router_events['router.update.end']
self._send_event_to_listener(update_event, self.listener_alias)
result = self._verify_event_processing(update_event, owner=TENANT3)
verification_keys = ['status', 'name', 'id']
self._verify_result(update_event, verification_keys, result,
inner_key='router')
delete_event = self.router_events['router.delete.end']
self._send_event_to_listener(delete_event, self.listener_alias)
self._verify_event_processing(delete_event, count=0,
owner=TENANT3)
def test_router_interface_create_delete(self):
"""Check that port creation and deletion is registered on interface
creation and deletion events
"""
interface_port = {
u'port': {
u'admin_state_up': True,
u'allowed_address_pairs': [],
u'binding:vnic_type': u'normal',
u'device_id': u'9262552b-6e46-41ee-9ede-393d5f65f325',
u'device_owner': u'network:router_interface',
u'dns_name': None,
u'extra_dhcp_opts': [],
u'fixed_ips': [{
u'ip_address': u'172.45.1.1',
u'subnet_id': u'4cd5c1d7-68ec-4e9a-bf16-9fd7571f8805'
}],
u'id': u'a5324522-47d3-4547-85df-a01ef6bde4b1',
u'mac_address': u'fa:16:3e:5f:06:e3',
u'name': u'',
u'network_id': NETID4,
u'port_security_enabled': False,
u'security_groups': [],
u'status': u'ACTIVE',
u'tenant_id': TENANT3
}
}
create_event = self.router_events['router.interface.create']
with mock.patch('neutronclient.v2_0.client.Client.show_port',
return_value=interface_port):
self._send_event_to_listener(create_event, self.listener_alias)
query = {
"type": "OS::Neutron::Port",
"query": {"term": {"id": "a5324522-47d3-4547-85df-a01ef6bde4b1"}}
}
response, json_content = self._search_request(query,
TENANT3)
self.assertEqual(200, response.status)
self.assertEqual(1, json_content['hits']['total'])
hit = json_content['hits']['hits'][0]['_source']
self.assertEqual(NETID4, hit['network_id'])
self.assertEqual('network:router_interface', hit['device_owner'])
delete_event = self.router_events['router.interface.delete']
self._send_event_to_listener(delete_event, self.listener_alias)
response, json_content = self._search_request(query,
TENANT3)
self.assertEqual(0, json_content['hits']['total'])

View File

@ -0,0 +1,98 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# 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_utils import timeutils
from searchlight.elasticsearch.plugins.neutron import\
routers as routers_plugin
import searchlight.tests.unit.utils as unit_test_utils
import searchlight.tests.utils as test_utils
_now_str = timeutils.isotime(datetime.datetime.utcnow())
USER1 = u'27f4d76b-be62-4e4e-aa33bb11cc55'
ID1 = "813dd936-663e-4e5b-877c-986021b73e2c"
NETID1 = "98dcb60c-59b9-4b3c-bf6a-b8504112e978"
TENANT1 = "8eaac046b2c44ab99246cb0850c7f06d"
def _router_fixture(router_id, tenant_id, name, **kwargs):
fixture = {
u'admin_state_up': True,
u'availability_zone_hints': [],
u'availability_zones': [],
u'external_gateway_info': {
u'enable_snat': True,
u'external_fixed_ips': [{
u'ip_address': u'172.25.0.2',
u'subnet_id': u'8cbe9b71-ecf1-4355-b5ba-dee54ec88fa7'
}, {
u'ip_address': u'2001:db8::1',
u'subnet_id': u'd7774648-ba81-477a-9329-2acc9a810e50'
}],
u'network_id': u'8891323e-bf5b-48d7-a75e-669af0608538'},
u'id': router_id,
u'name': name,
u'routes': [],
u'status': u'ACTIVE',
u'tenant_id': tenant_id}
fixture.update(**kwargs)
return fixture
def _gateway_info(network_id, fixed_ips):
return {
u'enable_snat': True,
u'external_fixed_ips': fixed_ips,
u'network_id': network_id
}
class TestRouterLoaderPlugin(test_utils.BaseTestCase):
def setUp(self):
super(TestRouterLoaderPlugin, self).setUp()
self.plugin = routers_plugin.RouterIndex()
self._create_fixtures()
def _create_fixtures(self):
self.router1 = _router_fixture(router_id=ID1, network_id=NETID1,
tenant_id=TENANT1, name="test-router-1")
self.routers = [self.router1]
def test_admin_only(self):
self.assertEqual(['distributed', 'ha'], self.plugin.admin_only_fields)
def test_document_type(self):
self.assertEqual('OS::Neutron::Router',
self.plugin.get_document_type())
def test_rbac_filter(self):
fake_request = unit_test_utils.get_fake_request(
USER1, TENANT1, '/v1/search', is_admin=False
)
rbac_terms = self.plugin._get_rbac_field_filters(fake_request.context)
self.assertEqual(
[{"term": {"tenant_id": TENANT1}}],
rbac_terms
)
def test_notification_events(self):
handler = self.plugin.get_notification_handler()
self.assertEqual(
set(['router.create.end', 'router.update.end',
'router.delete.end']),
set(handler.get_event_handlers().keys())
)

View File

@ -0,0 +1,87 @@
# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
#
# 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_utils import timeutils
from searchlight.elasticsearch.plugins.neutron import\
subnets as subnets_plugin
import searchlight.tests.unit.utils as unit_test_utils
import searchlight.tests.utils as test_utils
_now_str = timeutils.isotime(datetime.datetime.utcnow())
USER1 = u'27f4d76b-be62-4e4e-aa33bb11cc55'
ID1 = "813dd936-663e-4e5b-877c-986021b73e2c"
NETID1 = "98dcb60c-59b9-4b3c-bf6a-b8504112e978"
TENANT1 = "8eaac046b2c44ab99246cb0850c7f06d"
def _subnet_fixture(network_id, tenant_id, subnet_id, name, **kwargs):
fixture = {
"id": subnet_id,
"network_id": network_id,
"name": name,
"tenant_id": tenant_id,
"ip_version": kwargs.pop("ip_version", 4),
"cidr": kwargs.pop("cidr", "192.0.0.1/24")
}
pools = kwargs.get('allocation_pools', [{"start": "192.0.0.2",
"end": "192.254.254.254"}])
fixture['allocation_pools'] = pools
fixture.update(kwargs)
return fixture
class TestSubnetLoaderPlugin(test_utils.BaseTestCase):
def setUp(self):
super(TestSubnetLoaderPlugin, self).setUp()
self.plugin = subnets_plugin.SubnetIndex()
self._create_fixtures()
def _create_fixtures(self):
self.subnet1 = _subnet_fixture(subnet_id=ID1, network_id=NETID1,
tenant_id=TENANT1, name="test-net-1")
self.subnets = [self.subnet1]
def test_document_type(self):
self.assertEqual('OS::Neutron::Subnet',
self.plugin.get_document_type())
def test_rbac_filter(self):
fake_request = unit_test_utils.get_fake_request(
USER1, TENANT1, '/v1/search', is_admin=False
)
rbac_terms = self.plugin._get_rbac_field_filters(fake_request.context)
self.assertEqual(
[{"or": [
{"term": {"tenant_id": TENANT1}},
{
"has_parent": {
"type": "OS::Neutron::Net",
"query": {"term": {"shared": True}}
}
}
]}],
rbac_terms
)
def test_notification_events(self):
handler = self.plugin.get_notification_handler()
self.assertEqual(
set(['subnet.create.end', 'subnet.update.end',
'subnet.delete.end']),
set(handler.get_event_handlers().keys())
)

View File

@ -282,6 +282,8 @@ class TestSearchDeserializer(test_utils.BaseTestCase):
'OS::Nova::Server', 'OS::Nova::Server',
'OS::Neutron::Net', 'OS::Neutron::Net',
'OS::Neutron::Port', 'OS::Neutron::Port',
'OS::Neutron::Subnet',
'OS::Neutron::Router',
'OS::Cinder::Volume', 'OS::Cinder::Volume',
'OS::Cinder::Snapshot' 'OS::Cinder::Snapshot'
] ]

View File

@ -32,7 +32,9 @@ searchlight.index_backend =
os_glance_metadef = searchlight.elasticsearch.plugins.glance.metadefs:MetadefIndex os_glance_metadef = searchlight.elasticsearch.plugins.glance.metadefs:MetadefIndex
os_nova_server = searchlight.elasticsearch.plugins.nova.servers:ServerIndex os_nova_server = searchlight.elasticsearch.plugins.nova.servers:ServerIndex
os_neutron_network = searchlight.elasticsearch.plugins.neutron.networks:NetworkIndex os_neutron_network = searchlight.elasticsearch.plugins.neutron.networks:NetworkIndex
os_neutron_subnet = searchlight.elasticsearch.plugins.neutron.subnets:SubnetIndex
os_neutron_port = searchlight.elasticsearch.plugins.neutron.ports:PortIndex os_neutron_port = searchlight.elasticsearch.plugins.neutron.ports:PortIndex
os_neutron_router = searchlight.elasticsearch.plugins.neutron.routers:RouterIndex
os_designate_recordset = searchlight.elasticsearch.plugins.designate.recordsets:RecordSetIndex os_designate_recordset = searchlight.elasticsearch.plugins.designate.recordsets:RecordSetIndex
os_designate_zone = searchlight.elasticsearch.plugins.designate.zones:ZoneIndex os_designate_zone = searchlight.elasticsearch.plugins.designate.zones:ZoneIndex
os_cinder_volume = searchlight.elasticsearch.plugins.cinder.volumes:VolumeIndex os_cinder_volume = searchlight.elasticsearch.plugins.cinder.volumes:VolumeIndex