Removes KVS catalog backend

The templated backend relied on the KVS backend to implement some
functionality. The functionality (CRUD for endpoint, services, etc.) is
arguably incorrect since it won't actually change the contents of the
catalog. The read only methods have been fixed to use the templated data
and the write methods raise NotImplemented.

bp: removed-as-of-mitaka
Partial-Bug: #1077282
Closes-Bug: #1367113
Closes-Bug: #1269789
Change-Id: Iaa68b18f0b6d7e9f5dc0cbf7d21a3d90dcdc1ea4
This commit is contained in:
David Stanek 2015-02-17 19:05:56 +00:00 committed by Steve Martinelli
parent 1053b63e8c
commit b1b4350017
12 changed files with 175 additions and 499 deletions

View File

@ -1,371 +0,0 @@
# Copyright 2012 OpenStack Foundation
#
# 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 keystone import catalog
from keystone.common import driver_hints
from keystone.common import kvs
class Catalog(kvs.Base, catalog.CatalogDriverV8):
# Public interface
def get_catalog(self, user_id, tenant_id):
return self.db.get('catalog-%s-%s' % (tenant_id, user_id))
# region crud
def _delete_child_regions(self, region_id, root_region_id):
"""Delete all child regions.
Recursively delete any region that has the supplied region
as its parent.
"""
children = [r for r in self.list_regions(driver_hints.Hints())
if r['parent_region_id'] == region_id]
for child in children:
if child['id'] == root_region_id:
# Hit a circular region hierarchy
return
self._delete_child_regions(child['id'], root_region_id)
self._delete_region(child['id'])
def _check_parent_region(self, region_ref):
"""Raise a NotFound if the parent region does not exist.
If the region_ref has a specified parent_region_id, check that
the parent exists, otherwise, raise a NotFound.
"""
parent_region_id = region_ref.get('parent_region_id')
if parent_region_id is not None:
# This will raise NotFound if the parent doesn't exist,
# which is the behavior we want.
self.get_region(parent_region_id)
def create_region(self, region):
region_id = region['id']
region.setdefault('parent_region_id')
self._check_parent_region(region)
self.db.set('region-%s' % region_id, region)
region_list = set(self.db.get('region_list', []))
region_list.add(region_id)
self.db.set('region_list', list(region_list))
return region
def list_regions(self, hints):
return [self.get_region(x) for x in self.db.get('region_list', [])]
def get_region(self, region_id):
return self.db.get('region-%s' % region_id)
def update_region(self, region_id, region):
self._check_parent_region(region)
old_region = self.get_region(region_id)
old_region.update(region)
self._ensure_no_circle_in_hierarchical_regions(old_region)
self.db.set('region-%s' % region_id, old_region)
return old_region
def _delete_region(self, region_id):
self.db.delete('region-%s' % region_id)
region_list = set(self.db.get('region_list', []))
region_list.remove(region_id)
self.db.set('region_list', list(region_list))
def delete_region(self, region_id):
self._delete_child_regions(region_id, region_id)
self._delete_region(region_id)
# service crud
def create_service(self, service_id, service):
self.db.set('service-%s' % service_id, service)
service_list = set(self.db.get('service_list', []))
service_list.add(service_id)
self.db.set('service_list', list(service_list))
return service
def list_services(self, hints):
return [self.get_service(x) for x in self.db.get('service_list', [])]
def get_service(self, service_id):
return self.db.get('service-%s' % service_id)
def update_service(self, service_id, service):
old_service = self.get_service(service_id)
old_service.update(service)
self.db.set('service-%s' % service_id, old_service)
return old_service
def delete_service(self, service_id):
# delete referencing endpoints
for endpoint_id in self.db.get('endpoint_list', []):
if self.get_endpoint(endpoint_id)['service_id'] == service_id:
self.delete_endpoint(endpoint_id)
self.db.delete('service-%s' % service_id)
service_list = set(self.db.get('service_list', []))
service_list.remove(service_id)
self.db.set('service_list', list(service_list))
# endpoint crud
def create_endpoint(self, endpoint_id, endpoint):
self.db.set('endpoint-%s' % endpoint_id, endpoint)
endpoint_list = set(self.db.get('endpoint_list', []))
endpoint_list.add(endpoint_id)
self.db.set('endpoint_list', list(endpoint_list))
return endpoint
def list_endpoints(self, hints):
return [self.get_endpoint(x) for x in self.db.get('endpoint_list', [])]
def get_endpoint(self, endpoint_id):
return self.db.get('endpoint-%s' % endpoint_id)
def update_endpoint(self, endpoint_id, endpoint):
if endpoint.get('region_id') is not None:
self.get_region(endpoint['region_id'])
old_endpoint = self.get_endpoint(endpoint_id)
old_endpoint.update(endpoint)
self.db.set('endpoint-%s' % endpoint_id, old_endpoint)
return old_endpoint
def delete_endpoint(self, endpoint_id):
self.db.delete('endpoint-%s' % endpoint_id)
endpoint_list = set(self.db.get('endpoint_list', []))
endpoint_list.remove(endpoint_id)
self.db.set('endpoint_list', list(endpoint_list))
# Private interface
def _create_catalog(self, user_id, tenant_id, data):
self.db.set('catalog-%s-%s' % (tenant_id, user_id), data)
return data
# TODO(davechen): Apparently, these methods are not implemented but
# we cannot raise exception.NotImplemented() just because the notification
# to those resource will break some testcases, will look into CADF to
# see if there is any better way to do this.
def add_endpoint_to_project(self, endpoint_id, project_id):
"""Create an endpoint to project association.
:param endpoint_id: identity of endpoint to associate
:type endpoint_id: string
:param project_id: identity of the project to be associated with
:type project_id: string
:raises: keystone.exception.Conflict: If the endpoint was already
added to project.
:returns: None.
"""
pass
def remove_endpoint_from_project(self, endpoint_id, project_id):
"""Removes an endpoint to project association.
:param endpoint_id: identity of endpoint to remove
:type endpoint_id: string
:param project_id: identity of the project associated with
:type project_id: string
:raises keystone.exception.NotFound: If the endpoint was not found
in the project.
:returns: None.
"""
pass
def check_endpoint_in_project(self, endpoint_id, project_id):
"""Checks if an endpoint is associated with a project.
:param endpoint_id: identity of endpoint to check
:type endpoint_id: string
:param project_id: identity of the project associated with
:type project_id: string
:raises keystone.exception.NotFound: If the endpoint was not found
in the project.
:returns: None.
"""
pass
def list_endpoints_for_project(self, project_id):
"""List all endpoints associated with a project.
:param project_id: identity of the project to check
:type project_id: string
:returns: a list of identity endpoint ids or an empty list.
"""
pass
def list_projects_for_endpoint(self, endpoint_id):
"""List all projects associated with an endpoint.
:param endpoint_id: identity of endpoint to check
:type endpoint_id: string
:returns: a list of projects or an empty list.
"""
pass
def delete_association_by_endpoint(self, endpoint_id):
"""Removes all the endpoints to project association with endpoint.
:param endpoint_id: identity of endpoint to check
:type endpoint_id: string
:returns: None
"""
pass
def delete_association_by_project(self, project_id):
"""Removes all the endpoints to project association with project.
:param project_id: identity of the project to check
:type project_id: string
:returns: None
"""
pass
def create_endpoint_group(self, endpoint_group):
"""Create an endpoint group.
:param endpoint_group: endpoint group to create
:type endpoint_group: dictionary
:raises: keystone.exception.Conflict: If a duplicate endpoint group
already exists.
:returns: an endpoint group representation.
"""
pass
def get_endpoint_group(self, endpoint_group_id):
"""Get an endpoint group.
:param endpoint_group_id: identity of endpoint group to retrieve
:type endpoint_group_id: string
:raises keystone.exception.NotFound: If the endpoint group was not
found.
:returns: an endpoint group representation.
"""
pass
def update_endpoint_group(self, endpoint_group_id, endpoint_group):
"""Update an endpoint group.
:param endpoint_group_id: identity of endpoint group to retrieve
:type endpoint_group_id: string
:param endpoint_group: A full or partial endpoint_group
:type endpoint_group: dictionary
:raises keystone.exception.NotFound: If the endpoint group was not
found.
:returns: an endpoint group representation.
"""
pass
def delete_endpoint_group(self, endpoint_group_id):
"""Delete an endpoint group.
:param endpoint_group_id: identity of endpoint group to delete
:type endpoint_group_id: string
:raises keystone.exception.NotFound: If the endpoint group was not
found.
:returns: None.
"""
pass
def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
"""Adds an endpoint group to project association.
:param endpoint_group_id: identity of endpoint to associate
:type endpoint_group_id: string
:param project_id: identity of project to associate
:type project_id: string
:raises keystone.exception.Conflict: If the endpoint group was already
added to the project.
:returns: None.
"""
pass
def get_endpoint_group_in_project(self, endpoint_group_id, project_id):
"""Get endpoint group to project association.
:param endpoint_group_id: identity of endpoint group to retrieve
:type endpoint_group_id: string
:param project_id: identity of project to associate
:type project_id: string
:raises keystone.exception.NotFound: If the endpoint group to the
project association was not found.
:returns: a project endpoint group representation.
"""
pass
def list_endpoint_groups(self):
"""List all endpoint groups.
:returns: None.
"""
pass
def list_endpoint_groups_for_project(self, project_id):
"""List all endpoint group to project associations for a project.
:param project_id: identity of project to associate
:type project_id: string
:returns: None.
"""
pass
def list_projects_associated_with_endpoint_group(self, endpoint_group_id):
"""List all projects associated with endpoint group.
:param endpoint_group_id: identity of endpoint to associate
:type endpoint_group_id: string
:returns: None.
"""
pass
def remove_endpoint_group_from_project(self, endpoint_group_id,
project_id):
"""Remove an endpoint to project association.
:param endpoint_group_id: identity of endpoint to associate
:type endpoint_group_id: string
:param project_id: identity of project to associate
:type project_id: string
:raises keystone.exception.NotFound: If endpoint group project
association was not found.
:returns: None.
"""
pass
def delete_endpoint_group_association_by_project(self, project_id):
"""Remove endpoint group to project associations.
:param project_id: identity of the project to check
:type project_id: string
:returns: None
"""
pass

