diff --git a/congress/api/policy_model.py b/congress/api/policy_model.py index 28a39cb8a..f20ce3d84 100644 --- a/congress/api/policy_model.py +++ b/congress/api/policy_model.py @@ -102,9 +102,7 @@ class PolicyModel(base.APIModel): 'kind': item.get('kind'), 'desc': item.get('description')}) except exception.CongressException as e: - (num, desc) = error_codes.get('failed_to_create_policy') - raise webservice.DataModelException( - num, desc + ": " + str(e)) + raise webservice.DataModelException.create(e) return (policy_metadata['id'], policy_metadata) diff --git a/congress/db/db_policy_rules.py b/congress/db/db_policy_rules.py index b1dc98f5f..3bf0693fe 100644 --- a/congress/db/db_policy_rules.py +++ b/congress/db/db_policy_rules.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import absolute_import +from oslo_config import cfg import sqlalchemy as sa from sqlalchemy.orm import exc as db_exc @@ -59,10 +60,40 @@ class Policy(model_base.BASE, model_base.HasId, model_base.HasAudit): def add_policy(id_, name, abbreviation, description, owner, kind, deleted=False, session=None): session = session or db.get_session() - with session.begin(subtransactions=True): - policy = Policy(id_, name, abbreviation, description, owner, - kind, deleted) - session.add(policy) + mysql = (cfg.CONF.database.connection is not None and + (cfg.CONF.database.connection.split(':/')[0] == 'mysql' or + cfg.CONF.database.connection.split('+')[0] == 'mysql')) + postgres = (cfg.CONF.database.connection is not None and + (cfg.CONF.database.connection.split(':/')[0] == 'postgresql' or + cfg.CONF.database.connection.split('+')[0] == 'postgresql')) + try: + with session.begin(subtransactions=True): + # lock policies table to prevent duplicate named policy being added + # after duplicate check but before transaction closes. + # supported DBs are SQLite and MySQL and Postgres + # TODO(ekcs): table locking is special to underlying DB + # change DB schema to prevent duplicate generically without locking + if mysql: # Explicitly LOCK TABLES for MySQL + session.execute('LOCK TABLES policies WRITE') + if postgres: # Explicitly LOCK TABLE for Postgres + session.execute('LOCK TABLE policies IN EXCLUSIVE MODE') + # Do nothing for SQLite; DB auto locked for transaction + + # add if no policy of duplicate name exists + unique = (session.query(Policy). + filter(Policy.name == name). + filter(Policy.deleted == is_soft_deleted(name, deleted)). + count() == 0) + if unique: + policy = Policy(id_, name, abbreviation, description, owner, + kind, deleted) + session.add(policy) + finally: # always release table lock + if mysql: + session.execute('UNLOCK TABLES') + # postgres automatically releases lock after transaction completes + if not unique: + raise KeyError("Policy with name %s already exists" % name) return policy diff --git a/congress/policy_engines/agnostic.py b/congress/policy_engines/agnostic.py index 11c7b7dfe..91aa2ff0c 100644 --- a/congress/policy_engines/agnostic.py +++ b/congress/policy_engines/agnostic.py @@ -376,6 +376,9 @@ class Runtime (object): obj['description'], obj['owner_id'], obj['kind']) + except KeyError: + raise exception.Conflict( + "Policy with name %s already exists" % name) except Exception: policy_name = policy_obj.name msg = "Error thrown while adding policy %s into DB." % policy_name