summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorunicell <unicell@gmail.com>2012-08-13 20:19:54 +0800
committerunicell <unicell@gmail.com>2012-08-27 23:45:05 +0800
commit34c012c709cc5ae577330c7d67ba060293158210 (patch)
treee04765cbb3e09e9047c51aa9e03d4b1db15ac80c /nova/api
parent5e012d8d45935b68a5ce5d50ed043d4bb8066cf8 (diff)
downloadnova-34c012c709cc5ae577330c7d67ba060293158210.tar.gz
nova-34c012c709cc5ae577330c7d67ba060293158210.tar.xz
nova-34c012c709cc5ae577330c7d67ba060293158210.zip
Implement project specific flavors API
blueprint project-specific-flavors This change implements API extension to manage project specific flavor types, so that non-public flavor type can only see by projects with access rights. Change-Id: Ie2d2c605065b0c76897f843a4548a0c984a05f1a
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/compute/__init__.py3
-rw-r--r--nova/api/openstack/compute/contrib/flavor_access.py240
-rw-r--r--nova/api/openstack/compute/contrib/flavorextradata.py6
-rw-r--r--nova/api/openstack/compute/contrib/flavormanage.py3
-rw-r--r--nova/api/openstack/compute/flavors.py28
5 files changed, 273 insertions, 7 deletions
diff --git a/nova/api/openstack/compute/__init__.py b/nova/api/openstack/compute/__init__.py
index 70ec80dc2..9bf36bf25 100644
--- a/nova/api/openstack/compute/__init__.py
+++ b/nova/api/openstack/compute/__init__.py
@@ -90,7 +90,8 @@ class APIRouter(nova.api.openstack.APIRouter):
self.resources['flavors'] = flavors.create_resource()
mapper.resource("flavor", "flavors",
controller=self.resources['flavors'],
- collection={'detail': 'GET'})
+ collection={'detail': 'GET'},
+ member={'action': 'POST'})
self.resources['image_metadata'] = image_metadata.create_resource()
image_metadata_controller = self.resources['image_metadata']
diff --git a/nova/api/openstack/compute/contrib/flavor_access.py b/nova/api/openstack/compute/contrib/flavor_access.py
new file mode 100644
index 000000000..433d0c75d
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/flavor_access.py
@@ -0,0 +1,240 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 OpenStack, LLC.
+# 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.
+
+"""The flavor access extension."""
+
+import webob
+
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova.compute import instance_types
+from nova import exception
+
+
+authorize = extensions.soft_extension_authorizer('compute', 'flavor_access')
+
+
+def make_flavor(elem):
+ elem.set('{%s}is_public' % Flavor_access.namespace,
+ '%s:is_public' % Flavor_access.alias)
+
+
+def make_flavor_access(elem):
+ elem.set('flavor_id')
+ elem.set('tenant_id')
+
+
+class FlavorextradatumTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('flavor', selector='flavor')
+ make_flavor(root)
+ alias = Flavor_access.alias
+ namespace = Flavor_access.namespace
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
+
+
+class FlavorextradataTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('flavors')
+ elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors')
+ make_flavor(elem)
+ alias = Flavor_access.alias
+ namespace = Flavor_access.namespace
+ return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
+
+
+class FlavorAccessTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ def wrapped(obj, do_raise=False):
+ # wrap bare list in dict
+ return dict(flavor_access=obj)
+
+ root = xmlutil.TemplateElement('flavor_access', selector=wrapped)
+ elem = xmlutil.SubTemplateElement(root, 'access',
+ selector='flavor_access')
+ make_flavor_access(elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+def _marshall_flavor_access(flavor_id):
+ rval = []
+ try:
+ access_list = instance_types.\
+ get_instance_type_access_by_flavor_id(flavor_id)
+ except exception.FlavorNotFound:
+ explanation = _("Flavor not found.")
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
+ for access in access_list:
+ rval.append({'flavor_id': flavor_id,
+ 'tenant_id': access['project_id']})
+
+ return {'flavor_access': rval}
+
+
+class FlavorAccessController(object):
+ """The flavor access API controller for the OpenStack API."""
+
+ def __init__(self):
+ super(FlavorAccessController, self).__init__()
+
+ @wsgi.serializers(xml=FlavorAccessTemplate)
+ def index(self, req, flavor_id):
+ context = req.environ['nova.context']
+ authorize(context)
+
+ try:
+ flavor = instance_types.get_instance_type_by_flavor_id(flavor_id)
+ except exception.FlavorNotFound:
+ explanation = _("Flavor not found.")
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
+ # public flavor to all projects
+ if flavor['is_public']:
+ explanation = _("Access list not available for public flavors.")
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
+ # private flavor to listed projects only
+ return _marshall_flavor_access(flavor_id)
+
+
+class FlavorActionController(wsgi.Controller):
+ """The flavor access API controller for the OpenStack API."""
+
+ def _check_body(self, body):
+ if body is None or body == "":
+ raise webob.exc.HTTPBadRequest(explanation=_("No request body"))
+
+ def _get_flavor_refs(self, context):
+ """Return a dictionary mapping flavorid to flavor_ref."""
+
+ flavor_refs = instance_types.get_all_types(context)
+ rval = {}
+ for name, obj in flavor_refs.iteritems():
+ rval[obj['flavorid']] = obj
+ return rval
+
+ def _extend_flavor(self, flavor_rval, flavor_ref):
+ key = "%s:is_public" % (Flavor_access.alias)
+ flavor_rval[key] = flavor_ref['is_public']
+
+ @wsgi.extends
+ def show(self, req, resp_obj, id):
+ context = req.environ['nova.context']
+ if authorize(context):
+ # Attach our slave template to the response object
+ resp_obj.attach(xml=FlavorextradatumTemplate())
+
+ try:
+ flavor_ref = instance_types.get_instance_type_by_flavor_id(id)
+ except exception.FlavorNotFound:
+ explanation = _("Flavor not found.")
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
+ self._extend_flavor(resp_obj.obj['flavor'], flavor_ref)
+
+ @wsgi.extends
+ def detail(self, req, resp_obj):
+ context = req.environ['nova.context']
+ if authorize(context):
+ # Attach our slave template to the response object
+ resp_obj.attach(xml=FlavorextradataTemplate())
+
+ flavors = list(resp_obj.obj['flavors'])
+ flavor_refs = self._get_flavor_refs(context)
+
+ for flavor_rval in flavors:
+ flavor_ref = flavor_refs[flavor_rval['id']]
+ self._extend_flavor(flavor_rval, flavor_ref)
+
+ @wsgi.extends(action='create')
+ def create(self, req, body, resp_obj):
+ context = req.environ['nova.context']
+ if authorize(context):
+ # Attach our slave template to the response object
+ resp_obj.attach(xml=FlavorextradatumTemplate())
+
+ try:
+ fid = resp_obj.obj['flavor']['id']
+ flavor_ref = instance_types.get_instance_type_by_flavor_id(fid)
+ except exception.FlavorNotFound:
+ explanation = _("Flavor not found.")
+ raise webob.exc.HTTPNotFound(explanation=explanation)
+
+ self._extend_flavor(resp_obj.obj['flavor'], flavor_ref)
+
+ @wsgi.serializers(xml=FlavorAccessTemplate)
+ @wsgi.action("addTenantAccess")
+ def _addTenantAccess(self, req, id, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ self._check_body(body)
+
+ vals = body['addTenantAccess']
+ tenant = vals['tenant']
+
+ try:
+ instance_types.add_instance_type_access(id, tenant, context)
+ except exception.FlavorAccessExists as err:
+ raise webob.exc.HTTPConflict(explanation=str(err))
+
+ return _marshall_flavor_access(id)
+
+ @wsgi.serializers(xml=FlavorAccessTemplate)
+ @wsgi.action("removeTenantAccess")
+ def _removeTenantAccess(self, req, id, body):
+ context = req.environ['nova.context']
+ authorize(context)
+ self._check_body(body)
+
+ vals = body['removeTenantAccess']
+ tenant = vals['tenant']
+
+ try:
+ instance_types.remove_instance_type_access(id, tenant, context)
+ except exception.FlavorAccessNotFound, e:
+ raise webob.exc.HTTPNotFound(explanation=str(e))
+
+ return _marshall_flavor_access(id)
+
+
+class Flavor_access(extensions.ExtensionDescriptor):
+ """Flavor access supprt"""
+
+ name = "FlavorAccess"
+ alias = "os-flavor-access"
+ namespace = ("http://docs.openstack.org/compute/ext/"
+ "flavor_access/api/v2")
+ updated = "2012-08-01T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ res = extensions.ResourceExtension(
+ 'os-flavor-access',
+ controller=FlavorAccessController(),
+ parent=dict(member_name='flavor', collection_name='flavors'))
+ resources.append(res)
+
+ return resources
+
+ def get_controller_extensions(self):
+ extension = extensions.ControllerExtension(
+ self, 'flavors', FlavorActionController())
+
+ return [extension]
diff --git a/nova/api/openstack/compute/contrib/flavorextradata.py b/nova/api/openstack/compute/contrib/flavorextradata.py
index 5a6558ad0..834b68c60 100644
--- a/nova/api/openstack/compute/contrib/flavorextradata.py
+++ b/nova/api/openstack/compute/contrib/flavorextradata.py
@@ -35,10 +35,10 @@ authorize = extensions.soft_extension_authorizer('compute', 'flavorextradata')
class FlavorextradataController(wsgi.Controller):
- def _get_flavor_refs(self):
+ def _get_flavor_refs(self, context):
"""Return a dictionary mapping flavorid to flavor_ref."""
- flavor_refs = instance_types.get_all_types()
+ flavor_refs = instance_types.get_all_types(context)
rval = {}
for name, obj in flavor_refs.iteritems():
rval[obj['flavorid']] = obj
@@ -71,7 +71,7 @@ class FlavorextradataController(wsgi.Controller):
resp_obj.attach(xml=FlavorextradataTemplate())
flavors = list(resp_obj.obj['flavors'])
- flavor_refs = self._get_flavor_refs()
+ flavor_refs = self._get_flavor_refs(context)
for flavor_rval in flavors:
flavor_ref = flavor_refs[flavor_rval['id']]
diff --git a/nova/api/openstack/compute/contrib/flavormanage.py b/nova/api/openstack/compute/contrib/flavormanage.py
index 4dedcf981..3bcbf6981 100644
--- a/nova/api/openstack/compute/contrib/flavormanage.py
+++ b/nova/api/openstack/compute/contrib/flavormanage.py
@@ -65,11 +65,12 @@ class FlavorManageController(wsgi.Controller):
ephemeral_gb = vals.get('OS-FLV-EXT-DATA:ephemeral')
swap = vals.get('swap')
rxtx_factor = vals.get('rxtx_factor')
+ is_public = vals.get('os-flavor-access:is_public')
try:
flavor = instance_types.create(name, memory_mb, vcpus,
root_gb, ephemeral_gb, flavorid,
- swap, rxtx_factor)
+ swap, rxtx_factor, is_public)
except exception.InstanceTypeExists as err:
raise webob.exc.HTTPConflict(explanation=str(err))
diff --git a/nova/api/openstack/compute/flavors.py b/nova/api/openstack/compute/flavors.py
index 56b2e18ab..1a96c1346 100644
--- a/nova/api/openstack/compute/flavors.py
+++ b/nova/api/openstack/compute/flavors.py
@@ -91,12 +91,36 @@ class Controller(wsgi.Controller):
return self._view_builder.show(req, flavor)
+ def _get_is_public(self, req):
+ """Parse is_public into something usable."""
+ is_public = req.params.get('is_public', None)
+
+ if is_public is None:
+ # preserve default value of showing only public flavors
+ return True
+ elif is_public is True or \
+ is_public.lower() in ['t', 'true', 'yes', '1']:
+ return True
+ elif is_public is False or \
+ is_public.lower() in ['f', 'false', 'no', '0']:
+ return False
+ elif is_public.lower() == 'none':
+ # value to match all flavors, ignore is_public
+ return None
+ else:
+ msg = _('Invalid is_public filter [%s]') % req.params['is_public']
+ raise webob.exc.HTTPBadRequest(explanation=msg)
+
def _get_flavors(self, req):
"""Helper function that returns a list of flavor dicts."""
filters = {}
context = req.environ['nova.context']
- if not context.is_admin:
+ if context.is_admin:
+ # Only admin has query access to all flavor types
+ filters['is_public'] = self._get_is_public(req)
+ else:
+ filters['is_public'] = True
filters['disabled'] = False
if 'minRam' in req.params:
@@ -113,7 +137,7 @@ class Controller(wsgi.Controller):
msg = _('Invalid minDisk filter [%s]') % req.params['minDisk']
raise webob.exc.HTTPBadRequest(explanation=msg)
- flavors = instance_types.get_all_types(filters=filters)
+ flavors = instance_types.get_all_types(context, filters=filters)
flavors_list = flavors.values()
sorted_flavors = sorted(flavors_list,
key=lambda item: item['flavorid'])