diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 6e3d97ad46..b8887895d9 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -1286,6 +1286,34 @@ quota-health_monitor-optional: in: body required: false type: integer +quota-l7policy: + description: | + The configured l7policy quota limit. A setting of ``null`` means it is + using the deployment default quota. A setting of ``-1`` means unlimited. + in: body + required: true + type: integer +quota-l7policy-optional: + description: | + The configured l7policy quota limit. A setting of ``null`` means it is + using the deployment default quota. A setting of ``-1`` means unlimited. + in: body + required: false + type: integer +quota-l7rule: + description: | + The configured l7rule quota limit. A setting of ``null`` means it is + using the deployment default quota. A setting of ``-1`` means unlimited. + in: body + required: true + type: integer +quota-l7rule-optional: + description: | + The configured l7rule quota limit. A setting of ``null`` means it is + using the deployment default quota. A setting of ``-1`` means unlimited. + in: body + required: false + type: integer quota-listener: description: | The configured listener quota limit. A setting of ``null`` means it is diff --git a/api-ref/source/v2/examples/quota-update-curl b/api-ref/source/v2/examples/quota-update-curl index 5a6c70f30c..dc348632d4 100644 --- a/api-ref/source/v2/examples/quota-update-curl +++ b/api-ref/source/v2/examples/quota-update-curl @@ -1 +1 @@ -curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"quota":{"loadbalancer":10,"listener":-1,"member":50,"pool":-1,"healthmonitor":-1}}' http://198.51.100.10:9876/v2/lbaas/quotas/e3cd678b11784734bc366148aa37580e +curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"quota":{"loadbalancer":10,"listener":-1,"member":50,"pool":-1,"healthmonitor":-1,"l7policy":15,"l7rule":25}}' http://198.51.100.10:9876/v2/lbaas/quotas/e3cd678b11784734bc366148aa37580e diff --git a/api-ref/source/v2/examples/quota-update-request.json b/api-ref/source/v2/examples/quota-update-request.json index 26eb1636c9..56387b880c 100644 --- a/api-ref/source/v2/examples/quota-update-request.json +++ b/api-ref/source/v2/examples/quota-update-request.json @@ -4,6 +4,8 @@ "listener": -1, "member": 50, "pool": -1, - "healthmonitor": -1 + "healthmonitor": -1, + "l7policy": 15, + "l7rule": 25 } } diff --git a/api-ref/source/v2/examples/quota-update-response.json b/api-ref/source/v2/examples/quota-update-response.json index 26eb1636c9..56387b880c 100644 --- a/api-ref/source/v2/examples/quota-update-response.json +++ b/api-ref/source/v2/examples/quota-update-response.json @@ -4,6 +4,8 @@ "listener": -1, "member": 50, "pool": -1, - "healthmonitor": -1 + "healthmonitor": -1, + "l7policy": 15, + "l7rule": 25 } } diff --git a/api-ref/source/v2/examples/quotas-defaults-response.json b/api-ref/source/v2/examples/quotas-defaults-response.json index 700bee58bb..fd2a432acf 100644 --- a/api-ref/source/v2/examples/quotas-defaults-response.json +++ b/api-ref/source/v2/examples/quotas-defaults-response.json @@ -4,6 +4,8 @@ "listener": -1, "member": -1, "pool": -1, - "healthmonitor": -1 + "healthmonitor": -1, + "l7policy": -1, + "l7rule": -1 } } diff --git a/api-ref/source/v2/examples/quotas-list-response.json b/api-ref/source/v2/examples/quotas-list-response.json index 91af74dc9e..ae1f615d98 100644 --- a/api-ref/source/v2/examples/quotas-list-response.json +++ b/api-ref/source/v2/examples/quotas-list-response.json @@ -6,7 +6,9 @@ "healthmonitor": -1, "listener": null, "project_id": "e3cd678b11784734bc366148aa37580e", - "pool": null + "pool": null, + "l7policy": 3, + "l7rule": null } ] } diff --git a/api-ref/source/v2/examples/quotas-show-response.json b/api-ref/source/v2/examples/quotas-show-response.json index 3b05742617..a03731dd4b 100644 --- a/api-ref/source/v2/examples/quotas-show-response.json +++ b/api-ref/source/v2/examples/quotas-show-response.json @@ -4,6 +4,8 @@ "listener": -1, "member": 50, "pool": -1, - "healthmonitor": -1 + "healthmonitor": -1, + "l7policy": 20, + "l7rule": -1 } } diff --git a/api-ref/source/v2/quota.inc b/api-ref/source/v2/quota.inc index 26ed870e77..16929ca864 100644 --- a/api-ref/source/v2/quota.inc +++ b/api-ref/source/v2/quota.inc @@ -51,6 +51,8 @@ Response Parameters .. rest_parameters:: ../parameters.yaml - healthmonitor: quota-health_monitor + - l7policy: quota-l7policy + - l7rule: quota-l7rule - listener: quota-listener - loadbalancer: quota-load_balancer - member: quota-member @@ -99,6 +101,8 @@ Response Parameters .. rest_parameters:: ../parameters.yaml - healthmonitor: quota-health_monitor + - l7policy: quota-l7policy + - l7rule: quota-l7rule - listener: quota-listener - loadbalancer: quota-load_balancer - member: quota-member @@ -156,6 +160,8 @@ Response Parameters .. rest_parameters:: ../parameters.yaml - healthmonitor: quota-health_monitor + - l7policy: quota-l7policy + - l7rule: quota-l7rule - listener: quota-listener - loadbalancer: quota-load_balancer - member: quota-member @@ -206,6 +212,8 @@ Request .. rest_parameters:: ../parameters.yaml - healthmonitor: quota-health_monitor-optional + - l7policy: quota-l7policy-optional + - l7rule: quota-l7rule-optional - listener: quota-listener-optional - loadbalancer: quota-load_balancer-optional - member: quota-member-optional @@ -230,6 +238,8 @@ Response Parameters .. rest_parameters:: ../parameters.yaml - healthmonitor: quota-health_monitor + - l7policy: quota-l7policy + - l7rule: quota-l7rule - listener: quota-listener - loadbalancer: quota-load_balancer - member: quota-member diff --git a/etc/octavia.conf b/etc/octavia.conf index 906198913d..502260eeb0 100644 --- a/etc/octavia.conf +++ b/etc/octavia.conf @@ -588,6 +588,8 @@ # default_member_quota = -1 # default_pool_quota = -1 # default_health_monitor_quota = -1 +# default_l7policy_quota = -1 +# default_l7rule_quota = -1 [audit] # Enable auditing of API requests. diff --git a/octavia/api/root_controller.py b/octavia/api/root_controller.py index 1b407143ab..9a3f50042b 100644 --- a/octavia/api/root_controller.py +++ b/octavia/api/root_controller.py @@ -119,6 +119,9 @@ class RootController(object): self._add_a_version(versions, 'v2.17', 'v2', 'SUPPORTED', '2020-04-29T00:00:00Z', host_url) # Pool TLS versions - self._add_a_version(versions, 'v2.18', 'v2', 'CURRENT', + self._add_a_version(versions, 'v2.18', 'v2', 'SUPPORTED', '2020-04-29T01:00:00Z', host_url) + # Add quota support to octavia's l7policy and l7rule + self._add_a_version(versions, 'v2.19', 'v2', 'CURRENT', + '2020-05-12T00:00:00Z', host_url) return {'versions': versions} diff --git a/octavia/api/v2/controllers/base.py b/octavia/api/v2/controllers/base.py index 633183bc05..4bad735b92 100644 --- a/octavia/api/v2/controllers/base.py +++ b/octavia/api/v2/controllers/base.py @@ -184,7 +184,9 @@ class BaseController(pecan_rest.RestController): listener=CONF.quotas.default_listener_quota, pool=CONF.quotas.default_pool_quota, health_monitor=CONF.quotas.default_health_monitor_quota, - member=CONF.quotas.default_member_quota) + member=CONF.quotas.default_member_quota, + l7policy=CONF.quotas.default_l7policy_quota, + l7rule=CONF.quotas.default_l7rule_quota) return quotas def _get_db_quotas(self, session, project_id): @@ -213,6 +215,10 @@ class BaseController(pecan_rest.RestController): default_health_monitor_quota) if db_quotas.member is None: db_quotas.member = CONF.quotas.default_member_quota + if db_quotas.l7policy is None: + db_quotas.l7policy = CONF.quotas.default_l7policy_quota + if db_quotas.l7rule is None: + db_quotas.l7rule = CONF.quotas.default_l7rule_quota return db_quotas def _auth_get_all(self, context, project_id): diff --git a/octavia/api/v2/controllers/l7rule.py b/octavia/api/v2/controllers/l7rule.py index fda37097e4..1e11c4226c 100644 --- a/octavia/api/v2/controllers/l7rule.py +++ b/octavia/api/v2/controllers/l7rule.py @@ -146,6 +146,14 @@ class L7RuleController(base.BaseController): lock_session = db_api.get_session(autocommit=False) try: + if self.repositories.check_quota_met( + context.session, + lock_session, + data_models.L7Rule, + l7rule.project_id): + raise exceptions.QuotaException( + resource=data_models.L7Rule._name()) + l7rule_dict = db_prepare.create_l7rule( l7rule.to_dict(render_unsets=True), self.l7policy_id) diff --git a/octavia/api/v2/types/quotas.py b/octavia/api/v2/types/quotas.py index 08bff46c26..3b24490023 100644 --- a/octavia/api/v2/types/quotas.py +++ b/octavia/api/v2/types/quotas.py @@ -36,6 +36,10 @@ class QuotaBase(base.BaseType): # Misspelled version, deprecated in Rocky health_monitor = wtypes.wsattr(wtypes.IntegerType( minimum=consts.MIN_QUOTA, maximum=consts.MAX_QUOTA)) + l7policy = wtypes.wsattr(wtypes.IntegerType( + minimum=consts.MIN_QUOTA, maximum=consts.MAX_QUOTA)) + l7rule = wtypes.wsattr(wtypes.IntegerType( + minimum=consts.MIN_QUOTA, maximum=consts.MAX_QUOTA)) def to_dict(self, render_unsets=False): quota_dict = super(QuotaBase, self).to_dict(render_unsets) @@ -70,6 +74,8 @@ class QuotaAllBase(base.BaseType): healthmonitor = wtypes.wsattr(wtypes.IntegerType()) # Misspelled version, deprecated in Rocky, remove in T health_monitor = wtypes.wsattr(wtypes.IntegerType()) + l7policy = wtypes.wsattr(wtypes.IntegerType()) + l7rule = wtypes.wsattr(wtypes.IntegerType()) _type_to_model_map = {'loadbalancer': 'load_balancer', 'healthmonitor': 'health_monitor'} diff --git a/octavia/common/config.py b/octavia/common/config.py index a0c6a1c4e9..326d974304 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -721,6 +721,12 @@ quota_opts = [ cfg.IntOpt('default_health_monitor_quota', default=constants.QUOTA_UNLIMITED, help=_('Default per project health monitor quota.')), + cfg.IntOpt('default_l7policy_quota', + default=constants.QUOTA_UNLIMITED, + help=_('Default per project l7policy quota.')), + cfg.IntOpt('default_l7rule_quota', + default=constants.QUOTA_UNLIMITED, + help=_('Default per project l7rule quota.')), ] audit_opts = [ diff --git a/octavia/common/data_models.py b/octavia/common/data_models.py index 0e465d7c2f..4631d5e103 100644 --- a/octavia/common/data_models.py +++ b/octavia/common/data_models.py @@ -748,22 +748,30 @@ class Quotas(BaseDataModel): pool=None, health_monitor=None, member=None, + l7policy=None, + l7rule=None, in_use_health_monitor=None, in_use_listener=None, in_use_load_balancer=None, in_use_member=None, - in_use_pool=None): + in_use_pool=None, + in_use_l7policy=None, + in_use_l7rule=None): self.project_id = project_id self.health_monitor = health_monitor self.listener = listener self.load_balancer = load_balancer self.pool = pool self.member = member + self.l7policy = l7policy + self.l7rule = l7rule self.in_use_health_monitor = in_use_health_monitor self.in_use_listener = in_use_listener self.in_use_load_balancer = in_use_load_balancer self.in_use_member = in_use_member self.in_use_pool = in_use_pool + self.in_use_l7policy = in_use_l7policy + self.in_use_l7rule = in_use_l7rule class Flavor(BaseDataModel): diff --git a/octavia/controller/worker/v1/flows/l7policy_flows.py b/octavia/controller/worker/v1/flows/l7policy_flows.py index a74dfb55e0..3d71a5e827 100644 --- a/octavia/controller/worker/v1/flows/l7policy_flows.py +++ b/octavia/controller/worker/v1/flows/l7policy_flows.py @@ -63,6 +63,8 @@ class L7PolicyFlows(object): requires=constants.LOADBALANCER)) delete_l7policy_flow.add(database_tasks.DeleteL7PolicyInDB( requires=constants.L7POLICY)) + delete_l7policy_flow.add(database_tasks.DecrementL7policyQuota( + requires=constants.L7POLICY)) delete_l7policy_flow.add(database_tasks.MarkLBAndListenersActiveInDB( requires=[constants.LOADBALANCER, constants.LISTENERS])) diff --git a/octavia/controller/worker/v1/flows/l7rule_flows.py b/octavia/controller/worker/v1/flows/l7rule_flows.py index 2cb234ea85..c33e9e99bf 100644 --- a/octavia/controller/worker/v1/flows/l7rule_flows.py +++ b/octavia/controller/worker/v1/flows/l7rule_flows.py @@ -65,6 +65,8 @@ class L7RuleFlows(object): requires=constants.LOADBALANCER)) delete_l7rule_flow.add(database_tasks.DeleteL7RuleInDB( requires=constants.L7RULE)) + delete_l7rule_flow.add(database_tasks.DecrementL7ruleQuota( + requires=constants.L7RULE)) delete_l7rule_flow.add(database_tasks.MarkL7PolicyActiveInDB( requires=constants.L7POLICY)) delete_l7rule_flow.add(database_tasks.MarkLBAndListenersActiveInDB( diff --git a/octavia/controller/worker/v1/tasks/database_tasks.py b/octavia/controller/worker/v1/tasks/database_tasks.py index dd8a932a03..8afd76246d 100644 --- a/octavia/controller/worker/v1/tasks/database_tasks.py +++ b/octavia/controller/worker/v1/tasks/database_tasks.py @@ -2671,6 +2671,142 @@ class CountPoolChildrenForQuota(BaseDatabaseTask): return {'HM': health_mon_count, 'member': member_count} +class DecrementL7policyQuota(BaseDatabaseTask): + """Decrements the l7policy quota for a project. + + Since sqlalchemy will likely retry by itself always revert if it fails + """ + + def execute(self, l7policy): + """Decrements the l7policy quota. + + :param l7policy: The l7policy to decrement the quota on. + :returns: None + """ + + LOG.debug("Decrementing l7policy quota for " + "project: %s ", l7policy.project_id) + + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.decrement_quota(lock_session, + data_models.L7Policy, + l7policy.project_id) + + if l7policy.l7rules: + self.repos.decrement_quota(lock_session, + data_models.L7Rule, + l7policy.project_id, + quantity=len(l7policy.l7rules)) + lock_session.commit() + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error('Failed to decrement l7policy quota for project: ' + '%(proj)s the project may have excess quota in use.', + {'proj': l7policy.project_id}) + lock_session.rollback() + + def revert(self, l7policy, result, *args, **kwargs): + """Re-apply the quota + + :param l7policy: The l7policy to decrement the quota on. + :returns: None + """ + + LOG.warning('Reverting decrement quota for l7policy on project' + ' %(proj)s Project quota counts may be incorrect.', + {'proj': l7policy.project_id}) + + # Increment the quota back if this task wasn't the failure + if not isinstance(result, failure.Failure): + + try: + session = db_apis.get_session() + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Policy, + l7policy.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + + # Attempt to increment back the L7Rule quota + for i in range(len(l7policy.l7rules)): + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Rule, + l7policy.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + except Exception: + # Don't fail the revert flow + pass + + +class DecrementL7ruleQuota(BaseDatabaseTask): + """Decrements the l7rule quota for a project. + + Since sqlalchemy will likely retry by itself always revert if it fails + """ + + def execute(self, l7rule): + """Decrements the l7rule quota. + + :param l7rule: The l7rule to decrement the quota on. + :returns: None + """ + + LOG.debug("Decrementing l7rule quota for " + "project: %s ", l7rule.project_id) + + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.decrement_quota(lock_session, + data_models.L7Rule, + l7rule.project_id) + lock_session.commit() + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error('Failed to decrement l7rule quota for project: ' + '%(proj)s the project may have excess quota in use.', + {'proj': l7rule.project_id}) + lock_session.rollback() + + def revert(self, l7rule, result, *args, **kwargs): + """Re-apply the quota + + :param l7rule: The l7rule to decrement the quota on. + :returns: None + """ + + LOG.warning('Reverting decrement quota for l7rule on project %(proj)s ' + 'Project quota counts may be incorrect.', + {'proj': l7rule.project_id}) + + # Increment the quota back if this task wasn't the failure + if not isinstance(result, failure.Failure): + + try: + session = db_apis.get_session() + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Rule, + l7rule.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + except Exception: + # Don't fail the revert flow + pass + + class UpdatePoolMembersOperatingStatusInDB(BaseDatabaseTask): """Updates the members of a pool operating status. diff --git a/octavia/controller/worker/v2/flows/l7policy_flows.py b/octavia/controller/worker/v2/flows/l7policy_flows.py index 7b74114b8f..9b683e0088 100644 --- a/octavia/controller/worker/v2/flows/l7policy_flows.py +++ b/octavia/controller/worker/v2/flows/l7policy_flows.py @@ -60,6 +60,8 @@ class L7PolicyFlows(object): requires=constants.LOADBALANCER_ID)) delete_l7policy_flow.add(database_tasks.DeleteL7PolicyInDB( requires=constants.L7POLICY)) + delete_l7policy_flow.add(database_tasks.DecrementL7policyQuota( + requires=constants.L7POLICY)) delete_l7policy_flow.add(database_tasks.MarkLBAndListenersActiveInDB( requires=(constants.LOADBALANCER_ID, constants.LISTENERS))) diff --git a/octavia/controller/worker/v2/flows/l7rule_flows.py b/octavia/controller/worker/v2/flows/l7rule_flows.py index 76b9654ad1..39506204bb 100644 --- a/octavia/controller/worker/v2/flows/l7rule_flows.py +++ b/octavia/controller/worker/v2/flows/l7rule_flows.py @@ -64,6 +64,8 @@ class L7RuleFlows(object): requires=constants.LOADBALANCER_ID)) delete_l7rule_flow.add(database_tasks.DeleteL7RuleInDB( requires=constants.L7RULE)) + delete_l7rule_flow.add(database_tasks.DecrementL7ruleQuota( + requires=constants.L7RULE)) delete_l7rule_flow.add(database_tasks.MarkL7PolicyActiveInDB( requires=constants.L7POLICY)) delete_l7rule_flow.add(database_tasks.MarkLBAndListenersActiveInDB( diff --git a/octavia/controller/worker/v2/tasks/database_tasks.py b/octavia/controller/worker/v2/tasks/database_tasks.py index b13d9388c4..876e125e8d 100644 --- a/octavia/controller/worker/v2/tasks/database_tasks.py +++ b/octavia/controller/worker/v2/tasks/database_tasks.py @@ -2860,6 +2860,142 @@ class CountPoolChildrenForQuota(BaseDatabaseTask): return {'HM': health_mon_count, 'member': member_count} +class DecrementL7policyQuota(BaseDatabaseTask): + """Decrements the l7policy quota for a project. + + Since sqlalchemy will likely retry by itself always revert if it fails + """ + + def execute(self, l7policy): + """Decrements the l7policy quota. + + :param l7policy: The l7policy to decrement the quota on. + :returns: None + """ + + LOG.debug("Decrementing l7policy quota for " + "project: %s ", l7policy.project_id) + + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.decrement_quota(lock_session, + data_models.L7Policy, + l7policy.project_id) + + if l7policy.l7rules: + self.repos.decrement_quota(lock_session, + data_models.L7Rule, + l7policy.project_id, + quantity=len(l7policy.l7rules)) + lock_session.commit() + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error('Failed to decrement l7policy quota for project: ' + '%(proj)s the project may have excess quota in use.', + {'proj': l7policy.project_id}) + lock_session.rollback() + + def revert(self, l7policy, result, *args, **kwargs): + """Re-apply the quota + + :param l7policy: The l7policy to decrement the quota on. + :returns: None + """ + + LOG.warning('Reverting decrement quota for l7policy on project' + ' %(proj)s Project quota counts may be incorrect.', + {'proj': l7policy.project_id}) + + # Increment the quota back if this task wasn't the failure + if not isinstance(result, failure.Failure): + + try: + session = db_apis.get_session() + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Policy, + l7policy.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + + # Attempt to increment back the L7Rule quota + for i in range(len(l7policy.l7rules)): + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Rule, + l7policy.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + except Exception: + # Don't fail the revert flow + pass + + +class DecrementL7ruleQuota(BaseDatabaseTask): + """Decrements the l7rule quota for a project. + + Since sqlalchemy will likely retry by itself always revert if it fails + """ + + def execute(self, l7rule): + """Decrements the l7rule quota. + + :param l7rule: The l7rule to decrement the quota on. + :returns: None + """ + + LOG.debug("Decrementing l7rule quota for " + "project: %s ", l7rule.project_id) + + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.decrement_quota(lock_session, + data_models.L7Rule, + l7rule.project_id) + lock_session.commit() + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error('Failed to decrement l7rule quota for project: ' + '%(proj)s the project may have excess quota in use.', + {'proj': l7rule.project_id}) + lock_session.rollback() + + def revert(self, l7rule, result, *args, **kwargs): + """Re-apply the quota + + :param l7rule: The l7rule to decrement the quota on. + :returns: None + """ + + LOG.warning('Reverting decrement quota for l7rule on project %(proj)s ' + 'Project quota counts may be incorrect.', + {'proj': l7rule.project_id}) + + # Increment the quota back if this task wasn't the failure + if not isinstance(result, failure.Failure): + + try: + session = db_apis.get_session() + lock_session = db_apis.get_session(autocommit=False) + try: + self.repos.check_quota_met(session, + lock_session, + data_models.L7Rule, + l7rule.project_id) + lock_session.commit() + except Exception: + lock_session.rollback() + except Exception: + # Don't fail the revert flow + pass + + class UpdatePoolMembersOperatingStatusInDB(BaseDatabaseTask): """Updates the members of a pool operating status. diff --git a/octavia/db/migration/alembic_migrations/versions/32e5c35b26a8_add_l7policy_and_l7rule_quota.py b/octavia/db/migration/alembic_migrations/versions/32e5c35b26a8_add_l7policy_and_l7rule_quota.py new file mode 100644 index 0000000000..7162230a30 --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/32e5c35b26a8_add_l7policy_and_l7rule_quota.py @@ -0,0 +1,40 @@ +# Copyright (c) 2018 China Telecom Corporation +# All Rights Reserved. +# +# 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. + +"""add l7policy and l7rule quota + +Revision ID: 32e5c35b26a8 +Revises: d3c8a090f3de +Create Date: 2018-08-10 09:13:59.383272 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '32e5c35b26a8' +down_revision = 'd3c8a090f3de' + + +def upgrade(): + op.add_column(u'quotas', + sa.Column('l7policy', sa.Integer(), nullable=True)) + op.add_column(u'quotas', + sa.Column('l7rule', sa.Integer(), nullable=True)) + op.add_column(u'quotas', + sa.Column('in_use_l7policy', sa.Integer(), nullable=True)) + op.add_column(u'quotas', + sa.Column('in_use_l7rule', sa.Integer(), nullable=True)) diff --git a/octavia/db/models.py b/octavia/db/models.py index 8b889c7512..8a792d4045 100644 --- a/octavia/db/models.py +++ b/octavia/db/models.py @@ -750,11 +750,15 @@ class Quotas(base_models.BASE): load_balancer = sa.Column(sa.Integer(), nullable=True) member = sa.Column(sa.Integer(), nullable=True) pool = sa.Column(sa.Integer(), nullable=True) + l7policy = sa.Column(sa.Integer(), nullable=True) + l7rule = sa.Column(sa.Integer(), nullable=True) in_use_health_monitor = sa.Column(sa.Integer(), nullable=True) in_use_listener = sa.Column(sa.Integer(), nullable=True) in_use_load_balancer = sa.Column(sa.Integer(), nullable=True) in_use_member = sa.Column(sa.Integer(), nullable=True) in_use_pool = sa.Column(sa.Integer(), nullable=True) + in_use_l7policy = sa.Column(sa.Integer(), nullable=True) + in_use_l7rule = sa.Column(sa.Integer(), nullable=True) class FlavorProfile(base_models.BASE, base_models.IdMixin, diff --git a/octavia/db/repositories.py b/octavia/db/repositories.py index f47732dacb..e0f488fbf5 100644 --- a/octavia/db/repositories.py +++ b/octavia/db/repositories.py @@ -499,6 +499,48 @@ class Repositories(object): quotas.in_use_member = member_count return False return True + if _class == data_models.L7Policy: + # Decide which quota to use + if quotas.l7policy is None: + l7policy_quota = CONF.quotas.default_l7policy_quota + else: + l7policy_quota = quotas.l7policy + # Get the current in use count + if not quotas.in_use_l7policy: + # This is to handle the upgrade case + l7policy_count = session.query(models.L7Policy).filter( + models.L7Policy.project_id == project_id, + models.L7Policy.provisioning_status != + consts.DELETED).count() + count + else: + l7policy_count = quotas.in_use_l7policy + count + # Decide if the quota is met + if (l7policy_count <= l7policy_quota or + l7policy_quota == consts.QUOTA_UNLIMITED): + quotas.in_use_l7policy = l7policy_count + return False + return True + if _class == data_models.L7Rule: + # Decide which quota to use + if quotas.l7rule is None: + l7rule_quota = CONF.quotas.default_l7rule_quota + else: + l7rule_quota = quotas.l7rule + # Get the current in use count + if not quotas.in_use_l7rule: + # This is to handle the upgrade case + l7rule_count = session.query(models.L7Rule).filter( + models.L7Rule.project_id == project_id, + models.L7Rule.provisioning_status != + consts.DELETED).count() + count + else: + l7rule_count = quotas.in_use_l7rule + count + # Decide if the quota is met + if (l7rule_count <= l7rule_quota or + l7rule_quota == consts.QUOTA_UNLIMITED): + quotas.in_use_l7rule = l7rule_count + return False + return True except db_exception.DBDeadlock: LOG.warning('Quota project lock timed out for project: %(proj)s', {'proj': project_id}) @@ -584,6 +626,28 @@ class Repositories(object): 'project: %(proj)s that would cause a ' 'negative quota.', {'clss': type(_class), 'proj': project_id}) + if _class == data_models.L7Policy: + if (quotas.in_use_l7policy is not None and + quotas.in_use_l7policy > 0): + quotas.in_use_l7policy = ( + quotas.in_use_l7policy - quantity) + else: + if not CONF.api_settings.auth_strategy == consts.NOAUTH: + LOG.warning('Quota decrement on %(clss)s called on ' + 'project: %(proj)s that would cause a ' + 'negative quota.', + {'clss': type(_class), 'proj': project_id}) + if _class == data_models.L7Rule: + if (quotas.in_use_l7rule is not None and + quotas.in_use_l7rule > 0): + quotas.in_use_l7rule = ( + quotas.in_use_l7rule - quantity) + else: + if not CONF.api_settings.auth_strategy == consts.NOAUTH: + LOG.warning('Quota decrement on %(clss)s called on ' + 'project: %(proj)s that would cause a ' + 'negative quota.', + {'clss': type(_class), 'proj': project_id}) except db_exception.DBDeadlock: LOG.warning('Quota project lock timed out for project: %(proj)s', {'proj': project_id}) @@ -657,6 +721,13 @@ class Repositories(object): self.sni.create(lock_session, **sni_container) if l7policies_dict: for policy_dict in l7policies_dict: + # Add l7policy quota check + if self.check_quota_met(session, + lock_session, + data_models.L7Policy, + lb_dict['project_id']): + raise exceptions.QuotaException( + resource=data_models.L7Policy._name()) l7rules_dict = policy_dict.pop('l7rules') if policy_dict.get('redirect_pool'): # Add pool quota check @@ -711,6 +782,14 @@ class Repositories(object): policy_dm = self.l7policy.create(lock_session, **policy_dict) for rule_dict in l7rules_dict: + # Add l7rule quota check + if self.check_quota_met( + session, + lock_session, + data_models.L7Rule, + lb_dict['project_id']): + raise exceptions.QuotaException( + resource=data_models.L7Rule._name()) rule_dict['l7policy_id'] = policy_dm.id self.l7rule.create(lock_session, **rule_dict) lock_session.commit() @@ -1848,6 +1927,8 @@ class QuotasRepository(BaseRepository): quotas.listener = None quotas.member = None quotas.pool = None + quotas.l7policy = None + quotas.l7rule = None session.flush() diff --git a/octavia/tests/functional/api/test_root_controller.py b/octavia/tests/functional/api/test_root_controller.py index 1bf058c7b0..8c9792b9ab 100644 --- a/octavia/tests/functional/api/test_root_controller.py +++ b/octavia/tests/functional/api/test_root_controller.py @@ -45,7 +45,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): def test_api_versions(self): versions = self._get_versions_with_config() version_ids = tuple(v.get('id') for v in versions) - self.assertEqual(19, len(version_ids)) + self.assertEqual(20, len(version_ids)) self.assertIn('v2.0', version_ids) self.assertIn('v2.1', version_ids) self.assertIn('v2.2', version_ids) @@ -65,6 +65,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): self.assertIn('v2.16', version_ids) self.assertIn('v2.17', version_ids) self.assertIn('v2.18', version_ids) + self.assertIn('v2.19', version_ids) # Each version should have a 'self' 'href' to the API version URL # [{u'rel': u'self', u'href': u'http://localhost/v2'}] diff --git a/octavia/tests/functional/api/v2/test_l7rule.py b/octavia/tests/functional/api/v2/test_l7rule.py index 87a2aef00c..6cfab21cab 100644 --- a/octavia/tests/functional/api/v2/test_l7rule.py +++ b/octavia/tests/functional/api/v2/test_l7rule.py @@ -19,6 +19,7 @@ from oslo_utils import uuidutils from octavia.common import constants import octavia.common.context +from octavia.common import data_models from octavia.common import exceptions from octavia.db import repositories from octavia.tests.functional.api.v2 import base @@ -829,6 +830,15 @@ class TestL7Rule(base.BaseAPITest): def test_create_bad_cases_with_ssl_rule_types(self): self._test_bad_cases_with_ssl_rule_types() + def test_create_over_quota(self): + self.start_quota_mock(data_models.L7Rule) + l7rule = {'compare_type': 'REGEX', + 'invert': False, + 'type': 'PATH', + 'value': '/images*', + 'admin_state_up': True} + self.post(self.l7rules_path, self._build_body(l7rule), status=403) + def test_update(self): api_l7rule = self.create_l7rule( self.l7policy_id, constants.L7RULE_TYPE_PATH, diff --git a/octavia/tests/functional/api/v2/test_quotas.py b/octavia/tests/functional/api/v2/test_quotas.py index 0d0de4a74c..5d037e6cc5 100644 --- a/octavia/tests/functional/api/v2/test_quotas.py +++ b/octavia/tests/functional/api/v2/test_quotas.py @@ -54,6 +54,14 @@ class TestQuotas(base.BaseAPITest): group="quotas", default_health_monitor_quota=random.randrange( constants.QUOTA_UNLIMITED, 9000)) + conf.config( + group="quotas", + default_l7policy_quota=random.randrange( + constants.QUOTA_UNLIMITED, 9000)) + conf.config( + group="quotas", + default_l7rule_quota=random.randrange( + constants.QUOTA_UNLIMITED, 9000)) self.project_id = uuidutils.generate_uuid() @@ -65,13 +73,17 @@ class TestQuotas(base.BaseAPITest): 'pool': CONF.quotas.default_pool_quota, 'health_monitor': CONF.quotas.default_health_monitor_quota, - 'member': CONF.quotas.default_member_quota} + 'member': CONF.quotas.default_member_quota, + 'l7policy': CONF.quotas.default_l7policy_quota, + 'l7rule': CONF.quotas.default_l7rule_quota} self.assertEqual(expected['load_balancer'], observed['load_balancer']) self.assertEqual(expected['listener'], observed['listener']) self.assertEqual(expected['pool'], observed['pool']) self.assertEqual(expected['health_monitor'], observed['health_monitor']) self.assertEqual(expected['member'], observed['member']) + self.assertEqual(expected['l7policy'], observed['l7policy']) + self.assertEqual(expected['l7rule'], observed['l7rule']) def test_get_all_quotas_no_quotas(self): response = self.get(self.QUOTAS_PATH) @@ -83,12 +95,14 @@ class TestQuotas(base.BaseAPITest): project_id2 = uuidutils.generate_uuid() quota_path1 = self.QUOTA_PATH.format(project_id=project_id1) quota1 = {'load_balancer': constants.QUOTA_UNLIMITED, 'listener': 30, - 'pool': 30, 'health_monitor': 30, 'member': 30} + 'pool': 30, 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30} body1 = {'quota': quota1} self.put(quota_path1, body1, status=202) quota_path2 = self.QUOTA_PATH.format(project_id=project_id2) quota2 = {'load_balancer': 50, 'listener': 50, 'pool': 50, - 'health_monitor': 50, 'member': 50} + 'health_monitor': 50, 'member': 50, 'l7policy': 50, + 'l7rule': 50} body2 = {'quota': quota2} self.put(quota_path2, body2, status=202) @@ -111,12 +125,14 @@ class TestQuotas(base.BaseAPITest): project_id2 = uuidutils.generate_uuid() quota_path1 = self.QUOTA_PATH.format(project_id=project_id1) quota1 = {'load_balancer': constants.QUOTA_UNLIMITED, 'listener': 30, - 'pool': 30, 'health_monitor': 30, 'member': 30} + 'pool': 30, 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30} body1 = {'quota': quota1} self.put(quota_path1, body1, status=202) quota_path2 = self.QUOTA_PATH.format(project_id=project_id2) quota2 = {'loadbalancer': 50, 'listener': 50, 'pool': 50, - 'healthmonitor': 50, 'member': 50} + 'healthmonitor': 50, 'member': 50, 'l7policy': 50, + 'l7rule': 50} body2 = {'quota': quota2} self.put(quota_path2, body2, status=202) @@ -139,12 +155,14 @@ class TestQuotas(base.BaseAPITest): project_id2 = uuidutils.generate_uuid() quota_path1 = self.QUOTA_PATH.format(project_id=project_id1) quota1 = {'load_balancer': constants.QUOTA_UNLIMITED, 'listener': 30, - 'pool': 30, 'health_monitor': 30, 'member': 30} + 'pool': 30, 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30} body1 = {'quota': quota1} self.put(quota_path1, body1, status=202) quota_path2 = self.QUOTA_PATH.format(project_id=project_id2) quota2 = {'load_balancer': 50, 'listener': 50, 'pool': 50, - 'health_monitor': 50, 'member': 50} + 'health_monitor': 50, 'member': 50, 'l7policy': 50, + 'l7rule': 50} body2 = {'quota': quota2} self.put(quota_path2, body2, status=202) self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) @@ -160,7 +178,8 @@ class TestQuotas(base.BaseAPITest): project_id1 = uuidutils.generate_uuid() quota_path1 = self.QUOTA_PATH.format(project_id=project_id1) quota1 = {'load_balancer': constants.QUOTA_UNLIMITED, 'listener': 30, - 'pool': 30, 'health_monitor': 30, 'member': 30} + 'pool': 30, 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30} body1 = {'quota': quota1} self.put(quota_path1, body1, status=202) self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) @@ -193,12 +212,14 @@ class TestQuotas(base.BaseAPITest): project_id2 = uuidutils.generate_uuid() quota_path1 = self.QUOTA_PATH.format(project_id=project_id1) quota1 = {'load_balancer': constants.QUOTA_UNLIMITED, 'listener': 30, - 'pool': 30, 'health_monitor': 30, 'member': 30} + 'pool': 30, 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30} body1 = {'quota': quota1} self.put(quota_path1, body1, status=202) quota_path2 = self.QUOTA_PATH.format(project_id=project_id2) quota2 = {'load_balancer': 50, 'listener': 50, 'pool': 50, - 'health_monitor': 50, 'member': 50} + 'health_monitor': 50, 'member': 50, 'l7policy': 50, + 'l7rule': 50} body2 = {'quota': quota2} self.put(quota_path2, body2, status=202) self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) @@ -701,7 +722,8 @@ class TestQuotas(base.BaseAPITest): def test_custom_quotas(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json @@ -710,7 +732,8 @@ class TestQuotas(base.BaseAPITest): def test_custom_quotas_quota_admin(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) auth_strategy = self.conf.conf.api_settings.get('auth_strategy') self.conf.config(group='api_settings', auth_strategy=constants.TESTING) @@ -741,7 +764,8 @@ class TestQuotas(base.BaseAPITest): def test_custom_quotas_not_Authorized_member(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) auth_strategy = self.conf.conf.api_settings.get('auth_strategy') self.conf.config(group='api_settings', auth_strategy=constants.TESTING) @@ -770,11 +794,12 @@ class TestQuotas(base.BaseAPITest): def test_custom_partial_quotas(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': None, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} expected_body = {'quota': { 'load_balancer': 30, 'listener': CONF.quotas.default_listener_quota, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json @@ -784,11 +809,12 @@ class TestQuotas(base.BaseAPITest): def test_custom_missing_quotas(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, + 'l7policy': 30, 'l7rule': 30}} expected_body = {'quota': { 'load_balancer': 30, 'listener': CONF.quotas.default_listener_quota, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json @@ -798,7 +824,8 @@ class TestQuotas(base.BaseAPITest): def test_delete_custom_quotas(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json @@ -811,7 +838,8 @@ class TestQuotas(base.BaseAPITest): def test_delete_custom_quotas_admin(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json @@ -846,7 +874,8 @@ class TestQuotas(base.BaseAPITest): def test_delete_quotas_not_Authorized_member(self): quota_path = self.QUOTA_PATH.format(project_id=self.project_id) body = {'quota': {'load_balancer': 30, 'listener': 30, 'pool': 30, - 'health_monitor': 30, 'member': 30}} + 'health_monitor': 30, 'member': 30, 'l7policy': 30, + 'l7rule': 30}} self.put(quota_path, body, status=202) response = self.get(quota_path) quota_dict = response.json diff --git a/octavia/tests/functional/db/test_repositories.py b/octavia/tests/functional/db/test_repositories.py index 7892e7b1b2..d1814fbe93 100644 --- a/octavia/tests/functional/db/test_repositories.py +++ b/octavia/tests/functional/db/test_repositories.py @@ -612,6 +612,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): l7rule = {'type': constants.L7RULE_TYPE_HOST_NAME, 'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO, 'value': 'localhost'} + l7rule2 = {'type': constants.L7RULE_TYPE_PATH, + 'compare_type': constants.L7RULE_COMPARE_TYPE_CONTAINS, + 'value': 'abc'} r_health_monitor = {'type': constants.HEALTH_MONITOR_HTTP, 'delay': 1, 'timeout': 1, 'fall_threshold': 1, 'rise_threshold': 1, 'enabled': True} @@ -629,11 +632,16 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'redirect_pool_id': redirect_pool.get('id'), 'id': uuidutils.generate_uuid()} l7rule['l7policy_id'] = l7policy.get('id') + l7policy2 = {'name': 'l7policy2', 'enabled': True, + 'description': 'l7policy_description', 'position': 2, + 'action': constants.L7POLICY_ACTION_REJECT, + 'id': uuidutils.generate_uuid()} + l7rule2['l7policy_id'] = l7policy2.get('id') listener = {'project_id': project_id, 'name': 'listener1', 'description': 'listener_description', 'protocol': constants.PROTOCOL_HTTP, 'protocol_port': 80, 'connection_limit': 1, 'enabled': True, - 'default_pool': pool, 'l7policies': [l7policy], + 'default_pool': pool, 'l7policies': [l7policy, l7policy2], 'provisioning_status': constants.PENDING_CREATE, 'operating_status': constants.ONLINE, 'id': uuidutils.generate_uuid()} @@ -646,6 +654,7 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'operating_status': constants.ONLINE, 'id': uuidutils.generate_uuid()} l7policy['listener_id'] = listener.get('id') + l7policy2['listener_id'] = listener.get('id') vip = {'ip_address': '192.0.2.1', 'port_id': uuidutils.generate_uuid(), 'subnet_id': uuidutils.generate_uuid()} lb = {'name': 'lb1', 'description': 'desc1', 'enabled': True, @@ -661,6 +670,16 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): pool['load_balancer_id'] = lb.get('id') redirect_pool['load_balancer_id'] = lb.get('id') + lb2_l7rule = {'type': constants.L7RULE_TYPE_HOST_NAME, + 'compare_type': constants.L7RULE_COMPARE_TYPE_EQUAL_TO, + 'value': 'localhost'} + lb2_l7policy = {'name': 'l7policy1', 'enabled': True, + 'description': 'l7policy_description', 'position': 1, + 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_url': 'www.example.com', + 'l7rules': [lb2_l7rule], + 'id': uuidutils.generate_uuid()} + lb2_l7rule['l7policy_id'] = lb2_l7policy.get('id') lb2_health_monitor = {'type': constants.HEALTH_MONITOR_HTTP, 'delay': 1, 'timeout': 1, 'fall_threshold': 1, 'rise_threshold': 1, 'enabled': True} @@ -681,10 +700,11 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'protocol': constants.PROTOCOL_HTTP, 'protocol_port': 83, 'connection_limit': 1, 'enabled': True, - 'default_pool': lb2_pool, + 'default_pool': lb2_pool, 'l7policies': [lb2_l7policy], 'provisioning_status': constants.PENDING_CREATE, 'operating_status': constants.ONLINE, 'id': uuidutils.generate_uuid()} + lb2_l7policy['listener_id'] = lb2_listener.get('id') lb2 = {'name': 'lb2', 'description': 'desc2', 'enabled': True, 'topology': constants.TOPOLOGY_ACTIVE_STANDBY, 'vrrp_group': None, @@ -701,7 +721,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -716,7 +738,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 0, 'pool': 10, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -731,7 +755,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 0, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -746,7 +772,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 0, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -761,7 +789,43 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 10, - 'member': 0} + 'member': 0, + 'l7policy': 10, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb1')) + + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 0, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb1')) + + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 10, + 'l7rule': 0} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -777,7 +841,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 1, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -793,7 +859,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 1, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -809,7 +877,45 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 10, - 'member': 1} + 'member': 1, + 'l7policy': 10, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb1')) + + # Test quota for l7policy + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 1, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb1')) + + # Test quota for l7rule + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 10, + 'l7rule': 1} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -826,7 +932,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.repos.create_load_balancer_tree(self.session, lock_session, @@ -851,7 +959,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 2, 'pool': 10, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -869,7 +979,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 2, 'health_monitor': 10, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -887,7 +999,9 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 1, - 'member': 10} + 'member': 10, + 'l7policy': 10, + 'l7rule': 10} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -905,7 +1019,49 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): 'listener': 10, 'pool': 10, 'health_monitor': 10, - 'member': 2} + 'member': 2, + 'l7policy': 10, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb2)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb2')) + + # ### Test l7policy quota + # Create with custom quotas and limit to two l7policy (lb has two), + # expect error of too many l7policy/over quota + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 2, + 'l7rule': 10} + self.repos.quotas.update(self.session, project_id, quota=quota) + lock_session = db_api.get_session(autocommit=False) + self.assertRaises( + exceptions.QuotaException, + self.repos.create_load_balancer_tree, + self.session, lock_session, copy.deepcopy(lb2)) + # Make sure we didn't create the load balancer anyway + self.assertIsNone(self.repos.load_balancer.get(self.session, + name='lb2')) + + # ### Test l7rule quota + # Create with custom quotas and limit to two l7rule (lb has two), + # expect error of too many l7rule/over quota + quota = {'load_balancer': 10, + 'listener': 10, + 'pool': 10, + 'health_monitor': 10, + 'member': 10, + 'l7policy': 10, + 'l7rule': 2} self.repos.quotas.update(self.session, project_id, quota=quota) lock_session = db_api.get_session(autocommit=False) self.assertRaises( @@ -1665,6 +1821,314 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): self.assertEqual(2, self.repos.quotas.get( self.session, project_id=project_id).in_use_member) + # ### Test l7policy quota + # Test with no pre-existing quota record default 0 + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=0) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertIsNone(self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test with no pre-existing quota record default 1 + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=1) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + # Test above project is now at quota + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test with no pre-existing quota record default unlimited + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', + default_l7policy_quota=constants.QUOTA_UNLIMITED) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + # Test above project adding another l7policy + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(2, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test upgrade case with pre-quota l7policy + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=1) + lb = self.repos.load_balancer.create( + self.session, id=uuidutils.generate_uuid(), + project_id=project_id, name="lb_name", + description="lb_description", + provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, + enabled=True) + listener = self.repos.listener.create( + self.session, protocol=constants.PROTOCOL_HTTP, protocol_port=80, + enabled=True, provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, project_id=project_id, + load_balancer_id=lb.id) + self.repos.l7policy.create( + self.session, name='l7policy', enabled=True, position=1, + action=constants.L7POLICY_ACTION_REJECT, + provisioning_status=constants.ACTIVE, listener_id=listener.id, + operating_status=constants.ONLINE, project_id=project_id, + id=uuidutils.generate_uuid()) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + + # Test upgrade case with pre-quota deleted l7policy + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=1) + lb = self.repos.load_balancer.create( + self.session, id=uuidutils.generate_uuid(), + project_id=project_id, name="lb_name", + description="lb_description", + provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, + enabled=True) + listener = self.repos.listener.create( + self.session, protocol=constants.PROTOCOL_HTTP, protocol_port=80, + enabled=True, provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, project_id=project_id, + load_balancer_id=lb.id) + self.repos.l7policy.create( + self.session, name='l7policy', enabled=True, position=1, + action=constants.L7POLICY_ACTION_REJECT, + provisioning_status=constants.DELETED, listener_id=listener.id, + operating_status=constants.ONLINE, project_id=project_id, + id=uuidutils.generate_uuid()) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test pre-existing quota with quota of zero + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=10) + quota = {'l7policy': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + + # Test pre-existing quota with quota of one + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=0) + quota = {'l7policy': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + # Test above project is now at quota + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test pre-existing quota with quota of unlimited + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=0) + quota = {'l7policy': constants.QUOTA_UNLIMITED} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + # Test above project adding another l7policy + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Policy, + project_id)) + self.assertEqual(2, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # ### Test l7rule quota + # Test with no pre-existing quota record default 0 + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=0) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertIsNone(self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test with no pre-existing quota record default 1 + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=1) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + # Test above project is now at quota + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test with no pre-existing quota record default unlimited + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', + default_l7rule_quota=constants.QUOTA_UNLIMITED) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + # Test above project adding another l7rule + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(2, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test upgrade case with pre-quota l7rule + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=1) + lb = self.repos.load_balancer.create( + self.session, id=uuidutils.generate_uuid(), + project_id=project_id, name="lb_name", + description="lb_description", + provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, + enabled=True) + listener = self.repos.listener.create( + self.session, protocol=constants.PROTOCOL_HTTP, protocol_port=80, + enabled=True, provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, project_id=project_id, + load_balancer_id=lb.id) + l7policy = self.repos.l7policy.create( + self.session, name='l7policy', enabled=True, position=1, + action=constants.L7POLICY_ACTION_REJECT, + provisioning_status=constants.ACTIVE, listener_id=listener.id, + operating_status=constants.ONLINE, project_id=project_id, + id=uuidutils.generate_uuid()) + self.repos.l7rule.create( + self.session, id=uuidutils.generate_uuid(), + l7policy_id=l7policy.id, type=constants.L7RULE_TYPE_HOST_NAME, + compare_type=constants.L7RULE_COMPARE_TYPE_EQUAL_TO, enabled=True, + provisioning_status=constants.ACTIVE, value='hostname', + operating_status=constants.ONLINE, project_id=project_id) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + + # Test upgrade case with pre-quota deleted l7rule + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7policy_quota=1) + lb = self.repos.load_balancer.create( + self.session, id=uuidutils.generate_uuid(), + project_id=project_id, name="lb_name", + description="lb_description", + provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, + enabled=True) + listener = self.repos.listener.create( + self.session, protocol=constants.PROTOCOL_HTTP, protocol_port=80, + enabled=True, provisioning_status=constants.ACTIVE, + operating_status=constants.ONLINE, project_id=project_id, + load_balancer_id=lb.id) + l7policy = self.repos.l7policy.create( + self.session, name='l7policy', enabled=True, position=1, + action=constants.L7POLICY_ACTION_REJECT, + provisioning_status=constants.ACTIVE, listener_id=listener.id, + operating_status=constants.ONLINE, project_id=project_id, + id=uuidutils.generate_uuid()) + self.repos.l7rule.create( + self.session, id=uuidutils.generate_uuid(), + l7policy_id=l7policy.id, type=constants.L7RULE_TYPE_HOST_NAME, + compare_type=constants.L7RULE_COMPARE_TYPE_EQUAL_TO, enabled=True, + provisioning_status=constants.DELETED, value='hostname', + operating_status=constants.ONLINE, project_id=project_id) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test pre-existing quota with quota of zero + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=10) + quota = {'l7rule': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + + # Test pre-existing quota with quota of one + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=0) + quota = {'l7rule': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + # Test above project is now at quota + self.assertTrue(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test pre-existing quota with quota of unlimited + project_id = uuidutils.generate_uuid() + conf.config(group='quotas', default_l7rule_quota=0) + quota = {'l7rule': constants.QUOTA_UNLIMITED} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(1, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + # Test above project adding another l7rule + self.assertFalse(self.repos.check_quota_met(self.session, + self.session, + models.L7Rule, + project_id)) + self.assertEqual(2, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + def test_decrement_quota(self): # Test decrement on non-existent quota with noauth project_id = uuidutils.generate_uuid() @@ -1929,6 +2393,99 @@ class AllRepositoriesTest(base.OctaviaDBTestBase): self.assertEqual(0, self.repos.quotas.get( self.session, project_id=project_id).in_use_member) conf.config(group='api_settings', auth_strategy=constants.TESTING) + # ### Test l7policy quota + # Test decrement on zero in use quota + project_id = uuidutils.generate_uuid() + quota = {'in_use_l7policy': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Policy, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test decrement on zero in use quota with noauth + project_id = uuidutils.generate_uuid() + conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) + conf.config(group='api_settings', auth_strategy=constants.NOAUTH) + quota = {'in_use_l7policy': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Policy, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + conf.config(group='api_settings', auth_strategy=constants.TESTING) + + # Test decrement on in use quota + project_id = uuidutils.generate_uuid() + quota = {'in_use_l7policy': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Policy, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + + # Test decrement on in use quota with noauth + project_id = uuidutils.generate_uuid() + conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) + conf.config(group='api_settings', auth_strategy=constants.NOAUTH) + quota = {'in_use_l7policy': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Policy, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7policy) + conf.config(group='api_settings', auth_strategy=constants.TESTING) + + # ### Test l7rule quota + # Test decrement on zero in use quota + project_id = uuidutils.generate_uuid() + quota = {'in_use_l7rule': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Rule, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test decrement on zero in use quota with noauth + project_id = uuidutils.generate_uuid() + conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) + conf.config(group='api_settings', auth_strategy=constants.NOAUTH) + quota = {'in_use_l7rule': 0} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Rule, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + conf.config(group='api_settings', auth_strategy=constants.TESTING) + + # Test decrement on in use quota + project_id = uuidutils.generate_uuid() + quota = {'in_use_l7rule': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Rule, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + + # Test decrement on in use quota with noauth + project_id = uuidutils.generate_uuid() + conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) + conf.config(group='api_settings', auth_strategy=constants.NOAUTH) + quota = {'in_use_l7rule': 1} + self.repos.quotas.update(self.session, project_id, quota=quota) + self.repos.decrement_quota(self.session, + models.L7Rule, + project_id) + self.assertEqual(0, self.repos.quotas.get( + self.session, project_id=project_id).in_use_l7rule) + conf.config(group='api_settings', auth_strategy=constants.TESTING) def test_get_amphora_stats(self): listener2_id = uuidutils.generate_uuid() @@ -4337,12 +4894,14 @@ class TestQuotasRepository(BaseRepositoryTest): super(TestQuotasRepository, self).setUp() def update_quotas(self, project_id, load_balancer=20, listener=20, pool=20, - health_monitor=20, member=20): + health_monitor=20, member=20, l7policy=20, l7rule=20): quota = {'load_balancer': load_balancer, 'listener': listener, 'pool': pool, 'health_monitor': health_monitor, - 'member': member} + 'member': member, + 'l7policy': l7policy, + 'l7rule': l7rule} quotas = self.quota_repo.update(self.session, project_id, quota=quota) return quotas @@ -4358,6 +4917,10 @@ class TestQuotasRepository(BaseRepositoryTest): observed.health_monitor) self.assertEqual(expected.member, observed.member) + self.assertEqual(expected.l7policy, + observed.l7policy) + self.assertEqual(expected.l7rule, + observed.l7rule) def test_get(self): expected = self.update_quotas(self.FAKE_UUID_1) @@ -4394,6 +4957,8 @@ class TestQuotasRepository(BaseRepositoryTest): self.assertIsNone(observed.listener) self.assertIsNone(observed.member) self.assertIsNone(observed.pool) + self.assertIsNone(observed.l7policy) + self.assertIsNone(observed.l7rule) def test_delete_non_existent(self): self.assertRaises(exceptions.NotFound, diff --git a/octavia/tests/unit/api/v2/types/test_quotas.py b/octavia/tests/unit/api/v2/types/test_quotas.py new file mode 100644 index 0000000000..bdacfc2c97 --- /dev/null +++ b/octavia/tests/unit/api/v2/types/test_quotas.py @@ -0,0 +1,87 @@ +# Copyright (c) 2018 China Telecom Corporation +# All Rights Reserved. +# +# 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. + +from wsme import exc +from wsme.rest import json as wsme_json +from wsme import types as wsme_types + +from octavia.api.v2.types import quotas as quota_type +from octavia.common import constants +from octavia.tests.unit.api.v2.types import base + + +class TestQuotaPut(base.BaseTypesTest): + + _type = quota_type.QuotaPUT + + def test_quota(self): + body = {'quota': {'loadbalancer': 5}} + quota = wsme_json.fromjson(self._type, body) + self.assertEqual(wsme_types.Unset, quota.quota.listener) + self.assertEqual(wsme_types.Unset, quota.quota.pool) + self.assertEqual(wsme_types.Unset, quota.quota.member) + self.assertEqual(wsme_types.Unset, quota.quota.healthmonitor) + self.assertEqual(wsme_types.Unset, quota.quota.l7policy) + self.assertEqual(wsme_types.Unset, quota.quota.l7rule) + + def test_invalid_quota(self): + body = {'quota': {'loadbalancer': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'loadbalancer': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'listener': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'listener': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'pool': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'pool': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'member': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'member': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'healthmonitor': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'healthmonitor': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'l7policy': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'l7policy': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + + body = {'quota': {'l7rule': constants.MAX_QUOTA + 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) + body = {'quota': {'l7rule': constants.MIN_QUOTA - 1}} + self.assertRaises(exc.InvalidInput, wsme_json.fromjson, + self._type, body) diff --git a/octavia/tests/unit/common/test_data_models.py b/octavia/tests/unit/common/test_data_models.py index b738269497..1700cd9982 100644 --- a/octavia/tests/unit/common/test_data_models.py +++ b/octavia/tests/unit/common/test_data_models.py @@ -118,6 +118,24 @@ class TestDataModels(base.TestCase): compute_flavor=self.COMPUTE_FLAVOR ) + self.QUOTA_obj = data_models.Quotas( + project_id=self.PROJECT_ID, + load_balancer=None, + listener=None, + pool=None, + health_monitor=None, + member=None, + l7policy=None, + l7rule=None, + in_use_health_monitor=None, + in_use_listener=None, + in_use_load_balancer=None, + in_use_member=None, + in_use_pool=None, + in_use_l7policy=None, + in_use_l7rule=None + ) + super(TestDataModels, self).setUp() def test_LoadBalancer_update(self): @@ -417,3 +435,47 @@ class TestDataModels(base.TestCase): test_Amp_obj.update(update_dict) self.assertEqual(reference_Amp_obj, test_Amp_obj) + + def test_Quota_update(self): + + new_loadbalancer_quota = 10 + new_listener_quota = 11 + new_pool_quota = 12 + new_healthmonitor_quota = 13 + new_member_quota = 14 + new_l7policy_quota = 15 + new_l7rule_quota = 16 + + update_dict = { + 'load_balancer': new_loadbalancer_quota, + 'listener': new_listener_quota, + 'pool': new_pool_quota, + 'health_monitor': new_healthmonitor_quota, + 'member': new_member_quota, + 'l7policy': new_l7policy_quota, + 'l7rule': new_l7rule_quota + } + + test_Quota_obj = copy.deepcopy(self.QUOTA_obj) + + reference_Quota_obj = data_models.Quotas( + project_id=self.PROJECT_ID, + load_balancer=new_loadbalancer_quota, + listener=new_listener_quota, + pool=new_pool_quota, + health_monitor=new_healthmonitor_quota, + member=new_member_quota, + l7policy=new_l7policy_quota, + l7rule=new_l7rule_quota, + in_use_health_monitor=None, + in_use_listener=None, + in_use_load_balancer=None, + in_use_member=None, + in_use_pool=None, + in_use_l7policy=None, + in_use_l7rule=None + ) + + test_Quota_obj.update(update_dict) + + self.assertEqual(reference_Quota_obj, test_Quota_obj) diff --git a/octavia/tests/unit/controller/worker/v1/tasks/test_database_tasks_quota.py b/octavia/tests/unit/controller/worker/v1/tasks/test_database_tasks_quota.py index 999c9389ee..a68917301e 100644 --- a/octavia/tests/unit/controller/worker/v1/tasks/test_database_tasks_quota.py +++ b/octavia/tests/unit/controller/worker/v1/tasks/test_database_tasks_quota.py @@ -56,6 +56,8 @@ class TestDatabaseTasksQuota(base.TestCase): if data_model == data_models.Pool: task.execute(test_object, self.zero_pool_child_count) else: + if data_model == data_models.L7Policy: + test_object.l7rules = [] task.execute(test_object) mock_decrement_quota.assert_called_once_with( @@ -95,6 +97,8 @@ class TestDatabaseTasksQuota(base.TestCase): self.zero_pool_child_count, self._tf_failure_mock) else: + if data_model == data_models.L7Policy: + test_object.l7rules = [] task.revert(test_object, self._tf_failure_mock) self.assertFalse(mock_get_session.called) self.assertFalse(mock_check_quota_met.called) @@ -320,3 +324,92 @@ class TestDatabaseTasksQuota(base.TestCase): result = task.execute(pool_hm_2_mem) self.assertEqual({'HM': 1, 'member': 2}, result) + + def test_decrement_l7policy_quota(self): + task = database_tasks.DecrementL7policyQuota() + data_model = data_models.L7Policy + self._test_decrement_quota(task, data_model) + + @mock.patch('octavia.db.repositories.Repositories.decrement_quota') + @mock.patch('octavia.db.repositories.Repositories.check_quota_met') + def test_decrement_l7policy_quota_with_children(self, + mock_check_quota_met, + mock_decrement_quota): + project_id = uuidutils.generate_uuid() + test_l7rule1 = mock.MagicMock() + test_l7rule1.project_id = project_id + test_l7rule2 = mock.MagicMock() + test_l7rule2.project_id = project_id + test_object = mock.MagicMock() + test_object.project_id = project_id + test_object.l7rules = [test_l7rule1, test_l7rule2] + task = database_tasks.DecrementL7policyQuota() + mock_session = mock.MagicMock() + + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_get_session_local.return_value = mock_session + + task.execute(test_object) + + calls = [mock.call(mock_session, data_models.L7Policy, project_id), + mock.call(mock_session, data_models.L7Rule, project_id, + quantity=2)] + + mock_decrement_quota.assert_has_calls(calls) + + mock_session.commit.assert_called_once_with() + + # revert + mock_session.reset_mock() + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_lock_session = mock.MagicMock() + mock_get_session_local.side_effect = [mock_session, + mock_lock_session, + mock_lock_session, + mock_lock_session] + + task.revert(test_object, None) + + calls = [mock.call(mock_session, mock_lock_session, + data_models.L7Policy, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id)] + + mock_check_quota_met.assert_has_calls(calls) + + self.assertEqual(3, mock_lock_session.commit.call_count) + + # revert with l7rule quota exception + mock_session.reset_mock() + mock_check_quota_met.side_effect = [None, None, + Exception('fail')] + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_lock_session = mock.MagicMock() + mock_get_session_local.side_effect = [mock_session, + mock_lock_session, + mock_lock_session, + mock_lock_session] + + task.revert(test_object, None) + + calls = [mock.call(mock_session, mock_lock_session, + data_models.L7Policy, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id)] + + mock_check_quota_met.assert_has_calls(calls) + + self.assertEqual(2, mock_lock_session.commit.call_count) + self.assertEqual(1, mock_lock_session.rollback.call_count) + + def test_decrement_l7rule_quota(self): + task = database_tasks.DecrementL7ruleQuota() + data_model = data_models.L7Rule + self._test_decrement_quota(task, data_model) diff --git a/octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks_quota.py b/octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks_quota.py index 6637120c7b..798c23fab7 100644 --- a/octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks_quota.py +++ b/octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks_quota.py @@ -53,6 +53,9 @@ class TestDatabaseTasksQuota(base.TestCase): mock_session = mock.MagicMock() mock_get_session_local.return_value = mock_session + if data_model == data_models.L7Policy: + test_object.l7rules = [] + if data_model == data_models.Pool: task.execute(test_object, self.zero_pool_child_count) else: @@ -338,3 +341,92 @@ class TestDatabaseTasksQuota(base.TestCase): result = task.execute(pool_hm_2_mem.id) self.assertEqual({'HM': 1, 'member': 2}, result) + + def test_decrement_l7policy_quota(self): + task = database_tasks.DecrementL7policyQuota() + data_model = data_models.L7Policy + self._test_decrement_quota(task, data_model) + + @mock.patch('octavia.db.repositories.Repositories.decrement_quota') + @mock.patch('octavia.db.repositories.Repositories.check_quota_met') + def test_decrement_l7policy_quota_with_children(self, + mock_check_quota_met, + mock_decrement_quota): + project_id = uuidutils.generate_uuid() + test_l7rule1 = mock.MagicMock() + test_l7rule1.project_id = project_id + test_l7rule2 = mock.MagicMock() + test_l7rule2.project_id = project_id + test_object = mock.MagicMock() + test_object.project_id = project_id + test_object.l7rules = [test_l7rule1, test_l7rule2] + task = database_tasks.DecrementL7policyQuota() + mock_session = mock.MagicMock() + + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_get_session_local.return_value = mock_session + + task.execute(test_object) + + calls = [mock.call(mock_session, data_models.L7Policy, project_id), + mock.call(mock_session, data_models.L7Rule, project_id, + quantity=2)] + + mock_decrement_quota.assert_has_calls(calls) + + mock_session.commit.assert_called_once_with() + + # revert + mock_session.reset_mock() + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_lock_session = mock.MagicMock() + mock_get_session_local.side_effect = [mock_session, + mock_lock_session, + mock_lock_session, + mock_lock_session] + + task.revert(test_object, None) + + calls = [mock.call(mock_session, mock_lock_session, + data_models.L7Policy, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id)] + + mock_check_quota_met.assert_has_calls(calls) + + self.assertEqual(3, mock_lock_session.commit.call_count) + + # revert with l7rule quota exception + mock_session.reset_mock() + mock_check_quota_met.side_effect = [None, None, + Exception('fail')] + with mock.patch('octavia.db.api.' + 'get_session') as mock_get_session_local: + mock_lock_session = mock.MagicMock() + mock_get_session_local.side_effect = [mock_session, + mock_lock_session, + mock_lock_session, + mock_lock_session] + + task.revert(test_object, None) + + calls = [mock.call(mock_session, mock_lock_session, + data_models.L7Policy, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id), + mock.call(mock_session, mock_lock_session, + data_models.L7Rule, project_id)] + + mock_check_quota_met.assert_has_calls(calls) + + self.assertEqual(2, mock_lock_session.commit.call_count) + self.assertEqual(1, mock_lock_session.rollback.call_count) + + def test_decrement_l7rule_quota(self): + task = database_tasks.DecrementL7ruleQuota() + data_model = data_models.L7Rule + self._test_decrement_quota(task, data_model) diff --git a/releasenotes/notes/add-l7policy-and-l7rule-to-quota-4b873c77f1e608e6.yaml b/releasenotes/notes/add-l7policy-and-l7rule-to-quota-4b873c77f1e608e6.yaml new file mode 100644 index 0000000000..0988ef5520 --- /dev/null +++ b/releasenotes/notes/add-l7policy-and-l7rule-to-quota-4b873c77f1e608e6.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add l7policy and l7rule to octavia quota.