¡@

Home 

OpenStack Study: volumes.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright 2011 Justin Santa Barbara

# 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 volumes api."""

import ast

import webob

from webob import exc

from cinder.api import common

from cinder.api.openstack import wsgi

from cinder.api.v2.views import volumes as volume_views

from cinder.api import xmlutil

from cinder import exception

from cinder.openstack.common import log as logging

from cinder.openstack.common import uuidutils

from cinder import utils

from cinder import volume as cinder_volume

from cinder.volume import utils as volume_utils

from cinder.volume import volume_types

LOG = logging.getLogger(__name__)

SCHEDULER_HINTS_NAMESPACE =\

"http://docs.openstack.org/block-service/ext/scheduler-hints/api/v2"

**** CubicPower OpenStack Study ****

def make_attachment(elem):

    elem.set('id')

    elem.set('server_id')

    elem.set('host_name')

    elem.set('volume_id')

    elem.set('device')

**** CubicPower OpenStack Study ****

def make_volume(elem):

    elem.set('id')

    elem.set('status')

    elem.set('size')

    elem.set('availability_zone')

    elem.set('created_at')

    elem.set('name')

    elem.set('bootable')

    elem.set('description')

    elem.set('volume_type')

    elem.set('snapshot_id')

    elem.set('source_volid')

    attachments = xmlutil.SubTemplateElement(elem, 'attachments')

    attachment = xmlutil.SubTemplateElement(attachments, 'attachment',

                                            selector='attachments')

    make_attachment(attachment)

    # Attach metadata node

    elem.append(common.MetadataTemplate())

volume_nsmap = {None: xmlutil.XMLNS_VOLUME_V2, 'atom': xmlutil.XMLNS_ATOM}

**** CubicPower OpenStack Study ****

class VolumeTemplate(xmlutil.TemplateBuilder):

**** CubicPower OpenStack Study ****

    def construct(self):

        root = xmlutil.TemplateElement('volume', selector='volume')

        make_volume(root)

        return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)

**** CubicPower OpenStack Study ****

class VolumesTemplate(xmlutil.TemplateBuilder):

**** CubicPower OpenStack Study ****

    def construct(self):

        root = xmlutil.TemplateElement('volumes')

        elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')

        make_volume(elem)

        return xmlutil.MasterTemplate(root, 1, nsmap=volume_nsmap)

**** CubicPower OpenStack Study ****

class CommonDeserializer(wsgi.MetadataXMLDeserializer):

"""Common deserializer to handle xml-formatted volume requests.

Handles standard volume attributes as well as the optional metadata

attribute

"""

metadata_deserializer = common.MetadataXMLDeserializer()

**** CubicPower OpenStack Study ****

    def _extract_scheduler_hints(self, volume_node):

        """Marshal the scheduler hints attribute of a parsed request."""

        node =\

            self.find_first_child_named_in_namespace(volume_node,

                                                     SCHEDULER_HINTS_NAMESPACE,

                                                     "scheduler_hints")

        if node:

            scheduler_hints = {}

            for child in self.extract_elements(node):

                scheduler_hints.setdefault(child.nodeName, [])

                value = self.extract_text(child).strip()

                scheduler_hints[child.nodeName].append(value)

            return scheduler_hints

        else:

            return None

**** CubicPower OpenStack Study ****

    def _extract_volume(self, node):

        """Marshal the volume attribute of a parsed request."""

        volume = {}

        volume_node = self.find_first_child_named(node, 'volume')

        attributes = ['name', 'description', 'size',

                      'volume_type', 'availability_zone', 'imageRef',

                      'snapshot_id', 'source_volid']

        for attr in attributes:

            if volume_node.getAttribute(attr):

                volume[attr] = volume_node.getAttribute(attr)

        metadata_node = self.find_first_child_named(volume_node, 'metadata')

        if metadata_node is not None:

            volume['metadata'] = self.extract_metadata(metadata_node)

        scheduler_hints = self._extract_scheduler_hints(volume_node)

        if scheduler_hints:

            volume['scheduler_hints'] = scheduler_hints

        return volume

**** CubicPower OpenStack Study ****

class CreateDeserializer(CommonDeserializer):

"""Deserializer to handle xml-formatted create volume requests.

Handles standard volume attributes as well as the optional metadata

attribute

"""

**** CubicPower OpenStack Study ****

    def default(self, string):

        """Deserialize an xml-formatted volume create request."""

        dom = utils.safe_minidom_parse_string(string)

        volume = self._extract_volume(dom)

        return {'body': {'volume': volume}}

**** CubicPower OpenStack Study ****

class VolumeController(wsgi.Controller):

"""The Volumes API controller for the OpenStack API."""

_view_builder_class = volume_views.ViewBuilder

**** CubicPower OpenStack Study ****

    def __init__(self, ext_mgr):

        self.volume_api = cinder_volume.API()

        self.ext_mgr = ext_mgr

        super(VolumeController, self).__init__()

    @wsgi.serializers(xml=VolumeTemplate)

