Merge "Add Project Quota Support for Sub CAs"
This commit is contained in:
commit
847f0050f6
|
@ -18,6 +18,7 @@ from six.moves.urllib import parse
|
|||
from barbican import api
|
||||
from barbican.api import controllers
|
||||
from barbican.common import hrefs
|
||||
from barbican.common import quota
|
||||
from barbican.common import resources as res
|
||||
from barbican.common import utils
|
||||
from barbican.common import validators
|
||||
|
@ -234,6 +235,7 @@ class CertificateAuthoritiesController(controllers.ACLMixin):
|
|||
self.preferred_ca_repo = repo.get_preferred_ca_repository()
|
||||
self.project_repo = repo.get_project_repository()
|
||||
self.validator = validators.NewCAValidator()
|
||||
self.quota_enforcer = quota.QuotaEnforcer('cas', self.ca_repo)
|
||||
|
||||
def __getattr__(self, name):
|
||||
route_table = {
|
||||
|
@ -343,8 +345,7 @@ class CertificateAuthoritiesController(controllers.ACLMixin):
|
|||
if ctxt: # in authenticated pipeline case, always use auth token user
|
||||
creator_id = ctxt.user
|
||||
|
||||
# TODO(alee) Add quota enforcement
|
||||
# self.quota_enforcer.enforce(project)
|
||||
self.quota_enforcer.enforce(project)
|
||||
|
||||
new_ca = cert_resources.create_subordinate_ca(
|
||||
project_model=project,
|
||||
|
|
|
@ -44,7 +44,9 @@ quota_opts = [
|
|||
cfg.IntOpt('quota_consumers',
|
||||
default=-1,
|
||||
help='Number of consumers allowed per project'),
|
||||
]
|
||||
cfg.IntOpt('quota_cas',
|
||||
default=-1,
|
||||
help='Number of CAs allowed per project')]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_group(quota_opt_group)
|
||||
|
@ -59,8 +61,7 @@ class QuotaDriver(object):
|
|||
|
||||
def _get_resources(self):
|
||||
"""List of resources that can be constrained by a quota"""
|
||||
return ['secrets', 'orders', 'containers',
|
||||
'consumers']
|
||||
return ['secrets', 'orders', 'containers', 'consumers', 'cas']
|
||||
|
||||
def _get_defaults(self):
|
||||
"""Return list of default quotas"""
|
||||
|
@ -68,7 +69,8 @@ class QuotaDriver(object):
|
|||
'secrets': CONF.quotas.quota_secrets,
|
||||
'orders': CONF.quotas.quota_orders,
|
||||
'containers': CONF.quotas.quota_containers,
|
||||
'consumers': CONF.quotas.quota_consumers
|
||||
'consumers': CONF.quotas.quota_consumers,
|
||||
'cas': CONF.quotas.quota_cas
|
||||
}
|
||||
return quotas
|
||||
|
||||
|
|
|
@ -865,7 +865,8 @@ class ProjectQuotaValidator(ValidatorBase):
|
|||
'orders': {'type': 'integer'},
|
||||
'containers': {'type': 'integer'},
|
||||
'transport_keys': {'type': 'integer'},
|
||||
'consumers': {'type': 'integer'}
|
||||
'consumers': {'type': 'integer'},
|
||||
'cas': {'type': 'integer'}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
"""Add cas column to project quotas table
|
||||
|
||||
Revision ID: 4ecde3a3a72a
|
||||
Revises: 10220ccbe7fa
|
||||
Create Date: 2015-09-09 09:40:08.540064
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4ecde3a3a72a'
|
||||
down_revision = '10220ccbe7fa'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'project_quotas',
|
||||
sa.Column('cas', sa.Integer(), nullable=True))
|
|
@ -1281,6 +1281,7 @@ class ProjectQuotas(BASE, ModelBase):
|
|||
orders = sa.Column(sa.Integer, nullable=True)
|
||||
containers = sa.Column(sa.Integer, nullable=True)
|
||||
consumers = sa.Column(sa.Integer, nullable=True)
|
||||
cas = sa.Column(sa.Integer, nullable=True)
|
||||
|
||||
def __init__(self, project_id=None, parsed_project_quotas=None):
|
||||
"""Creates Project Quotas entity from a project and a dict.
|
||||
|
@ -1304,11 +1305,13 @@ class ProjectQuotas(BASE, ModelBase):
|
|||
self.orders = None
|
||||
self.containers = None
|
||||
self.consumers = None
|
||||
self.cas = None
|
||||
else:
|
||||
self.secrets = parsed_project_quotas.get('secrets')
|
||||
self.orders = parsed_project_quotas.get('orders')
|
||||
self.containers = parsed_project_quotas.get('containers')
|
||||
self.consumers = parsed_project_quotas.get('consumers')
|
||||
self.cas = parsed_project_quotas.get('cas')
|
||||
|
||||
def _do_extra_dict_fields(self):
|
||||
"""Sub-class hook method: return dict of fields."""
|
||||
|
@ -1323,4 +1326,6 @@ class ProjectQuotas(BASE, ModelBase):
|
|||
ret['containers'] = self.containers
|
||||
if self.consumers:
|
||||
ret['consumers'] = self.consumers
|
||||
if self.cas:
|
||||
ret['cas'] = self.cas
|
||||
return ret
|
||||
|
|
|
@ -1475,6 +1475,15 @@ class CertificateAuthorityRepo(BaseRepo):
|
|||
"""Sub-class hook: validate values."""
|
||||
pass
|
||||
|
||||
def _build_get_project_entities_query(self, project_id, session):
|
||||
"""Builds query for retrieving CA related to given project.
|
||||
|
||||
:param project_id: id of barbican project entity
|
||||
:param session: existing db session reference.
|
||||
"""
|
||||
return session.query(models.CertificateAuthority).filter_by(
|
||||
project_id=project_id).filter_by(deleted=False)
|
||||
|
||||
|
||||
class CertificateAuthorityMetadatumRepo(BaseRepo):
|
||||
"""Repository for the CertificateAuthorityMetadatum entity
|
||||
|
|
|
@ -26,7 +26,7 @@ class WhenTestingQuotas(utils.BarbicanAPIBaseTestCase):
|
|||
self.assertEqual(200, resp.status_int)
|
||||
quotas_list = resp.json.get('quotas')
|
||||
self.assertEqual({'consumers': -1, 'containers': -1, 'orders': -1,
|
||||
'secrets': -1},
|
||||
'secrets': -1, 'cas': -1},
|
||||
quotas_list)
|
||||
|
||||
def test_should_get_specific_project_quotas(self):
|
||||
|
@ -38,7 +38,7 @@ class WhenTestingQuotas(utils.BarbicanAPIBaseTestCase):
|
|||
self.assertEqual(200, resp.status_int)
|
||||
project_quotas = resp.json.get('project_quotas')
|
||||
self.assertEqual({'consumers': 105, 'containers': 103, 'orders': 102,
|
||||
'secrets': 101},
|
||||
'secrets': 101, 'cas': 106},
|
||||
project_quotas)
|
||||
|
||||
def test_should_return_not_found_get_specific_project_quotas(self):
|
||||
|
@ -164,7 +164,8 @@ class WhenTestingQuotas(utils.BarbicanAPIBaseTestCase):
|
|||
'secrets': index * 100 + 1,
|
||||
'orders': index * 100 + 2,
|
||||
'containers': index * 100 + 3,
|
||||
'consumers': index * 100 + 5}
|
||||
'consumers': index * 100 + 5,
|
||||
'cas': index * 100 + 6}
|
||||
request = {'project_quotas': parsed_project_quotas}
|
||||
resp = self.app.put_json(
|
||||
'/project-quotas/{0}'.format(project_id), request)
|
||||
|
|
|
@ -34,21 +34,26 @@ class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
|
|||
self.assertEqual(-1, quotas['orders'])
|
||||
self.assertEqual(-1, quotas['containers'])
|
||||
self.assertEqual(-1, quotas['consumers'])
|
||||
self.assertEqual(-1, quotas['cas'])
|
||||
|
||||
def test_compute_effective_quotas_using_some_defaults(self):
|
||||
configured_quotas = {'consumers': None, 'containers': 66,
|
||||
'orders': None, 'secrets': 55}
|
||||
'orders': None, 'secrets': 55,
|
||||
'cas': None}
|
||||
quotas = self.quota_driver._compute_effective_quotas(configured_quotas)
|
||||
expected_quotas = {'consumers': -1, 'containers': 66,
|
||||
'orders': -1, 'secrets': 55}
|
||||
'orders': -1, 'secrets': 55,
|
||||
'cas': -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}
|
||||
'orders': None, 'secrets': None,
|
||||
'cas': None}
|
||||
quotas = self.quota_driver._compute_effective_quotas(configured_quotas)
|
||||
expected_quotas = {'consumers': -1, 'containers': -1,
|
||||
'orders': -1, 'secrets': -1}
|
||||
'orders': -1, 'secrets': -1,
|
||||
'cas': -1}
|
||||
self.assertEqual(expected_quotas, quotas)
|
||||
|
||||
def test_is_unlimited_true(self):
|
||||
|
@ -83,7 +88,8 @@ class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
|
|||
'project_quotas': {'consumers': 105,
|
||||
'containers': 103,
|
||||
'orders': 102,
|
||||
'secrets': 101}}], 'total': 1},
|
||||
'secrets': 101,
|
||||
'cas': 106}}], 'total': 1},
|
||||
project_quotas)
|
||||
|
||||
def test_should_get_empty_project_quotas_list(self):
|
||||
|
@ -122,13 +128,15 @@ class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
|
|||
quotas = self.quota_driver.get_quotas(
|
||||
self.get_test_project_id('partial'))
|
||||
expected_quotas = {'quotas': {'consumers': -1, 'containers': 66,
|
||||
'orders': -1, 'secrets': 55}}
|
||||
'orders': -1, 'secrets': 55,
|
||||
'cas': -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}}
|
||||
'orders': -1, 'secrets': -1,
|
||||
'cas': -1}}
|
||||
self.assertEqual(expected_quotas, quotas)
|
||||
|
||||
# ----------------------- Helper Functions ---------------------------
|
||||
|
@ -152,7 +160,8 @@ class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
|
|||
'secrets': index * 100 + 1,
|
||||
'orders': index * 100 + 2,
|
||||
'containers': index * 100 + 3,
|
||||
'consumers': index * 100 + 5}
|
||||
'consumers': index * 100 + 5,
|
||||
'cas': index * 100 + 6}
|
||||
return parsed_project_quotas
|
||||
|
||||
def get_test_response_project_quotas(self, index=1):
|
||||
|
@ -161,19 +170,22 @@ class WhenTestingQuotaDriverFunctions(database_utils.RepositoryTestCase):
|
|||
'secrets': 55,
|
||||
'orders': None,
|
||||
'containers': 66,
|
||||
'consumers': None}
|
||||
'consumers': None,
|
||||
'cas': None}
|
||||
elif index == 'none':
|
||||
response_project_quotas = {
|
||||
'secrets': None,
|
||||
'orders': None,
|
||||
'containers': None,
|
||||
'consumers': None}
|
||||
'consumers': None,
|
||||
'cas': None}
|
||||
else:
|
||||
response_project_quotas = {
|
||||
'secrets': index * 100 + 1,
|
||||
'orders': index * 100 + 2,
|
||||
'containers': index * 100 + 3,
|
||||
'consumers': index * 100 + 5}
|
||||
'consumers': index * 100 + 5,
|
||||
'cas': index * 100 + 6}
|
||||
return response_project_quotas
|
||||
|
||||
def create_a_test_project_quotas(self, index=1):
|
||||
|
@ -213,7 +225,8 @@ class WhenTestingQuotaEnforcingFunctions(utils.BaseTestCase):
|
|||
test_repo = DummyRepoForTestingQuotaEnforcement(0)
|
||||
quota_enforcer = quota.QuotaEnforcer('secrets', test_repo)
|
||||
disabled_project_quotas = {'consumers': 0, 'containers': 0,
|
||||
'orders': 0, 'secrets': 0}
|
||||
'orders': 0, 'secrets': 0,
|
||||
'cas': 0}
|
||||
self.quota_driver.set_project_quotas(self.project.external_id,
|
||||
disabled_project_quotas)
|
||||
exception = self.assertRaises(
|
||||
|
@ -230,7 +243,8 @@ class WhenTestingQuotaEnforcingFunctions(utils.BaseTestCase):
|
|||
test_repo = DummyRepoForTestingQuotaEnforcement(4)
|
||||
quota_enforcer = quota.QuotaEnforcer('secrets', test_repo)
|
||||
five_project_quotas = {'consumers': 5, 'containers': 5,
|
||||
'orders': 5, 'secrets': 5}
|
||||
'orders': 5, 'secrets': 5,
|
||||
'cas': 5}
|
||||
self.quota_driver.set_project_quotas(self.project.external_id,
|
||||
five_project_quotas)
|
||||
quota_enforcer.enforce(self.project)
|
||||
|
@ -239,7 +253,8 @@ class WhenTestingQuotaEnforcingFunctions(utils.BaseTestCase):
|
|||
test_repo = DummyRepoForTestingQuotaEnforcement(5)
|
||||
quota_enforcer = quota.QuotaEnforcer('secrets', test_repo)
|
||||
five_project_quotas = {'consumers': 5, 'containers': 5,
|
||||
'orders': 5, 'secrets': 5}
|
||||
'orders': 5, 'secrets': 5,
|
||||
'cas': 5}
|
||||
self.quota_driver.set_project_quotas(self.project.external_id,
|
||||
five_project_quotas)
|
||||
exception = self.assertRaises(
|
||||
|
@ -256,7 +271,8 @@ class WhenTestingQuotaEnforcingFunctions(utils.BaseTestCase):
|
|||
test_repo = DummyRepoForTestingQuotaEnforcement(6)
|
||||
quota_enforcer = quota.QuotaEnforcer('secrets', test_repo)
|
||||
five_project_quotas = {'consumers': 5, 'containers': 5,
|
||||
'orders': 5, 'secrets': 5}
|
||||
'orders': 5, 'secrets': 5,
|
||||
'cas': 5}
|
||||
self.quota_driver.set_project_quotas(self.project.external_id,
|
||||
five_project_quotas)
|
||||
exception = self.assertRaises(
|
||||
|
|
|
@ -1372,7 +1372,8 @@ class WhenTestingProjectQuotasValidator(utils.BaseTestCase):
|
|||
self.good_project_quotas = {"project_quotas":
|
||||
{"secrets": 50,
|
||||
"orders": 10,
|
||||
"containers": 20}}
|
||||
"containers": 20,
|
||||
"cas": 30}}
|
||||
self.bad_project_quotas = {"bad key": "bad value"}
|
||||
self.validator = validators.ProjectQuotaValidator()
|
||||
|
||||
|
|
|
@ -120,6 +120,60 @@ class WhenTestingCertificateAuthorityRepo(database_utils.RepositoryTestCase):
|
|||
session=session,
|
||||
suppress_exception=False)
|
||||
|
||||
def test_get_count_should_return_zero(self):
|
||||
session = self.ca_repo.get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "my keystone id"
|
||||
project.save(session=session)
|
||||
|
||||
session.commit()
|
||||
count = self.ca_repo.get_count(project.id, session=session)
|
||||
|
||||
self.assertEqual(count, 0)
|
||||
|
||||
def test_get_count_should_return_one(self):
|
||||
session = self.ca_repo.get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "my keystone id"
|
||||
project.save(session=session)
|
||||
|
||||
ca_model = models.CertificateAuthority(self.parsed_ca)
|
||||
ca_model.project_id = project.id
|
||||
self.ca_repo.create_from(ca_model, session=session)
|
||||
|
||||
session.commit()
|
||||
count = self.ca_repo.get_count(project.id, session=session)
|
||||
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
def test_get_count_should_return_one_after_delete(self):
|
||||
session = self.ca_repo.get_session()
|
||||
|
||||
project = models.Project()
|
||||
project.external_id = "my keystone id"
|
||||
project.save(session=session)
|
||||
|
||||
ca_model = models.CertificateAuthority(self.parsed_ca)
|
||||
ca_model.project_id = project.id
|
||||
self.ca_repo.create_from(ca_model, session=session)
|
||||
|
||||
ca_model = models.CertificateAuthority(self.parsed_ca)
|
||||
ca_model.project_id = project.id
|
||||
self.ca_repo.create_from(ca_model, session=session)
|
||||
|
||||
session.commit()
|
||||
count = self.ca_repo.get_count(project.id, session=session)
|
||||
self.assertEqual(count, 2)
|
||||
|
||||
self.ca_repo.delete_entity_by_id(ca_model.id, "my keystone id",
|
||||
session=session)
|
||||
session.commit()
|
||||
|
||||
count = self.ca_repo.get_count(project.id, session=session)
|
||||
self.assertEqual(count, 1)
|
||||
|
||||
|
||||
class WhenTestingProjectCARepo(database_utils.RepositoryTestCase):
|
||||
|
||||
|
|
|
@ -45,12 +45,14 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
'secrets': 101,
|
||||
'orders': 102,
|
||||
'containers': 103,
|
||||
'consumers': 105}
|
||||
'consumers': 105,
|
||||
'cas': 106}
|
||||
self.parsed_project_quotas_2 = {
|
||||
'secrets': 201,
|
||||
'orders': 202,
|
||||
'containers': 203,
|
||||
'consumers': 205}
|
||||
'consumers': 205,
|
||||
'cas': 206}
|
||||
self.parsed_project_quotas_3 = {
|
||||
'secrets': 301,
|
||||
'containers': 303,
|
||||
|
@ -80,6 +82,8 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
[s.containers for s in retrieved_project_quotas])
|
||||
self.assertEqual([105],
|
||||
[s.consumers for s in retrieved_project_quotas])
|
||||
self.assertEqual([106],
|
||||
[s.cas for s in retrieved_project_quotas])
|
||||
|
||||
def test_get_list_of_two_project_quotas(self):
|
||||
self.project_quotas_repo.create_or_update_by_project_id(
|
||||
|
@ -110,6 +114,8 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
[s.containers for s in retrieved_project_quotas])
|
||||
self.assertItemsEqual([105, 205],
|
||||
[s.consumers for s in retrieved_project_quotas])
|
||||
self.assertItemsEqual([106, 206],
|
||||
[s.cas for s in retrieved_project_quotas])
|
||||
|
||||
def test_should_raise_get_list_of_zero_project_quotas(self):
|
||||
self.assertRaises(
|
||||
|
@ -143,6 +149,7 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
self.assertEqual(102, retrieved_project_quotas.orders)
|
||||
self.assertEqual(103, retrieved_project_quotas.containers)
|
||||
self.assertEqual(105, retrieved_project_quotas.consumers)
|
||||
self.assertEqual(106, retrieved_project_quotas.cas)
|
||||
|
||||
def test_project_quotas_with_some_defaults(self):
|
||||
self.project_quotas_repo.create_or_update_by_project_id(
|
||||
|
@ -161,6 +168,7 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
self.assertIsNone(retrieved_project_quotas.orders)
|
||||
self.assertEqual(303, retrieved_project_quotas.containers)
|
||||
self.assertEqual(305, retrieved_project_quotas.consumers)
|
||||
self.assertIsNone(retrieved_project_quotas.cas)
|
||||
|
||||
def test_update_specific_project_quotas(self):
|
||||
self.project_quotas_repo.create_or_update_by_project_id(
|
||||
|
@ -184,6 +192,7 @@ class WhenTestingProjectQuotasRepo(database_utils.RepositoryTestCase):
|
|||
self.assertEqual(202, retrieved_project_quotas.orders)
|
||||
self.assertEqual(203, retrieved_project_quotas.containers)
|
||||
self.assertEqual(205, retrieved_project_quotas.consumers)
|
||||
self.assertEqual(206, retrieved_project_quotas.cas)
|
||||
|
||||
def test_should_raise_get_missing_specific_project_quotas(self):
|
||||
self.assertRaises(
|
||||
|
|
|
@ -530,7 +530,8 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
'secrets': 101,
|
||||
'orders': 102,
|
||||
'containers': 103,
|
||||
'consumers': 105}
|
||||
'consumers': 105,
|
||||
'cas': 106}
|
||||
project_quotas = models.ProjectQuotas(project.id,
|
||||
parsed_project_quotas)
|
||||
|
||||
|
@ -539,6 +540,7 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
self.assertEqual(102, project_quotas.orders)
|
||||
self.assertEqual(103, project_quotas.containers)
|
||||
self.assertEqual(105, project_quotas.consumers)
|
||||
self.assertEqual(106, project_quotas.cas)
|
||||
|
||||
def test_create_new_project_quotas_with_all_default_quotas(self):
|
||||
project = models.Project()
|
||||
|
@ -552,6 +554,7 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
self.assertEqual(None, project_quotas.orders)
|
||||
self.assertEqual(None, project_quotas.containers)
|
||||
self.assertEqual(None, project_quotas.consumers)
|
||||
self.assertEqual(None, project_quotas.cas)
|
||||
|
||||
def test_create_new_project_quotas_with_some_default_quotas(self):
|
||||
project = models.Project()
|
||||
|
@ -569,6 +572,7 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
self.assertEqual(None, project_quotas.orders)
|
||||
self.assertEqual(103, project_quotas.containers)
|
||||
self.assertEqual(105, project_quotas.consumers)
|
||||
self.assertEqual(None, project_quotas.cas)
|
||||
|
||||
def test_should_throw_exception_missing_project_id(self):
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
|
@ -582,7 +586,8 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
'secrets': 101,
|
||||
'orders': 102,
|
||||
'containers': 103,
|
||||
'consumers': 105}
|
||||
'consumers': 105,
|
||||
'cas': 106}
|
||||
project_quotas = models.ProjectQuotas(project.id,
|
||||
parsed_project_quotas)
|
||||
self.assertEqual(project.id,
|
||||
|
@ -595,7 +600,8 @@ class WhenCreatingNewProjectQuotas(utils.BaseTestCase):
|
|||
project_quotas.to_dict_fields()['containers'])
|
||||
self.assertEqual(105,
|
||||
project_quotas.to_dict_fields()['consumers'])
|
||||
|
||||
self.assertEqual(106,
|
||||
project_quotas.to_dict_fields()['cas'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -33,7 +33,7 @@ role. The service administrator's role is defined in Barbican's policy.json fil
|
|||
The default role for a service admin is "key-manager:service-admin".
|
||||
|
||||
Quotas can be enforced for the following Barbican resources: secrets, containers,
|
||||
orders, and consumers. The configured quota value can be None (use the default),
|
||||
orders, consumers, and CAs. The configured quota value can be None (use the default),
|
||||
-1 (unlimited), 0 (disabled), or a positive integer defining the maximum number
|
||||
allowed for a project.
|
||||
|
||||
|
@ -61,6 +61,8 @@ in the standard configuration file are as follows.
|
|||
# default number of consumers allowed per project
|
||||
quota_consumers = -1
|
||||
|
||||
# default number of CAs allowed per project
|
||||
quota_cas = -1
|
||||
|
||||
The default quotas are returned via a **GET** on the **quotas** resource when no
|
||||
explicit project quotas have been set for the current project.
|
||||
|
@ -92,7 +94,8 @@ with the request.
|
|||
{"secrets": -1,
|
||||
"orders": -1,
|
||||
"containers": -1,
|
||||
"consumers": -1
|
||||
"consumers": -1,
|
||||
"cas": -1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +122,8 @@ To set or replace the quotas for the project with the ID 1234:
|
|||
curl -i -X PUT -H "content-type:application/json" \
|
||||
-H "X-Auth-Token:$TOKEN" \
|
||||
-d '{"project_quotas": {"secrets": 500,
|
||||
"orders": 100, "containers": -1, "consumers": 100}}' \
|
||||
"orders": 100, "containers": -1, "consumers": 100,
|
||||
"cas": 50}}' \
|
||||
http://localhost:9311/v1/project-quotas/1234
|
||||
|
||||
Response:
|
||||
|
@ -156,10 +160,11 @@ To get project quota information for a single project:
|
|||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
{"project_quotas":
|
||||
{"secrets": 500,
|
||||
"orders": 100,
|
||||
"containers": -1,
|
||||
"consumers": 100}}
|
||||
{"secrets": 500,
|
||||
"orders": 100,
|
||||
"containers": -1,
|
||||
"consumers": 100,
|
||||
"cas": 50}}
|
||||
|
||||
|
||||
The project quota information defined for all projects can be retrieved by using
|
||||
|
@ -185,13 +190,15 @@ The returned response contains a list with all project quota data.
|
|||
{"secrets": 500,
|
||||
"orders": 100,
|
||||
"containers": -1,
|
||||
"consumers": 100}},
|
||||
"consumers": 100,
|
||||
"cas": 50}},
|
||||
{"project_id": "5678",
|
||||
"project_quotas":
|
||||
{"secrets": 500,
|
||||
"orders": 100,
|
||||
"containers": -1,
|
||||
"consumers": 100}}]}
|
||||
"consumers": 100,
|
||||
"cas": 50}}]}
|
||||
|
||||
|
||||
To get more details on project quota lookup APIs you can reference
|
||||
|
|
|
@ -33,7 +33,8 @@ Request/Response:
|
|||
"secrets": 10,
|
||||
"orders": 20,
|
||||
"containers": 10,
|
||||
"consumers": -1
|
||||
"consumers": -1,
|
||||
"cas": 5
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,9 @@ Response Attributes
|
|||
| consumers | integer | Contains the effective quota value of the current project |
|
||||
| | | for the consumers resource. |
|
||||
+------------+---------+--------------------------------------------------------------+
|
||||
| cas | integer | Contains the effective quota value of the current project |
|
||||
| | | for the CAs resource. |
|
||||
+------------+---------+--------------------------------------------------------------+
|
||||
|
||||
Effective quota values are interpreted as follows:
|
||||
|
||||
|
@ -122,7 +126,8 @@ Request/Response:
|
|||
"secrets": 2000,
|
||||
"orders": 0,
|
||||
"containers": -1,
|
||||
"consumers": null
|
||||
"consumers": null,
|
||||
"cas": null
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -131,7 +136,8 @@ Request/Response:
|
|||
"secrets": 200,
|
||||
"orders": 100,
|
||||
"containers": -1,
|
||||
"consumers": null
|
||||
"consumers": null,
|
||||
"cas": null
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -177,6 +183,9 @@ Response Attributes
|
|||
| consumers | integer | Contains the effective quota value of the current project |
|
||||
| | | for the consumers resource. |
|
||||
+----------------+---------+--------------------------------------------------------------+
|
||||
| cas | integer | Contains the effective quota value of the current project |
|
||||
| | | for the CAs resource. |
|
||||
+----------------+---------+--------------------------------------------------------------+
|
||||
| total | integer | The total number of configured project quotas records. |
|
||||
+----------------+---------+--------------------------------------------------------------+
|
||||
| next | string | A HATEOS url to retrieve the next set of quotas based on |
|
||||
|
@ -250,7 +259,8 @@ Request/Response:
|
|||
"secrets": 10,
|
||||
"orders": 20,
|
||||
"containers": -1,
|
||||
"consumers": 10
|
||||
"consumers": 10,
|
||||
"cas": 5
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,6 +287,9 @@ Response Attributes
|
|||
| consumers | integer | Contains the configured quota value of the requested project |
|
||||
| | | for the consumers resource. |
|
||||
+----------------+---------+--------------------------------------------------------------+
|
||||
| cas | integer | Contains the configured quota value of the requested project |
|
||||
| | | for the CAs resource. |
|
||||
+----------------+---------+--------------------------------------------------------------+
|
||||
|
||||
.. _get_project_quotas_uuid_status_codes:
|
||||
|
||||
|
@ -351,6 +364,9 @@ Request Attributes
|
|||
| consumers | integer | The value to set for this project's |
|
||||
| | | consumer quota. |
|
||||
+----------------+---------+----------------------------------------------+
|
||||
| cas | integer | The value to set for this project's |
|
||||
| | | CA quota. |
|
||||
+----------------+---------+----------------------------------------------+
|
||||
|
||||
Configured project quota values are specified as follows:
|
||||
|
||||
|
|
|
@ -216,6 +216,8 @@ quota_transport_keys = -1
|
|||
# default number of consumers allowed per project
|
||||
quota_consumers = -1
|
||||
|
||||
# default number of CAs allowed per project
|
||||
quota_cas = -1
|
||||
|
||||
# ================= Keystone Notification Options - Application ===============
|
||||
|
||||
|
|
|
@ -98,3 +98,30 @@ class CABehaviors(base_behaviors.BaseBehaviors):
|
|||
admin = user_name
|
||||
self.created_entities.append((ca_ref, admin))
|
||||
return resp, ca_ref
|
||||
|
||||
def delete_ca(self, ca_ref, extra_headers=None,
|
||||
expected_fail=False, use_auth=True, user_name=None):
|
||||
"""Delete a secret.
|
||||
|
||||
:param ca_ref: HATEOS ref of the secret to be deleted
|
||||
:param extra_headers: Optional HTTP headers to add to the request
|
||||
:param expected_fail: If test is expected to fail the deletion
|
||||
:param use_auth: Boolean for whether to send authentication headers
|
||||
:param user_name: The user name used to delete the entity
|
||||
:return A request response object
|
||||
"""
|
||||
resp = self.client.delete(ca_ref, 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] == ca_ref:
|
||||
self.created_entities.remove(item)
|
||||
|
||||
return resp
|
||||
|
||||
def delete_all_created_cas(self):
|
||||
"""Delete all of the cas that we have created."""
|
||||
entities = list(self.created_entities)
|
||||
for (ca_ref, admin) in entities:
|
||||
self.delete_ca(ca_ref, user_name=admin)
|
||||
|
|
|
@ -90,6 +90,8 @@ class CertificateAuthoritiesTestCase(base.TestCase):
|
|||
self.send_test_order()
|
||||
|
||||
def tearDown(self):
|
||||
self.order_behaviors.delete_all_created_orders()
|
||||
self.ca_behaviors.delete_all_created_cas()
|
||||
super(CertificateAuthoritiesTestCase, self).tearDown()
|
||||
|
||||
def get_signing_cert(self, ca_ref):
|
||||
|
|
|
@ -54,6 +54,7 @@ class QuotasTestCase(base.TestCase):
|
|||
self.assertEqual(-1, resp.model.quotas.orders)
|
||||
self.assertEqual(-1, resp.model.quotas.containers)
|
||||
self.assertEqual(-1, resp.model.quotas.consumers)
|
||||
self.assertEqual(-1, resp.model.quotas.cas)
|
||||
|
||||
def test_get_project_quotas_by_project_id(self):
|
||||
"""Get project quota information for specific project"""
|
||||
|
@ -73,6 +74,7 @@ class QuotasTestCase(base.TestCase):
|
|||
self.assertEqual(10, resp.model.project_quotas.orders)
|
||||
self.assertEqual(20, resp.model.project_quotas.containers)
|
||||
self.assertIsNone(resp.model.project_quotas.consumers)
|
||||
self.assertIsNone(resp.model.project_quotas.cas)
|
||||
|
||||
def test_get_project_quotas_by_project_id_not_found(self):
|
||||
"""Get project quota information for specific project"""
|
||||
|
|
|
@ -13,14 +13,21 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from testtools import testcase
|
||||
import base64
|
||||
import testtools
|
||||
|
||||
from barbican.common import hrefs
|
||||
from barbican.plugin.interface import certificate_manager as cert_interface
|
||||
from barbican.tests import certificate_utils as certutil
|
||||
|
||||
from functionaltests.api import base
|
||||
from functionaltests.api.v1.behaviors import ca_behaviors
|
||||
from functionaltests.api.v1.behaviors import consumer_behaviors
|
||||
from functionaltests.api.v1.behaviors import container_behaviors
|
||||
from functionaltests.api.v1.behaviors import order_behaviors
|
||||
from functionaltests.api.v1.behaviors import quota_behaviors
|
||||
from functionaltests.api.v1.behaviors import secret_behaviors
|
||||
from functionaltests.api.v1.models import ca_models
|
||||
from functionaltests.api.v1.models import consumer_model
|
||||
from functionaltests.api.v1.models import container_models
|
||||
from functionaltests.api.v1.models import order_models
|
||||
|
@ -34,7 +41,12 @@ admin_b = CONF.rbac_users.admin_b
|
|||
service_admin = CONF.identity.service_admin
|
||||
|
||||
|
||||
@testcase.attr('no_parallel')
|
||||
def is_ca_backend_snakeoil():
|
||||
return 'snakeoil_ca' in\
|
||||
cert_interface.CONF.certificate.enabled_certificate_plugins
|
||||
|
||||
|
||||
@testtools.testcase.attr('no_parallel')
|
||||
class QuotaEnforcementTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -46,18 +58,22 @@ class QuotaEnforcementTestCase(base.TestCase):
|
|||
self.order_behaviors = order_behaviors.OrderBehaviors(self.client)
|
||||
self.consumer_behaviors = consumer_behaviors.ConsumerBehaviors(
|
||||
self.client)
|
||||
self.ca_behaviors = ca_behaviors.CABehaviors(self.client)
|
||||
|
||||
self.secret_data = self.get_default_secret_data()
|
||||
self.quota_data = self.get_default_quota_data()
|
||||
self.project_id = self.quota_behaviors.get_project_id_from_name(
|
||||
admin_b)
|
||||
self.order_secrets = []
|
||||
self.root_ca_ref = None
|
||||
self.test_order_sent = False
|
||||
|
||||
def tearDown(self):
|
||||
self.quota_behaviors.delete_all_created_quotas()
|
||||
self.consumer_behaviors.delete_all_created_consumers()
|
||||
self.container_behaviors.delete_all_created_containers()
|
||||
self.secret_behaviors.delete_all_created_secrets()
|
||||
self.ca_behaviors.delete_all_created_cas()
|
||||
for secret_ref in self.order_secrets:
|
||||
resp = self.secret_behaviors.delete_secret(
|
||||
secret_ref, user_name=admin_b)
|
||||
|
@ -137,6 +153,32 @@ class QuotaEnforcementTestCase(base.TestCase):
|
|||
self.create_consumers(count=5)
|
||||
self.create_consumers(expected_return=403)
|
||||
|
||||
@testtools.skipIf(not is_ca_backend_snakeoil(),
|
||||
"This test is only usable with snakeoil")
|
||||
def test_snakeoil_cas_unlimited(self):
|
||||
self.set_quotas('cas', -1)
|
||||
self.create_snakeoil_cas(count=5)
|
||||
|
||||
@testtools.skipIf(not is_ca_backend_snakeoil(),
|
||||
"This test is only usable with snakeoil")
|
||||
def test_snakeoil_cas_disabled(self):
|
||||
self.set_quotas('cas', 0)
|
||||
self.create_snakeoil_cas(expected_return=403)
|
||||
|
||||
@testtools.skipIf(not is_ca_backend_snakeoil(),
|
||||
"This test is only usable with snakeoil")
|
||||
def test_snakeoil_cas_limited_one(self):
|
||||
self.set_quotas('cas', 1)
|
||||
self.create_snakeoil_cas(count=1)
|
||||
self.create_snakeoil_cas(expected_return=403)
|
||||
|
||||
@testtools.skipIf(not is_ca_backend_snakeoil(),
|
||||
"This test is only usable with snakeoil")
|
||||
def test_snakeoil_cas_limited_five(self):
|
||||
self.set_quotas('cas', 5)
|
||||
self.create_snakeoil_cas(count=5)
|
||||
self.create_snakeoil_cas(expected_return=403)
|
||||
|
||||
# ----------------------- Helper Functions ---------------------------
|
||||
|
||||
def get_default_quota_data(self):
|
||||
|
@ -230,3 +272,63 @@ class QuotaEnforcementTestCase(base.TestCase):
|
|||
resp, consumer_dat = self.consumer_behaviors.create_consumer(
|
||||
model, container_ref, user_name=admin_b)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
||||
def get_order_simple_cmc_request_data(self):
|
||||
return {
|
||||
'type': 'certificate',
|
||||
'meta': {
|
||||
'request_type': 'simple-cmc',
|
||||
'requestor_name': 'Barbican User',
|
||||
'requestor_email': 'user@example.com',
|
||||
'requestor_phone': '555-1212'
|
||||
}
|
||||
}
|
||||
|
||||
def send_test_order(self, ca_ref=None):
|
||||
if self.test_order_sent:
|
||||
return
|
||||
self.test_order_sent = True
|
||||
test_model = order_models.OrderModel(
|
||||
**self.get_order_simple_cmc_request_data())
|
||||
test_model.meta['request_data'] = base64.b64encode(
|
||||
certutil.create_good_csr())
|
||||
if ca_ref is not None:
|
||||
ca_id = hrefs.get_ca_id_from_ref(ca_ref)
|
||||
test_model.meta['ca_id'] = ca_id
|
||||
|
||||
create_resp, order_ref = self.order_behaviors.create_order(test_model)
|
||||
self.assertEqual(202, create_resp.status_code)
|
||||
self.assertIsNotNone(order_ref)
|
||||
|
||||
def get_root_ca_ref(self):
|
||||
self.send_test_order()
|
||||
if self.root_ca_ref is not None:
|
||||
return self.root_ca_ref
|
||||
|
||||
(resp, cas, total, next_ref, prev_ref) = self.ca_behaviors.get_cas()
|
||||
snake_name = 'barbican.plugin.snakeoil_ca.SnakeoilCACertificatePlugin'
|
||||
snake_plugin_ca_id = "Snakeoil CA"
|
||||
|
||||
for item in cas:
|
||||
ca = self.ca_behaviors.get_ca(item)
|
||||
if ca.model.plugin_name == snake_name:
|
||||
if ca.model.plugin_ca_id == snake_plugin_ca_id:
|
||||
return item
|
||||
return None
|
||||
|
||||
def get_snakeoil_subca_model(self):
|
||||
parent_ca_ref = self.get_root_ca_ref()
|
||||
return ca_models.CAModel(
|
||||
parent_ca_ref=parent_ca_ref,
|
||||
description="Test Snake Oil Subordinate CA",
|
||||
name="Subordinate CA",
|
||||
subject_dn="CN=Subordinate CA, O=example.com"
|
||||
)
|
||||
|
||||
def create_snakeoil_cas(self, count=1, expected_return=201):
|
||||
"""Utility function to create snakeoil cas"""
|
||||
for _ in range(count):
|
||||
ca_model = self.get_snakeoil_subca_model()
|
||||
resp, ca_ref = self.ca_behaviors.create_ca(ca_model,
|
||||
user_name=admin_b)
|
||||
self.assertEqual(expected_return, resp.status_code)
|
||||
|
|
|
@ -20,12 +20,13 @@ from functionaltests.api.v1.models.base_models import BaseModel
|
|||
class QuotasModel(BaseModel):
|
||||
|
||||
def __init__(self, secrets=None, orders=None, containers=None,
|
||||
consumers=None):
|
||||
consumers=None, cas=None):
|
||||
super(QuotasModel, self).__init__()
|
||||
self.secrets = secrets
|
||||
self.orders = orders
|
||||
self.containers = containers
|
||||
self.consumers = consumers
|
||||
self.cas = cas
|
||||
|
||||
|
||||
class QuotasResponseModel(BaseModel):
|
||||
|
|
Loading…
Reference in New Issue