Implement Models and Repositories for Resource Quotas

In the interest of smaller CRs, this CR partially implements the
quota support blueprint.  It includes code model and repository
used to store project quota information.  Unit and functional tests
are also provided to verify the implementation.

This CR also cover some small design changes, such as: the new
defaults is for unlimited quotas for all resources.

Implements: blueprint quota-support-on-barbican-resources

Change-Id: Ief79dd36fd35528c9d8586d77e01d1f9ad723f4d
This commit is contained in:
Dave McCowan 2015-07-18 09:10:50 -05:00
parent d16c99518c
commit 023ccbbefc
15 changed files with 1197 additions and 147 deletions

View File

@ -16,20 +16,28 @@ import pecan
from barbican import api
from barbican.api import controllers
from barbican.common import exception
from barbican.common import quota
from barbican.common import resources as res
from barbican.common import utils
from barbican.common import validators
from barbican import i18n as u
LOG = utils.getLogger(__name__)
def _project_quotas_not_found():
"""Throw exception indicating project quotas not found."""
pecan.abort(404, u._('Not Found. Sorry but your project quotas are in '
'another castle.'))
class QuotasController(controllers.ACLMixin):
"""Handles quota retrieval requests."""
def __init__(self, quota_repo=None):
def __init__(self):
LOG.debug('=== Creating QuotasController ===')
self.repo = quota_repo
self.quota_driver = quota.QuotaDriver()
@pecan.expose(generic=True)
@ -40,18 +48,18 @@ class QuotasController(controllers.ACLMixin):
@controllers.handle_exceptions(u._('Quotas'))
@controllers.enforce_rbac('quotas:get')
def on_get(self, external_project_id, **kwargs):
# TODO(dave) implement
resp = {'quotas': self.quota_driver.get_defaults()}
LOG.debug('=== QuotasController GET ===')
project = res.get_or_create_project(external_project_id)
resp = self.quota_driver.get_quotas(project.id)
return resp
class ProjectQuotasController(controllers.ACLMixin):
"""Handles project quota requests."""
def __init__(self, project_id, project_quota_repo=None):
def __init__(self, project_id):
LOG.debug('=== Creating ProjectQuotasController ===')
self.passed_project_id = project_id
self.repo = project_quota_repo
self.validator = validators.ProjectQuotaValidator()
self.quota_driver = quota.QuotaDriver()
@ -63,29 +71,26 @@ class ProjectQuotasController(controllers.ACLMixin):
@controllers.handle_exceptions(u._('Project Quotas'))
@controllers.enforce_rbac('project_quotas:get')
def on_get(self, external_project_id, **kwargs):
# TODO(dave) implement
LOG.debug('=== ProjectQuotasController GET ===')
resp = {'project_quotas': self.quota_driver.get_defaults()}
resp = self.quota_driver.get_project_quotas(self.passed_project_id)
if resp:
return resp
else:
_project_quotas_not_found()
return resp
@index.when(method='POST', template='json')
@index.when(method='PUT', template='json')
@controllers.handle_exceptions(u._('Project Quotas'))
@controllers.enforce_rbac('project_quotas:post')
def on_post(self, external_project_id, **kwargs):
LOG.debug('=== ProjectQuotasController POST ===')
@controllers.enforce_rbac('project_quotas:put')
def on_put(self, external_project_id, **kwargs):
LOG.debug('=== ProjectQuotasController PUT ===')
if not pecan.request.body:
raise exception.NoDataToProcess()
api.load_body(pecan.request,
validator=self.validator)
# TODO(dave) implement
resp = {'project_quotas': {
'secrets': 10,
'orders': 20,
'containers': 10,
'transport_keys': 10,
'consumers': -1}
}
LOG.info(u._LI('Post Project Quotas'))
return resp
self.quota_driver.set_project_quotas(self.passed_project_id,
kwargs['project_quotas'])
LOG.info(u._LI('Put Project Quotas'))
pecan.response.status = 204
@index.when(method='DELETE', template='json')
@utils.allow_all_content_types
@ -93,23 +98,26 @@ class ProjectQuotasController(controllers.ACLMixin):
@controllers.enforce_rbac('project_quotas:delete')
def on_delete(self, external_project_id, **kwargs):
LOG.debug('=== ProjectQuotasController DELETE ===')
# TODO(dave) implement
LOG.info(u._LI('Delete Project Quotas'))
pecan.response.status = 204
try:
self.quota_driver.delete_project_quotas(self.passed_project_id)
except exception.NotFound:
LOG.info(u._LI('Delete Project Quotas - Project not found'))
_project_quotas_not_found()
else:
LOG.info(u._LI('Delete Project Quotas'))
pecan.response.status = 204
class ProjectsQuotasController(controllers.ACLMixin):
"""Handles projects quota retrieval requests."""
def __init__(self, project_quota_repo=None):
def __init__(self):
LOG.debug('=== Creating ProjectsQuotaController ===')
self.repo = project_quota_repo
self.quota_driver = quota.QuotaDriver()
@pecan.expose()
def _lookup(self, project_id, *remainder):
return ProjectQuotasController(project_id,
project_quota_repo=self.repo), remainder
return ProjectQuotasController(project_id), remainder
@pecan.expose(generic=True)
def index(self, **kwargs):
@ -119,13 +127,8 @@ class ProjectsQuotasController(controllers.ACLMixin):
@controllers.handle_exceptions(u._('Project Quotas'))
@controllers.enforce_rbac('project_quotas:get')
def on_get(self, external_project_id, **kwargs):
# TODO(dave) implement
project1 = {'project_id': "1234",
'project_quotas': self.quota_driver.get_defaults()}
project2 = {'project_id': "5678",
'project_quotas': self.quota_driver.get_defaults()}
project_quotas = {"project_quotas": [project1, project2]}
resp = project_quotas
resp = self.quota_driver.get_project_quotas_list(
offset_arg=kwargs.get('offset', 0),
limit_arg=kwargs.get('limit', None)
)
return resp

View File

