# Copyright (c) 2016 Red Hat Inc.
# 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.
"""Tests for Volume Code."""

import shutil
import tempfile
from unittest import mock

import ddt
import os_brick
from oslo_config import cfg
from oslo_utils import importutils

from cinder.brick.local_dev import lvm as brick_lvm
from cinder import context
from cinder import db
from cinder import exception
from cinder.image import image_utils
from cinder import objects
from cinder.objects import fields
import cinder.policy
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit.image import fake as fake_image
from cinder.tests.unit import test
from cinder.tests.unit import utils as tests_utils
import cinder.volume
from cinder.volume import configuration as conf
from cinder.volume import driver
from cinder.volume import manager
from cinder.volume import rpcapi as volume_rpcapi
import cinder.volume.targets.tgt
from cinder.volume import volume_utils


CONF = cfg.CONF


def my_safe_get(self, value):
    if value == 'replication_device':
        return ['replication']
    return None


@ddt.ddt
class DriverTestCase(test.TestCase):

    @staticmethod
    def _get_driver(relicated, version):
        class NonReplicatedDriver(driver.VolumeDriver):
            pass

        class V21Driver(driver.VolumeDriver):
            def failover_host(*args, **kwargs):
                pass

        class AADriver(V21Driver):
            def failover_completed(*args, **kwargs):
                pass

        if not relicated:
            return NonReplicatedDriver

        if version == 'v2.1':
            return V21Driver

        return AADriver

    @ddt.data('v2.1', 'a/a', 'newfeature')
    def test_supports_replication_feature_none(self, rep_version):
        my_driver = self._get_driver(False, None)
        self.assertFalse(my_driver.supports_replication_feature(rep_version))

    @ddt.data('v2.1', 'a/a', 'newfeature')
    def test_supports_replication_feature_only_21(self, rep_version):
        version = 'v2.1'
        my_driver = self._get_driver(True, version)
        self.assertEqual(rep_version == version,
                         my_driver.supports_replication_feature(rep_version))

    @ddt.data('v2.1', 'a/a', 'newfeature')
    def test_supports_replication_feature_aa(self, rep_version):
        my_driver = self._get_driver(True, 'a/a')
        self.assertEqual(rep_version in ('v2.1', 'a/a'),
                         my_driver.supports_replication_feature(rep_version))

    def test_init_non_replicated(self):
        config = manager.config.Configuration(manager.volume_manager_opts,
                                              config_group='volume')
        # No exception raised
        self._get_driver(False, None)(configuration=config)

    @ddt.data('v2.1', 'a/a')
    @mock.patch('cinder.volume.configuration.Configuration.safe_get',
                my_safe_get)
    def test_init_replicated_non_clustered(self, version):
        def append_config_values(self, volume_opts):
            pass

        config = manager.config.Configuration(manager.volume_manager_opts,
                                              config_group='volume')
        # No exception raised
        self._get_driver(True, version)(configuration=config)

    @mock.patch('cinder.volume.configuration.Configuration.safe_get',
                my_safe_get)
    def test_init_replicated_clustered_not_supported(self):
        config = manager.config.Configuration(manager.volume_manager_opts,
                                              config_group='volume')
        # Raises exception because we are trying to run a replicated service
        # in clustered mode but the driver doesn't support it.
        self.assertRaises(exception.Invalid, self._get_driver(True, 'v2.1'),
                          configuration=config, cluster_name='mycluster')

    @mock.patch('cinder.volume.configuration.Configuration.safe_get',
                my_safe_get)
    def test_init_replicated_clustered_supported(self):
        config = manager.config.Configuration(manager.volume_manager_opts,
                                              config_group='volume')
        # No exception raised
        self._get_driver(True, 'a/a')(configuration=config,
                                      cluster_name='mycluster')

    def test_failover(self):
        """Test default failover behavior of calling failover_host."""
        my_driver = self._get_driver(True, 'a/a')()
        with mock.patch.object(my_driver, 'failover_host') as failover_mock:
            res = my_driver.failover(mock.sentinel.context,
                                     mock.sentinel.volumes,
                                     secondary_id=mock.sentinel.secondary_id,
                                     groups=[])
        self.assertEqual(failover_mock.return_value, res)
        failover_mock.assert_called_once_with(mock.sentinel.context,
                                              mock.sentinel.volumes,
                                              mock.sentinel.secondary_id,
                                              [])