View File

@ -17,8 +17,8 @@ import os.path
from oslo_config import cfg
from oslo_log import log
import six
from keystone.catalog.backends import kvs
from keystone.catalog import core
from keystone import exception
from keystone.i18n import _LC
@ -56,7 +56,7 @@ def parse_templates(template_lines):
return o
class Catalog(kvs.Catalog):
class Catalog(core.Driver):
"""A backend that generates endpoints for the Catalog based on templates.
It is usually configured via config entries that look like:
@ -105,6 +105,95 @@ class Catalog(kvs.Catalog):
LOG.critical(_LC('Unable to open template file %s'), template_file)
raise
# region crud
def create_region(self, region_ref):
raise exception.NotImplemented()
def list_regions(self, hints):
return [{'id': region_id, 'description': '', 'parent_region_id': ''}
for region_id in self.templates]
def get_region(self, region_id):
if region_id in self.templates:
return {'id': region_id, 'description': '', 'parent_region_id': ''}
raise exception.RegionNotFound(region_id=region_id)
def update_region(self, region_id, region_ref):
raise exception.NotImplemented()
def delete_region(self, region_id):
raise exception.NotImplemented()
# service crud
def create_service(self, service_id, service_ref):
raise exception.NotImplemented()
def _list_services(self, hints):
for region_ref in six.itervalues(self.templates):
for service_type, service_ref in six.iteritems(region_ref):
yield {
'id': service_type,
'enabled': True,
'name': service_ref.get('name', ''),
'description': service_ref.get('description', ''),
'type': service_type,
}
def list_services(self, hints):
return list(self._list_services())
def get_service(self, service_id):
for service in self._list_services(hints=None):
if service['id'] == service_id:
return service
raise exception.ServiceNotFound(service_id=service_id)
def update_service(self, service_id, service_ref):
raise exception.NotImplemented()
def delete_service(self, service_id):
raise exception.NotImplemented()
# endpoint crud
def create_endpoint(self, endpoint_id, endpoint_ref):
raise exception.NotImplemented()
def _list_endpoints(self):
for region_id, region_ref in six.iteritems(self.templates):
for service_type, service_ref in six.iteritems(region_ref):
for key in service_ref:
if key.endswith('URL'):
interface = key[:-3]
endpoint_id = ('%s-%s-%s' %
(region_id, service_type, interface))
yield {
'id': endpoint_id,
'service_id': service_type,
'interface': interface,
'url': service_ref[key],
'legacy_endpoint_id': None,
'region_id': region_id,
'enabled': True,
}
def list_endpoints(self, hints):
return list(self._list_endpoints())
def get_endpoint(self, endpoint_id):
for endpoint in self._list_endpoints():
if endpoint['id'] == endpoint_id:
return endpoint
raise exception.EndpointNotFound(endpoint_id=endpoint_id)
def update_endpoint(self, endpoint_id, endpoint_ref):
raise exception.NotImplemented()
def delete_endpoint(self, endpoint_id):
raise exception.NotImplemented()
def get_catalog(self, user_id, tenant_id):
"""Retrieve and format the V2 service catalog.
@ -148,3 +237,58 @@ class Catalog(kvs.Catalog):
catalog[region][service] = service_data
return catalog
def add_endpoint_to_project(self, endpoint_id, project_id):
raise exception.NotImplemented()
def remove_endpoint_from_project(self, endpoint_id, project_id):
raise exception.NotImplemented()
def check_endpoint_in_project(self, endpoint_id, project_id):
raise exception.NotImplemented()
def list_endpoints_for_project(self, project_id):
raise exception.NotImplemented()
def list_projects_for_endpoint(self, endpoint_id):
raise exception.NotImplemented()
def delete_association_by_endpoint(self, endpoint_id):
raise exception.NotImplemented()
def delete_association_by_project(self, project_id):
raise exception.NotImplemented()
def create_endpoint_group(self, endpoint_group):
raise exception.NotImplemented()
def get_endpoint_group(self, endpoint_group_id):
raise exception.NotImplemented()
def update_endpoint_group(self, endpoint_group_id, endpoint_group):
raise exception.NotImplemented()
def delete_endpoint_group(self, endpoint_group_id):
raise exception.NotImplemented()
def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
raise exception.NotImplemented()
def get_endpoint_group_in_project(self, endpoint_group_id, project_id):
raise exception.NotImplemented()
def list_endpoint_groups(self):
raise exception.NotImplemented()
def list_endpoint_groups_for_project(self, project_id):
raise exception.NotImplemented()
def list_projects_associated_with_endpoint_group(self, endpoint_group_id):
raise exception.NotImplemented()
def remove_endpoint_group_from_project(self, endpoint_group_id,
project_id):
raise exception.NotImplemented()
def delete_endpoint_group_association_by_project(self, project_id):
raise exception.NotImplemented()

