summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCole Robinson <crobinso@redhat.com>2012-01-19 18:39:11 -0500
committerCole Robinson <crobinso@redhat.com>2012-01-24 13:30:23 -0500
commit35b3c08a463dd35a41f3c44f3fa8273b915cb378 (patch)
tree60933ff32d588d4569b19315d29485bcf9404d16
parentfefb88877c6d6f00626df747eb0172484c16f0ec (diff)
downloadnova-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.py95
-rw-r--r--nova/compute/instance_types.py10
-rw-r--r--nova/db/sqlalchemy/api.py8
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_flavor_manage.py130
-rw-r--r--nova/tests/api/openstack/compute/test_extensions.py1
-rw-r--r--nova/tests/test_instance_types_extra_specs.py2
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