import pprint
import os
import sys
import re
import subprocess
import shutil

from integralstor_utils import command

tgtadm = ['tgtadm', '--lld', 'iscsi', '--mode']


def get_targets():
    results = []
    try:
        global tgtadm
        cmd = tgtadm + ['target', '--op', 'show']
        p = subprocess.Popen(cmd,  stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
        lines = p.stdout.readlines()
        retval = p.wait()

        results = []
        acl = []

        current = {}
        lun = {}
        reading_acl = False
        reading_account = False

        for line in lines:
            # print line
            m = re.match('Target (\d+): (\S+)\n$', line)
            if m:
                if current <> {}:
                    results.append(current)
                    current = {}
                current["tid"] = int(m.group(1))
                current["iqn"] = str(m.group(2))
                current["luns"] = []
                reading_acl = False
                reading_luns = False
                reading_account = False

            m = re.match('\s*Account information:\n$', line)
            if m:
                reading_account = True
                reading_luns = False
                reading_acl = False
                current["accounts"] = []
                continue

            m = re.match('\s*LUN information:\n$', line)
            if m:
                reading_luns = True
                reading_acl = False
                reading_account = False
                current["accounts"] = []
                continue

            m = re.match('\s*ACL information:\n$', line)
            if m:
                reading_acl = True
                reading_account = False
                current["acl"] = []
                continue

            if reading_acl:
                m = re.match('\s*(\S+)\n$', line)
                if m:
                    current["acl"].append(str(m.group(1)))

            if reading_account:
                # print 'line : ', line
                m = re.match('\s*(\S+)\s*(\S*)\n$', line)
                if m:
                    grps = m.groups()
                    if grps:
                        d = {}
                        d['user'] = grps[0]
                        if len(grps) > 1 and grps[1] == '(outgoing)':
                            d['type'] = 'outgoing'
                        else:
                            d['type'] = 'incoming'

                        current["accounts"].append(d)
                        # current["accounts"].append(str(m.group(1)))

            m = re.match('\s*LUN: (\d+)\n$', line)
            if m:
                if lun <> {}:
                    current["luns"].append(lun)
                    lun = {}
                lun["id"] = int(m.group(1))
            m = re.match('\s*SCSI ID: (\S+)\s+(\S+)\n$', line)
            if m:
                lun["scsi_vendor"] = str(m.group(1))
                lun["scsi_id"] = str(m.group(2))
            m = re.match('\s*SCSI SN: (\S+)\n$', line)
            if m:
                lun["scsi_sn"] = str(m.group(1))
            m = re.match('\s*Type: (\S+)\n$', line)
            if m:
                if reading_luns:
                    lun["type"] = str(m.group(1))
            m = re.match('\s*Backing store path: (\S+)\n$', line)
            if m:
                lun["path"] = str(m.group(1))
                if lun <> {}:
                    current["luns"].append(lun)
                    lun = {}

        if current <> {}:
            results.append(current)
            current = {}
    except Exception, e:
        return None, 'Error getting targets : %s' % str(e)
    else:
        return results, None


def get_luns_on_subpath(subpath):
    # Given a subpath, return all LUNs that have that path prefix. Used to
    # find LUNs on a pool
    l = []
    try:
        if not subpath.startswith('/'):
            subpath = '/%s' % subpath
        if not subpath.endswith('/'):
            subpath += '/'
        target_list, err = get_targets()
        if err:
            raise Exception(err)
        if target_list:
            for target in target_list:
                if 'luns' not in target or not target['luns']:
                    continue
                for lun in target['luns']:
                    # Skip the /dev/zvol part of the path
                    lun_path = lun['path'][9:]
                    if not lun_path.endswith('/'):
                        lun_path += '/'
                    if subpath in lun_path:
                        l.append(lun)
    except Exception, e:
        return None, 'Error retrieving LUNs on subpath: %s' % str(e)
    else:
        return l, None


def get_target(name):
    target = None
    try:
        if not name:
            raise Exception('No target name specified')
        targets, err = get_targets()
        if err:
            raise Exception(err)
        for t in targets:
            if t['iqn'] == name:
                target = t
                break
    except Exception, e:
        return None, 'Error retrieving specified target : %s' % str(e)
    else:
        return target, None


def create_target(name):
    try:
        targets, err = get_targets()
        if err:
            raise Exception(err)
        highest_tid = 0
        if targets:
            for t in targets:
                if int(t['tid']) > highest_tid:
                    highest_tid = int(t['tid'])
        new_tid = highest_tid + 1
        target_name = 'com.integralstor.integralstor-gridcell:%s' % name
        cmd = 'tgtadm --lld iscsi --mode target --op new --tid %d -T %s' % (
            new_tid, target_name)
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)
    except Exception, e:
        return False, 'Error creating target : %s' % str(e)
    else:
        return True, None


