Resource Quota - DB layer changes

Change-Id: Ib1d4d8634eabdba76c5f7f858efc16702ebc0d2a
Partially-Implements: blueprint resource-quota
This commit is contained in:
Vijendar Komalla 2017-01-12 15:53:12 -06:00
parent 73059c5c20
commit ccc04d67fc
5 changed files with 290 additions and 1 deletions

View File

@ -383,6 +383,10 @@ class QuotaAlreadyExists(Conflict):
"for resource %(resource)s.")
class QuotaNotFound(ResourceNotFound):
message = _("Quota could not be found: %(msg)s")
class RegionsListFailed(MagnumException):
message = _("Failed to list regions.")

View File

@ -358,6 +358,62 @@ class Connection(object):
:returns: A quota record.
"""
@abc.abstractmethod
def update_quota(self, project_id, values):
"""Update quota record.
:param project_id: The project id.
:param values: A dict containing several items used to identify
and track quota for a resource in a project.
::
{
'id': uuidutils.generate_uuid(),
'project_id': 'fake_project',
'resource': 'fake_resource',
'hard_limit': 'fake_hardlimit',
}
:returns: A quota record.
"""
@abc.abstractmethod
def delete_quota(self, project_id, resource):
"""Delete a quota.
:param project_id: Project id.
:param resource: resource name.
"""
@abc.abstractmethod
def get_quota_by_id(self, context, quota_id):
"""Return a quota.
:param context: The security context
:param quota_id: The id of a quota.
:returns: A quota.
"""
@abc.abstractmethod
def get_quota_list(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get quota list.
Return a list of the specified columns for all quotas that match the
specified filters.
:param context: The security context
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of clusters to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def quota_get_all_by_project_id(self, project_id):
"""Gets Quota record for all the resources in a project.
@ -366,3 +422,13 @@ class Connection(object):
:returns: Quota record for all resources in a project.
"""
@abc.abstractmethod
def get_quota_by_project_id_resource(self, project_id, resource):
"""Gets quota record for the given quota id.
:param project_id: project id.
:param resource: resource name.
:returns: Quota record.
"""

View File

@ -527,8 +527,81 @@ class Connection(api.Connection):
resource=values['resource'])
return quotas
def _add_quota_filters(self, query, filters):
if filters is None:
filters = {}
possible_filters = ["resource", "project_id"]
filter_names = set(filters).intersection(possible_filters)
filter_dict = {filter_name: filters[filter_name]
for filter_name in filter_names}
query = query.filter_by(**filter_dict)
return query
def get_quota_list(self, context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Quota)
query = self._add_quota_filters(query, filters)
return _paginate_query(models.Quota, limit, marker,
sort_key, sort_dir, query)
def update_quota(self, project_id, values):
session = get_session()
with session.begin():
query = model_query(models.Quota, session=session)
resource = values['resource']
try:
query = query.filter_by(project_id=project_id).filter_by(
resource=resource)
ref = query.with_lockmode('update').one()
except NoResultFound:
msg = (_('project_id %(project_id)s resource %(resource)s.') %
{'project_id': project_id, 'resource': resource})
raise exception.QuotaNotFound(msg=msg)
ref.update(values)
return ref
def delete_quota(self, project_id, resource):
session = get_session()
with session.begin():
query = model_query(models.Quota, session=session)
try:
query.filter_by(project_id=project_id).filter_by(
resource=resource).one()
except NoResultFound:
msg = (_('project_id %(project_id)s resource %(resource)s.') %
{'project_id': project_id, 'resource': resource})
raise exception.QuotaNotFound(msg=msg)
query.delete()
def get_quota_by_id(self, context, quota_id):
query = model_query(models.Quota)
query = query.filter_by(id=quota_id)
try:
return query.one()
except NoResultFound:
msg = _('quota id %s .') % quota_id
raise exception.QuotaNotFound(msg=msg)
def quota_get_all_by_project_id(self, project_id):
query = model_query(models.Quota)
result = query.filter_by(project_id=project_id).all()
return result
def get_quota_by_project_id_resource(self, project_id, resource):
query = model_query(models.Quota)
query = query.filter_by(project_id=project_id).filter_by(
resource=resource)
try:
return query.one()
except NoResultFound:
msg = (_('project_id %(project_id)s resource %(resource)s.') %
{'project_id': project_id, 'resource': resource})
raise exception.QuotaNotFound(msg=msg)

View File

@ -39,3 +39,123 @@ class DbQuotaTestCase(base.DbTestCase):
self.assertEqual(q.hard_limit, r.hard_limit)
self.assertEqual(q.project_id, r.project_id)
self.assertEqual(q.resource, r.resource)
def test_get_quota_by_project_id_resource(self):
q = utils.create_test_quotas(project_id='123',
resource='test-res',
hard_limit=5)
res = self.dbapi.get_quota_by_project_id_resource('123', 'test-res')
self.assertEqual(q.hard_limit, res.hard_limit)
self.assertEqual(q.project_id, res.project_id)
self.assertEqual(q.resource, res.resource)
def test_get_quota_by_project_id_resource_not_found(self):
utils.create_test_quotas(project_id='123',
resource='test-res',
hard_limit=5)
self.assertRaises(exception.QuotaNotFound,
self.dbapi.get_quota_by_project_id_resource,
project_id='123',
resource='bad-res')
def test_get_quota_list(self):
project_ids = []
for i in range(1, 6):
project_id = 'proj-'+str(i)
utils.create_test_quotas(project_id=project_id)
project_ids.append(project_id)
res = self.dbapi.get_quota_list(self.context)
res_proj_ids = [r.project_id for r in res]
self.assertEqual(sorted(project_ids), sorted(res_proj_ids))
def test_get_quota_list_sorted(self):
project_ids = []
for i in range(1, 6):
project_id = 'proj-'+str(i)
utils.create_test_quotas(project_id=project_id)
project_ids.append(project_id)
res = self.dbapi.get_quota_list(self.context, sort_key='project_id')
res_proj_ids = [r.project_id for r in res]
self.assertEqual(sorted(project_ids), res_proj_ids)
def test_get_quota_list_invalid_sort_key(self):
project_ids = []
for i in range(1, 6):
project_id = 'proj-'+str(i)
utils.create_test_quotas(project_id=project_id)
project_ids.append(project_id)
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.get_quota_list,
self.context,
sort_key='invalid')
def test_get_quota_list_with_filters(self):
quota1 = utils.create_test_quotas(project_id='proj-1', resource='res1')
quota2 = utils.create_test_quotas(project_id='proj-1', resource='res2')
quota3 = utils.create_test_quotas(project_id='proj-2', resource='res1')
res = self.dbapi.get_quota_list(
self.context, filters={'resource': 'res2'})
self.assertEqual(quota2.project_id, res[0].project_id)
res = self.dbapi.get_quota_list(
self.context, filters={'project_id': 'proj-2'})
self.assertEqual(quota3.project_id, res[0].project_id)
res = self.dbapi.get_quota_list(
self.context, filters={'project_id': 'proj-1'})
self.assertEqual(sorted([quota1.project_id, quota2.project_id]),
sorted([r.project_id for r in res]))
def test_update_quota(self):
q = utils.create_test_quotas(hard_limit=5,
project_id='1234',
resource='Cluster')
res = self.dbapi.get_quota_by_project_id_resource('1234', 'Cluster')
self.assertEqual(q.hard_limit, res.hard_limit)
self.assertEqual(q.project_id, res.project_id)
self.assertEqual(q.resource, res.resource)
quota_dict = {'resource': 'Cluster', 'hard_limit': 15}
self.dbapi.update_quota('1234', quota_dict)
res = self.dbapi.get_quota_by_project_id_resource('1234', 'Cluster')
self.assertEqual(quota_dict['hard_limit'], res.hard_limit)
self.assertEqual(quota_dict['resource'], res.resource)
def test_update_quota_not_found(self):
utils.create_test_quotas(hard_limit=5,
project_id='1234',
resource='Cluster')
quota_dict = {'resource': 'Cluster', 'hard_limit': 15}
self.assertRaises(exception.QuotaNotFound,
self.dbapi.update_quota,
'invalid_proj',
quota_dict)
def test_delete_quota(self):
q = utils.create_test_quotas(project_id='123',
resource='test-res',
hard_limit=5)
res = self.dbapi.get_quota_by_project_id_resource('123', 'test-res')
self.assertEqual(q.hard_limit, res.hard_limit)
self.assertEqual(q.project_id, res.project_id)
self.assertEqual(q.resource, res.resource)
self.dbapi.delete_quota(q.project_id, q.resource)
self.assertRaises(exception.QuotaNotFound,
self.dbapi.get_quota_by_project_id_resource,
project_id='123',
resource='bad-res')
def test_delete_quota_that_does_not_exist(self):
# Make sure that quota does not exist
self.assertRaises(exception.QuotaNotFound,
self.dbapi.get_quota_by_project_id_resource,
project_id='123',
resource='bad-res')
# Now try to delete non-existing quota
self.assertRaises(exception.QuotaNotFound,
self.dbapi.delete_quota,
project_id='123',
resource='bad-res')

View File

@ -122,6 +122,32 @@ def create_test_cluster(**kw):
return dbapi.create_cluster(cluster)
def get_test_quota(**kw):
attrs = {
'id': kw.get('id', 42),
'project_id': kw.get('project_id', 'fake_project'),
'resource': kw.get('resource', 'Cluster'),
'hard_limit': kw.get('hard_limit', 10)
}
return attrs
def create_test_quota(**kw):
"""Create test quota entry in DB and return Quota DB object.
Function to be used to create test Quota objects in the database.
:param kw: kwargs with overriding values for quota's attributes.
:returns: Test Quota DB object.
"""
quota = get_test_quota(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del quota['id']
dbapi = db_api.get_instance()
return dbapi.create_quota(quota)
def get_test_x509keypair(**kw):
return {
'id': kw.get('id', 42),
@ -187,7 +213,7 @@ def get_test_quotas(**kw):
return {
'id': kw.get('', 18),
'project_id': kw.get('project_id', 'fake_project'),
'resource': kw.get('resource', 'fake_resource'),
'resource': kw.get('resource', 'Cluster'),
'hard_limit': kw.get('hard_limit', 10),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),