@ -17,6 +17,10 @@
from oslo_config import cfg
from oslo_log import log as logging
from barbican.common import exception
from barbican.common import hrefs
from barbican.model import repositories as repo
LOG = logging.getLogger(__name__)
UNLIMITED_VALUE = -1
@ -26,23 +30,20 @@ quota_opt_group = cfg.OptGroup(name='quotas',
title='Quota Options')
quota_opts = [
cfg.BoolOpt('enabled',
default=False,
help='When True, quotas are enforced.'),
cfg.IntOpt('quota_secrets',
default=500,
default=-1,
help='Number of secrets allowed per project'),
cfg.IntOpt('quota_orders',
default=100,
default=-1,
help='Number of orders allowed per project'),
cfg.IntOpt('quota_containers',
default=-1,
help='Number of containers allowed per project'),
cfg.IntOpt('quota_transport_keys',
default=100,
default=-1,
help='Number of transport keys allowed per project'),
cfg.IntOpt('quota_consumers',
default=100,
default=-1,
help='Number of consumers allowed per project'),
]
@ -54,7 +55,15 @@ CONF.register_opts(quota_opts, group=quota_opt_group)
class QuotaDriver(object):
"""Driver to enforce quotas and obtain quota information."""
def get_defaults(self):
def __init__(self):
self.repo = repo.get_project_quotas_repository()
def _get_resources(self):
"""List of resources that can be constrained by a quota"""
return ['secrets', 'orders', 'containers', 'transport_keys',
'consumers']
def _get_defaults(self):
"""Return list of default quotas"""
quotas = {
'secrets': CONF.quotas.quota_secrets,
@ -65,7 +74,113 @@ class QuotaDriver(object):
}
return quotas
def _extract_project_quotas(self, project_quotas_model):
"""Convert project quotas model to Python dict
:param project_quotas_model: Model containing quota information
:return: Python dict containing quota information
"""
resp_quotas = {}
for resource in self._get_resources():
resp_quotas[resource] = getattr(project_quotas_model, resource)
return resp_quotas
def _compute_effective_quotas(self, configured_quotas):
"""Merge configured and default quota information
When a quota value is not set, use the default value
:param configured_quotas: configured quota values
:return: effective quotas
"""
default_quotas = self._get_defaults()
resp_quotas = dict(configured_quotas)
for resource, quota in resp_quotas.iteritems():
if quota is None:
resp_quotas[resource] = default_quotas[resource]
return resp_quotas
def _is_unlimited_value(self, v):
"""A helper method to check for unlimited value."""
return v is not None and v <= UNLIMITED_VALUE
return v <= UNLIMITED_VALUE
def set_project_quotas(self, project_id, parsed_project_quotas):
"""Create a new database entry, or update existing one
:param project_id: ID of project whose quotas are to be set
:param parsed_project_quotas: quota values to save in database
:return: None
"""
session = self.repo.get_session()
self.repo.create_or_update_by_project_id(
project_id, parsed_project_quotas, session=session)
session.commit()
def get_project_quotas(self, project_id):
"""Retrieve configured quota information from database
:param project_id: ID of project for whose value are wanted
:return: the values
"""
session = self.repo.get_session()
try:
retrieved_project_quotas =\
self.repo.get_by_project_id(project_id, session=session)
except exception.NotFound:
return None
resp_quotas = self._extract_project_quotas(retrieved_project_quotas)
resp = {'project_quotas': resp_quotas}
return resp
def get_project_quotas_list(self, offset_arg=None, limit_arg=None):
"""Return a dict and list of all configured quota information
:return: a dict and list of a page of quota config info
"""
session = self.repo.get_session()
retrieved_project_quotas, offset, limit, total =\
self.repo.get_by_create_date(session=session,
offset_arg=offset_arg,
limit_arg=limit_arg,
suppress_exception=True)
resp_quotas = []
for quotas in retrieved_project_quotas:
list_item = {'project_id': quotas.project_id,
'project_quotas':
self._extract_project_quotas(quotas)}
resp_quotas.append(list_item)
resp = {'project_quotas': resp_quotas}
resp_overall = hrefs.add_nav_hrefs(
'project_quotas', offset, limit, total, resp)
resp_overall.update({'total': total})
return resp_overall
def delete_project_quotas(self, project_id):
"""Remove configured quota information from database
:param project_id: ID of project whose quota config will be deleted
:raises NotFound: if project has no configured values
:return: None
"""
session = self.repo.get_session()
self.repo.delete_by_project_id(project_id,
session=session)
def get_quotas(self, project_id):
"""Get the effective quotas for a project
Effective quotas are based on both configured and default values
:param project_id: ID of project for which to get effective quotas
:return: dict of effective quota values
"""
session = self.repo.get_session()
try:
retrieved_project_quotas =\
self.repo.get_by_project_id(project_id,
session=session)
except exception.NotFound:
resp_quotas = self._get_defaults()
else:
resp_quotas = self._compute_effective_quotas(
self._extract_project_quotas(retrieved_project_quotas))
resp = {'quotas': resp_quotas}
return resp

View File

@ -1242,3 +1242,76 @@ class ContainerACLUser(BASE, ModelBase):
"""Sub-class hook method: return dict of fields."""
return {'acl_id': self.acl_id,
'user_id': self.user_id}
class ProjectQuotas(BASE, ModelBase):
"""Stores Project Quotas.
Class to define project specific resource quotas.
Project quota deletes are not soft-deletes.
"""
__tablename__ = 'project_quotas'
project_id = sa.Column(
sa.String(36),
# TODO(dave): enforce project exists
# sa.ForeignKey('projects.id', name='project_quotas_fk'),
index=True,
nullable=False)
secrets = sa.Column(sa.Integer, nullable=True)
orders = sa.Column(sa.Integer, nullable=True)
containers = sa.Column(sa.Integer, nullable=True)
transport_keys = sa.Column(sa.Integer, nullable=True)
consumers = sa.Column(sa.Integer, nullable=True)
__table_args__ = (sa.UniqueConstraint('project_id',
name='project_quotas_uc'),)
def __init__(self, project_id=None, parsed_project_quotas=None):
"""Creates Project Quotas entity from a project and a dict.
:param project_id: the id of the project whose quotas are to be stored
:param parsed_project_quotas: a dict with the keys matching the
resources for which quotas are to be set, and the values containing
the quota value to be set for this project and that resource.
:return: None
"""
super(ProjectQuotas, self).__init__()
msg = u._("Must supply non-None {0} argument for ProjectQuotas entry.")
if project_id is None:
raise exception.MissingArgumentError(msg.format("project_id"))
self.project_id = project_id
if parsed_project_quotas is None:
self.secrets = None
self.orders = None
self.containers = None
self.transport_keys = None
self.consumers = None
else:
self.secrets = parsed_project_quotas.get('secrets')
self.orders = parsed_project_quotas.get('orders')
self.containers = parsed_project_quotas.get('containers')
self.transport_keys = parsed_project_quotas.get('transport_keys')
self.consumers = parsed_project_quotas.get('consumers')
def _do_extra_dict_fields(self):
"""Sub-class hook method: return dict of fields."""
ret = {
'project_id': self.project_id,
}
if self.secrets:
ret['secrets'] = self.secrets
if self.orders:
ret['orders'] = self.orders
if self.containers:
ret['containers'] = self.containers
if self.transport_keys:
ret['transport_keys'] = self.transport_keys
if self.consumers:
ret['consumers'] = self.consumers
return ret

View File