class BaseDriverTestCase(test.TestCase):
    """Base Test class for Drivers."""
    driver_name = "cinder.volume.driver.FakeBaseDriver"

    def setUp(self):
        super(BaseDriverTestCase, self).setUp()
        vol_tmpdir = tempfile.mkdtemp()
        self.override_config('volume_driver', self.driver_name,
                             conf.SHARED_CONF_GROUP)
        self.override_config('volumes_dir', vol_tmpdir,
                             conf.SHARED_CONF_GROUP)
        self.volume = importutils.import_object(CONF.volume_manager)
        self.mock_object(self.volume, '_driver_shares_targets',
                         return_value=False)
        self.context = context.get_admin_context()
        self.output = ""
        self.configuration = conf.Configuration(None)
        self.mock_object(brick_lvm.LVM, '_vg_exists', lambda x: True)

        def _fake_execute(_command, *_args, **_kwargs):
            """Fake _execute."""
            return self.output, None
        exec_patcher = mock.patch.object(self.volume.driver, '_execute',
                                         _fake_execute)
        exec_patcher.start()
        self.addCleanup(exec_patcher.stop)
        self.volume.driver.set_initialized()
        self.addCleanup(self._cleanup)

    def _cleanup(self):
        try:
            shutil.rmtree(CONF.volumes_dir)
        except OSError:
            pass

    def _attach_volume(self):
        """Attach volumes to an instance."""
        return []