def delete_target(name):
    try:
        target, err = get_target(name)
        if err:
            raise Exception(err)
        if not target:
            raise Exception('Specified target not found')
        cmd = 'tgtadm --lld iscsi --mode target --op delete --tid %d' % target['tid']
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)
    except Exception, e:
        return False, 'Error deleting target : %s' % str(e)
    else:
        return True, None


def create_lun(target_name, backing_store):
    try:
        if not target_name:
            raise Exception('No Target Specified')
        if not backing_store:
            raise Exception('No backing storage volume specified')

        target, err = get_target(target_name)
        if err:
            raise Exception(err)

        if not target:
            raise Exception('Specified target not found')

        luns = target['luns']
        if not luns:
            raise Exception('Error retrieving LUN list')

        highest_lun_id = 0
        for lun in luns:
            if lun['id'] > highest_lun_id:
                highest_lun_id = lun['id']

        new_lun_id = highest_lun_id + 1

        cmd = 'tgtadm --lld iscsi --mode logicalunit --op new --tid %d --lun %d --backing-store %s' % (
            int(target['tid']), new_lun_id, backing_store)
        print cmd
        (ret, rc), err = command.execute_with_rc(cmd, True)
        print ret, rc, err
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)

    except Exception, e:
        return False, 'Error creating LUN : %s' % str(e)
    else:
        return True, None


def delete_lun(target_name, backing_store):

    try:

        if not target_name:
            raise ('No target name specified')
        if not backing_store:
            raise ('No backing store path specified')

        target, err = get_target(target_name)
        if err:
            raise Exception(err)
        if not target:
            raise Exception('Specified target not found.')

        luns = target['luns']
        if not luns:
            raise Exception('Specified target does not have any LUNs.')
        print luns
        lun = None
        for tl in luns:
            if tl['path'] == backing_store:
                lun = tl

        if not lun:
            raise Exception('Specified LUN not found.')

        cmd = 'tgtadm --lld iscsi --mode logicalunit --op delete --tid %d --lun %d' % (
            target['tid'], lun['id'])
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)

    except Exception, e:
        return False, 'Error Deleting Logical Unit: %s' % str(e)
    else:
        return True, None


def add_user_authentication(target_name, authentication_type, username, password):
    try:
        tid = -1
        if not authentication_type:
            raise Exception('No authentication type specified')
        if not target_name:
            raise Exception('No target specified')
        if not username:
            raise Exception('No username specified')
        if not password:
            raise Exception('No password specified')

        target, err = get_target(target_name)
        if err:
            raise Exception(err)
        if not target:
            raise Exception('Specified target not found.')

        cmd1 = 'tgtadm --lld iscsi --mode account --op new --user %s --password %s' % (
            username, password)
        (ret, rc), err = command.execute_with_rc(cmd1)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)

        cmd2 = 'tgtadm --lld iscsi --mode account --op bind --tid %d --user %s' % (
            target['tid'], username)
        if authentication_type == 'outgoing':
            cmd2 += ' --outgoing'

        (ret, rc), err = command.execute_with_rc(cmd2)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        new_user_dict = {}
        new_user_dict['iqn'] = target['iqn']
        new_user_dict['username'] = username
        new_user_dict['type'] = authentication_type
        new_user_dict['pswd'] = password
        conf, err = _generate_targets_conf(new_user_dict)
        if err:
            raise Exception(err)

    except Exception, e:
        return False, 'Error Adding User: %s' % str(e)
    else:
        return True, None


