Merged flavorsextraspecs extension into core API

Partially implements blueprint nova-v3-api

Change-Id: I1eb9bea4996e106c848eb215e19088a968e5200c
This commit is contained in:
Alexei Kornienko
2013-07-02 12:56:41 +03:00
parent c4e7df7a98
commit 59906f1a3d
5 changed files with 363 additions and 0 deletions
+5
View File
@@ -103,6 +103,11 @@
"compute_extension:flavorextraspecs:create": "rule:admin_api",
"compute_extension:flavorextraspecs:update": "rule:admin_api",
"compute_extension:flavorextraspecs:delete": "rule:admin_api",
"compute_extension:v3:flavor-extra-specs:index": "",
"compute_extension:v3:flavor-extra-specs:show": "",
"compute_extension:v3:flavor-extra-specs:create": "rule:admin_api",
"compute_extension:v3:flavor-extra-specs:update": "rule:admin_api",
"compute_extension:v3:flavor-extra-specs:delete": "rule:admin_api",
"compute_extension:flavormanage": "rule:admin_api",
"compute_extension:floating_ip_dns": "",
"compute_extension:floating_ip_pools": "",
@@ -0,0 +1,137 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 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
from nova import db
from nova import exception
from nova.openstack.common.db import exception as db_exc
from nova.openstack.common.gettextutils import _
class ExtraSpecsTemplate(xmlutil.TemplateBuilder):
def construct(self):
return xmlutil.MasterTemplate(xmlutil.make_flat_dict('extra_specs'), 1)
class ExtraSpecTemplate(xmlutil.TemplateBuilder):
def construct(self):
sel = xmlutil.Selector(xmlutil.get_items, 0)
root = xmlutil.TemplateElement('extra_spec', selector=sel)
root.set('key', 0)
root.text = 1
return xmlutil.MasterTemplate(root, 1)
class FlavorExtraSpecsController(object):
"""The flavor extra specs API controller for the OpenStack API."""
ALIAS = 'flavor-extra-specs'
def __init__(self, *args, **kwargs):
super(FlavorExtraSpecsController, self).__init__(*args, **kwargs)
self.authorize = extensions.extension_authorizer('compute',
'v3:' + self.ALIAS)
def _get_extra_specs(self, context, flavor_id):
extra_specs = db.instance_type_extra_specs_get(context, flavor_id)
return dict(extra_specs=extra_specs)
def _check_body(self, body):
if body is None or body == "":
expl = _('No Request Body')
raise webob.exc.HTTPBadRequest(explanation=expl)
@wsgi.serializers(xml=ExtraSpecsTemplate)
def index(self, req, flavor_id):
"""Returns the list of extra specs for a given flavor."""
context = req.environ['nova.context']
self.authorize(context, action='index')
return self._get_extra_specs(context, flavor_id)
@wsgi.serializers(xml=ExtraSpecsTemplate)
def create(self, req, flavor_id, body):
context = req.environ['nova.context']
self.authorize(context, action='create')
self._check_body(body)
specs = body.get('extra_specs', {})
if not specs or type(specs) is not dict:
raise webob.exc.HTTPBadRequest(_('No or bad extra_specs provided'))
try:
db.instance_type_extra_specs_update_or_create(context, flavor_id,
specs)
except db_exc.DBDuplicateEntry as error:
raise webob.exc.HTTPBadRequest(explanation=error.format_message())
return body
@wsgi.serializers(xml=ExtraSpecTemplate)
def update(self, req, flavor_id, id, body):
context = req.environ['nova.context']
self.authorize(context, action='update')
self._check_body(body)
if id not in body:
expl = _('Request body and URI mismatch')
raise webob.exc.HTTPBadRequest(explanation=expl)
if len(body) > 1:
expl = _('Request body contains too many items')
raise webob.exc.HTTPBadRequest(explanation=expl)
try:
db.instance_type_extra_specs_update_or_create(context, flavor_id,
body)
except db_exc.DBDuplicateEntry as error:
raise webob.exc.HTTPBadRequest(explanation=error.format_message())
return body
@wsgi.serializers(xml=ExtraSpecTemplate)
def show(self, req, flavor_id, id):
"""Return a single extra spec item."""
context = req.environ['nova.context']
self.authorize(context, action='show')
try:
extra_spec = db.instance_type_extra_specs_get_item(context,
flavor_id, id)
return extra_spec
except exception.InstanceTypeExtraSpecsNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
@wsgi.response(204)
def delete(self, req, flavor_id, id):
"""Deletes an existing extra spec."""
context = req.environ['nova.context']
self.authorize(context, action='delete')
db.instance_type_extra_specs_delete(context, flavor_id, id)
class FlavorsExtraSpecs(extensions.V3APIExtensionBase):
"""Flavors Extension."""
name = 'FlavorsExtraSpecs'
alias = FlavorExtraSpecsController.ALIAS
namespace = "http://docs.openstack.org/compute/core/%s/v3" % alias
version = 1
def get_resources(self):
extra_specs = extensions.ResourceExtension(
self.alias,
FlavorExtraSpecsController(),
parent=dict(member_name='flavor', collection_name='flavors'))
return [extra_specs]
def get_controller_extensions(self):
return []
@@ -0,0 +1,215 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 University of Southern California
# 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.compute.plugins.v3 import flavors_extraspecs
import nova.db
from nova import exception
from nova import test
from nova.tests.api.openstack import fakes
def return_create_flavor_extra_specs(context, flavor_id, extra_specs):
return stub_flavor_extra_specs()
def return_flavor_extra_specs(context, flavor_id):
return stub_flavor_extra_specs()
def return_flavor_extra_specs_item(context, flavor_id, key):
return {key: stub_flavor_extra_specs()[key]}
def return_empty_flavor_extra_specs(context, flavor_id):
return {}
def delete_flavor_extra_specs(context, flavor_id, key):
pass
def stub_flavor_extra_specs():
specs = {
"key1": "value1",
"key2": "value2",
"key3": "value3",
"key4": "value4",
"key5": "value5"}
return specs
class FlavorsExtraSpecsTest(test.TestCase):
def setUp(self):
super(FlavorsExtraSpecsTest, self).setUp()
fakes.stub_out_key_pair_funcs(self.stubs)
self.controller = flavors_extraspecs.FlavorExtraSpecsController()
def test_index(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_get',
return_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs')
res_dict = self.controller.index(req, 1)
self.assertEqual('value1', res_dict['extra_specs']['key1'])
def test_index_no_data(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_get',
return_empty_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs')
res_dict = self.controller.index(req, 1)
self.assertEqual(0, len(res_dict['extra_specs']))
def test_show(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_get_item',
return_flavor_extra_specs_item)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key5')
res_dict = self.controller.show(req, 1, 'key5')
self.assertEqual('value5', res_dict['key5'])
def test_show_spec_not_found(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_get',
return_empty_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key6')
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
req, 1, 'key6')
def test_delete(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_delete',
delete_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key5',
use_admin_context=True)
self.controller.delete(req, 1, 'key5')
def test_delete_no_admin(self):
self.stubs.Set(nova.db, 'instance_type_extra_specs_delete',
delete_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key5')
self.assertRaises(exception.NotAuthorized, self.controller.delete,
req, 1, 'key 5')
def test_create(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"extra_specs": {"key1": "value1"}}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs',
use_admin_context=True)
res_dict = self.controller.create(req, 1, body)
self.assertEqual('value1', res_dict['extra_specs']['key1'])
def test_create_no_admin(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"extra_specs": {"key1": "value1"}}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs')
self.assertRaises(exception.NotAuthorized, self.controller.create,
req, 1, body)
def test_create_empty_body(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, 1, '')
def test_update_item(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"key1": "value1"}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key1',
use_admin_context=True)
res_dict = self.controller.update(req, 1, 'key1', body)
self.assertEqual('value1', res_dict['key1'])
def test_update_item_no_admin(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"key1": "value1"}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key1')
self.assertRaises(exception.NotAuthorized, self.controller.update,
req, 1, 'key1', body)
def test_update_item_empty_body(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key1',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 1, 'key1', '')
def test_update_item_too_many_keys(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"key1": "value1", "key2": "value2"}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/key1',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 1, 'key1', body)
def test_update_item_body_uri_mismatch(self):
self.stubs.Set(nova.db,
'instance_type_extra_specs_update_or_create',
return_create_flavor_extra_specs)
body = {"key1": "value1"}
req = fakes.HTTPRequest.blank('/v3/flavors/1/extra-specs/bad',
use_admin_context=True)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 1, 'bad', body)
class FlavorsExtraSpecsXMLSerializerTest(test.TestCase):
def test_serializer(self):
serializer = flavors_extraspecs.ExtraSpecsTemplate()
expected = ("<?xml version='1.0' encoding='UTF-8'?>\n"
'<extra_specs><key1>value1</key1></extra_specs>')
text = serializer.serialize(dict(extra_specs={"key1": "value1"}))
self.assertEqual(text, expected)
def test_show_update_serializer(self):
serializer = flavors_extraspecs.ExtraSpecTemplate()
expected = ("<?xml version='1.0' encoding='UTF-8'?>\n"
'<extra_spec key="key1">value1</extra_spec>')
text = serializer.serialize(dict({"key1": "value1"}))
self.assertEqual(text, expected)
+5
View File
@@ -181,6 +181,11 @@ policy_data = """
"compute_extension:flavorextraspecs:create": "is_admin:True",
"compute_extension:flavorextraspecs:update": "is_admin:True",
"compute_extension:flavorextraspecs:delete": "is_admin:True",
"compute_extension:v3:flavor-extra-specs:index": "",
"compute_extension:v3:flavor-extra-specs:show": "",
"compute_extension:v3:flavor-extra-specs:create": "is_admin:True",
"compute_extension:v3:flavor-extra-specs:update": "is_admin:True",
"compute_extension:v3:flavor-extra-specs:delete": "is_admin:True",
"compute_extension:flavormanage": "",
"compute_extension:floating_ip_dns": "",
"compute_extension:floating_ip_pools": "",
+1
View File
@@ -78,6 +78,7 @@ nova.api.v3.extensions =
extension_info = nova.api.openstack.compute.plugins.v3.extension_info:ExtensionInfo
fixed_ips = nova.api.openstack.compute.plugins.v3.fixed_ips:FixedIPs
flavors = nova.api.openstack.compute.plugins.v3.flavors:Flavors
flavors_extraspecs = nova.api.openstack.compute.plugins.v3.flavors_extraspecs:FlavorsExtraSpecs
flavor_access = nova.api.openstack.compute.plugins.v3.flavor_access:FlavorAccess
flavor_disabled = nova.api.openstack.compute.plugins.v3.flavor_disabled:FlavorDisabled
flavor_rxtx = nova.api.openstack.compute.plugins.v3.flavor_rxtx:FlavorRxtx