Merge "Fix quota lock on single_create with auth enabled"

This commit is contained in:
Jenkins 2017-03-13 20:05:51 +00:00 committed by Gerrit Code Review
commit acbb6a09ae
3 changed files with 198 additions and 129 deletions

View File

@ -77,12 +77,13 @@ class LoadBalancersController(base.BaseController):
raise exceptions.ImmutableObject(resource=db_lb._name(),
id=id)
def _create_load_balancer_graph_db(self, lock_session, load_balancer):
def _create_load_balancer_graph_db(self, session,
lock_session, load_balancer):
prepped_lb = db_prepare.create_load_balancer_tree(
load_balancer.to_dict(render_unsets=True))
try:
db_lb = self.repositories.create_load_balancer_tree(
lock_session, prepped_lb)
session, lock_session, prepped_lb)
except Exception:
raise
return db_lb
@ -158,17 +159,10 @@ class LoadBalancersController(base.BaseController):
load_balancer.vip.network_id = subnet.network_id
lock_session = db_api.get_session(autocommit=False)
if self.repositories.check_quota_met(
context.session,
lock_session,
data_models.LoadBalancer,
load_balancer.project_id):
lock_session.rollback()
raise exceptions.QuotaException
if load_balancer.listeners:
try:
db_lb = self._create_load_balancer_graph_db(lock_session,
db_lb = self._create_load_balancer_graph_db(context.session,
lock_session,
load_balancer)
lock_session.commit()
except Exception:
@ -176,6 +170,14 @@ class LoadBalancersController(base.BaseController):
lock_session.rollback()
return self._load_balancer_graph_to_handler(context, db_lb)
else:
if self.repositories.check_quota_met(
context.session,
lock_session,
data_models.LoadBalancer,
load_balancer.project_id):
lock_session.rollback()
raise exceptions.QuotaException
try:
lb_dict = db_prepare.create_load_balancer(load_balancer.to_dict(

View File

@ -31,7 +31,6 @@ from octavia.common import constants as consts
from octavia.common import data_models
from octavia.common import exceptions
from octavia.common import validate
from octavia.db import api as db_api
from octavia.db import models
from octavia.i18n import _LE, _LW
@ -273,11 +272,10 @@ class Repositories(object):
quotas = self.quotas.get(session, project_id=project_id)
if not quotas:
# Make sure we have a record to lock
quotas = self.quotas.update(
self.quotas.update(
session,
project_id,
quota={})
session.flush()
# Lock the project record in the database to block other quota checks
#
# Note: You cannot just use the current count as the in-use
@ -489,125 +487,123 @@ class Repositories(object):
'{proj}').format(proj=project_id))
raise exceptions.ProjectBusyException()
def create_load_balancer_tree(self, session, lb_dict):
def create_load_balancer_tree(self, session, lock_session, lb_dict):
listener_dicts = lb_dict.pop('listeners', [])
vip_dict = lb_dict.pop('vip')
with session.begin(subtransactions=True):
lock_session = db_api.get_session(autocommit=False)
try:
try:
if self.check_quota_met(session,
lock_session,
data_models.LoadBalancer,
lb_dict['project_id']):
raise exceptions.QuotaException
lb_dm = self.create_load_balancer_and_vip(
lock_session, lb_dict, vip_dict)
for listener_dict in listener_dicts:
# Add listener quota check
if self.check_quota_met(session,
lock_session,
data_models.LoadBalancer,
data_models.Listener,
lb_dict['project_id']):
raise exceptions.QuotaException
lb_dm = self.create_load_balancer_and_vip(
lock_session, lb_dict, vip_dict)
for listener_dict in listener_dicts:
# Add listener quota check
pool_dict = listener_dict.pop('default_pool', None)
l7policies_dict = listener_dict.pop('l7policies', None)
sni_containers = listener_dict.pop('sni_containers', [])
if pool_dict:
# Add pool quota check
if self.check_quota_met(session,
lock_session,
data_models.Listener,
data_models.Pool,
lb_dict['project_id']):
raise exceptions.QuotaException
pool_dict = listener_dict.pop('default_pool', None)
l7policies_dict = listener_dict.pop('l7policies', None)
sni_containers = listener_dict.pop('sni_containers', [])
if pool_dict:
# Add pool quota check
hm_dict = pool_dict.pop('health_monitor', None)
member_dicts = pool_dict.pop('members', [])
sp_dict = pool_dict.pop('session_persistence', None)
pool_dict['load_balancer_id'] = lb_dm.id
del pool_dict['listener_id']
pool_dm = self.pool.create(lock_session, **pool_dict)
if sp_dict:
sp_dict['pool_id'] = pool_dm.id
self.session_persistence.create(lock_session,
**sp_dict)
if hm_dict:
# Add hm quota check
if self.check_quota_met(session,
lock_session,
data_models.Pool,
data_models.HealthMonitor,
lb_dict['project_id']):
raise exceptions.QuotaException
hm_dict = pool_dict.pop('health_monitor', None)
member_dicts = pool_dict.pop('members', [])
sp_dict = pool_dict.pop('session_persistence', None)
pool_dict['load_balancer_id'] = lb_dm.id
del pool_dict['listener_id']
pool_dm = self.pool.create(lock_session, **pool_dict)
if sp_dict:
sp_dict['pool_id'] = pool_dm.id
self.session_persistence.create(lock_session,
**sp_dict)
if hm_dict:
# Add hm quota check
hm_dict['pool_id'] = pool_dm.id
self.health_monitor.create(lock_session, **hm_dict)
for r_member_dict in member_dicts:
# Add member quota check
if self.check_quota_met(session,
lock_session,
data_models.Member,
lb_dict['project_id']):
raise exceptions.QuotaException
r_member_dict['pool_id'] = pool_dm.id
self.member.create(lock_session, **r_member_dict)
listener_dict['default_pool_id'] = pool_dm.id
self.listener.create(lock_session, **listener_dict)
for sni_container in sni_containers:
self.sni.create(lock_session, **sni_container)
if l7policies_dict:
for policy_dict in l7policies_dict:
l7rules_dict = policy_dict.pop('l7rules')
if policy_dict.get('redirect_pool'):
# Add pool quota check
if self.check_quota_met(session,
lock_session,
data_models.HealthMonitor,
data_models.Pool,
lb_dict['project_id']):
raise exceptions.QuotaException
hm_dict['pool_id'] = pool_dm.id
self.health_monitor.create(lock_session, **hm_dict)
for r_member_dict in member_dicts:
# Add member quota check
if self.check_quota_met(session,
lock_session,
data_models.Member,
lb_dict['project_id']):
raise exceptions.QuotaException
r_member_dict['pool_id'] = pool_dm.id
self.member.create(lock_session, **r_member_dict)
listener_dict['default_pool_id'] = pool_dm.id
self.listener.create(lock_session, **listener_dict)
for sni_container in sni_containers:
self.sni.create(lock_session, **sni_container)
if l7policies_dict:
for policy_dict in l7policies_dict:
l7rules_dict = policy_dict.pop('l7rules')
if policy_dict.get('redirect_pool'):
# Add pool quota check
if self.check_quota_met(session,
lock_session,
data_models.Pool,
lb_dict['project_id']):
r_pool_dict = policy_dict.pop(
'redirect_pool')
r_hm_dict = r_pool_dict.pop('health_monitor',
None)
r_sp_dict = r_pool_dict.pop(
'session_persistence', None)
r_member_dicts = r_pool_dict.pop('members', [])
if 'listener_id' in r_pool_dict.keys():
del r_pool_dict['listener_id']
r_pool_dm = self.pool.create(lock_session,
**r_pool_dict)
if r_sp_dict:
r_sp_dict['pool_id'] = r_pool_dm.id
self.session_persistence.create(lock_session,
**r_sp_dict)
if r_hm_dict:
# Add hm quota check
if self.check_quota_met(
session,
lock_session,
data_models.HealthMonitor,
lb_dict['project_id']):
raise exceptions.QuotaException
r_pool_dict = policy_dict.pop(
'redirect_pool')
r_hm_dict = r_pool_dict.pop('health_monitor',
None)
r_sp_dict = r_pool_dict.pop(
'session_persistence', None)
r_member_dicts = r_pool_dict.pop('members', [])
if 'listener_id' in r_pool_dict.keys():
del r_pool_dict['listener_id']
r_pool_dm = self.pool.create(lock_session,
**r_pool_dict)
if r_sp_dict:
r_sp_dict['pool_id'] = r_pool_dm.id
self.session_persistence.create(
lock_session, **r_sp_dict)
if r_hm_dict:
# Add hm quota check
if self.check_quota_met(
session,
lock_session,
data_models.HealthMonitor,
lb_dict['project_id']):
raise exceptions.QuotaException
r_hm_dict['pool_id'] = r_pool_dm.id
self.health_monitor.create(lock_session,
**r_hm_dict)
for r_member_dict in r_member_dicts:
# Add member quota check
if self.check_quota_met(
session,
lock_session,
data_models.Member,
lb_dict['project_id']):
raise exceptions.QuotaException
r_member_dict['pool_id'] = r_pool_dm.id
self.member.create(lock_session,
**r_member_dict)
policy_dict['redirect_pool_id'] = r_pool_dm.id
policy_dm = self.l7policy.create(lock_session,
**policy_dict)
for rule_dict in l7rules_dict:
rule_dict['l7policy_id'] = policy_dm.id
self.l7rule.create(lock_session, **rule_dict)
lock_session.commit()
except Exception:
with excutils.save_and_reraise_exception():
lock_session.rollback()
r_hm_dict['pool_id'] = r_pool_dm.id
self.health_monitor.create(lock_session,
**r_hm_dict)
for r_member_dict in r_member_dicts:
# Add member quota check
if self.check_quota_met(
session,
lock_session,
data_models.Member,
lb_dict['project_id']):
raise exceptions.QuotaException
r_member_dict['pool_id'] = r_pool_dm.id
self.member.create(lock_session,
**r_member_dict)
policy_dict['redirect_pool_id'] = r_pool_dm.id
policy_dm = self.l7policy.create(lock_session,
**policy_dict)
for rule_dict in l7rules_dict:
rule_dict['l7policy_id'] = policy_dm.id
self.l7rule.create(lock_session, **rule_dict)
lock_session.commit()
except Exception:
with excutils.save_and_reraise_exception():
lock_session.rollback()
session.expire_all()
return self.load_balancer.get(session, id=lb_dm.id)

View File

@ -25,6 +25,8 @@ from oslo_utils import uuidutils
from octavia.common import constants
from octavia.common import data_models as models
from octavia.common import exceptions
from octavia.db import api as db_api
from octavia.db import models as db_models
from octavia.db import repositories as repo
from octavia.tests.functional.db import base
@ -387,11 +389,65 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
listener['load_balancer_id'] = lb.get('id')
pool['load_balancer_id'] = lb.get('id')
redirect_pool['load_balancer_id'] = lb.get('id')
db_lb = self.repos.create_load_balancer_tree(self.session, lb)
lock_session = db_api.get_session(autocommit=False)
db_lb = self.repos.create_load_balancer_tree(self.session,
lock_session, lb)
self.assertIsNotNone(db_lb)
self.assertIsInstance(db_lb, models.LoadBalancer)
def test_sqlite_transactions_broken(self):
"""This test is a canary for pysqlite fixing transaction handling.
When this test starts failing, we can fix and un-skip the deadlock
test below: `test_create_load_balancer_tree_quotas`.
"""
project_id = uuidutils.generate_uuid()
vip = {'ip_address': '10.0.0.1', 'port_id': uuidutils.generate_uuid(),
'subnet_id': uuidutils.generate_uuid()}
lb = {'name': 'lb1', 'description': 'desc1', 'enabled': True,
'topology': constants.TOPOLOGY_ACTIVE_STANDBY,
'vrrp_group': None, 'server_group_id': uuidutils.generate_uuid(),
'project_id': project_id,
'provisioning_status': constants.PENDING_CREATE,
'operating_status': constants.ONLINE,
'id': uuidutils.generate_uuid()}
session = db_api.get_session()
lock_session = db_api.get_session(autocommit=False)
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(0, len(lbs)) # Initially: 0
self.repos.create_load_balancer_and_vip(lock_session, lb, vip)
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(1, len(lbs)) # After create: 1
lock_session.rollback()
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(0, len(lbs)) # After rollback: 0
self.repos.create_load_balancer_and_vip(lock_session, lb, vip)
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(1, len(lbs)) # After create: 1
lock_session.rollback()
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(0, len(lbs)) # After rollback: 0
# Force a count(), which breaks transaction integrity in pysqlite
session.query(db_models.LoadBalancer).filter(
db_models.LoadBalancer.project_id == project_id).count()
self.repos.create_load_balancer_and_vip(lock_session, lb, vip)
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(1, len(lbs)) # After create: 1
lock_session.rollback()
lbs = lock_session.query(db_models.LoadBalancer).filter_by(
project_id=project_id).all()
self.assertEqual(1, len(lbs)) # After rollback: 1 (broken!)
def test_create_load_balancer_tree_quotas(self):
self.skipTest("PySqlite transaction handling is broken. We can unskip"
"this when `test_sqlite_transactions_broken` fails.")
conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
conf.config(auth_strategy='testing')
project_id = uuidutils.generate_uuid()
@ -523,10 +579,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -537,10 +594,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -551,10 +609,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -565,10 +624,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 0,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -579,10 +639,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 0}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -594,10 +655,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -609,10 +671,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 1,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -624,10 +687,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 1}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb))
self.session, lock_session, copy.deepcopy(lb))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb1'))
@ -640,15 +704,18 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
self.repos.create_load_balancer_tree(self.session, copy.deepcopy(lb))
lock_session = db_api.get_session(autocommit=False)
self.repos.create_load_balancer_tree(self.session, lock_session,
copy.deepcopy(lb))
# Check if first LB build passed quota checks
self.assertIsNotNone(self.repos.load_balancer.get(self.session,
name='lb1'))
# Try building another LB, it should fail
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb2))
self.session, lock_session, copy.deepcopy(lb2))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb2'))
@ -662,10 +729,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb2))
self.session, lock_session, copy.deepcopy(lb2))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb2'))
@ -679,10 +747,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb2))
self.session, lock_session, copy.deepcopy(lb2))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb2'))
@ -696,10 +765,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 1,
'member': 10}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb2))
self.session, lock_session, copy.deepcopy(lb2))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb2'))
@ -713,10 +783,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase):
'health_monitor': 10,
'member': 2}
self.repos.quotas.update(self.session, project_id, quota=quota)
lock_session = db_api.get_session(autocommit=False)
self.assertRaises(
exceptions.QuotaException,
self.repos.create_load_balancer_tree,
self.session, copy.deepcopy(lb2))
self.session, lock_session, copy.deepcopy(lb2))
# Make sure we didn't create the load balancer anyway
self.assertIsNone(self.repos.load_balancer.get(self.session,
name='lb2'))