Fix quota class set APIs

v2.1 API which does not return the 'server_groups' and
'server_group_members' quotas in GET & PUT os-quota-class-sets
API response. v2 API used to return those keys in API response.

Also filter out the network related quotas from os-quota-class-sets
APIs

Fixing this with microversion.
Closes-Bug: #1701211
Closes-Bug: #1693168
implement-blueprint fix-quota-classes-api

Change-Id: I66aeb7a92fb5ee906fead78030bd84a2e97916e8
This commit is contained in:
ghanshyam 2017-07-06 08:58:48 +03:00 committed by Ghanshyam Mann
parent e332797e42
commit 92e0efeefd
17 changed files with 260 additions and 72 deletions

View File

@ -26,14 +26,18 @@ Nova supports implicit 'default' Quota Class only.
for more details. for more details.
.. warning:: .. warning::
There is a bug in the v2.1 API and the legacy v2 compatible API which does There is a bug in the v2.1 API till microversion 2.49 and
not return the ``server_groups`` and ``server_group_members`` quotas in the legacy v2 compatible API which does not return the
``server_groups`` and ``server_group_members`` quotas in
GET and PUT ``os-quota-class-sets`` API response, whereas the v2 API GET and PUT ``os-quota-class-sets`` API response, whereas the v2 API
used to return those keys in the API response. used to return those keys in the API response.
There is workaround to get the ``server_groups`` and There is workaround to get the ``server_groups`` and
``server_group_members`` quotas using ``server_group_members`` quotas using
:ref:`list-default-quotas-for-tenant` :ref:`list-default-quotas-for-tenant`
API but that is per project quota. API but that is per project quota.
This issue is fixed in microversion 2.50, here onwards
``server_groups`` and ``server_group_members`` keys are
returned in API response body.
Show the quota for Quota Class Show the quota for Quota Class
============================== ==============================
@ -72,11 +76,13 @@ Response
- ram: ram_quota_class - ram: ram_quota_class
- security_group_rules: security_group_rules_quota_class - security_group_rules: security_group_rules_quota_class
- security_groups: security_groups_quota_class - security_groups: security_groups_quota_class
- server_groups: server_groups_quota_class
- server_group_members: server_group_members_quota_class
- networks: networks_quota_optional - networks: networks_quota_optional
**Example Show A Quota Class: JSON response** **Example Show A Quota Class: JSON response(2.50)**
.. literalinclude:: ../../doc/api_samples/os-quota-class-sets/quota-classes-show-get-resp.json .. literalinclude:: ../../doc/api_samples/os-quota-class-sets/v2.50/quota-classes-show-get-resp.json
:language: javascript :language: javascript
Create or Update Quotas for Quota Class Create or Update Quotas for Quota Class
@ -117,9 +123,9 @@ Request
- server_group_members: server_group_members_quota_optional - server_group_members: server_group_members_quota_optional
- networks: networks_quota_optional - networks: networks_quota_optional
**Example Update Quotas: JSON request** **Example Update Quotas: JSON request(2.50)**
.. literalinclude:: ../../doc/api_samples/os-quota-class-sets/quota-classes-update-post-req.json .. literalinclude:: ../../doc/api_samples/os-quota-class-sets/v2.50/quota-classes-update-post-req.json
:language: javascript :language: javascript
Response Response
@ -140,9 +146,11 @@ Response
- ram: ram_quota_class - ram: ram_quota_class
- security_group_rules: security_group_rules_quota_class - security_group_rules: security_group_rules_quota_class
- security_groups: security_groups_quota_class - security_groups: security_groups_quota_class
- server_groups: server_groups_quota_class
- server_group_members: server_group_members_quota_class
- networks: networks_quota_optional - networks: networks_quota_optional
**Example Update Quotas: JSON response** **Example Update Quotas: JSON response(2.50)**
.. literalinclude:: ../../doc/api_samples/os-quota-class-sets/quota-classes-update-post-resp.json .. literalinclude:: ../../doc/api_samples/os-quota-class-sets/v2.50/quota-classes-update-post-resp.json
:language: javascript :language: javascript

View File

