#!/usr/bin/python2.4

"""
Wait for job with passed in jobid.

The main upgrade algorithm is implemented in upgrade_agent().

Exit status of 0 if job finished successfully, 1 otherwise.
"""

import sys, os
import re
import gettext
import locale
import getopt
import codecs
import time
import xml.dom.minidom
from datetime import datetime, timedelta

# Adjust search path to include rest of n1sh files.
sys.path.append(os.path.join(sys.path[0], 'cli'))
import executor
import n1shmain

ALLOWED_LANGUAGES = ('en',)  # This script understands only english.

JOB_DONE_SUCCESS = ('Completed',)
JOB_DONE_ERROR = ('Warning', 'Error', 'Stopped', 'Timed Out',)

PAUSE_SEC = 5

JOB_STATES = {
        'hss.ui.diagnose.job.state.all' : 'All',
        'hss.ui.diagnose.job.state.complete' : 'Completed',
        'hss.ui.diagnose.job.state.warning' : 'Warning',
        'hss.ui.diagnose.job.state.running' : 'Running',
        'hss.ui.diagnose.job.state.aborted' : 'Stopped',
        'hss.ui.diagnose.job.state.hdabort' : 'Pending Stop',
        'hss.ui.diagnose.job.state.error' : 'Error',
        'hss.ui.diagnose.job.state.timedout' : 'Timed Out',
        'hss.ui.diagnose.job.state.notstarted' : 'Not Started',
        'hss.ui.diagnose.job.state.preflight' : 'Preflight',
    }



def main((cout, cerr), argv):
    pargs = parse_args(cerr, argv)
    if not pargs:
        usage(cerr)
        sys.exit(-1)

    jobid, role, delay, verbose = pargs

    if not verbose:
        cout = make_nop(cout)

    init_locale(cout)
    session = n1shmain.Session(role, 'xml')

    writer = codecs.getwriter(session.encoding)
    cout = writer(cout, 'replace')
    cerr = writer(cerr, 'replace')

    status = 0
    cacao = executor.CacaoExecutor()

    now = (datetime.utcnow() - timedelta(seconds=60)).strftime('%Y-%m-%dT%H:%M:%SZ')
    status = 0

    try:
        try:
            cacao.start(session)

            jstate = wait_for_job(cout, cacao, jobid, now, delay)

            if jstate not in JOB_DONE_SUCCESS:
                status = 1

            print >>cout, 'Job %s finished in state "%s"' % (jobid, jstate)

        finally:
            cacao.stop()
    except KeyError:
        print >>cerr
        print >>cerr, '*** No job with id "%s"' % (jobid)

    return status


def usage(strm):
    print >>strm, 'Wait for a job to finish.\n'
    print >>strm, 'Usage:', os.path.basename(sys.argv[0]), '[--quiet] [--role <role>] [--delay <sec>] <job id>'
    print >>strm
    print >>strm, '\t--quiet    Hide status information'
    print >>strm, '\t--role     The role to run "show job" as'
    print >>strm, '\t--delay    Pause between "show job" calls (min 2 sec)'
    print >>strm


def parse_args(cerr, argv):
    """
    Return (<list of servers>, arguments, loglevel, role) or None if
    there's a problem.
    """
    try:
        opts, args = getopt.gnu_getopt(argv, '', ['quiet', 'delay=', 'role='])
    except getopt.GetoptError, e:
        print >>cerr, e
        print >>cerr
        return

    verbose = True
    role = ''
    delay = PAUSE_SEC

    for o, v in opts:
        if o in ('--quiet',):
            verbose = False
        elif o in ('--delay',):
            delay = int(v)
        elif o in ('--role',):
            role = v

    # Run some checks
    if len(args) != 1:
        return

    if delay < 2:
        delay = 2

    return args[0].strip('"\''), role, delay, verbose


