diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index c6765ab72d..13c6c19f39 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -79,7 +79,7 @@ # expiration = 86400 [policy] -# driver = keystone.policy.backends.rules.Policy +# driver = keystone.policy.backends.sql.Policy [ec2] # driver = keystone.contrib.ec2.backends.kvs.Ec2 diff --git a/keystone/common/sql/core.py b/keystone/common/sql/core.py index 54f3e20ce5..a376ed8778 100644 --- a/keystone/common/sql/core.py +++ b/keystone/common/sql/core.py @@ -44,6 +44,7 @@ String = sql.String ForeignKey = sql.ForeignKey DateTime = sql.DateTime IntegrityError = sql.exc.IntegrityError +NotFound = sql.orm.exc.NoResultFound Boolean = sql.Boolean diff --git a/keystone/common/sql/migrate_repo/versions/006_add_policy_table.py b/keystone/common/sql/migrate_repo/versions/006_add_policy_table.py new file mode 100644 index 0000000000..525a3b02d6 --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/006_add_policy_table.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 migrate +import sqlalchemy as sql + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + policy_table = sql.Table( + 'policy', + meta, + sql.Column('id', sql.String(64), primary_key=True), + sql.Column('type', sql.String(255), nullable=False), + sql.Column('blob', sql.Text(), nullable=False), + sql.Column('extra', sql.Text())) + policy_table.create(migrate_engine, checkfirst=True) + + +def downgrade(migrate_engine): + pass diff --git a/keystone/config.py b/keystone/config.py index f1bfbd8737..62967ecd34 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -161,7 +161,7 @@ register_str('driver', group='catalog', register_str('driver', group='identity', default='keystone.identity.backends.sql.Identity') register_str('driver', group='policy', - default='keystone.policy.backends.rules.Policy') + default='keystone.policy.backends.sql.Policy') register_str('driver', group='token', default='keystone.token.backends.kvs.Token') register_str('driver', group='ec2', diff --git a/keystone/policy/backends/sql.py b/keystone/policy/backends/sql.py new file mode 100644 index 0000000000..6c45dade8d --- /dev/null +++ b/keystone/policy/backends/sql.py @@ -0,0 +1,103 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 functools + +from keystone.common import sql +from keystone.common.sql import migration +from keystone import exception +from keystone.policy.backends import rules + + +def handle_conflicts(type='object'): + """Converts IntegrityError into HTTP 409 Conflict.""" + def decorator(method): + @functools.wraps(method) + def wrapper(*args, **kwargs): + try: + return method(*args, **kwargs) + except sql.IntegrityError as e: + raise exception.Conflict(type=type, details=str(e)) + return wrapper + return decorator + + +class PolicyModel(sql.ModelBase, sql.DictBase): + __tablename__ = 'policy' + attributes = ['id', 'blob', 'type'] + id = sql.Column(sql.String(64), primary_key=True) + blob = sql.Column(sql.JsonBlob(), nullable=False) + type = sql.Column(sql.String(255), nullable=False) + extra = sql.Column(sql.JsonBlob()) + + +class Policy(sql.Base, rules.Policy): + # Internal interface to manage the database + def db_sync(self): + migration.db_sync() + + @handle_conflicts(type='policy') + def create_policy(self, policy_id, policy): + session = self.get_session() + + with session.begin(): + ref = PolicyModel.from_dict(policy) + session.add(ref) + session.flush() + + return ref.to_dict() + + def list_policies(self): + session = self.get_session() + + refs = session.query(PolicyModel).all() + return [ref.to_dict() for ref in refs] + + def _get_policy(self, session, policy_id): + """Private method to get a policy model object (NOT a dictionary).""" + try: + return session.query(PolicyModel).filter_by(id=policy_id).one() + except sql.NotFound: + raise exception.PolicyNotFound(policy_id=policy_id) + + def get_policy(self, policy_id): + session = self.get_session() + + return self._get_policy(session, policy_id).to_dict() + + @handle_conflicts(type='policy') + def update_policy(self, policy_id, policy): + session = self.get_session() + + with session.begin(): + ref = self._get_policy(session, policy_id) + old_dict = ref.to_dict() + old_dict.update(policy) + new_policy = PolicyModel.from_dict(old_dict) + ref.blob = new_policy.blob + ref.type = new_policy.type + ref.extra = new_policy.extra + session.flush() + + return ref.to_dict() + + def delete_policy(self, policy_id): + session = self.get_session() + + with session.begin(): + ref = self._get_policy(session, policy_id) + session.delete(ref) + session.flush() diff --git a/keystone/policy/core.py b/keystone/policy/core.py index 498e027ba1..36b982c8be 100644 --- a/keystone/policy/core.py +++ b/keystone/policy/core.py @@ -68,10 +68,9 @@ class Driver(object): raise exception.NotImplemented() def create_policy(self, policy_id, policy): - """Store a policy blob for a particular endpoint. + """Store a policy blob. - :raises: keystone.exception.EndpointNotFound, - keystone.exception.Conflict + :raises: keystone.exception.Conflict """ raise exception.NotImplemented() @@ -91,8 +90,7 @@ class Driver(object): def update_policy(self, policy_id, policy): """Update a policy blob. - :raises: keystone.exception.PolicyNotFound, - keystone.exception.EndpointNotFound + :raises: keystone.exception.PolicyNotFound """ raise exception.NotImplemented() @@ -113,9 +111,6 @@ class PolicyControllerV3(controller.V3Controller): ref = self._assign_unique_id(self._normalize_dict(policy)) self._require_attribute(ref, 'blob') self._require_attribute(ref, 'type') - self._require_attribute(ref, 'endpoint_id') - - self.catalog_api.get_endpoint(context, ref['endpoint_id']) ref = self.policy_api.create_policy(context, ref['id'], ref) return {'policy': ref} @@ -123,7 +118,6 @@ class PolicyControllerV3(controller.V3Controller): def list_policies(self, context): self.assert_admin(context) refs = self.policy_api.list_policies(context) - refs = self._filter_by_attribute(context, refs, 'endpoint_id') refs = self._filter_by_attribute(context, refs, 'type') return {'policies': self._paginate(context, refs)} @@ -134,10 +128,6 @@ class PolicyControllerV3(controller.V3Controller): def update_policy(self, context, policy_id, policy): self.assert_admin(context) - - if 'endpoint_id' in policy: - self.catalog_api.get_endpoint(context, policy['endpoint_id']) - ref = self.policy_api.update_policy(context, policy_id, policy) return {'policy': ref} diff --git a/keystone/test.py b/keystone/test.py index 4605609556..8caff1c28c 100644 --- a/keystone/test.py +++ b/keystone/test.py @@ -206,6 +206,7 @@ class TestCase(NoModule, unittest.TestCase): self.identity_api = importutils.import_object(CONF.identity.driver) self.token_api = importutils.import_object(CONF.token.driver) self.catalog_api = importutils.import_object(CONF.catalog.driver) + self.policy_api = importutils.import_object(CONF.policy.driver) def load_fixtures(self, fixtures): """Hacky basic and naive fixture loading based on a python module. diff --git a/tests/backend_sql.conf b/tests/backend_sql.conf index 0ac589ae71..79bb34055d 100644 --- a/tests/backend_sql.conf +++ b/tests/backend_sql.conf @@ -13,3 +13,6 @@ driver = keystone.contrib.ec2.backends.sql.Ec2 [catalog] driver = keystone.catalog.backends.sql.Catalog + +[policy] +driver = keystone.policy.backends.sql.Policy diff --git a/tests/test_backend.py b/tests/test_backend.py index 76e7678030..b1b0da8acd 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -903,3 +903,88 @@ class CatalogTests(object): self.assertRaises(exception.EndpointNotFound, self.catalog_api.delete_endpoint, uuid.uuid4().hex) + + +class PolicyTests(object): + def _new_policy_ref(self): + return { + 'id': uuid.uuid4().hex, + 'policy': uuid.uuid4().hex, + 'type': uuid.uuid4().hex, + 'endpoint_id': uuid.uuid4().hex, + } + + def assertEqualPolicies(self, a, b): + self.assertEqual(a['id'], b['id']) + self.assertEqual(a['endpoint_id'], b['endpoint_id']) + self.assertEqual(a['policy'], b['policy']) + self.assertEqual(a['type'], b['type']) + + def test_create(self): + ref = self._new_policy_ref() + res = self.policy_api.create_policy(ref['id'], ref) + self.assertEqualPolicies(ref, res) + + def test_get(self): + ref = self._new_policy_ref() + res = self.policy_api.create_policy(ref['id'], ref) + + res = self.policy_api.get_policy(ref['id']) + self.assertEqualPolicies(ref, res) + + def test_list(self): + ref = self._new_policy_ref() + self.policy_api.create_policy(ref['id'], ref) + + res = self.policy_api.list_policies() + res = [x for x in res if x['id'] == ref['id']][0] + self.assertEqualPolicies(ref, res) + + def test_update(self): + ref = self._new_policy_ref() + self.policy_api.create_policy(ref['id'], ref) + orig = ref + + ref = self._new_policy_ref() + + # (cannot change policy ID) + self.assertRaises(exception.ValidationError, + self.policy_man.update_policy, + {}, + orig['id'], + ref) + + ref['id'] = orig['id'] + res = self.policy_api.update_policy(orig['id'], ref) + self.assertEqualPolicies(ref, res) + + def test_delete(self): + ref = self._new_policy_ref() + self.policy_api.create_policy(ref['id'], ref) + + self.policy_api.delete_policy(ref['id']) + self.assertRaises(exception.PolicyNotFound, + self.policy_man.delete_policy, {}, ref['id']) + self.assertRaises(exception.PolicyNotFound, + self.policy_man.get_policy, {}, ref['id']) + res = self.policy_api.list_policies() + self.assertFalse(len([x for x in res if x['id'] == ref['id']])) + + def test_get_policy_404(self): + self.assertRaises(exception.PolicyNotFound, + self.policy_man.get_policy, + {}, + uuid.uuid4().hex) + + def test_update_policy_404(self): + self.assertRaises(exception.PolicyNotFound, + self.policy_man.update_policy, + {}, + uuid.uuid4().hex, + {}) + + def test_delete_policy_404(self): + self.assertRaises(exception.PolicyNotFound, + self.policy_man.delete_policy, + {}, + uuid.uuid4().hex) diff --git a/tests/test_backend_sql.py b/tests/test_backend_sql.py index c326dd0929..c089bac700 100644 --- a/tests/test_backend_sql.py +++ b/tests/test_backend_sql.py @@ -21,6 +21,7 @@ from keystone import catalog from keystone import config from keystone import exception from keystone import identity +from keystone import policy from keystone import test from keystone import token @@ -42,11 +43,13 @@ class SqlTests(test.TestCase): self.catalog_man = catalog.Manager() self.identity_man = identity.Manager() self.token_man = token.Manager() + self.policy_man = policy.Manager() # create shortcut references to each driver self.catalog_api = self.catalog_man.driver self.identity_api = self.identity_man.driver self.token_api = self.token_man.driver + self.policy_api = self.policy_man.driver # populate the engine with tables & fixtures self.load_fixtures(default_fixtures) @@ -272,3 +275,7 @@ class SqlCatalog(SqlTests, test_backend.CatalogTests): self.catalog_man.delete_service, {}, "c") self.assertRaises(exception.EndpointNotFound, self.catalog_man.delete_endpoint, {}, "d") + + +class SqlPolicy(SqlTests, test_backend.PolicyTests): + pass diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py index 1eb5be44a6..7531556b38 100644 --- a/tests/test_keystoneclient.py +++ b/tests/test_keystoneclient.py @@ -1097,7 +1097,14 @@ class KcEssex3TestCase(CompatTestCase, KeystoneClientTests): def test_endpoint_delete_404(self): raise nose.exc.SkipTest('N/A') + def test_policy_crud(self): + """Due to lack of endpoint CRUD""" + raise nose.exc.SkipTest('N/A') + class Kc11TestCase(CompatTestCase, KeystoneClientTests): def get_checkout(self): return KEYSTONECLIENT_REPO, '0.1.1' + + def test_policy_crud(self): + raise nose.exc.SkipTest('N/A') diff --git a/tests/test_keystoneclient_sql.py b/tests/test_keystoneclient_sql.py index 8509772c8e..69c667ed97 100644 --- a/tests/test_keystoneclient_sql.py +++ b/tests/test_keystoneclient_sql.py @@ -16,6 +16,8 @@ import uuid +import nose.exc + from keystone.common import sql from keystone import config from keystone import test @@ -94,3 +96,76 @@ class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase): self.assertRaises(client_exceptions.NotFound, client.endpoints.delete, id=uuid.uuid4().hex) + + def test_policy_crud(self): + # FIXME(dolph): this test was written prior to the v3 implementation of + # the client and essentially refers to a non-existent + # policy manager in the v2 client. this test needs to be + # moved to a test suite running against the v3 api + raise nose.exc.SkipTest('Written prior to v3 client; needs refactor') + + from keystoneclient import exceptions as client_exceptions + client = self.get_client(admin=True) + + policy_blob = uuid.uuid4().hex + policy_type = uuid.uuid4().hex + service = client.services.create( + name=uuid.uuid4().hex, + service_type=uuid.uuid4().hex, + description=uuid.uuid4().hex) + endpoint = client.endpoints.create( + service_id=service.id, + region=uuid.uuid4().hex, + adminurl=uuid.uuid4().hex, + internalurl=uuid.uuid4().hex, + publicurl=uuid.uuid4().hex) + + # create + policy = client.policies.create( + blob=policy_blob, + type=policy_type, + endpoint=endpoint.id) + self.assertEquals(policy_blob, policy.policy) + self.assertEquals(policy_type, policy.type) + self.assertEquals(endpoint.id, policy.endpoint_id) + + policy = client.policies.get(policy=policy.id) + self.assertEquals(policy_blob, policy.policy) + self.assertEquals(policy_type, policy.type) + self.assertEquals(endpoint.id, policy.endpoint_id) + + endpoints = [x for x in client.endpoints.list() if x.id == endpoint.id] + endpoint = endpoints[0] + self.assertEquals(policy_blob, policy.policy) + self.assertEquals(policy_type, policy.type) + self.assertEquals(endpoint.id, policy.endpoint_id) + + # update + policy_blob = uuid.uuid4().hex + policy_type = uuid.uuid4().hex + endpoint = client.endpoints.create( + service_id=service.id, + region=uuid.uuid4().hex, + adminurl=uuid.uuid4().hex, + internalurl=uuid.uuid4().hex, + publicurl=uuid.uuid4().hex) + + policy = client.policies.update( + policy=policy.id, + blob=policy_blob, + type=policy_type, + endpoint=endpoint.id) + + policy = client.policies.get(policy=policy.id) + self.assertEquals(policy_blob, policy.policy) + self.assertEquals(policy_type, policy.type) + self.assertEquals(endpoint.id, policy.endpoint_id) + + # delete + client.policies.delete(policy=policy.id) + self.assertRaises( + client_exceptions.NotFound, + client.policies.get, + policy=policy.id) + policies = [x for x in client.policies.list() if x.id == policy.id] + self.assertEquals(len(policies), 0) diff --git a/tests/test_sql_upgrade.py b/tests/test_sql_upgrade.py index f314a5bc41..9b67fd5f59 100644 --- a/tests/test_sql_upgrade.py +++ b/tests/test_sql_upgrade.py @@ -50,8 +50,7 @@ class SqlUpgradeTests(test.TestCase): super(SqlUpgradeTests, self).tearDown() def test_blank_db_to_start(self): - self.assertFalse(self.is_user_table_created(), - "User should not be defined yet") + self.assertTableDoesNotExist('user') def test_start_version_0(self): version = migration.db_version() @@ -66,7 +65,7 @@ class SqlUpgradeTests(test.TestCase): def test_upgrade_0_to_1(self): self.assertEqual(self.schema.version, 0, "DB is at version 0") self._migrate(self.repo_path, 1) - self.assertEqual(self.schema.version, 1, "DB is at version 0") + self.assertEqual(self.schema.version, 1, "DB is at version 1") self.assertTableColumns("user", ["id", "name", "extra"]) self.assertTableColumns("tenant", ["id", "name", "extra"]) self.assertTableColumns("role", ["id", "name"]) @@ -75,6 +74,16 @@ class SqlUpgradeTests(test.TestCase): self.assertTableColumns("metadata", ["user_id", "tenant_id", "data"]) self.populate_user_table() + def test_upgrade_5_to_6(self): + self._migrate(self.repo_path, 5) + self.assertEqual(self.schema.version, 5) + self.assertTableDoesNotExist('policy') + + self._migrate(self.repo_path, 6) + self.assertEqual(self.schema.version, 6) + self.assertTableExists('policy') + self.assertTableColumns('policy', ['id', 'type', 'blob', 'extra']) + def populate_user_table(self): for user in default_fixtures.USERS: extra = copy.deepcopy(user) @@ -92,12 +101,21 @@ class SqlUpgradeTests(test.TestCase): s = sqlalchemy.select([table]) return s - def is_user_table_created(self): + def assertTableExists(self, table_name): + """Asserts that a given table exists can be selected by name.""" try: - self.select_table("user") - return True + self.select_table(table_name) except sqlalchemy.exc.NoSuchTableError: - return False + raise AssertionError('Table "%s" does not exist' % table_name) + + def assertTableDoesNotExist(self, table_name): + """Asserts that a given table exists cannot be selected by name.""" + try: + self.assertTableExists(table_name) + except AssertionError: + pass + else: + raise AssertionError('Table "%s" already exists' % table_name) def _migrate(self, repository, version): upgrade = True diff --git a/tests/test_v3.py b/tests/test_v3.py new file mode 100644 index 0000000000..81a088e84c --- /dev/null +++ b/tests/test_v3.py @@ -0,0 +1,181 @@ +import uuid + +from keystone.common.sql import util as sql_util +from keystone import test + +import test_content_types + + +BASE_URL = 'http://127.0.0.1:35357/v3' + + +class RestfulTestCase(test_content_types.RestfulTestCase): + def setUp(self): + self.config([ + test.etcdir('keystone.conf.sample'), + test.testsdir('test_overrides.conf'), + test.testsdir('backend_sql.conf'), + test.testsdir('backend_sql_disk.conf')]) + sql_util.setup_test_database() + self.load_backends() + self.public_server = self.serveapp('keystone', name='main') + self.admin_server = self.serveapp('keystone', name='admin') + + def tearDown(self): + self.public_server.kill() + self.admin_server.kill() + self.public_server = None + self.admin_server = None + + def new_ref(self): + """Populates a ref with attributes common to all API entities.""" + return { + 'id': uuid.uuid4().hex, + 'name': uuid.uuid4().hex, + 'description': uuid.uuid4().hex, + 'enabled': True} + + def new_service_ref(self): + ref = self.new_ref() + ref['type'] = uuid.uuid4().hex + return ref + + def new_endpoint_ref(self, service_id): + ref = self.new_ref() + ref['interface'] = uuid.uuid4().hex + ref['service_id'] = service_id + return ref + + def new_domain_ref(self): + ref = self.new_ref() + return ref + + def new_project_ref(self, domain_id): + ref = self.new_ref() + ref['domain_id'] = domain_id + return ref + + def new_user_ref(self, domain_id, project_id=None): + ref = self.new_ref() + ref['domain_id'] = domain_id + ref['email'] = uuid.uuid4().hex + if project_id: + ref['project_id'] = project_id + return ref + + def new_credential_ref(self, user_id, project_id=None): + ref = self.new_ref() + ref['user_id'] = user_id + ref['blob'] = uuid.uuid4().hex + ref['type'] = uuid.uuid4().hex + if project_id: + ref['project_id'] = project_id + return ref + + def new_role_ref(self): + ref = self.new_ref() + return ref + + def new_policy_ref(self): + ref = self.new_ref() + ref['blob'] = uuid.uuid4().hex + ref['type'] = uuid.uuid4().hex + return ref + + def get_scoped_token(self): + """Convenience method so that we can test authenticated requests.""" + # FIXME(dolph): should use real auth + return 'ADMIN' + + r = self.admin_request( + method='POST', + path='/v3/tokens', + body={ + 'auth': { + 'passwordCredentials': { + 'username': self.user_foo['name'], + 'password': self.user_foo['password'], + }, + 'tenantId': self.tenant_bar['id'], + }, + }) + return r.body['access']['token']['id'] + + def v3_request(self, path, **kwargs): + path = '/v3' + path + return self.admin_request( + path=path, + token=self.get_scoped_token(), + **kwargs) + + def get(self, path, **kwargs): + return self.v3_request(method='GET', path=path, **kwargs) + + def head(self, path, **kwargs): + return self.v3_request(method='HEAD', path=path, **kwargs) + + def post(self, path, **kwargs): + return self.v3_request(method='POST', path=path, **kwargs) + + def patch(self, path, **kwargs): + return self.v3_request(method='PATCH', path=path, **kwargs) + + def delete(self, path, **kwargs): + return self.v3_request(method='DELETE', path=path, **kwargs) + + def assertValidListResponse(self, resp, key, entity_validator, ref=None): + """Make assertions common to all API list responses. + + If a reference is provided, it's ID will be searched for in the + response, and asserted to be equal. + + """ + entities = resp.body.get(key) + self.assertIsNotNone(entities) + self.assertTrue(len(entities)) + for entity in entities: + self.assertIsNotNone(entity) + self.assertValidEntity(entity) + entity_validator(entity) + if ref: + entity = [x for x in entities if x['id'] == ref['id']][0] + self.assertValidEntity(entity, ref) + entity_validator(entity, ref) + return entities + + def assertValidResponse(self, resp, key, entity_validator, ref): + """Make assertions common to all API responses.""" + entity = resp.body.get(key) + self.assertIsNotNone(entity) + self.assertValidEntity(entity, ref) + entity_validator(entity, ref) + return entity + + def assertValidEntity(self, entity, ref=None): + """Make assertions common to all API entities. + + If a reference is provided, the entity will also be compared against + the reference. + """ + keys = ['name', 'description', 'enabled'] + + for k in ['id'] + keys: + msg = '%s unnexpectedly None in %s' % (k, entity) + self.assertIsNotNone(entity.get(k), msg) + + # FIXME(dolph): need to test this in v3 + # self.assertIsNotNone(entity.get('link')) + # self.assertIsNotNone(entity['link'].get('href')) + # self.assertEquals(entity['link'].get('rel'), 'self') + + if ref: + for k in keys: + msg = '%s not equal: %s != %s' % (k, ref[k], entity[k]) + self.assertEquals(ref[k], entity[k]) + + return entity + + +class VersionTestCase(RestfulTestCase): + def test_get_version(self): + pass diff --git a/tests/test_v3_policy.py b/tests/test_v3_policy.py new file mode 100644 index 0000000000..5898aad3c0 --- /dev/null +++ b/tests/test_v3_policy.py @@ -0,0 +1,78 @@ +import uuid + +import test_v3 + + +class PolicyTestCase(test_v3.RestfulTestCase): + """Test policy CRUD""" + + def setUp(self): + super(PolicyTestCase, self).setUp() + self.policy_id = uuid.uuid4().hex + self.policy = self.new_policy_ref() + self.policy['id'] = self.policy_id + self.policy_api.create_policy( + self.policy_id, + self.policy.copy()) + + # policy validation + + def assertValidPolicyListResponse(self, resp, ref): + return self.assertValidListResponse( + resp, + 'policies', + self.assertValidPolicy, + ref) + + def assertValidPolicyResponse(self, resp, ref): + return self.assertValidResponse( + resp, + 'policy', + self.assertValidPolicy, + ref) + + def assertValidPolicy(self, entity, ref=None): + self.assertIsNotNone(entity.get('blob')) + self.assertIsNotNone(entity.get('type')) + if ref: + self.assertEqual(ref['blob'], entity['blob']) + self.assertEqual(ref['type'], entity['type']) + return entity + + # policy crud tests + + def test_create_policy(self): + """POST /policies""" + ref = self.new_policy_ref() + r = self.post( + '/policies', + body={'policy': ref}) + return self.assertValidPolicyResponse(r, ref) + + def test_list_policies(self): + """GET /policies""" + r = self.get('/policies') + self.assertValidPolicyListResponse(r, self.policy) + + def test_get_policy(self): + """GET /policies/{policy_id}""" + r = self.get( + '/policies/%(policy_id)s' % { + 'policy_id': self.policy_id}) + self.assertValidPolicyResponse(r, self.policy) + + def test_update_policy(self): + """PATCH /policies/{policy_id}""" + policy = self.new_policy_ref() + policy['id'] = self.policy_id + r = self.patch( + '/policies/%(policy_id)s' % { + 'policy_id': self.policy_id}, + body={'policy': policy}) + self.assertValidPolicyResponse(r, policy) + + def test_delete_policy(self): + """DELETE /policies/{policy_id}""" + self.delete( + '/policies/%(policy_id)s' % { + 'policy_id': self.policy_id})