Revert "Remove broken quota-classes API"

This reverts commit 7d22153d05.

The quota_classes API was used to set default quota values
so it shouldn't have been removed, so reverting a series
of changes that removed the API and it's internal code.

Related mailing list thread on the topic:

http://lists.openstack.org/pipermail/openstack-dev/2014-May/035383.html

Partial-Bug: #1299517

Conflicts:
	doc/api_samples/all_extensions/extensions-get-resp.json
	doc/api_samples/all_extensions/extensions-get-resp.xml
	nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl

The conflicts are due to Mark McLoughlin's timestamp-format series
of changes on master (Juno).

Also note that quota_classes.py was changed due to
commit c75a15a4 to rename the NotAuthorized exception
to Forbidden.

Change-Id: I7890e6b41d4ebf19944c1d4db65111fcc4c45463
This commit is contained in:
Matt Riedemann 2014-05-29 08:21:27 -07:00
parent d9cee6b2ee
commit 35338d54ad
22 changed files with 572 additions and 2 deletions

View File

@ -504,6 +504,14 @@
"namespace": "http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2",
"updated": "2013-12-17T00:00:00Z"
},
{
"alias": "os-quota-class-sets",
"description": "Quota classes management support.",
"links": [],
"name": "QuotaClasses",
"namespace": "http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1",
"updated": "2012-03-12T00:00:00Z"
},
{
"alias": "os-quota-sets",
"description": "Quotas management support.",
@ -665,4 +673,4 @@
"updated": "2011-03-25T00:00:00Z"
}
]
}
}

View File

@ -207,6 +207,9 @@
<extension alias="os-preserve-ephemeral-rebuild" updated="2013-12-17T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/preserve_ephemeral_rebuild/api/v2" name="PreserveEphemeralOnRebuild">
<description>Allow preservation of the ephemeral partition on rebuild.</description>
</extension>
<extension alias="os-quota-class-sets" updated="2012-03-12T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1" name="QuotaClasses">
<description>Quota classes management support.</description>
</extension>
<extension alias="os-quota-sets" updated="2011-08-08T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/quotas-sets/api/v1.1" name="Quotas">
<description>Quotas management support.</description>
</extension>
@ -267,4 +270,4 @@
<extension alias="os-volumes" updated="2011-03-25T00:00:00Z" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
<description>Volumes support.</description>
</extension>
</extensions>
</extensions>

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 @@
<?xml version='1.0' encoding='UTF-8'?>
<quota_class_set id="test_class">
<cores>20</cores>
<fixed_ips>-1</fixed_ips>
<floating_ips>10</floating_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>10</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

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,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<quota_class_set>
<cores>50</cores>
<floating_ips>10</floating_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>50</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

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

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<quota_class_set>
<cores>50</cores>
<fixed_ips>-1</fixed_ips>
<floating_ips>10</floating_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>50</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

View File

@ -201,6 +201,7 @@
"compute_extension:v3:os-quota-sets:update": "rule:admin_api",
"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:rescue": "",
"compute_extension:v3:os-rescue": "",
"compute_extension:v3:os-rescue:discoverable": "",

View File