@ -3813,6 +3813,7 @@ networks_quota_optional: &networks_quota_optional
in: body in: body
required: false required: false
type: integer type: integer
max_version: 2.50
networks_quota_set_optional: networks_quota_set_optional:
<<: *networks_quota_optional <<: *networks_quota_optional
max_version: 2.35 max_version: 2.35
@ -4746,12 +4747,15 @@ server_group_id_body:
in: body in: body
required: true required: true
type: string type: string
server_group_members: server_group_members: &server_group_members
description: | description: |
The number of allowed members for each server group. The number of allowed members for each server group.
in: body in: body
required: true required: true
type: integer type: integer
server_group_members_quota_class:
<<: *server_group_members
min_version: 2.50
server_group_members_quota_details: server_group_members_quota_details:
description: | description: |
The object of detailed server group members, including in_use, The object of detailed server group members, including in_use,
@ -4777,6 +4781,11 @@ server_groups_list:
in: body in: body
required: true required: true
type: array type: array
server_groups_quota_class:
<<: *server_groups
description: |
The number of allowed server groups for the quota class.
min_version: 2.50
server_groups_quota_class_optional: server_groups_quota_class_optional:
<<: *server_groups <<: *server_groups
description: | description: |

View File

@ -0,0 +1,15 @@
{
"quota_class_set": {
"cores": 20,
"id": "test_class",
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -0,0 +1,14 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"metadata_items": 128,
"injected_files": 5,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"key_pairs": 100,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -0,0 +1,14 @@
{
"quota_class_set": {
"cores": 50,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -19,7 +19,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.49", "version": "2.50",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@ -22,7 +22,7 @@
} }
], ],
"status": "CURRENT", "status": "CURRENT",
"version": "2.49", "version": "2.50",
"min_version": "2.1", "min_version": "2.1",
"updated": "2013-07-23T11:33:21Z" "updated": "2013-07-23T11:33:21Z"
} }

View File