**** CubicPower OpenStack Study ****

    def show(self, req, id):

        """Return data about the given volume."""

        context = req.environ['cinder.context']

        try:

            vol = self.volume_api.get(context, id)

            req.cache_resource(vol)

        except exception.NotFound:

            msg = _("Volume could not be found")

            raise exc.HTTPNotFound(explanation=msg)

        utils.add_visible_admin_metadata(context, vol, self.volume_api)

        return self._view_builder.detail(req, vol)

**** CubicPower OpenStack Study ****

    def delete(self, req, id):

        """Delete a volume."""

        context = req.environ['cinder.context']

        LOG.audit(_("Delete volume with id: %s"), id, context=context)

        try:

            volume = self.volume_api.get(context, id)

            self.volume_api.delete(context, volume)

        except exception.NotFound:

            msg = _("Volume could not be found")

            raise exc.HTTPNotFound(explanation=msg)

        except exception.VolumeAttached:

            msg = _("Volume cannot be deleted while in attached state")

            raise exc.HTTPBadRequest(explanation=msg)

        return webob.Response(status_int=202)

    @wsgi.serializers(xml=VolumesTemplate)

**** CubicPower OpenStack Study ****

    def index(self, req):

        """Returns a summary list of volumes."""

        return self._get_volumes(req, is_detail=False)

    @wsgi.serializers(xml=VolumesTemplate)

**** CubicPower OpenStack Study ****

    def detail(self, req):

        """Returns a detailed list of volumes."""

        return self._get_volumes(req, is_detail=True)

**** CubicPower OpenStack Study ****

    def _get_volumes(self, req, is_detail):

        """Returns a list of volumes, transformed through view builder."""

        context = req.environ['cinder.context']

        params = req.params.copy()

        marker = params.pop('marker', None)

        limit = params.pop('limit', None)

        sort_key = params.pop('sort_key', 'created_at')

        sort_dir = params.pop('sort_dir', 'desc')

        params.pop('offset', None)

        filters = params

        remove_invalid_options(context,

                               filters, self._get_volume_filter_options())

        # NOTE(thingee): v2 API allows name instead of display_name

        if 'name' in filters:

            filters['display_name'] = filters['name']

            del filters['name']

        if 'metadata' in filters:

            filters['metadata'] = ast.literal_eval(filters['metadata'])

        volumes = self.volume_api.get_all(context, marker, limit, sort_key,

                                          sort_dir, filters)

        volumes = [dict(vol.iteritems()) for vol in volumes]

        for volume in volumes:

            utils.add_visible_admin_metadata(context, volume, self.volume_api)

        limited_list = common.limited(volumes, req)

        if is_detail:

            volumes = self._view_builder.detail_list(req, limited_list)

        else:

            volumes = self._view_builder.summary_list(req, limited_list)

        req.cache_resource(limited_list)

        return volumes

**** CubicPower OpenStack Study ****

    def _image_uuid_from_href(self, image_href):

        # If the image href was generated by nova api, strip image_href

        # down to an id.

        try:

            image_uuid = image_href.split('/').pop()

        except (TypeError, AttributeError):

            msg = _("Invalid imageRef provided.")

            raise exc.HTTPBadRequest(explanation=msg)

        if not uuidutils.is_uuid_like(image_uuid):

            msg = _("Invalid imageRef provided.")

            raise exc.HTTPBadRequest(explanation=msg)

        return image_uuid

    @wsgi.response(202)

    @wsgi.serializers(xml=VolumeTemplate)

    @wsgi.deserializers(xml=CreateDeserializer)