@ -0,0 +1,115 @@
# 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
from nova.api.openstack import xmlutil
import nova.context
from nova import db
from nova import exception
from nova.openstack.common.gettextutils import _
from nova import quota
from nova import utils
QUOTAS = quota.QUOTAS
authorize = extensions.extension_authorizer('compute', 'quota_classes')
class QuotaClassTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('quota_class_set',
selector='quota_class_set')
root.set('id')
for resource in QUOTAS.resources:
elem = xmlutil.SubTemplateElement(root, resource)
elem.text = resource
return xmlutil.MasterTemplate(root, 1)
class QuotaClassSetsController(wsgi.Controller):
def _format_quota_set(self, quota_class, quota_set):
"""Convert the quota object to a result dict."""
result = dict(id=str(quota_class))
for resource in QUOTAS.resources:
result[resource] = quota_set[resource]
return dict(quota_class_set=result)
@wsgi.serializers(xml=QuotaClassTemplate)
def show(self, req, id):
context = req.environ['nova.context']
authorize(context)
try:
nova.context.authorize_quota_class_context(context, id)
return self._format_quota_set(id,
QUOTAS.get_class_quotas(context, id))
except exception.Forbidden:
raise webob.exc.HTTPForbidden()
@wsgi.serializers(xml=QuotaClassTemplate)
def update(self, req, id, body):
context = req.environ['nova.context']
authorize(context)
quota_class = id
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 in QUOTAS:
try:
value = utils.validate_integer(
body['quota_class_set'][key], key)
except exception.InvalidInput as e:
raise webob.exc.HTTPBadRequest(
explanation=e.format_message())
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()
return {'quota_class_set': QUOTAS.get_class_quotas(context,
quota_class)}
class Quota_classes(extensions.ExtensionDescriptor):
"""Quota classes management support."""
name = "QuotaClasses"
alias = "os-quota-class-sets"
namespace = ("http://docs.openstack.org/compute/ext/"
"quota-classes-sets/api/v1.1")
updated = "2012-03-12T00:00:00Z"
def get_resources(self):
resources = []
res = extensions.ResourceExtension('os-quota-class-sets',
QuotaClassSetsController())
resources.append(res)
return resources

View File

@ -0,0 +1,219 @@
# 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.
from lxml import etree
import webob
from nova.api.openstack.compute.contrib import quota_classes
from nova.api.openstack import wsgi
from nova import test
from nova.tests.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 QuotaClassSetsTest(test.TestCase):
def setUp(self):
super(QuotaClassSetsTest, self).setUp()
self.controller = quota_classes.QuotaClassSetsController()
def test_format_quota_set(self):
raw_quota_set = {
'instances': 10,
'cores': 20,
'ram': 51200,
'floating_ips': 10,
'fixed_ips': -1,
'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,
}
quota_set = self.controller._format_quota_set('test_class',
raw_quota_set)
qs = quota_set['quota_class_set']
self.assertEqual(qs['id'], 'test_class')
self.assertEqual(qs['instances'], 10)
self.assertEqual(qs['cores'], 20)
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['injected_files'], 5)
self.assertEqual(qs['injected_file_path_bytes'], 255)
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)
def test_quotas_show_as_admin(self):
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
res_dict = self.controller.show(req, 'test_class')
self.assertEqual(res_dict, quota_set('test_class'))
def test_quotas_show_as_unauthorized_user(self):
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class')
self.assertRaises(webob.exc.HTTPForbidden, self.controller.show,
req, 'test_class')
def test_quotas_update_as_admin(self):
body = {'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}}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
res_dict = self.controller.update(req, 'test_class', body)
self.assertEqual(res_dict, body)
def test_quotas_update_as_user(self):
body = {'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,
'security_groups': 10,
'security_group_rules': 20,
'key_pairs': 100,
}}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class')
self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
req, 'test_class', body)
def test_quotas_update_with_empty_body(self):
body = {}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 'test_class', body)
def test_quotas_update_with_non_integer(self):
body = {'quota_class_set': {'instances': "abc"}}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 'test_class', body)
body = {'quota_class_set': {'instances': 50.5}}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 'test_class', body)
body = {'quota_class_set': {
'instances': u'\u30aa\u30fc\u30d7\u30f3'}}
req = fakes.HTTPRequest.blank(
'/v2/fake4/os-quota-class-sets/test_class',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 'test_class', body)
class QuotaTemplateXMLSerializerTest(test.TestCase):
def setUp(self):
super(QuotaTemplateXMLSerializerTest, self).setUp()
self.serializer = quota_classes.QuotaClassTemplate()
self.deserializer = wsgi.XMLDeserializer()
def test_serializer(self):
exemplar = dict(quota_class_set=dict(
id='test_class',
metadata_items=10,
injected_file_path_bytes=255,
injected_file_content_bytes=20,
ram=50,
floating_ips=60,
fixed_ips=-1,
instances=70,
injected_files=80,
security_groups=10,
security_group_rules=20,
key_pairs=100,
cores=90))
text = self.serializer.serialize(exemplar)
tree = etree.fromstring(text)
self.assertEqual('quota_class_set', tree.tag)
self.assertEqual('test_class', tree.get('id'))
self.assertEqual(len(exemplar['quota_class_set']) - 1, len(tree))
for child in tree:
self.assertIn(child.tag, exemplar['quota_class_set'])
self.assertEqual(int(child.text),
exemplar['quota_class_set'][child.tag])
def test_deserializer(self):
exemplar = dict(quota_class_set=dict(
metadata_items='10',
injected_file_content_bytes='20',
ram='50',
floating_ips='60',
fixed_ips='-1',
instances='70',
injected_files='80',
security_groups='10',
security_group_rules='20',
key_pairs='100',
cores='90'))
intext = ("<?xml version='1.0' encoding='UTF-8'?>\n"
'<quota_class_set>'
'<metadata_items>10</metadata_items>'
'<injected_file_content_bytes>20'
'</injected_file_content_bytes>'
'<ram>50</ram>'
'<floating_ips>60</floating_ips>'
'<fixed_ips>-1</fixed_ips>'
'<instances>70</instances>'
'<injected_files>80</injected_files>'
'<cores>90</cores>'
'<security_groups>10</security_groups>'
'<security_group_rules>20</security_group_rules>'
'<key_pairs>100</key_pairs>'
'</quota_class_set>')
result = self.deserializer.deserialize(intext)['body']
self.assertEqual(result, exemplar)

