Flavor Plugin
This patch adds support to index Flavors of Nova. Implements: blueprint nova-flavor-plugin Change-Id: I190c37a7cce3e0f4d80a994315db7545eb4b6fb6
This commit is contained in:
parent
af80169f13
commit
f7d1a48428
|
@ -197,6 +197,9 @@ enabled = True
|
|||
[resource_plugin:os_nova_hypervisor]
|
||||
enabled = True
|
||||
|
||||
[resource_plugin:os_nova_flavor]
|
||||
enabled = True
|
||||
|
||||
[resource_plugin:os_glance_image]
|
||||
enabled = True
|
||||
|
||||
|
|
|
@ -85,6 +85,9 @@ Please read the rest of the guide for detailed information.::
|
|||
[resource_plugin:os_nova_hypervisor]
|
||||
enabled = True
|
||||
|
||||
[resource_plugin:os_nova_flavor]
|
||||
enabled = True
|
||||
|
||||
[resource_plugin:os_glance_image]
|
||||
enabled = True
|
||||
|
||||
|
|
|
@ -73,6 +73,19 @@ Plugin: OS::Nova::Hypervisor
|
|||
putting it to its own resource group and scheduling a cron job to re-sync
|
||||
with little overhead.
|
||||
|
||||
Plugin: OS::Nova::Flavor
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
[resource_plugin:os_nova_flavor]
|
||||
enabled = true
|
||||
|
||||
.. note::
|
||||
|
||||
There are no notifications for flavor from nova yet, so we recommend
|
||||
putting it in its own resource group and scheduling a cron job to re-sync
|
||||
with little overhead.
|
||||
|
||||
Nova Configuration
|
||||
==================
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"resource:OS::Glance::Metadef:allow": "",
|
||||
"resource:OS::Nova::Server:allow": "",
|
||||
"resource:OS::Nova::Hypervisor:allow": "role:admin",
|
||||
"resource:OS::Nova::Flavor:allow": "",
|
||||
"resource:OS::Cinder::Volume:allow": "",
|
||||
"resource:OS::Cinder::Snapshot:allow": "",
|
||||
"resource:OS::Designate::Zone:allow": "",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
features:
|
||||
- Adds nova plugin for flavors.
|
||||
issues:
|
||||
- There are no notifications for flavors from nova yet, so we recommend
|
||||
putting it to its own resource group and scheduling a cron job to re-sync
|
||||
with little overhead.
|
|
@ -16,16 +16,32 @@
|
|||
import copy
|
||||
import json
|
||||
import logging
|
||||
import novaclient.exceptions
|
||||
import six
|
||||
|
||||
from searchlight.elasticsearch.plugins import openstack_clients
|
||||
from searchlight.elasticsearch.plugins import utils
|
||||
from searchlight.i18n import _LW
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# All 'links' will also be removed
|
||||
BLACKLISTED_FIELDS = set((u'progress', u'links'))
|
||||
FLAVOR_ACCESS_FIELD = 'tenant_access'
|
||||
|
||||
|
||||
def _get_flavor_access(flavor):
|
||||
if flavor.is_public:
|
||||
return None
|
||||
try:
|
||||
n_client = openstack_clients.get_novaclient()
|
||||
return [access.tenant_id for access in
|
||||
n_client.flavor_access.list(flavor=flavor)] or None
|
||||
except novaclient.exceptions.Unauthorized:
|
||||
LOG.warning(_LW("Could not return tenant for %s; forbidden") %
|
||||
flavor)
|
||||
return None
|
||||
|
||||
|
||||
def serialize_nova_server(server):
|
||||
|
@ -78,6 +94,19 @@ def serialize_nova_hypervisor(hypervisor, updated_at=None):
|
|||
return serialized
|
||||
|
||||
|
||||
def serialize_nova_flavor(flavor, updated_at=None):
|
||||
serialized = {k: v for k, v in six.iteritems(flavor.to_dict())
|
||||
if k not in ("links")}
|
||||
serialized["extra_specs"] = flavor.get_keys()
|
||||
|
||||
serialized[FLAVOR_ACCESS_FIELD] = _get_flavor_access(flavor)
|
||||
|
||||
if not getattr(flavor, 'updated_at', None):
|
||||
serialized['updated_at'] = updated_at or utils.get_now_str()
|
||||
|
||||
return serialized
|
||||
|
||||
|
||||
def _format_networks(server, serialized):
|
||||
networks = []
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
# 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.common import resource_types
|
||||
from searchlight.elasticsearch.plugins import base
|
||||
from searchlight.elasticsearch.plugins.nova import FLAVOR_ACCESS_FIELD
|
||||
from searchlight.elasticsearch.plugins.nova \
|
||||
import notification_handler
|
||||
from searchlight.elasticsearch.plugins.nova import serialize_nova_flavor
|
||||
from searchlight.elasticsearch.plugins import openstack_clients
|
||||
|
||||
|
||||
class FlavorIndex(base.IndexBase):
|
||||
|
||||
NotificationHandlerCls = notification_handler.FlavorHandler
|
||||
|
||||
@classmethod
|
||||
def get_document_type(self):
|
||||
return resource_types.NOVA_FLAVOR
|
||||
|
||||
def get_mapping(self):
|
||||
str_analysis = {'type': 'string', 'index': 'not_analyzed'}
|
||||
integer = {'type': 'integer'}
|
||||
return {
|
||||
'dynamic': True,
|
||||
'properties': {
|
||||
'id': str_analysis,
|
||||
'tenant_id': str_analysis,
|
||||
'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
|
||||
'OS-FLV-EXT-DATA:ephemeral': integer,
|
||||
'disk': integer,
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'fields': {
|
||||
'raw': str_analysis
|
||||
}
|
||||
},
|
||||
'os-flavor-access:is_public': {'type': 'boolean'},
|
||||
FLAVOR_ACCESS_FIELD: str_analysis,
|
||||
'ram': integer,
|
||||
'rxtx_factor': {'type': 'float'},
|
||||
'swap': str_analysis,
|
||||
'vcpus': integer,
|
||||
'extra-specs': {
|
||||
'type': 'nested',
|
||||
'properties': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def get_objects(self):
|
||||
"""Generator that lists all nova Flavors"""
|
||||
return openstack_clients.get_novaclient().flavors.list(is_public=None)
|
||||
|
||||
@property
|
||||
def facets_with_options(self):
|
||||
return ('OS-FLV-DISABLED:disabled', 'os-flavor-access:is_public')
|
||||
|
||||
def serialize(self, flavor):
|
||||
return serialize_nova_flavor(flavor)
|
||||
|
||||
def _get_rbac_field_filters(self, request_context):
|
||||
"""Return any RBAC field filters to be injected into an indices
|
||||
query. Document type will be added to this list.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'or': [
|
||||
{
|
||||
'term': {
|
||||
'os-flavor-access:is_public': True
|
||||
}
|
||||
},
|
||||
{
|
||||
'term': {
|
||||
FLAVOR_ACCESS_FIELD: request_context.tenant
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
def filter_result(self, hit, request_context):
|
||||
super(FlavorIndex, self).filter_result(hit, request_context)
|
||||
# Only admins and tenants who the flavor has been granted
|
||||
# access can see the full list of access.
|
||||
if not request_context.is_admin:
|
||||
source = hit['_source']
|
||||
is_public = source.get('os-flavor-access:is_public', False)
|
||||
access = source.get(FLAVOR_ACCESS_FIELD, [])
|
||||
if is_public or request_context.tenant not in access:
|
||||
source.pop(FLAVOR_ACCESS_FIELD, None)
|
|
@ -244,3 +244,18 @@ class HypervisorHandler(base.NotificationBase):
|
|||
# hypervisor in nova is implemented:
|
||||
# https://blueprints.launchpad.net/nova/+spec/hypervisor-notification
|
||||
return {}
|
||||
|
||||
|
||||
class FlavorHandler(base.NotificationBase):
|
||||
"""Handles nova flavor notifications.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _get_notification_exchanges(cls):
|
||||
return []
|
||||
|
||||
def get_event_handlers(self):
|
||||
# TODO(): Currently there is no notification for flavor,
|
||||
# this needs to be changed once the notification for
|
||||
# flavor in nova is implemented:
|
||||
return {}
|
||||
|
|
|
@ -369,7 +369,8 @@ class FunctionalTest(test_utils.BaseTestCase):
|
|||
plugin_classes = {
|
||||
'glance': {'images': 'ImageIndex', 'metadefs': 'MetadefIndex'},
|
||||
'nova': {'servers': 'ServerIndex',
|
||||
'hypervisors': 'HypervisorIndex'},
|
||||
'hypervisors': 'HypervisorIndex',
|
||||
'flavors': 'FlavorIndex'},
|
||||
'cinder': {'volumes': 'VolumeIndex', 'snapshots': 'SnapshotIndex'},
|
||||
'neutron': {'networks': 'NetworkIndex', 'ports': 'PortIndex',
|
||||
'subnets': 'SubnetIndex', 'routers': 'RouterIndex',
|
||||
|
@ -385,6 +386,7 @@ class FunctionalTest(test_utils.BaseTestCase):
|
|||
plugins = include_plugins or (
|
||||
('glance', 'images'), ('glance', 'metadefs'),
|
||||
('nova', 'servers'), ('nova', 'hypervisors'),
|
||||
('nova', 'flavors'),
|
||||
('cinder', 'volumes'), ('cinder', 'snapshots'),
|
||||
('neutron', 'networks'), ('neutron', 'ports'),
|
||||
('neutron', 'subnets'), ('neutron', 'routers'),
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
{
|
||||
"name": "m1.tiny",
|
||||
"tenant_id": "",
|
||||
"ram": 512,
|
||||
"extra_spec": {},
|
||||
"OS-FLV-DISABLED:disabled": false,
|
||||
"vcpus": 1,
|
||||
"swap": "",
|
||||
"os-flavor-access:is_public": true,
|
||||
"rxtx_factor": 1.0,
|
||||
"OS-FLV-EXT-DATA:ephemeral": 0,
|
||||
"disk": 1,
|
||||
"id": "1"
|
||||
}
|
||||
]
|
|
@ -22,6 +22,7 @@ METADEFS_FILE = "searchlight/tests/functional/data/load/metadefs.json"
|
|||
IMAGE_MEMBERS_FILE = \
|
||||
"searchlight/tests/functional/data/load/image_members.json"
|
||||
SERVERS_FILE = "searchlight/tests/functional/data/load/servers.json"
|
||||
FLAVORS_FILE = "searchlight/tests/functional/data/load/flavors.json"
|
||||
|
||||
|
||||
from glanceclient.v2 import client as glance
|
||||
|
@ -32,6 +33,15 @@ import novaclient.client
|
|||
_session = None
|
||||
|
||||
|
||||
def _get_flavor_tenant(flavor):
|
||||
if flavor.is_public:
|
||||
return ""
|
||||
n_client = get_novaclient()
|
||||
flavor_access = n_client.flavor_access.list(flavor=flavor)[0]
|
||||
tenant_id = flavor_access.tenant_id
|
||||
return tenant_id
|
||||
|
||||
|
||||
def _get_session():
|
||||
|
||||
global _session
|
||||
|
@ -116,10 +126,24 @@ def get_nova_servers_with_pyclient():
|
|||
f.write(servers_json)
|
||||
|
||||
|
||||
def get_nova_flavors_with_pyclient():
|
||||
|
||||
nova_client = get_novaclient()
|
||||
flavor = nova_client.flavors.list()[0]
|
||||
flavor_dict = flavor.to_dict()
|
||||
flavor_dict.pop("links")
|
||||
flavor_dict.update({"tenant_id": _get_flavor_tenant(flavor)})
|
||||
flavor_dict.update({"extra_spec": flavor.get_keys()})
|
||||
flavors_json = json.dumps([flavor_dict], indent=4)
|
||||
with open(FLAVORS_FILE, "w") as f:
|
||||
f.write(flavors_json)
|
||||
|
||||
|
||||
def generate():
|
||||
get_glance_images_and_members_with_pyclient()
|
||||
get_glance_metadefs_with_pyclient()
|
||||
get_nova_servers_with_pyclient()
|
||||
get_nova_flavors_with_pyclient()
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate()
|
||||
|
|
|
@ -32,6 +32,8 @@ class TestNovaPlugins(functional.FunctionalTest):
|
|||
self.hyper_objects = self._load_fixture_data('load/hypervisors.json')
|
||||
self.server_plugin = self.initialized_plugins['OS::Nova::Server']
|
||||
self.server_objects = self._load_fixture_data('load/servers.json')
|
||||
self.flavor_plugin = self.initialized_plugins['OS::Nova::Flavor']
|
||||
self.flavor_objects = self._load_fixture_data('load/flavors.json')
|
||||
|
||||
@mock.patch(nova_version_getter, return_value=fake_version_list)
|
||||
def test_hypervisor_rbac(self, mock_version):
|
||||
|
@ -162,3 +164,39 @@ class TestNovaPlugins(functional.FunctionalTest):
|
|||
|
||||
actual_sources = [process(hit['_source']) for hit in hits]
|
||||
self.assertEqual(expected_sources, actual_sources)
|
||||
|
||||
def test_flavor_rbac(self):
|
||||
self._index(self.flavor_plugin,
|
||||
[utils.FlavorDictObj(**flavor)
|
||||
for flavor in self.flavor_objects]
|
||||
)
|
||||
|
||||
query = {
|
||||
"type": ["OS::Nova::Flavor"],
|
||||
"query": {
|
||||
"match_all": {}
|
||||
}
|
||||
}
|
||||
|
||||
response, json_content = self._search_request(query, TENANT1)
|
||||
expected_sources = [{
|
||||
u'OS-FLV-DISABLED:disabled': False,
|
||||
u'OS-FLV-EXT-DATA:ephemeral': 0,
|
||||
u'disk': 1,
|
||||
u"tenant_id": u"",
|
||||
u'extra_specs': {},
|
||||
u'id': u'1',
|
||||
u'name': u'm1.tiny',
|
||||
u'os-flavor-access:is_public': True,
|
||||
u'ram': 512,
|
||||
u'rxtx_factor': 1.0,
|
||||
u'swap': u'',
|
||||
u'vcpus': 1}]
|
||||
hits = json_content['hits']['hits']
|
||||
for hit in hits:
|
||||
source = hit["_source"]
|
||||
source.pop("updated_at")
|
||||
actual_sources = [hit["_source"] for hit in hits]
|
||||
self.assertEqual(expected_sources, actual_sources)
|
||||
self.assertEqual(200, response.status)
|
||||
self.assertEqual(1, json_content['hits']['total'])
|
||||
|
|
|
@ -294,6 +294,7 @@ class TestSearchDeserializer(test_utils.BaseTestCase):
|
|||
'OS::Designate::Zone',
|
||||
'OS::Glance::Image',
|
||||
'OS::Glance::Metadef',
|
||||
'OS::Nova::Flavor',
|
||||
'OS::Nova::Server',
|
||||
'OS::Nova::Hypervisor',
|
||||
'OS::Neutron::FloatingIP',
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
"""Common utilities used in testing"""
|
||||
|
||||
import copy
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
|
@ -566,3 +567,16 @@ class DictObj(object):
|
|||
class FakeVersion(object):
|
||||
def __init__(self, version):
|
||||
self.version = version
|
||||
|
||||
|
||||
class FlavorDictObj(DictObj):
|
||||
def get_keys(self):
|
||||
return self.extra_spec
|
||||
|
||||
def is_public(self):
|
||||
return self.is_public
|
||||
|
||||
def to_dict(self):
|
||||
d = copy.deepcopy(super(FlavorDictObj, self).to_dict())
|
||||
d.pop("extra_spec", None)
|
||||
return d
|
||||
|
|
|
@ -34,6 +34,7 @@ searchlight.index_backend =
|
|||
os_nova_server = searchlight.elasticsearch.plugins.nova.servers:ServerIndex
|
||||
os_nova_hypervisor = searchlight.elasticsearch.plugins.nova.hypervisors:HypervisorIndex
|
||||
os_neutron_floatingip = searchlight.elasticsearch.plugins.neutron.floatingips:FloatingIPIndex
|
||||
os_nova_flavor = searchlight.elasticsearch.plugins.nova.flavors:FlavorIndex
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue