Port v2 quota_classes extension to work in v2.1(v3) framework

Port v2 quota_classes extension and adapts it to the v2.1/v3 API
framework. API behaviour is identical with the exception that
there is no support for XML. Also

- unittest code modified to share existing testing with both
  v2/v2.1
- Adds expected error decorators for API methods
- Adds API samples

Partially implements blueprint v2-on-v3-api

Change-Id: I372e9940f499d3e2cf621a58eafa9502d4e14cea
This commit is contained in:
Chris Yeoh 2014-09-03 17:29:42 +09:30
parent 3b58ab43f5
commit c82f5886ab
12 changed files with 287 additions and 5 deletions

View File

@ -0,0 +1,17 @@
{
"quota_class_set": {
"cores": 20,
"fixed_ips": -1,
"floating_ips": 10,
"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,
"security_group_rules": 20,
"security_groups": 10
}
}

View File

@ -0,0 +1,15 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"floating_ips": 10,
"metadata_items": 128,
"injected_files": 5,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"security_groups": 10,
"security_group_rules": 20,
"key_pairs": 100
}
}

View File

@ -0,0 +1,16 @@
{
"quota_class_set": {
"cores": 50,
"fixed_ips": -1,
"floating_ips": 10,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"security_group_rules": 20,
"security_groups": 10
}
}

View File

@ -239,6 +239,8 @@
"compute_extension:v3:os-quota-sets:delete": "rule:admin_api",
"compute_extension:v3:os-quota-sets:detail": "rule:admin_api",
"compute_extension:quota_classes": "",
"compute_extension:v3:os-quota-class-sets": "",
"compute_extension:v3:os-quota-class-sets:discoverable": "",
"compute_extension:rescue": "",
"compute_extension:v3:os-rescue": "",
"compute_extension:v3:os-rescue:discoverable": "",

View File

@ -0,0 +1,131 @@
# Copyright 2012 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import webob
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
import nova.context
from nova import db
from nova import exception
from nova.i18n import _
from nova import quota
from nova import utils
QUOTAS = quota.QUOTAS
ALIAS = "os-quota-class-sets"
# Quotas that are only enabled by specific extensions
EXTENDED_QUOTAS = {'server_groups': 'os-server-group-quotas',
'server_group_members': 'os-server-group-quotas'}
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
class QuotaClassSetsController(wsgi.Controller):
supported_quotas = []
def __init__(self, **kwargs):
self.supported_quotas = QUOTAS.resources
extension_info = kwargs.pop('extension_info').get_extensions()
for resource, extension in EXTENDED_QUOTAS.items():
if extension not in extension_info:
self.supported_quotas.remove(resource)
def _format_quota_set(self, quota_class, quota_set):
"""Convert the quota object to a result dict."""
if quota_class:
result = dict(id=str(quota_class))
else:
result = {}
for resource in self.supported_quotas:
if resource in quota_set:
result[resource] = quota_set[resource]
return dict(quota_class_set=result)
@extensions.expected_errors(403)
def show(self, req, id):
context = req.environ['nova.context']
authorize(context)
try:
nova.context.authorize_quota_class_context(context, id)
values = QUOTAS.get_class_quotas(context, id)
return self._format_quota_set(id, values)
except exception.Forbidden:
raise webob.exc.HTTPForbidden()
@extensions.expected_errors((400, 403))
def update(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
quota_class = id
bad_keys = []
if not self.is_valid_body(body, 'quota_class_set'):
msg = _("quota_class_set not specified")
raise webob.exc.HTTPBadRequest(explanation=msg)
quota_class_set = body['quota_class_set']
for key in quota_class_set.keys():
if key not in self.supported_quotas:
bad_keys.append(key)
continue
try:
value = utils.validate_integer(
body['quota_class_set'][key], key)
except exception.InvalidInput as e:
raise webob.exc.HTTPBadRequest(
explanation=e.format_message())
if bad_keys:
msg = _("Bad key(s) %s in quota_set") % ",".join(bad_keys)
raise webob.exc.HTTPBadRequest(explanation=msg)
for key in quota_class_set.keys():
value = utils.validate_integer(
body['quota_class_set'][key], key)
try:
db.quota_class_update(context, quota_class, key, value)
except exception.QuotaClassNotFound:
db.quota_class_create(context, quota_class, key, value)
except exception.AdminRequired:
raise webob.exc.HTTPForbidden()
values = QUOTAS.get_class_quotas(context, quota_class)
return self._format_quota_set(None, values)
class QuotaClasses(extensions.V3APIExtensionBase):
"""Quota classes management support."""
name = "QuotaClasses"
alias = ALIAS
version = 1
def get_resources(self):
resources = []
res = extensions.ResourceExtension(
ALIAS,
QuotaClassSetsController(extension_info=self.extension_info))
resources.append(res)
return resources
def get_controller_extensions(self):
return []

View File

@ -17,6 +17,9 @@ from lxml import etree
import webob
from nova.api.openstack.compute.contrib import quota_classes
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import quota_classes \
as quota_classes_v21
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import test
@ -34,13 +37,16 @@ def quota_set(class_name):
'injected_file_path_bytes': 255}}
class QuotaClassSetsTest(test.TestCase):
class QuotaClassSetsTestV21(test.TestCase):
def setUp(self):
super(QuotaClassSetsTest, self).setUp()
self.ext_mgr = extensions.ExtensionManager()
self.ext_mgr.extensions = {}
self.controller = quota_classes.QuotaClassSetsController(self.ext_mgr)
super(QuotaClassSetsTestV21, self).setUp()
self._setup()
def _setup(self):
ext_info = plugins.LoadedExtensionInfo()
self.controller = quota_classes_v21.QuotaClassSetsController(
extension_info=ext_info)
def test_format_quota_set(self):
raw_quota_set = {
@ -156,6 +162,14 @@ class QuotaClassSetsTest(test.TestCase):
req, 'test_class', body)
class QuotaClassSetsTestV2(QuotaClassSetsTestV21):
def _setup(self):
ext_mgr = extensions.ExtensionManager()
ext_mgr.extensions = {}
self.controller = quota_classes.QuotaClassSetsController(ext_mgr)
class QuotaTemplateXMLSerializerTest(test.TestCase):
def setUp(self):
super(QuotaTemplateXMLSerializerTest, self).setUp()