@ddt.ddt
class GenericVolumeDriverTestCase(BaseDriverTestCase):
    """Test case for VolumeDriver."""
    driver_name = "cinder.tests.fake_driver.FakeLoggingVolumeDriver"

    def test_create_temp_cloned_volume(self):
        with mock.patch.object(
                self.volume.driver,
                'create_cloned_volume') as mock_create_cloned_volume:
            model_update = {'provider_location': 'dummy'}
            mock_create_cloned_volume.return_value = model_update
            vol = tests_utils.create_volume(self.context,
                                            status='backing-up')
            cloned_vol = self.volume.driver._create_temp_cloned_volume(
                self.context, vol)
            self.assertEqual('dummy', cloned_vol.provider_location)
            self.assertEqual('available', cloned_vol.status)

            mock_create_cloned_volume.return_value = None
            vol = tests_utils.create_volume(self.context,
                                            status='backing-up')
            cloned_vol = self.volume.driver._create_temp_cloned_volume(
                self.context, vol, status=fields.VolumeStatus.BACKING_UP)
            self.assertEqual(fields.VolumeStatus.BACKING_UP, cloned_vol.status)

    def test_get_backup_device_available(self):
        vol = tests_utils.create_volume(self.context)
        self.context.user_id = fake.USER_ID
        self.context.project_id = fake.PROJECT_ID
        backup_obj = tests_utils.create_backup(self.context,
                                               vol['id'])
        (backup_device, is_snapshot) = self.volume.driver.get_backup_device(
            self.context, backup_obj)
        volume = objects.Volume.get_by_id(self.context, vol.id)
        self.assertNotIn('temporary', backup_device.admin_metadata.keys())
        self.assertEqual(volume, backup_device)
        self.assertFalse(is_snapshot)
        backup_obj.refresh()
        self.assertIsNone(backup_obj.temp_volume_id)

    def test_get_backup_device_in_use(self):
        vol = tests_utils.create_volume(self.context,
                                        status='backing-up',
                                        previous_status='in-use')
        admin_meta = {'temporary': 'True'}
        temp_vol = tests_utils.create_volume(
            self.context, status=fields.VolumeStatus.BACKING_UP,
            admin_metadata=admin_meta)
        self.context.user_id = fake.USER_ID
        self.context.project_id = fake.PROJECT_ID
        backup_obj = tests_utils.create_backup(self.context,
                                               vol['id'])
        with mock.patch.object(
                self.volume.driver,
                '_create_temp_cloned_volume') as mock_create_temp:
            mock_create_temp.return_value = temp_vol
            (backup_device, is_snapshot) = (
                self.volume.driver.get_backup_device(self.context,
                                                     backup_obj))
            self.assertEqual(temp_vol, backup_device)
            self.assertFalse(is_snapshot)
            backup_obj.refresh()
            self.assertEqual(temp_vol.id, backup_obj.temp_volume_id)
            mock_create_temp.assert_called_once_with(
                self.context, vol, status=fields.VolumeStatus.BACKING_UP)

    def test_create_temp_volume_from_snapshot(self):
        volume_dict = {'id': fake.SNAPSHOT_ID,
                       'host': 'fakehost',
                       'cluster_name': 'fakecluster',
                       'availability_zone': 'fakezone',
                       'size': 1,
                       'volume_type_id': fake.VOLUME_TYPE_ID}
        vol = fake_volume.fake_volume_obj(self.context, **volume_dict)
        snapshot = fake_snapshot.fake_snapshot_obj(self.context)

        with mock.patch.object(
                self.volume.driver,
                'create_volume_from_snapshot'):
            temp_vol = self.volume.driver._create_temp_volume_from_snapshot(
                self.context,
                vol, snapshot)
            self.assertEqual(fields.VolumeAttachStatus.DETACHED,
                             temp_vol.attach_status)
            self.assertEqual('fakezone', temp_vol.availability_zone)
            self.assertEqual('fakecluster', temp_vol.cluster_name)
            self.assertFalse(temp_vol.use_quota)

    def test__create_temp_snapshot(self):
        volume_dict = {'id': fake.SNAPSHOT_ID,
                       'host': 'fakehost',
                       'cluster_name': 'fakecluster',
                       'availability_zone': 'fakezone',
                       'size': 1,
                       'volume_type_id': fake.VOLUME_TYPE_ID}
        volume = fake_volume.fake_volume_obj(self.context, **volume_dict)

        # We want to confirm that the driver properly updates fields with the
        # value returned by the create_snapshot method
        driver_updates = {'provider_location': 'driver_provider_location'}

        with mock.patch.object(self.volume.driver, 'create_snapshot',
                               return_value=driver_updates) as create_mock:
            res = self.volume.driver._create_temp_snapshot(self.context,
                                                           volume)
            create_mock.assert_called_once_with(res)

        expected = {'volume_id': volume.id,
                    'progress': '100%',
                    'status': fields.SnapshotStatus.AVAILABLE,
                    'use_quota': False,  # Temporary snapshots don't use quota
                    'project_id': self.context.project_id,
                    'user_id': self.context.user_id,
                    'volume_size': volume.size,
                    'volume_type_id': volume.volume_type_id,
                    'provider_location': 'driver_provider_location'}
        for key, value in expected.items():
            self.assertEqual(value, res[key])

    @mock.patch.object(volume_utils, 'brick_get_connector_properties')
    @mock.patch.object(cinder.volume.manager.VolumeManager, '_attach_volume')
    @mock.patch.object(cinder.volume.manager.VolumeManager, '_detach_volume')
    @mock.patch.object(volume_utils, 'copy_volume')
    @mock.patch.object(volume_rpcapi.VolumeAPI, 'get_capabilities')
    @mock.patch.object(cinder.volume.volume_types,
                       'volume_types_encryption_changed')
    @ddt.data(False, True)
    def test_copy_volume_data_mgr(self,
                                  encryption_changed,
                                  mock_encryption_changed,
                                  mock_get_capabilities,
                                  mock_copy,
                                  mock_detach,
                                  mock_attach,
                                  mock_get_connector):
        """Test function of _copy_volume_data."""

        src_vol = tests_utils.create_volume(self.context, size=1,
                                            host=CONF.host)
        dest_vol = tests_utils.create_volume(self.context, size=1,
                                             host=CONF.host)
        mock_get_connector.return_value = {}
        mock_encryption_changed.return_value = encryption_changed
        self.volume.driver._throttle = mock.MagicMock()

        attach_expected = [
            mock.call(self.context, dest_vol, {},
                      remote=False,
                      attach_encryptor=encryption_changed),
            mock.call(self.context, src_vol, {},
                      remote=False,
                      attach_encryptor=encryption_changed)]

        detach_expected = [
            mock.call(self.context, {'device': {'path': 'bar'}},
                      dest_vol, {}, force=True, remote=False,
                      attach_encryptor=encryption_changed),
            mock.call(self.context, {'device': {'path': 'foo'}},
                      src_vol, {}, force=True, remote=False,
                      attach_encryptor=encryption_changed)]

        attach_volume_returns = [
            {'device': {'path': 'bar'}},
            {'device': {'path': 'foo'}}
        ]

        #  Test case for sparse_copy_volume = False
        mock_attach.side_effect = attach_volume_returns
        mock_get_capabilities.return_value = {}
        self.volume._copy_volume_data(self.context,
                                      src_vol,
                                      dest_vol)

        self.assertEqual(attach_expected, mock_attach.mock_calls)
        mock_copy.assert_called_with('foo', 'bar', 1024, '1M', sparse=False)
        self.assertEqual(detach_expected, mock_detach.mock_calls)

        #  Test case for sparse_copy_volume = True
        mock_attach.reset_mock()
        mock_detach.reset_mock()
        mock_attach.side_effect = attach_volume_returns
        mock_get_capabilities.return_value = {'sparse_copy_volume': True}
        self.volume._copy_volume_data(self.context,
                                      src_vol,
                                      dest_vol)

        self.assertEqual(attach_expected, mock_attach.mock_calls)
        mock_copy.assert_called_with('foo', 'bar', 1024, '1M', sparse=True)
        self.assertEqual(detach_expected, mock_detach.mock_calls)

        # cleanup resource
        db.volume_destroy(self.context, src_vol['id'])
        db.volume_destroy(self.context, dest_vol['id'])

    @ddt.data(False, True)
    @mock.patch(driver_name + '.initialize_connection')
    @mock.patch(driver_name + '.create_export', return_value=None)
    @mock.patch(driver_name + '._connect_device')
    def test_attach_volume_enforce_mpath(self, enforce_mpath, connect_mock,
                                         export_mock, initialize_mock):
        properties = {'host': 'myhost', 'ip': '192.168.1.43',
                      'initiator': u'iqn.1994-05.com.redhat:d9be887375',
                      'multipath': False, 'os_type': 'linux2',
                      'platform': 'x86_64',
                      'enforce_multipath': enforce_mpath}

        data = {'target_discovered': True,
                'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
                'target_portal': '127.0.0.0.1:3260',
                'volume_id': 1,
                'discard': False,
                'encrypted': False,
                'enforce_multipath': enforce_mpath}

        passed_conn = {'driver_volume_type': 'iscsi', 'data': data.copy()}
        initialize_mock.return_value = passed_conn

        expected_conn = {'driver_volume_type': 'iscsi', 'data': data.copy()}

        volume = tests_utils.create_volume(
            self.context, status='available',
            size=2)

        attach_info, vol = self.volume.driver._attach_volume(self.context,
                                                             volume,
                                                             properties)

        export_mock.assert_called_once_with(self.context, volume, properties)
        initialize_mock.assert_called_once_with(volume, properties)

        connect_mock.assert_called_once_with(expected_conn)

        self.assertEqual(connect_mock.return_value, attach_info)
        self.assertEqual(volume, vol)

    @mock.patch(driver_name + '.initialize_connection')
    @mock.patch(driver_name + '.create_export', return_value=None)
    @mock.patch(driver_name + '._connect_device')
    def test_attach_volume_encrypted(self, connect_mock, export_mock,
                                     initialize_mock):
        properties = {'host': 'myhost', 'ip': '192.168.1.43',
                      'initiator': u'iqn.1994-05.com.redhat:d9be887375',
                      'multipath': False, 'os_type': 'linux2',
                      'platform': 'x86_64'}

        data = {'target_discovered': True,
                'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
                'target_portal': '127.0.0.0.1:3260',
                'volume_id': 1,
                'discard': False,
                'enforce_multipath': False}

        passed_conn = {'driver_volume_type': 'iscsi', 'data': data.copy()}
        initialize_mock.return_value = passed_conn

        # _attach_volume adds the encrypted value based on the volume
        expected_conn = {'driver_volume_type': 'iscsi', 'data': data.copy()}
        expected_conn['data']['encrypted'] = True

        volume = tests_utils.create_volume(
            self.context, status='available',
            size=2,
            encryption_key_id=fake.ENCRYPTION_KEY_ID)

        attach_info, vol = self.volume.driver._attach_volume(self.context,
                                                             volume,
                                                             properties)

        export_mock.assert_called_once_with(self.context, volume, properties)
        initialize_mock.assert_called_once_with(volume, properties)

        connect_mock.assert_called_once_with(expected_conn)

        self.assertEqual(connect_mock.return_value, attach_info)
        self.assertEqual(volume, vol)

    @mock.patch.object(os_brick.initiator.connector,
                       'get_connector_properties')
    @mock.patch.object(image_utils, 'fetch_to_raw')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_attach_volume')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_detach_volume')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_attach_volume_encryptor')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_detach_volume_encryptor')
    def test_copy_image_to_encrypted_volume(self,
                                            mock_detach_encryptor,
                                            mock_attach_encryptor,
                                            mock_detach_volume,
                                            mock_attach_volume,
                                            mock_fetch_to_raw,
                                            mock_get_connector_properties):
        properties = {}
        volume = tests_utils.create_volume(
            self.context, status='available',
            size=2,
            encryption_key_id=fake.ENCRYPTION_KEY_ID)
        volume_id = volume['id']
        volume = db.volume_get(context.get_admin_context(), volume_id)
        image_service = fake_image.FakeImageService()
        local_path = 'dev/sda'
        attach_info = {'device': {'path': local_path},
                       'conn': {'driver_volume_type': 'iscsi',
                                'data': {}, }}

        mock_get_connector_properties.return_value = properties
        mock_attach_volume.return_value = [attach_info, volume]

        self.volume.driver.copy_image_to_encrypted_volume(
            self.context, volume, image_service, fake.IMAGE_ID)

        encryption = {'encryption_key_id': fake.ENCRYPTION_KEY_ID}
        mock_attach_volume.assert_called_once_with(
            self.context, volume, properties)
        mock_attach_encryptor.assert_called_once_with(
            self.context, attach_info, encryption)
        mock_fetch_to_raw.assert_called_once_with(
            self.context, image_service, fake.IMAGE_ID,
            local_path, '1M', size=2, disable_sparse=False)
        mock_detach_encryptor.assert_called_once_with(
            attach_info, encryption)
        mock_detach_volume.assert_called_once_with(
            self.context, attach_info, volume, properties, force=True)

    @mock.patch.object(os_brick.initiator.connector,
                       'get_connector_properties')
    @mock.patch.object(image_utils, 'fetch_to_raw')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_attach_volume')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_detach_volume')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_attach_volume_encryptor')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_detach_volume_encryptor')
    def test_copy_image_to_encrypted_volume_failed_attach_encryptor(
            self,
            mock_detach_encryptor,
            mock_attach_encryptor,
            mock_detach_volume,
            mock_attach_volume,
            mock_fetch_to_raw,
            mock_get_connector_properties):
        properties = {}
        volume = tests_utils.create_volume(
            self.context, status='available',
            size=2,
            encryption_key_id=fake.ENCRYPTION_KEY_ID)
        volume_id = volume['id']
        volume = db.volume_get(context.get_admin_context(), volume_id)
        image_service = fake_image.FakeImageService()
        attach_info = {'device': {'path': 'dev/sda'},
                       'conn': {'driver_volume_type': 'iscsi',
                                'data': {}, }}

        mock_get_connector_properties.return_value = properties
        mock_attach_volume.return_value = [attach_info, volume]
        raised_exception = os_brick.exception.VolumeEncryptionNotSupported(
            volume_id = "123",
            volume_type = "abc")
        mock_attach_encryptor.side_effect = raised_exception

        self.assertRaises(os_brick.exception.VolumeEncryptionNotSupported,
                          self.volume.driver.copy_image_to_encrypted_volume,
                          self.context, volume, image_service, fake.IMAGE_ID)

        encryption = {'encryption_key_id': fake.ENCRYPTION_KEY_ID}
        mock_attach_volume.assert_called_once_with(
            self.context, volume, properties)
        mock_attach_encryptor.assert_called_once_with(
            self.context, attach_info, encryption)
        self.assertFalse(mock_fetch_to_raw.called)
        self.assertFalse(mock_detach_encryptor.called)
        mock_detach_volume.assert_called_once_with(
            self.context, attach_info, volume, properties, force=True)

    @mock.patch.object(os_brick.initiator.connector,
                       'get_connector_properties')
    @mock.patch.object(image_utils, 'fetch_to_raw')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_attach_volume')
    @mock.patch.object(cinder.volume.driver.VolumeDriver, '_detach_volume')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_attach_volume_encryptor')
    @mock.patch.object(cinder.volume.volume_utils,
                       'brick_detach_volume_encryptor')
    @ddt.data(exception.ImageUnacceptable(
              reason='fake', image_id=fake.IMAGE_ID),
              exception.ImageTooBig(
              reason='fake image size exceeded', image_id=fake.IMAGE_ID))
    def test_copy_image_to_encrypted_volume_failed_fetch(
            self, excep,
            mock_detach_encryptor, mock_attach_encryptor,
            mock_detach_volume, mock_attach_volume, mock_fetch_to_raw,
            mock_get_connector_properties):
        properties = {}
        volume = tests_utils.create_volume(
            self.context, status='available',
            size=2,
            encryption_key_id=fake.ENCRYPTION_KEY_ID)
        volume_id = volume['id']
        volume = db.volume_get(context.get_admin_context(), volume_id)
        image_service = fake_image.FakeImageService()
        local_path = 'dev/sda'
        attach_info = {'device': {'path': local_path},
                       'conn': {'driver_volume_type': 'iscsi',
                                'data': {}, }}

        mock_get_connector_properties.return_value = properties
        mock_attach_volume.return_value = [attach_info, volume]
        mock_fetch_to_raw.side_effect = excep

        encryption = {'encryption_key_id': fake.ENCRYPTION_KEY_ID}
        self.assertRaises(type(excep),
                          self.volume.driver.copy_image_to_encrypted_volume,
                          self.context, volume, image_service, fake.IMAGE_ID)

        mock_attach_volume.assert_called_once_with(
            self.context, volume, properties)
        mock_attach_encryptor.assert_called_once_with(
            self.context, attach_info, encryption)
        mock_fetch_to_raw.assert_called_once_with(
            self.context, image_service, fake.IMAGE_ID,
            local_path, '1M', size=2,
            disable_sparse=False)
        mock_detach_encryptor.assert_called_once_with(
            attach_info, encryption)
        mock_detach_volume.assert_called_once_with(
            self.context, attach_info, volume, properties, force=True)

    @mock.patch('cinder.volume.driver.brick_exception')
    @mock.patch('cinder.tests.fake_driver.FakeLoggingVolumeDriver.'
                'terminate_connection', side_effect=Exception)
    @mock.patch('cinder.tests.fake_driver.FakeLoggingVolumeDriver.'
                'remove_export', side_effect=Exception)
    def test_detach_volume_force(self, remove_mock, terminate_mock, exc_mock):
        """Test force parameter on _detach_volume.

        On the driver if we receive the force parameter we will do everything
        even with Exceptions on disconnect, terminate, and remove export.
        """
        connector = mock.Mock()
        connector.disconnect_volume.side_effect = Exception
        # TODO(geguileo): Remove this ExceptionChainer simulation once we
        # release OS-Brick version with it and bump min version.
        exc = exc_mock.ExceptionChainer.return_value
        exc.context.return_value.__enter__.return_value = exc
        exc.context.return_value.__exit__.return_value = True

        volume = {'id': fake.VOLUME_ID}
        attach_info = {'device': {},
                       'connector': connector,
                       'conn': {'data': {}, }}

        # TODO(geguileo): Change TypeError to ExceptionChainer once we release
        # OS-Brick version with it and bump min version.
        self.assertRaises(TypeError,
                          self.volume.driver._detach_volume, self.context,
                          attach_info, volume, {}, force=True)

        self.assertTrue(connector.disconnect_volume.called)
        self.assertTrue(remove_mock.called)
        self.assertTrue(terminate_mock.called)
        self.assertEqual(3, exc.context.call_count)

    @ddt.data({'cfg_value': '10', 'valid': True},
              {'cfg_value': 'auto', 'valid': True},
              {'cfg_value': '1', 'valid': True},
              {'cfg_value': '1.2', 'valid': True},
              {'cfg_value': '100', 'valid': True},
              {'cfg_value': '20.15', 'valid': True},
              {'cfg_value': 'True', 'valid': False},
              {'cfg_value': 'False', 'valid': False},
              {'cfg_value': '10.0.0', 'valid': False},
              {'cfg_value': '0.00', 'valid': True},
              {'cfg_value': 'anything', 'valid': False},)
    @ddt.unpack
    def test_auto_max_subscription_ratio_options(self, cfg_value, valid):
        # This tests the max_over_subscription_ratio option as it is now
        # checked by a regex
        def _set_conf(config, value):
            config.set_override('max_over_subscription_ratio', value)

        config = conf.Configuration(None)
        config.append_config_values(driver.volume_opts)

        if valid:
            _set_conf(config, cfg_value)
            self.assertEqual(cfg_value, config.safe_get(
                'max_over_subscription_ratio'))
        else:
            self.assertRaises(ValueError, _set_conf, config, cfg_value)


