Skip to content

Commit

Permalink
Clean up non-spec output in flavor extensions
Browse files Browse the repository at this point in the history
Adds option to cache flavors in the request object like instances.
Modifies the flavorextradata extension to use the cache and optimizes
tests. Adds flavor_disabled extension to control the extra data and
includes tests. Fixes api samples to show the new extension.

Fixes bug 1043585

Change-Id: Ie89df24a2891e3869d3fb604e07c79e8c913f290
  • Loading branch information
vishvananda committed Aug 30, 2012
1 parent 4a6193b commit f9fa7a6
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 101 deletions.
1 change: 1 addition & 0 deletions etc/nova/policy.json
Expand Up @@ -36,6 +36,7 @@
"compute_extension:extended_server_attributes": [["rule:admin_api"]],
"compute_extension:extended_status": [],
"compute_extension:flavor_access": [],
"compute_extension:flavor_disabled": [],
"compute_extension:flavorextradata": [],
"compute_extension:flavorextraspecs": [],
"compute_extension:flavormanage": [["rule:admin_api"]],
Expand Down
89 changes: 89 additions & 0 deletions nova/api/openstack/compute/contrib/flavor_disabled.py
@@ -0,0 +1,89 @@
# Copyright 2012 Nebula, Inc.
#
# 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 Disabled API extension."""

from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil


authorize = extensions.soft_extension_authorizer('compute', 'flavor_disabled')


class FlavorDisabledController(wsgi.Controller):
def _extend_flavors(self, req, flavors):
for flavor in flavors:
db_flavor = req.get_db_flavor(flavor['id'])
key = "%s:disabled" % Flavor_disabled.alias
flavor[key] = db_flavor['disabled']

def _show(self, req, resp_obj):
if not authorize(req.environ['nova.context']):
return
if 'flavor' in resp_obj.obj:
resp_obj.attach(xml=FlavorDisabledTemplate())
self._extend_flavors(req, [resp_obj.obj['flavor']])

@wsgi.extends
def show(self, req, resp_obj, id):
return self._show(req, resp_obj)

@wsgi.extends(action='create')
def create(self, req, resp_obj, body):
return self._show(req, resp_obj)

@wsgi.extends
def detail(self, req, resp_obj):
if not authorize(req.environ['nova.context']):
return
resp_obj.attach(xml=FlavorsDisabledTemplate())
self._extend_flavors(req, list(resp_obj.obj['flavors']))


class Flavor_disabled(extensions.ExtensionDescriptor):
"""Support to show the disabled status of a flavor"""

name = "FlavorDisabled"
alias = "OS-FLV-DISABLED"
namespace = ("http://docs.openstack.org/compute/ext/"
"flavor_disabled/api/v1.1")
updated = "2012-08-29T00:00:00+00:00"

def get_controller_extensions(self):
controller = FlavorDisabledController()
extension = extensions.ControllerExtension(self, 'flavors', controller)
return [extension]


def make_flavor(elem):
elem.set('{%s}disabled' % Flavor_disabled.namespace,
'%s:disabled' % Flavor_disabled.alias)


class FlavorDisabledTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('flavor', selector='flavor')
make_flavor(root)
return xmlutil.SlaveTemplate(root, 1, nsmap={
Flavor_disabled.alias: Flavor_disabled.namespace})


class FlavorsDisabledTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('flavors')
elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors')
make_flavor(elem)
return xmlutil.SlaveTemplate(root, 1, nsmap={
Flavor_disabled.alias: Flavor_disabled.namespace})
72 changes: 20 additions & 52 deletions nova/api/openstack/compute/contrib/flavorextradata.py
Expand Up @@ -27,71 +27,39 @@
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', 'flavorextradata')


class FlavorextradataController(wsgi.Controller):
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:ephemeral" % (Flavorextradata.alias)
flavor_rval[key] = flavor_ref['ephemeral_gb']
def _extend_flavors(self, req, flavors):
for flavor in flavors:
db_flavor = req.get_db_flavor(flavor['id'])
key = "%s:ephemeral" % Flavorextradata.alias
flavor[key] = db_flavor['ephemeral_gb']

def _show(self, req, resp_obj):
if not authorize(req.environ['nova.context']):
return
if 'flavor' in resp_obj.obj:
resp_obj.attach(xml=FlavorextradatumTemplate())
self._extend_flavors(req, [resp_obj.obj['flavor']])

@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())
return self._show(req, resp_obj)

try:
flavor_ref = instance_types.get_instance_type_by_flavor_id(id)
except exception.FlavorNotFound:
explanation = _("Flavor not found.")
raise exception.HTTPNotFound(explanation=explanation)

self._extend_flavor(resp_obj.obj['flavor'], flavor_ref)
@wsgi.extends(action='create')
def create(self, req, resp_obj, body):
return self._show(req, resp_obj)

