Merge "api: Add response body schemas for quota sets API"
This commit is contained in:
@@ -19,7 +19,7 @@ from oslo_utils import strutils
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack.compute.schemas import quota_sets
|
||||
from nova.api.openstack.compute.schemas import quota_sets as schema
|
||||
from nova.api.openstack import identity
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
@@ -31,32 +31,35 @@ from nova import objects
|
||||
from nova.policies import quota_sets as qs_policies
|
||||
from nova import quota
|
||||
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
QUOTAS = quota.QUOTAS
|
||||
|
||||
FILTERED_QUOTAS_2_36 = ["fixed_ips", "floating_ips",
|
||||
"security_group_rules", "security_groups"]
|
||||
FILTERED_QUOTAS_v236 = [
|
||||
'fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups'
|
||||
]
|
||||
|
||||
FILTERED_QUOTAS_2_57 = list(FILTERED_QUOTAS_2_36)
|
||||
FILTERED_QUOTAS_2_57.extend(['injected_files', 'injected_file_content_bytes',
|
||||
'injected_file_path_bytes'])
|
||||
FILTERED_QUOTAS_v257 = list(FILTERED_QUOTAS_v236)
|
||||
FILTERED_QUOTAS_v257.extend([
|
||||
'injected_files',
|
||||
'injected_file_content_bytes',
|
||||
'injected_file_path_bytes'
|
||||
])
|
||||
|
||||
|
||||
@validation.validated
|
||||
class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
def _format_quota_set(self, project_id, quota_set, filtered_quotas):
|
||||
"""Convert the quota object to a result dict."""
|
||||
result = {}
|
||||
if project_id:
|
||||
result = dict(id=str(project_id))
|
||||
else:
|
||||
result = {}
|
||||
result['id'] = str(project_id)
|
||||
|
||||
for resource in QUOTAS.resources:
|
||||
if (resource not in filtered_quotas and
|
||||
resource in quota_set):
|
||||
if resource not in filtered_quotas and resource in quota_set:
|
||||
result[resource] = quota_set[resource]
|
||||
return dict(quota_set=result)
|
||||
|
||||
return {'quota_set': result}
|
||||
|
||||
def _validate_quota_limit(self, resource, limit, minimum, maximum):
|
||||
def conv_inf(value):
|
||||
@@ -68,6 +71,7 @@ class QuotaSetsController(wsgi.Controller):
|
||||
"reserved %(minimum)s.") %
|
||||
{'limit': limit, 'resource': resource, 'minimum': minimum})
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if conv_inf(limit) > conv_inf(maximum):
|
||||
msg = (_("Quota limit %(limit)s for %(resource)s must be "
|
||||
"less than or equal to %(maximum)s.") %
|
||||
@@ -104,60 +108,59 @@ class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
def _get_filtered_quotas(self, req):
|
||||
if api_version_request.is_supported(req, '2.57'):
|
||||
return FILTERED_QUOTAS_2_57
|
||||
return FILTERED_QUOTAS_v257
|
||||
elif api_version_request.is_supported(req, '2.36'):
|
||||
return FILTERED_QUOTAS_2_36
|
||||
return FILTERED_QUOTAS_v236
|
||||
else:
|
||||
return []
|
||||
|
||||
@wsgi.expected_errors(400)
|
||||
@validation.query_schema(quota_sets.show_query, '2.0', '2.74')
|
||||
@validation.query_schema(quota_sets.show_query_v275, '2.75')
|
||||
@validation.query_schema(schema.show_query, '2.0', '2.74')
|
||||
@validation.query_schema(schema.show_query_v275, '2.75')
|
||||
@validation.response_body_schema(schema.show_response, '2.0', '2.35')
|
||||
@validation.response_body_schema(schema.show_response_v236, '2.36', '2.56')
|
||||
@validation.response_body_schema(schema.show_response_v257, '2.57')
|
||||
def show(self, req, id):
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._show(req, id, filtered_quotas)
|
||||
|
||||
def _show(self, req, id, filtered_quotas):
|
||||
context = req.environ['nova.context']
|
||||
context.can(qs_policies.POLICY_ROOT % 'show', {'project_id': id})
|
||||
identity.verify_project_id(context, id)
|
||||
|
||||
params = urlparse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._format_quota_set(
|
||||
id,
|
||||
self._get_quotas(context, id, user_id=user_id),
|
||||
filtered_quotas=filtered_quotas)
|
||||
|
||||
@wsgi.expected_errors(400)
|
||||
@validation.query_schema(quota_sets.show_query, '2.0', '2.74')
|
||||
@validation.query_schema(quota_sets.show_query_v275, '2.75')
|
||||
@validation.query_schema(schema.detail_query, '2.0', '2.74')
|
||||
@validation.query_schema(schema.detail_query_v275, '2.75')
|
||||
@validation.response_body_schema(schema.detail_response, '2.0', '2.35')
|
||||
@validation.response_body_schema(schema.detail_response_v236, '2.36', '2.56') # noqa: E501
|
||||
@validation.response_body_schema(schema.detail_response_v257, '2.57')
|
||||
def detail(self, req, id):
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._detail(req, id, filtered_quotas)
|
||||
|
||||
def _detail(self, req, id, filtered_quotas):
|
||||
context = req.environ['nova.context']
|
||||
context.can(qs_policies.POLICY_ROOT % 'detail', {'project_id': id})
|
||||
identity.verify_project_id(context, id)
|
||||
|
||||
user_id = req.GET.get('user_id', None)
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._format_quota_set(
|
||||
id,
|
||||
self._get_quotas(context, id, user_id=user_id, usages=True),
|
||||
filtered_quotas=filtered_quotas)
|
||||
|
||||
@wsgi.expected_errors(400)
|
||||
@validation.schema(quota_sets.update, '2.0', '2.35')
|
||||
@validation.schema(quota_sets.update_v236, '2.36', '2.56')
|
||||
@validation.schema(quota_sets.update_v257, '2.57')
|
||||
@validation.query_schema(quota_sets.show_query, '2.0', '2.74')
|
||||
@validation.query_schema(quota_sets.show_query_v275, '2.75')
|
||||
@validation.schema(schema.update, '2.0', '2.35')
|
||||
@validation.schema(schema.update_v236, '2.36', '2.56')
|
||||
@validation.schema(schema.update_v257, '2.57')
|
||||
@validation.query_schema(schema.update_query, '2.0', '2.74')
|
||||
@validation.query_schema(schema.update_query_v275, '2.75')
|
||||
@validation.response_body_schema(schema.update_response, '2.0', '2.35')
|
||||
@validation.response_body_schema(schema.update_response_v236, '2.36', '2.56') # noqa: E501
|
||||
@validation.response_body_schema(schema.update_response_v257, '2.57')
|
||||
def update(self, req, id, body):
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._update(req, id, body, filtered_quotas)
|
||||
|
||||
def _update(self, req, id, body, filtered_quotas):
|
||||
context = req.environ['nova.context']
|
||||
context.can(qs_policies.POLICY_ROOT % 'update', {'project_id': id})
|
||||
identity.verify_project_id(context, id)
|
||||
@@ -165,6 +168,7 @@ class QuotaSetsController(wsgi.Controller):
|
||||
project_id = id
|
||||
params = urlparse.parse_qs(req.environ.get('QUERY_STRING', ''))
|
||||
user_id = params.get('user_id', [None])[0]
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
|
||||
quota_set = body['quota_set']
|
||||
|
||||
@@ -221,27 +225,28 @@ class QuotaSetsController(wsgi.Controller):
|
||||
|
||||
@wsgi.api_version('2.0')
|
||||
@wsgi.expected_errors(400)
|
||||
@validation.query_schema(quota_sets.defaults_query)
|
||||
@validation.query_schema(schema.defaults_query)
|
||||
@validation.response_body_schema(schema.defaults_response, '2.0', '2.35')
|
||||
@validation.response_body_schema(schema.defaults_response_v236, '2.36', '2.56') # noqa: E501
|
||||
@validation.response_body_schema(schema.defaults_response_v257, '2.57')
|
||||
def defaults(self, req, id):
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._defaults(req, id, filtered_quotas)
|
||||
|
||||
def _defaults(self, req, id, filtered_quotas):
|
||||
context = req.environ['nova.context']
|
||||
context.can(qs_policies.POLICY_ROOT % 'defaults', {'project_id': id})
|
||||
identity.verify_project_id(context, id)
|
||||
|
||||
values = QUOTAS.get_defaults(context)
|
||||
return self._format_quota_set(id, values,
|
||||
filtered_quotas=filtered_quotas)
|
||||
filtered_quotas = self._get_filtered_quotas(req)
|
||||
return self._format_quota_set(
|
||||
id, values, filtered_quotas=filtered_quotas)
|
||||
|
||||
# TODO(oomichi): Here should be 204(No Content) instead of 202 by v2.1
|
||||
# +microversions because the resource quota-set has been deleted completely
|
||||
# when returning a response.
|
||||
@wsgi.expected_errors(())
|
||||
@validation.query_schema(quota_sets.show_query_v275, '2.75')
|
||||
@validation.query_schema(quota_sets.show_query, '2.0', '2.74')
|
||||
@wsgi.response(202)
|
||||
@validation.query_schema(schema.delete_query, '2.0', '2.74')
|
||||
@validation.query_schema(schema.delete_query_v275, '2.75')
|
||||
@validation.response_body_schema(schema.delete_response)
|
||||
def delete(self, req, id):
|
||||
context = req.environ['nova.context']
|
||||
context.can(qs_policies.POLICY_ROOT % 'delete', {'project_id': id})
|
||||
|
||||
@@ -20,7 +20,7 @@ update = {
|
||||
'properties': {
|
||||
'quota_class_set': {
|
||||
'type': 'object',
|
||||
'properties': quota_sets.quota_resources,
|
||||
'properties': quota_sets._quota_resources,
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ import copy
|
||||
|
||||
from nova.api.validation import parameter_types
|
||||
|
||||
common_quota = {
|
||||
_common_quota = {
|
||||
'type': ['integer', 'string'],
|
||||
'pattern': '^-?[0-9]+$',
|
||||
# -1 is a flag value for unlimited
|
||||
@@ -26,42 +26,47 @@ common_quota = {
|
||||
'maximum': 0x7FFFFFFF
|
||||
}
|
||||
|
||||
quota_resources = {
|
||||
'instances': common_quota,
|
||||
'cores': common_quota,
|
||||
'ram': common_quota,
|
||||
'floating_ips': common_quota,
|
||||
'fixed_ips': common_quota,
|
||||
'metadata_items': common_quota,
|
||||
'key_pairs': common_quota,
|
||||
'security_groups': common_quota,
|
||||
'security_group_rules': common_quota,
|
||||
'injected_files': common_quota,
|
||||
'injected_file_content_bytes': common_quota,
|
||||
'injected_file_path_bytes': common_quota,
|
||||
'server_groups': common_quota,
|
||||
'server_group_members': common_quota,
|
||||
_quota_resources = {
|
||||
'instances': _common_quota,
|
||||
'cores': _common_quota,
|
||||
'ram': _common_quota,
|
||||
'floating_ips': _common_quota,
|
||||
'fixed_ips': _common_quota,
|
||||
'metadata_items': _common_quota,
|
||||
'key_pairs': _common_quota,
|
||||
'security_groups': _common_quota,
|
||||
'security_group_rules': _common_quota,
|
||||
'injected_files': _common_quota,
|
||||
'injected_file_content_bytes': _common_quota,
|
||||
'injected_file_path_bytes': _common_quota,
|
||||
'server_groups': _common_quota,
|
||||
'server_group_members': _common_quota,
|
||||
# NOTE(stephenfin): This will always be rejected since it was nova-network
|
||||
# only, but we need to allow users to submit it at a minimum
|
||||
'networks': common_quota
|
||||
'networks': _common_quota
|
||||
}
|
||||
|
||||
update_quota_set = copy.deepcopy(quota_resources)
|
||||
update_quota_set.update({'force': parameter_types.boolean})
|
||||
_update_quota_set = copy.deepcopy(_quota_resources)
|
||||
_update_quota_set.update({'force': parameter_types.boolean})
|
||||
|
||||
update_quota_set_v236 = copy.deepcopy(update_quota_set)
|
||||
del update_quota_set_v236['fixed_ips']
|
||||
del update_quota_set_v236['floating_ips']
|
||||
del update_quota_set_v236['security_groups']
|
||||
del update_quota_set_v236['security_group_rules']
|
||||
del update_quota_set_v236['networks']
|
||||
_update_quota_set_v236 = copy.deepcopy(_update_quota_set)
|
||||
del _update_quota_set_v236['fixed_ips']
|
||||
del _update_quota_set_v236['floating_ips']
|
||||
del _update_quota_set_v236['security_groups']
|
||||
del _update_quota_set_v236['security_group_rules']
|
||||
del _update_quota_set_v236['networks']
|
||||
|
||||
_update_quota_set_v257 = copy.deepcopy(_update_quota_set_v236)
|
||||
del _update_quota_set_v257['injected_files']
|
||||
del _update_quota_set_v257['injected_file_content_bytes']
|
||||
del _update_quota_set_v257['injected_file_path_bytes']
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'quota_set': {
|
||||
'type': 'object',
|
||||
'properties': update_quota_set,
|
||||
'properties': _update_quota_set,
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
@@ -70,15 +75,10 @@ update = {
|
||||
}
|
||||
|
||||
update_v236 = copy.deepcopy(update)
|
||||
update_v236['properties']['quota_set']['properties'] = update_quota_set_v236
|
||||
update_v236['properties']['quota_set']['properties'] = _update_quota_set_v236
|
||||
|
||||
# 2.57 builds on 2.36 and removes injected_file* quotas.
|
||||
update_quota_set_v257 = copy.deepcopy(update_quota_set_v236)
|
||||
del update_quota_set_v257['injected_files']
|
||||
del update_quota_set_v257['injected_file_content_bytes']
|
||||
del update_quota_set_v257['injected_file_path_bytes']
|
||||
update_v257 = copy.deepcopy(update_v236)
|
||||
update_v257['properties']['quota_set']['properties'] = update_quota_set_v257
|
||||
update_v257['properties']['quota_set']['properties'] = _update_quota_set_v257
|
||||
|
||||
show_query = {
|
||||
'type': 'object',
|
||||
@@ -95,9 +95,194 @@ show_query = {
|
||||
show_query_v275 = copy.deepcopy(show_query)
|
||||
show_query_v275['additionalProperties'] = False
|
||||
|
||||
detail_query = copy.deepcopy(show_query)
|
||||
detail_query_v275 = copy.deepcopy(show_query_v275)
|
||||
|
||||
update_query = copy.deepcopy(show_query)
|
||||
update_query_v275 = copy.deepcopy(show_query_v275)
|
||||
|
||||
# TODO(stephenfin): Remove additionalProperties in a future API version
|
||||
defaults_query = {
|
||||
'type': 'object',
|
||||
'properties': {},
|
||||
'additionalProperties': True,
|
||||
}
|
||||
|
||||
delete_query = copy.deepcopy(show_query)
|
||||
delete_query_v275 = copy.deepcopy(show_query_v275)
|
||||
|
||||
_quota_response = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'cores': {'type': 'integer', 'minimum': -1},
|
||||
'fixed_ips': {'type': 'integer', 'minimum': -1},
|
||||
'floating_ips': {'type': 'integer', 'minimum': -1},
|
||||
'injected_file_content_bytes': {'type': 'integer', 'minimum': -1},
|
||||
'injected_file_path_bytes': {'type': 'integer', 'minimum': -1},
|
||||
'injected_files': {'type': 'integer', 'minimum': -1},
|
||||
'instances': {'type': 'integer', 'minimum': -1},
|
||||
'key_pairs': {'type': 'integer', 'minimum': -1},
|
||||
'metadata_items': {'type': 'integer', 'minimum': -1},
|
||||
'networks': {'type': 'integer', 'minimum': -1},
|
||||
'ram': {'type': 'integer', 'minimum': -1},
|
||||
'security_groups': {'type': 'integer', 'minimum': -1},
|
||||
'security_group_rules': {'type': 'integer', 'minimum': -1},
|
||||
'server_groups': {'type': 'integer', 'minimum': -1},
|
||||
'server_group_members': {'type': 'integer', 'minimum': -1},
|
||||
},
|
||||
'required': [
|
||||
# only networks is optional (it only appears under nova-network)
|
||||
'cores',
|
||||
'fixed_ips',
|
||||
'floating_ips',
|
||||
'injected_file_content_bytes',
|
||||
'injected_file_path_bytes',
|
||||
'injected_files',
|
||||
'instances',
|
||||
'key_pairs',
|
||||
'metadata_items',
|
||||
'ram',
|
||||
'security_groups',
|
||||
'security_group_rules',
|
||||
'server_groups',
|
||||
'server_group_members',
|
||||
],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
_quota_response_v236 = copy.deepcopy(_quota_response)
|
||||
for field in {
|
||||
'fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups'
|
||||
}:
|
||||
del _quota_response_v236['properties'][field]
|
||||
_quota_response_v236['required'].pop(
|
||||
_quota_response_v236['required'].index(field)
|
||||
)
|
||||
|
||||
_quota_response_v257 = copy.deepcopy(_quota_response_v236)
|
||||
for field in {
|
||||
'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes'
|
||||
}:
|
||||
del _quota_response_v257['properties'][field]
|
||||
_quota_response_v257['required'].pop(
|
||||
_quota_response_v257['required'].index(field)
|
||||
)
|
||||
|
||||
show_response = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'quota_set': copy.deepcopy(_quota_response),
|
||||
},
|
||||
'required': ['quota_set'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
show_response['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
show_response['properties']['quota_set']['required'].append('id')
|
||||
|
||||
show_response_v236 = copy.deepcopy(show_response)
|
||||
show_response_v236['properties']['quota_set'] = copy.deepcopy(
|
||||
_quota_response_v236
|
||||
)
|
||||
show_response_v236['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
show_response_v236['properties']['quota_set']['required'].append('id')
|
||||
|
||||
show_response_v257 = copy.deepcopy(show_response_v236)
|
||||
show_response_v257['properties']['quota_set'] = copy.deepcopy(
|
||||
_quota_response_v257
|
||||
)
|
||||
show_response_v257['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
show_response_v257['properties']['quota_set']['required'].append('id')
|
||||
|
||||
_detail_quota = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'in_use': {'type': 'integer', 'minimum': -1},
|
||||
'limit': {'type': 'integer', 'minimum': -1},
|
||||
'reserved': {'type': 'integer', 'minimum': -1},
|
||||
},
|
||||
'required': ['in_use', 'limit', 'reserved'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
_detail_quota_response = copy.deepcopy(_quota_response)
|
||||
for field in _detail_quota_response['properties']:
|
||||
if field == 'id':
|
||||
continue
|
||||
|
||||
_detail_quota_response['properties'][field] = _detail_quota
|
||||
|
||||
_detail_quota_response_v236 = copy.deepcopy(_detail_quota_response)
|
||||
for field in {
|
||||
'fixed_ips', 'floating_ips', 'security_group_rules', 'security_groups'
|
||||
}:
|
||||
del _detail_quota_response_v236['properties'][field]
|
||||
_detail_quota_response_v236['required'].pop(
|
||||
_detail_quota_response_v236['required'].index(field)
|
||||
)
|
||||
|
||||
_detail_quota_response_v257 = copy.deepcopy(_detail_quota_response_v236)
|
||||
for field in {
|
||||
'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes'
|
||||
}:
|
||||
del _detail_quota_response_v257['properties'][field]
|
||||
_detail_quota_response_v257['required'].pop(
|
||||
_detail_quota_response_v257['required'].index(field)
|
||||
)
|
||||
|
||||
detail_response = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'quota_set': copy.deepcopy(_detail_quota_response),
|
||||
},
|
||||
'required': ['quota_set'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
detail_response['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
detail_response['properties']['quota_set']['required'].append('id')
|
||||
|
||||
detail_response_v236 = copy.deepcopy(detail_response)
|
||||
detail_response_v236['properties']['quota_set'] = copy.deepcopy(
|
||||
_detail_quota_response_v236
|
||||
)
|
||||
detail_response_v236['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
detail_response_v236['properties']['quota_set']['required'].append('id')
|
||||
|
||||
detail_response_v257 = copy.deepcopy(detail_response_v236)
|
||||
detail_response_v257['properties']['quota_set'] = copy.deepcopy(
|
||||
_detail_quota_response_v257
|
||||
)
|
||||
detail_response_v257['properties']['quota_set']['properties'].update({
|
||||
'id': {'type': 'string'},
|
||||
})
|
||||
detail_response_v257['properties']['quota_set']['required'].append('id')
|
||||
|
||||
update_response = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'quota_set': _quota_response,
|
||||
},
|
||||
'required': ['quota_set'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
update_response_v236 = copy.deepcopy(update_response)
|
||||
update_response_v236['properties']['quota_set'] = _quota_response_v236
|
||||
|
||||
update_response_v257 = copy.deepcopy(update_response_v236)
|
||||
update_response_v257['properties']['quota_set'] = _quota_response_v257
|
||||
|
||||
defaults_response = copy.deepcopy(show_response)
|
||||
defaults_response_v236 = copy.deepcopy(show_response_v236)
|
||||
defaults_response_v257 = copy.deepcopy(show_response_v257)
|
||||
|
||||
delete_response = {'type': 'null'}
|
||||
|
||||
+123
-149
@@ -20,7 +20,7 @@ from oslo_limit import fixture as limit_fixture
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.compute import quota_sets as quotas_v21
|
||||
from nova.api.openstack.compute import quota_sets
|
||||
from nova.db import constants as db_const
|
||||
from nova import exception
|
||||
from nova.limit import local as local_limit
|
||||
@@ -32,12 +32,14 @@ from nova.tests.unit.api.openstack import fakes
|
||||
|
||||
|
||||
def quota_set(id, include_server_group_quotas=True):
|
||||
res = {'quota_set': {'id': id, 'metadata_items': 128,
|
||||
'ram': 51200, 'floating_ips': -1, 'fixed_ips': -1,
|
||||
'instances': 10, 'injected_files': 5, 'cores': 20,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': -1, 'security_group_rules': -1,
|
||||
'key_pairs': 100, 'injected_file_path_bytes': 255}}
|
||||
res = {
|
||||
'quota_set': {
|
||||
'id': id, 'metadata_items': 128,
|
||||
'ram': 51200, 'floating_ips': -1, 'fixed_ips': -1,
|
||||
'instances': 10, 'injected_files': 5, 'cores': 20,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'security_groups': -1, 'security_group_rules': -1,
|
||||
'key_pairs': 100, 'injected_file_path_bytes': 255}}
|
||||
if include_server_group_quotas:
|
||||
res['quota_set']['server_groups'] = 10
|
||||
res['quota_set']['server_group_members'] = 10
|
||||
@@ -47,7 +49,10 @@ def quota_set(id, include_server_group_quotas=True):
|
||||
class BaseQuotaSetsTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseQuotaSetsTest, self).setUp()
|
||||
super().setUp()
|
||||
|
||||
self.controller = quota_sets.QuotaSetsController()
|
||||
|
||||
# We need to stub out verify_project_id so that it doesn't
|
||||
# generate an EndpointNotFound exception and result in a
|
||||
# server error.
|
||||
@@ -56,13 +61,11 @@ class BaseQuotaSetsTest(test.TestCase):
|
||||
|
||||
|
||||
class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
plugin = quotas_v21
|
||||
validation_error = exception.ValidationError
|
||||
include_server_group_quotas = True
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSetsTestV21, self).setUp()
|
||||
self._setup_controller()
|
||||
self.default_quotas = {
|
||||
'instances': 10,
|
||||
'cores': 20,
|
||||
@@ -81,19 +84,15 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
self.default_quotas['server_groups'] = 10
|
||||
self.default_quotas['server_group_members'] = 10
|
||||
|
||||
def _setup_controller(self):
|
||||
self.controller = self.plugin.QuotaSetsController()
|
||||
|
||||
def _get_http_request(self, url=''):
|
||||
return fakes.HTTPRequest.blank(url)
|
||||
|
||||
def test_format_quota_set(self):
|
||||
quota_set = self.controller._format_quota_set('1234',
|
||||
self.default_quotas,
|
||||
[])
|
||||
quota_set = self.controller._format_quota_set(
|
||||
uuids.project_id, self.default_quotas, [])
|
||||
qs = quota_set['quota_set']
|
||||
|
||||
self.assertEqual(qs['id'], '1234')
|
||||
self.assertEqual(qs['id'], uuids.project_id)
|
||||
self.assertEqual(qs['instances'], 10)
|
||||
self.assertEqual(qs['cores'], 20)
|
||||
self.assertEqual(qs['ram'], 51200)
|
||||
@@ -171,9 +170,11 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
|
||||
def test_quotas_show(self):
|
||||
req = self._get_http_request()
|
||||
res_dict = self.controller.show(req, 1234)
|
||||
res_dict = self.controller.show(req, uuids.project_id)
|
||||
|
||||
ref_quota_set = quota_set('1234', self.include_server_group_quotas)
|
||||
ref_quota_set = quota_set(
|
||||
uuids.project_id, self.include_server_group_quotas
|
||||
)
|
||||
self.assertEqual(res_dict, ref_quota_set)
|
||||
|
||||
def test_quotas_update(self):
|
||||
@@ -197,8 +198,9 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
|
||||
@mock.patch('nova.api.validation.validators._SchemaValidator.validate')
|
||||
@mock.patch('nova.objects.Quotas.create_limit')
|
||||
def test_quotas_update_with_bad_data(self, mock_createlimit,
|
||||
mock_validate):
|
||||
def test_quotas_update_with_bad_data(
|
||||
self, mock_createlimit, mock_validate,
|
||||
):
|
||||
self.default_quotas.update({
|
||||
'instances': 50,
|
||||
'cores': -50
|
||||
@@ -278,69 +280,69 @@ class QuotaSetsTestV21(BaseQuotaSetsTest):
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
|
||||
def test_quotas_delete(self, mock_destroy_all_by_project):
|
||||
req = self._get_http_request()
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
self.assertEqual(202, self.controller.delete.wsgi_codes(req))
|
||||
mock_destroy_all_by_project.assert_called_once_with(
|
||||
req.environ['nova.context'], 1234)
|
||||
req.environ['nova.context'], uuids.project_id)
|
||||
|
||||
def test_duplicate_quota_filter(self):
|
||||
query_string = 'user_id=1&user_id=2'
|
||||
req = fakes.HTTPRequest.blank('', query_string=query_string)
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
def test_quota_filter_negative_int_as_string(self):
|
||||
req = fakes.HTTPRequest.blank('', query_string='user_id=-1')
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
def test_quota_filter_int_as_string(self):
|
||||
req = fakes.HTTPRequest.blank('', query_string='user_id=123')
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
def test_unknown_quota_filter(self):
|
||||
query_string = 'unknown_filter=abc'
|
||||
req = fakes.HTTPRequest.blank('', query_string=query_string)
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
def test_quota_additional_filter(self):
|
||||
query_string = 'user_id=1&additional_filter=2'
|
||||
req = fakes.HTTPRequest.blank('', query_string=query_string)
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
|
||||
class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
plugin = quotas_v21
|
||||
|
||||
def setUp(self):
|
||||
super(ExtendedQuotasTestV21, self).setUp()
|
||||
self._setup_controller()
|
||||
|
||||
fake_quotas = {'ram': {'limit': 51200,
|
||||
'in_use': 12800,
|
||||
'reserved': 12800},
|
||||
'cores': {'limit': 20,
|
||||
'in_use': 10,
|
||||
'reserved': 5},
|
||||
'instances': {'limit': 100,
|
||||
'in_use': 0,
|
||||
'reserved': 0}}
|
||||
|
||||
def _setup_controller(self):
|
||||
self.controller = self.plugin.QuotaSetsController()
|
||||
fake_quotas = {
|
||||
'cores': {'limit': 20, 'in_use': 10, 'reserved': 5},
|
||||
'fixed_ips': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'floating_ips': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'injected_file_content_bytes': {
|
||||
'limit': -1, 'in_use': 0, 'reserved': -1
|
||||
},
|
||||
'injected_file_path_bytes': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'injected_files': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'instances': {'limit': 100, 'in_use': 0, 'reserved': 0},
|
||||
'key_pairs': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'metadata_items': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'ram': {'limit': 51200, 'in_use': 12800, 'reserved': 12800},
|
||||
'security_groups': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'security_group_rules': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'server_groups': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
'server_group_members': {'limit': -1, 'in_use': 0, 'reserved': -1},
|
||||
}
|
||||
|
||||
def fake_get_quotas(self, context, id, user_id=None, usages=False):
|
||||
if usages:
|
||||
@@ -350,15 +352,10 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
|
||||
def fake_get_settable_quotas(self, context, project_id, user_id=None):
|
||||
return {
|
||||
'ram': {'minimum': self.fake_quotas['ram']['in_use'] +
|
||||
self.fake_quotas['ram']['reserved'],
|
||||
'maximum': -1},
|
||||
'cores': {'minimum': self.fake_quotas['cores']['in_use'] +
|
||||
self.fake_quotas['cores']['reserved'],
|
||||
'maximum': -1},
|
||||
'instances': {'minimum': self.fake_quotas['instances']['in_use'] +
|
||||
self.fake_quotas['instances']['reserved'],
|
||||
'maximum': -1},
|
||||
k: {
|
||||
'minimum': v['in_use'] + v['reserved'],
|
||||
'maximum': -1,
|
||||
} for k, v in self.fake_quotas.items()
|
||||
}
|
||||
|
||||
def _get_http_request(self, url=''):
|
||||
@@ -375,7 +372,7 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
|
||||
@mock.patch.object(quota.QUOTAS, 'get_settable_quotas')
|
||||
def test_quotas_force_update_exceed_in_used(self, get_settable_quotas):
|
||||
with mock.patch.object(self.plugin.QuotaSetsController,
|
||||
with mock.patch.object(quota_sets.QuotaSetsController,
|
||||
'_get_quotas') as _get_quotas:
|
||||
|
||||
body = {'quota_set': {'cores': 10, 'force': 'True'}}
|
||||
@@ -413,19 +410,12 @@ class ExtendedQuotasTestV21(BaseQuotaSetsTest):
|
||||
|
||||
|
||||
class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
plugin = quotas_v21
|
||||
plugin = quota_sets
|
||||
include_server_group_quotas = True
|
||||
|
||||
def setUp(self):
|
||||
super(UserQuotasTestV21, self).setUp()
|
||||
self._setup_controller()
|
||||
|
||||
def _get_http_request(self, url=''):
|
||||
return fakes.HTTPRequest.blank(url)
|
||||
|
||||
def _setup_controller(self):
|
||||
self.controller = self.plugin.QuotaSetsController()
|
||||
|
||||
def test_user_quotas_show(self):
|
||||
req = self._get_http_request(
|
||||
'/v2.1/os-quota-sets/%s?user_id=1' % fakes.FAKE_PROJECT_ID)
|
||||
@@ -496,19 +486,15 @@ class UserQuotasTestV21(BaseQuotaSetsTest):
|
||||
len(mock_createlimit.mock_calls))
|
||||
|
||||
|
||||
class QuotaSetsTestV236(test.NoDBTestCase):
|
||||
class QuotaSetsTestV236(BaseQuotaSetsTest):
|
||||
microversion = '2.36'
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSetsTestV236, self).setUp()
|
||||
# We need to stub out verify_project_id so that it doesn't
|
||||
# generate an EndpointNotFound exception and result in a
|
||||
# server error.
|
||||
self.stub_out('nova.api.openstack.identity.verify_project_id',
|
||||
lambda ctx, project_id: True)
|
||||
|
||||
self.old_req = fakes.HTTPRequest.blank('', version='2.1')
|
||||
self.filtered_quotas = ['fixed_ips', 'floating_ips',
|
||||
self.filtered_quotas = [
|
||||
'fixed_ips', 'floating_ips',
|
||||
'security_group_rules', 'security_groups']
|
||||
self.quotas = {
|
||||
'cores': {'limit': 20},
|
||||
@@ -542,58 +528,57 @@ class QuotaSetsTestV236(test.NoDBTestCase):
|
||||
'server_group_members': 10,
|
||||
'server_groups': 10
|
||||
}
|
||||
self.controller = quotas_v21.QuotaSetsController()
|
||||
self.controller = quota_sets.QuotaSetsController()
|
||||
self.req = fakes.HTTPRequest.blank('', version=self.microversion)
|
||||
|
||||
def _ensure_filtered_quotas_existed_in_old_api(self):
|
||||
res_dict = self.controller.show(self.old_req, 1234)
|
||||
def test_quotas_show_filtered(self):
|
||||
res_dict = self.controller.show(self.old_req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertIn(filtered, res_dict['quota_set'])
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quotas_show_filtered(self, mock_quotas):
|
||||
mock_quotas.return_value = self.quotas
|
||||
self._ensure_filtered_quotas_existed_in_old_api()
|
||||
res_dict = self.controller.show(self.req, 1234)
|
||||
res_dict = self.controller.show(self.req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertNotIn(filtered, res_dict['quota_set'])
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_defaults')
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quotas_default_filtered(self, mock_quotas, mock_defaults):
|
||||
mock_quotas.return_value = self.quotas
|
||||
self._ensure_filtered_quotas_existed_in_old_api()
|
||||
res_dict = self.controller.defaults(self.req, 1234)
|
||||
def test_quotas_default_filtered(self):
|
||||
res_dict = self.controller.defaults(self.old_req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertIn(filtered, res_dict['quota_set'])
|
||||
|
||||
res_dict = self.controller.defaults(self.req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertNotIn(filtered, res_dict['quota_set'])
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quotas_detail_filtered(self, mock_quotas):
|
||||
mock_quotas.return_value = self.quotas
|
||||
self._ensure_filtered_quotas_existed_in_old_api()
|
||||
res_dict = self.controller.detail(self.req, 1234)
|
||||
def test_quotas_detail_filtered(self):
|
||||
res_dict = self.controller.detail(self.old_req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertIn(filtered, res_dict['quota_set'])
|
||||
|
||||
res_dict = self.controller.detail(self.req, uuids.project_id)
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertNotIn(filtered, res_dict['quota_set'])
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quotas_update_input_filtered(self, mock_quotas):
|
||||
mock_quotas.return_value = self.quotas
|
||||
self._ensure_filtered_quotas_existed_in_old_api()
|
||||
def test_quotas_update_input_filtered(self):
|
||||
self.controller.update(
|
||||
self.old_req, uuids.project_id,
|
||||
body={'quota_set': {k: 100 for k in self.filtered_quotas}})
|
||||
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update, self.req, 1234,
|
||||
self.assertRaises(
|
||||
exception.ValidationError,
|
||||
self.controller.update, self.req, uuids.project_id,
|
||||
body={'quota_set': {filtered: 100}})
|
||||
|
||||
@mock.patch('nova.objects.Quotas.create_limit')
|
||||
@mock.patch('nova.quota.QUOTAS.get_settable_quotas')
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quotas_update_output_filtered(self, mock_quotas, mock_settable,
|
||||
mock_create_limit):
|
||||
mock_quotas.return_value = self.quotas
|
||||
mock_settable.return_value = {'cores': {'maximum': -1, 'minimum': 0}}
|
||||
self._ensure_filtered_quotas_existed_in_old_api()
|
||||
res_dict = self.controller.update(self.req, 1234,
|
||||
body={'quota_set': {'cores': 100}})
|
||||
def test_quotas_update_output_filtered(self):
|
||||
res_dict = self.controller.update(
|
||||
self.old_req, uuids.project_id,
|
||||
body={'quota_set': {'cores': 100}})
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertIn(filtered, res_dict['quota_set'])
|
||||
|
||||
res_dict = self.controller.update(
|
||||
self.req, uuids.project_id,
|
||||
body={'quota_set': {'cores': 101}})
|
||||
for filtered in self.filtered_quotas:
|
||||
self.assertNotIn(filtered, res_dict['quota_set'])
|
||||
|
||||
@@ -603,29 +588,20 @@ class QuotaSetsTestV257(QuotaSetsTestV236):
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaSetsTestV257, self).setUp()
|
||||
self.filtered_quotas.extend(quotas_v21.FILTERED_QUOTAS_2_57)
|
||||
self.filtered_quotas.extend(quota_sets.FILTERED_QUOTAS_v257)
|
||||
|
||||
|
||||
class QuotaSetsTestV275(QuotaSetsTestV257):
|
||||
microversion = '2.75'
|
||||
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
|
||||
@mock.patch('nova.objects.Quotas.create_limit')
|
||||
@mock.patch('nova.quota.QUOTAS.get_settable_quotas')
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_quota_additional_filter_older_version(self, mock_quotas,
|
||||
mock_settable,
|
||||
mock_create_limit,
|
||||
mock_destroy):
|
||||
mock_quotas.return_value = self.quotas
|
||||
mock_settable.return_value = {'cores': {'maximum': -1, 'minimum': 0}}
|
||||
def test_quota_additional_filter_older_version(self):
|
||||
query_string = 'additional_filter=2'
|
||||
req = fakes.HTTPRequest.blank('', version='2.74',
|
||||
query_string=query_string)
|
||||
self.controller.show(req, 1234)
|
||||
self.controller.update(req, 1234, body={'quota_set': {}})
|
||||
self.controller.detail(req, 1234)
|
||||
self.controller.delete(req, 1234)
|
||||
self.controller.show(req, uuids.project_id)
|
||||
self.controller.update(req, uuids.project_id, body={'quota_set': {}})
|
||||
self.controller.detail(req, uuids.project_id)
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
|
||||
def test_quota_update_additional_filter(self):
|
||||
query_string = 'user_id=1&additional_filter=2'
|
||||
@@ -639,33 +615,31 @@ class QuotaSetsTestV275(QuotaSetsTestV257):
|
||||
req = fakes.HTTPRequest.blank('', version=self.microversion,
|
||||
query_string=query_string)
|
||||
self.assertRaises(exception.ValidationError, self.controller.show,
|
||||
req, 1234)
|
||||
req, uuids.project_id)
|
||||
|
||||
def test_quota_detail_additional_filter(self):
|
||||
query_string = 'user_id=1&additional_filter=2'
|
||||
req = fakes.HTTPRequest.blank('', version=self.microversion,
|
||||
query_string=query_string)
|
||||
self.assertRaises(exception.ValidationError, self.controller.detail,
|
||||
req, 1234)
|
||||
req, uuids.project_id)
|
||||
|
||||
def test_quota_delete_additional_filter(self):
|
||||
query_string = 'user_id=1&additional_filter=2'
|
||||
req = fakes.HTTPRequest.blank('', version=self.microversion,
|
||||
query_string=query_string)
|
||||
self.assertRaises(exception.ValidationError, self.controller.delete,
|
||||
req, 1234)
|
||||
req, uuids.project_id)
|
||||
|
||||
|
||||
class NoopQuotaSetsTest(test.NoDBTestCase):
|
||||
class NoopQuotaSetsTest(BaseQuotaSetsTest):
|
||||
quota_driver = "nova.quota.NoopQuotaDriver"
|
||||
expected_detail = {'in_use': -1, 'limit': -1, 'reserved': -1}
|
||||
|
||||
def setUp(self):
|
||||
super(NoopQuotaSetsTest, self).setUp()
|
||||
self.flags(driver=self.quota_driver, group="quota")
|
||||
self.controller = quotas_v21.QuotaSetsController()
|
||||
self.stub_out('nova.api.openstack.identity.verify_project_id',
|
||||
lambda ctx, project_id: True)
|
||||
self.controller = quota_sets.QuotaSetsController()
|
||||
|
||||
def test_show_v21(self):
|
||||
req = fakes.HTTPRequest.blank("")
|
||||
@@ -845,16 +819,16 @@ class NoopQuotaSetsTest(test.NoDBTestCase):
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
|
||||
def test_quotas_delete(self, mock_destroy_all_by_project):
|
||||
req = fakes.HTTPRequest.blank("")
|
||||
self.controller.delete(req, "1234")
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
mock_destroy_all_by_project.assert_called_once_with(
|
||||
req.environ['nova.context'], "1234")
|
||||
req.environ['nova.context'], uuids.project_id)
|
||||
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project_and_user')
|
||||
def test_user_quotas_delete(self, mock_destroy_all_by_user):
|
||||
req = fakes.HTTPRequest.blank("?user_id=42")
|
||||
self.controller.delete(req, "1234")
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
mock_destroy_all_by_user.assert_called_once_with(
|
||||
req.environ['nova.context'], "1234", "42")
|
||||
req.environ['nova.context'], uuids.project_id, "42")
|
||||
|
||||
|
||||
class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest):
|
||||
@@ -1116,13 +1090,13 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest):
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
|
||||
def test_quotas_delete(self, mock_destroy_all_by_project):
|
||||
req = fakes.HTTPRequest.blank("")
|
||||
self.controller.delete(req, "1234")
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
# Ensure destroy isn't called for unified limits
|
||||
self.assertEqual(0, mock_destroy_all_by_project.call_count)
|
||||
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project_and_user')
|
||||
def test_user_quotas_delete(self, mock_destroy_all_by_user):
|
||||
req = fakes.HTTPRequest.blank("?user_id=42")
|
||||
self.controller.delete(req, "1234")
|
||||
self.controller.delete(req, uuids.project_id)
|
||||
# Ensure destroy isn't called for unified limits
|
||||
self.assertEqual(0, mock_destroy_all_by_user.call_count)
|
||||
@@ -74,6 +74,7 @@ class BasePolicyTest(test.TestCase):
|
||||
self.admin_project_id = uuids.admin_project_id
|
||||
self.project_id = uuids.project_id
|
||||
self.project_id_other = uuids.project_id_other
|
||||
self.project_id_unused = uuids.project_id_random
|
||||
|
||||
# all context are with implied roles.
|
||||
self.legacy_admin_context = nova_context.RequestContext(
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from nova.api.openstack.compute import quota_sets
|
||||
from nova import exception
|
||||
from nova.policies import quota_sets as policies
|
||||
from nova.tests import fixtures as nova_fixtures
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit.policies import base
|
||||
|
||||
@@ -30,10 +29,11 @@ class QuotaSetsPolicyTest(base.BasePolicyTest):
|
||||
def setUp(self):
|
||||
super(QuotaSetsPolicyTest, self).setUp()
|
||||
self.controller = quota_sets.QuotaSetsController()
|
||||
self.controller._validate_quota_limit = mock.MagicMock()
|
||||
self.req = fakes.HTTPRequest.blank('')
|
||||
self.project_id = self.req.environ['nova.context'].project_id
|
||||
|
||||
self.useFixture(nova_fixtures.NoopQuotaDriverFixture())
|
||||
|
||||
# With legacy rule all admin is able to update or revert their quota
|
||||
# to default or get other project quota.
|
||||
self.project_admin_authorized_contexts = set([
|
||||
@@ -42,9 +42,7 @@ class QuotaSetsPolicyTest(base.BasePolicyTest):
|
||||
# With legacy rule, everyone is able to get their own quota.
|
||||
self.project_reader_authorized_contexts = set([
|
||||
self.legacy_admin_context, self.system_admin_context,
|
||||
self.project_admin_context,
|
||||
self.system_member_context, self.system_reader_context,
|
||||
self.system_foo_context, self.project_manager_context,
|
||||
self.project_admin_context, self.project_manager_context,
|
||||
self.project_member_context, self.project_reader_context,
|
||||
self.project_foo_context,
|
||||
self.other_project_manager_context,
|
||||
@@ -63,20 +61,17 @@ class QuotaSetsPolicyTest(base.BasePolicyTest):
|
||||
self.other_project_member_context,
|
||||
self.other_project_reader_context, self.service_context])
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
@mock.patch('nova.quota.QUOTAS.get_settable_quotas')
|
||||
def test_update_quota_sets_policy(self, mock_update, mock_get):
|
||||
def test_update_quota_sets_policy(self):
|
||||
rule_name = policies.POLICY_ROOT % 'update'
|
||||
body = {'quota_set': {
|
||||
'instances': 50,
|
||||
'cores': 50}
|
||||
}
|
||||
body = {'quota_set': {'instances': 50, 'cores': 50}}
|
||||
|
||||
for cxtx in self.project_admin_authorized_contexts:
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
self.controller.update(req, cxtx.project_id, body=body)
|
||||
for cxtx in (self.all_contexts -
|
||||
set(self.project_admin_authorized_contexts)):
|
||||
for cxtx in (
|
||||
self.all_contexts - set(self.project_admin_authorized_contexts)
|
||||
):
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
exc = self.assertRaises(
|
||||
@@ -86,15 +81,15 @@ class QuotaSetsPolicyTest(base.BasePolicyTest):
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
|
||||
def test_delete_quota_sets_policy(self, mock_delete):
|
||||
def test_delete_quota_sets_policy(self):
|
||||
rule_name = policies.POLICY_ROOT % 'delete'
|
||||
for cxtx in self.project_admin_authorized_contexts:
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
self.controller.delete(req, cxtx.project_id)
|
||||
for cxtx in (self.all_contexts -
|
||||
set(self.project_admin_authorized_contexts)):
|
||||
for cxtx in (
|
||||
self.all_contexts - set(self.project_admin_authorized_contexts)
|
||||
):
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
exc = self.assertRaises(
|
||||
@@ -104,41 +99,62 @@ class QuotaSetsPolicyTest(base.BasePolicyTest):
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_defaults')
|
||||
def test_default_quota_sets_policy(self, mock_default):
|
||||
def test_default_quota_sets_policy(self):
|
||||
rule_name = policies.POLICY_ROOT % 'defaults'
|
||||
self.common_policy_auth(self.everyone_authorized_contexts,
|
||||
rule_name,
|
||||
self.controller.defaults,
|
||||
self.req, self.project_id)
|
||||
self.req, self.project_id_unused)
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_detail_quota_sets_policy(self, mock_get):
|
||||
def test_detail_quota_sets_policy(self):
|
||||
rule_name = policies.POLICY_ROOT % 'detail'
|
||||
self.common_policy_auth(self.project_admin_authorized_contexts,
|
||||
rule_name,
|
||||
self.controller.detail,
|
||||
self.req, 'try-other-project')
|
||||
self.req, self.project_id_unused)
|
||||
# Check if project reader or higher roles are able to get
|
||||
# their own quota
|
||||
for cxtx in self.project_reader_authorized_contexts:
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
self.controller.detail(req, cxtx.project_id)
|
||||
self.controller.detail(req, cxtx.project_id or self.project_id)
|
||||
for cxtx in (
|
||||
self.all_contexts - self.project_reader_authorized_contexts
|
||||
):
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
exc = self.assertRaises(
|
||||
exception.PolicyNotAuthorized, self.controller.detail,
|
||||
req, cxtx.project_id or self.project_id)
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
@mock.patch('nova.quota.QUOTAS.get_project_quotas')
|
||||
def test_show_quota_sets_policy(self, mock_get):
|
||||
def test_show_quota_sets_policy(self):
|
||||
rule_name = policies.POLICY_ROOT % 'show'
|
||||
self.common_policy_auth(self.project_admin_authorized_contexts,
|
||||
rule_name,
|
||||
self.controller.show,
|
||||
self.req, 'try-other-project')
|
||||
self.req, self.project_id_unused)
|
||||
# Check if project reader or higher roles are able to get
|
||||
# their own quota
|
||||
for cxtx in self.project_reader_authorized_contexts:
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
self.controller.show(req, cxtx.project_id)
|
||||
self.controller.show(req, cxtx.project_id or self.project_id)
|
||||
cnt = 0
|
||||
for cxtx in (
|
||||
self.all_contexts - self.project_reader_authorized_contexts
|
||||
):
|
||||
cnt += 1
|
||||
req = fakes.HTTPRequest.blank('')
|
||||
req.environ['nova.context'] = cxtx
|
||||
exc = self.assertRaises(
|
||||
exception.PolicyNotAuthorized, self.controller.show,
|
||||
req, cxtx.project_id or self.project_id)
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
|
||||
class QuotaSetsNoLegacyNoScopePolicyTest(QuotaSetsPolicyTest):
|
||||
@@ -154,17 +170,17 @@ class QuotaSetsNoLegacyNoScopePolicyTest(QuotaSetsPolicyTest):
|
||||
# Even with no legacy rule, because any admin requesting
|
||||
# update/revert quota for their own project will be allowed.
|
||||
# And any admin will be able to get other project quota.
|
||||
self.project_admin_authorized_contexts = [
|
||||
self.project_admin_authorized_contexts = set([
|
||||
self.legacy_admin_context, self.system_admin_context,
|
||||
self.project_admin_context]
|
||||
# With no legacy rule, other project and foo role will not be
|
||||
# able to get the quota.
|
||||
self.project_reader_authorized_contexts = [
|
||||
self.project_admin_context])
|
||||
# With no legacy rule, foo role will not be able to get the quota.
|
||||
self.project_reader_authorized_contexts = set([
|
||||
self.legacy_admin_context, self.system_admin_context,
|
||||
self.project_admin_context,
|
||||
self.system_member_context, self.system_reader_context,
|
||||
self.project_manager_context, self.project_member_context,
|
||||
self.project_reader_context]
|
||||
self.project_admin_context, self.project_manager_context,
|
||||
self.project_member_context, self.project_reader_context,
|
||||
self.other_project_manager_context,
|
||||
self.other_project_member_context,
|
||||
self.other_project_reader_context])
|
||||
|
||||
|
||||
class QuotaSetsScopeTypePolicyTest(QuotaSetsPolicyTest):
|
||||
|
||||
Reference in New Issue
Block a user