# Copyright 2017 Veritas Technologies LLC.
#
#    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.
"""
Unit tests for Veritas Access cinder driver.
"""
import hashlib
import json
import tempfile
from unittest import mock
from xml.dom.minidom import Document

from oslo_config import cfg
import requests

from cinder import context
from cinder import exception
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.veritas_access import veritas_iscsi

CONF = cfg.CONF
FAKE_BACKEND = 'fake_backend'


class MockResponse(object):
    def __init__(self):
        self.status_code = 200

    def json(self):
        data = {'fake_key': 'fake_val'}
        return json.dumps(data)


class FakeXML(object):

    def __init__(self):
        self.tempdir = tempfile.mkdtemp()

    def create_vrts_fake_config_file(self):

        target = 'iqn.2017-02.com.veritas:faketarget'
        portal = '1.1.1.1'
        auth_detail = '0'
        doc = Document()

        vrts_node = doc.createElement("VRTS")
        doc.appendChild(vrts_node)

        vrts_target_node = doc.createElement("VrtsTargets")
        vrts_node.appendChild(vrts_target_node)

        target_node = doc.createElement("Target")

        vrts_target_node.appendChild(target_node)

        name_ele = doc.createElement("Name")
        portal_ele = doc.createElement("PortalIP")
        auth_ele = doc.createElement("Authentication")

        name_ele.appendChild(doc.createTextNode(target))
        portal_ele.appendChild(doc.createTextNode(portal))
        auth_ele.appendChild(doc.createTextNode(auth_detail))

        target_node.appendChild(name_ele)
        target_node.appendChild(portal_ele)
        target_node.appendChild(auth_ele)

        filename = 'vrts_config.xml'
        config_file_path = self.tempdir + '/' + filename

        f = open(config_file_path, 'w')
        doc.writexml(f)
        f.close()
        return config_file_path


class fake_volume(object):
    def __init__(self):
        self.id = 'fakeid'
        self.name = 'fakename'
        self.size = 1
        self.snapshot_id = False
        self.metadata = {'dense': True}


class fake_volume2(object):
    def __init__(self):
        self.id = 'fakeid2'
        self.name = 'fakename2'
        self.size = 2
        self.snapshot_id = False
        self.metadata = {'dense': True}


class fake_clone_volume(object):
    def __init__(self):
        self.id = 'fakecloneid'
        self.name = 'fakeclonename'
        self.size = 1
        self.snapshot_id = False


class fake_clone_volume2(object):
    def __init__(self):
        self.id = 'fakecloneid2'
        self.name = 'fakeclonename'
        self.size = 2
        self.snapshot_id = False


class fake_snapshot(object):
    def __init__(self):
        self.id = 'fakeid'
        self.volume_id = 'fakevolumeid'
        self.volume_size = 1


