#!/usr/bin/env python

import getopt
import getpass
import os
import re
import signal
import string
import sys
import tempfile
import time

try:
    True
except NameError:
    True = 1
    False = 0

def usage(exitCode):
    sys.stderr.write('''
Usage: %s [OPTION]... (reboot|on|off)

Options:
  -a, --array-name ARRAY	Use ARRAY.
  -A, --array-file FILE		Read array information from FILE. The default is
				to first look for $HOME/.array.xml, and then
				/etc/ifs/array.xml.
  -h, --help			Print this message and exit.
  -n, --nodes NODES		Run commands on the given nodes. Can be
				specified multiple times. Must be a list of
				either node names or ranges of node IDs. For
				example: '1,3-5,neal8,10'. If no nodes are
				explicitly listed, the whole array is used.
  -x, --exclude-nodes NODES	Don't run commands on NODES. This argument is
				specified the same way as the -n option is.

Environment Variables:

  ISI_ARRAY_NAME		Specifies default array NAME.
  ISI_ARRAY_FILE		Specifies default array FILE.
  ISI_ARRAY_NODES		Specifies default NODES.

''' % (os.path.basename(sys.argv[0])))
    sys.exit(exitCode)

def main():
    # don't treat ^C special
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    isiNodesArgs = ['isi_nodes']

    # parse options
    try:
	opts, args = getopt.getopt(sys.argv[1:], 'a:A:hn:x:',
	    ['array-name=', 'array-file=', 'exclude-nodes=', 'help', 'nodes='])
	for o, a in opts:
	    if o in ['-a', '--array-name']:
		isiNodesArgs += [o, a]
	    if o in ['-A', '--array-file']:
		isiNodesArgs += [o, a]
	    if o in ['-h', '--help']:
		usage(0)
	    if o in ['-n', '--nodes']:
		isiNodesArgs += [o, a]
	    if o in ['-x', '--exclude-nodes']:
		isiNodesArgs += [o, a]
    except getopt.GetoptError:
	usage(1)

    if len(args) != 1:
        usage(1)
    command = args[0]

    try:
	# figure out which nodes to run on
	powerctls = runIsiNodes(isiNodesArgs)

	# run everything
	exitStatus = 0
        first = 1
	for powerctl in powerctls:
            if first:
                first = 0
                sleep = 0
            else:
                sleep = 1
	    child = os.fork()
	    if not child:
                if sleep:
                    time.sleep(30)
                args = ['powerctlall', powerctl, command]
                os.execvp(args[0], args)
	    else:
		exitStatus = max(waitForPid(child), exitStatus)

	sys.exit(exitStatus)

    except EnvironmentError, e:
	error(str(e))

def runIsiNodes(args):
    nodes = []
    pipeRead, pipeWrite = os.pipe()
    child = os.fork()
    if child:
        os.close(pipeWrite)
        pipeIn = os.fdopen(pipeRead, 'r')
        pattern = re.compile('(.+)')
        while True:
            line = pipeIn.readline()
            if not len(line):
                break
            match = pattern.match(line)
            if not match:
                error('bad line from isi_names: %s' % line.rstrip())
            nodes.append(match.group(1))
        return nodes
    else:
        os.close(pipeRead)
        os.dup2(pipeWrite, 1)
        if pipeWrite != 1:
            os.close(pipeWrite)
        os.execvp('isi_nodes', args + [r'%{powerctl}'])

def waitForPid(pid):
    while True:
	pid, status = os.waitpid(pid, 0)
	if os.WIFEXITED(status):
	    return os.WEXITSTATUS(status)
	elif os.WIFSIGNALED(status):
	    return 1

def error(str):
    print >> sys.stderr, os.path.basename(sys.argv[0]) + ':', str
    sys.exit(1)

if __name__ == '__main__':
    main()
