¡@

Home 

OpenStack Study: hp_msa_common.py

OpenStack Index

**** CubicPower OpenStack Study ****

# Copyright 2014 Objectif Libre

#

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

#

"""

Volume driver common utilities for HP MSA Storage array

"""

import base64

import uuid

from oslo.config import cfg

from cinder import exception

from cinder.openstack.common import log as logging

from cinder.volume.drivers.san.hp import hp_msa_client as msa

LOG = logging.getLogger(__name__)

hpmsa_opt = [

cfg.StrOpt('msa_vdisk',

default='OpenStack',

help="The VDisk to use for volume creation."),

]

CONF = cfg.CONF

CONF.register_opts(hpmsa_opt)

**** CubicPower OpenStack Study ****

class HPMSACommon(object):

VERSION = "0.1"

stats = {}

**** CubicPower OpenStack Study ****

    def __init__(self, config):

        self.config = config

        self.client = msa.HPMSAClient(self.config.san_ip,

                                      self.config.san_login,

                                      self.config.san_password)

        self.vdisk = self.config.msa_vdisk

**** CubicPower OpenStack Study ****

    def get_version(self):

        return self.VERSION

**** CubicPower OpenStack Study ****

    def do_setup(self, context):

        self.client_login()

        self._validate_vdisks()

        self.client_logout()

**** CubicPower OpenStack Study ****

    def client_login(self):

        LOG.debug(_("Connecting to MSA"))

        try:

            self.client.login()

        except msa.HPMSAConnectionError as ex:

            msg = (_("Failed to connect to MSA Array (%(host)s): %(err)s") %

                   {'host': self.config.san_ip, 'err': ex})

            LOG.error(msg)

            raise exception.HPMSAConnectionError(reason=msg)

        except msa.HPMSAAuthenticationError as e:

            msg = _("Failed to log on MSA Array (invalid login?)")

            LOG.error(msg)

            raise exception.HPMSAConnectionError(reason=msg)

**** CubicPower OpenStack Study ****

    def _validate_vdisks(self):

        if not self.client.vdisk_exists(self.vdisk):

            self.client_logout()

            raise exception.HPMSAInvalidVDisk(vdisk=self.vdisk)

**** CubicPower OpenStack Study ****

    def client_logout(self):

        self.client.logout()

        LOG.debug(_("Disconnected from MSA Array"))

**** CubicPower OpenStack Study ****

    def _get_vol_name(self, volume_id):

        volume_name = self._encode_name(volume_id)

        return "v%s" % volume_name

**** CubicPower OpenStack Study ****

    def _get_snap_name(self, snapshot_id):

        snapshot_name = self._encode_name(snapshot_id)

        return "s%s" % snapshot_name

**** CubicPower OpenStack Study ****

    def _encode_name(self, name):

        """Get converted MSA volume name.

        Converts the openstack volume id from

        ecffc30f-98cb-4cf5-85ee-d7309cc17cd2

        to

        7P_DD5jLTPWF7tcwnMF80g

        We convert the 128 bits of the uuid into a 24character long

        base64 encoded string. This still exceeds the limit of 20 characters

        so we truncate the name later.

        """

        uuid_str = name.replace("-", "")

        vol_uuid = uuid.UUID('urn:uuid:%s' % uuid_str)

        vol_encoded = base64.b64encode(vol_uuid.bytes)

        vol_encoded = vol_encoded.replace('=', '')

        # + is not a valid character for MSA

        vol_encoded = vol_encoded.replace('+', '.')

        # since we use http URLs to send paramters, '/' is not an acceptable

        # parameter

        vol_encoded = vol_encoded.replace('/', '_')

        # NOTE(gpocentek): we limit the size to 20 characters since the array

        # doesn't support more than that for now. Duplicates should happen very

        # rarely.

        # We return 19 chars here because the _get_{vol,snap}_name functions

        # prepend a character

        return vol_encoded[:19]

**** CubicPower OpenStack Study ****

    def check_flags(self, options, required_flags):

        for flag in required_flags:

            if not getattr(options, flag, None):

                msg = _('%s configuration option is not set') % flag

                LOG.error(msg)

                raise exception.InvalidInput(reason=msg)

**** CubicPower OpenStack Study ****

    def create_volume(self, volume):

        volume_id = self._get_vol_name(volume['id'])

        LOG.debug(_("Create Volume (%(display_name)s: %(name)s %(id)s)") %

                  {'display_name': volume['display_name'],

                   'name': volume['name'], 'id': volume_id})

        # use base64 to encode the volume name (UUID is too long for MSA)

        volume_name = self._get_vol_name(volume['id'])

        volume_size = "%dGB" % volume['size']

        try:

            metadata = self.client.create_volume(self.config.msa_vdisk,

                                                 volume_name,

                                                 volume_size)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

        return metadata

**** CubicPower OpenStack Study ****

    def _assert_enough_space_for_copy(self, volume_size):

        """The MSA creates a snap pool before trying to copy the volume.

        The pool is 5.27GB or 20% of the volume size, whichever is larger.

        Verify that we have enough space for the pool and then copy

        """

        pool_size = max(volume_size * 0.2, 5.27)

        required_size = pool_size + volume_size

        if required_size > self.stats['free_capacity_gb']:

            raise exception.HPMSANotEnoughSpace(vdisk=self.vdisk)

