Add domain level limit support - API

Add domain_id parameter for limit creation API

bp: domain-level-limit
Change-Id: I9f3d999f5f07fbb6bdbaf9410e15efefae19d262
This commit is contained in:
wangxiyuan 2018-12-05 11:48:49 +08:00
parent 347269184e
commit df173c7c90
6 changed files with 246 additions and 56 deletions

View File

@ -20,8 +20,6 @@ from keystone.common import json_home
from keystone.common import provider_api
from keystone.common import rbac_enforcer
from keystone.common import validation
from keystone import exception
from keystone.i18n import _
from keystone.limit import schema
from keystone.server import flask as ks_flask
@ -34,34 +32,34 @@ class LimitsResource(ks_flask.ResourceBase):
collection_key = 'limits'
member_key = 'limit'
json_home_resource_status = json_home.Status.EXPERIMENTAL
get_member_from_driver = PROVIDERS.deferred_provider_lookup(
api='unified_limit_api', method='get_limit')
def _list_limits(self):
filters = ['service_id', 'region_id', 'resource_name', 'project_id']
filters = ['service_id', 'region_id', 'resource_name', 'project_id',
'domain_id']
ENFORCER.enforce_call(action='identity:list_limits', filters=filters)
hints = self.build_driver_hints(filters)
project_id_filter = hints.get_exact_filter_by_name('project_id')
if project_id_filter:
domain_id_filter = hints.get_exact_filter_by_name('domain_id')
if project_id_filter or domain_id_filter:
if self.oslo_context.system_scope:
refs = PROVIDERS.unified_limit_api.list_limits(hints)
else:
refs = []
else:
project_id = self.oslo_context.project_id
domain_id = self.oslo_context.domain_id
if project_id:
hints.add_filter('project_id', project_id)
elif domain_id:
hints.add_filter('domain_id', domain_id)
refs = PROVIDERS.unified_limit_api.list_limits(hints)
return self.wrap_collection(refs, hints=hints)
def _get_limit(self, limit_id):
ENFORCER.enforce_call(action='identity:get_limit')
ref = PROVIDERS.unified_limit_api.get_limit(limit_id)
if (not self.oslo_context.is_admin and
not ('admin' in self.oslo_context.roles)):
project_id = self.oslo_context.project_id
if project_id and project_id != ref['project_id']:
action = _('The authenticated project should match the '
'project_id')
raise exception.Forbidden(action=action)
return self.wrap_member(ref)
def get(self, limit_id=None):

View File

