#!/usr/bin/env python

# isi_imdd_setfail
#
# Used to fail devices from or unfail devices back into imdd.
#
# When failing, the given device's imdd partitions are removed
# from their respective imdds and their disklabels are marked
# as idead.
#
# When unfailing, the given device's imdd partitions are marked
# as none. /etc/rc.d/isi_imdd_start is then executed to re-assimilate
# these partitions back into imdd.

import errno
import getopt
import os
import re
import sys
import isi.app.lib.procs as procs
import isi.hw.hal as hal

imdd_forcepart_app = '/usr/bin/isi_imdd_forcepart'
imdd_config_app = '/usr/bin/isi_imdd_config'

# device name regexes
_reg_devleaf_str = '[a-z]+\d+'
_reg_devleaf = re.compile('^%s$' %_reg_devleaf_str)
_reg_dev = re.compile('^/dev/%s$' % _reg_devleaf_str)

#
# get_exit_status
#
def get_error_code(status):
    rc = -1
    if os.WIFEXITED(status):
        rc = os.WEXITSTATUS(status)
    elif os.WIFSIGNALED(status):
        # child exited due to signal, we should exit via same signal
        os.kill(os.getpid(), os.WTERMSIG(status))
    return rc

#
# remove_drive_from_imdd
#
def remove_drive_from_imdd(device):
    """
    Removes the given device's imdd partitions from their
    respective imdds, if any.

    Returns 0 on success, non-zero on failure
    """
    result = 0
    # Get a list of imdds containing partitions belonging to 
    # the given device
    cmd = (('%s -g | /usr/bin/grep %s | /usr/bin/cut -f 1') %
           (imdd_config_app, device))
    (error, output) = procs.get_cmd_output(cmd)
    if error:
        result = get_error_code(error)
    else:
        # For each matching imdd, get a list of partitions in
        # it that belong to the given device 
        for imdd in output:
            cmd = (('%s -i %s | /usr/bin/tail -1 | ' +
                    '/usr/bin/tr -s "\t " "\n" | /usr/bin/grep %ss1') %
                   (imdd_config_app, imdd, device))
            (error, output) = procs.get_cmd_output(cmd)
            if error:
                result = get_error_code(error)
            else:
                # Remove any such partitions from the imdd
                for partition in output:
                    print '  - Removing %s from %s' % (partition, imdd)
                    cmd = ('%s -x %s %s' %
                           (imdd_config_app, imdd, partition))
                    (error, output) = procs.get_cmd_output(cmd)
                    if error:
                        result = get_error_code(error)
                        print ('    ERROR: unable to remove %s from %s' %
                               (partition, imdd))
    return result

#
# mark_drive_idead
#
def mark_drive_idead(device):
    """
    Marks the given device's two imdd partitions to be 'idead'
    
    Returns 0 on success, non-zero on failure
    """
    result = 0
    for suffix in hal.isAcceleratorClass() and \
        ['s1a', 's1d', 's1f', 's1g', 's1h'] or ['s1a', 's1d']:
        part = '%s%s' % (device, suffix)
        print '  - Setting imddflags for %s to "idead"' % part
        (error, output) = procs.get_cmd_output('%s idead %s' %
                                               (imdd_forcepart_app, part))
        if error:
            result = get_error_code(error)
            print ('    ERROR: unable to set imddflags for %s to "idead"' %
                   part)

    return result    


#
# print_usage_and_exit
#
def print_usage_and_exit(app_name, exit_status):
    print ('USAGE: %s <ACTION> <DEVICE> [<DEVICE2> ...]' %
           app_name)
    print
    print 'ACTIONS:'
    print '  -d, --down     Remove from imdd but do not mark dead'
    print '  -f, --fail     Fail the given device(s)'
    print '  -u, --unfail   Unfail the given device(s)'
    print '  -m, --markonly Mark device(s) but don\'t remirror'
    print
    print 'If fail is specified, the given device(s) will be removed '
    print 'from imdd, and their imdd partitions will be marked idead.'
    print
    print 'If unfail is specified, the imdd partitions of the given '
    print 'device(s) will be marked none, and the isi_imdd_start '
    print 'script will be run to remirror those partitions.'
    sys.exit(exit_status)

#
# argcmp
#
def argcmp(arg, exp):
    """
    Compares the the given argument with the given expected string. If
    they are equal, returns non-zero. If the argument is shorter than
    the expected string and the characters in the argument match the
    corresponding characters in the expected string, non-zero is also
    returned. Otherwise, they are considered non-matching, and zero is
    returned.
    """
    return arg == exp[0:len(arg)]

