# Copyright (c) 2014 Andrew Kerr
# Copyright (c) 2015 Alex Meade
# Copyright (c) 2015 Rushil Chugh
# Copyright (c) 2015 Yogesh Kshirsagar
# Copyright (c) 2015 Michael Price
# 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 copy
import ddt
import time
import uuid

import mock
from oslo_utils import units
import six
from six.moves import range
from six.moves import reduce

from cinder import context
from cinder import exception
from cinder import test

from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import utils as cinder_utils
from cinder.tests.unit.volume.drivers.netapp.eseries import fakes as \
    eseries_fake
from cinder.volume.drivers.netapp.eseries import client as es_client
from cinder.volume.drivers.netapp.eseries import exception as eseries_exc
from cinder.volume.drivers.netapp.eseries import host_mapper
from cinder.volume.drivers.netapp.eseries import library
from cinder.volume.drivers.netapp.eseries import utils
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.zonemanager import utils as fczm_utils


def get_fake_volume():
    """Return a fake Cinder Volume that can be used as a parameter"""
    return {
        'id': '114774fb-e15a-4fae-8ee2-c9723e3645ef', 'size': 1,
        'volume_name': 'lun1', 'host': 'hostname@backend#DDP',
        'os_type': 'linux', 'provider_location': 'lun1',
        'name_id': '114774fb-e15a-4fae-8ee2-c9723e3645ef',
        'provider_auth': 'provider a b', 'project_id': 'project',
        'display_name': None, 'display_description': 'lun1',
        'volume_type_id': None, 'migration_status': None, 'attach_status':
        "detached"
    }