@ -61,6 +61,7 @@ _ORDER_RETRY_TASK_REPOSITORY = None
_PREFERRED_CA_REPOSITORY = None
_PROJECT_REPOSITORY = None
_PROJECT_CA_REPOSITORY = None
_PROJECT_QUOTAS_REPOSITORY = None
_SECRET_ACL_REPOSITORY = None
_SECRET_META_REPOSITORY = None
_SECRET_REPOSITORY = None
@ -1836,6 +1837,126 @@ class ContainerACLRepo(BaseRepo):
entity.delete(session=session)
class ProjectQuotasRepo(BaseRepo):
"""Repository for the ProjectQuotas entity."""
def _do_entity_name(self):
"""Sub-class hook: return entity name, such as for debugging."""
return "ProjectQuotas"
def _do_build_get_query(self, entity_id, external_project_id, session):
"""Sub-class hook: build a retrieve query."""
return session.query(models.ProjectQuotas).filter_by(id=entity_id)
def _do_validate(self, values):
"""Sub-class hook: validate values."""
pass
def get_by_create_date(self, offset_arg=None, limit_arg=None,
suppress_exception=False, session=None):
"""Returns a list of ProjectQuotas
The list is ordered by the date they were created at and paged
based on the offset and limit fields.
:param offset_arg: The entity number where the query result should
start.
:param limit_arg: The maximum amount of entities in the result set.
:param suppress_exception: Whether NoResultFound exceptions should be
suppressed.
:param session: SQLAlchemy session object.
:raises NotFound: if no quota config is found for the project
:returns: Tuple consisting of (list_of_entities, offset, limit, total).
"""
offset, limit = clean_paging_values(offset_arg, limit_arg)
session = self.get_session(session)
query = session.query(models.ProjectQuotas)
query = query.order_by(models.ProjectQuotas.created_at)
query = query.filter_by(deleted=False)
start = offset
end = offset + limit
LOG.debug('Retrieving from %s to %s', start, end)
total = query.count()
entities = query.offset(start).limit(limit).all()
LOG.debug('Number entities retrieved: %s out of %s',
len(entities), total)
if total <= 0 and not suppress_exception:
_raise_no_entities_found(self._do_entity_name())
return entities, offset, limit, total
def create_or_update_by_project_id(self, project_id, parsed_project_quotas,
session=None):
"""Create or update Project Quotas config for a project by project_id.
:param project_id: ID of project whose quota config will be saved
:param parsed_project_quotas: Python dict with quota definition
:param session: SQLAlchemy session object.
:return: None
"""
session = self.get_session(session)
query = session.query(models.ProjectQuotas)
query = query.filter_by(project_id=project_id)
try:
entity = query.one()
except sa_orm.exc.NoResultFound:
self.create_from(
models.ProjectQuotas(project_id, parsed_project_quotas),
session=session)
else:
self._update_values(entity, parsed_project_quotas)
def get_by_project_id(self, project_id,
suppress_exception=False, session=None):
"""Return configured Project Quotas for a project by project_id.
:param project_id: ID of project whose quota config will be deleted
:param suppress_exception: when True, NotFound is not raised
:param session: SQLAlchemy session object.
:raises NotFound: if no quota config is found for the project
:return: None or Python dict of project quotas for project
"""
session = self.get_session(session)
query = session.query(models.ProjectQuotas)
query = query.filter_by(project_id=project_id)
try:
entity = query.one()
except sa_orm.exc.NoResultFound:
if suppress_exception:
return None
else:
_raise_no_entities_found(self._do_entity_name())
return entity
def delete_by_project_id(self, project_id,
suppress_exception=False, session=None):
"""Remove configured Project Quotas for a project by project_id.
:param project_id: ID of project whose quota config will be deleted
:param suppress_exception: when True, NotFound is not raised
:param session: SQLAlchemy session object.
:raises NotFound: if no quota config is found for the project
:return: None
"""
session = self.get_session(session)
query = session.query(models.ProjectQuotas)
query = query.filter_by(project_id=project_id)
try:
entity = query.one()
except sa_orm.exc.NoResultFound:
if suppress_exception:
return
else:
_raise_no_entities_found(self._do_entity_name())
entity.delete(session=session)
def get_ca_repository():
"""Returns a singleton Secret repository instance."""
global _CA_REPOSITORY
@ -1925,6 +2046,13 @@ def get_project_ca_repository():
ProjectCertificateAuthorityRepo)
def get_project_quotas_repository():
"""Returns a singleton Project Quotas repository instance."""
global _PROJECT_QUOTAS_REPOSITORY
return _get_repository(_PROJECT_QUOTAS_REPOSITORY,
ProjectQuotasRepo)
def get_secret_acl_repository():
"""Returns a singleton Secret ACL repository instance."""
global _SECRET_ACL_REPOSITORY

View File

