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:
parent
223178716e
commit
90a7c6eab2
@ -105,18 +105,24 @@ def is_over_quota(conn, project_id, user_id):
|
|||||||
|
|
||||||
over_quota = False
|
over_quota = False
|
||||||
|
|
||||||
# Start by checking for user quota
|
project_quotas = conn.get_quotas(project_id)
|
||||||
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
|
|
||||||
|
|
||||||
# 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)
|
project_alarms = conn.get_alarms(project_id=project_id)
|
||||||
over_quota = len(project_alarms) >= project_alarm_quota
|
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
|
||||||
|
|
||||||
|
# 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
|
return over_quota
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ ALLOWED_RESOURCES = ('alarms',)
|
|||||||
class Quota(base.Base):
|
class Quota(base.Base):
|
||||||
resource = wtypes.wsattr(wtypes.Enum(str, *ALLOWED_RESOURCES),
|
resource = wtypes.wsattr(wtypes.Enum(str, *ALLOWED_RESOURCES),
|
||||||
mandatory=True)
|
mandatory=True)
|
||||||
limit = wtypes.IntegerType(minimum=-1)
|
limit = wsme.wsattr(wtypes.IntegerType(minimum=-1), mandatory=True)
|
||||||
|
|
||||||
|
|
||||||
class Quotas(base.Base):
|
class Quotas(base.Base):
|
||||||
@ -79,6 +79,6 @@ class QuotasController(rest.RestController):
|
|||||||
input_quotas.append(i.to_dict())
|
input_quotas.append(i.to_dict())
|
||||||
|
|
||||||
db_quotas = pecan.request.storage.set_quotas(project_id, input_quotas)
|
db_quotas = pecan.request.storage.set_quotas(project_id, input_quotas)
|
||||||
|
|
||||||
quotas = [Quota.from_db_model(i) for i in db_quotas]
|
quotas = [Quota.from_db_model(i) for i in db_quotas]
|
||||||
|
|
||||||
return Quotas(project_id=project_id, quotas=quotas)
|
return Quotas(project_id=project_id, quotas=quotas)
|
||||||
|
@ -127,8 +127,10 @@ class TestAlarmsBase(v2.FunctionalTest):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestAlarmsBase, self).setUp()
|
super(TestAlarmsBase, self).setUp()
|
||||||
self.auth_headers = {'X-User-Id': uuidutils.generate_uuid(),
|
self.project_id = uuidutils.generate_uuid()
|
||||||
'X-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 = mock.Mock()
|
||||||
c.capabilities.list.return_value = {'aggregation_methods': [
|
c.capabilities.list.return_value = {'aggregation_methods': [
|
||||||
@ -1955,13 +1957,13 @@ class TestAlarmsHistory(TestAlarmsBase):
|
|||||||
|
|
||||||
|
|
||||||
class TestAlarmsQuotas(TestAlarmsBase):
|
class TestAlarmsQuotas(TestAlarmsBase):
|
||||||
|
def setUp(self):
|
||||||
def _test_alarm_quota(self):
|
super(TestAlarmsQuotas, self).setUp()
|
||||||
alarm = {
|
self.alarm = {
|
||||||
'name': 'alarm',
|
'name': 'alarm',
|
||||||
'type': 'gnocchi_aggregation_by_metrics_threshold',
|
'type': 'gnocchi_aggregation_by_metrics_threshold',
|
||||||
'user_id': self.auth_headers['X-User-Id'],
|
'user_id': self.user_id,
|
||||||
'project_id': self.auth_headers['X-Project-Id'],
|
'project_id': self.project_id,
|
||||||
RULE_KEY: {
|
RULE_KEY: {
|
||||||
'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
|
'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
|
||||||
'a1fb80f4-c242-4f57-87c6-68f47521059e'],
|
'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,
|
resp = self.post_json('/alarms', params=alarm,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers,
|
||||||
self.assertEqual(201, resp.status_code)
|
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)
|
alarms = self.get_json('/alarms', headers=self.auth_headers)
|
||||||
self.assertEqual(1, len(alarms))
|
self.assertEqual(1, len(alarms))
|
||||||
|
|
||||||
|
alarm = copy.copy(self.alarm)
|
||||||
alarm['name'] = 'another_user_alarm'
|
alarm['name'] = 'another_user_alarm'
|
||||||
resp = self.post_json('/alarms', params=alarm,
|
resp = self.post_json('/alarms', params=alarm,
|
||||||
expect_errors=True,
|
expect_errors=True,
|
||||||
headers=self.auth_headers)
|
headers=self.auth_headers,
|
||||||
self.assertEqual(403, resp.status_code)
|
status=403)
|
||||||
faultstring = 'Alarm quota exceeded for user'
|
faultstring = 'Alarm quota exceeded for user'
|
||||||
self.assertIn(faultstring,
|
self.assertIn(faultstring,
|
||||||
resp.json['error_message']['faultstring'])
|
resp.json['error_message']['faultstring'])
|
||||||
@ -2063,6 +2077,83 @@ class TestAlarmsQuotas(TestAlarmsBase):
|
|||||||
alarms = self.get_json('/alarms', headers=self.auth_headers)
|
alarms = self.get_json('/alarms', headers=self.auth_headers)
|
||||||
self.assertEqual(1, len(alarms))
|
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):
|
class TestAlarmsRuleThreshold(TestAlarmsBase):
|
||||||
|
|
||||||
|
@ -100,3 +100,93 @@ class TestQuotas(v2.FunctionalTest):
|
|||||||
expect_errors=True,
|
expect_errors=True,
|
||||||
status=403
|
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'])
|
||||||
|
Loading…
Reference in New Issue
Block a user