From ff669f0da9cbf5250d8bb3e904608677f9164b6c Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Tue, 20 Nov 2012 10:28:26 -0600 Subject: [PATCH] v3 Catalog - v3 catalog tests (bug 1023933) - v3 catalog implementation (bug 1023938) Change-Id: Ie118819d25afbff62327ffc8be5b5fda2ef7f4ed --- keystone/catalog/backends/kvs.py | 43 ++++++++-- keystone/catalog/backends/sql.py | 61 +++++++++---- keystone/catalog/core.py | 30 +------ tests/test_backend.py | 35 +++++++- tests/test_backend_kvs.py | 16 ---- tests/test_backend_templated.py | 17 ---- tests/test_keystoneclient.py | 5 +- tests/test_v3_catalog.py | 143 +++++++++++++++++++++++++++++++ 8 files changed, 263 insertions(+), 87 deletions(-) create mode 100644 tests/test_v3_catalog.py diff --git a/keystone/catalog/backends/kvs.py b/keystone/catalog/backends/kvs.py index e63a75ff2e..82a303516d 100644 --- a/keystone/catalog/backends/kvs.py +++ b/keystone/catalog/backends/kvs.py @@ -24,11 +24,7 @@ class Catalog(kvs.Base, catalog.Driver): def get_catalog(self, user_id, tenant_id, metadata=None): return self.db.get('catalog-%s-%s' % (tenant_id, user_id)) - def get_service(self, service_id): - return self.db.get('service-%s' % service_id) - - def list_services(self): - return self.db.get('service_list', []) + # service crud def create_service(self, service_id, service): self.db.set('service-%s' % service_id, service) @@ -37,16 +33,53 @@ class Catalog(kvs.Base, catalog.Driver): self.db.set('service_list', list(service_list)) return service + def list_services(self): + 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): self.db.set('service-%s' % service_id, service) return 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.get_service(endpoint['service_id']) + 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): + 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): + self.db.set('endpoint-%s' % endpoint_id, endpoint) + return 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) diff --git a/keystone/catalog/backends/sql.py b/keystone/catalog/backends/sql.py index b2012c694b..df128c7bdb 100644 --- a/keystone/catalog/backends/sql.py +++ b/keystone/catalog/backends/sql.py @@ -52,22 +52,25 @@ class Catalog(sql.Base, catalog.Driver): # Services def list_services(self): session = self.get_session() - services = session.query(Service) - return [s['id'] for s in list(services)] + services = session.query(Service).all() + return [s.to_dict() for s in list(services)] + + def _get_service(self, session, service_id): + try: + return session.query(Service).filter_by(id=service_id).one() + except sql.NotFound: + raise exception.ServiceNotFound(service_id=service_id) def get_service(self, service_id): session = self.get_session() - service_ref = session.query(Service).filter_by(id=service_id).first() - if not service_ref: - raise exception.ServiceNotFound(service_id=service_id) - return service_ref.to_dict() + return self._get_service(session, service_id).to_dict() def delete_service(self, service_id): session = self.get_session() with session.begin(): + ref = self._get_service(session, service_id) session.query(Endpoint).filter_by(service_id=service_id).delete() - if not session.query(Service).filter_by(id=service_id).delete(): - raise exception.ServiceNotFound(service_id=service_id) + session.delete(ref) session.flush() def create_service(self, service_id, service_ref): @@ -78,6 +81,18 @@ class Catalog(sql.Base, catalog.Driver): session.flush() return service.to_dict() + def update_service(self, service_id, service_ref): + session = self.get_session() + with session.begin(): + ref = self._get_service(session, service_id) + old_dict = ref.to_dict() + old_dict.update(service_ref) + new_service = Service.from_dict(old_dict) + ref.type = new_service.type + ref.extra = new_service.extra + session.flush() + return ref.to_dict() + # Endpoints def create_endpoint(self, endpoint_id, endpoint_ref): session = self.get_session() @@ -95,18 +110,33 @@ class Catalog(sql.Base, catalog.Driver): raise exception.EndpointNotFound(endpoint_id=endpoint_id) session.flush() + def _get_endpoint(self, session, endpoint_id): + try: + return session.query(Endpoint).filter_by(id=endpoint_id).one() + except sql.NotFound: + raise exception.EndpointNotFound(endpoint_id=endpoint_id) + def get_endpoint(self, endpoint_id): session = self.get_session() - endpoint_ref = session.query(Endpoint) - endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first() - if not endpoint_ref: - raise exception.EndpointNotFound(endpoint_id=endpoint_id) - return endpoint_ref.to_dict() + return self._get_endpoint(session, endpoint_id).to_dict() def list_endpoints(self): session = self.get_session() endpoints = session.query(Endpoint) - return [e['id'] for e in list(endpoints)] + return [e.to_dict() for e in list(endpoints)] + + def update_endpoint(self, endpoint_id, endpoint_ref): + session = self.get_session() + with session.begin(): + ref = self._get_endpoint(session, endpoint_id) + old_dict = ref.to_dict() + old_dict.update(endpoint_ref) + new_endpoint = Endpoint.from_dict(old_dict) + ref.service_id = new_endpoint.service_id + ref.region = new_endpoint.region + ref.extra = new_endpoint.extra + session.flush() + return ref.to_dict() def get_catalog(self, user_id, tenant_id, metadata=None): d = dict(CONF.iteritems()) @@ -114,8 +144,7 @@ class Catalog(sql.Base, catalog.Driver): 'user_id': user_id}) catalog = {} - endpoints = [self.get_endpoint(e) - for e in self.list_endpoints()] + endpoints = self.list_endpoints() for ep in endpoints: service = self.get_service(ep['service_id']) srv_type = service['type'] diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py index e5b8fe66c4..2c396b5d52 100644 --- a/keystone/catalog/core.py +++ b/keystone/catalog/core.py @@ -116,14 +116,6 @@ class Driver(object): raise exception.NotImplemented() def list_services(self): - """List all service ids in catalog. - - :returns: list of service_ids or an empty list. - - """ - raise exception.NotImplemented() - - def get_all_services(self): """List all services. :returns: list of service_refs or an empty list. @@ -176,14 +168,6 @@ class Driver(object): raise exception.NotImplemented() def list_endpoints(self): - """List all endpoint ids in catalog. - - :returns: list of endpoint_ids or an empty list. - - """ - raise exception.NotImplemented() - - def get_all_endpoints(self): """List all endpoints. :returns: list of endpoint_refs or an empty list. @@ -242,14 +226,10 @@ class ServiceController(wsgi.Application): self.token_api = token.Manager() super(ServiceController, self).__init__() - # CRUD extensions - # NOTE(termie): this OS-KSADM stuff is not very consistent def get_services(self, context): self.assert_admin(context) service_list = self.catalog_api.list_services(context) - service_refs = [self.catalog_api.get_service(context, x) - for x in service_list] - return {'OS-KSADM:services': service_refs} + return {'OS-KSADM:services': service_list} def get_service(self, context, service_id): self.assert_admin(context) @@ -281,9 +261,7 @@ class EndpointController(wsgi.Application): def get_endpoints(self, context): self.assert_admin(context) endpoint_list = self.catalog_api.list_endpoints(context) - endpoint_refs = [self.catalog_api.get_endpoint(context, e) - for e in endpoint_list] - return {'endpoints': endpoint_refs} + return {'endpoints': endpoint_list} def create_endpoint(self, context, endpoint): self.assert_admin(context) @@ -312,7 +290,7 @@ class ServiceControllerV3(controller.V3Controller): def list_services(self, context): self.assert_admin(context) - refs = self.catalog_api.get_all_services(context) + refs = self.catalog_api.list_services(context) refs = self._filter_by_attribute(context, refs, 'type') return {'services': self._paginate(context, refs)} @@ -351,7 +329,7 @@ class EndpointControllerV3(controller.V3Controller): def list_endpoints(self, context): self.assert_admin(context) - refs = self.catalog_api.get_all_endpoints(context) + refs = self.catalog_api.list_endpoints(context) refs = self._filter_by_attribute(context, refs, 'service_id') refs = self._filter_by_attribute(context, refs, 'interface') return {'endpoints': self._paginate(context, refs)} diff --git a/tests/test_backend.py b/tests/test_backend.py index b1b0da8acd..5fd8d3cc38 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -863,7 +863,7 @@ class CatalogTests(object): # list services = self.catalog_api.list_services() - self.assertIn(service_id, services) + self.assertIn(service_id, [x['id'] for x in services]) # delete self.catalog_api.delete_service(service_id) @@ -872,6 +872,30 @@ class CatalogTests(object): self.assertRaises(exception.ServiceNotFound, self.catalog_man.get_service, {}, service_id) + def test_delete_service_with_endpoint(self): + # create a service + service = { + 'id': uuid.uuid4().hex, + 'type': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'description': uuid.uuid4().hex, + } + self.catalog_api.create_service(service['id'], service) + + # create an endpoint attached to the service + endpoint = { + 'id': uuid.uuid4().hex, + 'service_id': service['id'], + } + self.catalog_api.create_endpoint(endpoint['id'], endpoint) + + # deleting the service should also delete the endpoint + self.catalog_api.delete_service(service['id']) + self.assertRaises(exception.EndpointNotFound, + self.catalog_man.get_endpoint, {}, endpoint['id']) + self.assertRaises(exception.EndpointNotFound, + self.catalog_man.delete_endpoint, {}, endpoint['id']) + def test_get_service_404(self): self.assertRaises(exception.ServiceNotFound, self.catalog_man.get_service, @@ -890,18 +914,21 @@ class CatalogTests(object): 'service_id': uuid.uuid4().hex, } self.assertRaises(exception.ServiceNotFound, - self.catalog_api.create_endpoint, + self.catalog_man.create_endpoint, + {}, endpoint['id'], endpoint) def test_get_endpoint_404(self): self.assertRaises(exception.EndpointNotFound, - self.catalog_api.get_endpoint, + self.catalog_man.get_endpoint, + {}, uuid.uuid4().hex) def test_delete_endpoint_404(self): self.assertRaises(exception.EndpointNotFound, - self.catalog_api.delete_endpoint, + self.catalog_man.delete_endpoint, + {}, uuid.uuid4().hex) diff --git a/tests/test_backend_kvs.py b/tests/test_backend_kvs.py index 76bf9bd072..d3c79e704a 100644 --- a/tests/test_backend_kvs.py +++ b/tests/test_backend_kvs.py @@ -68,19 +68,3 @@ class KvsCatalog(test.TestCase, test_backend.CatalogTests): def test_get_catalog(self): catalog_ref = self.catalog_api.get_catalog('foo', 'bar') self.assertDictEqual(catalog_ref, self.catalog_foobar) - - def test_create_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.create_endpoint, - uuid.uuid4().hex, - {}) - - def test_get_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.get_endpoint, - uuid.uuid4().hex) - - def test_delete_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.delete_endpoint, - uuid.uuid4().hex) diff --git a/tests/test_backend_templated.py b/tests/test_backend_templated.py index 55cede2ed3..31d77acd78 100644 --- a/tests/test_backend_templated.py +++ b/tests/test_backend_templated.py @@ -15,7 +15,6 @@ # under the License. import os -import uuid from keystone import catalog from keystone.catalog.backends import templated as catalog_templated @@ -67,19 +66,3 @@ class TestTemplatedCatalog(test.TestCase, test_backend.CatalogTests): 'http://localhost:$(compute_port)s/v1.1/$(tenant)s' with self.assertRaises(exception.MalformedEndpoint): self.catalog_api.get_catalog('fake-user', 'fake-tenant') - - def test_create_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.create_endpoint, - uuid.uuid4().hex, - {}) - - def test_get_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.get_endpoint, - uuid.uuid4().hex) - - def test_delete_endpoint_404(self): - self.assertRaises(exception.NotImplemented, - self.catalog_api.delete_endpoint, - uuid.uuid4().hex) diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py index 7531556b38..0b82685e1b 100644 --- a/tests/test_keystoneclient.py +++ b/tests/test_keystoneclient.py @@ -711,7 +711,7 @@ class KeystoneClientTests(object): self.assertEquals(service_type, service.type) self.assertEquals(service_desc, service.description) - # update is not supported... + # update is not supported in API v2... # delete & read client.services.delete(id=service.id) @@ -736,10 +736,9 @@ class KeystoneClientTests(object): id=uuid.uuid4().hex) def test_endpoint_delete_404(self): - # the catalog backend is expected to return Not Implemented from keystoneclient import exceptions as client_exceptions client = self.get_client(admin=True) - self.assertRaises(client_exceptions.HTTPNotImplemented, + self.assertRaises(client_exceptions.NotFound, client.endpoints.delete, id=uuid.uuid4().hex) diff --git a/tests/test_v3_catalog.py b/tests/test_v3_catalog.py new file mode 100644 index 0000000000..9f5bf9139a --- /dev/null +++ b/tests/test_v3_catalog.py @@ -0,0 +1,143 @@ +import uuid + +import test_v3 + + +class CatalogTestCase(test_v3.RestfulTestCase): + """Test service & endpoint CRUD""" + + def setUp(self): + super(CatalogTestCase, self).setUp() + + self.service_id = uuid.uuid4().hex + self.service = self.new_service_ref() + self.service['id'] = self.service_id + self.catalog_api.create_service( + self.service_id, + self.service.copy()) + + self.endpoint_id = uuid.uuid4().hex + self.endpoint = self.new_endpoint_ref(service_id=self.service_id) + self.endpoint['id'] = self.endpoint_id + self.catalog_api.create_endpoint( + self.endpoint_id, + self.endpoint.copy()) + + # service validation + + def assertValidServiceListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'services', + self.assertValidService, + ref) + + def assertValidServiceResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'service', + self.assertValidService, + ref) + + def assertValidService(self, entity, ref=None): + self.assertIsNotNone(entity.get('type')) + if ref: + self.assertEqual(ref['type'], entity['type']) + return entity + + # endpoint validation + + def assertValidEndpointListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'endpoints', + self.assertValidEndpoint, + ref) + + def assertValidEndpointResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'endpoint', + self.assertValidEndpoint, + ref) + + def assertValidEndpoint(self, entity, ref=None): + self.assertIsNotNone(entity.get('interface')) + self.assertIsNotNone(entity.get('service_id')) + if ref: + self.assertEqual(ref['interface'], entity['interface']) + self.assertEqual(ref['service_id'], entity['service_id']) + return entity + + # service crud tests + + def test_create_service(self): + """POST /services""" + ref = self.new_service_ref() + r = self.post( + '/services', + body={'service': ref}) + return self.assertValidServiceResponse(r, ref) + + def test_list_services(self): + """GET /services""" + r = self.get('/services') + self.assertValidServiceListResponse(r, self.service) + + def test_get_service(self): + """GET /services/{service_id}""" + r = self.get('/services/%(service_id)s' % { + 'service_id': self.service_id}) + self.assertValidServiceResponse(r, self.service) + + def test_update_service(self): + """PATCH /services/{service_id}""" + service = self.new_service_ref() + del service['id'] + r = self.patch('/services/%(service_id)s' % { + 'service_id': self.service_id}, + body={'service': service}) + self.assertValidServiceResponse(r, service) + + def test_delete_service(self): + """DELETE /services/{service_id}""" + self.delete('/services/%(service_id)s' % { + 'service_id': self.service_id}) + + # endpoint crud tests + + def test_list_endpoints(self): + """GET /endpoints""" + r = self.get('/endpoints') + self.assertValidEndpointListResponse(r, self.endpoint) + + def test_create_endpoint(self): + """POST /endpoints""" + ref = self.new_endpoint_ref(service_id=self.service_id) + r = self.post( + '/endpoints', + body={'endpoint': ref}) + self.assertValidEndpointResponse(r, ref) + + def test_get_endpoint(self): + """GET /endpoints/{endpoint_id}""" + r = self.get( + '/endpoints/%(endpoint_id)s' % { + 'endpoint_id': self.endpoint_id}) + self.assertValidEndpointResponse(r, self.endpoint) + + def test_update_endpoint(self): + """PATCH /endpoints/{endpoint_id}""" + ref = self.new_endpoint_ref(service_id=self.service_id) + del ref['id'] + r = self.patch( + '/endpoints/%(endpoint_id)s' % { + 'endpoint_id': self.endpoint_id}, + body={'endpoint': ref}) + self.assertValidEndpointResponse(r, ref) + + def test_delete_endpoint(self): + """DELETE /endpoints/{endpoint_id}""" + self.delete( + '/endpoints/%(endpoint_id)s' % { + 'endpoint_id': self.endpoint_id})