**** CubicPower OpenStack Study ****

    def create(self, req, body):

        """Creates a new volume."""

        if not self.is_valid_body(body, 'volume'):

            msg = _("Missing required element '%s' in request body") % 'volume'

            raise exc.HTTPBadRequest(explanation=msg)

        LOG.debug('Create volume request body: %s', body)

        context = req.environ['cinder.context']

        volume = body['volume']

        kwargs = {}

        # NOTE(thingee): v2 API allows name instead of display_name

        if volume.get('name'):

            volume['display_name'] = volume.get('name')

            del volume['name']

        # NOTE(thingee): v2 API allows description instead of

        #                display_description

        if volume.get('description'):

            volume['display_description'] = volume.get('description')

            del volume['description']

        req_volume_type = volume.get('volume_type', None)

        if req_volume_type:

            try:

                if not uuidutils.is_uuid_like(req_volume_type):

                    kwargs['volume_type'] = \

                        volume_types.get_volume_type_by_name(

                            context, req_volume_type)

                else:

                    kwargs['volume_type'] = volume_types.get_volume_type(

                        context, req_volume_type)

            except exception.VolumeTypeNotFound:

                msg = _("Volume type not found.")

                raise exc.HTTPNotFound(explanation=msg)

        kwargs['metadata'] = volume.get('metadata', None)

        snapshot_id = volume.get('snapshot_id')

        if snapshot_id is not None:

            try:

                kwargs['snapshot'] = self.volume_api.get_snapshot(context,

                                                                  snapshot_id)

            except exception.NotFound:

                explanation = _('snapshot id:%s not found') % snapshot_id

                raise exc.HTTPNotFound(explanation=explanation)

        else:

            kwargs['snapshot'] = None

        source_volid = volume.get('source_volid')

        if source_volid is not None:

            try:

                kwargs['source_volume'] = \

                    self.volume_api.get_volume(context,

                                               source_volid)

            except exception.NotFound:

                explanation = _('source volume id:%s not found') % source_volid

                raise exc.HTTPNotFound(explanation=explanation)

        else:

            kwargs['source_volume'] = None

        size = volume.get('size', None)

        if size is None and kwargs['snapshot'] is not None:

            size = kwargs['snapshot']['volume_size']

        elif size is None and kwargs['source_volume'] is not None:

            size = kwargs['source_volume']['size']

        LOG.audit(_("Create volume of %s GB"), size, context=context)

        if self.ext_mgr.is_loaded('os-image-create'):

            image_href = volume.get('imageRef')

            if image_href:

                image_uuid = self._image_uuid_from_href(image_href)

                kwargs['image_id'] = image_uuid

        kwargs['availability_zone'] = volume.get('availability_zone', None)

        kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)

        new_volume = self.volume_api.create(context,

                                            size,

                                            volume.get('display_name'),

                                            volume.get('display_description'),

                                            **kwargs)

        # TODO(vish): Instance should be None at db layer instead of

        #             trying to lazy load, but for now we turn it into

        #             a dict to avoid an error.

        new_volume = dict(new_volume.iteritems())

        utils.add_visible_admin_metadata(context, new_volume, self.volume_api)

        retval = self._view_builder.detail(req, new_volume)

        return retval

**** CubicPower OpenStack Study ****

    def _get_volume_filter_options(self):

        """Return volume search options allowed by non-admin."""

        return ('name', 'status', 'metadata')

    @wsgi.serializers(xml=VolumeTemplate)

**** CubicPower OpenStack Study ****

    def update(self, req, id, body):

        """Update a volume."""

        context = req.environ['cinder.context']

        if not body:

            msg = _("Missing request body")

            raise exc.HTTPBadRequest(explanation=msg)

        if 'volume' not in body:

            msg = _("Missing required element '%s' in request body") % 'volume'

            raise exc.HTTPBadRequest(explanation=msg)

        volume = body['volume']

        update_dict = {}

        valid_update_keys = (

            'name',

            'description',

            'display_name',

            'display_description',

            'metadata',

        )

        for key in valid_update_keys:

            if key in volume:

                update_dict[key] = volume[key]

        # NOTE(thingee): v2 API allows name instead of display_name

        if 'name' in update_dict:

            update_dict['display_name'] = update_dict['name']

            del update_dict['name']

        # NOTE(thingee): v2 API allows name instead of display_name

        if 'description' in update_dict:

            update_dict['display_description'] = update_dict['description']

            del update_dict['description']

        try:

            volume = self.volume_api.get(context, id)

            volume_utils.notify_about_volume_usage(context, volume,

                                                   'update.start')

            self.volume_api.update(context, volume, update_dict)

        except exception.NotFound:

            msg = _("Volume could not be found")

            raise exc.HTTPNotFound(explanation=msg)

        volume.update(update_dict)

        utils.add_visible_admin_metadata(context, volume, self.volume_api)

        volume_utils.notify_about_volume_usage(context, volume,

                                               'update.end')

        return self._view_builder.detail(req, volume)

def create_resource(ext_mgr):

    return wsgi.Resource(VolumeController(ext_mgr))

def remove_invalid_options(context, filters, allowed_search_options):

    """Remove search options that are not valid for non-admin API/context."""

    if context.is_admin:

        # Allow all options

        return

    # Otherwise, strip out all unknown options

    unknown_options = [opt for opt in filters

                       if opt not in allowed_search_options]

    bad_options = ", ".join(unknown_options)

    log_msg = _("Removing options '%s' from query") % bad_options

    LOG.debug(log_msg)

    for opt in unknown_options:

        del filters[opt]

**** CubicPower OpenStack Study ****

def create_resource(ext_mgr):

    return wsgi.Resource(VolumeController(ext_mgr))

**** CubicPower OpenStack Study ****

def remove_invalid_options(context, filters, allowed_search_options):

    """Remove search options that are not valid for non-admin API/context."""

    if context.is_admin:

        # Allow all options

        return

    # Otherwise, strip out all unknown options

    unknown_options = [opt for opt in filters

                       if opt not in allowed_search_options]

    bad_options = ", ".join(unknown_options)

    log_msg = _("Removing options '%s' from query") % bad_options

    LOG.debug(log_msg)

    for opt in unknown_options:

        del filters[opt]