Improve the quota check

- Check alarm quota by considering the quota setting in DB.
- Add more unit tests for quota API.

Change-Id: I1af381f357b0d80f68e3ead16e158428b7a14555
This commit is contained in:
Lingxian Kong 2020-01-31 10:02:51 +13:00
parent 223178716e
commit 90a7c6eab2
4 changed files with 211 additions and 24 deletions

View File

@ -105,18 +105,24 @@ def is_over_quota(conn, project_id, user_id):
over_quota = False
# Start by checking for user quota
user_alarm_quota = pecan.request.cfg.api.user_alarm_quota
if user_alarm_quota != -1:
user_alarms = conn.get_alarms(user_id=user_id)
over_quota = len(user_alarms) >= user_alarm_quota
project_quotas = conn.get_quotas(project_id)
project_alarms = conn.get_alarms(project_id=project_id)
user_alarms = conn.get_alarms(user_id=user_id)
user_default_alarm_quota = pecan.request.cfg.api.user_alarm_quota
project_default_alarm_quota = pecan.request.cfg.api.project_alarm_quota
# If the user quota isn't reached, we check for the project quota
if not over_quota:
project_alarm_quota = pecan.request.cfg.api.project_alarm_quota
if project_alarm_quota != -1:
project_alarms = conn.get_alarms(project_id=project_id)
over_quota = len(project_alarms) >= project_alarm_quota
# 1. Check project quota
if len(project_quotas) > 0:
for quota in project_quotas:
if quota.resource == 'alarms':
over_quota = len(user_alarms) >= quota.limit
else:
if project_default_alarm_quota != -1:
over_quota = len(project_alarms) >= project_default_alarm_quota
# 2. Check user quota
if not over_quota and user_default_alarm_quota != -1:
over_quota = len(user_alarms) >= user_default_alarm_quota
return over_quota

View File

@ -28,7 +28,7 @@ ALLOWED_RESOURCES = ('alarms',)
class Quota(base.Base):
resource = wtypes.wsattr(wtypes.Enum(str, *ALLOWED_RESOURCES),
mandatory=True)
limit = wtypes.IntegerType(minimum=-1)
limit = wsme.wsattr(wtypes.IntegerType(minimum=-1), mandatory=True)
class Quotas(base.Base):
@ -79,6 +79,6 @@ class QuotasController(rest.RestController):
input_quotas.append(i.to_dict())
db_quotas = pecan.request.storage.set_quotas(project_id, input_quotas)
quotas = [Quota.from_db_model(i) for i in db_quotas]
return Quotas(project_id=project_id, quotas=quotas)

View File

