# Copyright (c) 2014 Alex Meade.  All rights reserved.
# Copyright (c) 2014 Clinton Knight.  All rights reserved.
# Copyright (c) 2014 Andrew Kerr.  All rights reserved.
# Copyright (c) 2015 Tom Barron.  All rights reserved.
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# Copyright (c) 2016 Chuck Fouts. All rights reserved.
# 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.
"""Mock unit tests for the NetApp block storage library"""
from concurrent.futures import ThreadPoolExecutor
import copy
import itertools
from unittest import mock
import uuid

import ddt
from oslo_log import versionutils
from oslo_utils import units

from cinder import context
from cinder import exception
from cinder.objects import fields
from cinder.tests.unit import fake_volume
from cinder.tests.unit import test
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
from cinder.volume.drivers.netapp.dataontap import block_base
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.utils import loopingcalls
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import volume_utils


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

    def setUp(self):
        super(NetAppBlockStorageLibraryTestCase, self).setUp()

        kwargs = {
            'configuration': self.get_config_base(),
            'host': 'openstack@netappblock',
        }
        self.library = block_base.NetAppBlockStorageLibrary(
            'driver', 'protocol', **kwargs)
        self.library.zapi_client = mock.Mock()
        self.zapi_client = self.library.zapi_client
        self.mock_request = mock.Mock()
        self.ctxt = context.RequestContext('fake', 'fake', auth_token=True)

    def get_config_base(self):
        return na_fakes.create_configuration()

    @mock.patch.object(versionutils, 'report_deprecated_feature')
    def test_get_reserved_percentage_default_multipler(self, mock_report):

        default = 1.2
        reserved_percentage = 20.0
        self.library.configuration.netapp_size_multiplier = default
        self.library.configuration.reserved_percentage = reserved_percentage

        result = self.library._get_reserved_percentage()

        self.assertEqual(reserved_percentage, result)
        self.assertFalse(mock_report.called)

    @mock.patch.object(versionutils, 'report_deprecated_feature')
    def test_get_reserved_percentage(self, mock_report):

        multiplier = 2.0
        self.library.configuration.netapp_size_multiplier = multiplier

        result = self.library._get_reserved_percentage()

        reserved_ratio = round(1 - (1 / multiplier), 2)
        reserved_percentage = 100 * int(reserved_ratio)

        self.assertEqual(reserved_percentage, result)
        msg = ('The "netapp_size_multiplier" configuration option is '
               'deprecated and will be removed in the Mitaka release. '
               'Please set "reserved_percentage = %d" instead.' %
               result)
        mock_report.assert_called_once_with(block_base.LOG, msg)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr',
                       mock.Mock(return_value={'Volume': 'FAKE_CMODE_VOL1'}))
    def test_get_pool(self):
        pool = self.library.get_pool({'name': 'volume-fake-uuid'})
        self.assertEqual('FAKE_CMODE_VOL1', pool)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr',
                       mock.Mock(return_value=None))
    def test_get_pool_no_metadata(self):
        pool = self.library.get_pool({'name': 'volume-fake-uuid'})
        self.assertIsNone(pool)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr',
                       mock.Mock(return_value=dict()))
    def test_get_pool_volume_unknown(self):
        pool = self.library.get_pool({'name': 'volume-fake-uuid'})
        self.assertIsNone(pool)

    def test_create_volume(self):
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={})
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')
        self.zapi_client.get_ontap_version.return_value = (9, 14, 0)

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA,
            fake.QOS_POLICY_GROUP_NAME, False)
        self.library._get_volume_model_update.assert_called_once_with(
            fake.VOLUME)
        self.assertEqual(
            0, self.library. _mark_qos_policy_group_for_deletion.call_count)
        self.assertEqual(0, block_base.LOG.error.call_count)

    def test_create_volume_asar2(self):
        self.library.configuration.netapp_disaggregated_platform = True
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={})
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')
        self.zapi_client.get_ontap_version.return_value = (9, 14, 0)

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA_ASAR2,
            fake.QOS_POLICY_GROUP_NAME, False)
        self.library._get_volume_model_update.assert_called_once_with(
            fake.VOLUME)
        self.assertEqual(
            0, self.library. _mark_qos_policy_group_for_deletion.call_count)
        self.assertEqual(0, block_base.LOG.error.call_count)

    def test_create_volume_space_allocation_extra_spec_false(self):
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={
                             'netapp:space_allocation': '<is> False'
                         }
                         )
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA,
            fake.QOS_POLICY_GROUP_NAME, False)
        self.library._get_volume_model_update.assert_called_once_with(
            fake.VOLUME)
        self.assertEqual(
            0, self.library._mark_qos_policy_group_for_deletion.call_count)
        self.assertEqual(0, block_base.LOG.error.call_count)

    def test_create_volume_space_allocation_extra_spec_true(self):
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={
                             'netapp:space_allocation': '<is> True'
                         }
                         )
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA_WITH_SPACE_ALLOCATION,
            fake.QOS_POLICY_GROUP_NAME, False)
        self.library._get_volume_model_update.assert_called_once_with(
            fake.VOLUME)
        self.assertEqual(
            0, self.library._mark_qos_policy_group_for_deletion.call_count)
        self.assertEqual(0, block_base.LOG.error.call_count)

    def test_create_volume_space_allocation_ontap_pre_9_15_1(self):
        """No extra spec + ONTAP < 9.15.1 → space_allocation disabled."""
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={})
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')
        self.zapi_client.get_ontap_version.return_value = (9, 14, 0)

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA,
            fake.QOS_POLICY_GROUP_NAME, False)

    def test_create_volume_space_allocation_ontap_9_15_1(self):
        """No extra spec + ONTAP >= 9.15.1 → space_allocation enabled."""
        volume_size_in_bytes = int(fake.SIZE) * units.Gi
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={})
        self.mock_object(na_utils, 'log_extra_spec_warnings')
        self.mock_object(block_base, 'LOG')
        self.mock_object(volume_utils, 'extract_host',
                         return_value=fake.POOL_NAME)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun')
        self.mock_object(self.library, '_create_lun_handle')
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update')
        self.zapi_client.get_ontap_version.return_value = (9, 15, 1)

        self.library.create_volume(fake.VOLUME)

        self.library._create_lun.assert_called_once_with(
            fake.POOL_NAME, fake.LUN_NAME, volume_size_in_bytes,
            fake.LUN_METADATA_WITH_SPACE_ALLOCATION,
            fake.QOS_POLICY_GROUP_NAME, False)

    def test_create_volume_no_pool(self):
        self.mock_object(volume_utils, 'extract_host', return_value=None)

        self.assertRaises(exception.InvalidHost, self.library.create_volume,
                          fake.VOLUME)

    def test_space_allocation_exception_path(self):
        self.mock_object(block_base, 'LOG')
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={'netapp:space_allocation': 'xyz'})
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun', side_effect=Exception)
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.create_volume, fake.VOLUME)

    def test_create_volume_exception_path(self):
        self.mock_object(block_base, 'LOG')
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value={})
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_create_lun', side_effect=Exception)
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.zapi_client.get_ontap_version.return_value = (9, 14, 0)

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.create_volume, fake.VOLUME)

        self.assertEqual(1, self.library.
                         _mark_qos_policy_group_for_deletion.call_count)
        self.assertEqual(1, block_base.LOG.exception.call_count)

    def test_create_volume_no_pool_provided_by_scheduler(self):
        volume_copy = copy.deepcopy(fake.VOLUME)
        # Set up fake volume whose 'host' field is missing pool information.
        volume_copy['host'] = '%s@%s' % (fake.HOST_NAME, fake.BACKEND_NAME)

        self.assertRaises(exception.InvalidHost, self.library.create_volume,
                          volume_copy)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    def test_map_lun(self, mock_get_or_create_igroup, mock_get_lun_attr):
        os = 'linux'
        protocol = 'fcp'
        self.library.host_type = 'linux'
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        self.zapi_client.map_lun.return_value = '1'
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=False)
        lun_id = self.library._map_lun('fake_volume',
                                       fake.FC_FORMATTED_INITIATORS,
                                       protocol, None)
        self.assertEqual('1', lun_id)
        mock_get_or_create_igroup.assert_called_once_with(
            fake.FC_FORMATTED_INITIATORS, protocol, os)
        self.zapi_client.map_lun.assert_called_once_with(
            fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    def test_map_lun_active_sync(self, mock_get_or_create_igroup,
                                 mock_get_lun_attr):
        """Test _map_lun maps LUN on both source and dest with _dst path."""
        os = 'linux'
        protocol = 'fcp'
        self.library.host_type = 'linux'
        self.library.dest_zapi_client = mock.Mock()
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        self.zapi_client.map_lun.return_value = '1'
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=True)
        self.mock_object(self.library, '_is_consistent_replication_enabled',
                         return_value=True)

        lun_id = self.library._map_lun('fake_volume',
                                       fake.FC_FORMATTED_INITIATORS,
                                       protocol, None)

        self.assertEqual('1', lun_id)
        self.zapi_client.map_lun.assert_called_once_with(
            fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)
        # Destination path should have _dst suffix on the FlexVol name
        # /vol/vol0/lun -> /vol/vol0_dst/lun
        expected_dest_path = fake.LUN_PATH.replace('/vol/vol0/',
                                                   '/vol/vol0_dst/')
        self.library.dest_zapi_client.map_lun.assert_called_once_with(
            expected_dest_path, fake.IGROUP1_NAME, lun_id=None)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    def test_map_lun_active_sync_no_consistent_replication(
            self, mock_get_or_create_igroup, mock_get_lun_attr):
        """Test _map_lun uses same path when consistent replication off."""
        os = 'linux'
        protocol = 'fcp'
        self.library.host_type = 'linux'
        self.library.dest_zapi_client = mock.Mock()
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        self.zapi_client.map_lun.return_value = '1'
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=True)
        self.mock_object(self.library, '_is_consistent_replication_enabled',
                         return_value=False)

        lun_id = self.library._map_lun('fake_volume',
                                       fake.FC_FORMATTED_INITIATORS,
                                       protocol, None)

        self.assertEqual('1', lun_id)
        # Destination path should be same as source (no _dst suffix)
        self.library.dest_zapi_client.map_lun.assert_called_once_with(
            fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    @mock.patch.object(block_base, 'LOG', mock.Mock())
    def test_map_lun_mismatch_host_os(
            self, mock_get_or_create_igroup, mock_get_lun_attr):
        os = 'windows'
        protocol = 'fcp'
        self.library.host_type = 'linux'
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=False)
        self.library._map_lun('fake_volume',
                              fake.FC_FORMATTED_INITIATORS,
                              protocol, None)
        mock_get_or_create_igroup.assert_called_once_with(
            fake.FC_FORMATTED_INITIATORS, protocol,
            self.library.host_type)
        self.zapi_client.map_lun.assert_called_once_with(
            fake.LUN_PATH, fake.IGROUP1_NAME, lun_id=None)
        self.assertEqual(1, block_base.LOG.warning.call_count)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_map_lun_preexisting(self, mock_find_mapped_lun_igroup,
                                 mock_get_or_create_igroup, mock_get_lun_attr):
        os = 'linux'
        protocol = 'fcp'
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        mock_find_mapped_lun_igroup.return_value = (fake.IGROUP1_NAME, '2')
        self.zapi_client.map_lun.side_effect = netapp_api.NaApiError
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=False)
        lun_id = self.library._map_lun(
            'fake_volume', fake.FC_FORMATTED_INITIATORS, protocol, None)

        self.assertEqual('2', lun_id)
        mock_find_mapped_lun_igroup.assert_called_once_with(
            fake.LUN_PATH, fake.FC_FORMATTED_INITIATORS)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_or_create_igroup')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_map_lun_api_error(self, mock_find_mapped_lun_igroup,
                               mock_get_or_create_igroup, mock_get_lun_attr):
        os = 'linux'
        protocol = 'fcp'
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH, 'OsType': os}
        mock_get_or_create_igroup.return_value = (fake.IGROUP1_NAME, os,
                                                  'iscsi')
        mock_find_mapped_lun_igroup.return_value = (None, None)
        self.zapi_client.map_lun.side_effect = netapp_api.NaApiError

        self.assertRaises(netapp_api.NaApiError, self.library._map_lun,
                          'fake_volume', fake.FC_FORMATTED_INITIATORS,
                          protocol, None)

    def test__is_multiattached_true(self):
        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            fake.test_iscsi_attachment,
            fake.test_iscsi_attachment,
        ]

        self.assertTrue(self.library._is_multiattached(
            volume,
            fake.ISCSI_CONNECTOR))

    def test__is_multiattached_false(self):
        volume1 = copy.deepcopy(fake.test_volume)
        volume1.multiattach = True
        volume1.volume_attachment = []
        volume2 = copy.deepcopy(fake.test_volume)
        volume2.multiattach = False
        volume2.volume_attachment = []

        self.assertFalse(self.library._is_multiattached(
            volume1,
            fake.ISCSI_CONNECTOR))
        self.assertFalse(self.library._is_multiattached(
            volume2,
            fake.ISCSI_CONNECTOR))

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_unmap_lun_empty(self, mock_find_mapped_lun_igroup):
        self.zapi_client.get_lun_map.return_value = fake.ISCSI_ONE_MAP_LIST

        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_EMPTY_MAP_LIST)

        mock_find_mapped_lun_igroup.assert_not_called()
        self.zapi_client.get_lun_map.assert_called_once_with(fake.LUN_PATH)
        self.zapi_client.unmap_lun.assert_called_once_with(
            fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST[0]['initiator-group'])

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_unmap_lun_detach_one(self, mock_find_mapped_lun_igroup):
        fake_ini_group = fake.ISCSI_ONE_MAP_LIST[0]['initiator-group']
        mock_find_mapped_lun_igroup.return_value = (fake_ini_group, 1)
        self.zapi_client.get_lun_map.return_value = fake.ISCSI_ONE_MAP_LIST
        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)

        mock_find_mapped_lun_igroup.assert_called_once_with(
            fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)
        self.zapi_client.get_lun_map.assert_not_called()
        self.zapi_client.unmap_lun.assert_called_once_with(
            fake.LUN_PATH, fake_ini_group)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_unmap_lun_active_sync(self, mock_find_mapped_lun_igroup):
        """Test _unmap_lun unmaps LUN on both source and dest with _dst."""
        fake_ini_group = fake.ISCSI_ONE_MAP_LIST[0]['initiator-group']
        mock_find_mapped_lun_igroup.return_value = (fake_ini_group, 1)
        self.library.dest_zapi_client = mock.Mock()
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=True)
        self.mock_object(self.library, '_is_consistent_replication_enabled',
                         return_value=True)
        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)

        self.zapi_client.unmap_lun.assert_called_once_with(
            fake.LUN_PATH, fake_ini_group)
        # Destination path should have _dst suffix on the FlexVol name
        expected_dest_path = fake.LUN_PATH.replace('/vol/vol0/',
                                                   '/vol/vol0_dst/')
        self.library.dest_zapi_client.unmap_lun.assert_called_once_with(
            expected_dest_path, fake_ini_group)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_unmap_lun_active_sync_no_consistent_replication(
            self, mock_find_mapped_lun_igroup):
        """Test _unmap_lun uses same path when consistent replication off."""
        fake_ini_group = fake.ISCSI_ONE_MAP_LIST[0]['initiator-group']
        mock_find_mapped_lun_igroup.return_value = (fake_ini_group, 1)
        self.library.dest_zapi_client = mock.Mock()
        self.mock_object(self.library, '_is_active_sync_configured',
                         return_value=True)
        self.mock_object(self.library, '_is_consistent_replication_enabled',
                         return_value=False)
        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)

        self.zapi_client.unmap_lun.assert_called_once_with(
            fake.LUN_PATH, fake_ini_group)
        # Destination path should be same as source (no _dst suffix)
        self.library.dest_zapi_client.unmap_lun.assert_called_once_with(
            fake.LUN_PATH, fake_ini_group)

    @mock.patch.object(block_base, 'LOG')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup', return_value=(None, None))
    def test_unmap_lun_not_mapped(self, mock_find_mapped_lun_igroup, mock_log):

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)

        mock_find_mapped_lun_igroup.assert_called_once_with(
            fake.LUN_PATH, fake.ISCSI_ONE_MAP_LIST)
        mock_log.warning.assert_called()
        self.zapi_client.get_lun_map.assert_not_called()
        self.zapi_client.unmap_lun.assert_not_called()

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_find_mapped_lun_igroup')
    def test_unmap_lun_empty_detach_all(self, mock_find_mapped_lun_igroup):
        self.zapi_client.get_lun_map.return_value = fake.ISCSI_MULTI_MAP_LIST

        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        self.library._unmap_lun(fake.LUN_PATH, fake.ISCSI_EMPTY_MAP_LIST)

        mock_find_mapped_lun_igroup.assert_not_called()
        self.zapi_client.get_lun_map.assert_called_once_with(fake.LUN_PATH)
        calls = [mock.call(fake.LUN_PATH,
                           fake.ISCSI_MULTI_MAP_LIST[0]['initiator-group']),
                 mock.call(fake.LUN_PATH,
                           fake.ISCSI_MULTI_MAP_LIST[1]['initiator-group'])]
        self.zapi_client.unmap_lun.assert_has_calls(calls)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_unmap_lun')
    def test_terminate_connection_iscsi_multiattach(self, mock_unmap_lun):
        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            fake.test_iscsi_attachment,
            fake.test_iscsi_attachment,
        ]

        self.library.terminate_connection_iscsi(volume, fake.ISCSI_CONNECTOR)

        mock_unmap_lun.assert_not_called()

    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr')
    def test_terminate_connection_iscsi_last_attachment(self,
                                                        mock_get_lun_attr,
                                                        mock_unmap_lun):
        mock_get_lun_attr.return_value = {'Path': fake.PATH}
        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [fake.test_iscsi_attachment]

        self.library.terminate_connection_iscsi(volume, fake.ISCSI_CONNECTOR)

        mock_unmap_lun.assert_called_once_with(
            fake.PATH, [fake.ISCSI_CONNECTOR['initiator']])

    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr')
    def test_terminate_connection_iscsi_all_initiators(self, mock_get_lun_attr,
                                                       mock_unmap_lun):
        mock_get_lun_attr.return_value = {'Path': fake.PATH}
        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            fake.test_iscsi_attachment,
            fake.test_iscsi_attachment,
        ]

        self.library.terminate_connection_iscsi(volume, None)

        mock_unmap_lun.assert_called_once_with(fake.PATH, [])

    def test_find_mapped_lun_igroup(self):
        self.assertRaises(NotImplementedError,
                          self.library._find_mapped_lun_igroup,
                          fake.LUN_PATH,
                          fake.FC_FORMATTED_INITIATORS)

    def test_has_luns_mapped_to_initiators(self):
        self.zapi_client.has_luns_mapped_to_initiators.return_value = True
        self.assertTrue(self.library._has_luns_mapped_to_initiators(
            fake.FC_FORMATTED_INITIATORS))
        self.zapi_client.has_luns_mapped_to_initiators.assert_called_once_with(
            fake.FC_FORMATTED_INITIATORS)

    def test_get_or_create_igroup_preexisting(self):
        self.zapi_client.get_igroup_by_initiators.return_value = [fake.IGROUP1]
        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))
        self.library._create_igroup_add_initiators = mock.Mock()
        igroup_name, host_os, ig_type = self.library._get_or_create_igroup(
            fake.FC_FORMATTED_INITIATORS, 'fcp', 'linux')

        self.assertEqual(fake.IGROUP1_NAME, igroup_name)
        self.assertEqual('linux', host_os)
        self.assertEqual('fcp', ig_type)
        self.zapi_client.get_igroup_by_initiators.assert_called_once_with(
            fake.FC_FORMATTED_INITIATORS)
        self.assertEqual(
            0, self.library._create_igroup_add_initiators.call_count)

    @ddt.data([], [fake.CUSTOM_IGROUP])
    @mock.patch.object(uuid, 'uuid4', mock.Mock(return_value=fake.UUID1))
    def test_get_or_create_igroup_none_preexisting(self, igroups):
        """Test _create_igroup_add_initiators with not OS igroups."""
        # We only care about the OpenStack igroups, so we must have the same
        # result if there are no igroups and if the igroup is a custom one.
        self.zapi_client.get_igroup_by_initiators.return_value = igroups

        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        igroup_name, os, ig_type = self.library._get_or_create_igroup(
            fake.FC_FORMATTED_INITIATORS, 'fcp', 'linux')

        self.assertEqual('openstack-' + fake.UUID1, igroup_name)
        self.zapi_client.create_igroup.assert_called_once_with(
            igroup_name, 'fcp', 'linux')
        self.assertEqual(len(fake.FC_FORMATTED_INITIATORS),
                         self.zapi_client.add_igroup_initiator.call_count)
        self.assertEqual('linux', os)
        self.assertEqual('fcp', ig_type)

    @ddt.data([])
    @mock.patch.object(uuid, 'uuid4', mock.Mock(return_value=fake.UUID1))
    def test_get_or_create_igroup_active_sync_creates_dest_igroup(
            self, igroups):
        """Test that active sync creates igroup on destination SVM."""
        self.zapi_client.get_igroup_by_initiators.return_value = igroups
        self.library.dest_zapi_client = mock.Mock()
        self.library.dest_zapi_client.get_igroup_by_initiators.return_value \
            = []

        config = mock.Mock()
        config.safe_get.side_effect = lambda key: {
            'replication_device': [{'backend_id': 'backend1'}],
            'netapp_replication_policy': 'AutomatedFailOver',
        }.get(key)

        self.library.configuration = config

        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))

        igroup_name, os, ig_type = self.library._get_or_create_igroup(
            fake.FC_FORMATTED_INITIATORS,
            'iscsi', 'linux')

        self.assertEqual('openstack-' + fake.UUID1, igroup_name)
        # Source igroup created
        self.zapi_client.create_igroup.assert_called_once_with(
            igroup_name, 'iscsi', 'linux')
        # Dest igroup created with same name
        self.library.dest_zapi_client.create_igroup.assert_called_once_with(
            igroup_name, 'iscsi', 'linux')
        # Initiators added to both
        self.assertEqual(len(fake.FC_FORMATTED_INITIATORS),
                         self.zapi_client.add_igroup_initiator.call_count)
        self.assertEqual(
            len(fake.FC_FORMATTED_INITIATORS),
            self.library.dest_zapi_client.add_igroup_initiator.call_count)

    def test_get_or_create_igroup_active_sync_existing_dest_igroup(self):
        """Test that active sync reuses existing dest igroup."""
        self.zapi_client.get_igroup_by_initiators.return_value = [
            fake.IGROUP1]
        self.library.dest_zapi_client = mock.Mock()
        self.library.dest_zapi_client.get_igroup_by_initiators.return_value \
            = [fake.IGROUP1]

        config = mock.Mock()
        config.safe_get.side_effect = lambda key: {
            'replication_device': [{'backend_id': 'backend1'}],
            'netapp_replication_policy': 'AutomatedFailOverDuplex',
        }.get(key)
        self.library.configuration = config

        self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=(9, 16, 1))
        self.mock_object(
            self.library, '_ensure_igroup_host_proximity')

        igroup_name, os, ig_type = self.library._get_or_create_igroup(
            fake.FC_FORMATTED_INITIATORS, 'fcp', 'linux')

        self.assertEqual(fake.IGROUP1_NAME, igroup_name)
        # Should NOT create new igroup on dest since one exists
        self.library.dest_zapi_client.create_igroup.assert_not_called()
        # Proximity should still be checked
        self.library._ensure_igroup_host_proximity.assert_called_once()

    def test_get_fc_target_wwpns(self):
        self.assertRaises(NotImplementedError,
                          self.library._get_fc_target_wwpns)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_build_initiator_target_map')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_map_lun')
    def test_initialize_connection_fc(self, mock_map_lun,
                                      mock_build_initiator_target_map):
        self.maxDiff = None
        mock_map_lun.return_value = '1'
        mock_build_initiator_target_map.return_value = (fake.FC_TARGET_WWPNS,
                                                        fake.FC_I_T_MAP, 4)

        target_info = self.library.initialize_connection_fc(fake.FC_VOLUME,
                                                            fake.FC_CONNECTOR)

        self.assertDictEqual(target_info, fake.FC_TARGET_INFO)
        mock_map_lun.assert_called_once_with(
            'fake_volume', fake.FC_FORMATTED_INITIATORS, 'fcp', None)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_build_initiator_target_map')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_map_lun')
    def test_initialize_connection_fc_no_wwpns(
            self, mock_map_lun, mock_build_initiator_target_map):

        mock_map_lun.return_value = '1'
        mock_build_initiator_target_map.return_value = (None, None, 0)
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.initialize_connection_fc,
                          fake.FC_VOLUME,
                          fake.FC_CONNECTOR)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_has_luns_mapped_to_initiators')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    def test_terminate_connection_fc(self, mock_get_lun_attr, mock_unmap_lun,
                                     mock_has_luns_mapped_to_initiators):

        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
        mock_unmap_lun.return_value = None
        mock_has_luns_mapped_to_initiators.return_value = True
        volume = copy.deepcopy(fake.test_volume)
        target_info = self.library.terminate_connection_fc(volume,
                                                           fake.FC_CONNECTOR)

        self.assertDictEqual(target_info, fake.FC_TARGET_INFO_EMPTY)
        mock_unmap_lun.assert_called_once_with(fake.LUN_PATH,
                                               fake.FC_FORMATTED_INITIATORS)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_build_initiator_target_map')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_has_luns_mapped_to_initiators')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    def test_terminate_connection_fc_no_more_luns(
            self, mock_get_lun_attr, mock_unmap_lun,
            mock_has_luns_mapped_to_initiators,
            mock_build_initiator_target_map):

        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
        mock_unmap_lun.return_value = None
        mock_has_luns_mapped_to_initiators.return_value = False
        mock_build_initiator_target_map.return_value = (fake.FC_TARGET_WWPNS,
                                                        fake.FC_I_T_MAP, 4)
        volume = copy.deepcopy(fake.test_volume)
        target_info = self.library.terminate_connection_fc(volume,
                                                           fake.FC_CONNECTOR)

        self.assertDictEqual(target_info, fake.FC_TARGET_INFO_UNMAP)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_has_luns_mapped_to_initiators')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    def test_terminate_connection_fc_multiattach(
            self,
            mock_get_lun_attr,
            mock_unmap_lun,
            mock_has_luns_mapped_to_initiators):

        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            {'attach_status': fake.ATTACHED, 'attached_host': fake.HOST_NAME},
            {'attach_status': fake.ATTACHED, 'attached_host': fake.HOST_NAME},
        ]
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
        mock_unmap_lun.return_value = None
        mock_has_luns_mapped_to_initiators.return_value = True
        self.library.terminate_connection_fc(volume, fake.FC_CONNECTOR)
        mock_unmap_lun.assert_called_once_with(fake.LUN_PATH,
                                               fake.FC_FORMATTED_INITIATORS)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_has_luns_mapped_to_initiators')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_lun_attr')
    def test_terminate_connection_fc_last_attachment(
            self,
            mock_get_lun_attr,
            mock_unmap_lun,
            mock_has_luns_mapped_to_initiators):

        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            {'attach_status': fake.ATTACHED, 'attached_host': fake.HOST_NAME},
        ]
        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
        mock_unmap_lun.return_value = None
        mock_has_luns_mapped_to_initiators.return_value = True
        self.library.terminate_connection_fc(volume, fake.FC_CONNECTOR)
        mock_unmap_lun.assert_called_once_with(fake.LUN_PATH,
                                               fake.FC_FORMATTED_INITIATORS)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_has_luns_mapped_to_initiators')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_unmap_lun')
    @mock.patch.object(block_base.NetAppBlockStorageLibrary, '_get_lun_attr')
    def test_terminate_connection_fc_multiattach_cleanup(
            self, mock_get_lun_attr, mock_unmap_lun,
            mock_has_luns_mapped_to_initiators):
        volume = copy.deepcopy(fake.test_volume)
        volume.multiattach = True
        volume.volume_attachment = [
            {'attach_status': fake.ATTACHED, 'attached_host': fake.HOST_NAME},
            {'attach_status': fake.ATTACHED, 'attached_host': fake.HOST_NAME},
        ]
        connector = fake.FC_CONNECTOR

        mock_get_lun_attr.return_value = {'Path': fake.LUN_PATH}
        mock_unmap_lun.return_value = None
        mock_has_luns_mapped_to_initiators.return_value = True

        def terminate_connection(*args, **kwargs):
            self.library.terminate_connection_fc(volume, connector)

        # Run the termination operation in parallel using ThreadPoolExecutor
        with ThreadPoolExecutor(max_workers=2) as executor:
            list(executor.map(terminate_connection, range(2)))

        # Ensure that the LUN maps are cleaned up correctly for both
        # parallel operations
        self.assertEqual(mock_unmap_lun.call_count, 2)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_fc_target_wwpns')
    def test_build_initiator_target_map_no_lookup_service(
            self, mock_get_fc_target_wwpns):

        self.library.lookup_service = None
        mock_get_fc_target_wwpns.return_value = fake.FC_FORMATTED_TARGET_WWPNS

        (target_wwpns, init_targ_map, num_paths) = \
            self.library._build_initiator_target_map(fake.FC_CONNECTOR)

        self.assertSetEqual(set(fake.FC_TARGET_WWPNS), set(target_wwpns))
        self.assertDictEqual(fake.FC_I_T_MAP_COMPLETE, init_targ_map)
        self.assertEqual(0, num_paths)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_fc_target_wwpns')
    def test_build_initiator_target_map_with_lookup_service(
            self, mock_get_fc_target_wwpns):

        self.library.lookup_service = mock.Mock()
        self.library.lookup_service.get_device_mapping_from_network.\
            return_value = fake.FC_FABRIC_MAP
        mock_get_fc_target_wwpns.return_value = fake.FC_FORMATTED_TARGET_WWPNS

        (target_wwpns, init_targ_map, num_paths) = \
            self.library._build_initiator_target_map(fake.FC_CONNECTOR)

        self.assertSetEqual(set(fake.FC_TARGET_WWPNS), set(target_wwpns))

        for i in fake.FC_I_T_MAP:
            for t in fake.FC_I_T_MAP[i]:
                self.assertIn(t, init_targ_map[i])

        self.assertEqual(4, num_paths)

    @mock.patch.object(na_utils, 'check_flags')
    def test_do_setup_san_configured(self, mock_check_flags):
        self.library.configuration.netapp_lun_ostype = 'windows'
        self.library.configuration.netapp_host_type = 'solaris'
        self.library.configuration.netapp_lun_space_reservation = 'disabled'
        self.library.do_setup(mock.Mock())
        self.assertTrue(mock_check_flags.called)
        self.assertEqual('windows', self.library.lun_ostype)
        self.assertEqual('solaris', self.library.host_type)

    @mock.patch.object(na_utils, 'check_flags')
    def test_do_setup_san_unconfigured(self, mock_check_flags):
        self.library.configuration.netapp_lun_ostype = None
        self.library.configuration.netapp_host_type = None
        self.library.configuration.netapp_lun_space_reservation = 'enabled'
        self.library.do_setup(mock.Mock())
        self.assertTrue(mock_check_flags.called)
        self.assertEqual('linux', self.library.lun_ostype)
        self.assertEqual('linux', self.library.host_type)

    def test_do_setup_space_reservation_disabled(self):
        self.mock_object(na_utils, 'check_flags')
        self.library.configuration.netapp_lun_ostype = None
        self.library.configuration.netapp_host_type = None
        self.library.configuration.netapp_lun_space_reservation = 'disabled'

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

        self.assertEqual('false', self.library.lun_space_reservation)

    def test_do_setup_space_reservation_enabled(self):
        self.mock_object(na_utils, 'check_flags')
        self.library.configuration.netapp_lun_ostype = None
        self.library.configuration.netapp_host_type = None
        self.library.configuration.netapp_lun_space_reservation = 'enabled'

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

        self.assertEqual('true', self.library.lun_space_reservation)

    def test_get_existing_vol_with_manage_ref_no_source_info(self):

        self.assertRaises(exception.ManageExistingInvalidReference,
                          self.library._get_existing_vol_with_manage_ref,
                          {})

    def test_get_existing_vol_manage_not_found(self):

        self.zapi_client.get_lun_by_args.return_value = []

        self.assertRaises(exception.ManageExistingInvalidReference,
                          self.library._get_existing_vol_with_manage_ref,
                          {'source-name': 'lun_path'})
        self.assertEqual(1, self.zapi_client.get_lun_by_args.call_count)

    def test_get_existing_vol_manage_lun_by_path(self):

        self.library.vserver = 'fake_vserver'
        self.zapi_client.get_lun_by_args.return_value = ['lun0', 'lun1']
        mock_lun = block_base.NetAppLun(
            'lun0', 'lun0', '3', {'UUID': 'fake_uuid'})
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_extract_lun_info',
                         return_value=mock_lun)

        existing_ref = {'source-name': 'fake_path'}
        lun = self.library._get_existing_vol_with_manage_ref(existing_ref)

        self.zapi_client.get_lun_by_args.assert_called_once_with(
            path='fake_path')
        self.library._extract_lun_info.assert_called_once_with('lun0')
        self.assertEqual('lun0', lun.name)

    def test_get_existing_vol_manage_lun_by_uuid(self):

        self.library.vserver = 'fake_vserver'
        self.zapi_client.get_lun_by_args.return_value = ['lun0', 'lun1']
        mock_lun = block_base.NetAppLun(
            'lun0', 'lun0', '3', {'UUID': 'fake_uuid'})
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_extract_lun_info',
                         return_value=mock_lun)

        existing_ref = {'source-id': 'fake_uuid'}
        lun = self.library._get_existing_vol_with_manage_ref(existing_ref)

        self.zapi_client.get_lun_by_args.assert_called_once_with(
            uuid='fake_uuid')
        self.library._extract_lun_info.assert_called_once_with('lun0')
        self.assertEqual('lun0', lun.name)

    def test_get_existing_vol_manage_lun_invalid_mode(self):

        self.assertRaises(exception.ManageExistingInvalidReference,
                          self.library._get_existing_vol_with_manage_ref,
                          {'source-id': 'src_id'})

    def test_get_existing_vol_manage_lun_invalid_lun(self):

        self.zapi_client.get_lun_by_args.return_value = ['lun0', 'lun1']
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_is_lun_valid_on_storage',
                         side_effect=[False, True])
        mock_lun0 = block_base.NetAppLun(
            'lun0', 'lun0', '3', {'UUID': 'src_id_0'})
        mock_lun1 = block_base.NetAppLun(
            'lun1', 'lun1', '5', {'UUID': 'src_id_1'})
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_extract_lun_info',
                         side_effect=[mock_lun0, mock_lun1])

        lun = self.library._get_existing_vol_with_manage_ref(
            {'source-name': 'lun_path'})

        self.assertEqual(1, self.zapi_client.get_lun_by_args.call_count)
        self.library._extract_lun_info.assert_has_calls([
            mock.call('lun0'),
            mock.call('lun1'),
        ])
        self.assertEqual('lun1', lun.name)

    @mock.patch.object(block_base.NetAppBlockStorageLibrary,
                       '_get_existing_vol_with_manage_ref',
                       mock.Mock(return_value=block_base.NetAppLun(
                                 'handle', 'name', '1073742824', {})))
    def test_manage_existing_get_size(self):
        size = self.library.manage_existing_get_size(
            {'id': 'vol_id'}, {'ref': 'ref'})
        self.assertEqual(2, size)
        self.library._get_existing_vol_with_manage_ref.assert_called_once_with(
            {'ref': 'ref'})

    @ddt.data(None,
              {'replication_status': fields.ReplicationStatus.ENABLED})
    def test_manage_existing_lun_name_matches(self, model_update):
        volume = fake_volume.fake_volume_obj(self.ctxt)
        existing_ref = {'source-name': 'fake_path'}
        mock_lun = block_base.NetAppLun(
            volume['name'], volume['name'], '3',
            {'UUID': 'fake_uuid', 'Path': 'p'})
        self.mock_object(self.library, '_get_existing_vol_with_manage_ref',
                         return_value=mock_lun)

        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value=fake.EXTRA_SPECS)
        self.mock_object(self.library, '_check_volume_type_for_lun',
                         return_value=True)
        self.mock_object(self.library, '_setup_qos_for_volume')
        self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
                         return_value=None)
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_get_volume_model_update',
                         return_value=model_update)
        mock_info_log = self.mock_object(block_base.LOG, 'info')

        actual_update = self.library.manage_existing(volume, existing_ref)

        self.assertEqual(model_update, actual_update)
        self.assertEqual(2, mock_info_log.call_count)
        self.library._add_lun_to_table.assert_called_once_with(mock_lun)

    @ddt.data(*itertools.product((None, 'fake_qos_policy_group_name'),
                                 (True, False)))
    @ddt.unpack
    def test_manage_existing_rename_lun(self, qos_policy_group_name,
                                        is_qos_policy_group_spec_adaptive):
        expected_update = (
            {'replication_status': fields.ReplicationStatus.ENABLED})
        volume = fake_volume.fake_volume_obj(self.ctxt)
        existing_ref = {'source-name': 'fake_path'}
        mock_lun = block_base.NetAppLun(
            'lun0', 'lun0', '3', {'UUID': 'fake_uuid', 'Path': fake.LUN_PATH})
        self.mock_object(self.library, '_get_existing_vol_with_manage_ref',
                         return_value=mock_lun)

        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value=fake.EXTRA_SPECS)
        self.mock_object(self.library, '_check_volume_type_for_lun',
                         return_value=True)
        self.mock_object(self.library, '_setup_qos_for_volume')
        self.mock_object(na_utils, 'get_qos_policy_group_name_from_info',
                         return_value=qos_policy_group_name)
        self.mock_object(na_utils, 'is_qos_policy_group_spec_adaptive',
                         return_value=is_qos_policy_group_spec_adaptive)
        self.mock_object(self.library, '_add_lun_to_table')
        self.mock_object(self.library, '_get_volume_model_update',
                         return_value=expected_update)
        self.mock_object(self.zapi_client, 'set_lun_qos_policy_group')
        mock_info_log = self.mock_object(block_base.LOG, 'info')

        actual_update = self.library.manage_existing(volume, existing_ref)

        expected_new_path = '/vol/vol0/%s' % volume['name']
        self.assertEqual(expected_update, actual_update)
        self.assertEqual(1, mock_info_log.call_count)
        self.library._add_lun_to_table.assert_called_once_with(mock_lun)
        if qos_policy_group_name:
            (self.zapi_client.set_lun_qos_policy_group.
             assert_called_once_with(expected_new_path, qos_policy_group_name,
                                     is_qos_policy_group_spec_adaptive))
        else:
            self.assertFalse(
                self.zapi_client.set_lun_qos_policy_group.called)

    @mock.patch.object(block_base.LOG, 'info')
    def test_unmanage(self, log):
        mock_lun = block_base.NetAppLun('handle', 'name', '1',
                                        {'Path': 'p', 'UUID': 'uuid'})
        self.library._get_lun_from_table = mock.Mock(return_value=mock_lun)
        self.library.unmanage({'name': 'vol'})
        self.library._get_lun_from_table.assert_called_once_with('vol')
        self.assertEqual(1, log.call_count)

    def test_check_vol_type_for_lun(self):
        result = self.library._check_volume_type_for_lun(
            'vol', 'lun', 'existing_ref', {})
        self.assertIsNone(result)

    def test_is_lun_valid_on_storage(self):
        self.assertTrue(self.library._is_lun_valid_on_storage('lun'))

    def test_initialize_connection_iscsi(self):
        target_details_list = fake.ISCSI_TARGET_DETAILS_LIST
        volume = fake.ISCSI_VOLUME
        connector = fake.ISCSI_CONNECTOR
        self.mock_object(block_base.NetAppBlockStorageLibrary, '_map_lun',
                         return_value=fake.ISCSI_LUN['lun_id'])
        self.zapi_client.get_iscsi_target_details.return_value = (
            target_details_list)
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_get_targets_from_list',
                         return_value=target_details_list)
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_is_space_alloc_enabled',
                         return_value=True)
        self.zapi_client.get_iscsi_service_details.return_value = (
            fake.ISCSI_SERVICE_IQN)
        self.mock_object(na_utils,
                         'get_iscsi_connection_properties',
                         return_value=fake.ISCSI_CONNECTION_PROPERTIES)

        target_info = self.library.initialize_connection_iscsi(volume,
                                                               connector)

        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']['auth_method'],
            target_info['data']['auth_method'])
        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']['auth_password'],
            target_info['data']['auth_password'])
        self.assertIn('auth_password', target_info['data'])

        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']['discovery_auth_method'],
            target_info['data']['discovery_auth_method'])
        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']
            ['discovery_auth_password'],
            target_info['data']['discovery_auth_password'])
        self.assertIn('auth_password', target_info['data'])
        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']
            ['discovery_auth_username'],
            target_info['data']['discovery_auth_username'])
        self.assertEqual(
            fake.ISCSI_CONNECTION_PROPERTIES['data']
            ['discard'],
            target_info['data']['discard'])

        self.assertEqual(fake.ISCSI_CONNECTION_PROPERTIES, target_info)
        block_base.NetAppBlockStorageLibrary._map_lun.assert_called_once_with(
            fake.ISCSI_VOLUME['name'], [fake.ISCSI_CONNECTOR['initiator']],
            'iscsi', None)
        self.zapi_client.get_iscsi_target_details.assert_called_once_with()
        block_base.NetAppBlockStorageLibrary._get_targets_from_list\
                                            .assert_called_once_with(
                                                target_details_list)
        self.zapi_client.get_iscsi_service_details.assert_called_once_with()

    def test_initialize_connection_iscsi_no_target_list(self):
        volume = fake.ISCSI_VOLUME
        connector = fake.ISCSI_CONNECTOR
        self.mock_object(block_base.NetAppBlockStorageLibrary, '_map_lun',
                         return_value=fake.ISCSI_LUN['lun_id'])
        self.zapi_client.get_iscsi_target_details.return_value = None
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_get_targets_from_list')
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_is_space_alloc_enabled',
                         return_value=True)
        self.mock_object(na_utils,
                         'get_iscsi_connection_properties',
                         return_value=fake.ISCSI_CONNECTION_PROPERTIES)

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.initialize_connection_iscsi,
                          volume, connector)

        self.assertEqual(
            0, block_base.NetAppBlockStorageLibrary
                         ._get_targets_from_list.call_count)
        self.assertEqual(
            0, block_base.NetAppBlockStorageLibrary
            ._is_space_alloc_enabled.call_count)
        self.assertEqual(
            0, self.zapi_client.get_iscsi_service_details.call_count)
        self.assertEqual(
            0, na_utils.get_iscsi_connection_properties.call_count)

    def test_initialize_connection_iscsi_no_preferred_target(self):
        volume = fake.ISCSI_VOLUME
        connector = fake.ISCSI_CONNECTOR
        self.mock_object(block_base.NetAppBlockStorageLibrary, '_map_lun',
                         return_value=fake.ISCSI_LUN['lun_id'])
        self.zapi_client.get_iscsi_target_details.return_value = None
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_get_targets_from_list',
                         return_value=None)
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_is_space_alloc_enabled',
                         return_value=True)
        self.mock_object(na_utils, 'get_iscsi_connection_properties')

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.initialize_connection_iscsi,
                          volume, connector)

        self.assertEqual(0, self.zapi_client
                                .get_iscsi_service_details.call_count)
        self.assertEqual(0,
                         block_base.NetAppBlockStorageLibrary
                         ._is_space_alloc_enabled.call_count)
        self.assertEqual(0, na_utils.get_iscsi_connection_properties
                                    .call_count)

    def test_initialize_connection_iscsi_no_iscsi_service_details(self):
        target_details_list = fake.ISCSI_TARGET_DETAILS_LIST
        volume = fake.ISCSI_VOLUME
        connector = fake.ISCSI_CONNECTOR
        self.mock_object(block_base.NetAppBlockStorageLibrary, '_map_lun',
                         return_value=fake.ISCSI_LUN['lun_id'])
        self.zapi_client.get_iscsi_target_details.return_value = (
            target_details_list)
        self.mock_object(block_base.NetAppBlockStorageLibrary,
                         '_get_targets_from_list',
                         return_value=target_details_list)
        self.zapi_client.get_iscsi_service_details.return_value = None
        self.mock_object(na_utils, 'get_iscsi_connection_properties')

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library.initialize_connection_iscsi,
                          volume,
                          connector)

        block_base.NetAppBlockStorageLibrary._map_lun.assert_called_once_with(
            fake.ISCSI_VOLUME['name'], [fake.ISCSI_CONNECTOR['initiator']],
            'iscsi', None)
        self.zapi_client.get_iscsi_target_details.assert_called_once_with()
        block_base.NetAppBlockStorageLibrary._get_targets_from_list\
                  .assert_called_once_with(target_details_list)

    def test_get_target_details_list(self):
        target_details_list = fake.ISCSI_TARGET_DETAILS_LIST

        result = self.library._get_targets_from_list(target_details_list)

        self.assertEqual(target_details_list, result)

    def test_get_preferred_target_from_empty_list(self):
        target_details_list = []

        result = self.library._get_targets_from_list(target_details_list)

        self.assertFalse(bool(result))

    def test_get_targets_from_list_with_one_interface_disabled(self):
        target_details_list = copy.deepcopy(fake.ISCSI_TARGET_DETAILS_LIST)
        target_details_list[0]['interface-enabled'] = 'false'

        result = self.library._get_targets_from_list(target_details_list)

        self.assertEqual(target_details_list[1:], result)

    def test_get_targets_from_list_with_all_interfaces_disabled(self):
        target_details_list = copy.deepcopy(fake.ISCSI_TARGET_DETAILS_LIST)
        for target in target_details_list:
            target['interface-enabled'] = 'false'

        result = self.library._get_targets_from_list(target_details_list)

        self.assertEqual(target_details_list, result)

    def test_get_targets_from_list_with_filter(self):
        target_details_list = fake.ISCSI_TARGET_DETAILS_LIST
        filter = [target_detail['address']
                  for target_detail in target_details_list[1:]]

        result = self.library._get_targets_from_list(target_details_list,
                                                     filter)

        self.assertEqual(target_details_list[1:], result)

    @mock.patch.object(na_utils, 'check_flags', mock.Mock())
    @mock.patch.object(block_base, 'LOG', mock.Mock())
    def test_setup_error_invalid_lun_os(self):
        self.library.configuration.netapp_lun_ostype = 'unknown'
        self.library.do_setup(mock.Mock())

        self.assertRaises(na_utils.NetAppDriverException,
                          self.library.check_for_setup_error)

        block_base.LOG.error.assert_called_once_with(mock.ANY)

    @mock.patch.object(na_utils, 'check_flags', mock.Mock())
    @mock.patch.object(block_base, 'LOG', mock.Mock())
    def test_setup_error_invalid_host_type(self):
        self.library.configuration.netapp_lun_ostype = 'linux'
        self.library.configuration.netapp_host_type = 'future_os'
        self.library.do_setup(mock.Mock())

        self.assertRaises(na_utils.NetAppDriverException,
                          self.library.check_for_setup_error)

        block_base.LOG.error.assert_called_once_with(mock.ANY)

    @mock.patch.object(na_utils, 'check_flags', mock.Mock())
    def test_check_for_setup_error_both_config(self):
        self.library.configuration.netapp_lun_ostype = 'linux'
        self.library.configuration.netapp_host_type = 'linux'
        self.library.do_setup(mock.Mock())
        self.zapi_client.get_lun_list.return_value = ['lun1']
        self.library._extract_and_populate_luns = mock.Mock()
        mock_looping_start_tasks = self.mock_object(
            self.library.loopingcalls, 'start_tasks')

        self.library.check_for_setup_error()

        self.library._extract_and_populate_luns.assert_called_once_with(
            ['lun1'])
        mock_looping_start_tasks.assert_called_once_with()

    @mock.patch.object(na_utils, 'check_flags', mock.Mock())
    def test_check_for_setup_error_no_os_host(self):
        mock_start_tasks = self.mock_object(
            self.library.loopingcalls, 'start_tasks')
        self.library.configuration.netapp_lun_ostype = None
        self.library.configuration.netapp_host_type = None
        self.library.do_setup(mock.Mock())
        self.zapi_client.get_lun_list.return_value = ['lun1']
        self.library._extract_and_populate_luns = mock.Mock()

        self.library.check_for_setup_error()

        self.library._extract_and_populate_luns.assert_called_once_with(
            ['lun1'])

        mock_start_tasks.assert_called_once_with()

    def test_delete_volume(self):
        mock_delete_lun = self.mock_object(self.library, '_delete_lun')

        self.library.delete_volume(fake.VOLUME)

        mock_delete_lun.assert_called_once_with(fake.LUN_NAME)

    def test_delete_lun(self):
        mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
        mock_get_lun_attr.return_value = fake.LUN_METADATA
        self.library.zapi_client = mock.Mock()
        self.library.lun_table = fake.LUN_TABLE

        self.library._delete_lun(fake.LUN_NAME)

        mock_get_lun_attr.assert_called_once_with(
            fake.LUN_NAME, 'metadata')
        self.library.zapi_client.destroy_lun.assert_called_once_with(fake.PATH)

    def test_delete_lun_asar2(self):
        mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
        self.library.configuration.netapp_disaggregated_platform = True
        mock_get_lun_attr.return_value = fake.LUN_METADATA_ASAR2
        self.library.zapi_client = mock.Mock()

        lun_backend_name = na_utils.get_backend_lun_or_ns_name(
            fake.LUN_NAME, self.library.configuration)
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA_ASAR2)
        self.library.lun_table = {lun_backend_name: fake_lun}
        self.library._delete_lun(lun_backend_name)

        mock_get_lun_attr.assert_called_once_with(fake.LUN_NAME, 'metadata')
        self.library.zapi_client.destroy_lun.assert_called_once_with(
            fake.LUN_NAME)

    def test_delete_lun_no_metadata(self):
        self.mock_object(self.library, '_get_lun_attr', return_value=None)
        self.library.zapi_client = mock.Mock()
        self.library.lun_table = fake.LUN_TABLE
        self.mock_object(self.library, 'zapi_client')

        self.library._delete_lun(fake.LUN_NAME)

        self.library._get_lun_attr.assert_called_once_with(
            fake.LUN_NAME, 'metadata')
        self.assertEqual(0, self.library.zapi_client.destroy_lun.call_count)
        self.assertEqual(0,
                         self.zapi_client.
                         mark_qos_policy_group_for_deletion.call_count)

    @mock.patch.object(block_base, 'LOG', mock.Mock())
    def test_delete_lun_missing_lun(self):
        mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
        mock_get_lun_attr.return_value = fake.LUN_METADATA
        self.library.zapi_client = mock.Mock()
        error = netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND)
        self.mock_object(self.library.zapi_client, 'destroy_lun',
                         side_effect=error)
        self.library.lun_table = {fake.LUN_NAME: None}

        self.library._delete_lun(fake.LUN_NAME)

        mock_get_lun_attr.assert_called_once_with(
            fake.LUN_NAME, 'metadata')
        self.library.zapi_client.destroy_lun.assert_called_once_with(fake.PATH)
        block_base.LOG.error.assert_not_called()
        block_base.LOG.warning.assert_called_once()
        self.assertEqual({}, self.library.lun_table)

    @mock.patch.object(block_base, 'LOG', mock.Mock())
    def test_delete_lun_client_exception(self):
        mock_get_lun_attr = self.mock_object(self.library, '_get_lun_attr')
        mock_get_lun_attr.return_value = fake.LUN_METADATA
        self.library.zapi_client = mock.Mock()
        self.mock_object(self.library.zapi_client, 'destroy_lun',
                         side_effect=netapp_api.NaApiError)

        self.assertRaises(na_utils.NetAppDriverException,
                          self.library._delete_lun,
                          fake.LUN_NAME)

        block_base.LOG.error.assert_not_called()
        block_base.LOG.warning.assert_not_called()

    def test_delete_snapshot(self):
        mock_delete_lun = self.mock_object(self.library, '_delete_lun')

        self.library.delete_snapshot(fake.SNAPSHOT)

        mock_delete_lun.assert_called_once_with(fake.SNAPSHOT_NAME)

    def test_clone_source_to_destination(self):
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value=fake.EXTRA_SPECS)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_clone_lun')
        self.mock_object(self.library, '_extend_volume')
        self.mock_object(self.library, 'delete_volume')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.mock_object(self.library, '_get_volume_model_update',
                         return_value={'key': 'value'})
        self.library.lun_space_reservation = 'false'

        retval = self.library._clone_source_to_destination(
            fake.CLONE_SOURCE, fake.CLONE_DESTINATION)

        self.assertEqual({'key': 'value'}, retval)
        na_utils.get_volume_extra_specs.assert_called_once_with(
            fake.CLONE_DESTINATION)
        self.library._setup_qos_for_volume.assert_called_once_with(
            fake.CLONE_DESTINATION, fake.EXTRA_SPECS)
        self.library._clone_lun.assert_called_once_with(
            fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
            space_reserved='false',
            qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME,
            qos_policy_group_is_adaptive=False)
        self.library._extend_volume.assert_called_once_with(
            fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
            fake.QOS_POLICY_GROUP_NAME)
        self.assertEqual(0, self.library.delete_volume.call_count)
        self.assertEqual(0, self.library.
                         _mark_qos_policy_group_for_deletion.call_count)

    def test_clone_source_to_destination_exception_path(self):
        self.mock_object(na_utils, 'get_volume_extra_specs',
                         return_value=fake.EXTRA_SPECS)
        self.mock_object(self.library, '_setup_qos_for_volume',
                         return_value=fake.QOS_POLICY_GROUP_INFO)
        self.mock_object(self.library, '_clone_lun')
        self.mock_object(self.library, '_extend_volume', side_effect=Exception)
        self.mock_object(self.library, 'delete_volume')
        self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
        self.library.lun_space_reservation = 'true'

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library._clone_source_to_destination,
                          fake.CLONE_SOURCE, fake.CLONE_DESTINATION)

        na_utils.get_volume_extra_specs.assert_called_once_with(
            fake.CLONE_DESTINATION)
        self.library._setup_qos_for_volume.assert_called_once_with(
            fake.CLONE_DESTINATION, fake.EXTRA_SPECS)
        self.library._clone_lun.assert_called_once_with(
            fake.CLONE_SOURCE_NAME, fake.CLONE_DESTINATION_NAME,
            space_reserved='true',
            qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME,
            qos_policy_group_is_adaptive=False)
        self.library._extend_volume.assert_called_once_with(
            fake.CLONE_DESTINATION, fake.CLONE_DESTINATION_SIZE,
            fake.QOS_POLICY_GROUP_NAME)
        self.assertEqual(1, self.library.delete_volume.call_count)
        self.assertEqual(1, self.library.
                         _mark_qos_policy_group_for_deletion.call_count)

    def test_create_lun(self):
        self.assertRaises(NotImplementedError, self.library._create_lun,
                          fake.VOLUME_ID, fake.LUN_ID, fake.SIZE,
                          fake.LUN_METADATA)

    def test_clone_lun(self):
        self.assertRaises(NotImplementedError, self.library._clone_lun,
                          fake.VOLUME_ID, 'new-' + fake.VOLUME_ID)

    def test_create_snapshot(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE, fake.LUN_METADATA)
        mock_clone_lun = self.mock_object(self.library, '_clone_lun')
        self.mock_object(self.library,
                         '_get_lun_from_table',
                         return_value=fake_lun)

        self.library.create_snapshot(fake.SNAPSHOT)

        mock_clone_lun.assert_called_once_with(
            fake_lun.name, fake.SNAPSHOT_NAME, space_reserved='false',
            is_snapshot=True)

    def test_create_volume_from_snapshot(self):
        mock_do_clone = self.mock_object(self.library,
                                         '_clone_source_to_destination')
        source = {
            'name': fake.SNAPSHOT['name'],
            'size': fake.SNAPSHOT['volume_size']
        }

        self.library.create_volume_from_snapshot(fake.VOLUME, fake.SNAPSHOT)

        mock_do_clone.assert_has_calls([
            mock.call(source, fake.VOLUME)])

    def test_create_cloned_volume(self):
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE, fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(self.library,
                                                   '_get_lun_from_table')
        mock_get_lun_from_table.return_value = fake_lun
        mock_do_clone = self.mock_object(self.library,
                                         '_clone_source_to_destination')
        source = {
            'name': fake_lun.name,
            'size': fake.VOLUME_REF['size']
        }

        self.library.create_cloned_volume(fake.VOLUME, fake.VOLUME_REF)

        mock_do_clone.assert_has_calls([
            mock.call(source, fake.VOLUME)])

    def test_extend_volume(self):

        new_size = 100
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['size'] = new_size

        mock_get_volume_extra_specs = self.mock_object(
            na_utils, 'get_volume_extra_specs', return_value=fake.EXTRA_SPECS)
        mock_setup_qos_for_volume = self.mock_object(
            self.library, '_setup_qos_for_volume',
            return_value=fake.QOS_POLICY_GROUP_INFO)
        mock_extend_volume = self.mock_object(self.library, '_extend_volume')

        self.library.extend_volume(fake.VOLUME, new_size)

        mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
        mock_setup_qos_for_volume.assert_called_once_with(volume_copy,
                                                          fake.EXTRA_SPECS)
        mock_extend_volume.assert_called_once_with(fake.VOLUME,
                                                   new_size,
                                                   fake.QOS_POLICY_GROUP_NAME)

    def test_extend_volume_asar2(self):
        self.library.configuration.netapp_disaggregated_platform = True
        new_size = 100
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['size'] = new_size

        mock_get_volume_extra_specs = self.mock_object(
            na_utils, 'get_volume_extra_specs', return_value=fake.EXTRA_SPECS)
        mock_setup_qos_for_volume = self.mock_object(
            self.library, '_setup_qos_for_volume',
            return_value=fake.QOS_POLICY_GROUP_INFO)
        mock_extend_volume = self.mock_object(self.library, '_extend_volume')

        self.library.extend_volume(fake.VOLUME, new_size)

        mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
        mock_setup_qos_for_volume.assert_called_once_with(volume_copy,
                                                          fake.EXTRA_SPECS)
        mock_extend_volume.assert_called_once_with(fake.VOLUME,
                                                   new_size,
                                                   fake.QOS_POLICY_GROUP_NAME)

    def test_extend_volume_api_error(self):

        new_size = 100
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['size'] = new_size

        mock_get_volume_extra_specs = self.mock_object(
            na_utils, 'get_volume_extra_specs', return_value=fake.EXTRA_SPECS)
        mock_setup_qos_for_volume = self.mock_object(
            self.library, '_setup_qos_for_volume',
            return_value=fake.QOS_POLICY_GROUP_INFO)
        mock_extend_volume = self.mock_object(
            self.library, '_extend_volume', side_effect=netapp_api.NaApiError)

        self.assertRaises(netapp_api.NaApiError,
                          self.library.extend_volume,
                          fake.VOLUME,
                          new_size)

        mock_get_volume_extra_specs.assert_called_once_with(fake.VOLUME)
        mock_setup_qos_for_volume.assert_has_calls([
            mock.call(volume_copy, fake.EXTRA_SPECS),
            mock.call(fake.VOLUME, fake.EXTRA_SPECS)])
        mock_extend_volume.assert_called_once_with(
            fake.VOLUME, new_size, fake.QOS_POLICY_GROUP_NAME)

    @ddt.data((9, 4, 0), (9, 6, 0))
    def test__extend_volume_direct(self, ontap_version):

        current_size = fake.LUN_SIZE
        current_size_bytes = current_size * units.Gi
        new_size = fake.LUN_SIZE * 2
        new_size_bytes = new_size * units.Gi
        max_size = fake.LUN_SIZE * 10
        max_size_bytes = max_size * units.Gi

        mock_get_ontap_version = self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=ontap_version)
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        current_size_bytes,
                                        fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        fake_lun_geometry = {'max_resize': str(max_size_bytes)}
        mock_get_lun_geometry = self.mock_object(
            self.library.zapi_client, 'get_lun_geometry',
            return_value=fake_lun_geometry)
        mock_do_direct_resize = self.mock_object(self.library.zapi_client,
                                                 'do_direct_resize')
        mock_do_sub_clone_resize = self.mock_object(self.library,
                                                    '_do_sub_clone_resize')
        self.library.lun_table = {fake.VOLUME['name']: fake_lun}

        self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')

        mock_get_ontap_version.assert_called_once_with(cached=True)
        mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])

        if ontap_version < (9, 5, 0):
            mock_get_lun_geometry.assert_called_once_with(
                fake.LUN_METADATA['Path'])
        else:
            mock_get_lun_geometry.assert_not_called()

        mock_do_direct_resize.assert_called_once_with(
            fake.LUN_METADATA['Path'], str(new_size_bytes))
        self.assertFalse(mock_do_sub_clone_resize.called)
        self.assertEqual(str(new_size_bytes),
                         self.library.lun_table[fake.VOLUME['name']].size)

    @ddt.data((9, 4, 0), (9, 6, 0))
    def test__extend_attached_volume_direct(self, ontap_version):

        current_size = fake.LUN_SIZE
        current_size_bytes = current_size * units.Gi
        new_size = fake.LUN_SIZE * 2
        new_size_bytes = new_size * units.Gi
        max_size = fake.LUN_SIZE * 10
        max_size_bytes = max_size * units.Gi
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['size'] = new_size
        volume_copy['attach_status'] = fake.ATTACHED

        mock_get_ontap_version = self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=ontap_version)
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        current_size_bytes,
                                        fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        fake_lun_geometry = {'max_resize': str(max_size_bytes)}
        mock_get_lun_geometry = self.mock_object(
            self.library.zapi_client, 'get_lun_geometry',
            return_value=fake_lun_geometry)
        mock_do_direct_resize = self.mock_object(self.library.zapi_client,
                                                 'do_direct_resize')
        mock_do_sub_clone_resize = self.mock_object(self.library,
                                                    '_do_sub_clone_resize')

        self.library.lun_table = {volume_copy['name']: fake_lun}
        self.library._extend_volume(volume_copy, new_size, 'fake_qos_policy')

        mock_get_lun_from_table.assert_called_once_with(volume_copy['name'])
        mock_get_ontap_version.assert_called_once_with(cached=True)

        if ontap_version < (9, 5, 0):
            mock_get_lun_geometry.assert_called_once_with(
                fake.LUN_METADATA['Path'])
        else:
            mock_get_lun_geometry.assert_not_called()

        mock_do_direct_resize.assert_called_once_with(
            fake.LUN_METADATA['Path'], str(new_size_bytes))
        self.assertFalse(mock_do_sub_clone_resize.called)
        self.assertEqual(str(new_size_bytes),
                         self.library.lun_table[volume_copy['name']].size)

    @ddt.data((9, 4, 0), (9, 6, 0))
    def test__extend_volume_clone(self, ontap_version):

        current_size = fake.LUN_SIZE
        current_size_bytes = current_size * units.Gi
        new_size = fake.LUN_SIZE * 20
        new_size_bytes = new_size * units.Gi
        max_size = fake.LUN_SIZE * 10
        max_size_bytes = max_size * units.Gi

        mock_get_ontap_version = self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=ontap_version)
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        current_size_bytes,
                                        fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        fake_lun_geometry = {'max_resize': str(max_size_bytes)}
        mock_get_lun_geometry = self.mock_object(
            self.library.zapi_client, 'get_lun_geometry',
            return_value=fake_lun_geometry)
        mock_do_direct_resize = self.mock_object(self.library.zapi_client,
                                                 'do_direct_resize')
        mock_do_sub_clone_resize = self.mock_object(self.library,
                                                    '_do_sub_clone_resize')
        self.library.lun_table = {fake.VOLUME['name']: fake_lun}

        self.library._extend_volume(fake.VOLUME, new_size, 'fake_qos_policy')

        mock_get_ontap_version.assert_called_once_with(cached=True)
        mock_get_lun_from_table.assert_called_once_with(fake.VOLUME['name'])

        if ontap_version < (9, 5, 0):
            self.assertFalse(mock_do_direct_resize.called)
            mock_get_lun_geometry.assert_called_once_with(
                fake.LUN_METADATA['Path'])
            mock_do_sub_clone_resize.assert_called_once_with(
                fake.LUN_METADATA['Path'], str(new_size_bytes),
                qos_policy_group_name='fake_qos_policy')
        else:
            mock_get_lun_geometry.assert_not_called()
            mock_do_sub_clone_resize.assert_not_called()
            mock_do_direct_resize.assert_called_once_with(
                fake.LUN_METADATA['Path'], str(new_size_bytes))

        self.assertEqual(str(new_size_bytes),
                         self.library.lun_table[fake.VOLUME['name']].size)

    @ddt.data((9, 4, 0), (9, 6, 0))
    def test__extend_attached_volume_clone_error(self, ontap_version):

        current_size = fake.LUN_SIZE
        current_size_bytes = current_size * units.Gi
        new_size = fake.LUN_SIZE * 20
        new_size_bytes = new_size * units.Gi
        max_size = fake.LUN_SIZE * 10
        max_size_bytes = max_size * units.Gi
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['attach_status'] = fake.ATTACHED

        mock_get_ontap_version = self.mock_object(
            self.library.zapi_client, 'get_ontap_version',
            return_value=ontap_version)
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        str(current_size_bytes),
                                        fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        fake_lun_geometry = {'max_resize': str(max_size_bytes)}
        mock_get_lun_geometry = self.mock_object(
            self.library.zapi_client, 'get_lun_geometry',
            return_value=fake_lun_geometry)
        mock_do_direct_resize = self.mock_object(self.library.zapi_client,
                                                 'do_direct_resize')
        mock_do_sub_clone_resize = self.mock_object(self.library,
                                                    '_do_sub_clone_resize')
        self.library.lun_table = {volume_copy['name']: fake_lun}

        # (throne82) This error occurs only with versions older than 9.5
        if ontap_version < (9, 5, 0):
            self.assertRaises(exception.VolumeBackendAPIException,
                              self.library._extend_volume,
                              volume_copy,
                              new_size,
                              fake.QOS_POLICY_GROUP_NAME)
            self.assertFalse(mock_do_direct_resize.called)
            self.assertFalse(mock_do_sub_clone_resize.called)
            mock_get_lun_geometry.assert_called_once_with(
                fake.LUN_METADATA['Path'])
            self.assertEqual(str(current_size_bytes),
                             self.library.lun_table[volume_copy['name']].size)
        else:
            self.library._extend_volume(volume_copy,
                                        new_size, fake.QOS_POLICY_GROUP_NAME)
            mock_do_direct_resize.assert_called_once_with(
                fake.LUN_METADATA['Path'], str(new_size_bytes))
            mock_do_sub_clone_resize.assert_not_called()
            mock_get_lun_geometry.assert_not_called()
            self.assertEqual(str(new_size_bytes),
                             self.library.lun_table[volume_copy['name']].size)

        mock_get_ontap_version.assert_called_once_with(cached=True)
        mock_get_lun_from_table.assert_called_once_with(
            volume_copy['name'])

    @ddt.data((9, 4, 0), (9, 6, 0))
    def test__extend_volume_no_change(self, ontap_version):

        current_size = fake.LUN_SIZE
        current_size_bytes = current_size * units.Gi
        new_size = fake.LUN_SIZE
        max_size = fake.LUN_SIZE * 10
        max_size_bytes = max_size * units.Gi
        volume_copy = copy.copy(fake.VOLUME)
        volume_copy['size'] = new_size

        mock_get_ontap_version = self.mock_object(
            self.library.zapi_client, 'get_ontap_version')
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        current_size_bytes,
                                        fake.LUN_METADATA)
        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        fake_lun_geometry = {'max_resize': str(max_size_bytes)}
        mock_get_lun_geometry = self.mock_object(
            self.library.zapi_client, 'get_lun_geometry',
            return_value=fake_lun_geometry)
        mock_do_direct_resize = self.mock_object(self.library.zapi_client,
                                                 'do_direct_resize')
        mock_do_sub_clone_resize = self.mock_object(self.library,
                                                    '_do_sub_clone_resize')
        self.library.lun_table = {volume_copy['name']: fake_lun}

        self.library._extend_volume(volume_copy, new_size, 'fake_qos_policy')

        mock_get_lun_from_table.assert_called_once_with(volume_copy['name'])
        self.assertFalse(mock_get_lun_geometry.called)
        self.assertFalse(mock_do_direct_resize.called)
        self.assertFalse(mock_do_sub_clone_resize.called)
        self.assertFalse(mock_get_ontap_version.called)

    def test_do_sub_clone_resize(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA)
        new_lun_size = fake.LUN_SIZE * 10
        new_lun_name = 'new-%s' % fake.LUN_NAME
        block_count = fake.LUN_SIZE * units.Gi / 512

        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        mock_get_vol_option = self.mock_object(
            self.library, '_get_vol_option', return_value='off')
        mock_get_lun_block_count = self.mock_object(
            self.library, '_get_lun_block_count', return_value=block_count)
        mock_create_lun = self.mock_object(
            self.library.zapi_client, 'create_lun')
        mock_clone_lun = self.mock_object(self.library, '_clone_lun')
        mock_post_sub_clone_resize = self.mock_object(
            self.library, '_post_sub_clone_resize')
        mock_destroy_lun = self.mock_object(
            self.library.zapi_client, 'destroy_lun')

        self.library._do_sub_clone_resize(fake.LUN_PATH,
                                          new_lun_size,
                                          fake.QOS_POLICY_GROUP_NAME)

        mock_get_lun_from_table.assert_called_once_with(fake.LUN_NAME)
        mock_get_vol_option.assert_called_once_with('vol0', 'compression')
        mock_get_lun_block_count.assert_called_once_with(fake.LUN_PATH)
        mock_create_lun.assert_called_once_with(
            'vol0', new_lun_name, new_lun_size, fake.LUN_METADATA,
            qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
        mock_clone_lun.assert_called_once_with(
            fake.LUN_NAME, new_lun_name, block_count=block_count)
        mock_post_sub_clone_resize.assert_called_once_with(fake.LUN_PATH)
        self.assertFalse(mock_destroy_lun.called)

    def test_do_sub_clone_resize_compression_on(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA)
        new_lun_size = fake.LUN_SIZE * 10
        block_count = fake.LUN_SIZE * units.Gi / 512

        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        mock_get_vol_option = self.mock_object(
            self.library, '_get_vol_option', return_value='on')
        mock_get_lun_block_count = self.mock_object(
            self.library, '_get_lun_block_count', return_value=block_count)
        mock_create_lun = self.mock_object(
            self.library.zapi_client, 'create_lun')
        mock_clone_lun = self.mock_object(self.library, '_clone_lun')
        mock_post_sub_clone_resize = self.mock_object(
            self.library, '_post_sub_clone_resize')
        mock_destroy_lun = self.mock_object(
            self.library.zapi_client, 'destroy_lun')

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library._do_sub_clone_resize,
                          fake.LUN_PATH,
                          new_lun_size,
                          fake.QOS_POLICY_GROUP_NAME)

        mock_get_lun_from_table.assert_called_once_with(fake.LUN_NAME)
        mock_get_vol_option.assert_called_once_with('vol0', 'compression')
        self.assertFalse(mock_get_lun_block_count.called)
        self.assertFalse(mock_create_lun.called)
        self.assertFalse(mock_clone_lun.called)
        self.assertFalse(mock_post_sub_clone_resize.called)
        self.assertFalse(mock_destroy_lun.called)

    def test_do_sub_clone_resize_no_blocks(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA)
        new_lun_size = fake.LUN_SIZE * 10
        block_count = 0

        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        mock_get_vol_option = self.mock_object(
            self.library, '_get_vol_option', return_value='off')
        mock_get_lun_block_count = self.mock_object(
            self.library, '_get_lun_block_count', return_value=block_count)
        mock_create_lun = self.mock_object(
            self.library.zapi_client, 'create_lun')
        mock_clone_lun = self.mock_object(self.library, '_clone_lun')
        mock_post_sub_clone_resize = self.mock_object(
            self.library, '_post_sub_clone_resize')
        mock_destroy_lun = self.mock_object(
            self.library.zapi_client, 'destroy_lun')

        self.assertRaises(exception.VolumeBackendAPIException,
                          self.library._do_sub_clone_resize,
                          fake.LUN_PATH,
                          new_lun_size,
                          fake.QOS_POLICY_GROUP_NAME)

        mock_get_lun_from_table.assert_called_once_with(fake.LUN_NAME)
        mock_get_vol_option.assert_called_once_with('vol0', 'compression')
        mock_get_lun_block_count.assert_called_once_with(fake.LUN_PATH)
        self.assertFalse(mock_create_lun.called)
        self.assertFalse(mock_clone_lun.called)
        self.assertFalse(mock_post_sub_clone_resize.called)
        self.assertFalse(mock_destroy_lun.called)

    def test_do_sub_clone_resize_create_error(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA)
        new_lun_size = fake.LUN_SIZE * 10
        new_lun_name = 'new-%s' % fake.LUN_NAME
        block_count = fake.LUN_SIZE * units.Gi / 512

        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        mock_get_vol_option = self.mock_object(
            self.library, '_get_vol_option', return_value='off')
        mock_get_lun_block_count = self.mock_object(
            self.library, '_get_lun_block_count', return_value=block_count)
        mock_create_lun = self.mock_object(
            self.library.zapi_client, 'create_lun',
            side_effect=netapp_api.NaApiError)
        mock_clone_lun = self.mock_object(self.library, '_clone_lun')
        mock_post_sub_clone_resize = self.mock_object(
            self.library, '_post_sub_clone_resize')
        mock_destroy_lun = self.mock_object(
            self.library.zapi_client, 'destroy_lun')

        self.assertRaises(netapp_api.NaApiError,
                          self.library._do_sub_clone_resize,
                          fake.LUN_PATH,
                          new_lun_size,
                          fake.QOS_POLICY_GROUP_NAME)

        mock_get_lun_from_table.assert_called_once_with(fake.LUN_NAME)
        mock_get_vol_option.assert_called_once_with('vol0', 'compression')
        mock_get_lun_block_count.assert_called_once_with(fake.LUN_PATH)
        mock_create_lun.assert_called_once_with(
            'vol0', new_lun_name, new_lun_size, fake.LUN_METADATA,
            qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
        self.assertFalse(mock_clone_lun.called)
        self.assertFalse(mock_post_sub_clone_resize.called)
        self.assertFalse(mock_destroy_lun.called)

    def test_do_sub_clone_resize_clone_error(self):

        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE,
                                        fake.LUN_ID,
                                        fake.LUN_SIZE,
                                        fake.LUN_METADATA)
        new_lun_size = fake.LUN_SIZE * 10
        new_lun_name = 'new-%s' % fake.LUN_NAME
        new_lun_path = '/vol/vol0/%s' % new_lun_name
        block_count = fake.LUN_SIZE * units.Gi / 512

        mock_get_lun_from_table = self.mock_object(
            self.library, '_get_lun_from_table', return_value=fake_lun)
        mock_get_vol_option = self.mock_object(
            self.library, '_get_vol_option', return_value='off')
        mock_get_lun_block_count = self.mock_object(
            self.library, '_get_lun_block_count', return_value=block_count)
        mock_create_lun = self.mock_object(
            self.library.zapi_client, 'create_lun')
        mock_clone_lun = self.mock_object(
            self.library, '_clone_lun', side_effect=netapp_api.NaApiError)
        mock_post_sub_clone_resize = self.mock_object(
            self.library, '_post_sub_clone_resize')
        mock_destroy_lun = self.mock_object(
            self.library.zapi_client, 'destroy_lun')

        self.assertRaises(netapp_api.NaApiError,
                          self.library._do_sub_clone_resize,
                          fake.LUN_PATH,
                          new_lun_size,
                          fake.QOS_POLICY_GROUP_NAME)

        mock_get_lun_from_table.assert_called_once_with(fake.LUN_NAME)
        mock_get_vol_option.assert_called_once_with('vol0', 'compression')
        mock_get_lun_block_count.assert_called_once_with(fake.LUN_PATH)
        mock_create_lun.assert_called_once_with(
            'vol0', new_lun_name, new_lun_size, fake.LUN_METADATA,
            qos_policy_group_name=fake.QOS_POLICY_GROUP_NAME)
        mock_clone_lun.assert_called_once_with(
            fake.LUN_NAME, new_lun_name, block_count=block_count)
        self.assertFalse(mock_post_sub_clone_resize.called)
        mock_destroy_lun.assert_called_once_with(new_lun_path)

    def test_configure_chap_generate_username_and_password(self):
        """Ensure that a CHAP username and password are generated."""
        initiator_name = fake.ISCSI_CONNECTOR['initiator']

        username, password = self.library._configure_chap(initiator_name)

        self.assertEqual(na_utils.DEFAULT_CHAP_USER_NAME, username)
        self.assertIsNotNone(password)
        self.assertEqual(len(password), na_utils.CHAP_SECRET_LENGTH)

    def test_add_chap_properties(self):
        """Ensure that CHAP properties are added to the properties dictionary

        """
        properties = {'data': {}}
        self.library._add_chap_properties(properties, 'user1', 'pass1')

        data = properties['data']
        self.assertEqual('CHAP', data['auth_method'])
        self.assertEqual('user1', data['auth_username'])
        self.assertEqual('pass1', data['auth_password'])
        self.assertEqual('CHAP', data['discovery_auth_method'])
        self.assertEqual('user1', data['discovery_auth_username'])
        self.assertEqual('pass1', data['discovery_auth_password'])

    def test_add_looping_tasks(self):
        mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task')
        mock_call_snap_cleanup = self.mock_object(
            self.library, '_delete_snapshots_marked_for_deletion')
        mock_call_ems_logging = self.mock_object(
            self.library, '_handle_ems_logging')

        self.library._add_looping_tasks()

        mock_add_task.assert_has_calls([
            mock.call(mock_call_snap_cleanup, loopingcalls.ONE_MINUTE,
                      loopingcalls.ONE_MINUTE),
            mock.call(mock_call_ems_logging, loopingcalls.ONE_HOUR)])

    def test_delete_snapshots_marked_for_deletion(self):
        snapshots = [{
            'name': fake.SNAPSHOT_NAME,
            'volume_name': fake.VOLUME['name']
        }]
        mock_get_snapshots_marked = self.mock_object(
            self.zapi_client, 'get_snapshots_marked_for_deletion')
        mock_get_snapshots_marked.return_value = snapshots
        mock_delete_snapshot = self.mock_object(
            self.zapi_client, 'delete_snapshot')

        self.library._delete_snapshots_marked_for_deletion()

        mock_get_snapshots_marked.assert_called_once_with()
        mock_delete_snapshot.assert_called_once_with(
            fake.VOLUME['name'], fake.SNAPSHOT_NAME)

    def test_delete_lun_from_table(self):
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE, fake.LUN_METADATA)
        self.library.lun_table = {fake_lun.name: fake_lun}
        self.library._delete_lun_from_table(fake_lun.name)
        self.assertEqual({}, self.library.lun_table)

    def test_delete_lun_from_table_asar2(self):
        self.library.configuration.netapp_disaggregated_platform = True
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE, fake.LUN_METADATA_ASAR2)
        backend_name = na_utils.get_backend_lun_or_ns_name(
            fake_lun.name,
            self.library.configuration
        )
        self.library.lun_table = {backend_name: fake_lun}
        self.library._delete_lun_from_table(fake_lun.name)
        self.assertEqual({}, self.library.lun_table)

    def test_delete_lun_from_table_not_found(self):
        fake_lun = block_base.NetAppLun(fake.LUN_HANDLE, fake.LUN_ID,
                                        fake.LUN_SIZE, fake.LUN_METADATA)
        self.library.lun_table = {fake_lun.name: fake_lun}
        self.library._delete_lun_from_table('another-fake-lun')
        self.assertEqual({fake_lun.name: fake_lun}, self.library.lun_table)

    def test_active_sync_config_true_for_supported_version_and_policy(self):
        mock_zapi_client = mock.Mock()
        mock_zapi_client.get_ontap_version.return_value = "9.16.0"
        self.library.zapi_client = mock_zapi_client
        config = mock.Mock()
        config.safe_get.side_effect = lambda key: {
            'replication_device': [{'backend_id': 'backend1'}],
            'netapp_replication_policy': 'AutomatedFailOver'
        }.get(key)
        result = self.library._is_active_sync_configured(config)
        self.assertTrue(result)

    def test_active_sync_config_false_for_unsupported_version(self):
        mock_zapi_client = mock.Mock()
        mock_zapi_client.get_ontap_version.return_value = "9.14.0"
        self.library.zapi_client = mock_zapi_client
        config = mock.Mock()
        config.safe_get.return_value = None
        result = self.library._is_active_sync_configured(config)
        self.assertFalse(result)

    def test_active_sync_config_false_when_no_replication_device(self):
        mock_zapi_client = mock.Mock()
        mock_zapi_client.get_ontap_version.return_value = "9.16.0"
        config = mock.Mock()
        self.library.zapi_client = mock_zapi_client
        config.safe_get.side_effect = lambda key: {
            'replication_device': None,
            'netapp_replication_policy': 'AutomatedFailOver'
        }.get(key)
        result = self.library._is_active_sync_configured(config)
        self.assertFalse(result)

    def test_active_sync_config_false_when_policy_not_auto_failover(self):
        mock_zapi_client = mock.Mock()
        mock_zapi_client.get_ontap_version.return_value = "9.16.0"
        config = mock.Mock()
        self.library.zapi_client = mock_zapi_client
        config.safe_get.side_effect = lambda key: {
            'replication_device': [{'backend_id': 'backend1'}],
            'netapp_replication_policy': 'ManualFailOver'
        }.get(key)
        result = self.library._is_active_sync_configured(config)
        self.assertFalse(result)

    def test_active_sync_configured_false_for_none_version(self):
        mock_zapi_client = mock.Mock()
        mock_zapi_client.get_ontap_version.return_value = None
        config = mock.Mock()
        self.library.zapi_client = mock_zapi_client
        config.safe_get.return_value = None
        result = self.library._is_active_sync_configured(config)
        self.assertFalse(result)

    def test_add_looping_tasks_traditional_platform(self):
        """Test _add_looping_tasks with AFF platform"""
        mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task')
        mock_call_snap_cleanup = self.mock_object(
            self.library, '_delete_snapshots_marked_for_deletion')
        mock_call_ems_logging = self.mock_object(
            self.library, '_handle_ems_logging')

        self.library._add_looping_tasks()

        # Traditional platform should include snapshot cleanup task
        mock_add_task.assert_has_calls([
            mock.call(mock_call_snap_cleanup, loopingcalls.ONE_MINUTE,
                      loopingcalls.ONE_MINUTE),
            mock.call(mock_call_ems_logging, loopingcalls.ONE_HOUR)])

    def test_add_looping_tasks_disaggregated_platform(self):
        """Test _add_looping_tasks with disaggregated platform"""
        mock_add_task = self.mock_object(self.library.loopingcalls, 'add_task')
        self.library.configuration.netapp_disaggregated_platform = True
        mock_call_snap_cleanup = self.mock_object(
            self.library, '_delete_snapshots_marked_for_deletion')
        mock_call_ems_logging = self.mock_object(
            self.library, '_handle_ems_logging')

        self.library._add_looping_tasks()

        # Disaggregated platform should NOT include snapshot cleanup task
        mock_add_task.assert_has_calls([
            mock.call(mock_call_ems_logging, loopingcalls.ONE_HOUR)])

        # Verify snapshot cleanup is not called
        mock_call_snap_cleanup.assert_not_called()

    def test_is_consistent_replication_enabled_true(self):
        config = mock.Mock()
        config.safe_get.side_effect = lambda key: {
            'netapp_consistent_replication': True
        }.get(key)
        result = self.library._is_consistent_replication_enabled(config)
        self.assertTrue(result)

    def test_is_consistent_replication_enabled_false(self):
        config = mock.Mock()
        config.safe_get.side_effect = lambda key: {
            'netapp_consistent_replication': False
        }.get(key)
        result = self.library._is_consistent_replication_enabled(config)
        self.assertFalse(result)

    @mock.patch.object(uuid, 'uuid4', mock.Mock(return_value=fake.UUID1))
    def test_create_igroup_add_initiators_on_dest(self):
        """Test creating igroup on destination SVM."""
        self.library.dest_zapi_client = mock.Mock()
        igroup_name = 'openstack-' + fake.UUID1

        result = self.library._create_igroup_add_initiators_on_dest(
            igroup_name, 'iscsi', 'linux', fake.FC_FORMATTED_INITIATORS)

        self.assertEqual(igroup_name, result)
        self.library.dest_zapi_client.create_igroup.assert_called_once_with(
            igroup_name, 'iscsi', 'linux')
        self.assertEqual(
            len(fake.FC_FORMATTED_INITIATORS),
            self.library.dest_zapi_client.add_igroup_initiator.call_count)

    def test_is_automated_failover_duplex_policy_true(self):
        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOverDuplex'
        result = self.library._is_automated_failover_duplex_policy(config)
        self.assertTrue(result)

    def test_is_automated_failover_duplex_policy_false(self):
        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOver'
        result = self.library._is_automated_failover_duplex_policy(config)
        self.assertFalse(result)

    @mock.patch.object(
        block_base.NetAppBlockStorageLibrary,
        '_configure_igroup_host_proximity')
    def test_ensure_igroup_host_proximity_afd_not_configured(
            self, mock_configure_proximity):
        """Test ensuring proximity for AFD policy when not configured."""
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"

        self.library.zapi_client.get_igroup_by_name.return_value = {
            'initiator-group-uuid': igroup_uuid
        }
        self.library.zapi_client.is_igroup_proximity_configured.return_value \
            = False

        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOverDuplex'
        self.library.configuration = config

        self.library._ensure_igroup_host_proximity(
            igroup_name, destination_svm)

        mock_configure_proximity.assert_called_once_with(
            igroup_name, igroup_uuid, destination_svm)

    @mock.patch.object(
        block_base.NetAppBlockStorageLibrary,
        '_configure_igroup_host_proximity')
    def test_ensure_igroup_host_proximity_already_configured(
            self, mock_configure_proximity):
        """Test when proximity is already configured."""
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"

        self.library.zapi_client.get_igroup_by_name.return_value = {
            'initiator-group-uuid': igroup_uuid
        }
        self.library.zapi_client.is_igroup_proximity_configured.return_value \
            = True

        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOverDuplex'
        self.library.configuration = config

        self.library._ensure_igroup_host_proximity(
            igroup_name, destination_svm)

        # Verify igroup lookup and proximity check were performed
        self.library.zapi_client.get_igroup_by_name.assert_called_once_with(
            igroup_name)
        self.library.zapi_client.is_igroup_proximity_configured\
            .assert_called_once_with(igroup_uuid)
        # Should not configure proximity since it is already configured
        mock_configure_proximity.assert_not_called()

    def test_ensure_igroup_host_proximity_not_afd_policy(self):
        """Test that proximity is skipped for non-AFD policy."""
        igroup_name = "test-igroup"
        destination_svm = "dest-svm"

        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOver'
        self.library.configuration = config

        self.library._ensure_igroup_host_proximity(
            igroup_name, destination_svm)

        # Should not check proximity for non-AFD policy
        self.library.zapi_client.is_igroup_proximity_configured \
            .assert_not_called()

    def test_ensure_igroup_host_proximity_igroup_not_found(self):
        """Test when igroup is not found on source."""
        igroup_name = "test-igroup"
        destination_svm = "dest-svm"

        self.library.zapi_client.get_igroup_by_name.return_value = None

        config = mock.Mock()
        config.safe_get.return_value = 'AutomatedFailOverDuplex'
        self.library.configuration = config

        self.library._ensure_igroup_host_proximity(
            igroup_name, destination_svm)

        # Should not check proximity when igroup not found
        self.library.zapi_client.is_igroup_proximity_configured \
            .assert_not_called()

    @mock.patch.object(block_base.cmode_utils, 'get_backend_configuration')
    def test_configure_igroup_host_proximity_no_shared_initiators(
            self, mock_get_backend_config):
        """Test proximity with disjoint initiators (no overlap).

        Source has iqn.source1, iqn.source2 proximal to source SVM.
        Destination has iqn.dest1, iqn.dest2 proximal to dest SVM.
        No initiator appears in both configs, so no peer_svms should be set.
        """
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"
        source_svm = "source-svm"
        dest_igroup_uuid = "dest-uuid1"

        # Setup library with dest_zapi_client
        self.library.dest_zapi_client = mock.Mock()

        # Setup source vserver
        self.library.configuration.netapp_vserver = source_svm

        # Setup source config using mock_object pattern
        self.mock_object(
            self.library.configuration, 'safe_get',
            mock.Mock(side_effect=lambda key: {
                'netapp_proximal_nodes': ['iqn.source1', 'iqn.source2'],
                'replication_device': [{'backend_id': 'dest_backend'}]
            }.get(key)))

        # Setup destination config
        dest_config = mock.Mock()
        dest_config.netapp_vserver = destination_svm
        dest_config.safe_get.return_value = ['iqn.dest1', 'iqn.dest2']
        mock_get_backend_config.return_value = dest_config

        # Setup destination igroup
        self.library.dest_zapi_client.get_igroup_by_name.return_value = {
            'initiator-group-uuid': dest_igroup_uuid
        }

        self.library._configure_igroup_host_proximity(
            igroup_name, igroup_uuid, destination_svm)

        # Verify source igroup proximity was set with local initiators
        # and NO peer_svms (None) since no shared initiators
        (self.library.zapi_client.set_igroup_host_proximity
            .assert_called_once_with(
                igroup_uuid, ['iqn.source1', 'iqn.source2'], None))

        # Verify destination igroup proximity was set with peer initiators
        # and NO peer_svms (None) since no shared initiators
        (self.library.dest_zapi_client.set_igroup_host_proximity
            .assert_called_once_with(
                dest_igroup_uuid, ['iqn.dest1', 'iqn.dest2'], None))

    @mock.patch.object(block_base.cmode_utils, 'get_backend_configuration')
    def test_configure_igroup_host_proximity_shared_initiators(
            self, mock_get_backend_config):
        """Test proximity when some initiators appear in both configs.

        iqn.shared1 appears in both source and dest netapp_proximal_nodes
        (host connected to both sites). It should get peer_svms set.
        iqn.source_only is only in source config - no peer_svms.
        iqn.dest_only is only in dest config - no peer_svms.
        """
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"
        source_svm = "source-svm"
        dest_igroup_uuid = "dest-uuid1"

        self.library.dest_zapi_client = mock.Mock()
        self.library.configuration.netapp_vserver = source_svm

        # Source has iqn.source_only + iqn.shared1
        self.mock_object(
            self.library.configuration, 'safe_get',
            mock.Mock(side_effect=lambda key: {
                'netapp_vserver': source_svm,
                'netapp_proximal_nodes': ['iqn.source_only', 'iqn.shared1'],
                'replication_device': [{'backend_id': 'dest_backend'}]
            }.get(key)))

        # Destination has iqn.dest_only + iqn.shared1
        dest_config = mock.Mock()
        dest_config.netapp_vserver = destination_svm
        dest_config.safe_get.return_value = ['iqn.dest_only', 'iqn.shared1']
        mock_get_backend_config.return_value = dest_config

        self.library.dest_zapi_client.get_igroup_by_name.return_value = {
            'initiator-group-uuid': dest_igroup_uuid
        }

        self.library._configure_igroup_host_proximity(
            igroup_name, igroup_uuid, destination_svm)

        # Source igroup should get two calls:
        # 1. iqn.source_only with no peer_svms (local only)
        # 2. iqn.shared1 with peer_svms=[dest-svm] (shared)
        src_calls = self.library.zapi_client \
            .set_igroup_host_proximity.call_args_list
        self.assertEqual(2, len(src_calls))
        self.assertEqual(
            mock.call(igroup_uuid, ['iqn.source_only'], None),
            src_calls[0])
        self.assertEqual(
            mock.call(igroup_uuid, ['iqn.shared1'], destination_svm),
            src_calls[1])

        # Destination igroup should get two calls:
        # 1. iqn.dest_only with no peer_svms (local only)
        # 2. iqn.shared1 with peer_svms=[source-svm] (shared)
        dest_calls = self.library.dest_zapi_client \
            .set_igroup_host_proximity.call_args_list
        self.assertEqual(2, len(dest_calls))
        self.assertEqual(
            mock.call(dest_igroup_uuid, ['iqn.dest_only'], None),
            dest_calls[0])
        self.assertEqual(
            mock.call(dest_igroup_uuid, ['iqn.shared1'], source_svm),
            dest_calls[1])

    @mock.patch.object(block_base.cmode_utils, 'get_backend_configuration')
    def test_configure_igroup_host_proximity_all_shared(
            self, mock_get_backend_config):
        """Test proximity when ALL initiators appear in both configs.

        Both iqn.host1 and iqn.host2 appear in source and dest configs.
        All should get peer_svms set, and no local-only call should be made.
        """
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"
        source_svm = "source-svm"
        dest_igroup_uuid = "dest-uuid1"

        self.library.dest_zapi_client = mock.Mock()
        self.library.configuration.netapp_vserver = source_svm

        self.mock_object(
            self.library.configuration, 'safe_get',
            mock.Mock(side_effect=lambda key: {
                'netapp_vserver': source_svm,
                'netapp_proximal_nodes': ['iqn.host1', 'iqn.host2'],
                'replication_device': [{'backend_id': 'dest_backend'}]
            }.get(key)))

        dest_config = mock.Mock()
        dest_config.netapp_vserver = destination_svm
        dest_config.safe_get.return_value = ['iqn.host1', 'iqn.host2']
        mock_get_backend_config.return_value = dest_config

        self.library.dest_zapi_client.get_igroup_by_name.return_value = {
            'initiator-group-uuid': dest_igroup_uuid
        }

        self.library._configure_igroup_host_proximity(
            igroup_name, igroup_uuid, destination_svm)

        # Source igroup: only shared call with peer_svms
        (self.library.zapi_client.set_igroup_host_proximity
            .assert_called_once_with(
                igroup_uuid, ['iqn.host1', 'iqn.host2'], destination_svm))

        # Destination igroup: only shared call with peer_svms
        (self.library.dest_zapi_client.set_igroup_host_proximity
            .assert_called_once_with(
                dest_igroup_uuid, ['iqn.host1', 'iqn.host2'], source_svm))

    def test_configure_igroup_host_proximity_no_initiators(self):
        igroup_name = "test-igroup"
        igroup_uuid = "uuid1"
        destination_svm = "dest-svm"
        source_svm = "source-svm"

        # Setup library with dest_zapi_client
        self.library.dest_zapi_client = mock.Mock()

        # Setup source vserver
        self.library.configuration.netapp_vserver = source_svm

        # No proximity initiators configured
        self.mock_object(
            self.library.configuration, 'safe_get',
            mock.Mock(side_effect=lambda key: {
                'netapp_proximal_nodes': [],
                'replication_device': []
            }.get(key)))

        self.library._configure_igroup_host_proximity(
            igroup_name, igroup_uuid, destination_svm)

        # Verify no proximity calls were made
        self.library.zapi_client.set_igroup_host_proximity.assert_not_called()
        (self.library.dest_zapi_client.set_igroup_host_proximity
            .assert_not_called())