class ACCESSIscsiDriverTestCase(test.TestCase):
    """Tests ACCESSShareDriver."""

    volume = fake_volume()
    volume2 = fake_volume2()
    snapshot = fake_snapshot()
    clone_volume = fake_clone_volume()
    clone_volume2 = fake_clone_volume2()
    connector = {
        'initiator': 'iqn.1994-05.com.fakeinitiator'
    }

    def setUp(self):
        super(ACCESSIscsiDriverTestCase, self).setUp()
        self._create_fake_config()
        lcfg = self.configuration
        self._context = context.get_admin_context()
        self._driver = veritas_iscsi.ACCESSIscsiDriver(configuration=lcfg)
        self._driver.do_setup(self._context)

    def _create_fake_config(self):
        self.mock_object(veritas_iscsi.ACCESSIscsiDriver,
                         '_authenticate_access')
        self.configuration = mock.Mock(spec=conf.Configuration)
        self.configuration.safe_get = self.fake_safe_get
        self.configuration.san_ip = '1.1.1.1'
        self.configuration.san_login = 'user'
        self.configuration.san_password = 'passwd'
        self.configuration.san_api_port = 14161
        self.configuration.vrts_lun_sparse = True
        self.configuration.vrts_target_config = (
            FakeXML().create_vrts_fake_config_file())
        self.configuration.target_port = 3260
        self.configuration.volume_backend_name = FAKE_BACKEND

    def fake_safe_get(self, value):
        try:
            val = getattr(self.configuration, value)
        except AttributeError:
            val = None
        return val

    def test_create_volume(self):
        self.mock_object(self._driver, '_vrts_get_suitable_target')
        self.mock_object(self._driver, '_vrts_get_targets_store')
        self.mock_object(self._driver, '_access_api')

        mylist = []
        target = {}
        target['name'] = 'iqn.2017-02.com.veritas:faketarget'
        target['portal_ip'] = '1.1.1.1'
        target['auth'] = '0'
        mylist.append(target)

        self._driver._vrts_get_suitable_target.return_value = (
            'iqn.2017-02.com.veritas:faketarget')
        self._driver._access_api.return_value = True
        return_list = self._driver._vrts_parse_xml_file(
            self.configuration.vrts_target_config)

        self._driver.create_volume(self.volume)

        self.assertEqual(mylist, return_list)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_create_volume_negative(self):
        self.mock_object(self._driver, '_vrts_get_suitable_target')
        self.mock_object(self._driver, '_vrts_get_targets_store')
        self.mock_object(self._driver, '_access_api')

        self._driver._vrts_get_suitable_target.return_value = (
            'iqn.2017-02.com.veritas:faketarget')
        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.create_volume,
                          self.volume)

    def test_create_volume_negative_no_suitable_target_found(self):
        self.mock_object(self._driver, '_vrts_get_suitable_target')
        self.mock_object(self._driver, '_access_api')

        self._driver._vrts_get_suitable_target.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.create_volume,
                          self.volume)
        self.assertEqual(0, self._driver._access_api.call_count)

    def test_delete_volume(self):
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_access_api')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        length = len(self.volume.id)
        index = int(length / 2)
        name1 = self.volume.id[:index]
        name2 = self.volume.id[index:]
        crc1 = hashlib.md5(name1.encode('utf-8')).hexdigest()[:5]
        crc2 = hashlib.md5(name2.encode('utf-8')).hexdigest()[:5]

        volume_name_to_ret = 'cinder' + '-' + crc1 + '-' + crc2

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}
        self._driver._get_vrts_lun_list.return_value = lun_list

        self._driver._access_api.return_value = True

        self._driver.delete_volume(self.volume)
        self.assertEqual(volume_name_to_ret, va_lun_name)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_delete_volume_negative(self):
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_access_api')

        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.delete_volume,
                          self.volume)

    def test_create_snapshot(self):
        self.mock_object(self._driver, '_access_api')

        self._driver._access_api.return_value = True

        self._driver.create_snapshot(self.snapshot)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_create_snapshot_negative(self):
        self.mock_object(self._driver, '_access_api')

        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.create_snapshot,
                          self.snapshot)

        self.assertEqual(1, self._driver._access_api.call_count)

    def test_delete_snapshot(self):
        self.mock_object(self._driver, '_access_api')

        self._driver._access_api.return_value = True

        self._driver.delete_snapshot(self.snapshot)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_delete_snapshot_negative(self):
        self.mock_object(self._driver, '_access_api')

        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.delete_snapshot,
                          self.snapshot)

        self.assertEqual(1, self._driver._access_api.call_count)

    def test_create_cloned_volume(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_vrts_extend_lun')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list

        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver._access_api.return_value = True

        self._driver.create_cloned_volume(self.clone_volume, self.volume)
        self.assertEqual(1, self._driver._access_api.call_count)
        self.assertEqual(0, self._driver._vrts_extend_lun.call_count)

    def test_create_cloned_volume_of_greater_size(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_vrts_extend_lun')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list

        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver._access_api.return_value = True

        self._driver.create_cloned_volume(self.clone_volume2, self.volume)
        self.assertEqual(1, self._driver._access_api.call_count)
        self.assertEqual(1, self._driver._vrts_extend_lun.call_count)

    def test_create_cloned_volume_negative(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list

        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.create_cloned_volume,
                          self.clone_volume, self.volume)

        self.assertEqual(1, self._driver._access_api.call_count)

    def test_create_volume_from_snapshot(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_vrts_extend_lun')
        self.mock_object(self._driver, '_vrts_get_targets_store')
        self.mock_object(self._driver, '_vrts_get_assigned_store')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        snap_name = self._driver._get_va_lun_name(self.snapshot.id)

        snap = {}
        snap['snapshot_name'] = snap_name
        snap['target_name'] = 'fake_target'

        snapshots = []
        snapshots.append(snap)

        snap_info = {}
        snap_info['output'] = {'output': {'snapshots': snapshots}}

        self._driver._access_api.return_value = snap_info

        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver.create_volume_from_snapshot(self.volume, self.snapshot)
        self.assertEqual(2, self._driver._access_api.call_count)
        self.assertEqual(0, self._driver._vrts_extend_lun.call_count)

    def test_create_volume_from_snapshot_of_greater_size(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_vrts_extend_lun')
        self.mock_object(self._driver, '_vrts_get_targets_store')
        self.mock_object(self._driver, '_vrts_get_assigned_store')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        snap_name = self._driver._get_va_lun_name(self.snapshot.id)

        snap = {}
        snap['snapshot_name'] = snap_name
        snap['target_name'] = 'fake_target'

        snapshots = []
        snapshots.append(snap)

        snap_info = {}
        snap_info['output'] = {'output': {'snapshots': snapshots}}

        self._driver._access_api.return_value = snap_info

        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver.create_volume_from_snapshot(self.volume2, self.snapshot)
        self.assertEqual(2, self._driver._access_api.call_count)
        self.assertEqual(1, self._driver._vrts_extend_lun.call_count)

    def test_create_volume_from_snapshot_negative(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_vrts_get_targets_store')

        snap = {}
        snap['snapshot_name'] = 'fake_snap_name'
        snap['target_name'] = 'fake_target'

        snapshots = []
        snapshots.append(snap)

        snap_info = {}
        snap_info['output'] = {'output': {'snapshots': snapshots}}

        self._driver._access_api.return_value = snap_info

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.create_volume_from_snapshot,
                          self.volume, self.snapshot)

        self.assertEqual(1, self._driver._access_api.call_count)
        self.assertEqual(0, self._driver._vrts_get_targets_store.call_count)

    def test_extend_volume(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list
        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver._access_api.return_value = True

        self._driver.extend_volume(self.volume, 2)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_extend_volume_negative(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')
        self.mock_object(self._driver, '_vrts_is_space_available_in_store')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list
        self._driver._vrts_is_space_available_in_store.return_value = True

        self._driver._access_api.return_value = False

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.extend_volume, self.volume, 2)
        self.assertEqual(1, self._driver._vrts_get_fs_list.call_count)
        self.assertEqual(1, self._driver._access_api.call_count)

    def test_extend_volume_negative_not_volume_found(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_get_fs_list')

        lun = {}
        lun['lun_name'] = 'fake_lun'
        lun['fs_name'] = 'fake_fs'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.extend_volume, self.volume, 2)

        self.assertEqual(0, self._driver._vrts_get_fs_list.call_count)
        self.assertEqual(0, self._driver._access_api.call_count)

    def test_initialize_connection(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_target_initiator_mapping')
        self.mock_object(self._driver, '_vrts_get_iscsi_properties')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)

        lun = {}
        lun['lun_name'] = va_lun_name
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}

        self._driver._get_vrts_lun_list.return_value = lun_list
        self._driver._access_api.return_value = True
        self._driver.initialize_connection(self.volume, self.connector)
        self.assertEqual(1, self._driver._vrts_get_iscsi_properties.call_count)

    def test_initialize_connection_negative(self):
        self.mock_object(self._driver, '_access_api')
        self.mock_object(self._driver, '_get_vrts_lun_list')
        self.mock_object(self._driver, '_vrts_target_initiator_mapping')
        self.mock_object(self._driver, '_vrts_get_iscsi_properties')

        lun = {}
        lun['lun_name'] = 'fakelun'
        lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
        lun_list = {'output': {'output': {'luns': [lun]}}}
        self._driver.LUN_FOUND_INTERVAL = 5

        self._driver._get_vrts_lun_list.return_value = lun_list
        self._driver._access_api.return_value = True

        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver.initialize_connection, self.volume,
                          self.connector)

        self.assertEqual(
            0, self._driver._vrts_target_initiator_mapping.call_count)
        self.assertEqual(0, self._driver._vrts_get_iscsi_properties.call_count)

    def test___vrts_get_iscsi_properties(self):
        self.mock_object(self._driver, '_access_api')

        va_lun_name = self._driver._get_va_lun_name(self.volume.id)
        storage_object = "'/fakestores/fakeio/" + va_lun_name + "'"

        lun_id_list = {}
        lun_id_list['output'] = ("[{'storage_object': " +
                                 storage_object + ", 'index': '1'}]")

        target_name = 'iqn.2017-02.com.veritas:faketarget'

        self._driver._access_api.return_value = lun_id_list

        iscsi_properties_ret_value = {}
        iscsi_properties_ret_value['target_discovered'] = True
        iscsi_properties_ret_value['target_iqn'] = target_name
        iscsi_properties_ret_value['target_portal'] = '1.1.1.1:3260'
        iscsi_properties_ret_value['target_lun'] = 1
        iscsi_properties_ret_value['volume_id'] = 'fakeid'
        iscsi_properties = self._driver._vrts_get_iscsi_properties(self.volume,
                                                                   target_name)

        self.assertEqual(iscsi_properties_ret_value, iscsi_properties)

    def test__access_api(self):
        self.mock_object(requests, 'session')

        provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
        path = '/fake/path'
        input_data = {}
        mock_response = MockResponse()
        session = requests.session

        data = {'fake_key': 'fake_val'}
        json_data = json.dumps(data)

        session.request.return_value = mock_response
        ret_value = self._driver._access_api(session, provider, path,
                                             json.dumps(input_data), 'GET')

        self.assertEqual(json_data, ret_value)

    def test__access_api_negative(self):
        session = self._driver.session
        provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
        path = '/fake/path'
        input_data = {}
        ret_value = self._driver._access_api(session, provider, path,
                                             json.dumps(input_data), 'GET')
        self.assertEqual(False, ret_value)

    def test__get_api(self):
        provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
        tail = '/fake/path'
        ret = self._driver._get_api(provider, tail)

        api_root = 'https://%s/api/access' % (provider)
        to_be_ret = api_root + tail
        self.assertEqual(to_be_ret, ret)

    def test__vrts_target_initiator_mapping_negative(self):
        self.mock_object(self._driver, '_access_api')
        target_name = 'fake_target'
        initiator_name = 'fake_initiator'

        self._driver._access_api.return_value = False
        self.assertRaises(exception.VolumeBackendAPIException,
                          self._driver._vrts_target_initiator_mapping,
                          target_name, initiator_name)

    def test_get_volume_stats(self):
        self.mock_object(self._driver, '_authenticate_access')
        self.mock_object(self._driver, '_vrts_get_targets_store')
        self.mock_object(self._driver, '_vrts_get_fs_list')

        target_list = []
        target_details = {}
        target_details['fs_list'] = ['fs1']
        target_details['wwn'] = 'iqn.2017-02.com.veritas:faketarget'
        target_list.append(target_details)

        self._driver._vrts_get_targets_store.return_value = target_list

        fs_list = []
        fs_dict = {}
        fs_dict['name'] = 'fs1'
        fs_dict['file_storage_capacity'] = 10737418240
        fs_dict['file_storage_used'] = 1073741824
        fs_list.append(fs_dict)

        self._driver._vrts_get_fs_list.return_value = fs_list

        self._driver.get_volume_stats()
        data = {
            'volume_backend_name': FAKE_BACKEND,
            'vendor_name': 'Veritas',
            'driver_version': '1.0',
            'storage_protocol': 'iSCSI',
            'total_capacity_gb': 10,
            'free_capacity_gb': 9,
            'reserved_percentage': 0,
            'thin_provisioning_support': True
        }

        self.assertEqual(data, self._driver._stats)