@ddt.ddt
class NetAppEseriesLibraryTestCase(test.TestCase):
    def setUp(self):
        super(NetAppEseriesLibraryTestCase, self).setUp()

        kwargs = {'configuration':
                  eseries_fake.create_configuration_eseries()}

        self.library = library.NetAppESeriesLibrary('FAKE', **kwargs)

        # We don't want the looping calls to run
        self.mock_object(self.library, '_start_periodic_tasks',
                         new_attr=mock.Mock())
        # Deprecated Option
        self.library.configuration.netapp_storage_pools = None
        self.library._client = eseries_fake.FakeEseriesClient()

        self.mock_object(self.library, '_start_periodic_tasks',
                         new_attr=mock.Mock())

        self.mock_object(library.cinder_utils, 'synchronized',
                         mock.Mock(return_value=lambda f: f))

        with mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
                        new = cinder_utils.ZeroIntervalLoopingCall):
            self.library.check_for_setup_error()

        self.ctxt = context.get_admin_context()

    def test_do_setup(self):
        self.mock_object(self.library,
                         '_check_mode_get_or_register_storage_system')
        self.mock_object(es_client, 'RestClient',
                         eseries_fake.FakeEseriesClient)
        mock_check_flags = self.mock_object(na_utils, 'check_flags')
        self.library.do_setup(mock.Mock())

        self.assertTrue(mock_check_flags.called)

    @ddt.data(('optimal', True), ('offline', False), ('needsAttn', True),
              ('neverContacted', False), ('newKey', True), (None, True))
    @ddt.unpack
    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_check_storage_system_status(self, status, status_valid):
        system = copy.deepcopy(eseries_fake.STORAGE_SYSTEM)
        system['status'] = status
        status = status.lower() if status is not None else ''

        actual_status, actual_valid = (
            self.library._check_storage_system_status(system))

        self.assertEqual(status, actual_status)
        self.assertEqual(status_valid, actual_valid)

    @ddt.data(('valid', True), ('invalid', False), ('unknown', False),
              ('newKey', True), (None, True))
    @ddt.unpack
    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_check_password_status(self, status, status_valid):
        system = copy.deepcopy(eseries_fake.STORAGE_SYSTEM)
        system['passwordStatus'] = status
        status = status.lower() if status is not None else ''

        actual_status, actual_valid = (
            self.library._check_password_status(system))

        self.assertEqual(status, actual_status)
        self.assertEqual(status_valid, actual_valid)

    def test_check_storage_system_bad_system(self):
        exc_str = "bad_system"
        controller_ips = self.library.configuration.netapp_controller_ips
        self.library._client.list_storage_system = mock.Mock(
            side_effect=exception.NetAppDriverException(message=exc_str))
        info_log = self.mock_object(library.LOG, 'info', mock.Mock())

        self.assertRaisesRegexp(exception.NetAppDriverException, exc_str,
                                self.library._check_storage_system)

        info_log.assert_called_once_with(mock.ANY, controller_ips)

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_check_storage_system(self):
        system = copy.deepcopy(eseries_fake.STORAGE_SYSTEM)
        self.mock_object(self.library._client, 'list_storage_system',
                         new_attr=mock.Mock(return_value=system))
        update_password = self.mock_object(self.library._client,
                                           'update_stored_system_password')
        info_log = self.mock_object(library.LOG, 'info', mock.Mock())

        self.library._check_storage_system()

        self.assertTrue(update_password.called)
        self.assertTrue(info_log.called)

    @ddt.data({'status': 'optimal', 'passwordStatus': 'invalid'},
              {'status': 'offline', 'passwordStatus': 'valid'})
    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_check_storage_system_bad_status(self, system):
        self.mock_object(self.library._client, 'list_storage_system',
                         new_attr=mock.Mock(return_value=system))
        self.mock_object(self.library._client, 'update_stored_system_password')
        self.mock_object(time, 'time', new_attr = mock.Mock(
            side_effect=range(0, 60, 5)))

        self.assertRaisesRegexp(exception.NetAppDriverException,
                                'bad.*?status',
                                self.library._check_storage_system)

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_check_storage_system_update_password(self):
        self.library.configuration.netapp_sa_password = 'password'

        def get_system_iter():
            key = 'passwordStatus'
            system = copy.deepcopy(eseries_fake.STORAGE_SYSTEM)
            system[key] = 'invalid'
            yield system
            yield system

            system[key] = 'valid'
            yield system

        self.mock_object(self.library._client, 'list_storage_system',
                         new_attr=mock.Mock(side_effect=get_system_iter()))
        update_password = self.mock_object(self.library._client,
                                           'update_stored_system_password',
                                           new_attr=mock.Mock())
        info_log = self.mock_object(library.LOG, 'info', mock.Mock())

        self.library._check_storage_system()

        update_password.assert_called_once_with(
            self.library.configuration.netapp_sa_password)
        self.assertTrue(info_log.called)

    def test_get_storage_pools_empty_result(self):
        """Verify an exception is raised if no pools are returned."""
        self.library.configuration.netapp_pool_name_search_pattern = '$'

    def test_get_storage_pools_invalid_conf(self):
        """Verify an exception is raised if the regex pattern is invalid."""
        self.library.configuration.netapp_pool_name_search_pattern = '(.*'

        self.assertRaises(exception.InvalidConfigurationValue,
                          self.library._get_storage_pools)

    def test_get_storage_pools_default(self):
        """Verify that all pools are returned if the search option is empty."""
        filtered_pools = self.library._get_storage_pools()

        self.assertEqual(eseries_fake.STORAGE_POOLS, filtered_pools)

    @ddt.data(('[\d]+,a', ['1', '2', 'a', 'b'], ['1', '2', 'a']),
              ('1   ,    3', ['1', '2', '3'], ['1', '3']),
              ('$,3', ['1', '2', '3'], ['3']),
              ('[a-zA-Z]+', ['1', 'a', 'B'], ['a', 'B']),
              ('', ['1', '2'], ['1', '2'])
              )
    @ddt.unpack
    def test_get_storage_pools(self, pool_filter, pool_labels,
                               expected_pool_labels):
        """Verify that pool filtering via the search_pattern works correctly

        :param pool_filter: A regular expression to be used for filtering via
         pool labels
        :param pool_labels: A list of pool labels
        :param expected_pool_labels: The labels from 'pool_labels' that
         should be matched by 'pool_filter'
        """
        self.library.configuration.netapp_pool_name_search_pattern = (
            pool_filter)
        pools = [{'label': label} for label in pool_labels]

        self.library._client.list_storage_pools = mock.Mock(
            return_value=pools)

        filtered_pools = self.library._get_storage_pools()

        filtered_pool_labels = [pool['label'] for pool in filtered_pools]
        self.assertEqual(expected_pool_labels, filtered_pool_labels)

    def test_get_volume(self):
        fake_volume = copy.deepcopy(get_fake_volume())
        volume = copy.deepcopy(eseries_fake.VOLUME)
        self.library._client.list_volume = mock.Mock(return_value=volume)

        result = self.library._get_volume(fake_volume['id'])

        self.assertEqual(1, self.library._client.list_volume.call_count)
        self.assertDictMatch(volume, result)

    def test_get_volume_bad_input(self):
        volume = copy.deepcopy(eseries_fake.VOLUME)
        self.library._client.list_volume = mock.Mock(return_value=volume)

        self.assertRaises(exception.InvalidInput, self.library._get_volume,
                          None)

    def test_get_volume_bad_uuid(self):
        volume = copy.deepcopy(eseries_fake.VOLUME)
        self.library._client.list_volume = mock.Mock(return_value=volume)

        self.assertRaises(ValueError, self.library._get_volume, '1')

    def test_update_ssc_info_no_ssc(self):
        drives = [{'currentVolumeGroupRef': 'test_vg1',
                   'driveMediaType': 'ssd'}]
        pools = [{'volumeGroupRef': 'test_vg1', 'label': 'test_vg1',
                  'raidLevel': 'raid6', 'securityType': 'enabled'}]
        self.library._client = mock.Mock()
        self.library._client.features.SSC_API_V2 = na_utils.FeatureState(
            False, minimum_version="1.53.9000.1")
        self.library._client.SSC_VALID_VERSIONS = [(1, 53, 9000, 1),
                                                   (1, 53, 9010, 15)]
        self.library.configuration.netapp_pool_name_search_pattern = "test_vg1"
        self.library._client.list_storage_pools = mock.Mock(return_value=pools)
        self.library._client.list_drives = mock.Mock(return_value=drives)

        self.library._update_ssc_info()

        self.assertEqual(
            {'test_vg1': {'netapp_disk_encryption': 'true',
                          'netapp_disk_type': 'SSD',
                          'netapp_raid_type': 'raid6'}},
            self.library._ssc_stats)

    @ddt.data(True, False)
    def test_update_ssc_info(self, data_assurance_supported):
        self.library._client = mock.Mock()
        self.library._client.features.SSC_API_V2 = na_utils.FeatureState(
            True, minimum_version="1.53.9000.1")
        self.library._client.list_ssc_storage_pools = mock.Mock(
            return_value=eseries_fake.SSC_POOLS)
        self.library._get_storage_pools = mock.Mock(
            return_value=eseries_fake.STORAGE_POOLS)
        # Data Assurance is not supported on some storage backends
        self.library._is_data_assurance_supported = mock.Mock(
            return_value=data_assurance_supported)

        self.library._update_ssc_info()

        for pool in eseries_fake.SSC_POOLS:
            poolId = pool['poolId']

            raid_lvl = self.library.SSC_RAID_TYPE_MAPPING.get(
                pool['raidLevel'], 'unknown')

            if pool['pool']["driveMediaType"] == 'ssd':
                disk_type = 'SSD'
            else:
                disk_type = pool['pool']['drivePhysicalType']
                disk_type = (
                    self.library.SSC_DISK_TYPE_MAPPING.get(
                        disk_type, 'unknown'))

            da_enabled = pool['dataAssuranceCapable'] and (
                data_assurance_supported)

            thin_provisioned = pool['thinProvisioningCapable']

            expected = {
                'consistencygroup_support': True,
                'netapp_disk_encryption':
                    six.text_type(pool['encrypted']).lower(),
                'netapp_eseries_flash_read_cache':
                    six.text_type(pool['flashCacheCapable']).lower(),
                'netapp_thin_provisioned':
                    six.text_type(thin_provisioned).lower(),
                'netapp_eseries_data_assurance':
                    six.text_type(da_enabled).lower(),
                'netapp_eseries_disk_spindle_speed': pool['spindleSpeed'],
                'netapp_raid_type': raid_lvl,
                'netapp_disk_type': disk_type
            }
            actual = self.library._ssc_stats[poolId]
            self.assertDictMatch(expected, actual)

    @ddt.data(('FC', True), ('iSCSI', False))
    @ddt.unpack
    def test_is_data_assurance_supported(self, backend_storage_protocol,
                                         enabled):
        self.mock_object(self.library, 'driver_protocol',
                         backend_storage_protocol)

        actual = self.library._is_data_assurance_supported()

        self.assertEqual(enabled, actual)

    @ddt.data('scsi', 'fibre', 'sas', 'sata', 'garbage')
    def test_update_ssc_disk_types(self, disk_type):
        drives = [{'currentVolumeGroupRef': 'test_vg1',
                   'interfaceType': {'driveType': disk_type}}]
        pools = [{'volumeGroupRef': 'test_vg1'}]

        self.library._client.list_drives = mock.Mock(return_value=drives)
        self.library._client.get_storage_pool = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_disk_types(pools)

        expected = self.library.SSC_DISK_TYPE_MAPPING.get(disk_type, 'unknown')
        self.assertEqual({'test_vg1': {'netapp_disk_type': expected}},
                         ssc_stats)

    @ddt.data('scsi', 'fibre', 'sas', 'sata', 'garbage')
    def test_update_ssc_disk_types_ssd(self, disk_type):
        drives = [{'currentVolumeGroupRef': 'test_vg1',
                   'driveMediaType': 'ssd', 'driveType': disk_type}]
        pools = [{'volumeGroupRef': 'test_vg1'}]

        self.library._client.list_drives = mock.Mock(return_value=drives)
        self.library._client.get_storage_pool = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_disk_types(pools)

        self.assertEqual({'test_vg1': {'netapp_disk_type': 'SSD'}},
                         ssc_stats)

    @ddt.data('enabled', 'none', 'capable', 'unknown', '__UNDEFINED',
              'garbage')
    def test_update_ssc_disk_encryption(self, securityType):
        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': securityType}]
        self.library._client.list_storage_pools = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_disk_encryption(pools)

        # Convert the boolean value to a lower-case string value
        expected = 'true' if securityType == "enabled" else 'false'
        self.assertEqual({'test_vg1': {'netapp_disk_encryption': expected}},
                         ssc_stats)

    def test_update_ssc_disk_encryption_multiple(self):
        pools = [{'volumeGroupRef': 'test_vg1', 'securityType': 'none'},
                 {'volumeGroupRef': 'test_vg2', 'securityType': 'enabled'}]
        self.library._client.list_storage_pools = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_disk_encryption(pools)

        self.assertEqual({'test_vg1': {'netapp_disk_encryption': 'false'},
                          'test_vg2': {'netapp_disk_encryption': 'true'}},
                         ssc_stats)

    @ddt.data(True, False)
    def test_get_volume_stats(self, refresh):
        fake_stats = {'key': 'val'}

        def populate_stats():
            self.library._stats = fake_stats

        self.library._update_volume_stats = mock.Mock(
            side_effect=populate_stats)
        self.library._update_ssc_info = mock.Mock()
        self.library._ssc_stats = {self.library.THIN_UQ_SPEC: True}

        actual = self.library.get_volume_stats(refresh = refresh)

        if(refresh):
            self.library._update_volume_stats.assert_called_once_with()
            self.assertEqual(fake_stats, actual)
        else:
            self.assertEqual(0, self.library._update_volume_stats.call_count)
        self.assertEqual(0, self.library._update_ssc_info.call_count)

    def test_get_volume_stats_no_ssc(self):
        """Validate that SSC data is collected if not yet populated"""
        fake_stats = {'key': 'val'}

        def populate_stats():
            self.library._stats = fake_stats

        self.library._update_volume_stats = mock.Mock(
            side_effect=populate_stats)
        self.library._update_ssc_info = mock.Mock()
        self.library._ssc_stats = None

        actual = self.library.get_volume_stats(refresh = True)

        self.library._update_volume_stats.assert_called_once_with()
        self.library._update_ssc_info.assert_called_once_with()
        self.assertEqual(fake_stats, actual)

    def test_update_volume_stats_provisioning(self):
        """Validate pool capacity calculations"""
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
            "volumeGroupRef"]: {self.library.THIN_UQ_SPEC: True}})
        self.library.configuration = mock.Mock()
        reserved_pct = 5
        over_subscription_ratio = 1.0
        self.library.configuration.max_over_subscription_ratio = (
            over_subscription_ratio)
        self.library.configuration.reserved_percentage = reserved_pct
        total_gb = int(fake_pool['totalRaidedSpace']) / units.Gi
        used_gb = int(fake_pool['usedSpace']) / units.Gi
        free_gb = total_gb - used_gb

        self.library._update_volume_stats()

        self.assertEqual(1, len(self.library._stats['pools']))
        pool_stats = self.library._stats['pools'][0]
        self.assertEqual(fake_pool['label'], pool_stats.get('pool_name'))
        self.assertEqual(reserved_pct, pool_stats['reserved_percentage'])
        self.assertEqual(over_subscription_ratio,
                         pool_stats['max_over_subscription_ratio'])
        self.assertEqual(total_gb, pool_stats.get('total_capacity_gb'))
        self.assertEqual(used_gb, pool_stats.get('provisioned_capacity_gb'))
        self.assertEqual(free_gb, pool_stats.get('free_capacity_gb'))

    @ddt.data(False, True)
    def test_update_volume_stats_thin_provisioning(self, thin_provisioning):
        """Validate that thin provisioning support is correctly reported"""
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
            "volumeGroupRef"]: {self.library.THIN_UQ_SPEC: thin_provisioning}})

        self.library._update_volume_stats()

        self.assertEqual(1, len(self.library._stats['pools']))
        pool_stats = self.library._stats['pools'][0]
        self.assertEqual(thin_provisioning, pool_stats.get(
            'thin_provisioning_support'))
        # Should always be True
        self.assertTrue(pool_stats.get('thick_provisioning_support'))

    def test_update_volume_stats_ssc(self):
        """Ensure that the SSC data is correctly reported in the pool stats"""
        ssc = {self.library.THIN_UQ_SPEC: True, 'key': 'val'}
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        self.mock_object(self.library, '_ssc_stats', new_attr={fake_pool[
            "volumeGroupRef"]: ssc})

        self.library._update_volume_stats()

        self.assertEqual(1, len(self.library._stats['pools']))
        pool_stats = self.library._stats['pools'][0]
        for key in ssc:
            self.assertIn(key, pool_stats)
            self.assertEqual(ssc[key], pool_stats[key])

    def test_update_volume_stats_no_ssc(self):
        """Ensure that that pool stats are correctly reported without SSC"""
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        self.library._update_volume_stats()

        self.assertEqual(1, len(self.library._stats['pools']))
        pool_stats = self.library._stats['pools'][0]
        self.assertFalse(pool_stats.get('thin_provisioning_support'))
        # Should always be True
        self.assertTrue(pool_stats.get('thick_provisioning_support'))

    def test_terminate_connection_iscsi_no_hosts(self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}

        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[]))

        self.assertRaises(exception.NotFound,
                          self.library.terminate_connection_iscsi,
                          get_fake_volume(),
                          connector)

    def test_terminate_connection_iscsi_volume_not_mapped(self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        volume = copy.deepcopy(eseries_fake.VOLUME)
        volume['listOfMappings'] = []
        self.library._get_volume = mock.Mock(return_value=volume)
        self.assertRaises(eseries_exc.VolumeNotMapped,
                          self.library.terminate_connection_iscsi,
                          get_fake_volume(),
                          connector)

    def test_terminate_connection_iscsi_volume_mapped(self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            eseries_fake.VOLUME_MAPPING
        ]
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))
        self.mock_object(host_mapper, 'unmap_volume_from_host')

        self.library.terminate_connection_iscsi(get_fake_volume(), connector)

        self.assertTrue(host_mapper.unmap_volume_from_host.called)

    def test_terminate_connection_iscsi_not_mapped_initiator_does_not_exist(
            self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[eseries_fake.HOST_2]))
        self.assertRaises(exception.NotFound,
                          self.library.terminate_connection_iscsi,
                          get_fake_volume(),
                          connector)

    def test_initialize_connection_iscsi_volume_not_mapped(self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            eseries_fake.VOLUME_MAPPING
        ]
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))

        self.library.initialize_connection_iscsi(get_fake_volume(), connector)

        self.assertTrue(
            self.library._client.get_volume_mappings_for_volume.called)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_initialize_connection_iscsi_volume_not_mapped_host_does_not_exist(
            self):
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[]))
        self.mock_object(self.library._client, 'create_host_with_ports',
                         mock.Mock(return_value=eseries_fake.HOST))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            eseries_fake.VOLUME_MAPPING
        ]
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))

        self.library.initialize_connection_iscsi(get_fake_volume(), connector)

        self.assertTrue(
            self.library._client.get_volume_mappings_for_volume.called)
        self.assertTrue(self.library._client.list_hosts.called)
        self.assertTrue(self.library._client.create_host_with_ports.called)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_initialize_connection_iscsi_volume_already_mapped_to_target_host(
            self):
        """Should be a no-op"""
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))

        self.library.initialize_connection_iscsi(get_fake_volume(), connector)

        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_initialize_connection_iscsi_volume_mapped_to_another_host(self):
        """Should raise error saying multiattach not enabled"""
        connector = {'initiator': eseries_fake.INITIATOR_NAME}
        fake_mapping_to_other_host = copy.deepcopy(
            eseries_fake.VOLUME_MAPPING)
        fake_mapping_to_other_host['mapRef'] = eseries_fake.HOST_2[
            'hostRef']
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             side_effect=exception.NetAppDriverException))

        self.assertRaises(exception.NetAppDriverException,
                          self.library.initialize_connection_iscsi,
                          get_fake_volume(), connector)

        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    @ddt.data(eseries_fake.WWPN,
              fczm_utils.get_formatted_wwn(eseries_fake.WWPN))
    def test_get_host_with_matching_port_wwpn(self, port_id):
        port_ids = [port_id]
        host = copy.deepcopy(eseries_fake.HOST)
        host.update(
            {
                'hostSidePorts': [{'label': 'NewStore', 'type': 'fc',
                                   'address': eseries_fake.WWPN}]
            }
        )
        host_2 = copy.deepcopy(eseries_fake.HOST_2)
        host_2.update(
            {
                'hostSidePorts': [{'label': 'NewStore', 'type': 'fc',
                                   'address': eseries_fake.WWPN_2}]
            }
        )
        host_list = [host, host_2]
        self.mock_object(self.library._client,
                         'list_hosts',
                         mock.Mock(return_value=host_list))

        actual_host = self.library._get_host_with_matching_port(
            port_ids)

        self.assertEqual(host, actual_host)

    def test_get_host_with_matching_port_iqn(self):
        port_ids = [eseries_fake.INITIATOR_NAME]
        host = copy.deepcopy(eseries_fake.HOST)
        host.update(
            {
                'hostSidePorts': [{'label': 'NewStore', 'type': 'iscsi',
                                   'address': eseries_fake.INITIATOR_NAME}]
            }
        )
        host_2 = copy.deepcopy(eseries_fake.HOST_2)
        host_2.update(
            {
                'hostSidePorts': [{'label': 'NewStore', 'type': 'iscsi',
                                   'address': eseries_fake.INITIATOR_NAME_2}]
            }
        )
        host_list = [host, host_2]
        self.mock_object(self.library._client,
                         'list_hosts',
                         mock.Mock(return_value=host_list))

        actual_host = self.library._get_host_with_matching_port(
            port_ids)

        self.assertEqual(host, actual_host)

    def test_terminate_connection_fc_no_hosts(self):
        connector = {'wwpns': [eseries_fake.WWPN]}

        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[]))

        self.assertRaises(exception.NotFound,
                          self.library.terminate_connection_fc,
                          get_fake_volume(),
                          connector)

    def test_terminate_connection_fc_volume_not_mapped(self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        fake_host = copy.deepcopy(eseries_fake.HOST)
        fake_host['hostSidePorts'] = [{
            'label': 'NewStore',
            'type': 'fc',
            'address': eseries_fake.WWPN
        }]
        volume = copy.deepcopy(eseries_fake.VOLUME)
        volume['listOfMappings'] = []
        self.mock_object(self.library, '_get_volume',
                         mock.Mock(return_value=volume))

        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[fake_host]))

        self.assertRaises(eseries_exc.VolumeNotMapped,
                          self.library.terminate_connection_fc,
                          get_fake_volume(),
                          connector)

    def test_terminate_connection_fc_volume_mapped(self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        fake_host = copy.deepcopy(eseries_fake.HOST)
        fake_host['hostSidePorts'] = [{
            'label': 'NewStore',
            'type': 'fc',
            'address': eseries_fake.WWPN
        }]
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            copy.deepcopy(eseries_fake.VOLUME_MAPPING)
        ]
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[fake_host]))
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))
        self.mock_object(host_mapper, 'unmap_volume_from_host')

        self.library.terminate_connection_fc(get_fake_volume(), connector)

        self.assertTrue(host_mapper.unmap_volume_from_host.called)

    def test_terminate_connection_fc_volume_mapped_no_cleanup_zone(self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        fake_host = copy.deepcopy(eseries_fake.HOST)
        fake_host['hostSidePorts'] = [{
            'label': 'NewStore',
            'type': 'fc',
            'address': eseries_fake.WWPN
        }]
        expected_target_info = {
            'driver_volume_type': 'fibre_channel',
            'data': {},
        }
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            copy.deepcopy(eseries_fake.VOLUME_MAPPING)
        ]
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[fake_host]))
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))
        self.mock_object(host_mapper, 'unmap_volume_from_host')
        self.mock_object(self.library._client, 'get_volume_mappings_for_host',
                         mock.Mock(return_value=[copy.deepcopy
                                                 (eseries_fake.
                                                  VOLUME_MAPPING)]))

        target_info = self.library.terminate_connection_fc(get_fake_volume(),
                                                           connector)
        self.assertDictEqual(expected_target_info, target_info)

        self.assertTrue(host_mapper.unmap_volume_from_host.called)

    def test_terminate_connection_fc_volume_mapped_cleanup_zone(self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        fake_host = copy.deepcopy(eseries_fake.HOST)
        fake_host['hostSidePorts'] = [{
            'label': 'NewStore',
            'type': 'fc',
            'address': eseries_fake.WWPN
        }]
        expected_target_info = {
            'driver_volume_type': 'fibre_channel',
            'data': {
                'target_wwn': [eseries_fake.WWPN_2],
                'initiator_target_map': {
                    eseries_fake.WWPN: [eseries_fake.WWPN_2]
                },
            },
        }
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_eseries_volume['listOfMappings'] = [
            copy.deepcopy(eseries_fake.VOLUME_MAPPING)
        ]
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[fake_host]))
        self.mock_object(self.library._client, 'list_volume',
                         mock.Mock(return_value=fake_eseries_volume))
        self.mock_object(host_mapper, 'unmap_volume_from_host')
        self.mock_object(self.library._client, 'get_volume_mappings_for_host',
                         mock.Mock(return_value=[]))

        target_info = self.library.terminate_connection_fc(get_fake_volume(),
                                                           connector)
        self.assertDictEqual(expected_target_info, target_info)

        self.assertTrue(host_mapper.unmap_volume_from_host.called)

    def test_terminate_connection_fc_not_mapped_host_with_wwpn_does_not_exist(
            self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[eseries_fake.HOST_2]))
        self.assertRaises(exception.NotFound,
                          self.library.terminate_connection_fc,
                          get_fake_volume(),
                          connector)

    def test_initialize_connection_fc_volume_not_mapped(self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))
        expected_target_info = {
            'driver_volume_type': 'fibre_channel',
            'data': {
                'target_discovered': True,
                'target_lun': 0,
                'target_wwn': [eseries_fake.WWPN_2],
                'initiator_target_map': {
                    eseries_fake.WWPN: [eseries_fake.WWPN_2]
                },
            },
        }

        target_info = self.library.initialize_connection_fc(get_fake_volume(),
                                                            connector)

        self.assertTrue(
            self.library._client.get_volume_mappings_for_volume.called)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)
        self.assertDictEqual(expected_target_info, target_info)

    def test_initialize_connection_fc_volume_not_mapped_host_does_not_exist(
            self):
        connector = {'wwpns': [eseries_fake.WWPN]}
        self.library.driver_protocol = 'FC'
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[]))
        self.mock_object(self.library._client, 'create_host_with_ports',
                         mock.Mock(return_value=eseries_fake.HOST))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))

        self.library.initialize_connection_fc(get_fake_volume(), connector)

        self.library._client.create_host_with_ports.assert_called_once_with(
            mock.ANY, mock.ANY,
            [fczm_utils.get_formatted_wwn(eseries_fake.WWPN)],
            port_type='fc', group_id=None
        )

    def test_initialize_connection_fc_volume_already_mapped_to_target_host(
            self):
        """Should be a no-op"""
        connector = {'wwpns': [eseries_fake.WWPN]}
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))

        self.library.initialize_connection_fc(get_fake_volume(), connector)

        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_initialize_connection_fc_volume_mapped_to_another_host(self):
        """Should raise error saying multiattach not enabled"""
        connector = {'wwpns': [eseries_fake.WWPN]}
        fake_mapping_to_other_host = copy.deepcopy(
            eseries_fake.VOLUME_MAPPING)
        fake_mapping_to_other_host['mapRef'] = eseries_fake.HOST_2[
            'hostRef']
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             side_effect=exception.NetAppDriverException))

        self.assertRaises(exception.NetAppDriverException,
                          self.library.initialize_connection_fc,
                          get_fake_volume(), connector)

        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_initialize_connection_fc_no_target_wwpns(self):
        """Should be a no-op"""
        connector = {'wwpns': [eseries_fake.WWPN]}
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))
        self.mock_object(self.library._client, 'list_target_wwpns',
                         mock.Mock(return_value=[]))

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.initialize_connection_fc,
                          get_fake_volume(), connector)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_build_initiator_target_map_fc_with_lookup_service(
            self):
        connector = {'wwpns': [eseries_fake.WWPN, eseries_fake.WWPN_2]}
        self.library.lookup_service = mock.Mock()
        self.library.lookup_service.get_device_mapping_from_network = (
            mock.Mock(return_value=eseries_fake.FC_FABRIC_MAP))

        (target_wwpns, initiator_target_map, num_paths) = (
            self.library._build_initiator_target_map_fc(connector))

        self.assertSetEqual(set(eseries_fake.FC_TARGET_WWPNS),
                            set(target_wwpns))
        self.assertDictEqual(eseries_fake.FC_I_T_MAP, initiator_target_map)
        self.assertEqual(4, num_paths)

    @ddt.data(('raid0', 'raid0'), ('raid1', 'raid1'), ('raid3', 'raid5'),
              ('raid5', 'raid5'), ('raid6', 'raid6'), ('raidDiskPool', 'DDP'))
    @ddt.unpack
    def test_update_ssc_raid_type(self, raid_lvl, raid_lvl_mapping):
        pools = [{'volumeGroupRef': 'test_vg1', 'raidLevel': raid_lvl}]
        self.library._client.list_storage_pools = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_raid_type(pools)

        self.assertEqual({'test_vg1': {'netapp_raid_type': raid_lvl_mapping}},
                         ssc_stats)

    @ddt.data('raidAll', '__UNDEFINED', 'unknown',
              'raidUnsupported', 'garbage')
    def test_update_ssc_raid_type_invalid(self, raid_lvl):
        pools = [{'volumeGroupRef': 'test_vg1', 'raidLevel': raid_lvl}]
        self.library._client.list_storage_pools = mock.Mock(return_value=pools)

        ssc_stats = self.library._update_ssc_raid_type(pools)

        self.assertEqual({'test_vg1': {'netapp_raid_type': 'unknown'}},
                         ssc_stats)

    def test_create_asup(self):
        self.library._client = mock.Mock()
        self.library._client.features.AUTOSUPPORT = na_utils.FeatureState()
        self.library._client.api_operating_mode = (
            eseries_fake.FAKE_ASUP_DATA['operating-mode'])
        self.library._app_version = eseries_fake.FAKE_APP_VERSION
        self.mock_object(
            self.library._client, 'get_asup_info',
            mock.Mock(return_value=eseries_fake.GET_ASUP_RETURN))
        self.mock_object(
            self.library._client, 'set_counter',
            mock.Mock(return_value={'value': 1}))
        mock_invoke = self.mock_object(
            self.library._client, 'add_autosupport_data')

        self.library._create_asup(eseries_fake.FAKE_CINDER_HOST)

        mock_invoke.assert_called_with(eseries_fake.FAKE_KEY,
                                       eseries_fake.FAKE_ASUP_DATA)

    def test_create_asup_not_supported(self):
        self.library._client = mock.Mock()
        self.library._client.features.AUTOSUPPORT = na_utils.FeatureState(
            supported=False)
        mock_invoke = self.mock_object(
            self.library._client, 'add_autosupport_data')

        self.library._create_asup(eseries_fake.FAKE_CINDER_HOST)

        mock_invoke.assert_not_called()

    @mock.patch.object(library, 'LOG', mock.Mock())
    def test_create_volume_fail_clean(self):
        """Test volume creation fail w/o a partial volume being created.

        Test the failed creation of a volume where a partial volume with
        the name has not been created, thus no cleanup is required.
        """
        self.library._get_volume = mock.Mock(
            side_effect = exception.VolumeNotFound(message=''))
        self.library._client.create_volume = mock.Mock(
            side_effect = exception.NetAppDriverException)
        self.library._client.delete_volume = mock.Mock()
        fake_volume = copy.deepcopy(get_fake_volume())

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume, fake_volume)

        self.assertTrue(self.library._get_volume.called)
        self.assertFalse(self.library._client.delete_volume.called)
        self.assertEqual(1, library.LOG.error.call_count)

    @mock.patch.object(library, 'LOG', mock.Mock())
    def test_create_volume_fail_dirty(self):
        """Test volume creation fail where a partial volume has been created.

        Test scenario where the creation of a volume fails and a partial
        volume is created with the name/id that was supplied by to the
        original creation call.  In this situation the partial volume should
        be detected and removed.
        """
        fake_volume = copy.deepcopy(get_fake_volume())
        self.library._get_volume = mock.Mock(return_value=fake_volume)
        self.library._client.list_volume = mock.Mock(return_value=fake_volume)
        self.library._client.create_volume = mock.Mock(
            side_effect = exception.NetAppDriverException)
        self.library._client.delete_volume = mock.Mock()

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume, fake_volume)

        self.assertTrue(self.library._get_volume.called)
        self.assertTrue(self.library._client.delete_volume.called)
        self.library._client.delete_volume.assert_called_once_with(
            fake_volume["id"])
        self.assertEqual(1, library.LOG.error.call_count)

    @mock.patch.object(library, 'LOG', mock.Mock())
    def test_create_volume_fail_dirty_fail_delete(self):
        """Volume creation fail with partial volume deletion fails

        Test scenario where the creation of a volume fails and a partial
        volume is created with the name/id that was supplied by to the
        original creation call. The partial volume is detected but when
        the cleanup deletetion of that fragment volume is attempted it fails.
        """
        fake_volume = copy.deepcopy(get_fake_volume())
        self.library._get_volume = mock.Mock(return_value=fake_volume)
        self.library._client.list_volume = mock.Mock(return_value=fake_volume)
        self.library._client.create_volume = mock.Mock(
            side_effect = exception.NetAppDriverException)
        self.library._client.delete_volume = mock.Mock(
            side_effect = exception.NetAppDriverException)

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume, fake_volume)

        self.assertTrue(self.library._get_volume.called)
        self.assertTrue(self.library._client.delete_volume.called)
        self.library._client.delete_volume.assert_called_once_with(
            fake_volume["id"])
        self.assertEqual(2, library.LOG.error.call_count)

    def test_create_consistencygroup(self):
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        expected = {'status': 'available'}
        create_cg = self.mock_object(self.library,
                                     '_create_consistency_group',
                                     mock.Mock(return_value=expected))

        actual = self.library.create_consistencygroup(fake_cg)

        create_cg.assert_called_once_with(fake_cg)
        self.assertEqual(expected, actual)

    def test_create_consistency_group(self):
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        expected = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        create_cg = self.mock_object(self.library._client,
                                     'create_consistency_group',
                                     mock.Mock(return_value=expected))

        result = self.library._create_consistency_group(fake_cg)

        name = utils.convert_uuid_to_es_fmt(fake_cg['id'])
        create_cg.assert_called_once_with(name)
        self.assertEqual(expected, result)

    def test_delete_consistencygroup(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        volumes = [get_fake_volume()] * 3
        model_update = {'status': 'deleted'}
        volume_update = [{'status': 'deleted', 'id': vol['id']} for vol in
                         volumes]
        delete_cg = self.mock_object(self.library._client,
                                     'delete_consistency_group')
        updt_index = self.mock_object(
            self.library, '_merge_soft_delete_changes')
        delete_vol = self.mock_object(self.library, 'delete_volume')
        self.mock_object(self.library, '_get_consistencygroup',
                         mock.Mock(return_value=cg))

        result = self.library.delete_consistencygroup(fake_cg, volumes)

        self.assertEqual(len(volumes), delete_vol.call_count)
        delete_cg.assert_called_once_with(cg['id'])
        self.assertEqual((model_update, volume_update), result)
        updt_index.assert_called_once_with(None, [cg['id']])

    def test_delete_consistencygroup_index_update_failure(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        volumes = [get_fake_volume()] * 3
        model_update = {'status': 'deleted'}
        volume_update = [{'status': 'deleted', 'id': vol['id']} for vol in
                         volumes]
        delete_cg = self.mock_object(self.library._client,
                                     'delete_consistency_group')
        delete_vol = self.mock_object(self.library, 'delete_volume')
        self.mock_object(self.library, '_get_consistencygroup',
                         mock.Mock(return_value=cg))

        result = self.library.delete_consistencygroup(fake_cg, volumes)

        self.assertEqual(len(volumes), delete_vol.call_count)
        delete_cg.assert_called_once_with(cg['id'])
        self.assertEqual((model_update, volume_update), result)

    def test_delete_consistencygroup_not_found(self):
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        delete_cg = self.mock_object(self.library._client,
                                     'delete_consistency_group')
        updt_index = self.mock_object(
            self.library, '_merge_soft_delete_changes')
        delete_vol = self.mock_object(self.library, 'delete_volume')
        exc = exception.ConsistencyGroupNotFound(consistencygroup_id='')
        self.mock_object(self.library, '_get_consistencygroup',
                         mock.Mock(side_effect=exc))

        self.library.delete_consistencygroup(fake_cg, [])

        delete_cg.assert_not_called()
        delete_vol.assert_not_called()
        updt_index.assert_not_called()

    def test_get_consistencygroup(self):
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        name = utils.convert_uuid_to_es_fmt(fake_cg['id'])
        cg['name'] = name
        list_cgs = self.mock_object(self.library._client,
                                    'list_consistency_groups',
                                    mock.Mock(return_value=[cg]))

        result = self.library._get_consistencygroup(fake_cg)

        self.assertEqual(cg, result)
        list_cgs.assert_called_once_with()

    def test_get_consistencygroup_not_found(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        list_cgs = self.mock_object(self.library._client,
                                    'list_consistency_groups',
                                    mock.Mock(return_value=[cg]))

        self.assertRaises(exception.ConsistencyGroupNotFound,
                          self.library._get_consistencygroup,
                          copy.deepcopy(eseries_fake.FAKE_CINDER_CG))

        list_cgs.assert_called_once_with()

    def test_update_consistencygroup(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        vol = copy.deepcopy(eseries_fake.VOLUME)
        volumes = [get_fake_volume()] * 3
        self.mock_object(
            self.library, '_get_volume', mock.Mock(return_value=vol))
        self.mock_object(self.library, '_get_consistencygroup',
                         mock.Mock(return_value=cg))

        self.library.update_consistencygroup(fake_cg, volumes, volumes)

    def test_create_consistencygroup_from_src(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        volumes = [cinder_utils.create_volume(self.ctxt) for i in range(3)]
        src_volumes = [cinder_utils.create_volume(self.ctxt) for v in volumes]
        update_cg = self.mock_object(
            self.library, '_update_consistency_group_members')
        create_cg = self.mock_object(
            self.library, '_create_consistency_group',
            mock.Mock(return_value=cg))
        self.mock_object(
            self.library, '_create_volume_from_snapshot')

        self.mock_object(
            self.library, '_get_snapshot', mock.Mock(return_value=snap))

        self.library.create_consistencygroup_from_src(
            fake_cg, volumes, None, None, None, src_volumes)

        create_cg.assert_called_once_with(fake_cg)
        update_cg.assert_called_once_with(cg, volumes, [])

    def test_create_consistencygroup_from_src_cgsnapshot(self):
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        fake_cg = copy.deepcopy(eseries_fake.FAKE_CINDER_CG)
        fake_vol = cinder_utils.create_volume(self.ctxt)
        cgsnap = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        volumes = [fake_vol]
        snapshots = [cinder_utils.create_snapshot(self.ctxt, v['id']) for v
                     in volumes]
        update_cg = self.mock_object(
            self.library, '_update_consistency_group_members')
        create_cg = self.mock_object(
            self.library, '_create_consistency_group',
            mock.Mock(return_value=cg))
        clone_vol = self.mock_object(
            self.library, '_create_volume_from_snapshot')

        self.library.create_consistencygroup_from_src(
            fake_cg, volumes, cgsnap, snapshots, None, None)

        create_cg.assert_called_once_with(fake_cg)
        update_cg.assert_called_once_with(cg, volumes, [])
        self.assertEqual(clone_vol.call_count, len(volumes))

    @ddt.data({'consistencyGroupId': utils.NULL_REF},
              {'consistencyGroupId': None}, {'consistencyGroupId': '1'}, {})
    def test_is_cgsnapshot(self, snapshot_image):
        if snapshot_image.get('consistencyGroupId'):
            result = not (utils.NULL_REF == snapshot_image[
                'consistencyGroupId'])
        else:
            result = False

        actual = self.library._is_cgsnapshot(snapshot_image)

        self.assertEqual(result, actual)

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_copy_volume_high_priority_readonly(self):
        src_vol = copy.deepcopy(eseries_fake.VOLUME)
        dst_vol = copy.deepcopy(eseries_fake.VOLUME)
        vc = copy.deepcopy(eseries_fake.VOLUME_COPY_JOB)
        self.mock_object(self.library._client, 'create_volume_copy_job',
                         mock.Mock(return_value=vc))
        self.mock_object(self.library._client, 'list_vol_copy_job',
                         mock.Mock(return_value=vc))
        delete_copy = self.mock_object(self.library._client,
                                       'delete_vol_copy_job')

        result = self.library._copy_volume_high_priority_readonly(
            src_vol, dst_vol)

        self.assertIsNone(result)
        delete_copy.assert_called_once_with(vc['volcopyRef'])

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=
                cinder_utils.ZeroIntervalLoopingCall)
    def test_copy_volume_high_priority_readonly_job_create_failure(self):
        src_vol = copy.deepcopy(eseries_fake.VOLUME)
        dst_vol = copy.deepcopy(eseries_fake.VOLUME)
        self.mock_object(
            self.library._client, 'create_volume_copy_job', mock.Mock(
                side_effect=exception.NetAppDriverException))

        self.assertRaises(
            exception.NetAppDriverException,
            self.library._copy_volume_high_priority_readonly, src_vol,
            dst_vol)


@ddt.ddt
class NetAppEseriesLibraryMultiAttachTestCase(test.TestCase):
    """Test driver when netapp_enable_multiattach is enabled.

    Test driver behavior when the netapp_enable_multiattach configuration
    option is True.
    """

    def setUp(self):
        super(NetAppEseriesLibraryMultiAttachTestCase, self).setUp()
        config = eseries_fake.create_configuration_eseries()
        config.netapp_enable_multiattach = True

        kwargs = {'configuration': config}

        self.library = library.NetAppESeriesLibrary("FAKE", **kwargs)
        self.library._client = eseries_fake.FakeEseriesClient()

        self.mock_object(library.cinder_utils, 'synchronized',
                         mock.Mock(return_value=lambda f: f))
        self.mock_object(self.library, '_start_periodic_tasks',
                         new_attr=mock.Mock())

        self.ctxt = context.get_admin_context()

        with mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
                        new = cinder_utils.ZeroIntervalLoopingCall):
            self.library.check_for_setup_error()

    def test_do_setup_host_group_already_exists(self):
        mock_check_flags = self.mock_object(na_utils, 'check_flags')
        self.mock_object(self.library,
                         '_check_mode_get_or_register_storage_system')
        fake_rest_client = eseries_fake.FakeEseriesClient()
        self.mock_object(self.library, '_create_rest_client',
                         mock.Mock(return_value=fake_rest_client))
        mock_create = self.mock_object(fake_rest_client, 'create_host_group')

        self.library.do_setup(mock.Mock())

        self.assertTrue(mock_check_flags.called)
        self.assertFalse(mock_create.call_count)

    def test_do_setup_host_group_does_not_exist(self):
        mock_check_flags = self.mock_object(na_utils, 'check_flags')
        fake_rest_client = eseries_fake.FakeEseriesClient()
        self.mock_object(self.library, '_create_rest_client',
                         mock.Mock(return_value=fake_rest_client))
        mock_get_host_group = self.mock_object(
            fake_rest_client, "get_host_group_by_name",
            mock.Mock(side_effect=exception.NotFound))
        self.mock_object(self.library,
                         '_check_mode_get_or_register_storage_system')

        self.library.do_setup(mock.Mock())

        self.assertTrue(mock_check_flags.called)
        self.assertTrue(mock_get_host_group.call_count)

    def test_create_volume(self):
        self.library._client.create_volume = mock.Mock(
            return_value=eseries_fake.VOLUME)

        self.library.create_volume(get_fake_volume())
        self.assertTrue(self.library._client.create_volume.call_count)

    @ddt.data(('netapp_eseries_flash_read_cache', 'flash_cache', 'true'),
              ('netapp_eseries_flash_read_cache', 'flash_cache', 'false'),
              ('netapp_eseries_flash_read_cache', 'flash_cache', None),
              ('netapp_thin_provisioned', 'thin_provision', 'true'),
              ('netapp_thin_provisioned', 'thin_provision', 'false'),
              ('netapp_thin_provisioned', 'thin_provision', None),
              ('netapp_eseries_data_assurance', 'data_assurance', 'true'),
              ('netapp_eseries_data_assurance', 'data_assurance', 'false'),
              ('netapp_eseries_data_assurance', 'data_assurance', None),
              ('netapp:write_cache', 'write_cache', 'true'),
              ('netapp:write_cache', 'write_cache', 'false'),
              ('netapp:write_cache', 'write_cache', None),
              ('netapp:read_cache', 'read_cache', 'true'),
              ('netapp:read_cache', 'read_cache', 'false'),
              ('netapp:read_cache', 'read_cache', None),
              ('netapp_eseries_flash_read_cache', 'flash_cache', 'True'),
              ('netapp_eseries_flash_read_cache', 'flash_cache', '1'),
              ('netapp_eseries_data_assurance', 'data_assurance', ''))
    @ddt.unpack
    def test_create_volume_with_extra_spec(self, spec, key, value):
        fake_volume = get_fake_volume()
        extra_specs = {spec: value}
        volume = copy.deepcopy(eseries_fake.VOLUME)

        self.library._client.create_volume = mock.Mock(
            return_value=volume)
        # Make this utility method return our extra spec
        mocked_spec_method = self.mock_object(na_utils,
                                              'get_volume_extra_specs')
        mocked_spec_method.return_value = extra_specs

        self.library.create_volume(fake_volume)

        self.assertEqual(1, self.library._client.create_volume.call_count)
        # Ensure create_volume is called with the correct argument
        args, kwargs = self.library._client.create_volume.call_args
        self.assertIn(key, kwargs)
        if(value is not None):
            expected = na_utils.to_bool(value)
        else:
            expected = value
        self.assertEqual(expected, kwargs[key])

    def test_create_volume_too_many_volumes(self):
        self.library._client.list_volumes = mock.Mock(
            return_value=[eseries_fake.VOLUME for __ in
                          range(utils.MAX_LUNS_PER_HOST_GROUP + 1)])
        self.library._client.create_volume = mock.Mock(
            return_value=eseries_fake.VOLUME)

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume,
                          get_fake_volume())
        self.assertFalse(self.library._client.create_volume.call_count)

    @ddt.data(0, 1, 2)
    def test_create_snapshot(self, group_count):
        """Successful Snapshot creation test"""
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        self.library._get_volume = mock.Mock(return_value=fake_eseries_volume)
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        fake_cinder_snapshot = copy.deepcopy(
            eseries_fake.FAKE_CINDER_SNAPSHOT)
        fake_snapshot_group_list = eseries_fake.list_snapshot_groups(
            group_count)
        fake_snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        fake_snapshot_image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        self.library._client.create_snapshot_group = mock.Mock(
            return_value=fake_snapshot_group)
        self.library._client.list_snapshot_groups = mock.Mock(
            return_value=fake_snapshot_group_list)
        self.library._client.create_snapshot_image = mock.Mock(
            return_value=fake_snapshot_image)

        self.library.create_snapshot(fake_cinder_snapshot)

    @ddt.data(0, 1, 3)
    def test_create_cloned_volume(self, snapshot_group_count):
        """Test creating cloned volume with different exist group counts. """
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        self.library._get_volume = mock.Mock(return_value=fake_eseries_volume)
        fake_pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        self.library._get_storage_pools = mock.Mock(return_value=[fake_pool])
        fake_snapshot_group_list = eseries_fake.list_snapshot_groups(
            snapshot_group_count)
        self.library._client.list_snapshot_groups = mock.Mock(
            return_value=fake_snapshot_group_list)
        fake_snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        self.library._client.create_snapshot_group = mock.Mock(
            return_value=fake_snapshot_group)
        fake_snapshot_image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        self.library._client.create_snapshot_image = mock.Mock(
            return_value=fake_snapshot_image)
        self.library._get_snapshot_group_for_snapshot = mock.Mock(
            return_value=copy.deepcopy(eseries_fake.SNAPSHOT_GROUP))
        fake_created_volume = copy.deepcopy(eseries_fake.VOLUMES[1])
        self.library.create_volume_from_snapshot = mock.Mock(
            return_value = fake_created_volume)
        fake_cinder_volume = copy.deepcopy(eseries_fake.FAKE_CINDER_VOLUME)
        extend_vol = {'id': uuid.uuid4(), 'size': 10}
        self.mock_object(self.library, '_create_volume_from_snapshot')

        self.library.create_cloned_volume(extend_vol, fake_cinder_volume)

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
                new = cinder_utils.ZeroIntervalLoopingCall)
    def test_create_volume_from_snapshot(self):
        fake_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_snap = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)
        self.mock_object(self.library, "_schedule_and_create_volume",
                         mock.Mock(return_value=fake_eseries_volume))
        self.mock_object(self.library, "_get_snapshot",
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.SNAPSHOT_IMAGE)))

        self.library.create_volume_from_snapshot(
            get_fake_volume(), fake_snap)

        self.assertEqual(
            1, self.library._schedule_and_create_volume.call_count)

    def test_create_volume_from_snapshot_create_fails(self):
        fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        self.mock_object(self.library, "_schedule_and_create_volume",
                         mock.Mock(return_value=fake_dest_eseries_volume))
        self.mock_object(self.library._client, "delete_volume")
        self.mock_object(self.library._client, "delete_snapshot_volume")
        self.mock_object(self.library, "_get_snapshot",
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.SNAPSHOT_IMAGE)))
        self.mock_object(self.library._client, "create_snapshot_volume",
                         mock.Mock(
                             side_effect=exception.NetAppDriverException))

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume_from_snapshot,
                          get_fake_volume(),
                          fake_snapshot.fake_snapshot_obj(None))

        self.assertEqual(
            1, self.library._schedule_and_create_volume.call_count)
        # Ensure the volume we were going to copy to is cleaned up
        self.library._client.delete_volume.assert_called_once_with(
            fake_dest_eseries_volume['volumeRef'])

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
                new = cinder_utils.ZeroIntervalLoopingCall)
    def test_create_volume_from_snapshot_copy_job_fails(self):
        fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        self.mock_object(self.library, "_schedule_and_create_volume",
                         mock.Mock(return_value=fake_dest_eseries_volume))
        self.mock_object(self.library, "_create_snapshot_volume",
                         mock.Mock(return_value=fake_dest_eseries_volume))
        self.mock_object(self.library._client, "delete_volume")
        self.mock_object(self.library, "_get_snapshot",
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.SNAPSHOT_IMAGE)))

        fake_failed_volume_copy_job = copy.deepcopy(
            eseries_fake.VOLUME_COPY_JOB)
        fake_failed_volume_copy_job['status'] = 'failed'
        self.mock_object(self.library._client,
                         "create_volume_copy_job",
                         mock.Mock(return_value=fake_failed_volume_copy_job))
        self.mock_object(self.library._client,
                         "list_vol_copy_job",
                         mock.Mock(return_value=fake_failed_volume_copy_job))

        self.assertRaises(exception.NetAppDriverException,
                          self.library.create_volume_from_snapshot,
                          get_fake_volume(),
                          fake_snapshot.fake_snapshot_obj(None))

        self.assertEqual(
            1, self.library._schedule_and_create_volume.call_count)
        # Ensure the volume we were going to copy to is cleaned up
        self.library._client.delete_volume.assert_called_once_with(
            fake_dest_eseries_volume['volumeRef'])

    @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
                new = cinder_utils.ZeroIntervalLoopingCall)
    def test_create_volume_from_snapshot_fail_to_delete_snapshot_volume(self):
        fake_dest_eseries_volume = copy.deepcopy(eseries_fake.VOLUME)
        fake_dest_eseries_volume['volumeRef'] = 'fake_volume_ref'
        self.mock_object(self.library, "_schedule_and_create_volume",
                         mock.Mock(return_value=fake_dest_eseries_volume))
        self.mock_object(self.library, "_get_snapshot",
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.SNAPSHOT_IMAGE)))
        self.mock_object(self.library, '_create_snapshot_volume',
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.SNAPSHOT_VOLUME)))
        self.mock_object(self.library, "_create_snapshot_volume",
                         mock.Mock(return_value=copy.deepcopy(
                             eseries_fake.VOLUME)))
        self.mock_object(self.library._client, "delete_snapshot_volume",
                         mock.Mock(side_effect=exception.NetAppDriverException)
                         )
        self.mock_object(self.library._client, "delete_volume")

        self.library.create_volume_from_snapshot(
            get_fake_volume(), fake_snapshot.fake_snapshot_obj(None))

        self.assertEqual(
            1, self.library._schedule_and_create_volume.call_count)
        self.assertEqual(
            1, self.library._client.delete_snapshot_volume.call_count)
        # Ensure the volume we created is not cleaned up
        self.assertEqual(0, self.library._client.delete_volume.call_count)

    def test_create_snapshot_volume_cgsnap(self):
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        grp = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        self.mock_object(self.library, '_get_snapshot_group', mock.Mock(
            return_value=grp))
        expected = copy.deepcopy(eseries_fake.SNAPSHOT_VOLUME)
        self.mock_object(self.library, '_is_cgsnapshot', mock.Mock(
            return_value=True))
        create_view = self.mock_object(
            self.library._client, 'create_cg_snapshot_view',
            mock.Mock(return_value=expected))

        result = self.library._create_snapshot_volume(image)

        self.assertEqual(expected, result)
        create_view.assert_called_once_with(image['consistencyGroupId'],
                                            mock.ANY, image['id'])

    def test_create_snapshot_volume(self):
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        grp = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        self.mock_object(self.library, '_get_snapshot_group', mock.Mock(
            return_value=grp))
        expected = copy.deepcopy(eseries_fake.SNAPSHOT_VOLUME)
        self.mock_object(self.library, '_is_cgsnapshot', mock.Mock(
            return_value=False))
        create_view = self.mock_object(
            self.library._client, 'create_snapshot_volume',
            mock.Mock(return_value=expected))

        result = self.library._create_snapshot_volume(image)

        self.assertEqual(expected, result)
        create_view.assert_called_once_with(
            image['pitRef'], mock.ANY, image['baseVol'])

    def test_create_snapshot_group(self):
        label = 'label'

        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        get_call = self.mock_object(
            self.library, '_get_storage_pools', mock.Mock(return_value=None))
        create_call = self.mock_object(
            self.library._client, 'create_snapshot_group',
            mock.Mock(return_value=snapshot_group))

        actual = self.library._create_snapshot_group(label, vol)

        get_call.assert_not_called()
        create_call.assert_called_once_with(label, vol['id'], repo_percent=20)
        self.assertEqual(snapshot_group, actual)

    def test_create_snapshot_group_legacy_ddp(self):
        self.library._client.features.REST_1_3_RELEASE = False
        vol = copy.deepcopy(eseries_fake.VOLUME)
        pools = copy.deepcopy(eseries_fake.STORAGE_POOLS)
        pool = pools[-1]
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        vol['volumeGroupRef'] = pool['id']
        pool['raidLevel'] = 'raidDiskPool'
        get_call = self.mock_object(
            self.library, '_get_storage_pools', mock.Mock(return_value=pools))
        create_call = self.mock_object(
            self.library._client, 'create_snapshot_group',
            mock.Mock(return_value=snapshot_group))

        actual = self.library._create_snapshot_group('label', vol)

        create_call.assert_called_with('label', vol['id'],
                                       vol['volumeGroupRef'],
                                       repo_percent=mock.ANY)
        get_call.assert_called_once_with()
        self.assertEqual(snapshot_group, actual)

    def test_create_snapshot_group_legacy_vg(self):
        self.library._client.features.REST_1_3_RELEASE = False
        vol = copy.deepcopy(eseries_fake.VOLUME)
        vol_size_gb = int(vol['totalSizeInBytes']) / units.Gi
        pools = copy.deepcopy(eseries_fake.STORAGE_POOLS)
        pool = pools[0]
        pool['raidLevel'] = 'raid6'
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        vol['volumeGroupRef'] = pool['id']

        get_call = self.mock_object(
            self.library, '_get_sorted_available_storage_pools',
            mock.Mock(return_value=pools))
        self.mock_object(self.library._client, 'create_snapshot_group',
                         mock.Mock(return_value=snapshot_group))
        actual = self.library._create_snapshot_group('label', vol)

        get_call.assert_called_once_with(vol_size_gb)
        self.assertEqual(snapshot_group, actual)

    def test_get_snapshot(self):
        fake_snap = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        get_snap = self.mock_object(
            self.library._client, 'list_snapshot_image', mock.Mock(
                return_value=snap))

        result = self.library._get_snapshot(fake_snap)

        self.assertEqual(snap, result)
        get_snap.assert_called_once_with(fake_snap['provider_id'])

    def test_get_snapshot_fail(self):
        fake_snap = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)
        get_snap = self.mock_object(
            self.library._client, 'list_snapshot_image', mock.Mock(
                side_effect=exception.NotFound))

        self.assertRaises(exception.NotFound, self.library._get_snapshot,
                          fake_snap)

        get_snap.assert_called_once_with(fake_snap['provider_id'])

    def test_get_snapshot_group_for_snapshot(self):
        fake_id = 'id'
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        grp = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        get_snap = self.mock_object(
            self.library, '_get_snapshot',
            mock.Mock(return_value=snap))
        get_grp = self.mock_object(self.library._client, 'list_snapshot_group',
                                   mock.Mock(return_value=grp))

        result = self.library._get_snapshot_group_for_snapshot(fake_id)

        self.assertEqual(grp, result)
        get_grp.assert_called_once_with(snap['pitGroupRef'])
        get_snap.assert_called_once_with(fake_id)

    def test_get_snapshot_group_for_snapshot_fail(self):
        fake_id = 'id'
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        get_snap = self.mock_object(
            self.library, '_get_snapshot',
            mock.Mock(return_value=snap))
        get_grp = self.mock_object(self.library._client, 'list_snapshot_group',
                                   mock.Mock(side_effect=exception.NotFound))

        self.assertRaises(exception.NotFound,
                          self.library._get_snapshot_group_for_snapshot,
                          fake_id)

        get_grp.assert_called_once_with(snap['pitGroupRef'])
        get_snap.assert_called_once_with(fake_id)

    def test_get_snapshot_groups_for_volume(self):
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        # Generate some snapshot groups that will not match
        snapshot_groups = [copy.deepcopy(snapshot_group) for i in range(
            self.library.MAX_SNAPSHOT_GROUP_COUNT)]
        for i, group in enumerate(snapshot_groups):
            group['baseVolume'] = str(i)
        snapshot_groups.append(snapshot_group)
        get_call = self.mock_object(
            self.library._client, 'list_snapshot_groups', mock.Mock(
                return_value=snapshot_groups))

        groups = self.library._get_snapshot_groups_for_volume(vol)

        get_call.assert_called_once_with()
        self.assertEqual([snapshot_group], groups)

    def test_get_available_snapshot_group(self):
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        snapshot_group['snapshotCount'] = 0
        # Generate some snapshot groups that will not match

        reserved_group = copy.deepcopy(snapshot_group)
        reserved_group['label'] += self.library.SNAPSHOT_VOL_COPY_SUFFIX

        full_group = copy.deepcopy(snapshot_group)
        full_group['snapshotCount'] = self.library.MAX_SNAPSHOT_COUNT

        cgroup = copy.deepcopy(snapshot_group)
        cgroup['consistencyGroup'] = True

        snapshot_groups = [snapshot_group, reserved_group, full_group, cgroup]
        get_call = self.mock_object(
            self.library, '_get_snapshot_groups_for_volume', mock.Mock(
                return_value=snapshot_groups))

        group = self.library._get_available_snapshot_group(vol)

        get_call.assert_called_once_with(vol)
        self.assertEqual(snapshot_group, group)

    def test_get_snapshot_groups_for_volume_not_found(self):
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snapshot_group['baseVolume'] = vol['id']
        snapshot_group['snapshotCount'] = self.library.MAX_SNAPSHOT_COUNT
        # Generate some snapshot groups that will not match

        get_call = self.mock_object(
            self.library, '_get_snapshot_groups_for_volume', mock.Mock(
                return_value=[snapshot_group]))

        group = self.library._get_available_snapshot_group(vol)

        get_call.assert_called_once_with(vol)
        self.assertIsNone(group)

    def test_create_snapshot_available_snap_group(self):
        expected_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        expected = {'provider_id': expected_snap['id']}
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        fake_label = 'fakeName'
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        create_call = self.mock_object(
            self.library._client, 'create_snapshot_image', mock.Mock(
                return_value=expected_snap))
        self.mock_object(self.library, '_get_available_snapshot_group',
                         mock.Mock(return_value=snapshot_group))
        self.mock_object(utils, 'convert_uuid_to_es_fmt',
                         mock.Mock(return_value=fake_label))
        fake_snapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)

        model_update = self.library.create_snapshot(fake_snapshot)

        self.assertEqual(expected, model_update)
        create_call.assert_called_once_with(snapshot_group['id'])

    @ddt.data(False, True)
    def test_create_snapshot_failure(self, cleanup_failure):
        """Validate the behavior for a failure during snapshot creation"""

        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snap_vol = copy.deepcopy(eseries_fake.SNAPSHOT_VOLUME)
        fake_label = 'fakeName'
        create_fail_exc = exception.NetAppDriverException('fail_create')
        cleanup_fail_exc = exception.NetAppDriverException('volume_deletion')
        if cleanup_failure:
            exc_msg = cleanup_fail_exc.msg
            delete_snap_grp = self.mock_object(
                self.library, '_delete_snapshot_group',
                mock.Mock(side_effect=cleanup_fail_exc))
        else:
            exc_msg = create_fail_exc.msg
            delete_snap_grp = self.mock_object(
                self.library, '_delete_snapshot_group')
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        self.mock_object(self.library._client, 'create_snapshot_image',
                         mock.Mock(
                             side_effect=create_fail_exc))
        self.mock_object(self.library._client, 'create_snapshot_volume',
                         mock.Mock(return_value=snap_vol))
        self.mock_object(self.library, '_get_available_snapshot_group',
                         mock.Mock(return_value=snapshot_group))
        self.mock_object(utils, 'convert_uuid_to_es_fmt',
                         mock.Mock(return_value=fake_label))
        fake_snapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)

        self.assertRaisesRegexp(exception.NetAppDriverException,
                                exc_msg,
                                self.library.create_snapshot,
                                fake_snapshot)
        self.assertTrue(delete_snap_grp.called)

    def test_create_snapshot_no_snap_group(self):
        self.library._client.features = mock.Mock()
        expected_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        fake_label = 'fakeName'
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        create_call = self.mock_object(
            self.library._client, 'create_snapshot_image', mock.Mock(
                return_value=expected_snap))
        self.mock_object(self.library, '_get_snapshot_groups_for_volume',
                         mock.Mock(return_value=[snapshot_group]))
        self.mock_object(self.library, '_get_available_snapshot_group',
                         mock.Mock(return_value=None))
        self.mock_object(utils, 'convert_uuid_to_es_fmt',
                         mock.Mock(return_value=fake_label))
        fake_snapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)

        snapshot = self.library.create_snapshot(fake_snapshot)

        expected = {'provider_id': expected_snap['id']}
        self.assertEqual(expected, snapshot)
        create_call.assert_called_once_with(snapshot_group['id'])

    def test_create_snapshot_no_snapshot_groups_remaining(self):
        """Test the failure condition where all snap groups are allocated"""

        self.library._client.features = mock.Mock()
        expected_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snap_vol = copy.deepcopy(eseries_fake.SNAPSHOT_VOLUME)
        grp_count = (self.library.MAX_SNAPSHOT_GROUP_COUNT -
                     self.library.RESERVED_SNAPSHOT_GROUP_COUNT)
        fake_label = 'fakeName'
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        self.mock_object(self.library._client, 'create_snapshot_image',
                         mock.Mock(return_value=expected_snap))
        self.mock_object(self.library._client, 'create_snapshot_volume',
                         mock.Mock(return_value=snap_vol))
        self.mock_object(self.library, '_get_available_snapshot_group',
                         mock.Mock(return_value=None))
        self.mock_object(self.library, '_get_snapshot_groups_for_volume',
                         mock.Mock(return_value=[snapshot_group] * grp_count))
        self.mock_object(utils, 'convert_uuid_to_es_fmt',
                         mock.Mock(return_value=fake_label))
        fake_snapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)

        # Error message should contain the maximum number of supported
        # snapshots
        self.assertRaisesRegexp(exception.SnapshotLimitExceeded,
                                str(self.library.MAX_SNAPSHOT_COUNT *
                                    grp_count),
                                self.library.create_snapshot, fake_snapshot)

    def test_delete_snapshot(self):
        fake_vol = cinder_utils.create_volume(self.ctxt)
        fake_snap = cinder_utils.create_snapshot(self.ctxt, fake_vol['id'])
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        vol = copy.deepcopy(eseries_fake.VOLUME)
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        self.mock_object(self.library, '_get_snapshot', mock.Mock(
            return_value=snap))

        del_snap = self.mock_object(self.library, '_delete_es_snapshot',
                                    mock.Mock())

        self.library.delete_snapshot(fake_snap)

        del_snap.assert_called_once_with(snap)

    def test_delete_es_snapshot(self):
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snap_count = 30
        # Ensure that it's the oldest PIT
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        snapshot_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        fake_volume_refs = ['1', '2', snap['baseVol']]
        fake_snapshot_group_refs = ['3', '4', snapshot_group['id']]
        snapshots = [copy.deepcopy(snap) for i in range(snap_count)]
        bitset = na_utils.BitSet(0)
        for i, snapshot in enumerate(snapshots):
            volume_ref = fake_volume_refs[i % len(fake_volume_refs)]
            group_ref = fake_snapshot_group_refs[i %
                                                 len(fake_snapshot_group_refs)]
            snapshot['pitGroupRef'] = group_ref
            snapshot['baseVol'] = volume_ref
            snapshot['pitSequenceNumber'] = str(i)
            snapshot['id'] = i
            bitset.set(i)
        snapshots.append(snap)

        filtered_snaps = list(filter(lambda x: x['pitGroupRef'] == snap[
            'pitGroupRef'], snapshots))

        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        self.mock_object(self.library, '_get_snapshot', mock.Mock(
            return_value=snap))
        self.mock_object(self.library, '_get_soft_delete_map', mock.Mock(
            return_value={snap['pitGroupRef']: repr(bitset)}))
        self.mock_object(self.library._client, 'list_snapshot_images',
                         mock.Mock(return_value=snapshots))
        delete_image = self.mock_object(
            self.library, '_cleanup_snapshot_images',
            mock.Mock(return_value=({snap['pitGroupRef']: repr(bitset)},
                                    None)))

        self.library._delete_es_snapshot(snap)

        delete_image.assert_called_once_with(filtered_snaps, bitset)

    def test_delete_snapshot_oldest(self):
        vol = copy.deepcopy(eseries_fake.VOLUME)
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        snapshots = [snap]
        self.mock_object(self.library, '_get_volume', mock.Mock(
            return_value=vol))
        self.mock_object(self.library, '_get_snapshot', mock.Mock(
            return_value=snap))
        self.mock_object(self.library, '_get_soft_delete_map', mock.Mock(
            return_value={}))
        self.mock_object(self.library._client, 'list_snapshot_images',
                         mock.Mock(return_value=snapshots))
        delete_image = self.mock_object(
            self.library, '_cleanup_snapshot_images',
            mock.Mock(return_value=(None, [snap['pitGroupRef']])))

        self.library._delete_es_snapshot(snap)

        delete_image.assert_called_once_with(snapshots,
                                             na_utils.BitSet(1))

    def test_get_soft_delete_map(self):
        fake_val = 'fake'
        self.mock_object(self.library._client, 'list_backend_store', mock.Mock(
            return_value=fake_val))

        actual = self.library._get_soft_delete_map()

        self.assertEqual(fake_val, actual)

    def test_cleanup_snapshot_images_delete_all(self):
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        images = [image] * 32
        bitset = na_utils.BitSet()
        for i, image in enumerate(images):
            image['pitSequenceNumber'] = i
            bitset.set(i)
        delete_grp = self.mock_object(self.library._client,
                                      'delete_snapshot_group')

        updt, keys = self.library._cleanup_snapshot_images(
            images, bitset)

        delete_grp.assert_called_once_with(image['pitGroupRef'])
        self.assertIsNone(updt)
        self.assertEqual([image['pitGroupRef']], keys)

    def test_cleanup_snapshot_images_delete_all_fail(self):
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        bitset = na_utils.BitSet(2 ** 32 - 1)
        delete_grp = self.mock_object(
            self.library._client, 'delete_snapshot_group',
            mock.Mock(side_effect=exception.NetAppDriverException))

        updt, keys = self.library._cleanup_snapshot_images(
            [image], bitset)

        delete_grp.assert_called_once_with(image['pitGroupRef'])
        self.assertIsNone(updt)
        self.assertEqual([image['pitGroupRef']], keys)

    def test_cleanup_snapshot_images(self):
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        images = [image] * 32
        del_count = 16
        bitset = na_utils.BitSet()
        for i, image in enumerate(images):
            image['pitSequenceNumber'] = i
            if i < del_count:
                bitset.set(i)
        exp_bitset = copy.deepcopy(bitset)
        exp_bitset >>= 16
        delete_img = self.mock_object(
            self.library, '_delete_snapshot_image')

        updt, keys = self.library._cleanup_snapshot_images(
            images, bitset)

        self.assertEqual(del_count, delete_img.call_count)
        self.assertIsNone(keys)
        self.assertEqual({image['pitGroupRef']: exp_bitset}, updt)

    def test_delete_snapshot_image(self):
        snap_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)

        self.mock_object(self.library._client, 'list_snapshot_group',
                         mock.Mock(return_value=snap_group))

        self.library._delete_snapshot_image(snap)

    def test_delete_snapshot_image_fail_cleanup(self):
        snap_group = copy.deepcopy(eseries_fake.SNAPSHOT_GROUP)
        snap_group['snapshotCount'] = 0
        snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)

        self.mock_object(self.library._client, 'list_snapshot_group',
                         mock.Mock(return_value=snap_group))

        self.library._delete_snapshot_image(snap)

    def test_delete_snapshot_not_found(self):
        fake_snapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)
        get_snap = self.mock_object(self.library, '_get_snapshot',
                                    mock.Mock(side_effect=exception.NotFound))

        with mock.patch.object(library, 'LOG', mock.Mock()):
            self.library.delete_snapshot(fake_snapshot)
            get_snap.assert_called_once_with(fake_snapshot)
            self.assertTrue(library.LOG.warning.called)

    @ddt.data(['key1', 'key2'], [], None)
    def test_merge_soft_delete_changes_keys(self, keys_to_del):
        count = len(keys_to_del) if keys_to_del is not None else 0
        save_store = self.mock_object(
            self.library._client, 'save_backend_store')
        index = {'key1': 'val'}
        get_store = self.mock_object(self.library, '_get_soft_delete_map',
                                     mock.Mock(return_value=index))

        self.library._merge_soft_delete_changes(None, keys_to_del)

        if count:
            expected = copy.deepcopy(index)
            for key in keys_to_del:
                expected.pop(key, None)
            get_store.assert_called_once_with()
            save_store.assert_called_once_with(
                self.library.SNAPSHOT_PERSISTENT_STORE_KEY, expected)
        else:
            get_store.assert_not_called()
            save_store.assert_not_called()

    def test_create_cgsnapshot(self):
        fake_cgsnapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        fake_vol = cinder_utils.create_volume(self.ctxt)
        fake_snapshots = [cinder_utils.create_snapshot(self.ctxt,
                                                       fake_vol['id'])]
        vol = copy.deepcopy(eseries_fake.VOLUME)
        image = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        image['baseVol'] = vol['id']
        cg_snaps = [image]
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)

        for snap in cg_snaps:
            snap['baseVol'] = vol['id']
        get_cg = self.mock_object(
            self.library, '_get_consistencygroup_by_name',
            mock.Mock(return_value=cg))
        get_vol = self.mock_object(
            self.library, '_get_volume',
            mock.Mock(return_value=vol))
        mk_snap = self.mock_object(
            self.library._client, 'create_consistency_group_snapshot',
            mock.Mock(return_value=cg_snaps))

        model_update, snap_updt = self.library.create_cgsnapshot(
            fake_cgsnapshot, fake_snapshots)

        self.assertIsNone(model_update)
        for snap in cg_snaps:
            self.assertIn({'id': fake_snapshots[0]['id'],
                           'provider_id': snap['id'],
                           'status': 'available'}, snap_updt)
        self.assertEqual(len(cg_snaps), len(snap_updt))

        get_cg.assert_called_once_with(utils.convert_uuid_to_es_fmt(
            fake_cgsnapshot['consistencygroup_id']))
        self.assertEqual(get_vol.call_count, len(fake_snapshots))
        mk_snap.assert_called_once_with(cg['id'])

    def test_create_cgsnapshot_cg_fail(self):
        fake_cgsnapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        fake_snapshots = [copy.deepcopy(eseries_fake.FAKE_CINDER_SNAPSHOT)]
        self.mock_object(
            self.library, '_get_consistencygroup_by_name',
            mock.Mock(side_effect=exception.NetAppDriverException))

        self.assertRaises(
            exception.NetAppDriverException,
            self.library.create_cgsnapshot, fake_cgsnapshot, fake_snapshots)

    def test_delete_cgsnapshot(self):
        """Test the deletion of a cgsnapshot when a soft delete is required"""
        fake_cgsnapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        fake_vol = cinder_utils.create_volume(self.ctxt)
        fake_snapshots = [cinder_utils.create_snapshot(
            self.ctxt, fake_vol['id'])]
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        cg_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        # Ensure that the snapshot to be deleted is not the oldest
        cg_snap['pitSequenceNumber'] = str(max(cg['uniqueSequenceNumber']))
        cg_snaps = [cg_snap]
        for snap in fake_snapshots:
            snap['provider_id'] = cg_snap['id']
        vol = copy.deepcopy(eseries_fake.VOLUME)
        for snap in cg_snaps:
            snap['baseVol'] = vol['id']
        get_cg = self.mock_object(
            self.library, '_get_consistencygroup_by_name',
            mock.Mock(return_value=cg))
        self.mock_object(
            self.library._client, 'delete_consistency_group_snapshot')
        self.mock_object(
            self.library._client, 'get_consistency_group_snapshots',
            mock.Mock(return_value=cg_snaps))
        soft_del = self.mock_object(
            self.library, '_soft_delete_cgsnapshot',
            mock.Mock(return_value=(None, None)))

        # Mock the locking mechanism
        model_update, snap_updt = self.library.delete_cgsnapshot(
            fake_cgsnapshot, fake_snapshots)

        self.assertIsNone(model_update)
        self.assertIsNone(snap_updt)
        get_cg.assert_called_once_with(utils.convert_uuid_to_es_fmt(
            fake_cgsnapshot['consistencygroup_id']))
        soft_del.assert_called_once_with(
            cg, cg_snap['pitSequenceNumber'])

    @ddt.data(True, False)
    def test_soft_delete_cgsnapshot(self, bitset_exists):
        """Test the soft deletion of a cgsnapshot"""
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        cg_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        seq_num = 10
        cg_snap['pitSequenceNumber'] = seq_num
        cg_snaps = [cg_snap]
        self.mock_object(
            self.library._client, 'delete_consistency_group_snapshot')
        self.mock_object(
            self.library._client, 'get_consistency_group_snapshots',
            mock.Mock(return_value=cg_snaps))
        bitset = na_utils.BitSet(1)
        index = {cg['id']: repr(bitset)} if bitset_exists else {}
        bitset >>= len(cg_snaps)
        updt = {cg['id']: repr(bitset)}
        self.mock_object(self.library, '_get_soft_delete_map', mock.Mock(
            return_value=index))
        save_map = self.mock_object(
            self.library, '_merge_soft_delete_changes')

        model_update, snap_updt = self.library._soft_delete_cgsnapshot(
            cg, seq_num)

        self.assertIsNone(model_update)
        self.assertIsNone(snap_updt)
        save_map.assert_called_once_with(updt, None)

    def test_delete_cgsnapshot_single(self):
        """Test the backend deletion of the oldest cgsnapshot"""
        fake_cgsnapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        fake_vol = cinder_utils.create_volume(self.ctxt)
        fake_snapshots = [cinder_utils.create_snapshot(self.ctxt,
                                                       fake_vol['id'])]
        cg_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        cg_snaps = [cg_snap]
        for snap in fake_snapshots:
            snap['provider_id'] = cg_snap['id']
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        cg['uniqueSequenceNumber'] = [cg_snap['pitSequenceNumber']]
        vol = copy.deepcopy(eseries_fake.VOLUME)
        for snap in cg_snaps:
            snap['baseVol'] = vol['id']
        get_cg = self.mock_object(
            self.library, '_get_consistencygroup_by_name',
            mock.Mock(return_value=cg))
        del_snap = self.mock_object(
            self.library._client, 'delete_consistency_group_snapshot',
            mock.Mock(return_value=cg_snaps))

        model_update, snap_updt = self.library.delete_cgsnapshot(
            fake_cgsnapshot, fake_snapshots)

        self.assertIsNone(model_update)
        self.assertIsNone(snap_updt)
        get_cg.assert_called_once_with(utils.convert_uuid_to_es_fmt(
            fake_cgsnapshot['consistencygroup_id']))
        del_snap.assert_called_once_with(cg['id'], cg_snap[
            'pitSequenceNumber'])

    def test_delete_cgsnapshot_snap_not_found(self):
        fake_cgsnapshot = copy.deepcopy(eseries_fake.FAKE_CINDER_CG_SNAPSHOT)
        fake_vol = cinder_utils.create_volume(self.ctxt)
        fake_snapshots = [cinder_utils.create_snapshot(
            self.ctxt, fake_vol['id'])]
        cg_snap = copy.deepcopy(eseries_fake.SNAPSHOT_IMAGE)
        cg_snaps = [cg_snap]
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        self.mock_object(self.library, '_get_consistencygroup_by_name',
                         mock.Mock(return_value=cg))
        self.mock_object(
            self.library._client, 'delete_consistency_group_snapshot',
            mock.Mock(return_value=cg_snaps))

        self.assertRaises(
            exception.CgSnapshotNotFound,
            self.library.delete_cgsnapshot, fake_cgsnapshot, fake_snapshots)

    @ddt.data(0, 1, 10, 32)
    def test_cleanup_cg_snapshots(self, count):
        # Set the soft delete bit for 'count' snapshot images
        bitset = na_utils.BitSet()
        for i in range(count):
            bitset.set(i)
        cg = copy.deepcopy(eseries_fake.FAKE_CONSISTENCY_GROUP)
        # Define 32 snapshots for the CG
        cg['uniqueSequenceNumber'] = list(range(32))
        cg_id = cg['id']
        del_snap = self.mock_object(
            self.library._client, 'delete_consistency_group_snapshot')
        expected_bitset = copy.deepcopy(bitset) >> count
        expected_updt = {cg_id: repr(expected_bitset)}

        updt = self.library._cleanup_cg_snapshots(
            cg_id, cg['uniqueSequenceNumber'], bitset)

        self.assertEqual(count, del_snap.call_count)
        self.assertEqual(expected_updt, updt)

    @ddt.data(False, True)
    def test_get_pool_operation_progress(self, expect_complete):
        """Validate the operation progress is interpreted correctly"""

        pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        if expect_complete:
            pool_progress = []
        else:
            pool_progress = copy.deepcopy(
                eseries_fake.FAKE_POOL_ACTION_PROGRESS)

        expected_actions = set(action['currentAction'] for action in
                               pool_progress)
        expected_eta = reduce(lambda x, y: x + y['estimatedTimeToCompletion'],
                              pool_progress, 0)

        self.library._client.get_pool_operation_progress = mock.Mock(
            return_value=pool_progress)

        complete, actions, eta = self.library._get_pool_operation_progress(
            pool['id'])
        self.assertEqual(expect_complete, complete)
        self.assertEqual(expected_actions, actions)
        self.assertEqual(expected_eta, eta)

    @ddt.data(False, True)
    def test_get_pool_operation_progress_with_action(self, expect_complete):
        """Validate the operation progress is interpreted correctly"""

        expected_action = 'fakeAction'
        pool = copy.deepcopy(eseries_fake.STORAGE_POOL)
        if expect_complete:
            pool_progress = copy.deepcopy(
                eseries_fake.FAKE_POOL_ACTION_PROGRESS)
            for progress in pool_progress:
                progress['currentAction'] = 'none'
        else:
            pool_progress = copy.deepcopy(
                eseries_fake.FAKE_POOL_ACTION_PROGRESS)
            pool_progress[0]['currentAction'] = expected_action

        expected_actions = set(action['currentAction'] for action in
                               pool_progress)
        expected_eta = reduce(lambda x, y: x + y['estimatedTimeToCompletion'],
                              pool_progress, 0)

        self.library._client.get_pool_operation_progress = mock.Mock(
            return_value=pool_progress)

        complete, actions, eta = self.library._get_pool_operation_progress(
            pool['id'], expected_action)
        self.assertEqual(expect_complete, complete)
        self.assertEqual(expected_actions, actions)
        self.assertEqual(expected_eta, eta)

    @mock.patch('eventlet.greenthread.sleep')
    def test_extend_volume(self, _mock_sleep):
        """Test volume extend with a thick-provisioned volume"""

        def get_copy_progress():
            for eta in range(5, -1, -1):
                action_status = 'none' if eta == 0 else 'remappingDve'
                complete = action_status == 'none'
                yield complete, action_status, eta

        fake_volume = copy.deepcopy(get_fake_volume())
        volume = copy.deepcopy(eseries_fake.VOLUME)
        new_capacity = 10
        volume['objectType'] = 'volume'
        self.library._client.expand_volume = mock.Mock()
        self.library._get_pool_operation_progress = mock.Mock(
            side_effect=get_copy_progress())
        self.library._get_volume = mock.Mock(return_value=volume)

        self.library.extend_volume(fake_volume, new_capacity)

        # Ensure that the extend method waits until the expansion is completed
        self.assertEqual(6,
                         self.library._get_pool_operation_progress.call_count
                         )
        self.library._client.expand_volume.assert_called_with(volume['id'],
                                                              new_capacity,
                                                              False)

    def test_extend_volume_thin(self):
        """Test volume extend with a thin-provisioned volume"""

        fake_volume = copy.deepcopy(get_fake_volume())
        volume = copy.deepcopy(eseries_fake.VOLUME)
        new_capacity = 10
        volume['objectType'] = 'thinVolume'
        self.library._client.expand_volume = mock.Mock(return_value=volume)
        self.library._get_volume_operation_progress = mock.Mock()
        self.library._get_volume = mock.Mock(return_value=volume)

        self.library.extend_volume(fake_volume, new_capacity)

        self.assertFalse(self.library._get_volume_operation_progress.called)
        self.library._client.expand_volume.assert_called_with(volume['id'],
                                                              new_capacity,
                                                              True)

    def test_delete_non_existing_volume(self):
        volume2 = get_fake_volume()
        # Change to a nonexistent id.
        volume2['name_id'] = '88888888-4444-4444-4444-cccccccccccc'
        self.assertIsNone(self.library.delete_volume(volume2))

    def test_map_volume_to_host_volume_not_mapped(self):
        """Map the volume directly to destination host."""
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))

        self.library.map_volume_to_host(get_fake_volume(),
                                        eseries_fake.VOLUME,
                                        eseries_fake.INITIATOR_NAME_2)

        self.assertTrue(
            self.library._client.get_volume_mappings_for_volume.called)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_map_volume_to_host_volume_not_mapped_host_does_not_exist(self):
        """Should create the host map directly to the host."""
        self.mock_object(self.library._client, 'list_hosts',
                         mock.Mock(return_value=[]))
        self.mock_object(self.library._client, 'create_host_with_ports',
                         mock.Mock(
                             return_value=eseries_fake.HOST_2))
        self.mock_object(self.library._client,
                         'get_volume_mappings_for_volume',
                         mock.Mock(return_value=[]))
        self.mock_object(host_mapper, 'map_volume_to_single_host',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))

        self.library.map_volume_to_host(get_fake_volume(),
                                        eseries_fake.VOLUME,
                                        eseries_fake.INITIATOR_NAME_2)

        self.assertTrue(self.library._client.create_host_with_ports.called)
        self.assertTrue(
            self.library._client.get_volume_mappings_for_volume.called)
        self.assertTrue(host_mapper.map_volume_to_single_host.called)

    def test_map_volume_to_host_volume_already_mapped(self):
        """Should be a no-op."""
        self.mock_object(host_mapper, 'map_volume_to_multiple_hosts',
                         mock.Mock(
                             return_value=eseries_fake.VOLUME_MAPPING))

        self.library.map_volume_to_host(get_fake_volume(),
                                        eseries_fake.VOLUME,
                                        eseries_fake.INITIATOR_NAME)

        self.assertTrue(host_mapper.map_volume_to_multiple_hosts.called)