View File

@ -228,6 +228,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"Keypairs",
"Multinic",
"MultipleCreate",
"QuotaClasses",
"Quotas",
"ExtendedQuotas",
"Rescue",

View File

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

View File

@ -472,6 +472,14 @@
"namespace": "http://docs.openstack.org/compute/ext/networks_associate/api/v2",
"updated": "%(isotime)s"
},
{
"alias": "os-quota-class-sets",
"description": "%(text)s",
"links": [],
"name": "QuotaClasses",
"namespace": "http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1",
"updated": "%(isotime)s"
},
{
"alias": "os-extended-quotas",
"description": "%(text)s",

View File

@ -168,6 +168,9 @@
<extension alias="os-networks-associate" updated="%(isotime)s" namespace="http://docs.openstack.org/compute/ext/networks_associate/api/v2" name="NetworkAssociationSupport">
<description>%(text)s</description>
</extension>
<extension alias="os-quota-class-sets" updated="%(isotime)s" namespace="http://docs.openstack.org/compute/ext/quota-classes-sets/api/v1.1" name="QuotaClasses">
<description>%(text)s</description>
</extension>
<extension alias="os-extended-quotas" updated="%(isotime)s" namespace="http://docs.openstack.org/compute/ext/extended_quotas/api/v1.1" name="ExtendedQuotas">
<description>%(text)s</description>
</extension>

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,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<quota_class_set id="%(set_id)s">
<cores>20</cores>
<floating_ips>10</floating_ips>
<fixed_ips>-1</fixed_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>10</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

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,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<quota_class_set>
<cores>50</cores>
<floating_ips>10</floating_ips>
<fixed_ips>-1</fixed_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>50</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

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,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<quota_class_set>
<cores>50</cores>
<floating_ips>10</floating_ips>
<fixed_ips>-1</fixed_ips>
<injected_file_content_bytes>10240</injected_file_content_bytes>
<injected_file_path_bytes>255</injected_file_path_bytes>
<injected_files>5</injected_files>
<instances>50</instances>
<key_pairs>100</key_pairs>
<metadata_items>128</metadata_items>
<ram>51200</ram>
<security_group_rules>20</security_group_rules>
<security_groups>10</security_groups>
</quota_class_set>

View File

@ -2709,6 +2709,31 @@ class FlavorDisabledSampleXmlTests(FlavorDisabledSampleJsonTests):
ctype = "xml"
class QuotaClassesSampleJsonTests(ApiSampleTestBaseV2):
extension_name = ("nova.api.openstack.compute.contrib.quota_classes."
"Quota_classes")
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)
class QuotaClassesSampleXmlTests(QuotaClassesSampleJsonTests):
ctype = "xml"
class CellsSampleJsonTest(ApiSampleTestBaseV2):
extension_name = "nova.api.openstack.compute.contrib.cells.Cells"