Add gmcv support in SVC driver

SVC Global Mirror with Change Volumes(gmcv) provides asynchronous
replication based on point-in-time copies of data. Two
properties: replication_type="<in> gmcv" and
drivers:cycle_period_seconds=500 in volume type to support replication
volume with gmcv. Here is an example to define a gmcv replication
type: openstack volume type set
--property replication_type="<in> gmcv"
--property replication_enabled='<is> True'
--property drivers:cycle_period_seconds=500
--property volume_backend_name=svc75 SVCGMCVReplicationType

DocImpact
Change-Id: I525f82dccf88bc085825523d7e899143e2190a96
Implements: blueprint gmcv-support-in-svc-driver
This commit is contained in:
zhaochy 2017-04-28 17:07:24 +08:00
parent 3046f75968
commit b03992b616
5 changed files with 1148 additions and 544 deletions

File diff suppressed because it is too large Load Diff

View File

@ -24,194 +24,30 @@ import six
from cinder import exception
from cinder.i18n import _
from cinder.objects import fields
from cinder import ssh_utils
from cinder import utils
from cinder.volume.drivers.ibm.storwize_svc import storwize_const
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
class StorwizeSVCReplication(object):
def __init__(self, driver):
self.driver = driver
@staticmethod
def factory(driver):
"""Use replication methods for the requested mode."""
stretch = driver.configuration.storwize_svc_stretched_cluster_partner
if stretch:
return StorwizeSVCReplicationStretchedCluster(driver)
def create_replica(self, ctxt, volume):
return (None, None)
def is_replicated(self, volume):
return False
def promote_replica(self, volume):
pass
def test_replica(self, tgt_volume, src_volume):
pass
def get_replication_status(self, volume):
return None
def get_replication_info(self):
return {}
def reenable_replication(self, volume):
"""Enable the replication between the primary and secondary volumes.
This is not implemented in the StorwizeSVCReplicationStretchedCluster,
as the Storwize backend is responsible for automatically resuming
mirroring when stopped.
"""
pass
class StorwizeSVCReplicationStretchedCluster(StorwizeSVCReplication):
"""Support for Storwize/SVC stretched cluster mode replication.
This stretched cluster mode implements volume replication in terms of
adding a copy to an existing volume, which changes a nonmirrored volume
into a mirrored volume.
"""
def __init__(self, driver, replication_target=None):
super(StorwizeSVCReplicationStretchedCluster, self).__init__(driver)
self.driver = driver
self.target = replication_target or {}
def create_replica(self, ctxt, volume, vol_type = None):
# if vol_type is None, use the source volume type
if vol_type is None:
vol_type = volume['volume_type_id']
vol_type = volume_types.get_volume_type(ctxt, vol_type)
conf = self.driver.configuration
dest_pool = conf.storwize_svc_stretched_cluster_partner
def failover_volume_host(self, context, vref):
pass
self.driver.add_vdisk_copy(volume['name'], dest_pool, vol_type)
vol_update = {'replication_status': 'copying'}
return vol_update
def replication_failback(self, volume):
pass
def delete_replica(self, volume):
vdisk = volume['name']
copies = self.driver._helpers.get_vdisk_copies(vdisk)
secondary = copies['secondary']
if secondary:
self.driver._helpers.rm_vdisk_copy(volume['name'],
secondary['copy_id'])
else:
LOG.info('Could not find replica to delete of'
' volume %(vol)s.', {'vol': vdisk})
def test_replica(self, tgt_volume, src_volume):
vdisk = src_volume['name']
opts = self.driver._get_vdisk_params(tgt_volume['volume_type_id'])
copies = self.driver._helpers.get_vdisk_copies(vdisk)
if copies['secondary']:
dest_pool = copies['secondary']['mdisk_grp_name']
self.driver._helpers.create_copy(src_volume['name'],
tgt_volume['name'],
src_volume['id'],
self.driver.configuration,
opts,
True,
pool=dest_pool)
else:
msg = (_('Unable to create replica clone for volume %s.'), vdisk)
raise exception.VolumeDriverException(message=msg)
def promote_replica(self, volume):
vdisk = volume['name']
copies = self.driver._helpers.get_vdisk_copies(vdisk)
if copies['secondary']:
copy_id = copies['secondary']['copy_id']
self.driver._helpers.change_vdisk_primary_copy(volume['name'],
copy_id)
else:
msg = (_('Unable to promote replica to primary for volume %s.'
' No secondary copy available.'),
volume['id'])
raise exception.VolumeDriverException(message=msg)
def get_replication_status(self, volume):
# Make sure volume is replicated, otherwise ignore
if volume['replication_status'] == 'disabled':
return None
vdisk = volume['name']
orig = (volume['replication_status'],
volume['replication_extended_status'])
copies = self.driver._helpers.get_vdisk_copies(vdisk)
primary = copies.get('primary', None)
secondary = copies.get('secondary', None)
# Check status of primary copy, set status 'error' as default
status = 'error'
if not primary:
primary = {'status': 'not found',
'sync': 'no'}
else:
if primary['status'] == 'online':
status = 'active'
extended1 = (_('Primary copy status: %(status)s'
' and synchronized: %(sync)s.') %
{'status': primary['status'],
'sync': primary['sync']})
# Check status of secondary copy
if not secondary:
secondary = {'status': 'not found',
'sync': 'no',
'sync_progress': '0'}
status = 'error'
else:
if secondary['status'] != 'online':
status = 'error'
else:
if secondary['sync'] == 'yes':
secondary['sync_progress'] = '100'
# Only change the status if not in error state
if status != 'error':
status = 'active'
else:
# Primary offline, secondary online, data consistent,
# stop copying
status = 'active-stop'
else:
# Primary and secondary both online, the status is copying
if status != 'error':
status = 'copying'
extended2 = (_('Secondary copy status: %(status)s'
' and synchronized: %(sync)s,'
' sync progress is: %(progress)s%%.') %
{'status': secondary['status'],
'sync': secondary['sync'],
'progress': secondary['sync_progress']})
extended = '%s. %s' % (extended1, extended2)
if (status, extended) != orig:
return {'replication_status': status,
'replication_extended_status': extended}
else:
return None
def get_replication_info(self):
data = {}
data['replication'] = True
return data
def volume_replication_setup(self, context, vref):
pass
class StorwizeSVCReplicationGlobalMirror(
StorwizeSVCReplicationStretchedCluster):
class StorwizeSVCReplicationGlobalMirror(StorwizeSVCReplication):
"""Support for Storwize/SVC global mirror mode replication.
Global Mirror establishes a Global Mirror relationship between
@ -265,7 +101,8 @@ class StorwizeSVCReplicationGlobalMirror(
rel_info = self.target_helpers.get_relationship_info(target_vol)
# Reverse the role of the primary and secondary volumes
self.target_helpers.switch_relationship(rel_info['name'])
return {'replication_status': 'failed-over'}
return {'replication_status':
fields.ReplicationStatus.FAILED_OVER}
except Exception as e:
LOG.exception('Unable to fail-over the volume %(id)s to the '
'secondary back-end by switchrcrelationship '
@ -276,7 +113,8 @@ class StorwizeSVCReplicationGlobalMirror(
try:
self.target_helpers.stop_relationship(target_vol,
access=True)
return {'replication_status': 'failed-over'}
return {'replication_status':
fields.ReplicationStatus.FAILED_OVER}
except Exception as e:
msg = (_('Unable to fail-over the volume %(id)s to the '
'secondary back-end, error: %(error)s') %
@ -291,7 +129,8 @@ class StorwizeSVCReplicationGlobalMirror(
try:
self.target_helpers.switch_relationship(rel_info['name'],
aux=False)
return {'replication_status': 'enabled',
return {'replication_status':
fields.ReplicationStatus.ENABLED,
'status': 'available'}
except Exception as e:
msg = (_('Unable to fail-back the volume:%(vol)s to the '
@ -318,6 +157,134 @@ class StorwizeSVCReplicationMetroMirror(
driver, replication_target, target_helpers)
class StorwizeSVCReplicationGMCV(StorwizeSVCReplicationGlobalMirror):
"""Support for Storwize/SVC global mirror with change volumes mode replication.
Global Mirror with Change Volumes(GMCV) provides asynchronous replication
based on point-in-time copies of data. The volumes in a GMCV relationship
are referred to as the master (source) volume, master change volume, the
auxiliary (target) volume and auxiliary change volume.
"""
asyncmirror = True
def __init__(self, driver, replication_target=None, target_helpers=None):
super(StorwizeSVCReplicationGMCV, self).__init__(
driver, replication_target, target_helpers)
def volume_replication_setup(self, context, vref):
LOG.debug('enter: volume_replication_setup: volume %s', vref['name'])
source_change_vol_name = (storwize_const.REPLICA_CHG_VOL_PREFIX +
vref['name'])
target_vol_name = storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name']
target_change_vol_name = (storwize_const.REPLICA_CHG_VOL_PREFIX +
target_vol_name)
try:
src_attr = self.driver._helpers.get_vdisk_attributes(
vref['name'])
# Create source change volume if it doesn't exist
src_change_attr = self.driver._helpers.get_vdisk_attributes(
source_change_vol_name)
if not src_change_attr:
src_change_opts = self.driver._get_vdisk_params(
vref['volume_type_id'])
src_change_opts['iogrp'] = src_attr['IO_group_id']
# Change volumes would usually be thin-provisioned
src_change_opts['autoexpand'] = True
self.driver._helpers.create_vdisk(source_change_vol_name,
six.text_type(vref['size']),
'gb',
src_attr['mdisk_grp_id'],
src_change_opts)
# Create target volume if it doesn't exist
target_attr = self.target_helpers.get_vdisk_attributes(
target_vol_name)
if not target_attr:
target_opts = self.driver._get_vdisk_params(
vref['volume_type_id'])
target_pool = self.target.get('pool_name')
target_opts['iogrp'] = src_attr['IO_group_id']
self.target_helpers.create_vdisk(target_vol_name,
six.text_type(vref['size']),
'gb',
target_pool,
target_opts)
# Create target change volume if it doesn't exist
target_change_attr = self.target_helpers.get_vdisk_attributes(
target_change_vol_name)
if not target_change_attr:
target_change_opts = self.driver._get_vdisk_params(
vref['volume_type_id'])
target_change_pool = self.target.get('pool_name')
target_change_opts['iogrp'] = src_attr['IO_group_id']
# Change Volumes would usually be thin-provisioned
target_change_opts['autoexpand'] = True
self.target_helpers.create_vdisk(target_change_vol_name,
six.text_type(vref['size']),
'gb',
target_change_pool,
target_change_opts)
system_info = self.target_helpers.get_system_info()
# Get cycle_period_seconds
src_change_opts = self.driver._get_vdisk_params(
vref['volume_type_id'])
cycle_period_seconds = src_change_opts.get('cycle_period_seconds')
self.driver._helpers.create_relationship(
vref['name'], target_vol_name, system_info.get('system_name'),
self.asyncmirror, True, source_change_vol_name,
cycle_period_seconds)
# Set target change volume
self.target_helpers.change_relationship_changevolume(
target_vol_name, target_change_vol_name, False)
# Start gmcv relationship
self.driver._helpers.start_relationship(vref['name'])
except Exception as e:
msg = (_("Unable to set up gmcv mode replication for %(vol)s. "
"Exception: %(err)s.") % {'vol': vref['id'],
'err': six.text_type(e)})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
LOG.debug('leave: volume_replication_setup:volume %s', vref['name'])
def failover_volume_host(self, context, vref):
LOG.debug('enter: failover_volume_host: vref=%(vref)s',
{'vref': vref['name']})
# Make the aux volume writeable.
try:
self.target_helpers.stop_relationship(
storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name'],
access=True)
return {'replication_status':
fields.ReplicationStatus.FAILED_OVER}
except Exception as e:
msg = (_('Unable to fail-over the volume %(id)s to the '
'secondary back-end, error: %(error)s') %
{"id": vref['id'], "error": six.text_type(e)})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
def replication_failback(self, volume):
LOG.debug('enter: replication_failback: volume=%(volume)s',
{'volume': volume['name']})
tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name']
rel_info = self.target_helpers.get_relationship_info(tgt_volume)
if rel_info:
try:
self.target_helpers.stop_relationship(tgt_volume, access=True)
self.target_helpers.start_relationship(tgt_volume, 'master')
return {'replication_status':
fields.ReplicationStatus.ENABLED,
'status': 'available'}
except Exception as e:
msg = (_('Unable to fail-back the volume:%(vol)s to the '
'master back-end, error:%(error)s') %
{"vol": volume['name'], "error": six.text_type(e)})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
class StorwizeSVCReplicationManager(object):
def __init__(self, driver, replication_target=None, target_helpers=None):
@ -330,6 +297,8 @@ class StorwizeSVCReplicationManager(object):
self.driver, replication_target, self.target_helpers)
self.metro_m = StorwizeSVCReplicationMetroMirror(
self.driver, replication_target, self.target_helpers)
self.gmcv = StorwizeSVCReplicationGMCV(
self.driver, replication_target, self.target_helpers)
def _run_ssh(self, cmd_list, check_exit_code=True, attempts=1):
utils.check_ssh_injection(cmd_list)
@ -382,6 +351,8 @@ class StorwizeSVCReplicationManager(object):
return self.global_m
elif rep_type == storwize_const.METRO:
return self.metro_m
elif rep_type == storwize_const.GMCV:
return self.gmcv
else:
return None

View File

@ -32,16 +32,20 @@ REP_CAP_DEVS = (DEV_MODEL_SVC, DEV_MODEL_STORWIZE, DEV_MODEL_STORWIZE_V5000,
# constants used for replication
GLOBAL = 'global'
METRO = 'metro'
VALID_REP_TYPES = (GLOBAL, METRO)
GMCV = 'gmcv'
GMCV_MULTI = 'multi'
VALID_REP_TYPES = (GLOBAL, METRO, GMCV)
FAILBACK_VALUE = 'default'
DEFAULT_RC_TIMEOUT = 3600 * 24 * 7
DEFAULT_RC_INTERVAL = 5
REPLICA_AUX_VOL_PREFIX = 'aux_'
REPLICA_CHG_VOL_PREFIX = 'chg_'
# remote mirror copy status
REP_CONSIS_SYNC = 'consistent_synchronized'
REP_CONSIS_COPYING = 'consistent_copying'
REP_CONSIS_STOP = 'consistent_stopped'
REP_SYNC = 'synchronized'
REP_IDL = 'idling'

View File

@ -124,6 +124,15 @@ storwize_svc_opts = [
default=None,
help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'),
cfg.IntOpt('cycle_period_seconds',
default=300,
min=60, max=86400,
help='This defines an optional cycle period that applies to '
'Global Mirror relationships with a cycling mode of multi. '
'A Global Mirror relationship using the multi cycling_mode '
'performs a complete cycle at most once each period. '
'The default is 300 seconds, and the valid seconds '
'are 60-86400.'),
]
CONF = cfg.CONF
@ -301,11 +310,14 @@ class StorwizeSSH(object):
with excutils.save_and_reraise_exception():
LOG.error('Error mapping VDisk-to-host')
def mkrcrelationship(self, master, aux, system, asyncmirror):
def mkrcrelationship(self, master, aux, system, asyncmirror,
cyclingmode=False):
ssh_cmd = ['svctask', 'mkrcrelationship', '-master', master,
'-aux', aux, '-cluster', system]
if asyncmirror:
ssh_cmd.append('-global')
if cyclingmode:
ssh_cmd.extend(['-cyclingmode', 'multi'])
return self.run_ssh_check_created(ssh_cmd)
def rmrcrelationship(self, relationship, force=False):
@ -328,6 +340,30 @@ class StorwizeSSH(object):
ssh_cmd.append(rc_rel)
self.run_ssh_assert_no_output(ssh_cmd)
def ch_rcrelationship_cycleperiod(self, relationship,
cycle_period_seconds):
# Note: Can only change one attribute at a time,
# so define two ch_rcrelationship_xxx here
if cycle_period_seconds:
ssh_cmd = ['svctask', 'chrcrelationship']
ssh_cmd.extend(['-cycleperiodseconds',
six.text_type(cycle_period_seconds)])
ssh_cmd.append(relationship)
self.run_ssh_assert_no_output(ssh_cmd)
def ch_rcrelationship_changevolume(self, relationship,
changevolume, master):
# Note: Can only change one attribute at a time,
# so define two ch_rcrelationship_xxx here
if changevolume:
ssh_cmd = ['svctask', 'chrcrelationship']
if master:
ssh_cmd.extend(['-masterchange', changevolume])
else:
ssh_cmd.extend(['-auxchange', changevolume])
ssh_cmd.append(relationship)
self.run_ssh_assert_no_output(ssh_cmd)
def stoprcrelationship(self, relationship, access=False):
ssh_cmd = ['svctask', 'stoprcrelationship']
if access:
@ -336,10 +372,8 @@ class StorwizeSSH(object):
self.run_ssh_assert_no_output(ssh_cmd)
def lsrcrelationship(self, rc_rel):
key_value = 'name=%s' % rc_rel
ssh_cmd = ['svcinfo', 'lsrcrelationship', '-filtervalue',
key_value, '-delim', '!']
return self.run_ssh_info(ssh_cmd, with_header=True)
ssh_cmd = ['svcinfo', 'lsrcrelationship', '-delim', '!', rc_rel]
return self.run_ssh_info(ssh_cmd)
def lspartnership(self, system_name):
key_value = 'name=%s' % system_name
@ -1061,7 +1095,8 @@ class StorwizeHelpers(object):
'stretched_cluster': cluster_partner,
'replication': False,
'nofmtdisk': config.storwize_svc_vol_nofmtdisk,
'mirror_pool': config.storwize_svc_mirror_pool}
'mirror_pool': config.storwize_svc_mirror_pool,
'cycle_period_seconds': config.cycle_period_seconds}
return opt
@staticmethod
@ -1084,6 +1119,12 @@ class StorwizeHelpers(object):
reason=_('If compression is set to True, rsize must '
'also be set (not equal to -1).'))
# Check cycle_period_seconds are in 60-86400
if opts['cycle_period_seconds'] not in range(60, 86401):
raise exception.InvalidInput(
reason=_('cycle_period_seconds should be integer '
'between 60 and 86400.'))
iogs = StorwizeHelpers._get_valid_requested_io_groups(state, opts)
if len(iogs) == 0:
@ -1152,6 +1193,7 @@ class StorwizeHelpers(object):
# 'drivers' scope.
if scope and scope != 'drivers':
continue
if key in opts:
this_type = type(opts[key]).__name__
if this_type == 'int':
@ -1599,7 +1641,8 @@ class StorwizeHelpers(object):
wait_for_copy = False
for map_id in mapping_ids:
attrs = self._get_flashcopy_mapping_attributes(map_id)
if not attrs:
# We should ignore GMCV flash copies
if not attrs or 'yes' == attrs['rc_controlled']:
continue
source = attrs['source_vdisk_name']
target = attrs['target_vdisk_name']
@ -1677,10 +1720,12 @@ class StorwizeHelpers(object):
if vol_attrs['RC_name']:
self.ssh.stoprcrelationship(vol_attrs['RC_name'], access=access)
def create_relationship(self, master, aux, system, asyncmirror):
def create_relationship(self, master, aux, system, asyncmirror,
cyclingmode=False, masterchange=None,
cycle_period_seconds=None):
try:
rc_id = self.ssh.mkrcrelationship(master, aux, system,
asyncmirror)
asyncmirror, cyclingmode)
except exception.VolumeBackendAPIException as e:
# CMMVC5959E is the code in Stowize storage, meaning that
# there is a relationship that already has this name on the
@ -1690,8 +1735,32 @@ class StorwizeHelpers(object):
# secondary back-end storage, the exception is raised.
raise
if rc_id:
# We need setup master and aux change volumes for gmcv
# before we can start remote relationship
# aux change volume must be set on target site
if cycle_period_seconds:
self.change_relationship_cycleperiod(master,
cycle_period_seconds)
if masterchange:
self.change_relationship_changevolume(master,
masterchange, True)
else:
self.start_relationship(master)
def change_relationship_changevolume(self, volume_name,
change_volume, master):
vol_attrs = self.get_vdisk_attributes(volume_name)
if vol_attrs['RC_name'] and change_volume:
self.ssh.ch_rcrelationship_changevolume(vol_attrs['RC_name'],
change_volume, master)
def change_relationship_cycleperiod(self, volume_name,
cycle_period_seconds):
vol_attrs = self.get_vdisk_attributes(volume_name)
if vol_attrs['RC_name'] and cycle_period_seconds:
self.ssh.ch_rcrelationship_cycleperiod(vol_attrs['RC_name'],
cycle_period_seconds)
def delete_relationship(self, volume_name):
vol_attrs = self.get_vdisk_attributes(volume_name)
if vol_attrs['RC_name']:
@ -1716,6 +1785,9 @@ class StorwizeHelpers(object):
rel_info = self.get_relationship_info(vol_name)
if rel_info:
self.delete_relationship(vol_name)
# Delete change volume
self.delete_vdisk(
storwize_const.REPLICA_CHG_VOL_PREFIX + vol_name, False)
self.delete_vdisk(vol_name, False)
except Exception as e:
msg = (_('Unable to delete the volume for '
@ -2097,7 +2169,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._vdiskcopyops = {}
self._vdiskcopyops_loop = None
self.protocol = None
self.replication = None
self._state = {'storage_nodes': {},
'enabled_protocols': set(),
'compression_enabled': False,
@ -2140,9 +2211,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
# Update the storwize state
self._update_storwize_state()
# Get the replication helpers
self.replication = storwize_rep.StorwizeSVCReplication.factory(self)
# Validate that the pool exists
self._validate_pools_exist()
@ -2416,14 +2484,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
model_update = None
# The replication V2 has a higher priority than the replication V1.
# Check if V2 is available first, then check if V1 is available.
if rep_type:
replica_obj = self._get_replica_obj(rep_type)
replica_obj.volume_replication_setup(ctxt, volume)
model_update = {'replication_status': 'enabled'}
elif opts.get('replication'):
model_update = self.replication.create_replica(ctxt, volume)
model_update = {'replication_status':
fields.ReplicationStatus.ENABLED}
LOG.debug('leave: create_volume:\n volume: %(vol)s\n '
'model_update %(model_update)s',
@ -2473,11 +2538,23 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def create_snapshot(self, snapshot):
ctxt = context.get_admin_context()
try:
# TODO(zhaochy): change to use snapshot.volume
source_vol = self.db.volume_get(ctxt, snapshot['volume_id'])
except Exception:
msg = (_('create_snapshot: get source volume failed.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
rep_type = self._get_volume_replicated_type(
ctxt, None, source_vol['volume_type_id'])
if rep_type == storwize_const.GMCV:
# GMCV volume will have problem to failback
# when it has flash copy relationship besides change volumes
msg = _('create_snapshot: Create snapshot to '
'gmcv replication volume is not allowed.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
pool = utils.extract_host(source_vol['host'], 'pool')
opts = self._get_vdisk_params(source_vol['volume_type_id'])
self._helpers.create_copy(snapshot['volume_name'], snapshot['name'],
@ -2523,17 +2600,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
ctxt = context.get_admin_context()
rep_type = self._get_volume_replicated_type(ctxt, volume)
# The replication V2 has a higher priority than the replication V1.
# Check if V2 is available first, then check if V1 is available.
if rep_type:
self._validate_replication_enabled()
replica_obj = self._get_replica_obj(rep_type)
replica_obj.volume_replication_setup(ctxt, volume)
return {'replication_status': 'enabled'}
elif opts.get('replication'):
replica_status = self.replication.create_replica(ctxt, volume)
if replica_status:
return replica_status
return {'replication_status': fields.ReplicationStatus.ENABLED}
def create_cloned_volume(self, tgt_volume, src_volume):
"""Creates a clone of the specified volume."""
@ -2575,17 +2646,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
ctxt = context.get_admin_context()
rep_type = self._get_volume_replicated_type(ctxt, tgt_volume)
# The replication V2 has a higher priority than the replication V1.
# Check if V2 is available first, then check if V1 is available.
if rep_type:
self._validate_replication_enabled()
replica_obj = self._get_replica_obj(rep_type)
replica_obj.volume_replication_setup(ctxt, tgt_volume)
return {'replication_status': 'enabled'}
elif opts.get('replication'):
replica_status = self.replication.create_replica(ctxt, tgt_volume)
if replica_status:
return replica_status
return {'replication_status': fields.ReplicationStatus.ENABLED}
def extend_volume(self, volume, new_size):
self._extend_volume_op(volume, new_size)
@ -2602,7 +2667,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
raise exception.VolumeDriverException(message=msg)
if old_size is None:
old_size = volume['size']
old_size = volume.size
extend_amt = int(new_size) - old_size
rel_info = self._helpers.get_relationship_info(volume_name)
@ -2610,28 +2675,52 @@ class StorwizeSVCCommonDriver(san.SanDriver,
LOG.warning('_extend_volume_op: Extending a volume with '
'remote copy is not recommended.')
try:
tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX +
volume['name'])
rep_type = rel_info['copy_type']
cyclingmode = rel_info['cycling_mode']
self._master_backend_helpers.delete_relationship(
volume['name'])
self._master_backend_helpers.extend_vdisk(volume['name'],
volume.name)
tgt_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX +
volume.name)
self._master_backend_helpers.extend_vdisk(volume.name,
extend_amt)
self._aux_backend_helpers.extend_vdisk(tgt_vol, extend_amt)
tgt_sys = self._aux_backend_helpers.get_system_info()
if storwize_const.GMCV_MULTI == cyclingmode:
tgt_change_vol = (
storwize_const.REPLICA_CHG_VOL_PREFIX +
tgt_vol)
source_change_vol = (
storwize_const.REPLICA_CHG_VOL_PREFIX +
volume.name)
self._master_backend_helpers.extend_vdisk(
source_change_vol, extend_amt)
self._aux_backend_helpers.extend_vdisk(
tgt_change_vol, extend_amt)
src_change_opts = self._get_vdisk_params(
volume.volume_type_id)
cycle_period_seconds = src_change_opts.get(
'cycle_period_seconds')
self._master_backend_helpers.create_relationship(
volume['name'], tgt_vol, tgt_sys.get('system_name'),
volume.name, tgt_vol, tgt_sys.get('system_name'),
True, True, source_change_vol, cycle_period_seconds)
self._aux_backend_helpers.change_relationship_changevolume(
tgt_vol, tgt_change_vol, False)
self._master_backend_helpers.start_relationship(
volume.name)
else:
self._master_backend_helpers.create_relationship(
volume.name, tgt_vol, tgt_sys.get('system_name'),
True if storwize_const.GLOBAL == rep_type else False)
except Exception as e:
msg = (_('Failed to extend a volume with remote copy '
'%(volume)s. Exception: '
'%(err)s.') % {'volume': volume['id'],
'%(err)s.') % {'volume': volume.id,
'err': e})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
else:
self._helpers.extend_vdisk(volume_name, extend_amt)
LOG.debug('leave: _extend_volume_op: volume %s', volume['id'])
LOG.debug('leave: _extend_volume_op: volume %s', volume.id)
def add_vdisk_copy(self, volume, dest_pool, vol_type, auto_delete=False):
return self._helpers.add_vdisk_copy(volume, dest_pool,
@ -2712,28 +2801,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self.db.volume_admin_metadata_delete(ctxt.elevated(), volume['id'],
'vdiskcopyops')
def promote_replica(self, ctxt, volume):
return self.replication.promote_replica(volume)
def reenable_replication(self, ctxt, volume):
return self.replication.reenable_replication(volume)
def create_replica_test_volume(self, tgt_volume, src_volume):
if src_volume['size'] != tgt_volume['size']:
msg = (_('create_cloned_volume: Source and destination '
'size differ.'))
LOG.error(msg)
raise exception.InvalidInput(message=msg)
replica_status = self.replication.test_replica(tgt_volume,
src_volume)
return replica_status
def get_replication_status(self, ctxt, volume):
replica_status = None
if self.replication:
replica_status = self.replication.get_replication_status(volume)
return replica_status
def _check_volume_copy_ops(self):
LOG.debug("Enter: update volume copy status.")
ctxt = context.get_admin_context()
@ -2795,6 +2862,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def _replication_failback(self, ctxt, volumes):
"""Fail back all the volume on the secondary backend."""
volumes_update = []
if not self._active_backend_id:
LOG.info("Host has been failed back. doesn't need "
@ -2842,7 +2910,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
if not rep_info:
volumes_update.append(
{'volume_id': volume['id'],
'updates': {'replication_status': 'error',
'updates':
{'replication_status':
fields.ReplicationStatus.ERROR,
'status': 'error'}})
LOG.error('_failback_replica_volumes:no rc-releationship '
'is established between master: %(master)s and '
@ -2869,7 +2939,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
{'volume_id': volume.id})
volumes_update.append(
{'volume_id': volume['id'],
'updates': {'replication_status': 'error',
'updates': {'replication_status':
fields.ReplicationStatus.ERROR,
'status': 'error'}})
LOG.debug('leave: _failback_replica_volumes '
'volumes_update=%(volumes_update)s',
@ -2922,7 +2993,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'state': rep_info['state'],
'primary': rep_info['primary']})
try:
if rep_info['state'] != storwize_const.REP_CONSIS_SYNC:
if (rep_info['state'] not in
[storwize_const.REP_CONSIS_SYNC,
storwize_const.REP_CONSIS_COPYING]):
if rep_info['primary'] == 'master':
self._helpers.start_relationship(tgt_volume)
else:
@ -2971,9 +3044,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'aux_vol': rep_info['aux_vdisk_name'],
'state': rep_info['state'],
'primary': rep_info['primary']})
if rep_info['state'] == storwize_const.REP_CONSIS_SYNC:
if (rep_info['state'] in
[storwize_const.REP_CONSIS_SYNC,
storwize_const.REP_CONSIS_COPYING]):
return True
if rep_info['state'] == storwize_const.REP_IDL_DISC:
elif rep_info['state'] == storwize_const.REP_IDL_DISC:
msg = (_('Wait synchronize failed. volume: %(volume)s'),
{'volume': volume})
LOG.error(msg)
@ -3035,7 +3110,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
volumes_update.append(
{'volume_id': volume['id'],
'updates':
{'replication_status': 'error_failing-over',
{'replication_status':
fields.ReplicationStatus.FAILOVER_ERROR,
'status': 'error'}})
LOG.error('_failover_replica_volumes: no rc-'
'releationship is established for master:'
@ -3062,7 +3138,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
volumes_update.append(
{'volume_id': volume['id'],
'updates': {'status': 'error',
'replication_status': 'error_failing-over'}})
'replication_status':
fields.ReplicationStatus.FAILOVER_ERROR}})
LOG.debug('leave: _failover_replica_volumes '
'volumes_update=%(volumes_update)s',
{'volumes_update': volumes_update})
@ -3129,9 +3206,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
def _get_volume_replicated_type(self, ctxt, volume, vol_type_id=None):
replication_type = None
volume_type = (volume.volume_type if volume else
objects.VolumeType.get_by_name_or_id(ctxt,
vol_type_id))
volume_type = None
volume_type_id = volume.volume_type_id if volume else vol_type_id
if volume_type_id:
volume_type = objects.VolumeType.get_by_name_or_id(
ctxt, volume_type_id)
if volume_type:
replication_type = self._get_specs_replicated_type(volume_type)
return replication_type
@ -3271,7 +3350,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'is stored is not valid') % new_opts['mirror_pool'])
raise exception.VolumeDriverException(message=msg)
# There are three options for rep_type: None, metro, global
# There are four options for rep_type: None, metro, global, gmcv
if new_rep_type or old_rep_type:
# If volume is replicated, can't copy
if need_copy or new_opts['mirror_pool'] or old_opts['mirror_pool']:
@ -3297,6 +3376,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'new_rep_type': new_rep_type})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
elif storwize_const.GMCV == new_rep_type:
# To gmcv, we may change cycle_period_seconds if needed
previous_cps = old_opts.get('cycle_period_seconds')
new_cps = new_opts.get('cycle_period_seconds')
if previous_cps != new_cps:
self._helpers.change_relationship_cycleperiod(volume.name,
new_cps)
def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type.
@ -3411,14 +3497,25 @@ class StorwizeSVCCommonDriver(san.SanDriver,
if old_rep_type and not new_rep_type:
self._aux_backend_helpers.delete_rc_volume(volume['name'],
target_vol=True)
model_update = {'replication_status': 'disabled',
if storwize_const.GMCV == old_rep_type:
self._helpers.delete_vdisk(
storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'],
False)
model_update = {'replication_status':
fields.ReplicationStatus.DISABLED,
'replication_driver_data': None,
'replication_extended_status': None}
# Add replica if needed
if not old_rep_type and new_rep_type:
replica_obj = self._get_replica_obj(new_rep_type)
replica_obj.volume_replication_setup(ctxt, volume)
model_update = {'replication_status': 'enabled'}
if storwize_const.GMCV == new_rep_type:
# Set cycle_period_seconds if needed
self._helpers.change_relationship_cycleperiod(
volume['name'],
new_opts.get('cycle_period_seconds'))
model_update = {'replication_status':
fields.ReplicationStatus.ENABLED}
LOG.debug('exit: retype: ild=%(id)s, new_type=%(new_type)s,'
'diff=%(diff)s, host=%(host)s', {'id': volume['id'],
@ -3489,7 +3586,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
rel_info = self._helpers.get_relationship_info(vdisk['name'])
copies = self._helpers.get_vdisk_copies(vdisk['name'])
if rel_info:
vol_rep_type = rel_info['copy_type']
vol_rep_type = (
storwize_const.GMCV if
storwize_const.GMCV_MULTI == rel_info['cycling_mode']
else rel_info['copy_type'])
aux_info = self._aux_backend_helpers.get_system_info()
if rel_info['aux_cluster_id'] != aux_info['system_id']:
msg = (_("Failed to manage existing volume due to the aux "
@ -3505,6 +3606,22 @@ class StorwizeSVCCommonDriver(san.SanDriver,
"the replication type of the volume to be managed is "
"mismatch with the provided replication type."))
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
elif storwize_const.GMCV == rep_type:
if volume['volume_type_id']:
rep_opts = self._get_vdisk_params(
volume['volume_type_id'],
volume_metadata=volume.get('volume_metadata'))
# Check cycle_period_seconds
rep_cps = six.text_type(rep_opts.get('cycle_period_seconds'))
if rel_info['cycle_period_seconds'] != rep_cps:
msg = (_("Failed to manage existing volume due to "
"the cycle_period_seconds %(vol_cps)s of "
"the volume to be managed is mismatch with "
"cycle_period_seconds %(type_cps)s in "
"the provided gmcv replication type.") %
{'vol_cps': rel_info['cycle_period_seconds'],
'type_cps': rep_cps})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
if volume['volume_type_id']:
opts = self._get_vdisk_params(volume['volume_type_id'],
@ -3585,7 +3702,15 @@ class StorwizeSVCCommonDriver(san.SanDriver,
aux_vol = storwize_const.REPLICA_AUX_VOL_PREFIX + volume['name']
self._aux_backend_helpers.rename_vdisk(rel_info['aux_vdisk_name'],
aux_vol)
model_update = {'replication_status': 'enabled'}
if storwize_const.GMCV == vol_rep_type:
self._helpers.rename_vdisk(
rel_info['master_change_vdisk_name'],
storwize_const.REPLICA_CHG_VOL_PREFIX + volume['name'])
self._aux_backend_helpers.rename_vdisk(
rel_info['aux_change_vdisk_name'],
storwize_const.REPLICA_CHG_VOL_PREFIX + aux_vol)
model_update = {'replication_status':
fields.ReplicationStatus.ENABLED}
return model_update
def manage_existing_get_size(self, volume, ref):
@ -3897,8 +4022,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'replication_targets': self._get_replication_targets(),
'replication_count': len(self._get_replication_targets())
})
elif self.replication:
pool_stats.update(self.replication.get_replication_info())
except exception.VolumeBackendAPIException:
msg = _('Failed getting details for pool %s.') % pool

View File

@ -0,0 +1,8 @@
---
features:
- Add global mirror with change volumes(gmcv) support and
user can manage gmcv replication volume by SVC driver.
An example to set a gmcv replication volume type, set
property replication_type as "<in> gmcv", property
replication_enabled as "<is> True" and set
property drivers:cycle_period_seconds as 500.