diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 55bf5b2fc40b..632a25b39777 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -134,6 +134,7 @@ "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:v3:flavor-manage": "rule:admin_api", "compute_extension:floating_ip_dns": "", "compute_extension:floating_ip_pools": "", "compute_extension:floating_ips": "", diff --git a/nova/api/openstack/compute/plugins/v3/flavor_manage.py b/nova/api/openstack/compute/plugins/v3/flavor_manage.py index 5b22539e9d10..2fe99a340075 100644 --- a/nova/api/openstack/compute/plugins/v3/flavor_manage.py +++ b/nova/api/openstack/compute/plugins/v3/flavor_manage.py @@ -14,27 +14,29 @@ import webob -from nova.api.openstack.compute import flavors as flavors_api +from nova.api.openstack.compute.plugins.v3 import flavors as flavors_api from nova.api.openstack.compute.views import flavors as flavors_view from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.compute import flavors from nova import exception +ALIAS = "flavor-manage" -authorize = extensions.extension_authorizer('compute', 'flavormanage') +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) class FlavorManageController(wsgi.Controller): """ The Flavor Lifecycle API controller for the OpenStack API. """ - _view_builder_class = flavors_view.ViewBuilder + _view_builder_class = flavors_view.V3ViewBuilder def __init__(self): super(FlavorManageController, self).__init__() @wsgi.action("delete") + @extensions.expected_errors((404)) def _delete(self, req, id): context = req.environ['nova.context'] authorize(context) @@ -42,26 +44,31 @@ class FlavorManageController(wsgi.Controller): try: flavor = flavors.get_flavor_by_flavor_id( id, ctxt=context, read_deleted="no") - except exception.NotFound as e: + except exception.FlavorNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.format_message()) flavors.destroy(flavor['name']) - return webob.Response(status_int=202) + return webob.Response(status_int=204) @wsgi.action("create") + @extensions.expected_errors((400, 409)) @wsgi.serializers(xml=flavors_api.FlavorTemplate) def _create(self, req, body): context = req.environ['nova.context'] authorize(context) + if not self.is_valid_body(body, 'flavor'): + raise webob.exc.HTTPBadRequest('Invalid request body ') + vals = body['flavor'] - name = vals['name'] + + name = vals.get('name') flavorid = vals.get('id') memory = vals.get('ram') vcpus = vals.get('vcpus') root_gb = vals.get('disk') - ephemeral_gb = vals.get('OS-FLV-EXT-DATA:ephemeral', 0) + ephemeral_gb = vals.get('ephemeral', 0) swap = vals.get('swap', 0) rxtx_factor = vals.get('rxtx_factor', 1.0) is_public = vals.get('os-flavor-access:is_public', True) @@ -82,18 +89,20 @@ class FlavorManageController(wsgi.Controller): return self._view_builder.show(req, flavor) -class Flavormanage(extensions.ExtensionDescriptor): +class FlavorManage(extensions.V3APIExtensionBase): """ Flavor create/delete API support """ name = "FlavorManage" - alias = "os-flavor-manage" - namespace = ("http://docs.openstack.org/compute/ext/" - "flavor_manage/api/v1.1") - updated = "2012-01-19T00:00:00+00:00" + alias = ALIAS + namespace = "http://docs.openstack.org/compute/core/%s/api/v3" % ALIAS + version = 1 def get_controller_extensions(self): controller = FlavorManageController() extension = extensions.ControllerExtension(self, 'flavors', controller) return [extension] + + def get_resources(self): + return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_flavor_manage.py b/nova/tests/api/openstack/compute/plugins/v3/test_flavor_manage.py index e5da25fad8d4..e6ec71229de6 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_flavor_manage.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_flavor_manage.py @@ -17,7 +17,7 @@ import datetime import webob -from nova.api.openstack.compute.contrib import flavormanage +from nova.api.openstack.compute.plugins.v3 import flavor_manage from nova.compute import flavors from nova import exception from nova.openstack.common import jsonutils @@ -27,7 +27,7 @@ from nova.tests.api.openstack import fakes def fake_get_flavor_by_flavor_id(flavorid, ctxt=None, read_deleted='yes'): if flavorid == 'failtest': - raise exception.NotFound("Not found sucka!") + raise exception.FlavorNotFound("Not found!") elif not str(flavorid) == '1234': raise Exception("This test expects flavorid 1234, not %s" % flavorid) if read_deleted != 'no': @@ -85,32 +85,19 @@ class FlavorManageTest(test.TestCase): fake_get_flavor_by_flavor_id) self.stubs.Set(flavors, "destroy", fake_destroy) self.stubs.Set(flavors, "create", fake_create) - self.flags( - osapi_compute_extension=[ - 'nova.api.openstack.compute.contrib.select_extensions'], - osapi_compute_ext_list=['Flavormanage', 'Flavorextradata', - 'Flavor_access', 'Flavor_rxtx', 'Flavor_swap']) + self.controller = flavor_manage.FlavorManageController() + self.app = fakes.wsgi_app_v3(init_only=('servers', 'flavors', + 'flavor-manage', + 'os-flavor-rxtx', + 'os-flavor-access')) - self.controller = flavormanage.FlavorManageController() - self.app = fakes.wsgi_app(init_only=('flavors',)) - - def test_delete(self): - req = fakes.HTTPRequest.blank('/v2/123/flavors/1234') - res = self.controller._delete(req, 1234) - self.assertEqual(res.status_int, 202) - - # subsequent delete should fail - self.assertRaises(webob.exc.HTTPNotFound, - self.controller._delete, req, "failtest") - - def test_create(self): - expected = { + self.expected_flavor = { "flavor": { "name": "test", "ram": 512, "vcpus": 2, "disk": 1, - "OS-FLV-EXT-DATA:ephemeral": 1, + "ephemeral": 1, "id": 1234, "swap": 512, "rxtx_factor": 1, @@ -118,7 +105,18 @@ class FlavorManageTest(test.TestCase): } } - url = '/v2/fake/flavors' + def test_delete(self): + req = fakes.HTTPRequest.blank('/v3/flavors/1234') + res = self.controller._delete(req, 1234) + self.assertEqual(res.status_int, 204) + + # subsequent delete should fail + self.assertRaises(webob.exc.HTTPNotFound, + self.controller._delete, req, "failtest") + + def test_create(self): + expected = self.expected_flavor + url = '/v3/flavors' req = webob.Request.blank(url) req.headers['Content-Type'] = 'application/json' req.method = 'POST' @@ -135,29 +133,16 @@ class FlavorManageTest(test.TestCase): "ram": 512, "vcpus": 2, "disk": 1, - "OS-FLV-EXT-DATA:ephemeral": 1, + "ephemeral": 1, "id": 1234, "swap": 512, "rxtx_factor": 1, } } - expected = { - "flavor": { - "name": "test", - "ram": 512, - "vcpus": 2, - "disk": 1, - "OS-FLV-EXT-DATA:ephemeral": 1, - "id": 1234, - "swap": 512, - "rxtx_factor": 1, - "os-flavor-access:is_public": True, - } - } - + expected = self.expected_flavor self.stubs.Set(flavors, "create", fake_create) - url = '/v2/fake/flavors' + url = '/v3/flavors' req = webob.Request.blank(url) req.headers['Content-Type'] = 'application/json' req.method = 'POST' @@ -168,26 +153,17 @@ class FlavorManageTest(test.TestCase): self.assertEquals(body["flavor"][key], expected["flavor"][key]) def test_create_without_flavorid(self): - expected = { - "flavor": { - "name": "test", - "ram": 512, - "vcpus": 2, - "disk": 1, - "OS-FLV-EXT-DATA:ephemeral": 1, - "swap": 512, - "rxtx_factor": 1, - "os-flavor-access:is_public": True, - } - } + expected = self.expected_flavor + del expected['flavor']['id'] - url = '/v2/fake/flavors' + url = '/v3/flavors' req = webob.Request.blank(url) req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = jsonutils.dumps(expected) res = req.get_response(self.app) body = jsonutils.loads(res.body) + for key in expected["flavor"]: self.assertEquals(body["flavor"][key], expected["flavor"][key]) @@ -198,7 +174,7 @@ class FlavorManageTest(test.TestCase): "ram": 512, "vcpus": 2, "disk": 1, - "OS-FLV-EXT-DATA:ephemeral": 1, + "ephemeral": 1, "id": 1235, "swap": 512, "rxtx_factor": 1, @@ -211,7 +187,7 @@ class FlavorManageTest(test.TestCase): raise exception.InstanceTypeExists(name=name) self.stubs.Set(flavors, "create", fake_create) - url = '/v2/fake/flavors' + url = '/v3/flavors' req = webob.Request.blank(url) req.headers['Content-Type'] = 'application/json' req.method = 'POST' diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index afada9433c13..33ba7eaecf40 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -191,6 +191,7 @@ policy_data = """ "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:v3:flavor-manage": "", "compute_extension:floating_ip_dns": "", "compute_extension:floating_ip_pools": "", "compute_extension:floating_ips": "", diff --git a/setup.cfg b/setup.cfg index 109d2457848a..2036ce27e205 100644 --- a/setup.cfg +++ b/setup.cfg @@ -81,6 +81,7 @@ nova.api.v3.extensions = flavors_extraspecs = nova.api.openstack.compute.plugins.v3.flavors_extraspecs:FlavorsExtraSpecs flavor_access = nova.api.openstack.compute.plugins.v3.flavor_access:FlavorAccess flavor_rxtx = nova.api.openstack.compute.plugins.v3.flavor_rxtx:FlavorRxtx + flavor_manage = nova.api.openstack.compute.plugins.v3.flavor_manage:FlavorManage hide_server_addresses = nova.api.openstack.compute.plugins.v3.hide_server_addresses:HideServerAddresses hosts = nova.api.openstack.compute.plugins.v3.hosts:Hosts hypervisors = nova.api.openstack.compute.plugins.v3.hypervisors:Hypervisors