¡@

Home 

OpenStack Study: cinder.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright 2010 United States Government as represented by the

# Administrator of the National Aeronautics and Space Administration.

# 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.

"""

Handles all requests relating to volumes + cinder.

"""

import copy

import sys

from cinderclient import exceptions as cinder_exception

from cinderclient import service_catalog

from cinderclient.v1 import client as cinder_client

from oslo.config import cfg

from nova import exception

from nova.openstack.common.gettextutils import _

from nova.openstack.common import log as logging

cinder_opts = [

cfg.StrOpt('cinder_catalog_info',

default='volume:cinder:publicURL',

help='Info to match when looking for cinder in the service '

'catalog. Format is: separated values of the form: '

'::'),

cfg.StrOpt('cinder_endpoint_template',

help='Override service catalog lookup with template for cinder '

'endpoint e.g. http://localhost:8776/v1/%(project_id)s'),

cfg.StrOpt('os_region_name',

help='Region name of this node'),

cfg.StrOpt('cinder_ca_certificates_file',

help='Location of ca certificates file to use for cinder '

'client requests.'),

cfg.IntOpt('cinder_http_retries',

default=3,

help='Number of cinderclient retries on failed http calls'),

cfg.BoolOpt('cinder_api_insecure',

default=False,

help='Allow to perform insecure SSL requests to cinder'),

cfg.BoolOpt('cinder_cross_az_attach',

default=True,

help='Allow attach between instance and volume in different '

'availability zones.'),

]

CONF = cfg.CONF

CONF.register_opts(cinder_opts)

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

def cinderclient(context):

    # FIXME: the cinderclient ServiceCatalog object is mis-named.

    #        It actually contains the entire access blob.

    # Only needed parts of the service catalog are passed in, see

    # nova/context.py.

    compat_catalog = {

        'access': {'serviceCatalog': context.service_catalog or []}

    }

    sc = service_catalog.ServiceCatalog(compat_catalog)

    if CONF.cinder_endpoint_template:

        url = CONF.cinder_endpoint_template % context.to_dict()

    else:

        info = CONF.cinder_catalog_info

        service_type, service_name, endpoint_type = info.split(':')

        # extract the region if set in configuration

        if CONF.os_region_name:

            attr = 'region'

            filter_value = CONF.os_region_name

        else:

            attr = None

            filter_value = None

        url = sc.url_for(attr=attr,

                         filter_value=filter_value,

                         service_type=service_type,

                         service_name=service_name,

                         endpoint_type=endpoint_type)

    LOG.debug(_('Cinderclient connection created using URL: %s') % url)

    c = cinder_client.Client(context.user_id,

                             context.auth_token,

                             project_id=context.project_id,

                             auth_url=url,

                             insecure=CONF.cinder_api_insecure,

                             retries=CONF.cinder_http_retries,

                             cacert=CONF.cinder_ca_certificates_file)

    # noauth extracts user_id:project_id from auth_token

    c.client.auth_token = context.auth_token or '%s:%s' % (context.user_id,

                                                           context.project_id)

    c.client.management_url = url

    return c

**** CubicPower OpenStack Study ****

def _untranslate_volume_summary_view(context, vol):

    """Maps keys for volumes summary view."""

    d = {}

    d['id'] = vol.id

    d['status'] = vol.status

    d['size'] = vol.size

    d['availability_zone'] = vol.availability_zone

    d['created_at'] = vol.created_at

    # TODO(jdg): The calling code expects attach_time and

    #            mountpoint to be set. When the calling

    #            code is more defensive this can be

    #            removed.

    d['attach_time'] = ""

    d['mountpoint'] = ""

    if vol.attachments:

        att = vol.attachments[0]

        d['attach_status'] = 'attached'

        d['instance_uuid'] = att['server_id']

        d['mountpoint'] = att['device']

    else:

        d['attach_status'] = 'detached'

    d['display_name'] = vol.display_name

    d['display_description'] = vol.display_description

    # TODO(jdg): Information may be lost in this translation

    d['volume_type_id'] = vol.volume_type

    d['snapshot_id'] = vol.snapshot_id

    d['volume_metadata'] = {}

    for key, value in vol.metadata.items():

        d['volume_metadata'][key] = value

    if hasattr(vol, 'volume_image_metadata'):

        d['volume_image_metadata'] = copy.deepcopy(vol.volume_image_metadata)

    return d

**** CubicPower OpenStack Study ****