@ -23,46 +23,129 @@ class WhenTestingQuotas(utils.BarbicanAPIBaseTestCase):
def test_should_get_quotas(self):
params = {}
resp = self.app.get('/quotas', params)
self.assertIn('quotas', resp.namespace)
self.assertEqual(200, resp.status_int)
quotas_list = resp.json.get('quotas')
self.assertEqual({'consumers': -1, 'containers': -1, 'orders': -1,
'secrets': -1, 'transport_keys': -1},
quotas_list)
def test_should_get_specific_project_quotas(self):
params = {}
self.create_a_project_quotas()
resp = self.app.get(
'/project-quotas/{0}'.format(self.project_id),
'/project-quotas/{0}'.format(self.get_test_project_id()),
params)
self.assertEqual(200, resp.status_int)
self.assertIn('project_quotas', resp.namespace)
project_quotas = resp.json.get('project_quotas')
self.assertEqual({'consumers': 105, 'containers': 103, 'orders': 102,
'secrets': 101, 'transport_keys': 104},
project_quotas)
def test_should_return_not_found_get_specific_project_quotas(self):
params = {}
resp = self.app.get(
'/project-quotas/{0}'.format(self.get_test_project_id()),
params, expect_errors=True)
self.assertEqual(404, resp.status_int)
def test_should_get_project_quotas_list(self):
self.create_project_quotas()
params = {}
resp = self.app.get('/project-quotas', params)
self.assertEqual(200, resp.status_int)
self.assertIn('project_quotas', resp.namespace)
project_quotas_list = resp.json.get('project_quotas')
self.assertEqual(3, len(project_quotas_list))
self.assertIn('total', resp.json)
def test_should_post_project_quotas(self):
request = {'project_quotas': {}}
resp = self.app.post_json(
'/project-quotas/{0}'.format(self.project_id), request)
self.assertEqual(200, resp.status_int)
def test_should_delete_specific_project_quotas(self):
def test_should_get_empty_project_quotas_list(self):
params = {}
resp = self.app.delete(
'/project-quotas/{0}'.format(self.project_id), params)
resp = self.app.get('/project-quotas', params)
self.assertEqual(200, resp.status_int)
project_quotas_list = resp.json.get('project_quotas')
self.assertEqual([], project_quotas_list)
self.assertIn('total', resp.json)
def test_pagination_attributes(self):
for index in range(11):
self.create_a_project_quotas(index)
params = {'limit': '2', 'offset': '2'}
resp = self.app.get('/project-quotas', params)
self.assertEqual(200, resp.status_int)
self.assertIn('previous', resp.json)
self.assertIn('next', resp.json)
previous_ref = resp.json.get('previous')
next_ref = resp.json.get('next')
self.assertIn('offset=0', previous_ref)
self.assertIn('offset=4', next_ref)
def test_should_put_project_quotas(self):
request = {'project_quotas': {}}
resp = self.app.put_json(
'/project-quotas/{0}'.format(self.project_id), request)
self.assertEqual(204, resp.status_int)
def test_check_post_quotas_not_allowed(self):
"""POST not allowed operation for /quotas"""
params = {}
resp = self.app.post('/quotas/', params, expect_errors=True)
self.assertEqual(405, resp.status_int)
def test_should_return_bad_value_put_project_quotas(self):
request = '{"project_quotas": {"secrets": "foo"}}'
resp = self.app.put(
'/project-quotas/{0}'.format(self.project_id),
request,
headers={'Content-Type': 'application/json'},
expect_errors=True)
self.assertEqual(400, resp.status_int)
def test_check_put_project_quotas_not_allowed(self):
def test_should_return_bad_data_put_project_quotas(self):
"""PUT not allowed operation for /project-quotas/{project-id}"""
params = {'bad data'}
resp = self.app.put(
'/project-quotas/{0}'.format(self.project_id),
params, expect_errors=True)
self.assertEqual(400, resp.status_int)
def test_should_return_no_payload_for_put_project_quotas(self):
"""PUT not allowed operation for /project-quotas/{project-id}"""
params = {}
resp = self.app.put(
'/project-quotas/{0}'.format(self.project_id),
params, expect_errors=True)
self.assertEqual(400, resp.status_int)
def test_should_delete_specific_project_quotas(self):
params = {}
self.create_a_project_quotas()
resp = self.app.delete(
'/project-quotas/{0}'.format(self.get_test_project_id()),
params)
self.assertEqual(204, resp.status_int)
def test_should_return_not_found_delete_specific_project_quotas(self):
params = {}
resp = self.app.delete(
'/project-quotas/{0}'.format('dummy'),
params, expect_errors=True)
self.assertEqual(404, resp.status_int)
def test_check_put_quotas_not_allowed(self):
"""PuT not allowed operation for /quotas"""
params = {}
resp = self.app.put('/quotas/', params, expect_errors=True)
self.assertEqual(405, resp.status_int)
def test_check_put_project_quotas_list_not_allowed(self):
"""PUT not allowed operation for /project-quotas"""
params = {}
resp = self.app.put('/project-quotas', params, expect_errors=True)
self.assertEqual(405, resp.status_int)
def test_check_post_project_quotas_not_allowed(self):
"""POST not allowed operation for /project-quotas/{project-id}"""
params = {}
resp = self.app.post(
'/project-quotas/{0}'.format(self.project_id),
params, expect_errors=True)
self.assertEqual(405, resp.status_int)
def test_check_post_project_quotas_list_not_allowed(self):
@ -71,6 +154,27 @@ class WhenTestingQuotas(utils.BarbicanAPIBaseTestCase):
resp = self.app.post('/project-quotas', params, expect_errors=True)
self.assertEqual(405, resp.status_int)
# ----------------------- Helper Functions ---------------------------
def get_test_project_id(self, index=1):
return 'project' + str(index)
def create_a_project_quotas(self, index=1):
project_id = self.get_test_project_id(index)
parsed_project_quotas = {
'secrets': index * 100 + 1,
'orders': index * 100 + 2,
'containers': index * 100 + 3,
'transport_keys': index * 100 + 4,
'consumers': index * 100 + 5}
request = {'project_quotas': parsed_project_quotas}
resp = self.app.put_json(
'/project-quotas/{0}'.format(project_id), request)
self.assertEqual(204, resp.status_int)
def create_project_quotas(self):
for index in [1, 2, 3]:
self.create_a_project_quotas(index)
if __name__ == '__main__':
unittest.main()

View File