@ -116,6 +116,10 @@ REST_API_VERSION_HISTORY = """REST API Version History:
the output. the output.
* 2.48 - Standardize VM diagnostics info. * 2.48 - Standardize VM diagnostics info.
* 2.49 - Support tagged attachment of network interfaces and block devices. * 2.49 - Support tagged attachment of network interfaces and block devices.
* 2.50 - Exposes ``server_groups`` and ``server_group_members`` keys in
GET & PUT ``os-quota-class-sets`` APIs response.
Also filter out Network related quotas from
``os-quota-class-sets`` API
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
@ -124,7 +128,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions # Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API. # support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1" _MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.49" _MAX_API_VERSION = "2.50"
DEFAULT_API_VERSION = _MIN_API_VERSION DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which related to network, images and baremetal # Almost all proxy APIs which related to network, images and baremetal

View File

@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import webob import webob
from nova.api.openstack import api_version_request
from nova.api.openstack.compute.schemas import quota_classes from nova.api.openstack.compute.schemas import quota_classes
from nova.api.openstack import extensions from nova.api.openstack import extensions
from nova.api.openstack import wsgi from nova.api.openstack import wsgi
@ -29,9 +30,14 @@ from nova import utils
QUOTAS = quota.QUOTAS QUOTAS = quota.QUOTAS
# Quotas that are only enabled by specific extensions # NOTE(gmann): Quotas which were returned in v2 but in v2.1 those
EXTENDED_QUOTAS = {'server_groups': 'os-server-group-quotas', # were not returned. Fixed in microversion 2.50. Bug#1693168.
'server_group_members': 'os-server-group-quotas'} EXTENDED_QUOTAS = ['server_groups', 'server_group_members']
# NOTE(gmann): Network related quotas are filter out in
# microversion 2.50. Bug#1701211.
FILTERED_QUOTAS = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
class QuotaClassSetsController(wsgi.Controller): class QuotaClassSetsController(wsgi.Controller):
@ -40,20 +46,25 @@ class QuotaClassSetsController(wsgi.Controller):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.supported_quotas = QUOTAS.resources self.supported_quotas = QUOTAS.resources
# TODO(jichenjc): need fix v2 and v2.1 API difference here see bug
# 1693168 for more info
for resource, extension in EXTENDED_QUOTAS.items():
self.supported_quotas.remove(resource)
def _format_quota_set(self, quota_class, quota_set): def _format_quota_set(self, quota_class, quota_set, req):
"""Convert the quota object to a result dict.""" """Convert the quota object to a result dict."""
if quota_class: if quota_class:
result = dict(id=str(quota_class)) result = dict(id=str(quota_class))
else: else:
result = {} result = {}
original_quotas = copy.deepcopy(self.supported_quotas)
for resource in self.supported_quotas: if api_version_request.is_supported(req, min_version="2.50"):
original_quotas = [resource for resource in original_quotas
if resource not in FILTERED_QUOTAS]
# NOTE(gmann): Before microversion v2.50, v2.1 API does not return the
# 'server_groups' & 'server_group_members' key in quota class API
# response.
else:
for resource in EXTENDED_QUOTAS:
original_quotas.remove(resource)
for resource in original_quotas:
if resource in quota_set: if resource in quota_set:
result[resource] = quota_set[resource] result[resource] = quota_set[resource]
@ -64,10 +75,11 @@ class QuotaClassSetsController(wsgi.Controller):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(qcs_policies.POLICY_ROOT % 'show', {'quota_class': id}) context.can(qcs_policies.POLICY_ROOT % 'show', {'quota_class': id})
values = QUOTAS.get_class_quotas(context, id) values = QUOTAS.get_class_quotas(context, id)
return self._format_quota_set(id, values) return self._format_quota_set(id, values, req)
@extensions.expected_errors(400) @extensions.expected_errors(400)
@validation.schema(quota_classes.update) @validation.schema(quota_classes.update, "2.0", "2.49")
@validation.schema(quota_classes.update_v250, "2.50")
def update(self, req, id, body): def update(self, req, id, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
context.can(qcs_policies.POLICY_ROOT % 'update', {'quota_class': id}) context.can(qcs_policies.POLICY_ROOT % 'update', {'quota_class': id})
@ -87,4 +99,4 @@ class QuotaClassSetsController(wsgi.Controller):
db.quota_class_create(context, quota_class, key, value) db.quota_class_create(context, quota_class, key, value)
values = QUOTAS.get_class_quotas(context, quota_class) values = QUOTAS.get_class_quotas(context, quota_class)
return self._format_quota_set(None, values) return self._format_quota_set(None, values, req)

View File

@ -579,3 +579,19 @@ user documentation.
be reflected in the config drive. be reflected in the config drive.
Tagged volume attachment is not supported for shelved-offloaded instances. Tagged volume attachment is not supported for shelved-offloaded instances.
2.50
----
The ``server_groups`` and ``server_group_members`` keys are exposed in GET & PUT
``os-quota-class-sets`` APIs Response body.
Networks related quotas have been filtered out from os-quota-class. Below quotas
are filtered out and not available in ``os-quota-class-sets`` APIs from this
microversion onwards.
- "fixed_ips"
- "floating_ips"
- "networks",
- "security_group_rules"
- "security_groups"

View File

@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
from nova.api.openstack.compute.schemas import quota_sets from nova.api.openstack.compute.schemas import quota_sets
@ -26,3 +27,12 @@ update = {
'required': ['quota_class_set'], 'required': ['quota_class_set'],
'additionalProperties': False, 'additionalProperties': False,
} }
update_v250 = copy.deepcopy(update)
del update_v250['properties']['quota_class_set']['properties']['fixed_ips']
del update_v250['properties']['quota_class_set']['properties']['floating_ips']
del update_v250['properties']['quota_class_set']['properties'][
'security_groups']
del update_v250['properties']['quota_class_set']['properties'][
'security_group_rules']
del update_v250['properties']['quota_class_set']['properties']['networks']

View File

@ -0,0 +1,15 @@
{
"quota_class_set": {
"cores": 20,
"id": "%(set_id)s",
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -0,0 +1,14 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"metadata_items": 128,
"injected_files": 5,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"key_pairs": 100,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -0,0 +1,14 @@
{
"quota_class_set": {
"cores": 50,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}

View File

@ -35,3 +35,8 @@ class QuotaClassesSampleJsonTests(api_sample_base.ApiSampleTestBaseV21):
{}) {})
self._verify_response('quota-classes-update-post-resp', self._verify_response('quota-classes-update-post-resp',
{}, response, 200) {}, response, 200)
class QuotaClassesV250SampleJsonTests(QuotaClassesSampleJsonTests):
microversion = '2.50'
scenarios = [('v2_50', {'api_major_version': 'v2.1'})]

View File

@ -12,7 +12,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import webob import webob
from nova.api.openstack.compute import extension_info from nova.api.openstack.compute import extension_info
@ -23,23 +23,26 @@ from nova import test
from nova.tests.unit.api.openstack import fakes from nova.tests.unit.api.openstack import fakes
def quota_set(class_name):
return {'quota_class_set': {'id': class_name, 'metadata_items': 128,
'ram': 51200, 'floating_ips': 10,
'fixed_ips': -1, 'instances': 10,
'injected_files': 5, 'cores': 20,
'injected_file_content_bytes': 10240,
'security_groups': 10,
'security_group_rules': 20, 'key_pairs': 100,
'injected_file_path_bytes': 255}}
class QuotaClassSetsTestV21(test.TestCase): class QuotaClassSetsTestV21(test.TestCase):
validation_error = exception.ValidationError validation_error = exception.ValidationError
api_version = '2.1'
quota_resources = {'metadata_items': 128,
'ram': 51200, 'floating_ips': 10,
'fixed_ips': -1, 'instances': 10,
'injected_files': 5, 'cores': 20,
'injected_file_content_bytes': 10240,
'security_groups': 10,
'security_group_rules': 20, 'key_pairs': 100,
'injected_file_path_bytes': 255}
def quota_set(self, class_name):
quotas = copy.deepcopy(self.quota_resources)
quotas['id'] = class_name
return {'quota_class_set': quotas}
def setUp(self): def setUp(self):
super(QuotaClassSetsTestV21, self).setUp() super(QuotaClassSetsTestV21, self).setUp()
self.req = fakes.HTTPRequest.blank('') self.req = fakes.HTTPRequest.blank('', version=self.api_version)
self._setup() self._setup()
def _setup(self): def _setup(self):
@ -47,60 +50,46 @@ class QuotaClassSetsTestV21(test.TestCase):
self.controller = quota_classes_v21.QuotaClassSetsController( self.controller = quota_classes_v21.QuotaClassSetsController(
extension_info=ext_info) extension_info=ext_info)
def test_format_quota_set(self): def _check_filtered_extended_quota(self, quota_set):
raw_quota_set = { self.assertNotIn('server_groups', quota_set)
'instances': 10, self.assertNotIn('server_group_members', quota_set)
'cores': 20, self.assertEqual(10, quota_set['floating_ips'])
'ram': 51200, self.assertEqual(-1, quota_set['fixed_ips'])
'floating_ips': 10, self.assertEqual(10, quota_set['security_groups'])
'fixed_ips': -1, self.assertEqual(20, quota_set['security_group_rules'])
'metadata_items': 128,
'injected_files': 5,
'injected_file_path_bytes': 255,
'injected_file_content_bytes': 10240,
'security_groups': 10,
'security_group_rules': 20,
'key_pairs': 100,
}
def test_format_quota_set(self):
quota_set = self.controller._format_quota_set('test_class', quota_set = self.controller._format_quota_set('test_class',
raw_quota_set) self.quota_resources,
self.req)
qs = quota_set['quota_class_set'] qs = quota_set['quota_class_set']
self.assertEqual(qs['id'], 'test_class') self.assertEqual(qs['id'], 'test_class')
self.assertEqual(qs['instances'], 10) self.assertEqual(qs['instances'], 10)
self.assertEqual(qs['cores'], 20) self.assertEqual(qs['cores'], 20)
self.assertEqual(qs['ram'], 51200) self.assertEqual(qs['ram'], 51200)
self.assertEqual(qs['floating_ips'], 10)
self.assertEqual(qs['fixed_ips'], -1)
self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['metadata_items'], 128)
self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_files'], 5)
self.assertEqual(qs['injected_file_path_bytes'], 255) self.assertEqual(qs['injected_file_path_bytes'], 255)
self.assertEqual(qs['injected_file_content_bytes'], 10240) self.assertEqual(qs['injected_file_content_bytes'], 10240)
self.assertEqual(qs['security_groups'], 10)
self.assertEqual(qs['security_group_rules'], 20)
self.assertEqual(qs['key_pairs'], 100) self.assertEqual(qs['key_pairs'], 100)
self._check_filtered_extended_quota(qs)
def test_quotas_show(self): def test_quotas_show(self):
res_dict = self.controller.show(self.req, 'test_class') res_dict = self.controller.show(self.req, 'test_class')
self.assertEqual(res_dict, quota_set('test_class')) self.assertEqual(res_dict, self.quota_set('test_class'))
def test_quotas_update(self): def test_quotas_update(self):
body = {'quota_class_set': {'instances': 50, 'cores': 50, expected_body = {'quota_class_set': self.quota_resources}
'ram': 51200, 'floating_ips': 10, request_quota_resources = copy.deepcopy(self.quota_resources)
'fixed_ips': -1, 'metadata_items': 128, request_quota_resources['server_groups'] = 10
'injected_files': 5, request_quota_resources['server_group_members'] = 10
'injected_file_content_bytes': 10240, request_body = {'quota_class_set': request_quota_resources}
'injected_file_path_bytes': 255,
'security_groups': 10,
'security_group_rules': 20,
'key_pairs': 100}}
res_dict = self.controller.update(self.req, 'test_class', res_dict = self.controller.update(self.req, 'test_class',
body=body) body=request_body)
self.assertEqual(res_dict, body) self.assertEqual(res_dict, expected_body)
def test_quotas_update_with_empty_body(self): def test_quotas_update_with_empty_body(self):
body = {} body = {}
@ -139,6 +128,35 @@ class QuotaClassSetsTestV21(test.TestCase):
self.req, 'test_class', body=body) self.req, 'test_class', body=body)
class QuotaClassSetsTestV250(QuotaClassSetsTestV21):
api_version = '2.50'
quota_resources = {'metadata_items': 128,
'ram': 51200, 'instances': 10,
'injected_files': 5, 'cores': 20,
'injected_file_content_bytes': 10240,
'key_pairs': 100,
'injected_file_path_bytes': 255,
'server_groups': 10,
'server_group_members': 10}
def _check_filtered_extended_quota(self, quota_set):
self.assertEqual(10, quota_set['server_groups'])
self.assertEqual(10, quota_set['server_group_members'])
self.assertNotIn('floating_ips', quota_set)
self.assertNotIn('fixed_ips', quota_set)
self.assertNotIn('security_groups', quota_set)
self.assertNotIn('security_group_rules', quota_set)
self.assertNotIn('networks', quota_set)
def test_quotas_update_with_filtered_quota(self):
filtered_quotas = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
for resource in filtered_quotas:
body = {'quota_class_set': {resource: 10}}
self.assertRaises(self.validation_error, self.controller.update,
self.req, 'test_class', body=body)
class QuotaClassesPolicyEnforcementV21(test.NoDBTestCase): class QuotaClassesPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self): def setUp(self):

View File

@ -0,0 +1,20 @@
---
fixes:
- |
Fix bug 1693168. v2.1 API which does not return the ``server_groups`` and
``server_group_members`` quotas in GET and PUT ``os-quota-class-sets`` API
response. v2 API used to return those keys in the API response.
Microversion 2.50 restored that behavior. In microversion 2.50, the
``server_groups`` and ``server_group_members`` keys are exposed in
GET and PUT ``os-quota-class-sets`` APIs Response body.
Fix bug 1701211. Network related quotas are filtered out of
``os-quota-class-sets`` APIs and not available from microversion
2.50 onwards.
Filtered quotas are:
- 'fixed_ips'
- 'floating ips'
- 'security_groups'
- 'security_group_rules'
- 'networks'