def remove_user_authentication(target_name, username, authentication_type):
    try:
        if not target_name:
            raise "Target Not Specified"
        if not username:
            raise "Username Not Specified"

        target, err = get_target(target_name)
        if err:
            raise Exception(err)
        if not target:
            raise Exception('Specified target not found.')

        cmd = 'tgtadm --lld iscsi --mode account --op unbind --tid %d --user %s' % (
            target['tid'], username)
        if authentication_type == 'outgoing':
            cmd += ' --outgoing'
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)
    except Exception, e:
        return False, 'Error removing user autentication: %s' % str(e)
    else:
        return True, None


def add_acl(target_name, acl):
    try:
        if not target_name:
            raise Exception('No Target Specified')
        if not acl:
            raise Exception('No ACL specified')

        target, err = get_target(target_name)
        if err:
            raise Exception(err)

        if not target:
            raise Exception('Specified target not found')

        acls = target['acl']

        # First remove ALL from the ACL list
        if acls and 'ALL' in acls:
            cmd = 'tgtadm --lld iscsi --mode target --op unbind --tid %d -I ALL' % target['tid']
            (ret, rc), err = command.execute_with_rc(cmd)
            if err:
                raise Exception(err)
            if rc != 0:
                err = ''
                tl, er = command.get_output_list(ret)
                if er:
                    raise Exception(er)
                if tl:
                    err = ','.join(tl)
                tl, er = command.get_error_list(ret)
                if er:
                    raise Exception(er)
                if tl:
                    err = err + ','.join(tl)
                raise Exception('Error removind wildcard ACL : %s' % err)

        cmd = 'tgtadm --lld iscsi --mode target --op bind --tid %d -I %s' % (
            target['tid'], acl)
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)

    except Exception, e:
        return False, 'Error adding ACL: %s' % str(e)
    else:
        return True, None


def remove_acl(target_name, acl):
    try:
        if not target_name:
            raise Exception('No Target Specified')
        if not acl:
            raise Exception('No ACL specified')

        target, err = get_target(target_name)
        if err:
            raise Exception(err)

        if not target:
            raise Exception('Specified target not found')

        acls = target['acl']
        if not acls:
            raise Exception('No ACLs found')

        if acl not in acls:
            raise Exception('Specified ACL not found')

        cmd = 'tgtadm --lld iscsi --mode target --op unbind --tid %d -I %s' % (
            target['tid'], acl)
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            # Could be an initiator name so try this..
            cmd = 'tgtadm --lld iscsi --mode target --op unbind --tid %d --initiator-name %s' % (
                target['tid'], acl)
            ret, rc = command.execute_with_rc(cmd)
            if rc != 0:
                err = ''
                tl, er = command.get_output_list(ret)
                if er:
                    raise Exception(er)
                if tl:
                    err = ','.join(tl)
                tl, er = command.get_error_list(ret)
                if er:
                    raise Exception(er)
                if tl:
                    err = err + ','.join(tl)
                raise Exception(err)
        conf, err = _generate_targets_conf()
        if err:
            raise Exception(err)

    except Exception, e:
        return False, 'Error removing ACL: %s' % str(e)
    else:
        return True, None


