Volume Replication implementation for IBM Storwize/SVC

Adds replication support to the IBM Storwize/SVC driver.
Supports IBM SVC stretched Cluster mode.

DocImpact

Change-Id: Id82623518c3508ad637f77d08699ffebcb44921e
This commit is contained in:
TaoBai 2014-08-17 22:13:48 +03:00
parent 387afbbd58
commit 48b8dc6c96
6 changed files with 543 additions and 17 deletions

View File

@ -214,6 +214,7 @@ class StorwizeSVCManagementSimulator:
'vdisk',
'warning',
'wwpn',
'primary'
]
no_or_one_param_args = [
'autoexpand',
@ -1341,7 +1342,8 @@ port_speed!N/A
vol = self._volumes_list[vol_name]
kwargs.pop('obj')
params = ['name', 'warning', 'udid', 'autoexpand', 'easytier']
params = ['name', 'warning', 'udid',
'autoexpand', 'easytier', 'primary']
for key, value in kwargs.iteritems():
if key == 'easytier':
vol['easy_tier'] = value
@ -1353,6 +1355,20 @@ port_speed!N/A
vol['name'] = value
del self._volumes_list[vol_name]
self._volumes_list[value] = vol
if key == 'primary':
if value == '0':
self._volumes_list[vol_name]['copies']['0']['primary']\
= 'yes'
self._volumes_list[vol_name]['copies']['1']['primary']\
= 'no'
elif value == '1':
self._volumes_list[vol_name]['copies']['0']['primary']\
= 'no'
self._volumes_list[vol_name]['copies']['1']['primary']\
= 'yes'
else:
err = self._errors['CMMVC6353E'][1] % {'VALUE': key}
return ('', err)
if key in params:
vol[key] = value
else:
@ -1388,6 +1404,31 @@ port_speed!N/A
return self._errors['CMMVC5701E']
return ('', '')
# list vdisk sync process
def _cmd_lsvdisksyncprogress(self, **kwargs):
if 'obj' not in kwargs:
return self._errors['CMMVC5804E']
name = kwargs['obj']
copy_id = kwargs.get('copy', None)
vol = self._volumes_list[name]
rows = []
rows.append(['vdisk_id', 'vdisk_name', 'copy_id', 'progress',
'estimated_completion_time'])
copy_found = False
for copy in vol['copies'].itervalues():
if not copy_id or copy_id == copy['id']:
copy_found = True
row = [vol['id'], name, copy['id']]
if copy['sync'] == 'yes':
row.extend(['100', ''])
else:
row.extend(['50', '140210115226'])
copy['sync'] = 'yes'
rows.append(row)
if not copy_found:
return self._errors['CMMVC5804E']
return self._print_info_cmd(rows=rows, **kwargs)
def _add_host_to_list(self, connector):
host_info = {}
host_info['id'] = self._find_unused_id(self._hosts_list)
@ -1483,6 +1524,7 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.sim = StorwizeSVCManagementSimulator('openstack')
self.driver.set_fake_storage(self.sim)
self.ctxt = context.get_admin_context()
else:
self.driver = storwize_svc.StorwizeSVCDriver(
configuration=conf.Configuration(None))
@ -2754,6 +2796,123 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.assertEqual(term_data, term_ret)
def test_storwize_create_volume_with_strech_cluster_replication(self):
# Set replication flag, set pool openstack2 for secondary volume.
self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2')
# Create a type for repliation.
volume = self._generate_vol_info(None, None)
volume_type = self._create_replication_volume_type(True)
volume['volume_type_id'] = volume_type['id']
self.driver.do_setup(self.ctxt)
model_update = self.driver.create_volume(volume)
self.assertEqual('copying', model_update['replication_status'])
volume['replication_status'] = 'copying'
volume['replication_extended_status'] = None
model_update = self.driver.get_replication_status(self.ctxt, volume)
self.assertEqual('copying', model_update['replication_status'])
# Check the volume copy created on pool opentack2.
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertIn('openstack2', attrs['mdisk_grp_name'])
primary_status = attrs['primary']
self.driver.promote_replica(self.ctxt, volume)
# After promote_replica, primary copy should be swiched.
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertEqual(primary_status[0], attrs['primary'][1])
self.assertEqual(primary_status[1], attrs['primary'][0])
self.driver.delete_volume(volume)
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertIsNone(attrs)
def test_storwize_create_cloned_volume_with_strech_cluster_replica(self):
# Set replication flag, set pool openstack2 for secondary volume.
self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2')
self.driver.do_setup(self.ctxt)
# Create a source volume.
src_volume = self._generate_vol_info(None, None)
self.driver.create_volume(src_volume)
# Create a type for repliation.
volume = self._generate_vol_info(None, None)
volume_type = self._create_replication_volume_type(True)
volume['volume_type_id'] = volume_type['id']
# Create a cloned volume from source volume.
model_update = self.driver.create_cloned_volume(volume, src_volume)
self.assertEqual('copying', model_update['replication_status'])
# Check the replication volume created on pool openstack2.
attrs = self.driver._helpers.get_vdisk_attributes(volume['name'])
self.assertIn('openstack2', attrs['mdisk_grp_name'])
def test_storwize_create_snapshot_volume_with_strech_cluster_replica(self):
# Set replication flag, set pool openstack2 for secondary volume.
self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2')
self.driver.do_setup(self.ctxt)
vol1 = self._create_volume()
snap = self._generate_vol_info(vol1['name'], vol1['id'])
self.driver.create_snapshot(snap)
vol2 = self._generate_vol_info(None, None)
# Create a type for repliation.
vol2 = self._generate_vol_info(None, None)
volume_type = self._create_replication_volume_type(True)
vol2['volume_type_id'] = volume_type['id']
model_update = self.driver.create_volume_from_snapshot(vol2, snap)
self._assert_vol_exists(vol2['name'], True)
self.assertEqual('copying', model_update['replication_status'])
# Check the replication volume created on pool openstack2.
attrs = self.driver._helpers.get_vdisk_attributes(vol2['name'])
self.assertIn('openstack2', attrs['mdisk_grp_name'])
def test_storwize_retype_with_strech_cluster_replication(self):
self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2')
self.driver.do_setup(self.ctxt)
self.driver.do_setup(None)
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack')
cap = {'location_info': loc, 'extent_size': '128'}
self.driver._stats = {'location_info': loc}
host = {'host': 'foo', 'capabilities': cap}
ctxt = context.get_admin_context()
disable_type = self._create_replication_volume_type(False)
enable_type = self._create_replication_volume_type(True)
diff, equal = volume_types.volume_types_diff(ctxt,
disable_type['id'],
enable_type['id'])
volume = self._generate_vol_info(None, None)
volume['host'] = host
volume['volume_type_id'] = disable_type['id']
volume['volume_type'] = disable_type
volume['replication_status'] = None
volume['replication_extended_status'] = None
# Create volume which is not volume replication
self.driver.create_volume(volume)
# volume should be DB object in this parameter
model_update = self.driver.get_replication_status(self.ctxt, volume)
self.assertIs('error', model_update['replication_status'])
# Enable replica
self.driver.retype(ctxt, volume, enable_type, diff, host)
model_update = self.driver.get_replication_status(self.ctxt, volume)
self.assertIs('copying', model_update['replication_status'])
self.driver.delete_volume(volume)
def test_storwize_initiator_target_map_npiv(self):
# Create two volumes to be used in mappings
ctxt = context.get_admin_context()
@ -2812,6 +2971,20 @@ class StorwizeSVCDriverTestCase(test.TestCase):
self.assertEqual(term_data, term_ret)
def _create_replication_volume_type(self, enable):
# Generate a volume type for volume repliation.
if enable:
spec = {'capabilities:replication': '<is> True'}
type_ref = volume_types.create(self.ctxt, "replication_1", spec)
else:
spec = {'capabilities:replication': '<is> False'}
type_ref = volume_types.create(self.ctxt, "replication_2", spec)
replication_type = volume_types.get_volume_type(self.ctxt,
type_ref['id'])
return replication_type
def _get_vdisk_uid(self, vdisk_name):
"""Return vdisk_UID for given vdisk.

View File

@ -48,6 +48,7 @@ from cinder.openstack.common import loopingcall
from cinder.openstack.common import units
from cinder import utils
from cinder.volume.drivers.ibm.storwize_svc import helpers as storwize_helpers
from cinder.volume.drivers.ibm.storwize_svc import replication as storwize_rep
from cinder.volume.drivers.san import san
from cinder.volume import volume_types
from cinder.zonemanager import utils as fczm_utils
@ -110,6 +111,11 @@ storwize_svc_opts = [
cfg.BoolOpt('storwize_svc_allow_tenant_qos',
default=False,
help='Allow tenants to specify QOS on create'),
cfg.StrOpt('storwize_svc_stretched_cluster_partner',
default=None,
help='If operating in stretched cluster mode, specify the '
'name of the pool in which mirrored copies are stored.'
'Example: "pool2"'),
]
CONF = cfg.CONF
@ -132,9 +138,10 @@ class StorwizeSVCDriver(san.SanDriver):
1.2.4 - Fix bug #1278035 (async migration/retype)
1.2.5 - Added support for manage_existing (unmanage is inherited)
1.2.6 - Added QoS support in terms of I/O throttling rate
1.3.1 - Added support for volume replication
"""
VERSION = "1.2.6"
VERSION = "1.3.1"
VDISKCOPYOPS_INTERVAL = 600
def __init__(self, *args, **kwargs):
@ -143,6 +150,7 @@ class StorwizeSVCDriver(san.SanDriver):
self._helpers = storwize_helpers.StorwizeHelpers(self._run_ssh)
self._vdiskcopyops = {}
self._vdiskcopyops_loop = None
self.replication = None
self._state = {'storage_nodes': {},
'enabled_protocols': set(),
'compression_enabled': False,
@ -162,6 +170,9 @@ class StorwizeSVCDriver(san.SanDriver):
# Get storage system name, id, and code level
self._state.update(self._helpers.get_system_info())
# Get the replication helpers
self.replication = storwize_rep.StorwizeSVCReplication.factory(self)
# Validate that the pool exists
pool = self.configuration.storwize_svc_volpool_name
try:
@ -549,11 +560,16 @@ class StorwizeSVCDriver(san.SanDriver):
volume_metadata=
volume.get('volume_metadata'))
pool = self.configuration.storwize_svc_volpool_name
data = self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
if opts['qos']:
self._helpers.add_vdisk_qos(volume['name'], opts['qos'])
return data
model_update = None
if 'replication' in opts and opts['replication']:
ctxt = context.get_admin_context()
model_update = self.replication.create_replica(ctxt, volume)
return model_update
def delete_volume(self, volume):
self._helpers.delete_vdisk(volume['name'], False)
@ -597,6 +613,12 @@ class StorwizeSVCDriver(san.SanDriver):
if opts['qos']:
self._helpers.add_vdisk_qos(volume['name'], opts['qos'])
if 'replication' in opts and opts['replication']:
ctxt = context.get_admin_context()
replica_status = self.replication.create_replica(ctxt, volume)
if replica_status:
return replica_status
def create_cloned_volume(self, tgt_volume, src_volume):
if src_volume['size'] != tgt_volume['size']:
msg = (_('create_cloned_volume: Source and destination '
@ -613,6 +635,12 @@ class StorwizeSVCDriver(san.SanDriver):
if opts['qos']:
self._helpers.add_vdisk_qos(tgt_volume['name'], opts['qos'])
if 'replication' in opts and opts['replication']:
ctxt = context.get_admin_context()
replica_status = self.replication.create_replica(ctxt, tgt_volume)
if replica_status:
return replica_status
def extend_volume(self, volume, new_size):
LOG.debug('enter: extend_volume: volume %s' % volume['id'])
ret = self._helpers.ensure_vdisk_no_fc_mappings(volume['name'],
@ -627,6 +655,11 @@ class StorwizeSVCDriver(san.SanDriver):
self._helpers.extend_vdisk(volume['name'], extend_amt)
LOG.debug('leave: extend_volume: volume %s' % volume['id'])
def add_vdisk_copy(self, volume, dest_pool, vol_type):
return self._helpers.add_vdisk_copy(volume, dest_pool,
vol_type, self._state,
self.configuration)
def _add_vdisk_copy_op(self, ctxt, volume, new_op):
metadata = self.db.volume_admin_metadata_get(ctxt.elevated(),
volume['id'])
@ -703,6 +736,25 @@ class StorwizeSVCDriver(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):
return self.replication.get_replication_status(volume)
def _check_volume_copy_ops(self):
LOG.debug("enter: update volume copy status")
ctxt = context.get_admin_context()
@ -765,9 +817,7 @@ class StorwizeSVCDriver(san.SanDriver):
vol_type = None
self._check_volume_copy_ops()
new_op = self._helpers.add_vdisk_copy(volume['name'], dest_pool,
vol_type, self._state,
self.configuration)
new_op = self.add_vdisk_copy(volume['name'], dest_pool, vol_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s' %
{'id': volume['id'], 'host': host['host']})
@ -807,6 +857,18 @@ class StorwizeSVCDriver(san.SanDriver):
new_opts = self._get_vdisk_params(new_type['id'],
volume_type=new_type)
# Check if retype affects volume replication
model_update = None
old_type_replication = old_opts.get('replication', False)
new_type_replication = new_opts.get('replication', False)
# Delete replica if needed
if old_type_replication and not new_type_replication:
self.replication.delete_replica(volume)
model_update = {'replication_status': 'disabled',
'replication_driver_data': None,
'replication_extended_status': None}
vdisk_changes = []
need_copy = False
for key in all_keys:
@ -827,12 +889,20 @@ class StorwizeSVCDriver(san.SanDriver):
if dest_pool is None:
return False
retype_iogrp_property(volume, new_opts['iogrp'], old_opts['iogrp'])
# If volume is replicated, can't copy
if new_type_replication:
msg = (_('Unable to retype: Volume %s is replicated.'),
volume['id'])
raise exception.VolumeDriverException(message=msg)
retype_iogrp_property(volume,
new_opts['iogrp'],
old_opts['iogrp'])
try:
new = self._helpers.add_vdisk_copy(volume['name'], dest_pool,
new_type, self._state,
self.configuration)
self._add_vdisk_copy_op(ctxt, volume, new)
new_op = self.add_vdisk_copy(volume['name'],
dest_pool,
new_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
except exception.VolumeDriverException:
# roll back changing iogrp property
retype_iogrp_property(volume, old_opts['iogrp'],
@ -850,12 +920,17 @@ class StorwizeSVCDriver(san.SanDriver):
qos = new_opts['qos'] or old_opts['qos']
if qos:
self._helpers.add_vdisk_qos(volume['name'], qos)
# Add replica if needed
if not old_type_replication and new_type_replication:
model_update = self.replication.create_replica(ctxt, volume)
LOG.debug('exit: retype: ild=%(id)s, new_type=%(new_type)s,'
'diff=%(diff)s, host=%(host)s' % {'id': volume['id'],
'new_type': new_type,
'diff': diff,
'host': host['host']})
return True
return True, model_update
def manage_existing(self, volume, ref):
"""Manages an existing vdisk.
@ -958,4 +1033,7 @@ class StorwizeSVCDriver(san.SanDriver):
{'sys_id': self._state['system_id'],
'pool': pool})
if self.replication:
data.update(self.replication.get_replication_info())
self._stats = data

View File

@ -378,6 +378,7 @@ class StorwizeHelpers(object):
elif protocol.lower() == 'iscsi':
protocol = 'iSCSI'
cluster_partner = config.storwize_svc_stretched_cluster_partner
opt = {'rsize': config.storwize_svc_vol_rsize,
'warning': config.storwize_svc_vol_warning,
'autoexpand': config.storwize_svc_vol_autoexpand,
@ -387,7 +388,9 @@ class StorwizeHelpers(object):
'protocol': protocol,
'multipath': config.storwize_svc_multipath_enabled,
'iogrp': config.storwize_svc_vol_iogrp,
'qos': None}
'qos': None,
'stretched_cluster': cluster_partner,
'replication': False}
return opt
@staticmethod
@ -467,6 +470,21 @@ class StorwizeHelpers(object):
del words[0]
value = words[0]
# We generally do not look at capabilities in the driver, but
# replication is a special case where the user asks for
# a volume to be replicated, and we want both the scheduler and
# the driver to act on the value.
if ((not scope or scope == 'capabilities') and
key == 'replication'):
scope = None
key = 'replication'
words = value.split()
if not (words and len(words) == 2 and words[0] == '<is>'):
LOG.error(_('Replication must be specified as '
'\'<is> True\' or \'<is> False\'.'))
del words[0]
value = words[0]
# Add the QoS.
if scope and scope == 'qos':
type_fn = self.svc_qos_keys[key]
@ -528,6 +546,7 @@ class StorwizeHelpers(object):
if volume_type:
qos_specs_id = volume_type.get('qos_specs_id')
specs = dict(volume_type).get('extra_specs')
# NOTE(vhou): We prefer the qos_specs association
# and over-ride any existing
# extra-specs settings if present
@ -543,6 +562,7 @@ class StorwizeHelpers(object):
qos = self._get_qos_from_volume_metadata(volume_metadata)
if len(qos) != 0:
opts['qos'] = qos
self.check_vdisk_opts(state, opts)
return opts
@ -582,6 +602,62 @@ class StorwizeHelpers(object):
attrs = self.get_vdisk_attributes(vdisk_name)
return attrs is not None
def find_vdisk_copy_id(self, vdisk, pool):
resp = self.ssh.lsvdiskcopy(vdisk)
for copy_id, mdisk_grp in resp.select('copy_id', 'mdisk_grp_name'):
if mdisk_grp == pool:
return copy_id
msg = _('Failed to find a vdisk copy in the expected pool.')
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def get_vdisk_copy_attrs(self, vdisk, copy_id):
return self.ssh.lsvdiskcopy(vdisk, copy_id=copy_id)[0]
def get_vdisk_copies(self, vdisk):
copies = {'primary': None,
'secondary': None}
resp = self.ssh.lsvdiskcopy(vdisk)
for copy_id, status, sync, primary, mdisk_grp in \
resp.select('copy_id', 'status', 'sync',
'primary', 'mdisk_grp_name'):
copy = {'copy_id': copy_id,
'status': status,
'sync': sync,
'primary': primary,
'mdisk_grp_name': mdisk_grp,
'sync_progress': None}
if copy['sync'] != 'yes':
progress_info = self.ssh.lsvdisksyncprogress(vdisk, copy_id)
copy['sync_progress'] = progress_info['progress']
if copy['primary'] == 'yes':
copies['primary'] = copy
else:
copies['secondary'] = copy
return copies
def check_copy_ok(self, vdisk, pool, copy_type):
try:
copy_id = self.find_vdisk_copy_id(vdisk, pool)
attrs = self.get_vdisk_copy_attrs(vdisk, copy_id)
except (exception.VolumeBackendAPIException,
exception.VolumeDriverException):
extended = ('No %(type)s copy in pool %(pool)s' %
{'type': copy_type, 'pool': pool})
return ('error', extended)
if attrs['status'] != 'online':
extended = 'The %s copy is offline' % copy_type
return ('error', extended)
if copy_type == 'secondary':
if attrs['sync'] == 'yes':
return ('active', None)
else:
progress_info = self.ssh.lsvdisksyncprogress(vdisk, copy_id)
extended = 'progress: %s%%' % progress_info['progress']
return ('copying', extended)
return (None, None)
def _prepare_fc_map(self, fc_map_id, timeout):
self.ssh.prestartfcmap(fc_map_id)
mapping_ready = False
@ -720,7 +796,8 @@ class StorwizeHelpers(object):
self.ssh.rmvdisk(vdisk, force=force)
LOG.debug('leave: delete_vdisk: vdisk %s' % vdisk)
def create_copy(self, src, tgt, src_id, config, opts, full_copy):
def create_copy(self, src, tgt, src_id, config, opts,
full_copy, pool=None):
"""Create a new snapshot using FlashCopy."""
LOG.debug('enter: create_copy: snapshot %(src)s to %(tgt)s' %
{'tgt': tgt, 'src': src})
@ -733,7 +810,9 @@ class StorwizeHelpers(object):
raise exception.VolumeDriverException(message=msg)
src_size = src_attrs['capacity']
pool = config.storwize_svc_volpool_name
# In case we need to use a specific pool
if not pool:
pool = config.storwize_svc_volpool_name
self.create_vdisk(tgt, src_size, 'b', pool, opts)
timeout = config.storwize_svc_flashcopy_timeout
try:
@ -850,3 +929,6 @@ class StorwizeHelpers(object):
def rename_vdisk(self, vdisk, new_name):
self.ssh.chvdisk(vdisk, ['-name', new_name])
def change_vdisk_primary_copy(self, vdisk, copy_id):
self.ssh.chvdisk(vdisk, ['-primary', copy_id])

View File

@ -0,0 +1,183 @@
# Copyright 2014 IBM Corp.
# 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.
#
from cinder import exception
from cinder.i18n import _
from cinder.openstack.common import log as logging
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."""
def __init__(self, driver):
super(StorwizeSVCReplicationStretchedCluster, self).__init__(driver)
def create_replica(self, ctxt, volume):
conf = self.driver.configuration
vol_type = volume['volume_type_id']
vol_type = volume_types.get_volume_type(ctxt, vol_type)
dest_pool = conf.storwize_svc_stretched_cluster_partner
self.driver.add_vdisk_copy(volume['name'], dest_pool, vol_type)
vol_update = {'replication_status': 'copying'}
return vol_update
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)
status = None
# Check status of primary copy
if not primary:
primary = {'status': 'not found',
'sync': 'no'}
if primary['status'] != 'online':
status = 'error'
else:
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'}
if secondary['status'] != 'online':
status = 'error'
else:
if secondary['sync'] == 'yes':
status = 'active'
secondary['sync_progress'] = '100'
else:
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

View File

@ -313,6 +313,11 @@ class StorwizeSSH(object):
ssh_cmd += [vdisk]
return self.run_ssh_info(ssh_cmd, with_header=with_header)
def lsvdisksyncprogress(self, vdisk, copy_id):
ssh_cmd = ['svcinfo', 'lsvdisksyncprogress', '-delim', '!',
'-copy', copy_id, vdisk]
return self.run_ssh_info(ssh_cmd, with_header=True)[0]
def rmvdiskcopy(self, vdisk, copy_id):
ssh_cmd = ['svctask', 'rmvdiskcopy', '-copy', copy_id, vdisk]
self.run_ssh_assert_no_output(ssh_cmd)

View File

@ -1475,6 +1475,11 @@
# Allow tenants to specify QOS on create (boolean value)
#storwize_svc_allow_tenant_qos=false
# If operating in stretched cluster mode, specify the name of
# the pool in which mirrored copies are stored.Example:
# "pool2" (string value)
#storwize_svc_stretched_cluster_partner=<None>
#
# Options defined in cinder.volume.drivers.ibm.xiv_ds8k