@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 exception.HTTPNotFound(explanation=explanation)

self._extend_flavor(resp_obj.obj['flavor'], flavor_ref)
if not authorize(req.environ['nova.context']):
return
resp_obj.attach(xml=FlavorextradataTemplate())
self._extend_flavors(req, list(resp_obj.obj['flavors']))


class Flavorextradata(extensions.ExtensionDescriptor):
Expand Down
1 change: 1 addition & 0 deletions nova/api/openstack/compute/contrib/flavormanage.py
Expand Up @@ -71,6 +71,7 @@ def _create(self, req, body):
flavor = instance_types.create(name, memory_mb, vcpus,
root_gb, ephemeral_gb, flavorid,
swap, rxtx_factor, is_public)
req.cache_db_flavor(flavor)
except exception.InstanceTypeExists as err:
raise webob.exc.HTTPConflict(explanation=str(err))

Expand Down
2 changes: 2 additions & 0 deletions nova/api/openstack/compute/flavors.py
Expand Up @@ -79,13 +79,15 @@ def index(self, req):
def detail(self, req):
"""Return all flavors in detail."""
flavors = self._get_flavors(req)
req.cache_db_flavors(flavors)
return self._view_builder.detail(req, flavors)

@wsgi.serializers(xml=FlavorTemplate)
def show(self, req, id):
"""Return data about the given flavor id."""
try:
flavor = instance_types.get_instance_type_by_flavor_id(id)
req.cache_db_flavor(flavor)
except exception.NotFound:
raise webob.exc.HTTPNotFound()

Expand Down
6 changes: 0 additions & 6 deletions nova/api/openstack/compute/views/flavors.py
Expand Up @@ -49,12 +49,6 @@ def show(self, request, flavor):
},
}

# NOTE(sirp): disabled attribute is namespaced for now for
# compatability with the OpenStack API. This should ultimately be made
# a first class attribute.
flavor_dict["flavor"]["OS-FLV-DISABLED:disabled"] =\
flavor.get("disabled", "")

return flavor_dict

def index(self, request, flavors):
Expand Down
61 changes: 37 additions & 24 deletions nova/api/openstack/wsgi.py
Expand Up @@ -66,49 +66,62 @@ class Request(webob.Request):

def __init__(self, *args, **kwargs):
super(Request, self).__init__(*args, **kwargs)
self._extension_data = {'db_instances': {}}
self._extension_data = {'db_items': {}}

def cache_db_instances(self, instances):
def cache_db_items(self, key, items, item_key='id'):
"""
Allow API methods to store instances from a DB query to be
Allow API methods to store objects from a DB query to be
used by API extensions within the same API request.
An instance of this class only lives for the lifetime of a
single API request, so there's no need to implement full
cache management.
"""
db_instances = self._extension_data['db_instances']
for instance in instances:
db_instances[instance['uuid']] = instance
db_items = self._extension_data['db_items'].setdefault(key, {})
for item in items:
db_items[item[item_key]] = item

def cache_db_instance(self, instance):
def get_db_items(self, key):
"""
Allow API methods to store an instance from a DB query to be
used by API extensions within the same API request.
Allow an API extension to get previously stored objects within
the same API request.
An instance of this class only lives for the lifetime of a
single API request, so there's no need to implement full
cache management.
Note that the object data will be slightly stale.
"""
self.cache_db_instances([instance])
return self._extension_data['db_items'][key]

def get_db_instances(self):
def get_db_item(self, key, item_key):
"""
Allow an API extension to get previously stored instances within
the same API request.
Allow an API extension to get a previously stored object
within the same API request.
Note that the instance data will be slightly stale.
Note that the object data will be slightly stale.
"""
return self._extension_data['db_instances']
return self.get_db_items(key).get(item_key)

def cache_db_instances(self, instances):
self.cache_db_items('instances', instances, 'uuid')

def cache_db_instance(self, instance):
self.cache_db_items('instances', [instance], 'uuid')

def get_db_instances(self):
return self.get_db_items('instances')

def get_db_instance(self, instance_uuid):
"""
Allow an API extension to get a previously stored instance
within the same API request.
return self.get_db_item('instances', instance_uuid)

Note that the instance data will be slightly stale.
"""
return self._extension_data['db_instances'].get(instance_uuid)
def cache_db_flavors(self, flavors):
self.cache_db_items('flavors', flavors, 'flavorid')

def cache_db_flavor(self, flavor):
self.cache_db_items('flavors', [flavor], 'flavorid')

def get_db_flavors(self):
return self.get_db_items('flavors')

def get_db_flavor(self, flavorid):
return self.get_db_item('flavors', flavorid)

def best_match_content_type(self):
"""Determine the requested response content-type."""
Expand Down

0 comments on commit f9fa7a6

Please sign in to comment.