From 48b8dc6c9658ffa79972b39dc54191b11ca3dc57 Mon Sep 17 00:00:00 2001 From: TaoBai Date: Sun, 17 Aug 2014 22:13:48 +0300 Subject: [PATCH] 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 --- cinder/tests/test_storwize_svc.py | 175 ++++++++++++++++- .../drivers/ibm/storwize_svc/__init__.py | 104 ++++++++-- .../drivers/ibm/storwize_svc/helpers.py | 88 ++++++++- .../drivers/ibm/storwize_svc/replication.py | 183 ++++++++++++++++++ cinder/volume/drivers/ibm/storwize_svc/ssh.py | 5 + etc/cinder/cinder.conf.sample | 5 + 6 files changed, 543 insertions(+), 17 deletions(-) create mode 100644 cinder/volume/drivers/ibm/storwize_svc/replication.py diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py index b0221ca289a..a79b59271db 100644 --- a/cinder/tests/test_storwize_svc.py +++ b/cinder/tests/test_storwize_svc.py @@ -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': ' True'} + type_ref = volume_types.create(self.ctxt, "replication_1", spec) + else: + spec = {'capabilities:replication': ' 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. diff --git a/cinder/volume/drivers/ibm/storwize_svc/__init__.py b/cinder/volume/drivers/ibm/storwize_svc/__init__.py index 463e10824a0..ce649ac1ce6 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/__init__.py +++ b/cinder/volume/drivers/ibm/storwize_svc/__init__.py @@ -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 diff --git a/cinder/volume/drivers/ibm/storwize_svc/helpers.py b/cinder/volume/drivers/ibm/storwize_svc/helpers.py index 3e2007029b4..c71f21fed2e 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/helpers.py +++ b/cinder/volume/drivers/ibm/storwize_svc/helpers.py @@ -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] == ''): + LOG.error(_('Replication must be specified as ' + '\' True\' or \' 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]) diff --git a/cinder/volume/drivers/ibm/storwize_svc/replication.py b/cinder/volume/drivers/ibm/storwize_svc/replication.py new file mode 100644 index 00000000000..6d969421634 --- /dev/null +++ b/cinder/volume/drivers/ibm/storwize_svc/replication.py @@ -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 diff --git a/cinder/volume/drivers/ibm/storwize_svc/ssh.py b/cinder/volume/drivers/ibm/storwize_svc/ssh.py index 8b3876a9630..9f90da43a5e 100644 --- a/cinder/volume/drivers/ibm/storwize_svc/ssh.py +++ b/cinder/volume/drivers/ibm/storwize_svc/ssh.py @@ -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) diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 000bd4ac311..0efc1203951 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -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= + # # Options defined in cinder.volume.drivers.ibm.xiv_ds8k