View File

@ -277,6 +277,7 @@ policy_data = """
"compute_extension:v3:os-quota-sets:delete": "",
"compute_extension:v3:os-quota-sets:detail": "",
"compute_extension:quota_classes": "",
"compute_extension:v3:os-quota-class-sets": "",
"compute_extension:rescue": "",
"compute_extension:v3:os-rescue": "",
"compute_extension:security_group_default_rules": "",

View File

@ -0,0 +1,17 @@
{
"quota_class_set": {
"cores": 20,
"floating_ips": 10,
"fixed_ips": -1,
"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,
"security_group_rules": 20,
"security_groups": 10
}
}

View File

@ -0,0 +1,16 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"floating_ips": 10,
"fixed_ips": -1,
"metadata_items": 128,
"injected_files": 5,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"security_groups": 10,
"security_group_rules": 20,
"key_pairs": 100
}
}

View File

@ -0,0 +1,16 @@
{
"quota_class_set": {
"cores": 50,
"floating_ips": 10,
"fixed_ips": -1,
"injected_file_content_bytes": 10240,
"injected_file_path_bytes": 255,
"injected_files": 5,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"security_group_rules": 20,
"security_groups": 10
}
}

View File

@ -0,0 +1,36 @@
# Copyright 2012 Nebula, Inc.
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.tests.unit.integrated.v3 import api_sample_base
class QuotaClassesSampleJsonTests(api_sample_base.ApiSampleTestBaseV3):
extension_name = "os-quota-class-sets"
set_id = 'test_class'
def test_show_quota_classes(self):
# Get api sample to show quota classes.
response = self._do_get('os-quota-class-sets/%s' % self.set_id)
subs = {'set_id': self.set_id}
self._verify_response('quota-classes-show-get-resp', subs,
response, 200)
def test_update_quota_classes(self):
# Get api sample to update quota classes.
response = self._do_put('os-quota-class-sets/%s' % self.set_id,
'quota-classes-update-post-req',
{})
self._verify_response('quota-classes-update-post-resp',
{}, response, 200)

View File

@ -111,6 +111,7 @@ nova.api.v3.extensions =
networks_associate = nova.api.openstack.compute.plugins.v3.networks_associate:NetworksAssociate
pause_server = nova.api.openstack.compute.plugins.v3.pause_server:PauseServer
pci = nova.api.openstack.compute.plugins.v3.pci:Pci
quota_classes = nova.api.openstack.compute.plugins.v3.quota_classes:QuotaClasses
quota_sets = nova.api.openstack.compute.plugins.v3.quota_sets:QuotaSets
remote_consoles = nova.api.openstack.compute.plugins.v3.remote_consoles:RemoteConsoles
rescue = nova.api.openstack.compute.plugins.v3.rescue:Rescue