**** CubicPower OpenStack Study ****

    def _assert_source_detached(self, volume):

        """The MSA requires a volume to be dettached to clone it.

        Make sure that the volume is not in use when trying to copy it.

        """

        if volume['status'] != "available" or \

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

            msg = _("Volume must be detached to perform a clone operation.")

            LOG.error(msg)

            raise exception.VolumeAttached(volume_id=volume['id'])

**** CubicPower OpenStack Study ****

    def create_cloned_volume(self, volume, src_vref):

        self.get_volume_stats(True)

        self._assert_enough_space_for_copy(volume['size'])

        self._assert_source_detached(src_vref)

        LOG.debug(_("Cloning Volume %(source_id)s (%(dest_id)s)") %

                  {'source_id': volume['source_volid'],

                   'dest_id': volume['id']})

        orig_name = self._get_vol_name(volume['source_volid'])

        dest_name = self._get_vol_name(volume['id'])

        try:

            self.client.copy_volume(orig_name, dest_name,

                                    self.config.msa_vdisk)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

        return None

**** CubicPower OpenStack Study ****

    def create_volume_from_snapshot(self, volume, snapshot):

        self.get_volume_stats(True)

        self._assert_enough_space_for_copy(volume['size'])

        LOG.debug(_("Creating Volume from snapshot %(source_id)s "

                    "(%(dest_id)s)") %

                  {'source_id': snapshot['id'], 'dest_id': volume['id']})

        orig_name = self._get_snap_name(snapshot['id'])

        dest_name = self._get_vol_name(volume['id'])

        try:

            self.client.copy_volume(orig_name, dest_name,

                                    self.config.msa_vdisk)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

        return None

**** CubicPower OpenStack Study ****

    def delete_volume(self, volume):

        LOG.debug(_("Deleting Volume (%s)") % volume['id'])

        volume_name = self._get_vol_name(volume['id'])

        try:

            self.client.delete_volume(volume_name)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            # if the volume wasn't found, ignore the error

            if 'The volume was not found on this system.' in ex:

                return

            raise exception.Invalid(ex)

**** CubicPower OpenStack Study ****

    def get_volume_stats(self, refresh):

        if refresh:

            self._update_volume_stats()

        return self.stats

**** CubicPower OpenStack Study ****

    def _update_volume_stats(self):

        # storage_protocol and volume_backend_name are

        # set in the child classes

        stats = {'driver_version': self.VERSION,

                 'free_capacity_gb': 'unknown',

                 'reserved_percentage': 0,

                 'storage_protocol': None,

                 'total_capacity_gb': 'unknown',

                 'QoS_support': False,

                 'vendor_name': 'Hewlett-Packard',

                 'volume_backend_name': None}

        try:

            vdisk_stats = self.client.vdisk_stats(self.config.msa_vdisk)

            stats.update(vdisk_stats)

        except msa.HPMSARequestError:

            err = (_("Unable to get stats for VDisk (%s)")

                   % self.config.msa_vdisk)

            LOG.error(err)

            raise exception.Invalid(reason=err)

        self.stats = stats

**** CubicPower OpenStack Study ****

    def _assert_connector_ok(self, connector):

        if not connector['wwpns']:

            msg = _("Connector doesn't provide wwpns")

            LOG.error(msg)

            raise exception.InvalidInput(reason=msg)

**** CubicPower OpenStack Study ****

    def map_volume(self, volume, connector):

        self._assert_connector_ok(connector)

        volume_name = self._get_vol_name(volume['id'])

        try:

            data = self.client.map_volume(volume_name, connector['wwpns'])

            return data

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

**** CubicPower OpenStack Study ****

    def unmap_volume(self, volume, connector):

        self._assert_connector_ok(connector)

        volume_name = self._get_vol_name(volume['id'])

        try:

            self.client.unmap_volume(volume_name, connector['wwpns'])

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

**** CubicPower OpenStack Study ****

    def get_active_fc_target_ports(self):

        return self.client.get_active_fc_target_ports()

**** CubicPower OpenStack Study ****

    def create_snapshot(self, snapshot):

        LOG.debug(_("Creating Snapshot from %(volume_id)s (%(snap_id)s)") %

                  {'volume_id': snapshot['volume_id'],

                   'snap_id': snapshot['id']})

        snap_name = self._get_snap_name(snapshot['id'])

        vol_name = self._get_vol_name(snapshot['volume_id'])

        try:

            self.client.create_snapshot(vol_name, snap_name)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)

**** CubicPower OpenStack Study ****

    def delete_snapshot(self, snapshot):

        snap_name = self._get_snap_name(snapshot['id'])

        LOG.debug(_("Deleting Snapshot (%s)") % snapshot['id'])

        try:

            self.client.delete_snapshot(snap_name)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            # if the volume wasn't found, ignore the error

            if 'The volume was not found on this system.' in ex:

                return

            raise exception.Invalid(ex)

**** CubicPower OpenStack Study ****

    def extend_volume(self, volume, new_size):

        volume_name = self._get_vol_name(volume['id'])

        old_size = volume['size']

        growth_size = int(new_size) - old_size

        LOG.debug(_("Extending Volume %(volume_name)s from %(old_size)s to "

                  "%(new_size)s, by %(growth_size)s GB.") %

                  {'volume_name': volume_name, 'old_size': old_size,

                   'new_size': new_size, 'growth_size': growth_size})

        try:

            self.client.extend_volume(volume_name, "%dGB" % growth_size)

        except msa.HPMSARequestError as ex:

            LOG.error(ex)

            raise exception.Invalid(ex)