View File

@ -15,7 +15,6 @@
from dogpile.cache import region
from keystone.common.kvs.core import * # noqa
from keystone.common.kvs.legacy import Base, DictKvs, INMEMDB # noqa
# NOTE(morganfainberg): Provided backends are registered here in the __init__

View File

@ -1,61 +0,0 @@
# Copyright 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import versionutils
from keystone import exception
class DictKvs(dict):
def get(self, key, default=None):
try:
if isinstance(self[key], dict):
return self[key].copy()
else:
return self[key][:]
except KeyError:
if default is not None:
return default
raise exception.NotFound(target=key)
def set(self, key, value):
if isinstance(value, dict):
self[key] = value.copy()
else:
self[key] = value[:]
def delete(self, key):
"""Deletes an item, returning True on success, False otherwise."""
try:
del self[key]
except KeyError:
raise exception.NotFound(target=key)
INMEMDB = DictKvs()
class Base(object):
@versionutils.deprecated(versionutils.deprecated.ICEHOUSE,
in_favor_of='keystone.common.kvs.KeyValueStore',
remove_in=+2,
what='keystone.common.kvs.Base')
def __init__(self, db=None):
if db is None:
db = INMEMDB
elif isinstance(db, DictKvs):
db = db
elif isinstance(db, dict):
db = DictKvs(db)
self.db = db