#
# normalize_dev
#
def normalize_dev(device):
    """
    Normalizes the given device name to /dev/abc0 format.
    
    If the given device does not look like a valid device
    name, None is returned.
    """
    global _reg_devleaf
    global _reg_dev
    if _reg_devleaf.match(device):
        device = '/dev/' + device
    if _reg_dev.match(device):
        return device
    else:
        return None

#
# get_imdd_for_partition
#
def get_imdd_for_partition(partition):
    """
    Returns the imdd device for the given disk partition, or
    None if the partition is not in imdd
    """    
    cmd = (("%s -g | /usr/bin/grep %s | " +
            "/usr/bin/awk '{print $1}'") % (imdd_config_app, partition))
    (error, output) = procs.get_cmd_output(cmd)
    if error or len(output) <= 0:
        return None
    else:
        return output[0]

#
# do_down
#
def do_down(device):
    """
    Downs the given device. Returns 0 on success, non-zero on failure.
    """
    error = remove_drive_from_imdd(device)
    return error

#
# do_fail
#
def do_fail(device):
    """
    Fails the given device. Returns 0 on success, non-zero on failure.
    """
    error = remove_drive_from_imdd(device)
    if not error:
        error = mark_drive_idead(device)
    return error

#
# do_unfail
#
def do_unfail(device):
    """
    Unfails the given device. Returns 0 on success, non-zero on failure.
    """
    result = 0
    for suffix in hal.isAcceleratorClass() and \
        ['s1a', 's1d', 's1f', 's1g', 's1h'] or ['s1a', 's1d']:
        part = '%s%s' % (device, suffix)
        imdd = get_imdd_for_partition(part)
        if imdd == None:
            print '  - Setting imddflags for %s to "none"' % part
            (error, output) = procs.get_cmd_output(('%s none %s') %
                                                   (imdd_forcepart_app, part))
            if error:
                result = get_error_code(error)
                print ('    ERROR: unable to set imddflags for %s to "none"' %
                       part)
        else:
            print '  - %s belongs to %s, ignoring' % (part, imdd)
 
    return result

g_markonly = 0
#
# main
#
def main(args):
    global g_markonly

    app_name = os.path.basename(args[0])

    # process options
    try:
        (opts, params) = getopt.getopt(args[1:], 'hdfum',
                         ['help', 'down', 'fail', 'unfail', 'markonly'])
    except getopt.GetoptError:
        print_usage_and_exit(app_name, 2)
    if len(params) < 1:
        print_usage_and_exit(app_name, 2)
    name = None
    func = None
    for (opt, opt_param) in opts:
        if opt in ['-h', '--help']:
            print_usage_and_exit(app_name, 0)
        elif opt in ['-d', '--down']:
            if name == None:
                name = 'down'
                func = do_down
            elif name != 'down':
                print ('ERROR: cannot specify both %s and down actions' %
                       name)
                print_usage_and_exit(app_name, 2)
        elif opt in ['-f', '--fail']:
            if name == None:
                name = 'fail'
                func = do_fail
            elif name != 'fail':
                print ('ERROR: cannot specify both %s and fail actions' %
                       name)
                print_usage_and_exit(app_name, 2)
        elif opt in ['-u', '--unfail'] :
            if name == None:
                name = 'unfail'
                func = do_unfail
            elif name != 'unfail':
                print ('ERROR: cannot specify both %s and unfail actions' %
                       name)
                print_usage_and_exit(app_name, 2)
        elif opt in ['-m', '--markonly'] :
            g_markonly = 1



    if name == None:
        print_usage_and_exit(app_name, 2)

    # do the deeds
    count = 0
    result = 0
    for param in params:
        device = normalize_dev(param)
        if device != None:
            count += 1
            print '- %sing %s' % (name, device)
            result = func(device)
        else:
            print 'ERROR: invalid device: \'%s\'; skipping' % param
            result = errno.EINVAL

    # Do any needed imdd rebalancing after adding/removing devices 
    if result == 0 and count > 0 and not g_markonly :
        print '- executing isi_imdd_rebalance'
        result = os.system('isi_imdd_rebalance')
        
    sys.exit(result)

#
# Start here
#
if __name__ == '__main__':
    main(sys.argv)
 
