deb-cinder/cinder/volume/drivers/ibm/storwize_svc/replication.py

420 lines
17 KiB
Python

# 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.
#
import random
from eventlet import greenthread
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import excutils
import six
from cinder import exception
from cinder.i18n import _, _LE, _LI
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.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
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(_LI('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
class StorwizeSVCReplicationGlobalMirror(
StorwizeSVCReplicationStretchedCluster):
"""Support for Storwize/SVC global mirror mode replication.
Global Mirror establishes a Global Mirror relationship between
two volumes of equal size. The volumes in a Global Mirror relationship
are referred to as the master (source) volume and the auxiliary
(target) volume. This mode is dedicated to the asynchronous volume
replication.
"""
asyncmirror = True
def __init__(self, driver, replication_target=None, target_helpers=None):
super(StorwizeSVCReplicationGlobalMirror, self).__init__(
driver, replication_target)
self.target_helpers = target_helpers
def volume_replication_setup(self, context, vref):
LOG.debug('enter: volume_replication_setup: volume %s', vref['name'])
target_vol_name = storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name']
try:
attr = self.target_helpers.get_vdisk_attributes(target_vol_name)
if not attr:
opts = self.driver._get_vdisk_params(vref['volume_type_id'])
pool = self.target.get('pool_name')
src_attr = self.driver._helpers.get_vdisk_attributes(
vref['name'])
opts['iogrp'] = src_attr['IO_group_id']
self.target_helpers.create_vdisk(target_vol_name,
six.text_type(vref['size']),
'gb', pool, opts)
system_info = self.target_helpers.get_system_info()
self.driver._helpers.create_relationship(
vref['name'], target_vol_name, system_info.get('system_name'),
self.asyncmirror)
except Exception as e:
msg = (_("Unable to set up mirror mode replication for %(vol)s. "
"Exception: %(err)s.") % {'vol': vref['id'],
'err': e.message})
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']})
target_vol = storwize_const.REPLICA_AUX_VOL_PREFIX + vref['name']
try:
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'}
except Exception as e:
LOG.exception(_LE('Unable to fail-over the volume %(id)s to the '
'secondary back-end by switchrcrelationship '
'command, error: %(error)s'),
{"id": vref['id'], "error": e})
# If the switch command fail, try to make the aux volume
# writeable again.
try:
self.target_helpers.stop_relationship(target_vol,
access=True)
return {'replication_status': '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": e})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
LOG.debug('leave: failover_volume_host: vref=%(vref)s',
{'vref': vref['name']})
def replication_failback(self, volume):
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.switch_relationship(rel_info['name'],
aux=False)
return {'replication_status': '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": e})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
class StorwizeSVCReplicationMetroMirror(
StorwizeSVCReplicationGlobalMirror):
"""Support for Storwize/SVC metro mirror mode replication.
Metro Mirror establishes a Metro Mirror relationship between
two volumes of equal size. The volumes in a Metro Mirror relationship
are referred to as the master (source) volume and the auxiliary
(target) volume.
"""
asyncmirror = False
def __init__(self, driver, replication_target=None, target_helpers=None):
super(StorwizeSVCReplicationMetroMirror, self).__init__(
driver, replication_target, target_helpers)
class StorwizeSVCReplicationManager(object):
def __init__(self, driver, replication_target=None, target_helpers=None):
self.sshpool = None
self.driver = driver
self.target = replication_target
self.target_helpers = target_helpers(self._run_ssh)
self._master_helpers = self.driver._master_backend_helpers
self.global_m = StorwizeSVCReplicationGlobalMirror(
self.driver, replication_target, self.target_helpers)
self.metro_m = StorwizeSVCReplicationMetroMirror(
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)
# TODO(vhou): We'll have a common method in ssh_utils to take
# care of this _run_ssh method.
command = ' '. join(cmd_list)
if not self.sshpool:
self.sshpool = ssh_utils.SSHPool(
self.target.get('san_ip'),
self.target.get('san_ssh_port', 22),
self.target.get('ssh_conn_timeout', 30),
self.target.get('san_login'),
password=self.target.get('san_password'),
privatekey=self.target.get('san_private_key', ''),
min_size=self.target.get('ssh_min_pool_conn', 1),
max_size=self.target.get('ssh_max_pool_conn', 5),)
last_exception = None
try:
with self.sshpool.item() as ssh:
while attempts > 0:
attempts -= 1
try:
return processutils.ssh_execute(
ssh, command, check_exit_code=check_exit_code)
except Exception as e:
LOG.error(six.text_type(e))
last_exception = e
greenthread.sleep(random.randint(20, 500) / 100.0)
try:
raise processutils.ProcessExecutionError(
exit_code=last_exception.exit_code,
stdout=last_exception.stdout,
stderr=last_exception.stderr,
cmd=last_exception.cmd)
except AttributeError:
raise processutils.ProcessExecutionError(
exit_code=-1, stdout="",
stderr="Error running SSH command",
cmd=command)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error running SSH command: %s"), command)
def get_target_helpers(self):
return self.target_helpers
def get_replica_obj(self, rep_type):
if rep_type == storwize_const.GLOBAL:
return self.global_m
elif rep_type == storwize_const.METRO:
return self.metro_m
else:
return None
def _partnership_validate_create(self, client, remote_name, remote_ip):
try:
partnership_info = client.get_partnership_info(
remote_name)
if not partnership_info:
candidate_info = client.get_partnershipcandidate_info(
remote_name)
if candidate_info:
client.mkfcpartnership(remote_name)
else:
client.mkippartnership(remote_ip)
if partnership_info['partnership'] != 'fully_configured':
client.chpartnership(partnership_info['id'])
except Exception:
msg = (_('Unable to establish the partnership with '
'the Storwize cluster %s.'), remote_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def establish_target_partnership(self):
local_system_info = self._master_helpers.get_system_info()
target_system_info = self.target_helpers.get_system_info()
local_system_name = local_system_info['system_name']
target_system_name = target_system_info['system_name']
local_ip = self.driver.configuration.safe_get('san_ip')
target_ip = self.target.get('san_ip')
self._partnership_validate_create(self._master_helpers,
target_system_name, target_ip)
self._partnership_validate_create(self.target_helpers,
local_system_name, local_ip)