def _load_targets_conf():
    targets = []
    try:
        with open('/etc/tgt/targets.conf', 'r') as f:
            lines = f.readlines()

        targets = []
        target = None
        in_target = False
        for line in lines:
            # print line
            ret = re.match('<target (\S+)>$', line)
            if ret:
                grps = ret.groups()
                if grps:
                    # print 'inside target'
                    in_target = True
                    target = {}
                    target['incoming_users'] = []
                    target['outgoing_users'] = []
                    target['initiator_addresses'] = []
                    target['backing_stores'] = []
                    target['iqn'] = grps[0]
                    continue
            ret = re.match('</target>$', line)
            if ret:
                # print 'out of target'
                if target:
                    targets.append(target)
                in_target = False
                continue

            if in_target:
                ret = re.match('\s*backing-store\s+(\S+)\s*', line)
                if ret:
                    grps = ret.groups()
                    if grps:
                        # print 'matched backing'
                        target['backing_stores'].append(grps[0])

                ret = re.match('\s*initiator-address\s+(\S+)\s*', line)
                if ret:
                    grps = ret.groups()
                    if grps:
                        # print 'matched initiator-addr'
                        target['initiator_addresses'].append(grps[0])

                ret = re.match('\s*incominguser\s+(\S+)\s+(\S+)\s*', line)
                if ret:
                    grps = ret.groups()
                    if grps:
                        # print 'matched incoming-user'
                        d = {}
                        d['username'] = grps[0]
                        d['pswd'] = grps[1]
                        target['incoming_users'].append(d)

                ret = re.match('\s*outgoinguser\s+(\S+)\s+(\S+)\s*', line)
                if ret:
                    grps = ret.groups()
                    if grps:
                        # print 'matched outgoinguser'
                        d = {}
                        d['username'] = grps[0]
                        d['pswd'] = grps[1]
                        target['outgoing_users'].append(d)
    except Exception, e:
        return None, 'Error loading ISCSI config file: %s' % str(e)
    else:
        return targets, None


def _generate_targets_conf(new_user=None):
    try:
        targets, err = get_targets()
        if err:
            raise Exception(err)
        config_targets, err = _load_targets_conf()
        if err:
            raise Exception(err)

        with open('/tmp/targets.conf', 'w') as f:
            f.write('default-driver iscsi\n')
            for target in targets:
                f.write('\n<target %s>\n' % target['iqn'])
                for lun in target['luns']:
                    if lun['path'] and lun['path'] != 'None':
                        f.write('  backing-store %s\n' % lun['path'])
                for acl in target['acl']:
                    if acl != 'ALL':
                        f.write('  initiator-address %s\n' % acl)
                config_target = None
                # First process new users if any
                if new_user and new_user['iqn'] == target['iqn']:
                    if new_user['type'] == 'incoming':
                        f.write('  incominguser %s %s\n' %
                                (new_user['username'], new_user['pswd']))
                    else:
                        f.write('  outgoinguser %s %s\n' %
                                (new_user['username'], new_user['pswd']))
                for account in target['accounts']:
                    # Now process existing users. Take the list from tgtadm,
                    # get pswds from existing config file and write it out
                    # again
                    for ct in config_targets:
                        if ct['iqn'] == target['iqn']:
                            config_target = ct
                            break
                    if account['type'] == 'incoming':
                        for ctiu in config_target['incoming_users']:
                            if ctiu['username'] == account['user']:
                                f.write('  incominguser %s %s\n' %
                                        (account['user'], ctiu['pswd']))
                    else:
                        for ctiu in config_target['outgoing_users']:
                            if ctiu['username'] == account['user']:
                                f.write('  outgoinguser %s %s\n' %
                                        (account['user'], ctiu['pswd']))
                f.write('</target>\n\n')
            f.flush()
            f.close()
        shutil.move('/tmp/targets.conf', '/etc/tgt/targets.conf')
        cmd = 'tgt-admin -e'
        (ret, rc), err = command.execute_with_rc(cmd)
        if err:
            raise Exception(err)
        if rc != 0:
            err = ''
            tl, er = command.get_output_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = ','.join(tl)
            tl, er = command.get_error_list(ret)
            if er:
                raise Exception(er)
            if tl:
                err = err + ','.join(tl)
            raise Exception(err)

    except Exception, e:
        return False, 'Error generating ISCSI config file: %s' % str(e)
    else:
        return True, None
    pass


def main():
    #targets, err = del_lun('com.integralstor.integralstor-gridcell:anand.test.target','/dev/zvol/pool1/disk1')
    #targets, err = get_targets()
    #pp = pprint.PrettyPrinter(indent=4)
    # pp.pprint(targets)
    l, err = get_luns_on_subpath('/fractalio/ds1')
    print l, err

    # print _generate_targets_conf()
    '''
  #targets, err = _load_targets_conf()
  pp = pprint.PrettyPrinter(indent=4)
  pp.pprint(targets)
  '''


if __name__ == '__main__':
    main()


# vim: tabstop=8 softtabstop=0 expandtab ai shiftwidth=4 smarttab