@ -15,23 +15,44 @@
import unittest
from barbican.common import exception
from barbican.common import quota
from barbican.tests import utils
from barbican.tests import database_utils
class WhenTestingQuotaFunctions(utils.BaseTestCase):
class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
def setUp(self):
super(WhenTestingQuotaFunctions, self).setUp()
super(WhenTestingQuotaDriverFunctions, self).setUp()
self.quota_driver = quota.QuotaDriver()
def test_get_defaults(self):
quotas = self.quota_driver.get_defaults()
self.assertEqual(500, quotas['secrets'])
self.assertEqual(100, quotas['orders'])
quotas = self.quota_driver._get_defaults()
self.assertEqual(-1, quotas['secrets'])
self.assertEqual(-1, quotas['orders'])
self.assertEqual(-1, quotas['containers'])
self.assertEqual(100, quotas['transport_keys'])
self.assertEqual(100, quotas['consumers'])
self.assertEqual(-1, quotas['transport_keys'])
self.assertEqual(-1, quotas['consumers'])
def test_compute_effective_quotas_using_some_defaults(self):
configured_quotas = {'consumers': None, 'containers': 66,
'orders': None, 'secrets': 55,
'transport_keys': None}
quotas = self.quota_driver._compute_effective_quotas(configured_quotas)
expected_quotas = {'consumers': -1, 'containers': 66,
'orders': -1, 'secrets': 55,
'transport_keys': -1}
self.assertEqual(expected_quotas, quotas)
def test_compute_effective_quotas_using_all_defaults(self):
configured_quotas = {'consumers': None, 'containers': None,
'orders': None, 'secrets': None,
'transport_keys': None}
quotas = self.quota_driver._compute_effective_quotas(configured_quotas)
expected_quotas = {'consumers': -1, 'containers': -1,
'orders': -1, 'secrets': -1,
'transport_keys': -1}
self.assertEqual(expected_quotas, quotas)
def test_is_unlimited_true(self):
self.assertTrue(self.quota_driver._is_unlimited_value(-1))
@ -39,6 +60,137 @@ class WhenTestingQuotaFunctions(utils.BaseTestCase):
def test_is_unlimited_false(self):
self.assertFalse(self.quota_driver._is_unlimited_value(1))
def test_is_unlimited_none_is_false(self):
self.assertFalse(self.quota_driver._is_unlimited_value(None))
def test_should_get_project_quotas(self):
self.create_a_test_project_quotas()
project_quotas = self.quota_driver.get_project_quotas(
self.get_test_project_id())
self.assertEqual({'project_quotas':
self.get_test_parsed_project_quotas()},
project_quotas)
def test_should_return_not_found_get_project_quotas(self):
project_quotas = self.quota_driver.get_project_quotas('dummy')
self.assertIsNone(project_quotas)
def test_should_get_project_quotas_list(self):
self.create_a_test_project_quotas()
project_quotas = self.quota_driver.get_project_quotas_list()
self.assertEqual({'project_quotas': [{
'project_id': u'project1',
'project_quotas': {'consumers': 105,
'containers': 103,
'orders': 102,
'secrets': 101,
'transport_keys': 104}}], 'total': 1},
project_quotas)
def test_should_get_empty_project_quotas_list(self):
project_quotas = self.quota_driver.get_project_quotas_list()
self.assertEqual({'total': 0, 'project_quotas': []}, project_quotas)
def test_should_delete_project_quotas(self):
self.create_a_test_project_quotas()
self.quota_driver.delete_project_quotas(
self.get_test_project_id())
def test_should_raise_not_found_delete_project_quotas(self):
self.assertRaises(
exception.NotFound,
self.quota_driver.delete_project_quotas,
'dummy')
def test_get_project_quotas_with_partial_definition(self):
self.create_a_test_project_quotas('partial')
project_quotas = self.quota_driver.get_project_quotas(
self.get_test_project_id('partial'))
self.assertEqual({'project_quotas':
self.get_test_response_project_quotas('partial')},
project_quotas)
def test_get_project_quotas_using_empty_definition(self):
self.create_a_test_project_quotas('none')
project_quotas = self.quota_driver.get_project_quotas(
self.get_test_project_id('none'))
self.assertEqual({'project_quotas':
self.get_test_response_project_quotas('none')},
project_quotas)
def test_get_quotas_using_some_defaults(self):
self.create_a_test_project_quotas('partial')
quotas = self.quota_driver.get_quotas(
self.get_test_project_id('partial'))
expected_quotas = {'quotas': {'consumers': -1, 'containers': 66,
'orders': -1, 'secrets': 55,
'transport_keys': -1}}
self.assertEqual(expected_quotas, quotas)
def test_get_quotas_using_all_defaults(self):
quotas = self.quota_driver.get_quotas('not_configured')
expected_quotas = {'quotas': {'consumers': -1, 'containers': -1,
'orders': -1, 'secrets': -1,
'transport_keys': -1}}
self.assertEqual(expected_quotas, quotas)
# ----------------------- Helper Functions ---------------------------
def get_test_project_id(self, index=1):
if index == 'partial':
return 'project_partial'
elif index == 'none':
return 'project_none'
else:
return 'project' + str(index)
def get_test_parsed_project_quotas(self, index=1):
if index == 'partial':
parsed_project_quotas = {
'secrets': 55,
'containers': 66}
elif index == 'none':
parsed_project_quotas = {}
else:
parsed_project_quotas = {
'secrets': index * 100 + 1,
'orders': index * 100 + 2,
'containers': index * 100 + 3,
'transport_keys': index * 100 + 4,
'consumers': index * 100 + 5}
return parsed_project_quotas
def get_test_response_project_quotas(self, index=1):
if index == 'partial':
response_project_quotas = {
'secrets': 55,
'orders': None,
'containers': 66,
'transport_keys': None,
'consumers': None}
elif index == 'none':
response_project_quotas = {
'secrets': None,
'orders': None,
'containers': None,
'transport_keys': None,
'consumers': None}
else:
response_project_quotas = {
'secrets': index * 100 + 1,
'orders': index * 100 + 2,
'containers': index * 100 + 3,
'transport_keys': index * 100 + 4,
'consumers': index * 100 + 5}
return response_project_quotas
def create_a_test_project_quotas(self, index=1):
project_id = self.get_test_project_id(index)
parsed_project_quotas = self.get_test_parsed_project_quotas(index)
self.quota_driver.set_project_quotas(project_id, parsed_project_quotas)
def create_project_quotas(self):
for index in [1, 2, 3]:
self.create_a_test_project_quotas(index)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,249 @@
# Copyright (c) 2015 Cisco Systems
#
# 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 unittest
from barbican.common import exception
from barbican.model import models
from barbican.model import repositories
from barbican.tests import database_utils
class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
def setUp(self):
super(WhenTestingProjectQuotasRepo, self).setUp()
self.project_quotas_repo = repositories.ProjectQuotasRepo()
self.project_id_1 = '11111'
self.project_id_2 = '22222'
self.project_id_3 = '33333'
self.parsed_project_quotas_1 = {
'secrets': 101,
'orders': 102,
'containers': 103,
'transport_keys': 104,
'consumers': 105}
self.parsed_project_quotas_2 = {
'secrets': 201,
'orders': 202,
'containers': 203,
'transport_keys': 204,
'consumers': 205}
self.parsed_project_quotas_3 = {
'secrets': 301,
'containers': 303,
'consumers': 305}
def test_get_list_of_one_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1,
self.parsed_project_quotas_1,
session)
session.commit()
retrieved_project_quotas, offset, limit, total =\
self.project_quotas_repo.get_by_create_date(session=session)
self.assertEqual(0, offset)
self.assertEqual(10, limit)
self.assertEqual(1, total)
self.assertEqual([self.project_id_1],
[s.project_id for s in retrieved_project_quotas])
self.assertEqual([101],
[s.secrets for s in retrieved_project_quotas])
self.assertEqual([102],
[s.orders for s in retrieved_project_quotas])
self.assertEqual([103],
[s.containers for s in retrieved_project_quotas])
self.assertEqual([104],
[s.transport_keys for s in retrieved_project_quotas])
self.assertEqual([105],
[s.consumers for s in retrieved_project_quotas])
def test_get_list_of_two_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1,
self.parsed_project_quotas_1,
session)
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_2,
self.parsed_project_quotas_2,
session)
session.commit()
retrieved_project_quotas, offset, limit, total =\
self.project_quotas_repo.get_by_create_date(session=session)
self.assertEqual(0, offset)
self.assertEqual(10, limit)
self.assertEqual(2, total)
self.assertItemsEqual([self.project_id_1, self.project_id_2],
[s.project_id for s in retrieved_project_quotas])
self.assertItemsEqual([101, 201],
[s.secrets for s in retrieved_project_quotas])
self.assertItemsEqual([102, 202],
[s.orders for s in retrieved_project_quotas])
self.assertItemsEqual([103, 203],
[s.containers for s in retrieved_project_quotas])
self.assertItemsEqual([104, 204],
[s.transport_keys for s in
retrieved_project_quotas])
self.assertItemsEqual([105, 205],
[s.consumers for s in retrieved_project_quotas])
def test_should_raise_get_list_of_zero_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.assertRaises(
exception.NotFound,
self.project_quotas_repo.get_by_create_date,
session=session,
suppress_exception=False)
def test_should_suppress_get_list_of_zero_project_quotas(self):
session = self.project_quotas_repo.get_session()
retrieved_project_quotas, offset, limit, total =\
self.project_quotas_repo.get_by_create_date(
session=session, suppress_exception=True)
self.assertEqual(0, offset)
self.assertEqual(10, limit)
self.assertEqual(0, total)
def test_get_specific_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1, self.parsed_project_quotas_1, session)
session.commit()
retrieved_project_quotas =\
self.project_quotas_repo.get_by_project_id(self.project_id_1,
session=session)
self.assertEqual(self.project_id_1,
retrieved_project_quotas.project_id)
self.assertEqual(101, retrieved_project_quotas.secrets)
self.assertEqual(102, retrieved_project_quotas.orders)
self.assertEqual(103, retrieved_project_quotas.containers)
self.assertEqual(104, retrieved_project_quotas.transport_keys)
self.assertEqual(105, retrieved_project_quotas.consumers)
def test_project_quotas_with_some_defaults(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_3, self.parsed_project_quotas_3, session)
session.commit()
retrieved_project_quotas =\
self.project_quotas_repo.get_by_project_id(self.project_id_3,
session=session)
self.assertEqual(self.project_id_3,
retrieved_project_quotas.project_id)
self.assertEqual(301, retrieved_project_quotas.secrets)
self.assertIsNone(retrieved_project_quotas.orders)
self.assertEqual(303, retrieved_project_quotas.containers)
self.assertIsNone(retrieved_project_quotas.transport_keys)
self.assertEqual(305, retrieved_project_quotas.consumers)
def test_update_specific_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1, self.parsed_project_quotas_1, session)
session.commit()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1, self.parsed_project_quotas_2, session)
session.commit()
retrieved_project_quotas =\
self.project_quotas_repo.get_by_project_id(self.project_id_1,
session=session)
self.assertEqual(self.project_id_1,
retrieved_project_quotas.project_id)
self.assertEqual(201, retrieved_project_quotas.secrets)
self.assertEqual(202, retrieved_project_quotas.orders)
self.assertEqual(203, retrieved_project_quotas.containers)
self.assertEqual(204, retrieved_project_quotas.transport_keys)
self.assertEqual(205, retrieved_project_quotas.consumers)
def test_should_raise_get_missing_specific_project_quotas(self):
session = self.project_quotas_repo.get_session()
self.assertRaises(
exception.NotFound,
self.project_quotas_repo.get_by_project_id,
"dummy",
suppress_exception=False,
session=session)
def test_should_suppress_get_missing_specific_project_quotas(self):
session = self.project_quotas_repo.get_session()
retrieved_project_quotas =\
self.project_quotas_repo.get_by_project_id(self.project_id_1,
suppress_exception=True,
session=session)
self.assertIsNone(retrieved_project_quotas)
def test_get_by_create_date_nothing(self):
session = self.project_quotas_repo.get_session()
retrieved_project_quotas, offset, limit, total =\
self.project_quotas_repo.get_by_create_date(
session=session, suppress_exception=True)
self.assertEqual([], retrieved_project_quotas)
self.assertEqual(0, offset)
self.assertEqual(10, limit)
self.assertEqual(0, total)
def test_should_raise_add_duplicate_project_id(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1, self.parsed_project_quotas_1, session)
session.commit()
project_quotas = models.ProjectQuotas(
self.project_id_1, self.parsed_project_quotas_2)
self.assertRaises(
exception.Duplicate,
self.project_quotas_repo.create_from,
project_quotas,
session)
def test_should_delete(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.create_or_update_by_project_id(
self.project_id_1, self.parsed_project_quotas_1, session)
session.commit()
self.project_quotas_repo.delete_by_project_id(self.project_id_1,
session=session)
def test_should_raise_delete_not_found(self):
session = self.project_quotas_repo.get_session()
self.assertRaises(
exception.NotFound,
self.project_quotas_repo.delete_by_project_id,
"dummy",
session=session)
def test_should_suppress_delete_not_found(self):
session = self.project_quotas_repo.get_session()
self.project_quotas_repo.delete_by_project_id('dummy',
suppress_exception=True,
session=session)
def test_do_entity_name(self):
self.assertEqual("ProjectQuotas",
self.project_quotas_repo._do_entity_name())
def test_should_raise_not_found_get_by_entity_id(self):
session = self.project_quotas_repo.get_session()
self.assertRaises(
exception.NotFound,
self.project_quotas_repo.get,
"dummy",
session=session)
if __name__ == '__main__':
unittest.main()

View File

@ -14,6 +14,7 @@
# limitations under the License.
import datetime
import unittest
from barbican.common import exception
from barbican.model import models
@ -506,3 +507,86 @@ class WhenCreatingNewContainerACLUser(utils.BaseTestCase):
self.assertRaises(exception.MissingArgumentError,
models.ContainerACLUser, self.container_acl_id,
None)
class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
def setUp(self):
super(WhenCreatingNewProjectQuotas, self).setUp()
def test_create_new_project_quotas(self):
project_id = '12345'
parsed_project_quotas = {
'secrets': 101,
'orders': 102,
'containers': 103,
'transport_keys': 104,
'consumers': 105}
project_quotas = models.ProjectQuotas(project_id,
parsed_project_quotas)
self.assertEqual('12345', project_quotas.project_id)
self.assertEqual(101, project_quotas.secrets)
self.assertEqual(102, project_quotas.orders)
self.assertEqual(103, project_quotas.containers)
self.assertEqual(104, project_quotas.transport_keys)
self.assertEqual(105, project_quotas.consumers)
def test_create_new_project_quotas_with_all_default_quotas(self):
project_id = '12345'
project_quotas = models.ProjectQuotas(project_id,
None)
self.assertEqual('12345', project_quotas.project_id)
self.assertEqual(None, project_quotas.secrets)
self.assertEqual(None, project_quotas.orders)
self.assertEqual(None, project_quotas.containers)
self.assertEqual(None, project_quotas.transport_keys)
self.assertEqual(None, project_quotas.consumers)
def test_create_new_project_quotas_with_some_default_quotas(self):
project_id = '12345'
parsed_project_quotas = {
'secrets': 101,
'containers': 103,
'consumers': 105}
project_quotas = models.ProjectQuotas(project_id,
parsed_project_quotas)
self.assertEqual('12345', project_quotas.project_id)
self.assertEqual(101, project_quotas.secrets)
self.assertEqual(None, project_quotas.orders)
self.assertEqual(103, project_quotas.containers)
self.assertEqual(None, project_quotas.transport_keys)
self.assertEqual(105, project_quotas.consumers)
def test_should_throw_exception_missing_project_id(self):
self.assertRaises(exception.MissingArgumentError,
models.ProjectQuotas, None,
None)
def test_project_quotas_check_to_dict_fields(self):
project_id = '12345'
parsed_project_quotas = {
'secrets': 101,
'orders': 102,
'containers': 103,
'transport_keys': 104,
'consumers': 105}
project_quotas = models.ProjectQuotas(project_id,
parsed_project_quotas)
self.assertEqual(project_id,
project_quotas.to_dict_fields()['project_id'])
self.assertEqual(101,
project_quotas.to_dict_fields()['secrets'])
self.assertEqual(102,
project_quotas.to_dict_fields()['orders'])
self.assertEqual(103,
project_quotas.to_dict_fields()['containers'])
self.assertEqual(104,
project_quotas.to_dict_fields()['transport_keys'])
self.assertEqual(105,
project_quotas.to_dict_fields()['consumers'])
if __name__ == '__main__':
unittest.main()

View File

@ -195,28 +195,26 @@ periodic_interval_max_seconds = 10.0
# ====================== Quota Options ===============================
[quotas]
enabled = true
# True enforces quotas for the number of resources used by each project.
# For each resource, the default maximum number that can be used for
# a project is set below. This value can be overridden for each
# project through the API. A negative value means no limit. A zero
# value effectively disables the resource.
# default number of secrets allowed per project
quota_secrets = 500
quota_secrets = -1
# default number of orders allowed per project
quota_orders = 100
quota_orders = -1
# default number of containers allowed per project
quota_containers = -1
# Note, a negative value signifies unlimited
# default number of transport_keys allowed per project
quota_transport_keys = 100
quota_transport_keys = -1
# default number of consumers allowed per project
quota_consumers = 100
quota_consumers = -1
# ================= Keystone Notification Options - Application ===============

View File

@ -70,6 +70,6 @@
"container_acls:get": "rule:all_but_audit and rule:container_project_match",
"quotas:get": "rule:all_users",
"project_quotas:get": "rule:service_admin",
"project_quotas:post": "rule:service_admin",
"project_quotas:put": "rule:service_admin",
"project_quotas:delete": "rule:service_admin"
}

