implement GET /v3/catalog

Change-Id: I0ed53f4758cc6bd8771ef2b4b291480bbbfc1631
blueprint: get-catalog
This commit is contained in:
Dolph Mathews 2014-07-14 18:11:46 -05:00
parent 6ee93ca415
commit d39f9f2bfa
6 changed files with 107 additions and 3 deletions

View File

@ -25,6 +25,8 @@
"identity:update_endpoint": "rule:admin_required",
"identity:delete_endpoint": "rule:admin_required",
"identity:get_catalog": "",
"identity:get_domain": "rule:admin_required",
"identity:list_domains": "rule:admin_required",
"identity:create_domain": "rule:admin_required",

View File

@ -28,6 +28,8 @@
"identity:update_endpoint": "rule:cloud_admin",
"identity:delete_endpoint": "rule:cloud_admin",
"identity:get_catalog": "",
"identity:get_domain": "rule:cloud_admin",
"identity:list_domains": "rule:cloud_admin",
"identity:create_domain": "rule:cloud_admin",

View File

@ -17,6 +17,7 @@ import uuid
import six
from keystone.common import authorization
from keystone.common import controller
from keystone.common import dependency
from keystone.common import wsgi
@ -138,6 +139,36 @@ class Endpoint(controller.V2Controller):
raise exception.EndpointNotFound(endpoint_id=endpoint_id)
@dependency.requires('catalog_api')
class CatalogV3(controller.V3Controller):
collection_name = 'catalog'
@controller.protected()
def get_catalog(self, context):
# TODO(dolphm): this method of accessing the auth context is terrible,
# but context needs to be refactored to always have reasonable values.
env_context = context.get('environment', {})
auth_context = env_context.get(authorization.AUTH_CONTEXT_ENV, {})
user_id = auth_context.get('user_id')
project_id = auth_context.get('project_id')
if not user_id or not project_id:
raise exception.Forbidden(
_('A project-scoped token is required to produce a service '
'catalog.'))
# The V3Controller base methods mostly assume that you're returning
# either a collection or a single element from a collection, neither of
# which apply to the catalog. Because this is a special case, this
# re-implements a tiny bit of work done by the base controller (such as
# self-referential link building) to avoid overriding or refactoring
# several private methods.
return {
'catalog': self.catalog_api.get_v3_catalog(user_id, project_id),
'links': {
'self': CatalogV3.base_url(context)}}
@dependency.requires('catalog_api')
class RegionV3(controller.V3Controller):
collection_name = 'regions'

View File

@ -32,3 +32,9 @@ def append_v3_routers(mapper, routers):
'services', 'service'))
routers.append(router.Router(controllers.EndpointV3(),
'endpoints', 'endpoint'))
mapper.connect(
'/catalog',
controller=controllers.CatalogV3(),
action='get_catalog',
conditions=dict(method=['GET']))

View File

@ -725,6 +725,35 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase,
return self.assertDictEqual(normalize(a), normalize(b))
# catalog validation
def assertValidCatalogResponse(self, resp, *args, **kwargs):
self.assertEqual(['catalog', 'links'], resp.json.keys())
self.assertValidCatalog(resp.json['catalog'])
self.assertIn('links', resp.json)
self.assertIsInstance(resp.json['links'], dict)
self.assertEqual(['self'], resp.json['links'].keys())
self.assertEqual(
'http://localhost/v3/catalog',
resp.json['links']['self'])
def assertValidCatalog(self, entity):
self.assertIsInstance(entity, list)
self.assertTrue(len(entity) > 0)
for service in entity:
self.assertIsNotNone(service.get('id'))
self.assertIsNotNone(service.get('name'))
self.assertIsNotNone(service.get('type'))
self.assertNotIn('enabled', service)
self.assertTrue(len(service['endpoints']) > 0)
for endpoint in service['endpoints']:
self.assertIsNotNone(endpoint.get('id'))
self.assertIsNotNone(endpoint.get('interface'))
self.assertIsNotNone(endpoint.get('url'))
self.assertNotIn('enabled', endpoint)
self.assertNotIn('legacy_endpoint_id', endpoint)
self.assertNotIn('service_id', endpoint)
# region validation
def assertValidRegionListResponse(self, resp, *args, **kwargs):

View File

@ -24,6 +24,43 @@ from keystone.tests import test_v3
class CatalogTestCase(test_v3.RestfulTestCase):
"""Test service & endpoint CRUD."""
def test_get_catalog_project_scoped_token(self):
"""Call ``GET /catalog`` with a project-scoped token."""
r = self.get(
'/catalog',
expected_status=200)
self.assertValidCatalogResponse(r)
def test_get_catalog_domain_scoped_token(self):
"""Call ``GET /catalog`` with a domain-scoped token."""
# grant a domain role to a user
self.put(path='/domains/%s/users/%s/roles/%s' % (
self.domain['id'], self.user['id'], self.role['id']))
self.get(
'/catalog',
auth=self.build_authentication_request(
user_id=self.user['id'],
password=self.user['password'],
domain_id=self.domain['id']),
expected_status=403)
def test_get_catalog_unscoped_token(self):
"""Call ``GET /catalog`` with an unscoped token."""
self.get(
'/catalog',
auth=self.build_authentication_request(
user_id=self.default_domain_user['id'],
password=self.default_domain_user['password']),
expected_status=403)
def test_get_catalog_no_token(self):
"""Call ``GET /catalog`` without a token."""
self.get(
'/catalog',
noauth=True,
expected_status=401)
# region crud tests
def test_create_region_with_id(self):
@ -358,9 +395,6 @@ class CatalogTestCase(test_v3.RestfulTestCase):
body={'endpoint': ref},
expected_status=400)
def assertValidErrorResponse(self, response):
self.assertIn(response.status_code, [400, 409])
def test_create_endpoint_400(self):
"""Call ``POST /endpoints``."""
ref = self.new_endpoint_ref(service_id=self.service_id)