def _untranslate_snapshot_summary_view(context, snapshot):

    """Maps keys for snapshots summary view."""

    d = {}

    d['id'] = snapshot.id

    d['status'] = snapshot.status

    d['progress'] = snapshot.progress

    d['size'] = snapshot.size

    d['created_at'] = snapshot.created_at

    d['display_name'] = snapshot.display_name

    d['display_description'] = snapshot.display_description

    d['volume_id'] = snapshot.volume_id

    d['project_id'] = snapshot.project_id

    d['volume_size'] = snapshot.size

    return d

**** CubicPower OpenStack Study ****

def translate_volume_exception(method):

    """Transforms the exception for the volume but keeps its traceback intact.

    """

    def wrapper(self, ctx, volume_id, *args, **kwargs):

        try:

            res = method(self, ctx, volume_id, *args, **kwargs)

        except cinder_exception.ClientException:

            exc_type, exc_value, exc_trace = sys.exc_info()

            if isinstance(exc_value, cinder_exception.NotFound):

                exc_value = exception.VolumeNotFound(volume_id=volume_id)

            elif isinstance(exc_value, cinder_exception.BadRequest):

                exc_value = exception.InvalidInput(reason=exc_value.message)

            raise exc_value, None, exc_trace

        return res

    return wrapper

**** CubicPower OpenStack Study ****

 def wrapper(self, ctx, volume_id, *args, **kwargs):

        try:

            res = method(self, ctx, volume_id, *args, **kwargs)

        except cinder_exception.ClientException:

            exc_type, exc_value, exc_trace = sys.exc_info()

            if isinstance(exc_value, cinder_exception.NotFound):

                exc_value = exception.VolumeNotFound(volume_id=volume_id)

            elif isinstance(exc_value, cinder_exception.BadRequest):

                exc_value = exception.InvalidInput(reason=exc_value.message)

            raise exc_value, None, exc_trace

        return res

    return wrapper

**** CubicPower OpenStack Study ****

def translate_snapshot_exception(method):

    """Transforms the exception for the snapshot but keeps its traceback

       intact.

    """

    def wrapper(self, ctx, snapshot_id, *args, **kwargs):

        try:

            res = method(self, ctx, snapshot_id, *args, **kwargs)

        except cinder_exception.ClientException:

            exc_type, exc_value, exc_trace = sys.exc_info()

            if isinstance(exc_value, cinder_exception.NotFound):

                exc_value = exception.SnapshotNotFound(snapshot_id=snapshot_id)

            raise exc_value, None, exc_trace

        return res

    return wrapper

**** CubicPower OpenStack Study ****

 def wrapper(self, ctx, snapshot_id, *args, **kwargs):

        try:

            res = method(self, ctx, snapshot_id, *args, **kwargs)

        except cinder_exception.ClientException:

            exc_type, exc_value, exc_trace = sys.exc_info()

            if isinstance(exc_value, cinder_exception.NotFound):

                exc_value = exception.SnapshotNotFound(snapshot_id=snapshot_id)

            raise exc_value, None, exc_trace

        return res

    return wrapper

**** CubicPower OpenStack Study ****

class API(object):

"""API for interacting with the volume manager."""

@translate_volume_exception

**** CubicPower OpenStack Study ****

    def get(self, context, volume_id):

        item = cinderclient(context).volumes.get(volume_id)

        return _untranslate_volume_summary_view(context, item)

**** CubicPower OpenStack Study ****

    def get_all(self, context, search_opts={}):

        items = cinderclient(context).volumes.list(detailed=True)

        rval = []

        for item in items:

            rval.append(_untranslate_volume_summary_view(context, item))

        return rval

**** CubicPower OpenStack Study ****

    def check_attached(self, context, volume):

        """Raise exception if volume in use."""

        if volume['status'] != "in-use":

            msg = _("status must be 'in-use'")

            raise exception.InvalidVolume(reason=msg)

**** CubicPower OpenStack Study ****

    def check_attach(self, context, volume, instance=None):

        # TODO(vish): abstract status checking?

        if volume['status'] != "available":

            msg = _("status must be 'available'")

            raise exception.InvalidVolume(reason=msg)

        if volume['attach_status'] == "attached":

            msg = _("already attached")

            raise exception.InvalidVolume(reason=msg)

        if instance and not CONF.cinder_cross_az_attach:

            if instance['availability_zone'] != volume['availability_zone']:

                msg = _("Instance and volume not in same availability_zone")

                raise exception.InvalidVolume(reason=msg)