@ -127,8 +127,10 @@ class TestAlarmsBase(v2.FunctionalTest):
def setUp(self):
super(TestAlarmsBase, self).setUp()
self.auth_headers = {'X-User-Id': uuidutils.generate_uuid(),
'X-Project-Id': uuidutils.generate_uuid()}
self.project_id = uuidutils.generate_uuid()
self.user_id = uuidutils.generate_uuid()
self.auth_headers = {'X-User-Id': self.user_id,
'X-Project-Id': self.project_id}
c = mock.Mock()
c.capabilities.list.return_value = {'aggregation_methods': [
@ -1955,13 +1957,13 @@ class TestAlarmsHistory(TestAlarmsBase):
class TestAlarmsQuotas(TestAlarmsBase):
def _test_alarm_quota(self):
alarm = {
def setUp(self):
super(TestAlarmsQuotas, self).setUp()
self.alarm = {
'name': 'alarm',
'type': 'gnocchi_aggregation_by_metrics_threshold',
'user_id': self.auth_headers['X-User-Id'],
'project_id': self.auth_headers['X-Project-Id'],
'user_id': self.user_id,
'project_id': self.project_id,
RULE_KEY: {
'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
'a1fb80f4-c242-4f57-87c6-68f47521059e'],
@ -1973,17 +1975,29 @@ class TestAlarmsQuotas(TestAlarmsBase):
}
}
def _create_alarm(self, alarm=None):
if not alarm:
alarm = self.alarm
resp = self.post_json('/alarms', params=alarm,
headers=self.auth_headers)
self.assertEqual(201, resp.status_code)
headers=self.auth_headers,
status=201)
return resp
def _test_alarm_quota(self):
"""Failed on the second creation."""
resp = self._create_alarm()
alarms = self.get_json('/alarms', headers=self.auth_headers)
self.assertEqual(1, len(alarms))
alarm = copy.copy(self.alarm)
alarm['name'] = 'another_user_alarm'
resp = self.post_json('/alarms', params=alarm,
expect_errors=True,
headers=self.auth_headers)
self.assertEqual(403, resp.status_code)
headers=self.auth_headers,
status=403)
faultstring = 'Alarm quota exceeded for user'
self.assertIn(faultstring,
resp.json['error_message']['faultstring'])
@ -2063,6 +2077,83 @@ class TestAlarmsQuotas(TestAlarmsBase):
alarms = self.get_json('/alarms', headers=self.auth_headers)
self.assertEqual(1, len(alarms))
def test_overquota_by_quota_api(self):
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
# Update project quota.
self.post_json(
'/quotas',
{
"project_id": self.project_id,
"quotas": [
{
"resource": "alarms",
"limit": 1
}
]
},
headers=auth_headers,
status=201
)
self._test_alarm_quota()
# Update project quota back
self.post_json(
'/quotas',
{
"project_id": self.project_id,
"quotas": [
{
"resource": "alarms",
"limit": -1
}
]
},
headers=auth_headers,
status=201
)
def test_overquota_by_user_quota_config(self):
self.CONF.set_override('user_alarm_quota', 1, 'api')
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
# Update project quota.
self.post_json(
'/quotas',
{
"project_id": self.project_id,
"quotas": [
{
"resource": "alarms",
"limit": 2
}
]
},
headers=auth_headers,
status=201
)
self._test_alarm_quota()
# Update project quota back
self.post_json(
'/quotas',
{
"project_id": self.project_id,
"quotas": [
{
"resource": "alarms",
"limit": -1
}
]
},
headers=auth_headers,
status=201
)
class TestAlarmsRuleThreshold(TestAlarmsBase):

View File

@ -100,3 +100,93 @@ class TestQuotas(v2.FunctionalTest):
expect_errors=True,
status=403
)
def test_post_quotas_no_limit_failed(self):
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
resp = self.post_json(
'/quotas',
{
"project_id": self.project,
"quotas": [
{
"resource": "alarms"
}
]
},
headers=auth_headers,
expect_errors=True,
status=400
)
self.assertIn('Mandatory field missing',
resp.json['error_message']['faultstring'])
def test_post_quotas_no_resource_failed(self):
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
resp = self.post_json(
'/quotas',
{
"project_id": self.project,
"quotas": [
{
"limit": 1
}
]
},
headers=auth_headers,
expect_errors=True,
status=400
)
self.assertIn('Mandatory field missing',
resp.json['error_message']['faultstring'])
def test_post_quotas_wrong_limit_failed(self):
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
resp = self.post_json(
'/quotas',
{
"project_id": self.project,
"quotas": [
{
"resource": "alarms",
"limit": -5
}
]
},
headers=auth_headers,
expect_errors=True,
status=400
)
self.assertIn('Value should be greater or equal to -1',
resp.json['error_message']['faultstring'])
def test_post_quotas_unsupported_resource_failed(self):
auth_headers = copy.copy(self.auth_headers)
auth_headers['X-Roles'] = 'admin'
resp = self.post_json(
'/quotas',
{
"project_id": self.project,
"quotas": [
{
"resource": "other_resource",
"limit": 1
}
]
},
headers=auth_headers,
expect_errors=True,
status=400
)
self.assertIn('Value should be one of',
resp.json['error_message']['faultstring'])