# Copyright 2014 Violin Memory, Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
Tests for Violin Memory 6000 Series All-Flash Array Fibrechannel Driver
"""

import mock
from oslo.utils import units

from cinder import context
from cinder.db.sqlalchemy import models
from cinder import exception
from cinder import test
from cinder.tests import fake_vmem_xgtools_client as vxg
from cinder.volume import configuration as conf
from cinder.volume.drivers.violin import v6000_common
from cinder.volume.drivers.violin import v6000_fcp

VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
VOLUME = {
    "name": "volume-" + VOLUME_ID,
    "id": VOLUME_ID,
    "display_name": "fake_volume",
    "size": 2,
    "host": "irrelevant",
    "volume_type": None,
    "volume_type_id": None,
}
SNAPSHOT_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbb"
SNAPSHOT = {
    "name": "snapshot-" + SNAPSHOT_ID,
    "id": SNAPSHOT_ID,
    "volume_id": VOLUME_ID,
    "volume_name": "volume-" + VOLUME_ID,
    "volume_size": 2,
    "display_name": "fake_snapshot",
    "volume": VOLUME,
}
SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc"
SRC_VOL = {
    "name": "volume-" + SRC_VOL_ID,
    "id": SRC_VOL_ID,
    "display_name": "fake_src_vol",
    "size": 2,
    "host": "irrelevant",
    "volume_type": None,
    "volume_type_id": None,
}
INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
CONNECTOR = {
    "initiator": INITIATOR_IQN,
    "host": "irrelevant",
    'wwpns': [u'50014380186b3f65', u'50014380186b3f67'],
}
FC_TARGET_WWPNS = [
    '31000024ff45fb22', '21000024ff45fb23',
    '51000024ff45f1be', '41000024ff45f1bf'
]
FC_INITIATOR_WWPNS = [
    '50014380186b3f65', '50014380186b3f67'
]
FC_FABRIC_MAP = {
    'fabricA':
        {'target_port_wwn_list': [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]],
         'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[0]]},
    'fabricB':
        {'target_port_wwn_list': [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]],
         'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[1]]}
}
FC_INITIATOR_TARGET_MAP = {
    FC_INITIATOR_WWPNS[0]: [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]],
    FC_INITIATOR_WWPNS[1]: [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]]
}


class V6000FCPDriverTestCase(test.TestCase):
    """Test cases for VMEM FCP driver."""
    def setUp(self):
        super(V6000FCPDriverTestCase, self).setUp()
        self.conf = self.setup_configuration()
        self.driver = v6000_fcp.V6000FCDriver(configuration=self.conf)
        self.driver.common.container = 'myContainer'
        self.driver.device_id = 'ata-VIOLIN_MEMORY_ARRAY_23109R00000022'
        self.driver.gateway_fc_wwns = FC_TARGET_WWPNS
        self.stats = {}
        self.driver.set_initialized()

    def tearDown(self):
        super(V6000FCPDriverTestCase, self).tearDown()

    def setup_configuration(self):
        config = mock.Mock(spec=conf.Configuration)
        config.volume_backend_name = 'v6000_fcp'
        config.san_ip = '1.1.1.1'
        config.san_login = 'admin'
        config.san_password = ''
        config.san_thin_provision = False
        config.san_is_local = False
        config.gateway_mga = '2.2.2.2'
        config.gateway_mgb = '3.3.3.3'
        config.use_igroups = False
        config.request_timeout = 300
        config.container = 'myContainer'
        return config

    def setup_mock_vshare(self, m_conf=None):
        """Create a fake VShare communication object."""
        _m_vshare = mock.Mock(name='VShare',
                              version='1.1.1',
                              spec=vxg.mock_client_conf)

        if m_conf:
            _m_vshare.configure_mock(**m_conf)

        return _m_vshare

    @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
    def test_check_for_setup_error(self, m_setup_func):
        """No setup errors are found."""
        result = self.driver.check_for_setup_error()
        m_setup_func.assert_called_with()
        self.assertTrue(result is None)

    @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
    def test_check_for_setup_error_no_wwn_config(self, m_setup_func):
        """No wwns were found during setup."""
        self.driver.gateway_fc_wwns = []
        self.assertRaises(exception.ViolinInvalidBackendConfig,
                          self.driver.check_for_setup_error)

    def test_create_volume(self):
        """Volume created successfully."""
        self.driver.common._create_lun = mock.Mock()

        result = self.driver.create_volume(VOLUME)

        self.driver.common._create_lun.assert_called_with(VOLUME)
        self.assertTrue(result is None)

    def test_delete_volume(self):
        """Volume deleted successfully."""
        self.driver.common._delete_lun = mock.Mock()

        result = self.driver.delete_volume(VOLUME)

        self.driver.common._delete_lun.assert_called_with(VOLUME)
        self.assertTrue(result is None)

    def test_create_snapshot(self):
        """Snapshot created successfully."""
        self.driver.common._create_lun_snapshot = mock.Mock()

        result = self.driver.create_snapshot(SNAPSHOT)

        self.driver.common._create_lun_snapshot.assert_called_with(SNAPSHOT)
        self.assertTrue(result is None)

    def test_delete_snapshot(self):
        """Snapshot deleted successfully."""
        self.driver.common._delete_lun_snapshot = mock.Mock()

        result = self.driver.delete_snapshot(SNAPSHOT)

        self.driver.common._delete_lun_snapshot.assert_called_with(SNAPSHOT)
        self.assertTrue(result is None)

    @mock.patch.object(context, 'get_admin_context')
    def test_create_volume_from_snapshot(self, m_context_func):
        """Volume created from a snapshot successfully."""
        m_context_func.return_value = None
        self.driver.common._create_lun = mock.Mock()
        self.driver.copy_volume_data = mock.Mock()

        result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)

        m_context_func.assert_called_with()
        self.driver.common._create_lun.assert_called_with(VOLUME)
        self.driver.copy_volume_data.assert_called_with(None, SNAPSHOT, VOLUME)
        self.assertTrue(result is None)

    @mock.patch.object(context, 'get_admin_context')
    def test_create_cloned_volume(self, m_context_func):
        """Volume clone created successfully."""
        m_context_func.return_value = None
        self.driver.common._create_lun = mock.Mock()
        self.driver.copy_volume_data = mock.Mock()

        result = self.driver.create_cloned_volume(VOLUME, SRC_VOL)

        m_context_func.assert_called_with()
        self.driver.common._create_lun.assert_called_with(VOLUME)
        self.driver.copy_volume_data.assert_called_with(None, SRC_VOL, VOLUME)
        self.assertTrue(result is None)

    def test_initialize_connection(self):
        lun_id = 1
        igroup = None
        target_wwns = self.driver.gateway_fc_wwns
        init_targ_map = {}
        volume = mock.Mock(spec=models.Volume)

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver._export_lun = mock.Mock(return_value=lun_id)
        self.driver._build_initiator_target_map = mock.Mock(
            return_value=(target_wwns, init_targ_map))

        props = self.driver.initialize_connection(volume, CONNECTOR)

        self.driver._export_lun.assert_called_with(volume, CONNECTOR, igroup)
        self.driver.common.vip.basic.save_config.assert_called_with()
        self.driver._build_initiator_target_map.assert_called_with(
            CONNECTOR)
        self.assertEqual("fibre_channel", props['driver_volume_type'])
        self.assertEqual(True, props['data']['target_discovered'])
        self.assertEqual(target_wwns, props['data']['target_wwn'])
        self.assertEqual(lun_id, props['data']['target_lun'])
        self.assertEqual(init_targ_map, props['data']['initiator_target_map'])

    def test_initialize_connection_with_snapshot_object(self):
        lun_id = 1
        igroup = None
        target_wwns = self.driver.gateway_fc_wwns
        init_targ_map = {}
        snapshot = mock.Mock(spec=models.Snapshot)

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver._export_snapshot = mock.Mock(return_value=lun_id)
        self.driver._build_initiator_target_map = mock.Mock(
            return_value=(target_wwns, init_targ_map))

        props = self.driver.initialize_connection(snapshot, CONNECTOR)

        self.driver._export_snapshot.assert_called_with(
            snapshot, CONNECTOR, igroup)
        self.driver.common.vip.basic.save_config.assert_called_with()
        self.driver._build_initiator_target_map.assert_called_with(
            CONNECTOR)
        self.assertEqual("fibre_channel", props['driver_volume_type'])
        self.assertEqual(True, props['data']['target_discovered'])
        self.assertEqual(target_wwns, props['data']['target_wwn'])
        self.assertEqual(lun_id, props['data']['target_lun'])
        self.assertEqual(init_targ_map, props['data']['initiator_target_map'])

    def test_terminate_connection(self):
        target_wwns = self.driver.gateway_fc_wwns
        init_targ_map = {}
        volume = mock.Mock(spec=models.Volume)

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver._unexport_lun = mock.Mock()
        self.driver._is_initiator_connected_to_array = mock.Mock(
            return_value=False)
        self.driver._build_initiator_target_map = mock.Mock(
            return_value=(target_wwns, init_targ_map))

        props = self.driver.terminate_connection(volume, CONNECTOR)

        self.driver._unexport_lun.assert_called_with(volume)
        self.driver.common.vip.basic.save_config.assert_called_with()
        self.driver._is_initiator_connected_to_array.assert_called_with(
            CONNECTOR)
        self.driver._build_initiator_target_map.assert_called_with(
            CONNECTOR)
        self.assertEqual("fibre_channel", props['driver_volume_type'])
        self.assertEqual(target_wwns, props['data']['target_wwn'])
        self.assertEqual(init_targ_map, props['data']['initiator_target_map'])

    def test_terminate_connection_snapshot_object(self):
        target_wwns = self.driver.gateway_fc_wwns
        init_targ_map = {}
        snapshot = mock.Mock(spec=models.Snapshot)

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver._unexport_snapshot = mock.Mock()
        self.driver._is_initiator_connected_to_array = mock.Mock(
            return_value=False)
        self.driver._build_initiator_target_map = mock.Mock(
            return_value=(target_wwns, init_targ_map))

        props = self.driver.terminate_connection(snapshot, CONNECTOR)

        self.assertEqual("fibre_channel", props['driver_volume_type'])
        self.assertEqual(target_wwns, props['data']['target_wwn'])
        self.assertEqual(init_targ_map, props['data']['initiator_target_map'])

    def test_get_volume_stats(self):
        self.driver._update_stats = mock.Mock()
        self.driver._update_stats()

        result = self.driver.get_volume_stats(True)

        self.driver._update_stats.assert_called_with()
        self.assertEqual(self.driver.stats, result)

    def test_export_lun(self):
        lun_id = '1'
        igroup = 'test-igroup-1'
        response = {'code': 0, 'message': ''}

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd_and_verify = mock.Mock(
            return_value=response)
        self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)

        result = self.driver._export_lun(VOLUME, CONNECTOR, igroup)

        self.driver.common._send_cmd_and_verify.assert_called_with(
            self.driver.common.vip.lun.export_lun,
            self.driver.common._wait_for_export_config, '',
            [self.driver.common.container, VOLUME['id'], 'all',
             igroup, 'auto'], [VOLUME['id'], 'state=True'])
        self.driver.common._get_lun_id.assert_called_with(VOLUME['id'])
        self.assertEqual(lun_id, result)

    def test_export_lun_fails_with_exception(self):
        lun_id = '1'
        igroup = 'test-igroup-1'
        response = {'code': 14000, 'message': 'Generic error'}
        failure = exception.ViolinBackendErr

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd_and_verify = mock.Mock(
            side_effect=failure(response['message']))
        self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)

        self.assertRaises(failure, self.driver._export_lun,
                          VOLUME, CONNECTOR, igroup)

    def test_unexport_lun(self):
        response = {'code': 0, 'message': ''}

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd_and_verify = mock.Mock(
            return_value=response)

        result = self.driver._unexport_lun(VOLUME)

        self.driver.common._send_cmd_and_verify.assert_called_with(
            self.driver.common.vip.lun.unexport_lun,
            self.driver.common._wait_for_export_config, '',
            [self.driver.common.container, VOLUME['id'], 'all', 'all', 'auto'],
            [VOLUME['id'], 'state=False'])
        self.assertTrue(result is None)

    def test_unexport_lun_fails_with_exception(self):
        response = {'code': 14000, 'message': 'Generic error'}
        failure = exception.ViolinBackendErr

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd_and_verify = mock.Mock(
            side_effect=failure(response['message']))

        self.assertRaises(failure, self.driver._unexport_lun, VOLUME)

    def test_export_snapshot(self):
        lun_id = '1'
        igroup = 'test-igroup-1'
        response = {'code': 0, 'message': ''}

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd = mock.Mock(return_value=response)
        self.driver.common._wait_for_export_config = mock.Mock()
        self.driver.common._get_snapshot_id = mock.Mock(return_value=lun_id)

        result = self.driver._export_snapshot(SNAPSHOT, CONNECTOR, igroup)

        self.driver.common._send_cmd.assert_called_with(
            self.driver.common.vip.snapshot.export_lun_snapshot, '',
            self.driver.common.container, SNAPSHOT['volume_id'],
            SNAPSHOT['id'], igroup, 'all', 'auto')
        self.driver.common._wait_for_export_config.assert_called_with(
            SNAPSHOT['volume_id'], SNAPSHOT['id'], state=True)
        self.driver.common._get_snapshot_id.assert_called_once_with(
            SNAPSHOT['volume_id'], SNAPSHOT['id'])
        self.assertEqual(lun_id, result)

    def test_unexport_snapshot(self):
        response = {'code': 0, 'message': ''}

        self.driver.common.vip = self.setup_mock_vshare()
        self.driver.common._send_cmd = mock.Mock(return_value=response)
        self.driver.common._wait_for_export_config = mock.Mock()

        result = self.driver._unexport_snapshot(SNAPSHOT)

        self.driver.common._send_cmd.assert_called_with(
            self.driver.common.vip.snapshot.unexport_lun_snapshot, '',
            self.driver.common.container, SNAPSHOT['volume_id'],
            SNAPSHOT['id'], 'all', 'all', 'auto', False)
        self.driver.common._wait_for_export_config.assert_called_with(
            SNAPSHOT['volume_id'], SNAPSHOT['id'], state=False)
        self.assertTrue(result is None)

    def test_add_igroup_member(self):
        igroup = 'test-group-1'
        response = {'code': 0, 'message': 'success'}
        wwpns = ['wwn.50:01:43:80:18:6b:3f:65', 'wwn.50:01:43:80:18:6b:3f:67']

        conf = {
            'igroup.add_initiators.return_value': response,
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)

        self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
            return_value=wwpns)

        result = self.driver._add_igroup_member(CONNECTOR, igroup)

        self.driver._convert_wwns_openstack_to_vmem.assert_called_with(
            CONNECTOR['wwpns'])
        self.driver.common.vip.igroup.add_initiators.assert_called_with(
            igroup, wwpns)
        self.assertTrue(result is None)

    def test_build_initiator_target_map(self):
        """Successfully build a map when zoning is enabled."""
        expected_targ_wwns = FC_TARGET_WWPNS
        expected_init_targ_map = FC_INITIATOR_TARGET_MAP

        self.driver.lookup_service = mock.Mock()
        self.driver.lookup_service.get_device_mapping_from_network.\
            return_value = FC_FABRIC_MAP

        (targ_wwns, init_targ_map) = \
            self.driver._build_initiator_target_map(CONNECTOR)

        self.driver.lookup_service.get_device_mapping_from_network.\
            assert_called_with(CONNECTOR['wwpns'], self.driver.gateway_fc_wwns)
        self.assertEqual(set(expected_targ_wwns), set(targ_wwns))
        self.assertEqual(expected_init_targ_map, init_targ_map)

    def test_build_initiator_target_map_no_lookup_service(self):
        """Successfully build a map when zoning is disabled."""
        expected_targ_wwns = FC_TARGET_WWPNS
        expected_init_targ_map = {
            CONNECTOR['wwpns'][0]: FC_TARGET_WWPNS,
            CONNECTOR['wwpns'][1]: FC_TARGET_WWPNS
        }
        self.driver.lookup_service = None

        targ_wwns, init_targ_map = self.driver._build_initiator_target_map(
            CONNECTOR)

        self.assertEqual(expected_targ_wwns, targ_wwns)
        self.assertEqual(expected_init_targ_map, init_targ_map)

    def test_is_initiator_connected_to_array(self):
        """Successfully finds an initiator with remaining active session."""
        converted_wwpns = ['50:01:43:80:18:6b:3f:65',
                           '50:01:43:80:18:6b:3f:67']
        prefix = "/vshare/config/export/container"
        bn = "%s/%s/lun/**" % (prefix, self.driver.common.container)
        resp_binding0 = "%s/%s/lun/%s/target/hba-a1/initiator/%s" \
            % (prefix, self.driver.common.container, VOLUME['id'],
               converted_wwpns[0])
        resp_binding1 = "%s/%s/lun/%s/target/hba-a1/initiator/%s" \
            % (prefix, self.driver.common.container, VOLUME['id'],
               converted_wwpns[1])
        response = {
            resp_binding0: converted_wwpns[0],
            resp_binding1: converted_wwpns[1]
        }

        conf = {
            'basic.get_node_values.return_value': response,
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
        self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
            return_value=converted_wwpns)

        self.assertTrue(self.driver._is_initiator_connected_to_array(
            CONNECTOR))
        self.driver.common.vip.basic.get_node_values.assert_called_with(bn)

    def test_is_initiator_connected_to_array_empty_response(self):
        """Successfully finds no initiators with remaining active sessions."""
        converted_wwpns = ['50:01:43:80:18:6b:3f:65',
                           '50:01:43:80:18:6b:3f:67']
        response = {}

        conf = {
            'basic.get_node_values.return_value': response,
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
        self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
            return_value=converted_wwpns)

        self.assertFalse(self.driver._is_initiator_connected_to_array(
            CONNECTOR))

    def test_update_stats(self):
        backend_name = self.conf.volume_backend_name
        vendor_name = "Violin Memory, Inc."
        tot_bytes = 100 * units.Gi
        free_bytes = 50 * units.Gi
        bn0 = '/cluster/state/master_id'
        bn1 = "/vshare/state/global/1/container/myContainer/total_bytes"
        bn2 = "/vshare/state/global/1/container/myContainer/free_bytes"
        response1 = {bn0: '1'}
        response2 = {bn1: tot_bytes, bn2: free_bytes}

        conf = {
            'basic.get_node_values.side_effect': [response1, response2],
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)

        result = self.driver._update_stats()

        calls = [mock.call(bn0), mock.call([bn1, bn2])]
        self.driver.common.vip.basic.get_node_values.assert_has_calls(calls)
        self.assertEqual(100, self.driver.stats['total_capacity_gb'])
        self.assertEqual(50, self.driver.stats['free_capacity_gb'])
        self.assertEqual(backend_name,
                         self.driver.stats['volume_backend_name'])
        self.assertEqual(vendor_name, self.driver.stats['vendor_name'])
        self.assertTrue(result is None)

    def test_update_stats_fails_data_query(self):
        backend_name = self.conf.volume_backend_name
        vendor_name = "Violin Memory, Inc."
        bn0 = '/cluster/state/master_id'
        response1 = {bn0: '1'}
        response2 = {}

        conf = {
            'basic.get_node_values.side_effect': [response1, response2],
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)

        self.assertTrue(self.driver._update_stats() is None)
        self.assertEqual(0, self.driver.stats['total_capacity_gb'])
        self.assertEqual(0, self.driver.stats['free_capacity_gb'])
        self.assertEqual(backend_name,
                         self.driver.stats['volume_backend_name'])
        self.assertEqual(vendor_name, self.driver.stats['vendor_name'])

    def test_get_active_fc_targets(self):
        bn0 = '/vshare/state/global/*'
        response0 = {'/vshare/state/global/1': 1,
                     '/vshare/state/global/2': 2}
        bn1 = '/vshare/state/global/1/target/fc/**'
        response1 = {'/vshare/state/global/1/target/fc/hba-a1/wwn':
                     'wwn.21:00:00:24:ff:45:fb:22'}
        bn2 = '/vshare/state/global/2/target/fc/**'
        response2 = {'/vshare/state/global/2/target/fc/hba-a1/wwn':
                     'wwn.21:00:00:24:ff:45:e2:30'}
        wwpns = ['21000024ff45fb22', '21000024ff45e230']

        conf = {
            'basic.get_node_values.side_effect':
            [response0, response1, response2],
        }
        self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)

        result = self.driver._get_active_fc_targets()

        calls = [mock.call(bn0), mock.call(bn1), mock.call(bn2)]
        self.driver.common.vip.basic.get_node_values.assert_has_calls(
            calls, any_order=True)
        self.assertEqual(wwpns, result)

    def test_convert_wwns_openstack_to_vmem(self):
        vmem_wwns = ['wwn.50:01:43:80:18:6b:3f:65']
        openstack_wwns = ['50014380186b3f65']
        result = self.driver._convert_wwns_openstack_to_vmem(openstack_wwns)
        self.assertEqual(vmem_wwns, result)

    def test_convert_wwns_vmem_to_openstack(self):
        vmem_wwns = ['wwn.50:01:43:80:18:6b:3f:65']
        openstack_wwns = ['50014380186b3f65']
        result = self.driver._convert_wwns_vmem_to_openstack(vmem_wwns)
        self.assertEqual(openstack_wwns, result)