**** CubicPower OpenStack Study ****

    def check_detach(self, context, volume):

        # TODO(vish): abstract status checking?

        if volume['status'] == "available":

            msg = _("already detached")

            raise exception.InvalidVolume(reason=msg)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def reserve_volume(self, context, volume_id):

        cinderclient(context).volumes.reserve(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def unreserve_volume(self, context, volume_id):

        cinderclient(context).volumes.unreserve(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def begin_detaching(self, context, volume_id):

        cinderclient(context).volumes.begin_detaching(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def roll_detaching(self, context, volume_id):

        cinderclient(context).volumes.roll_detaching(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def attach(self, context, volume_id, instance_uuid, mountpoint):

        cinderclient(context).volumes.attach(volume_id, instance_uuid,

                                             mountpoint)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def detach(self, context, volume_id):

        cinderclient(context).volumes.detach(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def initialize_connection(self, context, volume_id, connector):

        return cinderclient(context).volumes.initialize_connection(volume_id,

                                                                   connector)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def terminate_connection(self, context, volume_id, connector):

        return cinderclient(context).volumes.terminate_connection(volume_id,

                                                                  connector)

**** CubicPower OpenStack Study ****

    def migrate_volume_completion(self, context, old_volume_id, new_volume_id,

                                  error=False):

        return cinderclient(context).volumes.migrate_volume_completion(

            old_volume_id, new_volume_id, error)

**** CubicPower OpenStack Study ****

    def create(self, context, size, name, description, snapshot=None,

               image_id=None, volume_type=None, metadata=None,

               availability_zone=None):

        if snapshot is not None:

            snapshot_id = snapshot['id']

        else:

            snapshot_id = None

        kwargs = dict(snapshot_id=snapshot_id,

                      display_name=name,

                      display_description=description,

                      volume_type=volume_type,

                      user_id=context.user_id,

                      project_id=context.project_id,

                      availability_zone=availability_zone,

                      metadata=metadata,

                      imageRef=image_id)

        try:

            item = cinderclient(context).volumes.create(size, **kwargs)

            return _untranslate_volume_summary_view(context, item)

        except cinder_exception.BadRequest as e:

            raise exception.InvalidInput(reason=unicode(e))

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def delete(self, context, volume_id):

        cinderclient(context).volumes.delete(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def update(self, context, volume_id, fields):

        raise NotImplementedError()

    @translate_snapshot_exception

**** CubicPower OpenStack Study ****

    def get_snapshot(self, context, snapshot_id):

        item = cinderclient(context).volume_snapshots.get(snapshot_id)

        return _untranslate_snapshot_summary_view(context, item)

**** CubicPower OpenStack Study ****

    def get_all_snapshots(self, context):

        items = cinderclient(context).volume_snapshots.list(detailed=True)

        rvals = []

        for item in items:

            rvals.append(_untranslate_snapshot_summary_view(context, item))

        return rvals

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def create_snapshot(self, context, volume_id, name, description):

        item = cinderclient(context).volume_snapshots.create(volume_id,

                                                             False,

                                                             name,

                                                             description)

        return _untranslate_snapshot_summary_view(context, item)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def create_snapshot_force(self, context, volume_id, name, description):

        item = cinderclient(context).volume_snapshots.create(volume_id,

                                                             True,

                                                             name,

                                                             description)

        return _untranslate_snapshot_summary_view(context, item)

    @translate_snapshot_exception

**** CubicPower OpenStack Study ****

    def delete_snapshot(self, context, snapshot_id):

        cinderclient(context).volume_snapshots.delete(snapshot_id)

**** CubicPower OpenStack Study ****

    def get_volume_encryption_metadata(self, context, volume_id):

        return cinderclient(context).volumes.get_encryption_metadata(volume_id)

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def get_volume_metadata(self, context, volume_id):

        raise NotImplementedError()

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def delete_volume_metadata(self, context, volume_id, key):

        raise NotImplementedError()

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def update_volume_metadata(self, context, volume_id,

                               metadata, delete=False):

        raise NotImplementedError()

    @translate_volume_exception

**** CubicPower OpenStack Study ****

    def get_volume_metadata_value(self, volume_id, key):

        raise NotImplementedError()

    @translate_snapshot_exception

**** CubicPower OpenStack Study ****

    def update_snapshot_status(self, context, snapshot_id, status):

        vs = cinderclient(context).volume_snapshots

        # '90%' here is used to tell Cinder that Nova is done

        # with its portion of the 'creating' state. This can

        # be removed when we are able to split the Cinder states

        # into 'creating' and a separate state of

        # 'creating_in_nova'. (Same for 'deleting' state.)

        vs.update_snapshot_status(

            snapshot_id,

            {'status': status,

             'progress': '90%'}

        )