def init_locale(strm):
    """Make sure the message catalog for gettext is configured correctly."""
    # Make sure locale is set correctly.
    try:
        loc = locale.setlocale(locale.LC_ALL, '')
        try:
            lang, enc = loc.split('.')
        except ValueError:
            lang = loc
            enc = ''
        enc = enc.split('@')[0]
        if lang.split('_')[0] not in ALLOWED_LANGUAGES:
            # Set to a default locale if none set.  Let system determine
            # the default encoding.
            def_lang = 'en_US'
            if enc:
                def_lang += '.' + enc

            locale.setlocale(locale.LC_ALL, def_lang)

    except locale.Error, e:
        print >>strm, '%s: %s' % (e.__class__, str(e))
        print >>strm, 'The current locale is not supported.'
        sys.exit(n1shmain.CODE_INTERNAL_ERROR)

    langvar = 'LANGUAGE'
    try:
        l = locale.getlocale(locale.LC_ALL)[0]
        language = os.environ[langvar].split('.')[0]
        print >>strm, '%s = %s; should be "%s"' % (langvar, language, l)
        if language != l:
            raise locale.Error  # Borrow so we don't need our own exception.
    except (KeyError, locale.Error):
        # Use the current locale (which may be from default) to
        # set 'langvar' environment variable for the duration of this run.
        l = locale.getlocale(locale.LC_ALL)
        os.environ[langvar] = '%s' % (l[0])
        print >>strm, 'Setting default %s = "%s"' % (langvar, os.environ[langvar])


    # Note:  If using symlinks for n1sh.py, the "sys.path[0]" trick may not
    #        work.  Use the commented out line (hardcoded path) instead.
    gettext.bindtextdomain('n1sh', os.path.join(sys.path[0], 'cli/locale'))
    #gettext.bindtextdomain('n1sh', '/opt/sun/n1gc/bin/cli/locale')
    gettext.textdomain('n1sh')




# XML results
class Base(object):
    """Some default features."""
    def __str__(self):
        return self.__class__.__name__ + " " + str(vars(self))


class Job(Base):
    """Salient job data."""
    def __init__(self, jobid, status):
        self.id = jobid
        self.status = status

    def __cmp__(self, o):
        return cmp(int(self.id), int(o.id))


def parse_result_jobs(result):
    """Return map of {"id" => Job()} from output xml."""
    alljobs = {}

    if result.status == 0:
        dom = xml.dom.minidom.parseString(result.output)
        for job in dom.getElementsByTagName('job'):
            jid = get_text_from_node(job, 'id')
            jstatus = get_text_from_node(job, 'job_status').strip()
            if not jstatus:
                j = job.getElementsByTagName('job_status')[0]
                msg = j.getElementsByTagName('o:message')[0]
                jstatus = JOB_STATES[msg.getAttribute('key')]
            alljobs[jid] = Job(jid, jstatus)

    return alljobs


# helpers
def get_text_from_node(dom, n):
    """Return the text value of the first node named "n"."""
    return get_text(dom.getElementsByTagName(n)[0].childNodes)


def get_text(nodelist):
    """Return all the gathered text from node."""
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc


def get_job(cacao, id, now):
    """Return job with id "id"."""
    #result = cacao.execute('show job startafter %s' % now)
    result = cacao.execute('show job')
    if result.status != 0:
        #logging.warning(result.output)
        raise JobExecuteError('"show job" failed')
    #print parse_result_jobs(result)[id]
    return parse_result_jobs(result)[id]


def wait_for_job(strm, cacao, id, now, delay):
    """Return whether job completed successfully or failed."""
    strm.write('waiting for job %s.' % (id))
    strm.flush()
    while True:
        strm.write('.')
        strm.flush()
        time.sleep(delay)

        job = get_job(cacao, id, now)
        if job.status in JOB_DONE_SUCCESS + JOB_DONE_ERROR:
            strm.write('\n')
            return job.status

        # Wait a bit
        #for s in range(1, delay):
        #    time.sleep(1)
        #    strm.write('.')
        #    strm.flush()


def make_nop(obj):
    """
    Return a no-op object with the same API as as obj.
    """
    class Obj: pass

    o = Obj()
    for f in [getattr(obj, a) for a in dir(obj)]:
        if not callable(f):
            continue
        setattr(o, f.__name__, lambda *a, **k: None)

    return o


if __name__ == '__main__':
    status = main((sys.stdout, sys.stderr), sys.argv[1:])
    sys.exit(status)




