Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
800 changes: 713 additions & 87 deletions README.md

Large diffs are not rendered by default.

3,206 changes: 2,056 additions & 1,150 deletions emc_vmax_common.py

Large diffs are not rendered by default.

295 changes: 167 additions & 128 deletions emc_vmax_fast.py

Large diffs are not rendered by default.

216 changes: 171 additions & 45 deletions emc_vmax_fc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2014 EMC Corporation.
# Copyright (c) 2015 EMC Corporation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
Expand All @@ -12,10 +12,14 @@
# 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 ast

from oslo_log import log as logging
import six

from cinder import context
from cinder.openstack.common import log as logging
from cinder.i18n import _LW
from cinder.volume import driver
from cinder.volume.drivers.emc import emc_vmax_common
from cinder.zonemanager import utils as fczm_utils
Expand All @@ -32,15 +36,49 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
performance enhancement.
2.0.0 - Add driver requirement functions
2.1.0 - Add consistency group functions
2.1.1 - Fixed issue with mismatched config (bug #1442376)
2.1.2 - Clean up failed clones (bug #1440154)
2.1.3 - Fixed a problem with FAST support (bug #1435069)
2.2.0 - Add manage/unmanage
2.2.1 - Support for SE 8.0.3
2.2.2 - Update Consistency Group
2.2.3 - Pool aware scheduler(multi-pool) support
2.2.4 - Create CG from CG snapshot
2.3.0 - Name change for MV and SG for FAST (bug #1515181)
- Fix for randomly choosing port group. (bug #1501919)
- get_short_host_name needs to be called in find_device_number
(bug #1520635)
- Proper error handling for invalid SLOs (bug #1512795)
- Extend Volume for VMAX3, SE8.1.0.3
https://blueprints.launchpad.net/cinder/+spec/vmax3-extend-volume
- Incorrect SG selected on an attach (#1515176)
- Cleanup Zoning (bug #1501938) NOTE: FC only
- Last volume in SG fix
- _remove_last_vol_and_delete_sg is not being called
for VMAX3 (bug #1520549)
- necessary updates for CG changes (#1534616)
- Changing PercentSynced to CopyState (bug #1517103)
- Getting iscsi ip from port in existing masking view
- Replacement of EMCGetTargetEndpoints api (bug #1512791)
- VMAX3 snapvx improvements (bug #1522821)
2.3.1 - VMAX2/VMAX3 iscsi multipath support (iscsi only)
2.3.2 - VMAX oversubscription Support (blueprint vmax-oversubscription)
2.3.3 - VMAX Driver - Live Migration for VMAX3 (bug #1587967)
2.3.4 - additional locking (bug #1630535)(bug #1660374)
2.3.5 - remove_and_reset_members fix
2.3.6 - copy state fix (bug #1660378)
2.3.7 - superfluous debug messages causing error(#1664630)
2.3.8 - manage/unmanage volumes fix
"""

VERSION = "2.1.0"
VERSION = "2.3.8"

def __init__(self, *args, **kwargs):

super(EMCVMAXFCDriver, self).__init__(*args, **kwargs)
self.common = emc_vmax_common.EMCVMAXCommon(
'FC',
self.VERSION,
configuration=self.configuration)
self.zonemanager_lookup_service = fczm_utils.create_lookup_service()

Expand Down Expand Up @@ -107,7 +145,7 @@ def ensure_export(self, context, volume):
"""Driver entry point to get the export info for an existing volume."""
pass

def create_export(self, context, volume):
def create_export(self, context, volume, connector):
"""Driver entry point to get the export info for a new volume."""
pass

Expand Down Expand Up @@ -141,7 +179,7 @@ def initialize_connection(self, volume, connector):

or

{
{
'driver_volume_type': 'fibre_channel'
'data': {
'target_discovered': True,
Expand Down Expand Up @@ -177,53 +215,93 @@ def terminate_connection(self, volume, connector, **kwargs):
if there isn't an initiator_target_map in the
return of terminate_connection.

:returns: data - the target_wwns and initiator_target_map if the
zone is to be removed, otherwise empty
:param volume: the volume object
:param connector: the connector object
:returns: dict -- the target_wwns and initiator_target_map if the
zone is to be removed, otherwise empty
"""
data = {}
data = {'driver_volume_type': 'fibre_channel',
'data': {}}
loc = volume['provider_location']
name = eval(loc)
name = ast.literal_eval(loc)
storage_system = name['keybindings']['SystemName']
LOG.debug("Start FC detach process for volume: %(volume)s",
LOG.debug("Start FC detach process for volume: %(volume)s.",
{'volume': volume['name']})

target_wwns, init_targ_map = self._build_initiator_target_map(
storage_system, volume, connector)

mvInstanceName = self.common.get_masking_view_by_volume(
volume, connector)
if mvInstanceName is not None:
portGroupInstanceName = (
self.common.get_port_group_from_masking_view(
mvInstanceName))
initiatorGroupInstanceName = (
self.common.get_initiator_group_from_masking_view(
mvInstanceName))

LOG.debug("Found port group: %(portGroup)s "
"in masking view %(maskingView)s",
"in masking view %(maskingView)s.",
{'portGroup': portGroupInstanceName,
'maskingView': mvInstanceName})
# Map must be populated before the terminate_connection
target_wwns, init_targ_map = self._build_initiator_target_map(
storage_system, volume, connector)

self.common.terminate_connection(volume, connector)

LOG.debug("Looking for masking views still associated with"
"Port Group %s", portGroupInstanceName)
mvInstances = self.common.get_masking_views_by_port_group(
portGroupInstanceName)
if len(mvInstances) > 0:
LOG.debug("Found %(numViews)lu MaskingViews.",
{'numViews': len(mvInstances)})
data = {'driver_volume_type': 'fibre_channel',
'data': {}}
else: # no views found
LOG.debug("No MaskingViews were found. Deleting zone.")
LOG.debug("Looking for masking views still associated with "
"Port Group %s.", portGroupInstanceName)
# check if the initiator group has been deleted
checkIgInstanceName = (
self.common.check_ig_instance_name(initiatorGroupInstanceName))

# if it has not been deleted, check for remaining masking views
if checkIgInstanceName is not None:
mvInstances = self._get_common_masking_views(
portGroupInstanceName, initiatorGroupInstanceName)

if len(mvInstances) > 0:
LOG.debug("Found %(numViews)lu MaskingViews.",
{'numViews': len(mvInstances)})
data = {'driver_volume_type': 'fibre_channel',
'data': {}}
else: # no masking views found
LOG.debug("No MaskingViews were found. Deleting zone.")
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}}

LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})

else: # The initiator group has been deleted
LOG.debug("Initiator Group has been deleted. Deleting zone.")
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}}

LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})
LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})

else:
LOG.warning(_LW("Volume %(volume)s is not in any masking view."),
{'volume': volume['name']})
return data

def _get_common_masking_views(
self, portGroupInstanceName, initiatorGroupInstanceName):
"""Check to see the existence of mv in list"""
mvInstances = []
mvInstancesByPG = self.common.get_masking_views_by_port_group(
portGroupInstanceName)

mvInstancesByIG = self.common.get_masking_views_by_initiator_group(
initiatorGroupInstanceName)

for mvInstanceByPG in mvInstancesByPG:
if mvInstanceByPG in mvInstancesByIG:
mvInstances.append(mvInstanceByPG)
return mvInstances

def _build_initiator_target_map(self, storage_system, volume, connector):
"""Build the target_wwns and the initiator target map."""
target_wwns = []
Expand All @@ -241,7 +319,7 @@ def _build_initiator_target_map(self, storage_system, volume, connector):
target_wwns.extend(map_d['target_port_wwn_list'])
for initiator in map_d['initiator_port_wwn_list']:
init_targ_map[initiator] = map_d['target_port_wwn_list']
else: # no lookup service, pre-zoned case
else: # No lookup service, pre-zoned case.
target_wwns = self.common.get_target_wwns(storage_system,
connector)
for initiator in initiator_wwns:
Expand All @@ -256,7 +334,8 @@ def extend_volume(self, volume, new_size):
def get_volume_stats(self, refresh=False):
"""Get volume stats.

If 'refresh' is True, run update the stats first.
:param refresh: boolean -- If True, run update the stats first.
:returns: dict -- the stats dict
"""
if refresh:
self.update_volume_stats()
Expand All @@ -274,44 +353,91 @@ def update_volume_stats(self):
def migrate_volume(self, ctxt, volume, host):
"""Migrate a volume from one Volume Backend to another.

:param self: reference to class
:param ctxt:
:param ctxt: context
:param volume: the volume object including the volume_type_id
:param host: the host dict holding the relevant target(destination)
information
:returns: moved
:returns: list
information
:returns: boolean -- Always returns True
:returns: dict -- Empty dict {}
"""
return self.common.migrate_volume(ctxt, volume, host)

def retype(self, ctxt, volume, new_type, diff, host):
"""Migrate volume to another host using retype.

:param self: reference to class
:param ctxt:
:param ctxt: context
:param volume: the volume object including the volume_type_id
:param new_type: the new volume type.
:param diff: Unused parameter.
:param host: the host dict holding the relevant
target(destination) information
:returns: moved
"returns: list
target(destination) information
:returns: boolean -- True if retype succeeded, False if error
"""
return self.common.retype(ctxt, volume, new_type, diff, host)

def create_consistencygroup(self, context, group):
"""Creates a consistencygroup."""
self.common.create_consistencygroup(context, group)

def delete_consistencygroup(self, context, group):
def delete_consistencygroup(self, context, group, volumes):
"""Deletes a consistency group."""
volumes = self.db.volume_get_all_by_group(context, group['id'])
return self.common.delete_consistencygroup(
context, group, volumes)

def create_cgsnapshot(self, context, cgsnapshot):
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Creates a cgsnapshot."""
return self.common.create_cgsnapshot(context, cgsnapshot, self.db)
return self.common.create_cgsnapshot(context, cgsnapshot, snapshots)

def delete_cgsnapshot(self, context, cgsnapshot):
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
"""Deletes a cgsnapshot."""
return self.common.delete_cgsnapshot(context, cgsnapshot, self.db)
return self.common.delete_cgsnapshot(context, cgsnapshot, snapshots)

def manage_existing(self, volume, external_ref):
"""Manages an existing VMAX Volume (import to Cinder).

Renames the Volume to match the expected name for the volume.
Also need to consider things like QoS, Emulation, account/tenant.
"""
return self.common.manage_existing(volume, external_ref)

def manage_existing_get_size(self, volume, external_ref):
"""Return size of an existing VMAX volume to manage_existing.

:param self: reference to class
:param volume: the volume object including the volume_type_id
:param external_ref: reference to the existing volume
:returns: size of the volume in GB
"""
return self.common.manage_existing_get_size(volume, external_ref)

def unmanage(self, volume):
"""Export VMAX volume from Cinder.

Leave the volume intact on the backend array.
"""
return self.common.unmanage(volume)

def update_consistencygroup(self, context, group,
add_volumes, remove_volumes):
"""Updates LUNs in consistency group."""
return self.common.update_consistencygroup(group, add_volumes,
remove_volumes)

def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
"""Creates the consistency group from source.

Currently the source can only be a cgsnapshot.

:param context: the context
:param group: the consistency group object to be created
:param volumes: volumes in the consistency group
:param cgsnapshot: the source consistency group snapshot
:param snapshots: snapshots of the source volumes
:param source_cg: the dictionary of a consistency group as source.
:param source_vols: a list of volume dictionaries in the source_cg.
"""
return self.common.create_consistencygroup_from_src(
context, group, volumes, cgsnapshot, snapshots, source_cg,
source_vols)
Loading