
According to the OpenStack translation policy available at https://wiki.openstack.org/wiki/LoggingStandards debug messages should not be translated. Like mentioned in several changes in Nova by garyk this is to help prioritize log translation. This patch adds a new hacking check - N319 - that ensures all debug log messages don't have translations. Change-Id: Id9c2715f25c8f2ea52235aba4bd1583655391584 Implements: blueprint debug-translation-removal Closes-Bug: #1318713
316 lines
11 KiB
Python
316 lines
11 KiB
Python
# 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)
|
|
|
|
|
|
class HPMSACommon(object):
|
|
VERSION = "0.1"
|
|
|
|
stats = {}
|
|
|
|
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
|
|
|
|
def get_version(self):
|
|
return self.VERSION
|
|
|
|
def do_setup(self, context):
|
|
self.client_login()
|
|
self._validate_vdisks()
|
|
self.client_logout()
|
|
|
|
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:
|
|
msg = _("Failed to log on MSA Array (invalid login?)")
|
|
LOG.error(msg)
|
|
raise exception.HPMSAConnectionError(reason=msg)
|
|
|
|
def _validate_vdisks(self):
|
|
if not self.client.vdisk_exists(self.vdisk):
|
|
self.client_logout()
|
|
raise exception.HPMSAInvalidVDisk(vdisk=self.vdisk)
|
|
|
|
def client_logout(self):
|
|
self.client.logout()
|
|
LOG.debug("Disconnected from MSA Array")
|
|
|
|
def _get_vol_name(self, volume_id):
|
|
volume_name = self._encode_name(volume_id)
|
|
return "v%s" % volume_name
|
|
|
|
def _get_snap_name(self, snapshot_id):
|
|
snapshot_name = self._encode_name(snapshot_id)
|
|
return "s%s" % snapshot_name
|
|
|
|
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]
|
|
|
|
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)
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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'])
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
def get_volume_stats(self, refresh):
|
|
if refresh:
|
|
self._update_volume_stats()
|
|
|
|
return self.stats
|
|
|
|
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
|
|
|
|
def _assert_connector_ok(self, connector):
|
|
if not connector['wwpns']:
|
|
msg = _("Connector doesn't provide wwpns")
|
|
LOG.error(msg)
|
|
raise exception.InvalidInput(reason=msg)
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
def get_active_fc_target_ports(self):
|
|
return self.client.get_active_fc_target_ports()
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
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)
|