# Copyright 2014 IBM Corp.
# Copyright 2012 OpenStack Foundation
# 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 the IBM FlashSystem volume driver."""

import mock
from oslo_concurrency import processutils
from oslo_utils import units
import six

import random
import re

from cinder import context
from cinder import exception
from cinder import test
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.ibm import flashsystem_fc
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types


class FlashSystemManagementSimulator(object):
    def __init__(self):
        # Default protocol is FC
        self._protocol = 'FC'
        self._volumes_list = {}
        self._hosts_list = {}
        self._mappings_list = {}
        self._next_cmd_error = {
            'lsnode': '',
            'lssystem': '',
            'lsmdiskgrp': ''
        }
        self._errors = {
            # CMMVC50000 is a fake error which indicates that command has not
            # got expected results. This error represents kinds of CLI errors.
            'CMMVC50000': ('', 'CMMVC50000 The command can not be executed '
                               'successfully.')
        }

    @staticmethod
    def _find_unused_id(d):
        ids = []
        for v in d.values():
            ids.append(int(v['id']))
        ids.sort()
        for index, n in enumerate(ids):
            if n > index:
                return six.text_type(index)
        return six.text_type(len(ids))

    @staticmethod
    def _is_invalid_name(name):
        if re.match(r'^[a-zA-Z_][\w ._-]*$', name):
            return False
        return True

    @staticmethod
    def _cmd_to_dict(arg_list):
        no_param_args = [
            'bytes',
            'force'
        ]
        one_param_args = [
            'delim',
            'hbawwpn',
            'host',
            'iogrp',
            'iscsiname',
            'mdiskgrp',
            'name',
            'scsi',
            'size',
            'unit'
        ]

        # All commands should begin with svcinfo or svctask
        if arg_list[0] not in ('svcinfo', 'svctask') or len(arg_list) < 2:
            raise exception.InvalidInput(reason=six.text_type(arg_list))
        ret = {'cmd': arg_list[1]}
        arg_list.pop(0)

        skip = False
        for i in range(1, len(arg_list)):
            if skip:
                skip = False
                continue
            if arg_list[i][0] == '-':
                param = arg_list[i][1:]
                if param in no_param_args:
                    ret[param] = True
                elif param in one_param_args:
                    ret[param] = arg_list[i + 1]
                    skip = True
                else:
                    raise exception.InvalidInput(
                        reason=('unrecognized argument %s') % arg_list[i])
            else:
                ret['obj'] = arg_list[i]
        return ret

    @staticmethod
    def _print_cmd_info(rows, delim=' ', nohdr=False, **kwargs):
        """Generic function for printing information."""
        if nohdr:
            del rows[0]
        for index in range(len(rows)):
            rows[index] = delim.join(rows[index])
        return ('%s' % '\n'.join(rows), '')

    @staticmethod
    def _convert_units_bytes(num, unit):
        unit_array = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
        unit_index = 0

        while unit.lower() != unit_array[unit_index].lower():
            num = num * 1024
            unit_index += 1

        return six.text_type(num)

    def _cmd_lshost(self, **kwargs):
        """lshost command.

        svcinfo lshost -delim !
        svcinfo lshost -delim ! <host>
        """
        if 'obj' not in kwargs:
            rows = []
            rows.append(['id', 'name', 'port_count', 'iogrp_count', 'status'])
            for host in self._hosts_list.values():
                rows.append([host['id'], host['host_name'], '1', '1',
                            'degraded'])
            if len(rows) > 1:
                return self._print_cmd_info(rows=rows, **kwargs)
            else:
                return ('', '')
        else:
            host_name = kwargs['obj'].strip('\'\"')
            if host_name not in self._hosts_list:
                return self._errors['CMMVC50000']
            host = self._hosts_list[host_name]
            rows = []
            rows.append(['id', host['id']])
            rows.append(['name', host['host_name']])
            rows.append(['port_count', '1'])
            rows.append(['type', 'generic'])
            rows.append(['mask', '1111'])
            rows.append(['iogrp_count', '1'])
            rows.append(['status', 'degraded'])
            for port in host['iscsi_names']:
                rows.append(['iscsi_name', port])
                rows.append(['node_logged_in_count', '0'])
                rows.append(['state', 'offline'])
            for port in host['wwpns']:
                rows.append(['WWPN', port])
                rows.append(['node_logged_in_count', '0'])
                rows.append(['state', 'active'])

            if 'delim' in kwargs:
                for index in range(len(rows)):
                    rows[index] = kwargs['delim'].join(rows[index])

            return ('%s' % '\n'.join(rows), '')

    def _cmd_lshostvdiskmap(self, **kwargs):
        """svcinfo lshostvdiskmap -delim ! <host_name>"""

        if 'obj' not in kwargs:
            return self._errors['CMMVC50000']

        host_name = kwargs['obj'].strip('\'\"')
        if host_name not in self._hosts_list:
            return self._errors['CMMVC50000']

        rows = []
        rows.append(['id', 'name', 'SCSI_id', 'vdisk_id', 'vdisk_name',
                     'vdisk_UID'])

        for mapping in self._mappings_list.values():
            if (host_name == '') or (mapping['host'] == host_name):
                volume = self._volumes_list[mapping['vol']]
                rows.append([mapping['id'], mapping['host'],
                            mapping['lun'], volume['id'],
                            volume['name'], volume['vdisk_UID']])

        return self._print_cmd_info(rows=rows, **kwargs)

    def _cmd_lsmdiskgrp(self, **kwargs):
        """svcinfo lsmdiskgrp -gui -bytes -delim ! <pool>"""

        status = 'online'
        if self._next_cmd_error['lsmdiskgrp'] == 'error':
            self._next_cmd_error['lsmdiskgrp'] = ''
            return self._errors['CMMVC50000']

        if self._next_cmd_error['lsmdiskgrp'] == 'status=offline':
            self._next_cmd_error['lsmdiskgrp'] = ''
            status = 'offline'

        rows = [None] * 2
        rows[0] = ['id', 'status', 'mdisk_count', 'vdisk_count', 'capacity',
                   'free_capacity', 'virtual_capacity', 'used_capacity',
                   'real_capacity', 'encrypted', 'type', 'encrypt']
        rows[1] = ['0', status, '1', '0', '3573412790272',
                   '3529432325160', '1693247906775', '277841182',
                   '38203734097', 'no', 'parent', 'no']

        if kwargs['obj'] == 'mdiskgrp0':
            row = rows[1]
        else:
            return self._errors['CMMVC50000']

        objrows = []
        for idx, val in enumerate(rows[0]):
            objrows.append([val, row[idx]])

        if 'delim' in kwargs:
            for index in range(len(objrows)):
                objrows[index] = kwargs['delim'].join(objrows[index])

        return ('%s' % '\n'.join(objrows), '')

    def _cmd_lsnode(self, **kwargs):
        """lsnode command.

        svcinfo lsnode -delim !
        svcinfo lsnode -delim ! <node>
        """

        if self._protocol == 'FC' or self._protocol == 'both':
            port_status = 'active'
        else:
            port_status = 'unconfigured'

        rows1 = [None] * 7
        rows1[0] = ['name', 'node1']
        rows1[1] = ['port_id', '000000000000001']
        rows1[2] = ['port_status', port_status]
        rows1[3] = ['port_speed', '8Gb']
        rows1[4] = ['port_id', '000000000000001']
        rows1[5] = ['port_status', port_status]
        rows1[6] = ['port_speed', '8Gb']

        rows2 = [None] * 7
        rows2[0] = ['name', 'node2']
        rows2[1] = ['port_id', '000000000000002']
        rows2[2] = ['port_status', port_status]
        rows2[3] = ['port_speed', '8Gb']
        rows2[4] = ['port_id', '000000000000002']
        rows2[5] = ['port_status', port_status]
        rows2[6] = ['port_speed', 'N/A']

        rows3 = [None] * 3
        rows3[0] = ['id', 'name', 'UPS_serial_number', 'WWNN', 'status',
                    'IO_group_id', 'IO_group_name', 'config_node',
                    'UPS_unique_id', 'hardware', 'iscsi_name', 'iscsi_alias',
                    'panel_name', 'enclosure_id', 'canister_id',
                    'enclosure_serial_number']
        rows3[1] = ['1', 'node1', '', '0123456789ABCDEF', 'online', '0',
                    'io_grp0', 'yes', '', 'TR1', 'naa.0123456789ABCDEF', '',
                    '01-1', '1', '1', 'H441028']
        rows3[2] = ['2', 'node2', '', '0123456789ABCDEF', 'online', '0',
                    'io_grp0', 'no', '', 'TR1', 'naa.0123456789ABCDEF', '',
                    '01-2', '1', '2', 'H441028']

        if self._next_cmd_error['lsnode'] == 'error':
            self._next_cmd_error['lsnode'] = ''
            return self._errors['CMMVC50000']

        rows = None
        if 'obj' not in kwargs:
            rows = rows3
        elif kwargs['obj'] == '1':
            rows = rows1
        elif kwargs['obj'] == '2':
            rows = rows2
        else:
            return self._errors['CMMVC50000']

        if self._next_cmd_error['lsnode'] == 'header_mismatch':
            rows[0].pop(2)
            self._next_cmd_error['lsnode'] = ''

        return self._print_cmd_info(rows=rows, delim=kwargs.get('delim', None))

    def _cmd_lssystem(self, **kwargs):
        """svcinfo lssystem -delim !"""

        open_access_enabled = 'off'

        if self._next_cmd_error['lssystem'] == 'error':
            self._next_cmd_error['lssystem'] = ''
            return self._errors['CMMVC50000']

        if self._next_cmd_error['lssystem'] == 'open_access_enabled=on':
            self._next_cmd_error['lssystem'] = ''
            open_access_enabled = 'on'

        rows = [None] * 3
        rows[0] = ['id', '0123456789ABCDEF']
        rows[1] = ['name', 'flashsystem_1.2.3.4']
        rows[2] = ['open_access_enabled', open_access_enabled]

        return self._print_cmd_info(rows=rows, **kwargs)

    def _cmd_lsportfc(self, **kwargs):
        """svcinfo lsportfc"""

        if self._protocol == 'FC' or self._protocol == 'both':
            status = 'active'
        else:
            status = 'unconfigured'

        rows = [None] * 3
        rows[0] = ['id', 'canister_id', 'adapter_id', 'port_id', 'type',
                   'port_speed', 'node_id', 'node_name', 'WWPN',
                   'nportid', 'status', 'attachment', 'topology']
        rows[1] = ['0', '1', '1', '1', 'fc',
                   '8Gb', '1', 'node_1', 'AABBCCDDEEFF0011',
                   '000000', status, 'host', 'al']
        rows[2] = ['1', '1', '1', '1', 'fc',
                   '8Gb', '1', 'node_1', 'AABBCCDDEEFF0010',
                   '000000', status, 'host', 'al']
        return self._print_cmd_info(rows=rows, **kwargs)

    def _cmd_lsportip(self, **kwargs):
        """svcinfo lsportip"""

        if self._protocol == 'iSCSI' or self._protocol == 'both':
            IP_address1 = '192.168.1.10'
            IP_address2 = '192.168.1.11'
            state = 'online'
            speed = '8G'
        else:
            IP_address1 = ''
            IP_address2 = ''
            state = ''
            speed = ''

        rows = [None] * 3
        rows[0] = ['id', 'node_id', 'node_name', 'canister_id', 'adapter_id',
                   'port_id', 'IP_address', 'mask', 'gateway', 'IP_address_6',
                   'prefix_6', 'gateway_6', 'MAC', 'duplex', 'state', 'speed',
                   'failover', 'link_state', 'host', 'host_6', 'vlan',
                   'vlan_6', 'adapter_location', 'adapter_port_id']
        rows[1] = ['1', '1', 'node1', '0', '0',
                   '0', IP_address1, '', '', '',
                   '0', '', '11:22:33:44:55:AA', '', state, speed,
                   'no', 'active', '', '', '', '', '0', '0']
        rows[2] = ['2', '2', 'node2', '0', '0',
                   '0', IP_address2, '', '', '',
                   '0', '', '11:22:33:44:55:BB', '', state, speed,
                   'no', 'active', '', '', '', '', '0', '0']

        return self._print_cmd_info(rows=rows, **kwargs)

    def _cmd_lsvdisk(self, **kwargs):
        """cmd: svcinfo lsvdisk -gui -bytes -delim ! <vdisk_name>"""

        if 'obj' not in kwargs or (
                'delim' not in kwargs) or (
                'bytes' not in kwargs):
            return self._errors['CMMVC50000']

        if kwargs['obj'] not in self._volumes_list:
            return self._errors['CMMVC50000']

        vol = self._volumes_list[kwargs['obj']]

        rows = []
        rows.append(['id', vol['id']])
        rows.append(['name', vol['name']])
        rows.append(['status', vol['status']])
        rows.append(['capacity', vol['capacity']])
        rows.append(['vdisk_UID', vol['vdisk_UID']])
        rows.append(['udid', ''])
        rows.append(['open_access_scsi_id', '1'])
        rows.append(['parent_mdisk_grp_id', '0'])
        rows.append(['parent_mdisk_grp_name', 'mdiskgrp0'])

        for index in range(len(rows)):
            rows[index] = kwargs['delim'].join(rows[index])
        return ('%s' % '\n'.join(rows), '')

    def _cmd_lsvdiskhostmap(self, **kwargs):
        """svcinfo lsvdiskhostmap -delim ! <vdisk_name>"""

        if 'obj' not in kwargs or (
                'delim' not in kwargs):
            return self._errors['CMMVC50000']

        vdisk_name = kwargs['obj']
        if vdisk_name not in self._volumes_list:
            return self._errors['CMMVC50000']

        rows = []
        rows.append(['id', 'name', 'SCSI_id', 'host_id', 'host_name',
                     'vdisk_UID', 'IO_group_id', 'IO_group_name'])

        mappings_found = 0
        for mapping in self._mappings_list.values():
            if (mapping['vol'] == vdisk_name):
                mappings_found += 1
                volume = self._volumes_list[mapping['vol']]
                host = self._hosts_list[mapping['host']]
                rows.append([volume['id'], volume['name'], '1', host['id'],
                            host['host_name'], volume['vdisk_UID'],
                            '0', 'mdiskgrp0'])

        if mappings_found:
            return self._print_cmd_info(rows=rows, **kwargs)
        else:
            return ('', '')

    def _cmd_expandvdisksize(self, **kwargs):
        """svctask expandvdisksize -size <size> -unit gb <vdisk_name>"""

        if 'obj' not in kwargs:
            return self._errors['CMMVC50000']
        vol_name = kwargs['obj'].strip('\'\"')

        if 'size' not in kwargs:
            return self._errors['CMMVC50000']
        size = int(kwargs['size'])

        if vol_name not in self._volumes_list:
            return self._errors['CMMVC50000']

        curr_size = int(self._volumes_list[vol_name]['capacity'])
        addition = size * units.Gi
        self._volumes_list[vol_name]['capacity'] = six.text_type(
            curr_size + addition)
        return ('', '')

    def _cmd_mkvdisk(self, **kwargs):
        """mkvdisk command.

        svctask mkvdisk -name <name> -mdiskgrp <mdiskgrp> -iogrp <iogrp>
        -size <size> -unit <unit>
        """

        if 'name' not in kwargs or (
                'size' not in kwargs) or (
                'unit' not in kwargs):
            return self._errors['CMMVC50000']

        vdisk_info = {}
        vdisk_info['id'] = self._find_unused_id(self._volumes_list)
        vdisk_info['name'] = kwargs['name'].strip('\'\"')
        vdisk_info['status'] = 'online'
        vdisk_info['capacity'] = self._convert_units_bytes(
            int(kwargs['size']), kwargs['unit'])
        vdisk_info['vdisk_UID'] = ('60050760') + ('0' * 14) + vdisk_info['id']

        if vdisk_info['name'] in self._volumes_list:
            return self._errors['CMMVC50000']
        else:
            self._volumes_list[vdisk_info['name']] = vdisk_info
            return ('Virtual Disk, id [%s], successfully created' %
                    (vdisk_info['id']), '')

    def _cmd_rmvdisk(self, **kwargs):
        """svctask rmvdisk -force <vdisk_name>"""

        if 'obj' not in kwargs:
            return self._errors['CMMVC50000']

        vdisk_name = kwargs['obj'].strip('\'\"')

        if vdisk_name not in self._volumes_list:
            return self._errors['CMMVC50000']

        del self._volumes_list[vdisk_name]
        return ('', '')

    def _add_port_to_host(self, host_info, **kwargs):
        if 'iscsiname' in kwargs:
            added_key = 'iscsi_names'
            added_val = kwargs['iscsiname'].strip('\'\"')
        elif 'hbawwpn' in kwargs:
            added_key = 'wwpns'
            added_val = kwargs['hbawwpn'].strip('\'\"')
        else:
            return self._errors['CMMVC50000']

        host_info[added_key].append(added_val)

        for v in self._hosts_list.values():
            if v['id'] == host_info['id']:
                continue
            for port in v[added_key]:
                if port == added_val:
                    return self._errors['CMMVC50000']
        return ('', '')

    def _cmd_mkhost(self, **kwargs):
        """mkhost command.

        svctask mkhost -force -hbawwpn <wwpn> -name <host_name>
        svctask mkhost -force -iscsiname <initiator> -name <host_name>
        """

        if 'name' not in kwargs:
            return self._errors['CMMVC50000']

        host_name = kwargs['name'].strip('\'\"')
        if self._is_invalid_name(host_name):
            return self._errors['CMMVC50000']
        if host_name in self._hosts_list:
            return self._errors['CMMVC50000']

        host_info = {}
        host_info['id'] = self._find_unused_id(self._hosts_list)
        host_info['host_name'] = host_name
        host_info['iscsi_names'] = []
        host_info['wwpns'] = []

        out, err = self._add_port_to_host(host_info, **kwargs)
        if not len(err):
            self._hosts_list[host_name] = host_info
            return ('Host, id [%s], successfully created' %
                    (host_info['id']), '')
        else:
            return (out, err)

    def _cmd_addhostport(self, **kwargs):
        """addhostport command.

        svctask addhostport -force -hbawwpn <wwpn> <host>
        svctask addhostport -force -iscsiname <initiator> <host>
        """

        if 'obj' not in kwargs:
            return self._errors['CMMVC50000']
        host_name = kwargs['obj'].strip('\'\"')

        if host_name not in self._hosts_list:
            return self._errors['CMMVC50000']

        host_info = self._hosts_list[host_name]
        return self._add_port_to_host(host_info, **kwargs)

    def _cmd_rmhost(self, **kwargs):
        """svctask rmhost <host>"""

        if 'obj' not in kwargs:
            return self._errors['CMMVC50000']

        host_name = kwargs['obj'].strip('\'\"')
        if host_name not in self._hosts_list:
            return self._errors['CMMVC50000']

        for v in self._mappings_list.values():
            if (v['host'] == host_name):
                return self._errors['CMMVC50000']

        del self._hosts_list[host_name]
        return ('', '')

    def _cmd_mkvdiskhostmap(self, **kwargs):
        """svctask mkvdiskhostmap -host <host> -scsi <lun> <vdisk_name>"""

        mapping_info = {}
        mapping_info['id'] = self._find_unused_id(self._mappings_list)

        if 'host' not in kwargs or (
                'scsi' not in kwargs) or (
                'obj' not in kwargs):
            return self._errors['CMMVC50000']
        mapping_info['host'] = kwargs['host'].strip('\'\"')
        mapping_info['lun'] = kwargs['scsi'].strip('\'\"')
        mapping_info['vol'] = kwargs['obj'].strip('\'\"')

        if mapping_info['vol'] not in self._volumes_list:
            return self._errors['CMMVC50000']

        if mapping_info['host'] not in self._hosts_list:
            return self._errors['CMMVC50000']

        if mapping_info['vol'] in self._mappings_list:
            return self._errors['CMMVC50000']

        for v in self._mappings_list.values():
            if ((v['host'] == mapping_info['host']) and
                    (v['lun'] == mapping_info['lun'])):
                return self._errors['CMMVC50000']

        for v in self._mappings_list.values():
            if (v['lun'] == mapping_info['lun']) and ('force' not in kwargs):
                return self._errors['CMMVC50000']

        self._mappings_list[mapping_info['id']] = mapping_info
        return ('Virtual Disk to Host map, id [%s], successfully created'
                % (mapping_info['id']), '')

    def _cmd_rmvdiskhostmap(self, **kwargs):
        """svctask rmvdiskhostmap -host <host> <vdisk_name>"""

        if 'host' not in kwargs or 'obj' not in kwargs:
            return self._errors['CMMVC50000']
        host = kwargs['host'].strip('\'\"')
        vdisk = kwargs['obj'].strip('\'\"')

        mapping_ids = []
        for v in self._mappings_list.values():
            if v['vol'] == vdisk:
                mapping_ids.append(v['id'])
        if not mapping_ids:
            return self._errors['CMMVC50000']

        this_mapping = None
        for mapping_id in mapping_ids:
            if self._mappings_list[mapping_id]['host'] == host:
                this_mapping = mapping_id
        if this_mapping is None:
            return self._errors['CMMVC50000']

        del self._mappings_list[this_mapping]
        return ('', '')

    def set_protocol(self, protocol):
        self._protocol = protocol

    def execute_command(self, cmd, check_exit_code=True):
        try:
            kwargs = self._cmd_to_dict(cmd)
        except exception.InvalidInput:
            return self._errors['CMMVC50000']

        command = kwargs['cmd']
        del kwargs['cmd']
        func = getattr(self, '_cmd_' + command)
        out, err = func(**kwargs)

        if (check_exit_code) and (len(err) != 0):
            raise processutils.ProcessExecutionError(exit_code=1,
                                                     stdout=out,
                                                     stderr=err,
                                                     cmd=command)
        return (out, err)

    def error_injection(self, cmd, error):
        self._next_cmd_error[cmd] = error


