cinder/cinder/volume/drivers/coprhd/common.py
Ravi Edpuganti b248aad12a Added CG capability to volume group in CoprHD
CoprHD has been supporting consistency group starting from kilo release.
This code change makes sure that cg support is added for generic
volume groups for PIKE. Volume group maps to consistency group in
CoprHD when the consistency group snapshot flag is enabled for the
generic volume group.

Change-Id: I3ec946ca594e7c18a84f0b030607f0cc6f5864fb
Closes-Bug: #1682239
2017-07-19 17:49:59 +05:30

1513 lines
59 KiB
Python

# Copyright (c) 2016 EMC Corporation
# 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.
import base64
import binascii
import random
import string
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import units
import six
from cinder import context
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder.volume import configuration
from cinder.volume.drivers.coprhd.helpers import (
authentication as coprhd_auth)
from cinder.volume.drivers.coprhd.helpers import (
commoncoprhdapi as coprhd_utils)
from cinder.volume.drivers.coprhd.helpers import (
consistencygroup as coprhd_cg)
from cinder.volume.drivers.coprhd.helpers import exportgroup as coprhd_eg
from cinder.volume.drivers.coprhd.helpers import host as coprhd_host
from cinder.volume.drivers.coprhd.helpers import snapshot as coprhd_snap
from cinder.volume.drivers.coprhd.helpers import tag as coprhd_tag
from cinder.volume.drivers.coprhd.helpers import (
virtualarray as coprhd_varray)
from cinder.volume.drivers.coprhd.helpers import volume as coprhd_vol
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
MAX_RETRIES = 10
INTERVAL_10_SEC = 10
volume_opts = [
cfg.StrOpt('coprhd_hostname',
default=None,
help='Hostname for the CoprHD Instance'),
cfg.PortOpt('coprhd_port',
default=4443,
help='Port for the CoprHD Instance'),
cfg.StrOpt('coprhd_username',
default=None,
help='Username for accessing the CoprHD Instance'),
cfg.StrOpt('coprhd_password',
default=None,
help='Password for accessing the CoprHD Instance',
secret=True),
cfg.StrOpt('coprhd_tenant',
default=None,
help='Tenant to utilize within the CoprHD Instance'),
cfg.StrOpt('coprhd_project',
default=None,
help='Project to utilize within the CoprHD Instance'),
cfg.StrOpt('coprhd_varray',
default=None,
help='Virtual Array to utilize within the CoprHD Instance'),
cfg.BoolOpt('coprhd_emulate_snapshot',
default=False,
help='True | False to indicate if the storage array '
'in CoprHD is VMAX or VPLEX')
]
CONF = cfg.CONF
CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP)
URI_VPOOL_VARRAY_CAPACITY = '/block/vpools/{0}/varrays/{1}/capacity'
URI_BLOCK_EXPORTS_FOR_INITIATORS = '/block/exports?initiators={0}'
EXPORT_RETRY_COUNT = 5
MAX_DEFAULT_NAME_LENGTH = 128
MAX_SNAPSHOT_NAME_LENGTH = 63
MAX_CONSISTENCY_GROUP_NAME_LENGTH = 64
MAX_SIO_LEN = 31
def retry_wrapper(func):
def try_and_retry(*args, **kwargs):
retry = False
try:
return func(*args, **kwargs)
except coprhd_utils.CoprHdError as e:
# if we got an http error and
# the string contains 401 or if the string contains the word cookie
if (e.err_code == coprhd_utils.CoprHdError.HTTP_ERR and
(e.msg.find('401') != -1 or
e.msg.lower().find('cookie') != -1)):
retry = True
args[0].AUTHENTICATED = False
else:
exception_message = (_("\nCoprHD Exception: %(msg)s\n") %
{'msg': e.msg})
LOG.exception(exception_message)
raise exception.VolumeBackendAPIException(
data=exception_message)
except Exception as exc:
exception_message = (_("\nGeneral Exception: %(exec_info)s\n") %
{'exec_info':
encodeutils.exception_to_unicode(exc)})
LOG.exception(exception_message)
raise exception.VolumeBackendAPIException(
data=exception_message)
if retry:
return func(*args, **kwargs)
return try_and_retry
class EMCCoprHDDriverCommon(object):
OPENSTACK_TAG = 'OpenStack'
def __init__(self, protocol, default_backend_name, configuration=None):
self.AUTHENTICATED = False
self.protocol = protocol
self.configuration = configuration
self.configuration.append_config_values(volume_opts)
self.init_coprhd_api_components()
self.stats = {'driver_version': '3.0.0.0',
'free_capacity_gb': 'unknown',
'reserved_percentage': '0',
'storage_protocol': protocol,
'total_capacity_gb': 'unknown',
'vendor_name': 'CoprHD',
'volume_backend_name':
self.configuration.volume_backend_name or
default_backend_name}
def init_coprhd_api_components(self):
coprhd_utils.AUTH_TOKEN = None
# instantiate coprhd api objects for later use
self.volume_obj = coprhd_vol.Volume(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.exportgroup_obj = coprhd_eg.ExportGroup(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.host_obj = coprhd_host.Host(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.varray_obj = coprhd_varray.VirtualArray(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.snapshot_obj = coprhd_snap.Snapshot(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.consistencygroup_obj = coprhd_cg.ConsistencyGroup(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
self.tag_obj = coprhd_tag.Tag(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
def check_for_setup_error(self):
# validate all of the coprhd_* configuration values
if self.configuration.coprhd_hostname is None:
message = _("coprhd_hostname is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_port is None:
message = _("coprhd_port is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_username is None:
message = _("coprhd_username is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_password is None:
message = _("coprhd_password is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_tenant is None:
message = _("coprhd_tenant is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_project is None:
message = _("coprhd_project is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
if self.configuration.coprhd_varray is None:
message = _("coprhd_varray is not set in cinder configuration")
raise exception.VolumeBackendAPIException(data=message)
def authenticate_user(self):
# we should check to see if we are already authenticated before blindly
# doing it again
if self.AUTHENTICATED is False:
obj = coprhd_auth.Authentication(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
username = self.configuration.coprhd_username
password = self.configuration.coprhd_password
coprhd_utils.AUTH_TOKEN = obj.authenticate_user(username,
password)
self.AUTHENTICATED = True
def create_volume(self, vol, driver, truncate_name=False):
self.authenticate_user()
name = self._get_resource_name(vol, MAX_DEFAULT_NAME_LENGTH,
truncate_name)
size = int(vol.size) * units.Gi
vpool = self._get_vpool(vol)
self.vpool = vpool['CoprHD:VPOOL']
try:
coprhd_cgid = None
try:
if vol.group_id:
if volume_utils.is_group_a_cg_snapshot_type(vol.group):
coprhd_cgid = self._get_coprhd_cgid(vol.group_id)
except KeyError:
coprhd_cgid = None
except AttributeError:
coprhd_cgid = None
full_project_name = ("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project)
)
self.volume_obj.create(full_project_name, name, size,
self.configuration.coprhd_varray,
self.vpool,
# no longer specified in volume creation
sync=True,
# no longer specified in volume creation
consistencygroup=coprhd_cgid)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(name)s: create failed\n%(err)s") %
{'name': name, 'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s creation failed" % name)
self._raise_or_log_exception(
e.err_code, coprhd_err_msg, log_err_msg)
@retry_wrapper
def create_consistencygroup(self, context, group, truncate_name=False):
self.authenticate_user()
name = self._get_resource_name(group,
MAX_CONSISTENCY_GROUP_NAME_LENGTH,
truncate_name)
try:
self.consistencygroup_obj.create(
name,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
cg_uri = self.consistencygroup_obj.consistencygroup_query(
name,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
self.set_tags_for_resource(
coprhd_cg.ConsistencyGroup.URI_CONSISTENCY_GROUP_TAGS,
cg_uri, group)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Consistency Group %(name)s:"
" create failed\n%(err)s") %
{'name': name, 'err': six.text_type(e.msg)})
log_err_msg = ("Consistency Group : %s creation failed" %
name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def update_consistencygroup(self, group, add_volumes,
remove_volumes):
self.authenticate_user()
model_update = {'status': fields.GroupStatus.AVAILABLE}
cg_uri = self._get_coprhd_cgid(group.id)
add_volnames = []
remove_volnames = []
try:
if add_volumes:
for vol in add_volumes:
vol_name = self._get_coprhd_volume_name(vol)
add_volnames.append(vol_name)
if remove_volumes:
for vol in remove_volumes:
vol_name = self._get_coprhd_volume_name(vol)
remove_volnames.append(vol_name)
self.consistencygroup_obj.update(
cg_uri,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant,
add_volnames, remove_volnames, True)
return model_update, None, None
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Consistency Group %(cg_uri)s:"
" update failed\n%(err)s") %
{'cg_uri': cg_uri, 'err': six.text_type(e.msg)})
log_err_msg = ("Consistency Group : %s update failed" %
cg_uri)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def delete_consistencygroup(self, context, group, volumes,
truncate_name=False):
self.authenticate_user()
name = self._get_resource_name(group,
MAX_CONSISTENCY_GROUP_NAME_LENGTH,
truncate_name)
volumes_model_update = []
try:
for vol in volumes:
try:
vol_name = self._get_coprhd_volume_name(vol)
full_project_name = "%s/%s" % (
self.configuration.coprhd_tenant,
self.configuration.coprhd_project)
self.volume_obj.delete(full_project_name, vol_name,
sync=True,
force_delete=True)
update_item = {'id': vol.id,
'status':
fields.GroupStatus.DELETED}
volumes_model_update.append(update_item)
except exception.VolumeBackendAPIException:
update_item = {'id': vol.id,
'status': fields.ConsistencyGroupStatus.
ERROR_DELETING}
volumes_model_update.append(update_item)
LOG.exception("Failed to delete the volume %s of CG.",
vol.name)
self.consistencygroup_obj.delete(
name,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
model_update = {}
model_update['status'] = group.status
return model_update, volumes_model_update
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Consistency Group %(name)s:"
" delete failed\n%(err)s") %
{'name': name, 'err': six.text_type(e.msg)})
log_err_msg = ("Consistency Group : %s deletion failed" %
name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def create_cgsnapshot(self, cgsnapshot, snapshots, truncate_name=False):
self.authenticate_user()
snapshots_model_update = []
cgsnapshot_name = self._get_resource_name(cgsnapshot,
MAX_SNAPSHOT_NAME_LENGTH,
truncate_name)
cg_id = None
cg_group = None
try:
cg_id = cgsnapshot.group_id
cg_group = cgsnapshot.group
except AttributeError:
pass
cg_name = None
coprhd_cgid = None
if cg_id:
coprhd_cgid = self._get_coprhd_cgid(cg_id)
cg_name = self._get_consistencygroup_name(cg_group)
LOG.info('Start to create cgsnapshot for consistency group'
': %(group_name)s',
{'group_name': cg_name})
try:
self.snapshot_obj.snapshot_create(
'block',
'consistency-groups',
coprhd_cgid,
cgsnapshot_name,
False,
True)
for snapshot in snapshots:
vol_id_of_snap = snapshot.volume_id
# Finding the volume in CoprHD for this volume id
tagname = "OpenStack:id:" + vol_id_of_snap
rslt = coprhd_utils.search_by_tag(
coprhd_vol.Volume.URI_SEARCH_VOLUMES_BY_TAG.format(
tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
if not rslt:
continue
vol_uri = rslt[0]
snapshots_of_volume = self.snapshot_obj.snapshot_list_uri(
'block',
'volumes',
vol_uri)
for snapUri in snapshots_of_volume:
snapshot_obj = self.snapshot_obj.snapshot_show_uri(
'block',
vol_uri,
snapUri['id'])
if not coprhd_utils.get_node_value(snapshot_obj,
'inactive'):
# Creating snapshot for a consistency group.
# When we create a consistency group snapshot on
# coprhd then each snapshot of volume in the
# consistencygroup will be given a subscript. Ex if
# the snapshot name is cgsnap1 and lets say there are
# three vols(a,b,c) in CG. Then the names of snapshots
# of the volumes in cg on coprhd end will be like
# cgsnap1-1 cgsnap1-2 cgsnap1-3. So, we list the
# snapshots of the volume under consideration and then
# split the name using - from the ending as prefix
# and postfix. We compare the prefix to the cgsnapshot
# name and filter our the snapshots that correspond to
# the cgsnapshot
if '-' in snapshot_obj['name']:
(prefix, postfix) = snapshot_obj[
'name'].rsplit('-', 1)
if cgsnapshot_name == prefix:
self.set_tags_for_resource(
coprhd_snap.Snapshot.
URI_BLOCK_SNAPSHOTS_TAG,
snapUri['id'],
snapshot)
elif cgsnapshot_name == snapshot_obj['name']:
self.set_tags_for_resource(
coprhd_snap.Snapshot.URI_BLOCK_SNAPSHOTS_TAG,
snapUri['id'],
snapshot)
snapshot['status'] = fields.SnapshotStatus.AVAILABLE
snapshots_model_update.append(
{'id': snapshot.id, 'status':
fields.SnapshotStatus.AVAILABLE})
model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE}
return model_update, snapshots_model_update
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Snapshot for Consistency Group %(cg_name)s:"
" create failed\n%(err)s") %
{'cg_name': cg_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Snapshot %(name)s for Consistency"
" Group: %(cg_name)s creation failed" %
{'cg_name': cg_name,
'name': cgsnapshot_name})
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def delete_cgsnapshot(self, cgsnapshot, snapshots, truncate_name=False):
self.authenticate_user()
cgsnapshot_id = cgsnapshot.id
cgsnapshot_name = self._get_resource_name(cgsnapshot,
MAX_SNAPSHOT_NAME_LENGTH,
truncate_name)
snapshots_model_update = []
cg_id = None
cg_group = None
try:
cg_id = cgsnapshot.group_id
cg_group = cgsnapshot.group
except AttributeError:
pass
coprhd_cgid = self._get_coprhd_cgid(cg_id)
cg_name = self._get_consistencygroup_name(cg_group)
model_update = {}
LOG.info('Delete cgsnapshot %(snap_name)s for consistency group: '
'%(group_name)s', {'snap_name': cgsnapshot.name,
'group_name': cg_name})
try:
uri = None
try:
uri = self.snapshot_obj.snapshot_query('block',
'consistency-groups',
coprhd_cgid,
cgsnapshot_name + '-1')
except coprhd_utils.CoprHdError as e:
if e.err_code == coprhd_utils.CoprHdError.NOT_FOUND_ERR:
uri = self.snapshot_obj.snapshot_query(
'block',
'consistency-groups',
coprhd_cgid,
cgsnapshot_name)
self.snapshot_obj.snapshot_delete_uri(
'block',
coprhd_cgid,
uri,
True,
0)
for snapshot in snapshots:
snapshots_model_update.append(
{'id': snapshot.id,
'status': fields.SnapshotStatus.DELETED})
return model_update, snapshots_model_update
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Snapshot %(cgsnapshot_id)s: for"
" Consistency Group %(cg_name)s: delete"
" failed\n%(err)s") %
{'cgsnapshot_id': cgsnapshot_id,
'cg_name': cg_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Snapshot %(name)s for Consistency"
" Group: %(cg_name)s deletion failed" %
{'cg_name': cg_name,
'name': cgsnapshot_name})
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def set_volume_tags(self, vol, exempt_tags=None, truncate_name=False):
if exempt_tags is None:
exempt_tags = []
self.authenticate_user()
name = self._get_resource_name(vol,
MAX_DEFAULT_NAME_LENGTH,
truncate_name)
full_project_name = ("%s/%s" % (
self.configuration.coprhd_tenant,
self.configuration.coprhd_project))
vol_uri = self.volume_obj.volume_query(full_project_name,
name)
self.set_tags_for_resource(
coprhd_vol.Volume.URI_TAG_VOLUME, vol_uri, vol, exempt_tags)
@retry_wrapper
def set_tags_for_resource(self, uri, resource_id, resource,
exempt_tags=None):
if exempt_tags is None:
exempt_tags = []
self.authenticate_user()
# first, get the current tags that start with the OPENSTACK_TAG
# eyecatcher
formattedUri = uri.format(resource_id)
remove_tags = []
currentTags = self.tag_obj.list_tags(formattedUri)
for cTag in currentTags:
if cTag.startswith(self.OPENSTACK_TAG):
remove_tags.append(cTag)
try:
if remove_tags:
self.tag_obj.tag_resource(uri,
resource_id,
None,
remove_tags)
except coprhd_utils.CoprHdError as e:
if e.err_code == coprhd_utils.CoprHdError.SOS_FAILURE_ERR:
LOG.debug("CoprHdError adding the tag:\n %s", e.msg)
# now add the tags for the resource
add_tags = []
# put all the openstack resource properties into the CoprHD resource
try:
for prop, value in vars(resource).items():
try:
if prop in exempt_tags:
continue
if prop.startswith("_"):
prop = prop.replace("_", '', 1)
# don't put the status in, it's always the status before
# the current transaction
if ((not prop.startswith("status") and not
prop.startswith("obj_status") and
prop != "obj_volume") and value):
tag = ("%s:%s:%s" %
(self.OPENSTACK_TAG, prop,
six.text_type(value)))
if len(tag) > 128:
tag = tag[0:128]
add_tags.append(tag)
except TypeError:
LOG.error(
"Error tagging the resource property %s", prop)
except TypeError:
LOG.error("Error tagging the resource properties")
try:
self.tag_obj.tag_resource(
uri,
resource_id,
add_tags,
None)
except coprhd_utils.CoprHdError as e:
if e.err_code == coprhd_utils.CoprHdError.SOS_FAILURE_ERR:
LOG.debug(
"Adding the tag failed. CoprHdError: %s", e.msg)
return self.tag_obj.list_tags(formattedUri)
@retry_wrapper
def create_cloned_volume(self, vol, src_vref, truncate_name=False):
"""Creates a clone of the specified volume."""
self.authenticate_user()
name = self._get_resource_name(vol,
MAX_DEFAULT_NAME_LENGTH,
truncate_name)
srcname = self._get_coprhd_volume_name(src_vref)
try:
if src_vref.group_id:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
_("Clone can't be taken individually on a volume"
" that is part of a Consistency Group"))
except KeyError as e:
pass
except AttributeError:
pass
try:
(storageres_type,
storageres_typename) = self.volume_obj.get_storageAttributes(
srcname, None, None)
resource_id = self.volume_obj.storage_resource_query(
storageres_type,
srcname,
None,
None,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
self.volume_obj.clone(
name,
resource_id,
sync=True)
full_project_name = "%s/%s" % (
self.configuration.coprhd_tenant,
self.configuration.coprhd_project)
detachable = self.volume_obj.is_volume_detachable(
full_project_name, name)
LOG.debug("Is volume detachable : %s", detachable)
# detach it from the source volume immediately after creation
if detachable:
self.volume_obj.volume_clone_detach(
"", full_project_name, name, True)
except IndexError:
LOG.exception("Volume clone detach returned empty task list")
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(name)s: clone failed\n%(err)s") %
{'name': name, 'err': six.text_type(e.msg)})
log_err_msg = ("Volume : {%s} clone failed" % name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
src_vol_size = 0
dest_vol_size = 0
try:
src_vol_size = src_vref.size
except AttributeError:
src_vol_size = src_vref.volume_size
try:
dest_vol_size = vol.size
except AttributeError:
dest_vol_size = vol.volume_size
if dest_vol_size > src_vol_size:
size_in_bytes = coprhd_utils.to_bytes("%sG" % dest_vol_size)
try:
self.volume_obj.expand(
("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project)), name,
size_in_bytes,
True)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(volume_name)s: expand failed"
"\n%(err)s") %
{'volume_name': name,
'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s expand failed" % name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def expand_volume(self, vol, new_size):
"""expands the volume to new_size specified."""
self.authenticate_user()
volume_name = self._get_coprhd_volume_name(vol)
size_in_bytes = coprhd_utils.to_bytes("%sG" % new_size)
try:
self.volume_obj.expand(
("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project)), volume_name,
size_in_bytes,
True)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(volume_name)s:"
" expand failed\n%(err)s") %
{'volume_name': volume_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s expand failed" %
volume_name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def create_volume_from_snapshot(self, snapshot, volume,
truncate_name=False):
"""Creates volume from given snapshot ( snapshot clone to volume )."""
self.authenticate_user()
if self.configuration.coprhd_emulate_snapshot:
self.create_cloned_volume(volume, snapshot, truncate_name)
return
try:
if snapshot.group_snapshot_id:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
_("Volume cannot be created individually from a snapshot "
"that is part of a Consistency Group"))
except AttributeError:
pass
src_snapshot_name = None
src_vol_ref = snapshot.volume
new_volume_name = self._get_resource_name(volume,
MAX_DEFAULT_NAME_LENGTH,
truncate_name)
try:
coprhd_vol_info = self._get_coprhd_volume_name(
src_vol_ref, True)
src_snapshot_name = self._get_coprhd_snapshot_name(
snapshot, coprhd_vol_info['volume_uri'])
(storageres_type,
storageres_typename) = self.volume_obj.get_storageAttributes(
coprhd_vol_info['volume_name'], None, src_snapshot_name)
resource_id = self.volume_obj.storage_resource_query(
storageres_type,
coprhd_vol_info['volume_name'],
None,
src_snapshot_name,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
self.volume_obj.clone(
new_volume_name,
resource_id,
sync=True)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Snapshot %(src_snapshot_name)s:"
" clone failed\n%(err)s") %
{'src_snapshot_name': src_snapshot_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Snapshot : %s clone failed" %
src_snapshot_name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
if volume.size > snapshot.volume_size:
size_in_bytes = coprhd_utils.to_bytes("%sG" % volume.size)
try:
self.volume_obj.expand(
("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project)),
new_volume_name, size_in_bytes, True)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(volume_name)s: expand failed"
"\n%(err)s") %
{'volume_name': new_volume_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s expand failed" %
new_volume_name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def delete_volume(self, vol):
self.authenticate_user()
name = self._get_coprhd_volume_name(vol)
try:
full_project_name = ("%s/%s" % (
self.configuration.coprhd_tenant,
self.configuration.coprhd_project))
self.volume_obj.delete(full_project_name, name, sync=True)
except coprhd_utils.CoprHdError as e:
if e.err_code == coprhd_utils.CoprHdError.NOT_FOUND_ERR:
LOG.info(
"Volume %s"
" no longer exists; volume deletion is"
" considered successful.", name)
else:
coprhd_err_msg = (_("Volume %(name)s: delete failed"
"\n%(err)s") %
{'name': name, 'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s delete failed" % name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def create_snapshot(self, snapshot, truncate_name=False):
self.authenticate_user()
volume = snapshot.volume
try:
if volume.group_id:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
_("Snapshot can't be taken individually on a volume"
" that is part of a Consistency Group"))
except KeyError:
LOG.info("No Consistency Group associated with the volume")
if self.configuration.coprhd_emulate_snapshot:
self.create_cloned_volume(snapshot, volume, truncate_name)
self.set_volume_tags(
snapshot, ['_volume', '_obj_volume_type'], truncate_name)
return
try:
snapshotname = self._get_resource_name(snapshot,
MAX_SNAPSHOT_NAME_LENGTH,
truncate_name)
vol = snapshot.volume
volumename = self._get_coprhd_volume_name(vol)
projectname = self.configuration.coprhd_project
tenantname = self.configuration.coprhd_tenant
storageres_type = 'block'
storageres_typename = 'volumes'
resource_uri = self.snapshot_obj.storage_resource_query(
storageres_type,
volume_name=volumename,
cg_name=None,
project=projectname,
tenant=tenantname)
inactive = False
sync = True
self.snapshot_obj.snapshot_create(
storageres_type,
storageres_typename,
resource_uri,
snapshotname,
inactive,
sync)
snapshot_uri = self.snapshot_obj.snapshot_query(
storageres_type,
storageres_typename,
resource_uri,
snapshotname)
self.set_tags_for_resource(
coprhd_snap.Snapshot.URI_BLOCK_SNAPSHOTS_TAG,
snapshot_uri, snapshot, ['_volume'])
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Snapshot: %(snapshotname)s, create failed"
"\n%(err)s") % {'snapshotname': snapshotname,
'err': six.text_type(e.msg)})
log_err_msg = ("Snapshot : %s create failed" % snapshotname)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def delete_snapshot(self, snapshot):
self.authenticate_user()
vol = snapshot.volume
try:
if vol.group_id:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
_("Snapshot delete can't be done individually on a volume"
" that is part of a Consistency Group"))
except KeyError:
LOG.info("No Consistency Group associated with the volume")
if self.configuration.coprhd_emulate_snapshot:
self.delete_volume(snapshot)
return
snapshotname = None
try:
volumename = self._get_coprhd_volume_name(vol)
projectname = self.configuration.coprhd_project
tenantname = self.configuration.coprhd_tenant
storageres_type = 'block'
storageres_typename = 'volumes'
resource_uri = self.snapshot_obj.storage_resource_query(
storageres_type,
volume_name=volumename,
cg_name=None,
project=projectname,
tenant=tenantname)
if resource_uri is None:
LOG.info(
"Snapshot %s"
" is not found; snapshot deletion"
" is considered successful.", snapshotname)
else:
snapshotname = self._get_coprhd_snapshot_name(
snapshot, resource_uri)
self.snapshot_obj.snapshot_delete(
storageres_type,
storageres_typename,
resource_uri,
snapshotname,
sync=True)
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Snapshot %s : Delete Failed\n") %
snapshotname)
log_err_msg = ("Snapshot : %s delete failed" % snapshotname)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)
@retry_wrapper
def initialize_connection(self, volume, protocol, initiator_ports,
hostname):
try:
self.authenticate_user()
volumename = self._get_coprhd_volume_name(volume)
foundgroupname = self._find_exportgroup(initiator_ports)
foundhostname = None
if foundgroupname is None:
for i in range(len(initiator_ports)):
# check if this initiator is contained in any CoprHD Host
# object
LOG.debug(
"checking for initiator port: %s", initiator_ports[i])
foundhostname = self._find_host(initiator_ports[i])
if foundhostname:
LOG.info("Found host %s", foundhostname)
break
if not foundhostname:
LOG.error("Auto host creation not supported")
# create an export group for this host
foundgroupname = foundhostname + 'SG'
# create a unique name
foundgroupname = foundgroupname + '-' + ''.join(
random.choice(string.ascii_uppercase +
string.digits)
for x in range(6))
self.exportgroup_obj.exportgroup_create(
foundgroupname,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant,
self.configuration.coprhd_varray,
'Host',
foundhostname)
LOG.debug(
"adding the volume to the exportgroup : %s", volumename)
self.exportgroup_obj.exportgroup_add_volumes(
True,
foundgroupname,
self.configuration.coprhd_tenant,
None,
None,
None,
self.configuration.coprhd_project,
[volumename],
None,
None)
return self._find_device_info(volume, initiator_ports)
except coprhd_utils.CoprHdError as e:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
(_("Attach volume (%(name)s) to host"
" (%(hostname)s) initiator (%(initiatorport)s)"
" failed:\n%(err)s") %
{'name': self._get_coprhd_volume_name(
volume),
'hostname': hostname,
'initiatorport': initiator_ports[0],
'err': six.text_type(e.msg)})
)
@retry_wrapper
def terminate_connection(self, volume, protocol, initiator_ports,
hostname):
try:
self.authenticate_user()
volumename = self._get_coprhd_volume_name(volume)
full_project_name = ("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project))
voldetails = self.volume_obj.show(full_project_name, volumename)
volid = voldetails['id']
# find the exportgroups
exports = self.volume_obj.get_exports_by_uri(volid)
exportgroups = set()
itls = exports['itl']
for itl in itls:
itl_port = itl['initiator']['port']
if itl_port in initiator_ports:
exportgroups.add(itl['export']['id'])
for exportgroup in exportgroups:
self.exportgroup_obj.exportgroup_remove_volumes_by_uri(
exportgroup,
volid,
True,
None,
None,
None,
None)
else:
LOG.info(
"No export group found for the host: %s"
"; this is considered already detached.", hostname)
return itls
except coprhd_utils.CoprHdError as e:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
(_("Detaching volume %(volumename)s from host"
" %(hostname)s failed: %(err)s") %
{'volumename': volumename,
'hostname': hostname,
'err': six.text_type(e.msg)})
)
@retry_wrapper
def _find_device_info(self, volume, initiator_ports):
"""Returns device_info in list of itls having the matched initiator.
(there could be multiple targets, hence a list):
[
{
"hlu":9,
"initiator":{...,"port":"20:00:00:25:B5:49:00:22"},
"export":{...},
"device":{...,"wwn":"600601602B802D00B62236585D0BE311"},
"target":{...,"port":"50:06:01:6A:46:E0:72:EF"},
"san_zone_name":"..."
},
{
"hlu":9,
"initiator":{...,"port":"20:00:00:25:B5:49:00:22"},
"export":{...},
"device":{...,"wwn":"600601602B802D00B62236585D0BE311"},
"target":{...,"port":"50:06:01:62:46:E0:72:EF"},
"san_zone_name":"..."
}
]
"""
volumename = self._get_coprhd_volume_name(volume)
full_project_name = ("%s/%s" % (self.configuration.coprhd_tenant,
self.configuration.coprhd_project))
vol_uri = self.volume_obj.volume_query(full_project_name, volumename)
# The itl info shall be available at the first try since now export is
# a synchronous call. We are trying a few more times to accommodate
# any delay on filling in the itl info after the export task is
# completed.
itls = []
for x in range(MAX_RETRIES):
exports = self.volume_obj.get_exports_by_uri(vol_uri)
LOG.debug("Volume exports: ")
LOG.info(vol_uri)
LOG.debug(exports)
for itl in exports['itl']:
itl_port = itl['initiator']['port']
if itl_port in initiator_ports:
found_device_number = itl['hlu']
if (found_device_number is not None and
found_device_number != '-1'):
# 0 is a valid number for found_device_number.
# Only loop if it is None or -1
LOG.debug("Found Device Number: %s",
found_device_number)
itls.append(itl)
if itls:
break
else:
LOG.debug("Device Number not found yet."
" Retrying after 10 seconds...")
eventlet.sleep(INTERVAL_10_SEC)
if itls is None:
# No device number found after 10 tries; return an empty itl
LOG.info(
"No device number has been found after 10 tries; "
"this likely indicates an unsuccessful attach of "
"volume volumename=%(volumename)s to"
" initiator initiator_ports=%(initiator_ports)s",
{'volumename': volumename,
'initiator_ports': initiator_ports})
return itls
def _get_coprhd_cgid(self, cgid):
tagname = self.OPENSTACK_TAG + ":id:" + cgid
rslt = coprhd_utils.search_by_tag(
coprhd_cg.ConsistencyGroup.URI_SEARCH_CONSISTENCY_GROUPS_BY_TAG.
format(tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
# if the result is empty, then search with the tagname as
# "OpenStack:obj_id" the openstack attribute for id can be obj_id
# instead of id. this depends on the version
if rslt is None or len(rslt) == 0:
tagname = self.OPENSTACK_TAG + ":obj_id:" + cgid
rslt = coprhd_utils.search_by_tag(
coprhd_cg.ConsistencyGroup
.URI_SEARCH_CONSISTENCY_GROUPS_BY_TAG.
format(tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
if len(rslt) > 0:
rslt_cg = self.consistencygroup_obj.show(
rslt[0],
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
return rslt_cg['id']
else:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.NOT_FOUND_ERR,
(_("Consistency Group %s not found") % cgid))
def _get_consistencygroup_name(self, consisgrp):
return consisgrp.name
def _get_coprhd_snapshot_name(self, snapshot, resUri):
tagname = self.OPENSTACK_TAG + ":id:" + snapshot['id']
rslt = coprhd_utils.search_by_tag(
coprhd_snap.Snapshot.URI_SEARCH_SNAPSHOT_BY_TAG.format(tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
# if the result is empty, then search with the tagname
# as "OpenStack:obj_id"
# as snapshots will be having the obj_id instead of just id.
if not rslt:
tagname = self.OPENSTACK_TAG + ":obj_id:" + snapshot['id']
rslt = coprhd_utils.search_by_tag(
coprhd_snap.Snapshot.URI_SEARCH_SNAPSHOT_BY_TAG.format(
tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
if rslt is None or len(rslt) == 0:
return snapshot['name']
else:
rslt_snap = self.snapshot_obj.snapshot_show_uri(
'block',
resUri,
rslt[0])
return rslt_snap['name']
def _get_coprhd_volume_name(self, vol, verbose=False):
tagname = self.OPENSTACK_TAG + ":id:" + vol.id
rslt = coprhd_utils.search_by_tag(
coprhd_vol.Volume.URI_SEARCH_VOLUMES_BY_TAG.format(tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
# if the result is empty, then search with the tagname
# as "OpenStack:obj_id"
# as snapshots will be having the obj_id instead of just id.
if len(rslt) == 0:
tagname = self.OPENSTACK_TAG + ":obj_id:" + vol.id
rslt = coprhd_utils.search_by_tag(
coprhd_vol.Volume.URI_SEARCH_VOLUMES_BY_TAG.format(tagname),
self.configuration.coprhd_hostname,
self.configuration.coprhd_port)
if len(rslt) > 0:
rslt_vol = self.volume_obj.show_by_uri(rslt[0])
if verbose is True:
return {'volume_name': rslt_vol['name'], 'volume_uri': rslt[0]}
else:
return rslt_vol['name']
else:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.NOT_FOUND_ERR,
(_("Volume %s not found") % vol['display_name']))
def _get_resource_name(self, resource,
max_name_cap=MAX_DEFAULT_NAME_LENGTH,
truncate_name=False):
# 36 refers to the length of UUID and +1 for '-'
permitted_name_length = max_name_cap - (36 + 1)
name = resource.display_name
if not name:
name = resource.name
'''
for scaleio, truncate_name will be true. We make sure the
total name is less than or equal to 31 characters.
_id_to_base64 will return a 24 character name'''
if truncate_name:
name = self._id_to_base64(resource.id)
return name
elif len(name) > permitted_name_length:
'''
The maximum length of resource name in CoprHD is 128. Hence we use
only first 91 characters of the resource name'''
return name[0:permitted_name_length] + "-" + resource.id
else:
return name + "-" + resource.id
def _get_vpool(self, volume):
vpool = {}
ctxt = context.get_admin_context()
type_id = volume.volume_type_id
if type_id is not None:
volume_type = volume_types.get_volume_type(ctxt, type_id)
specs = volume_type.get('extra_specs')
for key, value in specs.items():
vpool[key] = value
return vpool
def _id_to_base64(self, id):
# Base64 encode the id to get a volume name less than 32 characters due
# to ScaleIO limitation.
name = six.text_type(id).replace("-", "")
try:
name = base64.b16decode(name.upper())
except (TypeError, binascii.Error):
pass
encoded_name = name
if isinstance(encoded_name, six.text_type):
encoded_name = encoded_name.encode('utf-8')
encoded_name = base64.b64encode(encoded_name)
if six.PY3:
encoded_name = encoded_name.decode('ascii')
LOG.debug("Converted id %(id)s to scaleio name %(name)s.",
{'id': id, 'name': encoded_name})
return encoded_name
def _raise_or_log_exception(self, err_code, coprhd_err_msg, log_err_msg):
if err_code == coprhd_utils.CoprHdError.SOS_FAILURE_ERR:
raise coprhd_utils.CoprHdError(
coprhd_utils.CoprHdError.SOS_FAILURE_ERR,
coprhd_err_msg)
else:
with excutils.save_and_reraise_exception():
LOG.exception(log_err_msg)
@retry_wrapper
def _find_exportgroup(self, initiator_ports):
"""Find export group with initiator ports same as given initiators."""
foundgroupname = None
grouplist = self.exportgroup_obj.exportgroup_list(
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
for groupid in grouplist:
groupdetails = self.exportgroup_obj.exportgroup_show(
groupid,
self.configuration.coprhd_project,
self.configuration.coprhd_tenant)
if groupdetails is not None:
if groupdetails['inactive']:
continue
initiators = groupdetails['initiators']
if initiators is not None:
inits_eg = set()
for initiator in initiators:
inits_eg.add(initiator['initiator_port'])
if inits_eg <= set(initiator_ports):
foundgroupname = groupdetails['name']
if foundgroupname is not None:
# Check the associated varray
if groupdetails['varray']:
varray_uri = groupdetails['varray']['id']
varray_details = self.varray_obj.varray_show(
varray_uri)
if varray_details['name'] == (
self.configuration.coprhd_varray):
LOG.debug(
"Found exportgroup %s",
foundgroupname)
break
# Not the right varray
foundgroupname = None
return foundgroupname
@retry_wrapper
def _find_host(self, initiator_port):
"""Find the host, if exists, to which the given initiator belong."""
foundhostname = None
hosts = self.host_obj.list_all(self.configuration.coprhd_tenant)
for host in hosts:
initiators = self.host_obj.list_initiators(host['id'])
for initiator in initiators:
if initiator_port == initiator['name']:
foundhostname = host['name']
break
if foundhostname is not None:
break
return foundhostname
@retry_wrapper
def get_exports_count_by_initiators(self, initiator_ports):
"""Fetches ITL map for a given list of initiator ports."""
comma_delimited_initiator_list = ",".join(initiator_ports)
(s, h) = coprhd_utils.service_json_request(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port, "GET",
URI_BLOCK_EXPORTS_FOR_INITIATORS.format(
comma_delimited_initiator_list),
None)
export_itl_maps = coprhd_utils.json_decode(s)
if export_itl_maps is None:
return 0
itls = export_itl_maps['itl']
return itls.__len__()
@retry_wrapper
def update_volume_stats(self):
"""Retrieve stats info."""
LOG.debug("Updating volume stats")
self.authenticate_user()
try:
self.stats['consistencygroup_support'] = True
self.stats['consistent_group_snapshot_enabled'] = True
vols = self.volume_obj.list_volumes(
self.configuration.coprhd_tenant +
"/" +
self.configuration.coprhd_project)
vpairs = set()
if len(vols) > 0:
for vol in vols:
if vol:
vpair = (vol["vpool"]["id"], vol["varray"]["id"])
if vpair not in vpairs:
vpairs.add(vpair)
if len(vpairs) > 0:
free_gb = 0.0
used_gb = 0.0
for vpair in vpairs:
if vpair:
(s, h) = coprhd_utils.service_json_request(
self.configuration.coprhd_hostname,
self.configuration.coprhd_port,
"GET",
URI_VPOOL_VARRAY_CAPACITY.format(vpair[0],
vpair[1]),
body=None)
capacity = coprhd_utils.json_decode(s)
free_gb += float(capacity["free_gb"])
used_gb += float(capacity["used_gb"])
self.stats['free_capacity_gb'] = free_gb
self.stats['total_capacity_gb'] = free_gb + used_gb
self.stats['reserved_percentage'] = (
self.configuration.reserved_percentage)
return self.stats
except coprhd_utils.CoprHdError:
with excutils.save_and_reraise_exception():
LOG.exception("Update volume stats failed")
@retry_wrapper
def retype(self, ctxt, volume, new_type, diff, host):
"""changes the vpool type."""
self.authenticate_user()
volume_name = self._get_coprhd_volume_name(volume)
vpool_name = new_type['extra_specs']['CoprHD:VPOOL']
try:
full_project_name = "%s/%s" % (
self.configuration.coprhd_tenant,
self.configuration.coprhd_project)
task = self.volume_obj.update(
full_project_name,
volume_name,
vpool_name)
self.volume_obj.check_for_sync(task['task'][0], True)
return True
except coprhd_utils.CoprHdError as e:
coprhd_err_msg = (_("Volume %(volume_name)s: update failed"
"\n%(err)s") % {'volume_name': volume_name,
'err': six.text_type(e.msg)})
log_err_msg = ("Volume : %s type update failed" %
volume_name)
self._raise_or_log_exception(e.err_code, coprhd_err_msg,
log_err_msg)