View File

@ -28,10 +28,10 @@ class QuotaBehaviors(base_behaviors.BaseBehaviors):
:param user_name: The user name used for REST command
:return: a request Response object
"""
resp = self.client.get('quotas',
response_model_type=quota_models.QuotaModel,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
resp = self.client.get(
'quotas', response_model_type=quota_models.QuotasResponseModel,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
return resp
def get_project_quotas_list(self, limit=10, offset=0, extra_headers=None,
@ -44,21 +44,25 @@ class QuotaBehaviors(base_behaviors.BaseBehaviors):
:param extra_headers: extra HTTP headers for the REST request
:param use_auth: Boolean for whether to send authentication headers
:param user_name: The user name used for REST command
:return: a request Response object
:return: the response, a list of project quotas and the next/prev refs
"""
params = {'limit': limit, 'offset': offset}
resp = self.client.get(
'project-quotas',
response_model_type=quota_models.ProjectQuotaModel,
response_model_type=quota_models.ProjectQuotaListModel,
params=params,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
response = self.get_json(resp)
project_quotas, next_ref, prev_ref = self.client.get_list_of_models(
response, quota_models.ProjectQuotaModel)
# handle expected JSON parsing errors for unauthenticated requests
if resp.status_code == 401 and not use_auth:
return resp, None, None, None
return resp, project_quotas
project_quotas_list = self.get_json(resp)
project_quotas, next_ref, prev_ref = self.client.get_list_of_models(
project_quotas_list, quota_models.ProjectQuotaListItemModel)
return resp, project_quotas, next_ref, prev_ref
def get_project_quotas(self, project_id, extra_headers=None,
use_auth=True, user_name=None):
@ -71,9 +75,14 @@ class QuotaBehaviors(base_behaviors.BaseBehaviors):
"""
resp = self.client.get(
'project-quotas/' + project_id,
response_model_type=quota_models.ProjectQuotaModel,
response_model_type=quota_models.ProjectQuotaOneModel,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
# handle expected JSON parsing errors for unauthenticated requests
if resp.status_code == 401 and not use_auth:
return resp, None, None, None
return resp
def set_project_quotas(self, project_id, request_model, extra_headers=None,
@ -86,15 +95,18 @@ class QuotaBehaviors(base_behaviors.BaseBehaviors):
:param user_name: The user name used for REST command
:return: a request Response object
"""
resp = self.client.post(
resp = self.client.put(
'project-quotas/' + project_id,
request_model=request_model,
response_model_type=quota_models.ProjectQuotaModel,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
self.created_entities.append((project_id, user_name))
return resp
def delete_project_quotas(self, project_id, extra_headers=None,
expected_fail=False,
use_auth=True, user_name=None):
"""Handles deleting project quotas
@ -107,4 +119,16 @@ class QuotaBehaviors(base_behaviors.BaseBehaviors):
resp = self.client.delete('project-quotas/' + project_id,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)
if not expected_fail:
for item in self.created_entities:
if item[0] == project_id:
self.created_entities.remove(item)
return resp
def delete_all_created_quotas(self):
"""Delete all of the project_quotas that we have created."""
entities = list(self.created_entities)
for (acl_ref, user_name) in entities:
self.delete_project_quotas(acl_ref, user_name=user_name)

View File

@ -35,64 +35,140 @@ class QuotasTestCase(base.TestCase):
def setUp(self):
super(QuotasTestCase, self).setUp()
self.behaviors = quota_behaviors.QuotaBehaviors(self.client)
self.project_id = self.behaviors.get_project_id_from_name('admin')
self.project_id = self.behaviors.get_project_id_from_name(
CONF.identity.username)
def tearDown(self):
super(QuotasTestCase, self).tearDown()
self.behaviors.delete_all_created_quotas()
def test_get_quotas(self):
"""Get quota information"""
def test_get_quotas_with_defaults(self):
"""Get effective quota information for own project"""
resp = self.behaviors.get_quotas()
self.assertEqual(200, resp.status_code)
self.assertEqual(500, resp.model.quotas['secrets'])
self.assertEqual(100, resp.model.quotas['transport_keys'])
self.assertEqual(100, resp.model.quotas['orders'])
self.assertEqual(-1, resp.model.quotas['containers'])
self.assertEqual(100, resp.model.quotas['consumers'])
self.assertEqual(-1, resp.model.quotas.secrets)
self.assertEqual(-1, resp.model.quotas.transport_keys)
self.assertEqual(-1, resp.model.quotas.orders)
self.assertEqual(-1, resp.model.quotas.containers)
self.assertEqual(-1, resp.model.quotas.consumers)
def test_get_project_quota_list(self):
"""Get list of all project quotas"""
resp, project_quotas_list = self.behaviors.get_project_quotas_list(
user_name=service_admin)
self.assertEqual(200, resp.status_code)
for project_quotas in project_quotas_list:
self.assertEqual(500, project_quotas.project_quotas['secrets'])
self.assertEqual(100,
project_quotas.project_quotas['transport_keys'])
self.assertEqual(100, project_quotas.project_quotas['orders'])
self.assertEqual(-1, project_quotas.project_quotas['containers'])
self.assertEqual(100, project_quotas.project_quotas['consumers'])
def test_get_one_project_quotas(self):
def test_get_project_quotas_by_project_id(self):
"""Get project quota information for specific project"""
resp = self.behaviors.get_project_quotas(self.project_id,
user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(500, resp.model.project_quotas['secrets'])
self.assertEqual(100, resp.model.project_quotas['transport_keys'])
self.assertEqual(100, resp.model.project_quotas['orders'])
self.assertEqual(-1, resp.model.project_quotas['containers'])
self.assertEqual(100, resp.model.project_quotas['consumers'])
def test_set_project_quotas(self):
"""Set project quota information"""
request_model = quota_models.ProjectQuotaRequestModel(
**get_set_project_quotas_request())
resp = self.behaviors.set_project_quotas(self.project_id,
resp = self.behaviors.set_project_quotas('44444',
request_model,
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.behaviors.get_project_quotas('44444',
user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(50, resp.model.project_quotas.secrets)
self.assertIsNone(resp.model.project_quotas.transport_keys)
self.assertEqual(10, resp.model.project_quotas.orders)
self.assertEqual(20, resp.model.project_quotas.containers)
self.assertIsNone(resp.model.project_quotas.consumers)
def test_get_project_quotas_by_project_id_not_found(self):
"""Get project quota information for specific project"""
resp = self.behaviors.get_project_quotas('dummy',
user_name=service_admin)
self.assertEqual(404, resp.status_code)
def test_delete_project_quotas(self):
"""Delete project quota information"""
request_model = quota_models.ProjectQuotaRequestModel(
**get_set_project_quotas_request())
resp = self.behaviors.set_project_quotas('55555',
request_model,
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.behaviors.delete_project_quotas(self.project_id,
resp = self.behaviors.delete_project_quotas('55555',
user_name=service_admin)
self.assertEqual(204, resp.status_code)
def test_delete_project_quotas_not_found(self):
"""Get project quota information"""
resp = self.behaviors.delete_project_quotas('dummy',
user_name=service_admin)
self.assertEqual(404, resp.status_code)
class ProjectQuotasPagingTestCase(base.PagingTestCase):
def setUp(self):
super(ProjectQuotasPagingTestCase, self).setUp()
self.behaviors = quota_behaviors.QuotaBehaviors(self.client)
def tearDown(self):
self.behaviors.delete_all_created_quotas()
super(ProjectQuotasPagingTestCase, self).tearDown()
def create_model(self):
request_model = quota_models.ProjectQuotaRequestModel(
**get_set_project_quotas_request())
return request_model
def create_resources(self, count=0, model=None):
for x in range(0, count):
self.behaviors.set_project_quotas(str(x), model,
user_name=service_admin)
def get_resources(self, limit=10, offset=0, filter=None):
return self.behaviors.get_project_quotas_list(
limit=limit, offset=offset, user_name=service_admin)
def set_filter_field(self, unique_str, model):
"""ProjectQuotas API does not support filter """
pass
def test_get_project_quota_list_none(self):
"""Get list of all project quotas, when there are none"""
resp, project_quotas_list, _, _ =\
self.behaviors.get_project_quotas_list(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual([], project_quotas_list)
def test_get_project_quota_list_one(self):
"""Get list of all project quotas, when there is one"""
request_model = quota_models.ProjectQuotaRequestModel(
**get_set_project_quotas_request())
resp = self.behaviors.set_project_quotas('11111',
request_model,
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp, project_quotas_list, _, _ =\
self.behaviors.get_project_quotas_list(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(1, len(project_quotas_list))
def test_get_project_quota_list_two(self):
"""Get list of all project quotas, when there is one"""
request_model = quota_models.ProjectQuotaRequestModel(
**get_set_project_quotas_request())
resp = self.behaviors.set_project_quotas('22222',
request_model,
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.behaviors.set_project_quotas('33333',
request_model,
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp, project_quotas_list, _, _ =\
self.behaviors.get_project_quotas_list(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(2, len(project_quotas_list))

View File

@ -56,7 +56,7 @@ test_data_rbac_get_project_quotas = {
test_data_rbac_set_project_quotas = {
'with_service_admin': {'user': service_admin, 'admin': service_admin,
'expected_return': 200},
'expected_return': 204},
'with_admin_a': {'user': admin_a, 'admin': admin_a,
'expected_return': 403},
'with_creator_a': {'user': creator_a, 'admin': admin_a,
@ -125,7 +125,7 @@ class RBACQuotasTestCase(base.TestCase):
:param admin: the admin of the group owning quotas
:param expected_return: the expected http return code
"""
resp, _ = self.behaviors.get_project_quotas_list(user_name=user)
resp, _, _, _ = self.behaviors.get_project_quotas_list(user_name=user)
self.assertEqual(expected_return, resp.status_code)
@utils.parameterized_dataset(test_data_rbac_set_project_quotas)

View File

@ -17,19 +17,28 @@ limitations under the License.
from functionaltests.api.v1.models.base_models import BaseModel
class QuotaModel(BaseModel):
class QuotasModel(BaseModel):
def __init__(self, secrets=None, orders=None, containers=None,
transport_keys=None, consumers=None):
super(QuotasModel, self).__init__()
self.secrets = secrets
self.orders = orders
self.containers = containers
self.transport_keys = transport_keys
self.consumers = consumers
class QuotasResponseModel(BaseModel):
def __init__(self, quotas=None):
super(QuotaModel, self).__init__()
super(QuotasResponseModel, self).__init__()
self.quotas = quotas
class ProjectQuotaModel(BaseModel):
def __init__(self, project_quotas=None, project_id=None):
super(ProjectQuotaModel, self).__init__()
self.project_quotas = project_quotas
self.project_id = project_id
@classmethod
def dict_to_obj(cls, input_dict):
quotas = QuotasModel(**input_dict.get('quotas'))
return cls(quotas=quotas)
class ProjectQuotaRequestModel(BaseModel):
@ -37,3 +46,37 @@ class ProjectQuotaRequestModel(BaseModel):
def __init__(self, project_quotas=None):
super(ProjectQuotaRequestModel, self).__init__()
self.project_quotas = project_quotas
@classmethod
def dict_to_obj(cls, input_dict):
project_quotas = QuotasModel(**input_dict.get('project_quotas'))
return cls(project_quotas=project_quotas)
class ProjectQuotaOneModel(BaseModel):
def __init__(self, project_quotas=None):
super(ProjectQuotaOneModel, self).__init__()
self.project_quotas = QuotasModel(**project_quotas)
class ProjectQuotaListItemModel(BaseModel):
def __init__(self, project_id=None, project_quotas=None):
super(ProjectQuotaListItemModel, self).__init__()
self.project_id = project_id
self.project_quotas = QuotasModel(**project_quotas)
class ProjectQuotaListModel(BaseModel):
def __init__(self, project_quotas=None):
super(ProjectQuotaListModel, self).__init__()
self.project_quotas = project_quotas
@classmethod
def dict_to_obj(cls, input_dict):
project_quotas = [ProjectQuotaListItemModel(**project_quotas_item)
for project_quotas_item in
input_dict.get('project_quotas', [])]
return cls(project_quotas=project_quotas)

View File

@ -35,8 +35,9 @@ retval=$?
testr slowest
# run the tests in parallel
SKIP=^\(\?\!\.\*ProjectQuotasPagingTestCase\)
testr init
testr run --parallel --subunit | subunit-trace --no-failure-debug -f
testr run $SKIP --parallel --subunit | subunit-trace --no-failure-debug -f
retval=$?
testr slowest