text: # 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 _
  from cinder import ssh_utils
  from cinder import utils
  from cinder.volume.drivers.ibm.storwize_svc import storwize_const
  
  LOG = logging.getLogger(__name__)
  
  
  class StorwizeSVCReplication(object):
      def __init__(self, driver, replication_target=None):
          self.driver = driver
          self.target = replication_target or {}
  
      def failover_volume_host(self, context, vref):
          pass
  
      def replication_failback(self, volume):
          pass
  
      def volume_replication_setup(self, context, vref):
          pass
  
  
  class StorwizeSVCReplicationGlobalMirror(StorwizeSVCReplication):
      """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})
              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
          except Exception as e:
              LOG.exception('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)
                  try:
                      self.target_helpers.start_relationship(target_vol, 'aux')
                  except exception.VolumeBackendAPIException as e:
                      LOG.error(
                          'Error running startrcrelationship due to %(err)s.',
                          {'err': e})
                  return
              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)
  
      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
              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 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:
              tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + vref.name
              self.target_helpers.stop_relationship(tgt_volume, access=True)
              try:
                  self.target_helpers.start_relationship(tgt_volume, 'aux')
              except exception.VolumeBackendAPIException as e:
                  LOG.error('Error running startrcrelationship due to %(err)s.',
                            {'err': e})
              return
          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
              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):
          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)
          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)
          # 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("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
          elif rep_type == storwize_const.GMCV:
              return self.gmcv
          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)
          except Exception:
              msg = (_('Unable to establish the partnership with '
                       'the Storwize cluster %s.'), remote_name)
              LOG.error(msg)
              raise exception.VolumeDriverException(message=msg)
  
      def _partnership_start(self, client, remote_name):
          try:
              partnership_info = client.get_partnership_info(
                  remote_name)
              if (partnership_info and
                      partnership_info['partnership'] != 'fully_configured'):
                  client.chpartnership(partnership_info['id'])
          except Exception:
              msg = (_('Unable to start 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')
          # Establish partnership only when the local system and the replication
          # target system is different.
          if target_system_name != local_system_name:
              self._partnership_validate_create(self._master_helpers,
                                                target_system_name, target_ip)
              self._partnership_validate_create(self.target_helpers,
                                                local_system_name, local_ip)
              self._partnership_start(self._master_helpers, target_system_name)
              self._partnership_start(self.target_helpers, local_system_name)
  