class FibreChannelTestCase(BaseDriverTestCase):
    """Test Case for FibreChannelDriver."""
    driver_name = "cinder.volume.driver.FibreChannelDriver"

    def test_initialize_connection(self):
        self.assertRaises(NotImplementedError,
                          self.volume.driver.initialize_connection, {}, {})

    def test_validate_connector(self):
        """validate_connector() successful use case.

        validate_connector() does not throw an exception when
        wwpns and wwnns are both set and both are not empty.
        """
        connector = {'wwpns': ["not empty"],
                     'wwnns': ["not empty"]}
        self.volume.driver.validate_connector(connector)

    def test_validate_connector_no_wwpns(self):
        """validate_connector() throws exception when it has no wwpns."""
        connector = {'wwnns': ["not empty"]}
        self.assertRaises(exception.InvalidConnectorException,
                          self.volume.driver.validate_connector, connector)

    def test_validate_connector_empty_wwpns(self):
        """validate_connector() throws exception when it has empty wwpns."""
        connector = {'wwpns': [],
                     'wwnns': ["not empty"]}
        self.assertRaises(exception.InvalidConnectorException,
                          self.volume.driver.validate_connector, connector)

    def test_validate_connector_no_wwnns(self):
        """validate_connector() throws exception when it has no wwnns."""
        connector = {'wwpns': ["not empty"]}
        self.assertRaises(exception.InvalidConnectorException,
                          self.volume.driver.validate_connector, connector)

    def test_validate_connector_empty_wwnns(self):
        """validate_connector() throws exception when it has empty wwnns."""
        connector = {'wwnns': [],
                     'wwpns': ["not empty"]}
        self.assertRaises(exception.InvalidConnectorException,
                          self.volume.driver.validate_connector, connector)
