diff options
| author | Cole Robinson <crobinso@redhat.com> | 2012-01-19 18:39:11 -0500 |
|---|---|---|
| committer | Cole Robinson <crobinso@redhat.com> | 2012-01-24 13:30:23 -0500 |
| commit | 35b3c08a463dd35a41f3c44f3fa8273b915cb378 (patch) | |
| tree | 60933ff32d588d4569b19315d29485bcf9404d16 | |
| parent | fefb88877c6d6f00626df747eb0172484c16f0ec (diff) | |
| download | nova-35b3c08a463dd35a41f3c44f3fa8273b915cb378.tar.gz nova-35b3c08a463dd35a41f3c44f3fa8273b915cb378.tar.xz nova-35b3c08a463dd35a41f3c44f3fa8273b915cb378.zip | |
Add an API extension for creating+deleting flavors
This extension is a step towards deprecating openstackx for horizon.
Most of the extension code is based on the equivalent in openstackx.
v2:
s/lifecycle/manage/ for all bits
Address Pádraig style issues
Drop purge API option
Adjust now inaccurate comment in DB api
Make extension admin_only
Extend existing /flavors namespace rather than os-flavor-lifecycle
Only allow API access from admin user
v3:
Some pep8 fixes
v4:
Adjust to root_gb, ephemeral_gb changes
Drop admin_only (it's on the way out AIUI)
Change-Id: I3fdfccdd8e7337e1759f5875c3b15fa9954371ef
| -rw-r--r-- | nova/api/openstack/compute/contrib/flavormanage.py | 95 | ||||
| -rw-r--r-- | nova/compute/instance_types.py | 10 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 8 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_flavor_manage.py | 130 | ||||
| -rw-r--r-- | nova/tests/api/openstack/compute/test_extensions.py | 1 | ||||
| -rw-r--r-- | nova/tests/test_instance_types_extra_specs.py | 2 |
6 files changed, 239 insertions, 7 deletions
diff --git a/nova/api/openstack/compute/contrib/flavormanage.py b/nova/api/openstack/compute/contrib/flavormanage.py new file mode 100644 index 000000000..604f40766 --- /dev/null +++ b/nova/api/openstack/compute/contrib/flavormanage.py @@ -0,0 +1,95 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 urlparse + +import webob + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack.compute import flavors as flavors_api +from nova.api.openstack.compute.views import flavors as flavors_view +from nova.compute import instance_types +from nova import log as logging +from nova import exception + + +LOG = logging.getLogger('nova.api.openstack.compute.contrib.flavormanage') + + +class FlavorManageController(wsgi.Controller): + """ + The Flavor Lifecycle API controller for the OpenStack API. + """ + _view_builder_class = flavors_view.ViewBuilder + + def __init__(self): + super(FlavorManageController, self).__init__() + + @wsgi.action("delete") + def _delete(self, req, id): + context = req.environ['nova.context'] + + if not context.is_admin: + return webob.Response(status_int=403) + + try: + flavor = instance_types.get_instance_type_by_flavor_id(id) + except exception.NotFound, e: + raise webob.exc.HTTPNotFound(explanation=str(e)) + + instance_types.destroy(flavor['name']) + + return webob.Response(status_int=202) + + @wsgi.action("create") + @wsgi.serializers(xml=flavors_api.FlavorTemplate) + def _create(self, req, body): + context = req.environ['nova.context'] + + if not context.is_admin: + return webob.Response(status_int=403) + + vals = body['flavor'] + name = vals['name'] + flavorid = vals['id'] + memory_mb = vals.get('ram') + vcpus = vals.get('vcpus') + root_gb = vals.get('disk') + ephemeral_gb = vals.get('disk') + swap = vals.get('swap') + rxtx_factor = vals.get('rxtx_factor') + + flavor = instance_types.create(name, memory_mb, vcpus, + root_gb, ephemeral_gb, flavorid, + swap, rxtx_factor) + + return self._view_builder.show(req, flavor) + + +class Flavormanage(extensions.ExtensionDescriptor): + """ + 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" + + def get_controller_extensions(self): + controller = FlavorManageController() + extension = extensions.ControllerExtension(self, 'flavors', controller) + return [extension] diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 191f9ca0e..583b2da4e 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -30,9 +30,15 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.instance_types') -def create(name, memory, vcpus, root_gb, ephemeral_gb, flavorid, swap=0, - rxtx_factor=1): +def create(name, memory, vcpus, root_gb, ephemeral_gb, flavorid, swap=None, + rxtx_factor=None): """Creates instance types.""" + + if swap is None: + swap = 0 + if rxtx_factor is None: + rxtx_factor = 1 + kwargs = { 'memory_mb': memory, 'vcpus': vcpus, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2e5f40a3c..5577fb932 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3340,13 +3340,13 @@ def instance_type_create(context, values): instance_type_ref.save() except Exception, e: raise exception.DBError(e) - return instance_type_ref + return _dict_with_extra_specs(instance_type_ref) def _dict_with_extra_specs(inst_type_query): - """Takes an instance OR volume type query returned by sqlalchemy - and returns it as a dictionary, converting the extra_specs - entry from a list of dicts: + """Takes an instance, volume, or instance type query returned + by sqlalchemy and returns it as a dictionary, converting the + extra_specs entry from a list of dicts: 'extra_specs' : [{'key': 'k1', 'value': 'v1', ...}, ...] diff --git a/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py b/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py new file mode 100644 index 000000000..1346e63d6 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_flavor_manage.py @@ -0,0 +1,130 @@ +# Copyright 2011 Andrew Bogott for the Wikimedia 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 datetime + +import webob + +from nova import exception +from nova import test +from nova.tests.api.openstack import fakes +from nova.compute import instance_types +from nova.api.openstack.compute.contrib import flavormanage + + +def fake_get_instance_type_by_flavor_id(flavorid): + if flavorid == "failtest": + raise exception.NotFound("Not found sucka!") + + return { + 'root_gb': 1, + 'ephemeral_gb': 1, + 'name': u'frob', + 'deleted': False, + 'created_at': datetime.datetime(2012, 1, 19, 18, 49, 30, 877329), + 'updated_at': None, + 'memory_mb': 256, + 'vcpus': 1, + 'flavorid': flavorid, + 'swap': 0, + 'rxtx_factor': 1.0, + 'extra_specs': {}, + 'deleted_at': None, + 'vcpu_weight': None, + 'id': 7 + } + + +def fake_purge(flavorname): + pass + + +def fake_destroy(flavorname): + pass + + +def fake_create(name, memory_mb, vcpus, root_gb, ephemeral_gb, + flavorid, swap, rxtx_factor): + newflavor = fake_get_instance_type_by_flavor_id(flavorid) + + newflavor["name"] = name + newflavor["memory_mb"] = int(memory_mb) + newflavor["vcpus"] = int(vcpus) + newflavor["root_gb"] = int(root_gb) + newflavor["ephemeral_gb"] = int(ephemeral_gb) + newflavor["swap"] = swap + newflavor["rxtx_factor"] = float(rxtx_factor) + + return newflavor + + +class FlavorManageTest(test.TestCase): + def setUp(self): + super(FlavorManageTest, self).setUp() + self.stubs.Set(instance_types, + "get_instance_type_by_flavor_id", + fake_get_instance_type_by_flavor_id) + self.stubs.Set(instance_types, "destroy", fake_destroy) + self.stubs.Set(instance_types, "create", fake_create) + + self.controller = flavormanage.FlavorManageController() + + def tearDown(self): + super(FlavorManageTest, self).tearDown() + + def test_delete(self): + req = fakes.HTTPRequest.blank( + '/v2/123/flavor/delete/1234', + use_admin_context=True) + + res = self.controller._delete(req, id) + self.assertEqual(res.status_int, 202) + + self.assertRaises(webob.exc.HTTPNotFound, + self.controller._delete, req, "failtest") + + req = fakes.HTTPRequest.blank( + '/v2/123/flavor/delete/1234', + use_admin_context=False) + + res = self.controller._delete(req, id) + self.assertEqual(res.status_int, 403) + + def test_create(self): + body = { + "flavor": { + "name": "test", + "ram": 512, + "vcpus": 2, + "disk": 10, + "id": 1235, + "swap": 512, + "rxtx_factor": 1, + } + } + + req = fakes.HTTPRequest.blank( + '/v2/123/flavor/create/', + use_admin_context=True) + + res = self.controller._create(req, body) + for key in body["flavor"]: + self.assertEquals(res["flavor"][key], body["flavor"][key]) + + req = fakes.HTTPRequest.blank( + '/v2/123/flavor/create/', + use_admin_context=False) + res = self.controller._create(req, body) + self.assertEqual(res.status_int, 403) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 796880e1f..820cf0b0e 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -163,6 +163,7 @@ class ExtensionControllerTest(ExtensionTestCase): "ExtendedStatus", "FlavorExtraSpecs", "FlavorExtraData", + "FlavorManage", "Floating_ips", "Floating_ip_dns", "Floating_ip_pools", diff --git a/nova/tests/test_instance_types_extra_specs.py b/nova/tests/test_instance_types_extra_specs.py index 8f4ea89d3..2bf09dd91 100644 --- a/nova/tests/test_instance_types_extra_specs.py +++ b/nova/tests/test_instance_types_extra_specs.py @@ -40,7 +40,7 @@ class InstanceTypeExtraSpecsTestCase(test.TestCase): values['extra_specs'] = specs ref = db.instance_type_create(self.context, values) - self.instance_type_id = ref.id + self.instance_type_id = ref["id"] def tearDown(self): # Remove the instance type from the database |
