diff --git a/cinder/api/contrib/quota_classes.py b/cinder/api/contrib/quota_classes.py index a694d856cb7..bffd1a56fd5 100644 --- a/cinder/api/contrib/quota_classes.py +++ b/cinder/api/contrib/quota_classes.py @@ -25,6 +25,7 @@ from cinder import utils QUOTAS = quota.QUOTAS +GROUP_QUOTAS = quota.GROUP_QUOTAS authorize = extensions.extension_authorizer('volume', 'quota_classes') @@ -46,9 +47,11 @@ class QuotaClassSetsController(wsgi.Controller): db.sqlalchemy.api.authorize_quota_class_context(context, id) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() + quota_set = QUOTAS.get_class_quotas(context, id) + group_quota_set = GROUP_QUOTAS.get_class_quotas(context, id) + quota_set.update(group_quota_set) - return self._format_quota_set(id, - QUOTAS.get_class_quotas(context, id)) + return self._format_quota_set(id, quota_set) def update(self, req, id, body): context = req.environ['cinder.context'] @@ -63,7 +66,7 @@ class QuotaClassSetsController(wsgi.Controller): raise webob.exc.HTTPBadRequest(explanation=msg) for key, value in body['quota_class_set'].items(): - if key in QUOTAS: + if key in QUOTAS or key in GROUP_QUOTAS: try: value = utils.validate_integer(value, key, min_value=-1, max_value=db.MAX_INT) @@ -72,8 +75,12 @@ class QuotaClassSetsController(wsgi.Controller): db.quota_class_create(context, quota_class, key, value) except exception.AdminRequired: raise webob.exc.HTTPForbidden() - return {'quota_class_set': QUOTAS.get_class_quotas(context, - quota_class)} + + quota_set = QUOTAS.get_class_quotas(context, quota_class) + group_quota_set = GROUP_QUOTAS.get_class_quotas(context, quota_class) + quota_set.update(group_quota_set) + + return {'quota_class_set': quota_set} class Quota_classes(extensions.ExtensionDescriptor): diff --git a/cinder/api/contrib/quotas.py b/cinder/api/contrib/quotas.py index 1dbe8201f32..334426eed08 100644 --- a/cinder/api/contrib/quotas.py +++ b/cinder/api/contrib/quotas.py @@ -28,6 +28,7 @@ from cinder import quota_utils from cinder import utils QUOTAS = quota.QUOTAS +GROUP_QUOTAS = quota.GROUP_QUOTAS NON_QUOTA_KEYS = ['tenant_id', 'id'] authorize_update = extensions.extension_authorizer('volume', 'quotas:update') @@ -63,6 +64,9 @@ class QuotaSetsController(wsgi.Controller): def _get_quotas(self, context, id, usages=False): values = QUOTAS.get_project_quotas(context, id, usages=usages) + group_values = GROUP_QUOTAS.get_project_quotas(context, id, + usages=usages) + values.update(group_values) if usages: return values @@ -225,7 +229,8 @@ class QuotaSetsController(wsgi.Controller): # NOTE(ankit): Pass #1 - In this loop for body['quota_set'].items(), # we figure out if we have any bad keys. for key, value in body['quota_set'].items(): - if (key not in QUOTAS and key not in NON_QUOTA_KEYS): + if (key not in QUOTAS and key not in GROUP_QUOTAS and key not in + NON_QUOTA_KEYS): bad_keys.append(key) continue @@ -259,6 +264,10 @@ class QuotaSetsController(wsgi.Controller): # resources. quota_values = QUOTAS.get_project_quotas(context, target_project_id, defaults=False) + group_quota_values = GROUP_QUOTAS.get_project_quotas(context, + target_project_id, + defaults=False) + quota_values.update(group_quota_values) valid_quotas = {} reservations = [] for key in body['quota_set'].keys(): @@ -326,17 +335,20 @@ class QuotaSetsController(wsgi.Controller): res_change = new_quota_from_target_proj - orig_quota_from_target_proj if res_change != 0: deltas = {res: res_change} + resources = QUOTAS.resources + resources.update(GROUP_QUOTAS.resources) reservations += quota_utils.update_alloc_to_next_hard_limit( - ctxt, QUOTAS.resources, deltas, res, None, target_project.id) + ctxt, resources, deltas, res, None, target_project.id) return reservations def defaults(self, req, id): context = req.environ['cinder.context'] authorize_show(context) - - return self._format_quota_set(id, QUOTAS.get_defaults( - context, project_id=id)) + defaults = QUOTAS.get_defaults(context, project_id=id) + group_defaults = GROUP_QUOTAS.get_defaults(context, project_id=id) + defaults.update(group_defaults) + return self._format_quota_set(id, defaults) def delete(self, req, id): """Delete Quota for a particular tenant. @@ -366,6 +378,9 @@ class QuotaSetsController(wsgi.Controller): try: project_quotas = QUOTAS.get_project_quotas( ctxt, proj_id, usages=True, defaults=False) + project_group_quotas = GROUP_QUOTAS.get_project_quotas( + ctxt, proj_id, usages=True, defaults=False) + project_quotas.update(project_group_quotas) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() @@ -382,6 +397,7 @@ class QuotaSetsController(wsgi.Controller): parent_id) defaults = QUOTAS.get_defaults(ctxt, proj_id) + defaults.update(GROUP_QUOTAS.get_defaults(ctxt, proj_id)) # If the project which is being deleted has allocated part of its # quota to its subprojects, then subprojects' quotas should be # deleted first. @@ -416,8 +432,11 @@ class QuotaSetsController(wsgi.Controller): ctxt = req.environ['cinder.context'] params = req.params try: + resources = QUOTAS.resources + resources.update(GROUP_QUOTAS.resources) + quota_utils.validate_setup_for_nested_quota_use( - ctxt, QUOTAS.resources, quota.NestedDbQuotaDriver(), + ctxt, resources, quota.NestedDbQuotaDriver(), fix_allocated_quotas=params.get('fix_allocated_quotas')) except exception.InvalidNestedQuotaSetup as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) diff --git a/cinder/quota_utils.py b/cinder/quota_utils.py index eee4ddf2809..0425b6f0c00 100644 --- a/cinder/quota_utils.py +++ b/cinder/quota_utils.py @@ -147,6 +147,7 @@ def update_alloc_to_next_hard_limit(context, resources, deltas, res, expire, project_id): from cinder import quota QUOTAS = quota.QUOTAS + GROUP_QUOTAS = quota.GROUP_QUOTAS reservations = [] projects = get_project_hierarchy(context, project_id, parents_as_ids=True).parents @@ -156,8 +157,12 @@ def update_alloc_to_next_hard_limit(context, resources, deltas, res, while projects and not hard_limit_found: cur_proj_id = list(projects)[0] projects = projects[cur_proj_id] - cur_quota_lim = QUOTAS.get_by_project_or_default( - context, cur_proj_id, res) + if res == 'groups': + cur_quota_lim = GROUP_QUOTAS.get_by_project_or_default( + context, cur_proj_id, res) + else: + cur_quota_lim = QUOTAS.get_by_project_or_default( + context, cur_proj_id, res) hard_limit_found = (cur_quota_lim != -1) cur_quota = {res: cur_quota_lim} cur_delta = {res: deltas[res]} diff --git a/cinder/tests/unit/api/contrib/test_quotas.py b/cinder/tests/unit/api/contrib/test_quotas.py index cf499136f3f..8753fa476e6 100644 --- a/cinder/tests/unit/api/contrib/test_quotas.py +++ b/cinder/tests/unit/api/contrib/test_quotas.py @@ -43,13 +43,14 @@ CONF = cfg.CONF def make_body(root=True, gigabytes=1000, snapshots=10, volumes=10, backups=10, backup_gigabytes=1000, - tenant_id=fake.PROJECT_ID, per_volume_gigabytes=-1): + tenant_id=fake.PROJECT_ID, per_volume_gigabytes=-1, groups=10): resources = {'gigabytes': gigabytes, 'snapshots': snapshots, 'volumes': volumes, 'backups': backups, 'backup_gigabytes': backup_gigabytes, - 'per_volume_gigabytes': per_volume_gigabytes, } + 'per_volume_gigabytes': per_volume_gigabytes, + 'groups': groups} # need to consider preexisting volume types as well volume_types = db.volume_type_get_all(context.get_admin_context()) diff --git a/cinder/tests/unit/api/contrib/test_quotas_classes.py b/cinder/tests/unit/api/contrib/test_quotas_classes.py index 945f693cb00..d0b15870227 100644 --- a/cinder/tests/unit/api/contrib/test_quotas_classes.py +++ b/cinder/tests/unit/api/contrib/test_quotas_classes.py @@ -33,19 +33,21 @@ from cinder.volume import volume_types QUOTAS = quota.QUOTAS +GROUP_QUOTAS = quota.GROUP_QUOTAS def make_body(root=True, gigabytes=1000, snapshots=10, volumes=10, backups=10, backup_gigabytes=1000, per_volume_gigabytes=-1, volume_types_faked=None, - tenant_id=fake.PROJECT_ID): + tenant_id=fake.PROJECT_ID, groups=10): resources = {'gigabytes': gigabytes, 'snapshots': snapshots, 'volumes': volumes, 'backups': backups, 'per_volume_gigabytes': per_volume_gigabytes, - 'backup_gigabytes': backup_gigabytes} + 'backup_gigabytes': backup_gigabytes, + 'groups': groups} if not volume_types_faked: volume_types_faked = {'fake_type': None} for volume_type in volume_types_faked: @@ -68,6 +70,7 @@ def make_response_body(root=True, ctxt=None, quota_class='foo', if not ctxt: ctxt = context.get_admin_context() resources.update(QUOTAS.get_class_quotas(ctxt, quota_class)) + resources.update(GROUP_QUOTAS.get_class_quotas(ctxt, quota_class)) if not request_body and not request_body['quota_class_set']: resources.update(request_body['quota_class_set']) diff --git a/releasenotes/notes/generic-group-quota-manage-support-559629ad07a406f4.yaml b/releasenotes/notes/generic-group-quota-manage-support-559629ad07a406f4.yaml new file mode 100644 index 00000000000..cc058ac783c --- /dev/null +++ b/releasenotes/notes/generic-group-quota-manage-support-559629ad07a406f4.yaml @@ -0,0 +1,3 @@ +--- +features: + - Generic group is added into quota management.