#    Copyright (c) 2013 Dell Inc.
#    Copyright 2013 OpenStack 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.

import time

import mox
import paramiko

from cinder import context
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common import processutils
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers import eqlx


LOG = logging.getLogger(__name__)


class DellEQLSanISCSIDriverTestCase(test.TestCase):

    def setUp(self):
        super(DellEQLSanISCSIDriverTestCase, self).setUp()
        self.configuration = mox.MockObject(conf.Configuration)
        self.configuration.append_config_values(mox.IgnoreArg())
        self.configuration.san_is_local = False
        self.configuration.san_ip = "10.0.0.1"
        self.configuration.san_login = "foo"
        self.configuration.san_password = "bar"
        self.configuration.san_ssh_port = 16022
        self.configuration.san_thin_provision = True
        self.configuration.eqlx_pool = 'non-default'
        self.configuration.eqlx_use_chap = True
        self.configuration.eqlx_group_name = 'group-0'
        self.configuration.eqlx_cli_timeout = 30
        self.configuration.eqlx_cli_max_retries = 5
        self.configuration.eqlx_chap_login = 'admin'
        self.configuration.eqlx_chap_password = 'password'
        self.configuration.volume_name_template = 'volume_%s'
        self._context = context.get_admin_context()
        self.driver = eqlx.DellEQLSanISCSIDriver(
            configuration=self.configuration)
        self.volume_name = "fakevolume"
        self.volid = "fakeid"
        self.connector = {
            'ip': '10.0.0.2',
            'initiator': 'iqn.1993-08.org.debian:01:2227dab76162',
            'host': 'fakehost'}
        self.access_record_output = [
            "ID  Initiator       Ipaddress     AuthMethod UserName   Apply-To",
            "--- --------------- ------------- ---------- ---------- --------",
            "1   iqn.1993-08.org.debian:01:222 *.*.*.*       none        both",
            "       7dab76162"]

        self.fake_iqn = 'iqn.2003-10.com.equallogic:group01:25366:fakev'
        self.driver._group_ip = '10.0.1.6'
        self.properties = {
            'target_discoverd': True,
            'target_portal': '%s:3260' % self.driver._group_ip,
            'target_iqn': self.fake_iqn,
            'volume_id': 1}
        self._model_update = {
            'provider_location': "%s:3260,1 %s 0" % (self.driver._group_ip,
                                                     self.fake_iqn),
            'provider_auth': 'CHAP %s %s' % (
                self.configuration.eqlx_chap_login,
                self.configuration.eqlx_chap_password)
        }

    def _fake_get_iscsi_properties(self, volume):
        return self.properties

    def test_create_volume(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name, 'size': 1}
        self.driver._eql_execute('volume', 'create', volume['name'],
                                 "%sG" % (volume['size']), 'pool',
                                 self.configuration.eqlx_pool,
                                 'thin-provision').\
            AndReturn(['iSCSI target name is %s.' % self.fake_iqn])
        self.driver._eql_execute('volume', 'select', volume['name'],
                                 'multihost-access', 'enable')
        self.mox.ReplayAll()
        model_update = self.driver.create_volume(volume)
        self.assertEqual(model_update, self._model_update)

    def test_delete_volume(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name, 'size': 1}
        self.driver._eql_execute('volume', 'select', volume['name'], 'show')
        self.driver._eql_execute('volume', 'select', volume['name'], 'offline')
        self.driver._eql_execute('volume', 'delete', volume['name'])
        self.mox.ReplayAll()
        self.driver.delete_volume(volume)

    def test_delete_absent_volume(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name, 'size': 1, 'id': self.volid}
        self.driver._eql_execute('volume', 'select', volume['name'], 'show').\
            AndRaise(processutils.ProcessExecutionError(
                stdout='% Error ..... does not exist.\n'))
        self.mox.ReplayAll()
        self.driver.delete_volume(volume)

    def test_ensure_export(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name, 'size': 1}
        self.driver._eql_execute('volume', 'select', volume['name'], 'show')
        self.mox.ReplayAll()
        self.driver.ensure_export({}, volume)

    def test_create_snapshot(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        snapshot = {'name': 'fakesnap', 'volume_name': 'fakevolume_name'}
        snap_name = 'fake_snap_name'
        self.driver._eql_execute('volume', 'select', snapshot['volume_name'],
                                 'snapshot', 'create-now').\
            AndReturn(['Snapshot name is %s' % snap_name])
        self.driver._eql_execute('volume', 'select', snapshot['volume_name'],
                                 'snapshot', 'rename', snap_name,
                                 snapshot['name'])
        self.mox.ReplayAll()
        self.driver.create_snapshot(snapshot)

    def test_create_volume_from_snapshot(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        snapshot = {'name': 'fakesnap', 'volume_name': 'fakevolume_name'}
        volume = {'name': self.volume_name}
        self.driver._eql_execute('volume', 'select', snapshot['volume_name'],
                                 'snapshot', 'select', snapshot['name'],
                                 'clone', volume['name']).\
            AndReturn(['iSCSI target name is %s.' % self.fake_iqn])
        self.driver._eql_execute('volume', 'select', volume['name'],
                                 'multihost-access', 'enable')
        self.mox.ReplayAll()
        model_update = self.driver.create_volume_from_snapshot(volume,
                                                               snapshot)
        self.assertEqual(model_update, self._model_update)

    def test_create_cloned_volume(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        src_vref = {'id': 'fake_uuid'}
        volume = {'name': self.volume_name}
        src_volume_name = self.configuration.\
            volume_name_template % src_vref['id']
        self.driver._eql_execute('volume', 'select', src_volume_name, 'clone',
                                 volume['name']).\
            AndReturn(['iSCSI target name is %s.' % self.fake_iqn])
        self.driver._eql_execute('volume', 'select', volume['name'],
                                 'multihost-access', 'enable')
        self.mox.ReplayAll()
        model_update = self.driver.create_cloned_volume(volume, src_vref)
        self.assertEqual(model_update, self._model_update)

    def test_delete_snapshot(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        snapshot = {'name': 'fakesnap', 'volume_name': 'fakevolume_name'}
        self.driver._eql_execute('volume', 'select', snapshot['volume_name'],
                                 'snapshot', 'delete', snapshot['name'])
        self.mox.ReplayAll()
        self.driver.delete_snapshot(snapshot)

    def test_extend_volume(self):
        new_size = '200'
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name, 'size': 100}
        self.driver._eql_execute('volume', 'select', volume['name'],
                                 'size', "%sG" % new_size)
        self.mox.ReplayAll()
        self.driver.extend_volume(volume, new_size)

    def test_initialize_connection(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name}
        self.stubs.Set(self.driver, "_get_iscsi_properties",
                       self._fake_get_iscsi_properties)
        self.driver._eql_execute('volume', 'select', volume['name'], 'access',
                                 'create', 'initiator',
                                 self.connector['initiator'],
                                 'authmethod', 'chap',
                                 'username',
                                 self.configuration.eqlx_chap_login)
        self.mox.ReplayAll()
        iscsi_properties = self.driver.initialize_connection(volume,
                                                             self.connector)
        self.assertEqual(iscsi_properties['data'],
                         self._fake_get_iscsi_properties(volume))

    def test_terminate_connection(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        volume = {'name': self.volume_name}
        self.driver._eql_execute('volume', 'select', volume['name'], 'access',
                                 'show').AndReturn(self.access_record_output)
        self.driver._eql_execute('volume', 'select', volume['name'], 'access',
                                 'delete', '1')
        self.mox.ReplayAll()
        self.driver.terminate_connection(volume, self.connector)

    def test_do_setup(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        fake_group_ip = '10.1.2.3'
        for feature in ('confirmation', 'paging', 'events', 'formatoutput'):
            self.driver._eql_execute('cli-settings', feature, 'off')
        self.driver._eql_execute('grpparams', 'show').\
            AndReturn(['Group-Ipaddress: %s' % fake_group_ip])
        self.mox.ReplayAll()
        self.driver.do_setup(self._context)
        self.assertEqual(fake_group_ip, self.driver._group_ip)

    def test_update_volume_stats(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        self.driver._eql_execute('pool', 'select',
                                 self.configuration.eqlx_pool, 'show').\
            AndReturn(['TotalCapacity: 111GB', 'FreeSpace: 11GB'])
        self.mox.ReplayAll()
        self.driver._update_volume_stats()
        self.assertEqual(self.driver._stats['total_capacity_gb'], 111.0)
        self.assertEqual(self.driver._stats['free_capacity_gb'], 11.0)

    def test_get_volume_stats(self):
        self.driver._eql_execute = self.mox.\
            CreateMock(self.driver._eql_execute)
        self.driver._eql_execute('pool', 'select',
                                 self.configuration.eqlx_pool, 'show').\
            AndReturn(['TotalCapacity: 111GB', 'FreeSpace: 11GB'])
        self.mox.ReplayAll()
        stats = self.driver.get_volume_stats(refresh=True)
        self.assertEqual(stats['total_capacity_gb'], float('111.0'))
        self.assertEqual(stats['free_capacity_gb'], float('11.0'))
        self.assertEqual(stats['vendor_name'], 'Dell')

    def test_get_space_in_gb(self):
        self.assertEqual(self.driver._get_space_in_gb('123.0GB'), 123.0)
        self.assertEqual(self.driver._get_space_in_gb('123.0TB'), 123.0 * 1024)
        self.assertEqual(self.driver._get_space_in_gb('1024.0MB'), 1.0)

    def test_get_output(self):

        def _fake_recv(ignore_arg):
            return '%s> ' % self.configuration.eqlx_group_name

        chan = self.mox.CreateMock(paramiko.Channel)
        self.stubs.Set(chan, "recv", _fake_recv)
        self.assertEqual(self.driver._get_output(chan), [_fake_recv(None)])

    def test_get_prefixed_value(self):
        lines = ['Line1 passed', 'Line1 failed']
        prefix = ['Line1', 'Line2']
        expected_output = [' passed', None]
        self.assertEqual(self.driver._get_prefixed_value(lines, prefix[0]),
                         expected_output[0])
        self.assertEqual(self.driver._get_prefixed_value(lines, prefix[1]),
                         expected_output[1])

    def test_ssh_execute(self):
        ssh = self.mox.CreateMock(paramiko.SSHClient)
        chan = self.mox.CreateMock(paramiko.Channel)
        transport = self.mox.CreateMock(paramiko.Transport)
        self.mox.StubOutWithMock(self.driver, '_get_output')
        self.mox.StubOutWithMock(chan, 'invoke_shell')
        expected_output = ['NoError: test run']
        ssh.get_transport().AndReturn(transport)
        transport.open_session().AndReturn(chan)
        chan.invoke_shell()
        self.driver._get_output(chan).AndReturn(expected_output)
        cmd = 'this is dummy command'
        chan.send('stty columns 255' + '\r')
        self.driver._get_output(chan).AndReturn(expected_output)
        chan.send(cmd + '\r')
        self.driver._get_output(chan).AndReturn(expected_output)
        chan.close()
        self.mox.ReplayAll()
        self.assertEqual(self.driver._ssh_execute(ssh, cmd), expected_output)

    def test_ssh_execute_error(self):
        ssh = self.mox.CreateMock(paramiko.SSHClient)
        chan = self.mox.CreateMock(paramiko.Channel)
        transport = self.mox.CreateMock(paramiko.Transport)
        self.mox.StubOutWithMock(self.driver, '_get_output')
        self.mox.StubOutWithMock(ssh, 'get_transport')
        self.mox.StubOutWithMock(chan, 'invoke_shell')
        expected_output = ['Error: test run', '% Error']
        ssh.get_transport().AndReturn(transport)
        transport.open_session().AndReturn(chan)
        chan.invoke_shell()
        self.driver._get_output(chan).AndReturn(expected_output)
        cmd = 'this is dummy command'
        chan.send('stty columns 255' + '\r')
        self.driver._get_output(chan).AndReturn(expected_output)
        chan.send(cmd + '\r')
        self.driver._get_output(chan).AndReturn(expected_output)
        chan.close()
        self.mox.ReplayAll()
        self.assertRaises(processutils.ProcessExecutionError,
                          self.driver._ssh_execute, ssh, cmd)

    def test_with_timeout(self):
        @eqlx.with_timeout
        def no_timeout(cmd, *args, **kwargs):
            return 'no timeout'

        @eqlx.with_timeout
        def w_timeout(cmd, *args, **kwargs):
            time.sleep(1)

        self.assertEqual(no_timeout('fake cmd'), 'no timeout')
        self.assertRaises(exception.VolumeBackendAPIException,
                          w_timeout, 'fake cmd', timeout=0.1)

    def test_local_path(self):
        self.assertRaises(NotImplementedError, self.driver.local_path, '')