class FlashSystemFakeDriver(flashsystem_fc.FlashSystemFCDriver):
    def __init__(self, *args, **kwargs):
        super(FlashSystemFakeDriver, self).__init__(*args, **kwargs)

    def set_fake_storage(self, fake):
        self.fake_storage = fake

    def _ssh(self, cmd, check_exit_code=True):
        utils.check_ssh_injection(cmd)
        ret = self.fake_storage.execute_command(cmd, check_exit_code)
        return ret


class FlashSystemDriverTestCase(test.TestCase):

    def _set_flag(self, flag, value):
        group = self.driver.configuration.config_group
        self.driver.configuration.set_override(flag, value, group)

    def _reset_flags(self):
        self.driver.configuration.local_conf.reset()
        for k, v in self._def_flags.items():
            self._set_flag(k, v)

    def _generate_vol_info(self,
                           vol_name,
                           vol_size=10,
                           vol_status='available'):
        rand_id = six.text_type(random.randint(10000, 99999))
        if not vol_name:
            vol_name = 'test_volume%s' % rand_id

        return {'name': vol_name,
                'size': vol_size,
                'id': '%s' % rand_id,
                'volume_type_id': None,
                'status': vol_status,
                'mdisk_grp_name': 'mdiskgrp0'}

    def _generate_snap_info(self,
                            vol_name,
                            vol_id,
                            vol_size,
                            vol_status,
                            snap_status='available'):
        rand_id = six.text_type(random.randint(10000, 99999))
        return {'name': 'test_snap_%s' % rand_id,
                'id': rand_id,
                'volume': {'name': vol_name,
                           'id': vol_id,
                           'size': vol_size,
                           'status': vol_status},
                'volume_size': vol_size,
                'status': snap_status,
                'mdisk_grp_name': 'mdiskgrp0'}

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

        self._def_flags = {'san_ip': 'hostname',
                           'san_login': 'username',
                           'san_password': 'password',
                           'flashsystem_connection_protocol': 'FC',
                           'flashsystem_multihostmap_enabled': True}

        self.connector = {
            'host': 'flashsystem',
            'wwnns': ['0123456789abcdef', '0123456789abcdeg'],
            'wwpns': ['abcd000000000001', 'abcd000000000002'],
            'initiator': 'iqn.123456'}

        self.sim = FlashSystemManagementSimulator()
        self.driver = FlashSystemFakeDriver(
            configuration=conf.Configuration(None))
        self.driver.set_fake_storage(self.sim)

        self._reset_flags()
        self.ctxt = context.get_admin_context()
        self.driver.do_setup(None)
        self.driver.check_for_setup_error()

        self.sleeppatch = mock.patch('eventlet.greenthread.sleep')
        self.sleeppatch.start()

    def tearDown(self):
        self.sleeppatch.stop()
        super(FlashSystemDriverTestCase, self).tearDown()

    def test_flashsystem_do_setup(self):
        # case 1: cmd lssystem encounters error
        self.sim.error_injection('lssystem', 'error')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.do_setup, None)

        # case 2: open_access_enabled is not off
        self.sim.error_injection('lssystem', 'open_access_enabled=on')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.do_setup, None)

        # case 3: cmd lsmdiskgrp encounters error
        self.sim.error_injection('lsmdiskgrp', 'error')
        self.assertRaises(exception.InvalidInput,
                          self.driver.do_setup, None)

        # case 4: status is not online
        self.sim.error_injection('lsmdiskgrp', 'status=offline')
        self.assertRaises(exception.InvalidInput,
                          self.driver.do_setup, None)

        # case 5: cmd lsnode encounters error
        self.sim.error_injection('lsnode', 'error')
        self.assertRaises(processutils.ProcessExecutionError,
                          self.driver.do_setup, None)

        # case 6: cmd lsnode header does not match
        self.sim.error_injection('lsnode', 'header_mismatch')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.do_setup, None)

        # case 7: set as FC
        self.sim.set_protocol('FC')
        self.driver.do_setup(None)
        self.assertEqual('FC', self.driver._protocol)

        # case 8: no configured nodes available
        self.sim.set_protocol('unknown')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.do_setup, None)

        # clear environment
        self.sim.set_protocol('FC')
        self.driver.do_setup(None)

    def test_flashsystem_check_for_setup_error(self):
        self._set_flag('san_ip', '')
        self.assertRaises(exception.InvalidInput,
                          self.driver.check_for_setup_error)
        self._reset_flags()

        self._set_flag('san_ssh_port', '')
        self.assertRaises(exception.InvalidInput,
                          self.driver.check_for_setup_error)
        self._reset_flags()

        self._set_flag('san_login', '')
        self.assertRaises(exception.InvalidInput,
                          self.driver.check_for_setup_error)
        self._reset_flags()

        self._set_flag('san_password', None)
        self._set_flag('san_private_key', None)
        self.assertRaises(exception.InvalidInput,
                          self.driver.check_for_setup_error)
        self._reset_flags()

        self._set_flag('flashsystem_connection_protocol', 'foo')
        self.assertRaises(exception.InvalidInput,
                          self.driver.check_for_setup_error)
        self._reset_flags()

        # clear environment
        self.driver.do_setup(None)

    def test_flashsystem_validate_connector(self):
        conn_neither = {'host': 'host'}
        conn_iscsi = {'host': 'host', 'initiator': 'foo'}
        conn_fc = {'host': 'host', 'wwpns': 'bar'}
        conn_both = {'host': 'host', 'initiator': 'foo', 'wwpns': 'bar'}

        protocol = self.driver._protocol

        # case 1: when protocol is FC
        self.driver._protocol = 'FC'
        self.driver.validate_connector(conn_fc)
        self.driver.validate_connector(conn_both)
        self.assertRaises(exception.InvalidConnectorException,
                          self.driver.validate_connector, conn_iscsi)
        self.assertRaises(exception.InvalidConnectorException,
                          self.driver.validate_connector, conn_neither)

        # clear environment
        self.driver._protocol = protocol

    def test_flashsystem_volumes(self):
        # case 1: create volume
        vol = self._generate_vol_info(None)
        self.driver.create_volume(vol)

        # Check whether volume is created successfully
        attributes = self.driver._get_vdisk_attributes(vol['name'])
        attr_size = float(attributes['capacity']) / units.Gi
        self.assertEqual(float(vol['size']), attr_size)

        # case 2: create volume with empty returning value
        with mock.patch.object(FlashSystemFakeDriver,
                               '_ssh') as mock_ssh:
            mock_ssh.return_value = ("", "")
            vol1 = self._generate_vol_info(None)
            self.assertRaises(exception.VolumeBackendAPIException,
                              self.driver.create_volume, vol1)

        # case 3: create volume with error returning value
        with mock.patch.object(FlashSystemFakeDriver,
                               '_ssh') as mock_ssh:
            mock_ssh.return_value = ("CMMVC6070E",
                                     "An invalid or duplicated "
                                     "parameter has been detected.")
            vol2 = self._generate_vol_info(None)
            self.assertRaises(exception.VolumeBackendAPIException,
                              self.driver.create_volume, vol2)

        # case 4: delete volume
        self.driver.delete_volume(vol)

        # case 5: delete volume that doesn't exist (expected not fail)
        vol_no_exist = self._generate_vol_info(None)
        self.driver.delete_volume(vol_no_exist)

    def test_flashsystem_extend_volume(self):
        vol = self._generate_vol_info(None)
        self.driver.create_volume(vol)
        self.driver.extend_volume(vol, '200')
        attrs = self.driver._get_vdisk_attributes(vol['name'])
        vol_size = int(attrs['capacity']) / units.Gi
        self.assertAlmostEqual(vol_size, 200)

        # clear environment
        self.driver.delete_volume(vol)

    def test_flashsystem_connection(self):
        # case 1: initialize_connection/terminate_connection for good path
        vol1 = self._generate_vol_info(None)
        self.driver.create_volume(vol1)
        self.driver.initialize_connection(vol1, self.connector)
        self.driver.terminate_connection(vol1, self.connector)

        # case 2: when volume is not existed
        vol2 = self._generate_vol_info(None)
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.initialize_connection,
                          vol2, self.connector)

        # case 3: _get_vdisk_map_properties raises exception
        with mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                               '_get_vdisk_map_properties') as get_properties:
            get_properties.side_effect = exception.VolumeBackendAPIException
            self.assertRaises(exception.VolumeBackendAPIException,
                              self.driver.initialize_connection,
                              vol1, self.connector)

        # case 4: terminate_connection with no host
        with mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                               '_get_hostvdisk_mappings') as mock_host:
            mock_host.return_value = {}
            vol3 = self._generate_vol_info(None)
            self.driver.create_volume(vol3)
            self.driver.initialize_connection(vol3, self.connector)
            return_value = self.driver.terminate_connection(vol3,
                                                            self.connector)
            self.assertNotEqual({}, return_value['data'])

        # case 5: terminate_connection with host
        vol4 = self._generate_vol_info(None)
        self.driver.create_volume(vol4)
        self.driver.initialize_connection(vol4, self.connector)
        vol5 = self._generate_vol_info(None)
        self.driver.create_volume(vol5)
        self.driver.initialize_connection(vol5, self.connector)
        return_value = self.driver.terminate_connection(vol4,
                                                        self.connector)
        self.assertEqual({}, return_value['data'])

        # clear environment
        self.driver.delete_volume(vol1)
        self.driver.delete_volume(vol2)
        self.driver.delete_volume(vol3)
        self.driver.delete_volume(vol4)
        self.driver.delete_volume(vol5)

    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                       '_create_and_copy_vdisk_data')
    def test_flashsystem_create_snapshot(self, _create_and_copy_vdisk_data):
        # case 1: good path
        vol1 = self._generate_vol_info(None)
        snap1 = self._generate_snap_info(vol1['name'],
                                         vol1['id'],
                                         vol1['size'],
                                         vol1['status'])
        self.driver.create_snapshot(snap1)

        # case 2: when volume status is error
        vol2 = self._generate_vol_info(None, vol_status='error')
        snap2 = self._generate_snap_info(vol2['name'],
                                         vol2['id'],
                                         vol2['size'],
                                         vol2['status'])
        self.assertRaises(exception.InvalidVolume,
                          self.driver.create_snapshot, snap2)

    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                       '_delete_vdisk')
    def test_flashsystem_delete_snapshot(self, _delete_vdisk):
        vol1 = self._generate_vol_info(None)
        snap1 = self._generate_snap_info(vol1['name'],
                                         vol1['id'],
                                         vol1['size'],
                                         vol1['status'])
        self.driver.delete_snapshot(snap1)

    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                       '_create_and_copy_vdisk_data')
    def test_flashsystem_create_volume_from_snapshot(
            self, _create_and_copy_vdisk_data):
        # case 1: good path
        vol = self._generate_vol_info(None)
        snap = self._generate_snap_info(vol['name'],
                                        vol['id'],
                                        vol['size'],
                                        vol['status'])
        self.driver.create_volume_from_snapshot(vol, snap)

        # case 2: when size does not match
        vol = self._generate_vol_info(None, vol_size=100)
        snap = self._generate_snap_info(vol['name'],
                                        vol['id'],
                                        200,
                                        vol['status'])
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.create_volume_from_snapshot,
                          vol, snap)

        # case 3: when snapshot status is not available
        vol = self._generate_vol_info(None)
        snap = self._generate_snap_info(vol['name'],
                                        vol['id'],
                                        vol['size'],
                                        vol['status'],
                                        snap_status='error')
        self.assertRaises(exception.InvalidSnapshot,
                          self.driver.create_volume_from_snapshot,
                          vol, snap)

    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                       '_create_and_copy_vdisk_data')
    def test_flashsystem_create_cloned_volume(
            self, _create_and_copy_vdisk_data):
        # case 1: good path
        vol1 = self._generate_vol_info(None)
        vol2 = self._generate_vol_info(None)
        self.driver.create_cloned_volume(vol2, vol1)

        # case 2: when size does not match
        vol1 = self._generate_vol_info(None, vol_size=10)
        vol2 = self._generate_vol_info(None, vol_size=20)
        self.assertRaises(exception.VolumeDriverException,
                          self.driver.create_cloned_volume,
                          vol2, vol1)

    def test_flashsystem_get_volume_stats(self):
        # case 1: good path
        self._set_flag('reserved_percentage', 25)
        pool = 'mdiskgrp0'
        backend_name = 'flashsystem_1.2.3.4' + '_' + pool

        stats = self.driver.get_volume_stats()

        self.assertEqual(25, stats['reserved_percentage'])
        self.assertEqual('IBM', stats['vendor_name'])
        self.assertEqual('FC', stats['storage_protocol'])
        self.assertEqual(backend_name, stats['volume_backend_name'])

        self._reset_flags()

        # case 2: when lsmdiskgrp returns error
        self.sim.error_injection('lsmdiskgrp', 'error')
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver.get_volume_stats, refresh=True)

    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver,
                       '_copy_vdisk_data')
    def test_flashsystem_create_and_copy_vdisk_data(self, _copy_vdisk_data):
        # case 1: when volume does not exist
        vol1 = self._generate_vol_info(None)
        vol2 = self._generate_vol_info(None)
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver._create_and_copy_vdisk_data,
                          vol1['name'], vol1['id'], vol2['name'], vol2['id'])

        # case 2: good path
        self.driver.create_volume(vol1)
        self.driver._create_and_copy_vdisk_data(
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        self.driver.delete_volume(vol1)
        self.driver.delete_volume(vol2)

        # case 3: _copy_vdisk_data raises exception
        self.driver.create_volume(vol1)
        _copy_vdisk_data.side_effect = exception.VolumeBackendAPIException
        self.assertRaises(
            exception.VolumeBackendAPIException,
            self.driver._create_and_copy_vdisk_data,
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        self.assertEqual(set(), self.driver._vdisk_copy_in_progress)

        # clear environment
        self.driver.delete_volume(vol1)
        self.driver.delete_volume(vol2)

    @mock.patch.object(volume_utils, 'copy_volume')
    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver, '_scan_device')
    @mock.patch.object(flashsystem_fc.FlashSystemFCDriver, '_remove_device')
    @mock.patch.object(utils, 'brick_get_connector_properties')
    def test_flashsystem_copy_vdisk_data(self,
                                         _connector,
                                         _remove_device,
                                         _scan_device,
                                         copy_volume):

        connector = _connector.return_value = self.connector
        vol1 = self._generate_vol_info(None)
        vol2 = self._generate_vol_info(None)
        self.driver.create_volume(vol1)
        self.driver.create_volume(vol2)

        # case 1: no mapped before copy
        self.driver._copy_vdisk_data(
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        (v1_mapped, lun) = self.driver._is_vdisk_map(vol1['name'], connector)
        (v2_mapped, lun) = self.driver._is_vdisk_map(vol2['name'], connector)
        self.assertFalse(v1_mapped)
        self.assertFalse(v2_mapped)

        # case 2: mapped before copy
        self.driver.initialize_connection(vol1, connector)
        self.driver.initialize_connection(vol2, connector)
        self.driver._copy_vdisk_data(
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        (v1_mapped, lun) = self.driver._is_vdisk_map(vol1['name'], connector)
        (v2_mapped, lun) = self.driver._is_vdisk_map(vol2['name'], connector)
        self.assertTrue(v1_mapped)
        self.assertTrue(v2_mapped)
        self.driver.terminate_connection(vol1, connector)
        self.driver.terminate_connection(vol2, connector)

        # case 3: no mapped before copy, raise exception when scan
        _scan_device.side_effect = exception.VolumeBackendAPIException
        self.assertRaises(
            exception.VolumeBackendAPIException,
            self.driver._copy_vdisk_data,
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        (v1_mapped, lun) = self.driver._is_vdisk_map(vol1['name'], connector)
        (v2_mapped, lun) = self.driver._is_vdisk_map(vol2['name'], connector)
        self.assertFalse(v1_mapped)
        self.assertFalse(v2_mapped)

        # case 4: no mapped before copy, raise exception when copy
        copy_volume.side_effect = exception.VolumeBackendAPIException
        self.assertRaises(
            exception.VolumeBackendAPIException,
            self.driver._copy_vdisk_data,
            vol1['name'], vol1['id'], vol2['name'], vol2['id'])
        (v1_mapped, lun) = self.driver._is_vdisk_map(vol1['name'], connector)
        (v2_mapped, lun) = self.driver._is_vdisk_map(vol2['name'], connector)
        self.assertFalse(v1_mapped)
        self.assertFalse(v2_mapped)

        # clear environment
        self.driver.delete_volume(vol1)
        self.driver.delete_volume(vol2)

    def test_flashsystem_connector_to_hostname_prefix(self):
        # Invalid characters will be translated to '-'

        # case 1: host name is unicode with invalid characters
        conn = {'host': u'unicode.test}.abc{.abc'}
        self.assertEqual(u'unicode.test-.abc-.abc',
                         self.driver._connector_to_hostname_prefix(conn))

        # case 2: host name is string with invalid characters
        conn = {'host': 'string.test}.abc{.abc'}
        self.assertEqual('string.test-.abc-.abc',
                         self.driver._connector_to_hostname_prefix(conn))

        # case 3: host name is neither unicode nor string
        conn = {'host': 12345}
        self.assertRaises(exception.NoValidHost,
                          self.driver._connector_to_hostname_prefix,
                          conn)

        # case 4: host name started with number will be translated
        conn = {'host': '192.168.1.1'}
        self.assertEqual('_192.168.1.1',
                         self.driver._connector_to_hostname_prefix(conn))

    def test_flashsystem_create_host(self):
        # case 1: create host
        conn = {
            'host': 'flashsystem',
            'wwnns': ['0123456789abcdef', '0123456789abcdeg'],
            'wwpns': ['abcd000000000001', 'abcd000000000002'],
            'initiator': 'iqn.123456'}
        host = self.driver._create_host(conn)

        # case 2: create host that already exists
        self.assertRaises(processutils.ProcessExecutionError,
                          self.driver._create_host,
                          conn)

        # case 3: delete host
        self.driver._delete_host(host)

        # case 4: create host with empty ports
        conn = {'host': 'flashsystem', 'wwpns': []}
        self.assertRaises(exception.VolumeBackendAPIException,
                          self.driver._create_host,
                          conn)

    def test_flashsystem_find_host_exhaustive(self):
        # case 1: create host and find it
        conn1 = {
            'host': 'flashsystem-01',
            'wwnns': ['1111111111abcdef', '1111111111abcdeg'],
            'wwpns': ['1111111111000001', '1111111111000002'],
            'initiator': 'iqn.111111'}
        conn2 = {
            'host': 'flashsystem-02',
            'wwnns': ['2222222222abcdef', '2222222222abcdeg'],
            'wwpns': ['2222222222000001', '2222222222000002'],
            'initiator': 'iqn.222222'}
        conn3 = {
            'host': 'flashsystem-03',
            'wwnns': ['3333333333abcdef', '3333333333abcdeg'],
            'wwpns': ['3333333333000001', '3333333333000002'],
            'initiator': 'iqn.333333'}
        host1 = self.driver._create_host(conn1)
        host2 = self.driver._create_host(conn2)
        self.assertEqual(
            host2,
            self.driver._find_host_exhaustive(conn2, [host1, host2]))
        self.assertIsNone(self.driver._find_host_exhaustive(conn3,
                                                            [host1, host2]))

        # case 2: hosts contains non-existent host info
        with mock.patch.object(FlashSystemFakeDriver,
                               '_ssh') as mock_ssh:
            mock_ssh.return_value = ("pass", "")
            self.driver._find_host_exhaustive(conn1, [host2])
            self.assertFalse(mock_ssh.called)

        # clear environment
        self.driver._delete_host(host1)
        self.driver._delete_host(host2)

    def test_flashsystem_get_vdisk_params(self):
        # case 1: use default params
        self.driver._get_vdisk_params(None)

        # case 2: use extra params from type
        opts1 = {'storage_protocol': 'FC'}
        opts2 = {'capabilities:storage_protocol': 'FC'}
        opts3 = {'storage_protocol': 'iSCSI'}
        type1 = volume_types.create(self.ctxt, 'opts1', opts1)
        type2 = volume_types.create(self.ctxt, 'opts2', opts2)
        type3 = volume_types.create(self.ctxt, 'opts3', opts3)
        self.assertEqual(
            'FC',
            self.driver._get_vdisk_params(type1['id'])['protocol'])
        self.assertEqual(
            'FC',
            self.driver._get_vdisk_params(type2['id'])['protocol'])
        self.assertRaises(exception.InvalidInput,
                          self.driver._get_vdisk_params,
                          type3['id'])

        # clear environment
        volume_types.destroy(self.ctxt, type1['id'])
        volume_types.destroy(self.ctxt, type2['id'])

    def test_flashsystem_map_vdisk_to_host(self):
        # case 1: no host found
        vol1 = self._generate_vol_info(None)
        self.driver.create_volume(vol1)
        self.assertEqual(
            # lun id shoud begin with 1
            1,
            self.driver._map_vdisk_to_host(vol1['name'], self.connector))

        # case 2: host already exists
        vol2 = self._generate_vol_info(None)
        self.driver.create_volume(vol2)
        self.assertEqual(
            # lun id shoud be sequential
            2,
            self.driver._map_vdisk_to_host(vol2['name'], self.connector))

        # case 3: test if already mapped
        self.assertEqual(
            1,
            self.driver._map_vdisk_to_host(vol1['name'], self.connector))

        # clean environment
        self.driver._unmap_vdisk_from_host(vol1['name'], self.connector)
        self.driver._unmap_vdisk_from_host(vol2['name'], self.connector)
        self.driver.delete_volume(vol1)
        self.driver.delete_volume(vol2)

        # case 4: If there is no vdisk mapped to host, host should be removed
        self.assertIsNone(self.driver._get_host_from_connector(self.connector))