View File

@ -49,7 +49,6 @@ environment.use_eventlet()
from keystone import auth
from keystone.common import config
from keystone.common import dependency
from keystone.common import kvs
from keystone.common.kvs import core as kvs_core
from keystone.common import sql
from keystone import exception
@ -534,7 +533,7 @@ class TestCase(BaseTestCase):
proxies=['oslo_cache.testing.CacheIsolatingProxy'])
self.config_fixture.config(
group='catalog',
driver='templated',
driver='sql',
template_file=dirs.tests('default_catalog.templates'))
self.config_fixture.config(
group='kvs',
@ -619,8 +618,6 @@ class TestCase(BaseTestCase):
# tests aren't used.
self.addCleanup(dependency.reset)
self.addCleanup(kvs.INMEMDB.clear)
# Ensure Notification subscriptions and resource types are empty
self.addCleanup(notifications.clear_subscribers)
self.addCleanup(notifications.reset_notifier)

View File

@ -4801,7 +4801,7 @@ class TrustTests(object):
class CatalogTests(object):
_legacy_endpoint_id_in_endpoint = False
_legacy_endpoint_id_in_endpoint = True
_enabled_default_to_true_when_creating_endpoint = False
def test_region_crud(self):
@ -5261,6 +5261,7 @@ class CatalogTests(object):
res = self.catalog_api.update_endpoint(endpoint_ref['id'],
{'interface': 'private'})
expected_endpoint = endpoint_ref.copy()
expected_endpoint['enabled'] = True
expected_endpoint['interface'] = 'private'
if self._legacy_endpoint_id_in_endpoint:
expected_endpoint['legacy_endpoint_id'] = None

View File

@ -103,60 +103,6 @@ class KvsToken(unit.TestCase, test_backend.TokenTests):
self.assertEqual(expected_user_token_list, user_token_list)
class KvsCatalog(unit.TestCase, test_backend.CatalogTests):
def setUp(self):
super(KvsCatalog, self).setUp()
self.load_backends()
self._load_fake_catalog()
def config_overrides(self):
super(KvsCatalog, self).config_overrides()
self.config_fixture.config(group='catalog', driver='kvs')
def _load_fake_catalog(self):
self.catalog_foobar = self.catalog_api.driver._create_catalog(
'foo', 'bar',
{'RegionFoo': {'service_bar': {'foo': 'bar'}}})
def test_get_catalog_returns_not_found(self):
# FIXME(dolph): this test should be moved up to test_backend
# FIXME(dolph): exceptions should be UserNotFound and ProjectNotFound
self.assertRaises(exception.NotFound,
self.catalog_api.get_catalog,
uuid.uuid4().hex,
'bar')
self.assertRaises(exception.NotFound,
self.catalog_api.get_catalog,
'foo',
uuid.uuid4().hex)
def test_get_catalog(self):
catalog_ref = self.catalog_api.get_catalog('foo', 'bar')
self.assertDictEqual(self.catalog_foobar, catalog_ref)
def test_get_catalog_endpoint_disabled(self):
# This test doesn't apply to KVS because with the KVS backend the
# application creates the catalog (including the endpoints) for each
# user and project. Whether endpoints are enabled or disabled isn't
# a consideration.
f = super(KvsCatalog, self).test_get_catalog_endpoint_disabled
self.assertRaises(exception.NotFound, f)
def test_get_v3_catalog_endpoint_disabled(self):
# There's no need to have disabled endpoints in the kvs catalog. Those
# endpoints should just be removed from the store. This just tests
# what happens currently when the super impl is called.
f = super(KvsCatalog, self).test_get_v3_catalog_endpoint_disabled
self.assertRaises(exception.NotFound, f)
def test_list_regions_filtered_by_parent_region_id(self):
self.skipTest('KVS backend does not support hints')
def test_service_filtering(self):
self.skipTest("kvs backend doesn't support filtering")
class KvsTokenCacheInvalidation(unit.TestCase,
test_backend.TokenCacheInvalidation):
def setUp(self):

View File

@ -232,11 +232,11 @@ class TestTemplatedCatalog(unit.TestCase, test_backend.CatalogTests):
self.skipTest(BROKEN_WRITE_FUNCTIONALITY_MSG)
def test_list_endpoints(self):
# NOTE(dstanek): a future commit will fix this functionality and
# this test
expected_ids = set()
expected_urls = set(['http://localhost:$(public_port)s/v2.0',
'http://localhost:$(admin_port)s/v2.0',
'http://localhost:8774/v1.1/$(tenant_id)s'])
endpoints = self.catalog_api.list_endpoints()
self.assertEqual(expected_ids, set(e['id'] for e in endpoints))
self.assertEqual(expected_urls, set(e['url'] for e in endpoints))
@unit.skip_if_cache_disabled('catalog')
def test_invalidate_cache_when_updating_endpoint(self):

View File

@ -23,6 +23,7 @@ from six.moves import http_client
from testtools import matchers
from keystone.common import extension as keystone_extension
from keystone.tests import unit
from keystone.tests.unit import ksfixtures
from keystone.tests.unit import rest
@ -987,6 +988,14 @@ class RestfulTestCase(rest.RestfulTestCase):
class V2TestCase(RestfulTestCase, CoreApiTests, LegacyV2UsernameTests):
def config_overrides(self):
super(V2TestCase, self).config_overrides()
self.config_fixture.config(
group='catalog',
driver='templated',
template_file=unit.dirs.tests('default_catalog.templates'))
def _get_user_id(self, r):
return r['user']['id']

View File

@ -0,0 +1,9 @@
---
other:
- >
[`bug 1367113 <https://bugs.launchpad.net/keystone/+bug/1367113>`_]
The "get entity" and "list entities" functionality for the KVS catalog
backend has been reimplemented to use the data from the catalog template.
Previously this would only act on temporary data that was created at
runtime. The create, update and delete entity functionality now raises
an exception.

View File

@ -22,3 +22,7 @@ other:
Removed ``check_role_for_trust`` from the trust controller, ensure policy
files do not refer to this target. This was deprecated in the Kilo
release.
- >
[`blueprint removed-as-of-mitaka <https://blueprints.launchpad.net/keystone/+spec/removed-as-of-mitaka>`_]
Removed Catalog KVS backend (``keystone.catalog.backends.sql.Catalog``).
This was deprecated in the Icehouse release.

View File

@ -106,7 +106,6 @@ keystone.auth.x509 =
default = keystone.auth.plugins.mapped:Mapped
keystone.catalog =
kvs = keystone.catalog.backends.kvs:Catalog
sql = keystone.catalog.backends.sql:Catalog
templated = keystone.catalog.backends.templated:Catalog
endpoint_filter.sql = keystone.contrib.endpoint_filter.backends.catalog_sql:EndpointFilterCatalog