@ -26,14 +26,10 @@ limit_policies = [
'method': 'HEAD'}]),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'get_limit',
check_str='',
# Getting a single limit or listing all limits should be information
# accessible to everyone. By setting scope_types=['system', 'project']
# we're making it so that anyone with a role on the system or a project
# can obtain this information. Making changes to a limit should be
# considered a protected system-level API, as noted below with
# scope_types=['system'].
scope_types=['system', 'project'],
check_str='(role:reader and system_scope:all) or '
'project_id:%(target.limit.project_id)s or '
'domain_id:%(target.limit.domain_id)s',
scope_types=['system', 'project', 'domain'],
description='Show limit details.',
operations=[{'path': '/v3/limits/{limit_id}',
'method': 'GET'},

View File

@ -51,7 +51,7 @@ registered_limit_update = {
'additionalProperties': False,
}
_limit_create_properties = {
_project_limit_create_properties = {
'project_id': parameter_types.id_string,
'service_id': parameter_types.id_string,
'region_id': {
@ -70,11 +70,39 @@ _limit_create_properties = {
'description': validation.nullable(parameter_types.description)
}
_domain_limit_create_properties = {
'domain_id': parameter_types.id_string,
'service_id': parameter_types.id_string,
'region_id': {
'type': 'string'
},
'resource_name': {
'type': 'string',
'minLength': 1,
'maxLength': 255
},
'resource_limit': {
'type': 'integer',
'minimum': -1,
'maximum': 0x7FFFFFFF # The maximum value a signed INT may have
},
'description': validation.nullable(parameter_types.description)
}
_limit_create = {
'type': 'object',
'properties': _limit_create_properties,
'additionalProperties': False,
'required': ['project_id', 'service_id', 'resource_name', 'resource_limit']
'oneOf': [
{'properties': _project_limit_create_properties,
'required': ['project_id', 'service_id', 'resource_name',
'resource_limit'],
'additionalProperties': False,
},
{'properties': _domain_limit_create_properties,
'required': ['domain_id', 'service_id', 'resource_name',
'resource_limit'],
'additionalProperties': False,
},
]
}
limit_create = {

View File

@ -503,7 +503,6 @@ def new_registered_limit_ref(**kwargs):
def new_limit_ref(**kwargs):
ref = {
'project_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'resource_name': uuid.uuid4().hex,
'resource_limit': 10,

View File

@ -597,7 +597,36 @@ class LimitsTestCase(test_v3.RestfulTestCase):
token=self.system_admin_token,
expected_status=http_client.CREATED)
def test_create_limit(self):
# Create more assignments, all are:
#
# self.user -- admin -- self.project
# self.user -- non-admin -- self.project_2
# self.user -- admin -- self.domain
# self.user -- non-admin -- self.domain_2
# self.user -- admin -- system
self.project_2 = unit.new_project_ref(domain_id=self.domain_id)
self.project_2_id = self.project_2['id']
PROVIDERS.resource_api.create_project(self.project_2_id,
self.project_2)
self.domain_2 = unit.new_domain_ref()
self.domain_2_id = self.domain_2['id']
PROVIDERS.resource_api.create_domain(self.domain_2_id, self.domain_2)
self.role_2 = unit.new_role_ref(name='non-admin')
self.role_2_id = self.role_2['id']
PROVIDERS.role_api.create_role(self.role_2_id, self.role_2)
PROVIDERS.assignment_api.create_grant(
self.role_2_id, user_id=self.user_id, project_id=self.project_2_id)
PROVIDERS.assignment_api.create_grant(
self.role_id, user_id=self.user_id, domain_id=self.domain_id)
PROVIDERS.assignment_api.create_grant(
self.role_2_id, user_id=self.user_id, domain_id=self.domain_2_id)
PROVIDERS.assignment_api.create_system_grant_for_user(
self.user_id, self.role_id)
def test_create_project_limit(self):
ref = unit.new_limit_ref(project_id=self.project_id,
service_id=self.service_id,
region_id=self.region_id,
@ -610,9 +639,27 @@ class LimitsTestCase(test_v3.RestfulTestCase):
limits = r.result['limits']
self.assertIsNotNone(limits[0]['id'])
self.assertIsNotNone(limits[0]['project_id'])
self.assertIsNone(limits[0]['domain_id'])
for key in ['service_id', 'region_id', 'resource_name',
'resource_limit', 'description']:
'resource_limit', 'description', 'project_id']:
self.assertEqual(limits[0][key], ref[key])
def test_create_domain_limit(self):
ref = unit.new_limit_ref(domain_id=self.domain_id,
service_id=self.service_id,
region_id=self.region_id,
resource_name='volume')
r = self.post(
'/limits',
body={'limits': [ref]},
token=self.system_admin_token,
expected_status=http_client.CREATED)
limits = r.result['limits']
self.assertIsNotNone(limits[0]['id'])
self.assertIsNone(limits[0]['project_id'])
for key in ['service_id', 'region_id', 'resource_name',
'resource_limit', 'description', 'domain_id']:
self.assertEqual(limits[0][key], ref[key])
def test_create_limit_without_region(self):
@ -879,31 +926,12 @@ class LimitsTestCase(test_v3.RestfulTestCase):
self.assertEqual(limits[0][key], ref1[key])
def test_list_limit_with_project_id_filter(self):
# Create a new projects and a 'non-admin' role for test. The assignment
# is like:
#
# self.user -- admin -- self.project (default)
# self.user -- non-admin -- the new project
# self.user -- admin -- system
project_2 = unit.new_project_ref(domain_id=self.domain_id)
project_2_id = project_2['id']
PROVIDERS.resource_api.create_project(project_2_id, project_2)
role_2 = unit.new_role_ref(name='non-admin')
role_2_id = role_2['id']
PROVIDERS.role_api.create_role(role_2_id, role_2)
PROVIDERS.assignment_api.add_role_to_user_and_project(
self.user_id, project_2_id, role_2_id)
PROVIDERS.assignment_api.create_system_grant_for_user(
self.user_id, self.role['id'])
# create two limit in different projects for test.
ref1 = unit.new_limit_ref(project_id=self.project_id,
service_id=self.service_id,
region_id=self.region_id,
resource_name='volume')
ref2 = unit.new_limit_ref(project_id=project_2_id,
ref2 = unit.new_limit_ref(project_id=self.project_2_id,
service_id=self.service_id2,
resource_name='snapshot')
self.post(
@ -922,10 +950,10 @@ class LimitsTestCase(test_v3.RestfulTestCase):
'/limits', expected_status=http_client.OK,
auth=self.build_authentication_request(
user_id=self.user['id'], password=self.user['password'],
project_id=project_2_id))
project_id=self.project_2_id))
limits = r.result['limits']
self.assertEqual(1, len(limits))
self.assertEqual(project_2_id, limits[0]['project_id'])
self.assertEqual(self.project_2_id, limits[0]['project_id'])
# if non system scoped request contain project_id filter, keystone
# will return an empty list.
@ -947,7 +975,60 @@ class LimitsTestCase(test_v3.RestfulTestCase):
self.assertEqual(1, len(limits))
self.assertEqual(self.project_id, limits[0]['project_id'])
def test_show_limit(self):
def test_list_limit_with_domain_id_filter(self):
# create two limit in different domains for test.
ref1 = unit.new_limit_ref(domain_id=self.domain_id,
service_id=self.service_id,
region_id=self.region_id,
resource_name='volume')
ref2 = unit.new_limit_ref(domain_id=self.domain_2_id,
service_id=self.service_id2,
resource_name='snapshot')
self.post(
'/limits',
body={'limits': [ref1, ref2]},
token=self.system_admin_token,
expected_status=http_client.CREATED)
# non system scoped request will get the limits in its domain.
r = self.get(
'/limits', expected_status=http_client.OK,
auth=self.build_authentication_request(
user_id=self.user['id'], password=self.user['password'],
domain_id=self.domain_id))
limits = r.result['limits']
self.assertEqual(1, len(limits))
self.assertEqual(self.domain_id, limits[0]['domain_id'])
r = self.get(
'/limits', expected_status=http_client.OK,
auth=self.build_authentication_request(
user_id=self.user['id'], password=self.user['password'],
domain_id=self.domain_2_id))
limits = r.result['limits']
self.assertEqual(1, len(limits))
self.assertEqual(self.domain_2_id, limits[0]['domain_id'])
# if non system scoped request contain domain_id filter, keystone
# will return an empty list.
r = self.get(
'/limits?domain_id=%s' % self.domain_id,
expected_status=http_client.OK)
limits = r.result['limits']
self.assertEqual(0, len(limits))
# a system scoped request can specify the domain_id filter
r = self.get(
'/limits?domain_id=%s' % self.domain_id,
expected_status=http_client.OK,
auth=self.build_authentication_request(
user_id=self.user['id'], password=self.user['password'],
system=True))
limits = r.result['limits']
self.assertEqual(1, len(limits))
self.assertEqual(self.domain_id, limits[0]['domain_id'])
def test_show_project_limit(self):
ref1 = unit.new_limit_ref(project_id=self.project_id,
service_id=self.service_id,
region_id=self.region_id,
@ -960,14 +1041,42 @@ class LimitsTestCase(test_v3.RestfulTestCase):
body={'limits': [ref1, ref2]},
token=self.system_admin_token,
expected_status=http_client.CREATED)
id1 = r.result['limits'][0]['id']
if r.result['limits'][0]['resource_name'] == 'volume':
id1 = r.result['limits'][0]['id']
else:
id1 = r.result['limits'][1]['id']
self.get('/limits/fake_id',
expected_status=http_client.NOT_FOUND)
r = self.get('/limits/%s' % id1,
expected_status=http_client.OK)
limit = r.result['limit']
self.assertIsNone(limit['domain_id'])
for key in ['service_id', 'region_id', 'resource_name',
'resource_limit', 'description']:
'resource_limit', 'description', 'project_id']:
self.assertEqual(limit[key], ref1[key])
def test_show_domain_limit(self):
ref1 = unit.new_limit_ref(domain_id=self.domain_id,
service_id=self.service_id2,
resource_name='snapshot')
r = self.post(
'/limits',
body={'limits': [ref1]},
token=self.system_admin_token,
expected_status=http_client.CREATED)
id1 = r.result['limits'][0]['id']
r = self.get('/limits/%s' % id1,
expected_status=http_client.OK,
auth=self.build_authentication_request(
user_id=self.user['id'],
password=self.user['password'],
domain_id=self.domain_id))
limit = r.result['limit']
self.assertIsNone(limit['project_id'])
self.assertIsNone(limit['region_id'])
for key in ['service_id', 'resource_name', 'resource_limit',
'description', 'domain_id']:
self.assertEqual(limit[key], ref1[key])
def test_delete_limit(self):
@ -1528,3 +1637,15 @@ class StrictTwoLevelLimitsTestCase(LimitsTestCase):
def test_create_limit_with_domain_as_project(self):
self.skipTest('enable this test once strict two level model support'
'domain level check.')
def test_create_domain_limit(self):
self.skipTest('enable this test once strict two level model support'
'domain level check.')
def test_show_domain_limit(self):
self.skipTest('enable this test once strict two level model support'
'domain level check.')
def test_list_limit_with_domain_id_filter(self):
self.skipTest('enable this test once strict two level model support'
'domain level check.')

View File

@ -2675,7 +2675,7 @@ class LimitValidationTestCase(unit.BaseTestCase):
self.create_registered_limits_validator.validate,
request_to_validate)
def test_validate_limit_create_request_succeeds(self):
def test_validate_project_limit_create_request_succeeds(self):
request_to_validate = [{'project_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
@ -2684,6 +2684,15 @@ class LimitValidationTestCase(unit.BaseTestCase):
'description': 'test description'}]
self.create_limits_validator.validate(request_to_validate)
def test_validate_domain_limit_create_request_succeeds(self):
request_to_validate = [{'domain_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
'resource_name': 'volume',
'resource_limit': 10,
'description': 'test description'}]
self.create_limits_validator.validate(request_to_validate)
def test_validate_limit_create_request_without_optional(self):
request_to_validate = [{'project_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
@ -2731,6 +2740,17 @@ class LimitValidationTestCase(unit.BaseTestCase):
self.create_limits_validator.validate,
request_to_validate)
def test_validate_limit_create_request_with_invalid_domain(self):
request_to_validate = [{'domain_id': 'fake_id',
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
'resource_name': 'volume',
'resource_limit': 10,
'description': 'test description'}]
self.assertRaises(exception.SchemaValidationError,
self.create_limits_validator.validate,
request_to_validate)
def test_validate_limit_update_request_with_invalid_input(self):
_INVALID_FORMATS = [{'resource_name': 123},
{'resource_limit': 'not_int'},
@ -2770,9 +2790,12 @@ class LimitValidationTestCase(unit.BaseTestCase):
self.update_limits_validator.validate,
request_to_validate)
def test_validate_limit_create_request_without_required_fails(self):
for key in ['service_id', 'resource_name', 'resource_limit']:
request_to_validate = [{'service_id': uuid.uuid4().hex,
def test_validate_project_limit_create_request_without_required_fails(
self):
for key in ['project_id', 'service_id', 'resource_name',
'resource_limit']:
request_to_validate = [{'project_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
'resource_name': 'volume',
'resource_limit': 10}]
@ -2781,6 +2804,31 @@ class LimitValidationTestCase(unit.BaseTestCase):
self.create_limits_validator.validate,
request_to_validate)
def test_validate_domain_limit_create_request_without_required_fails(self):
for key in ['domain_id', 'service_id', 'resource_name',
'resource_limit']:
request_to_validate = [{'domain_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
'resource_name': 'volume',
'resource_limit': 10}]
request_to_validate[0].pop(key)
self.assertRaises(exception.SchemaValidationError,
self.create_limits_validator.validate,
request_to_validate)
def test_validate_limit_create_request_with_both_project_and_domain(self):
request_to_validate = [{'project_id': uuid.uuid4().hex,
'domain_id': uuid.uuid4().hex,
'service_id': uuid.uuid4().hex,
'region_id': 'RegionOne',
'resource_name': 'volume',
'resource_limit': 10,
'description': 'test description'}]
self.assertRaises(exception.SchemaValidationError,
self.create_limits_validator.validate,
request_to_validate)
class ApplicationCredentialValidatorTestCase(unit.TestCase):
_valid_roles = [{'name': 'member'},