Disallow duplicated policy name in DB
Bug: DB allows two PE instances to insert two policies with the same name (different ID), causing irrecoverable error in all following policy sync attempts. This patch fixes the bug by locking database table and checking for duplicates before adding new policy. Manually tested under SQLite and MySQL. Change-Id: I0de1692a895fbf47703f668b44e2c9300f518f76
This commit is contained in:
parent
dcf097b9cd
commit
a0f1c1a17d
|
@ -102,9 +102,7 @@ class PolicyModel(base.APIModel):
|
||||||
'kind': item.get('kind'),
|
'kind': item.get('kind'),
|
||||||
'desc': item.get('description')})
|
'desc': item.get('description')})
|
||||||
except exception.CongressException as e:
|
except exception.CongressException as e:
|
||||||
(num, desc) = error_codes.get('failed_to_create_policy')
|
raise webservice.DataModelException.create(e)
|
||||||
raise webservice.DataModelException(
|
|
||||||
num, desc + ": " + str(e))
|
|
||||||
|
|
||||||
return (policy_metadata['id'], policy_metadata)
|
return (policy_metadata['id'], policy_metadata)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from __future__ import division
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import exc as db_exc
|
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,
|
def add_policy(id_, name, abbreviation, description, owner, kind,
|
||||||
deleted=False, session=None):
|
deleted=False, session=None):
|
||||||
session = session or db.get_session()
|
session = session or db.get_session()
|
||||||
|
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):
|
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,
|
policy = Policy(id_, name, abbreviation, description, owner,
|
||||||
kind, deleted)
|
kind, deleted)
|
||||||
session.add(policy)
|
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
|
return policy
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -376,6 +376,9 @@ class Runtime (object):
|
||||||
obj['description'],
|
obj['description'],
|
||||||
obj['owner_id'],
|
obj['owner_id'],
|
||||||
obj['kind'])
|
obj['kind'])
|
||||||
|
except KeyError:
|
||||||
|
raise exception.Conflict(
|
||||||
|
"Policy with name %s already exists" % name)
|
||||||
except Exception:
|
except Exception:
|
||||||
policy_name = policy_obj.name
|
policy_name = policy_obj.name
|
||||||
msg = "Error thrown while adding policy %s into DB." % policy_name
|
msg = "Error thrown while adding policy %s into DB." % policy_name
|
||||||
|
|
Loading…
Reference in New Issue