#!/usr/bin/python -u
# "-u": Force stdin, stdout and stderr to be  totally  unbuffered.
#       To get more accurate log files
# 
# see also 
# http://www.faqts.com/knowledge_base/entry/versions/index.phtml?aid=4419

import datetime
import glob
import logging
import multiprocessing
import os
import shutil
import sys
import time

# check if we run from a bzr checkout
if os.path.exists("./UpgradeTestBackend.py"):
    sys.path.insert(0, "../")

from optparse import OptionParser
from AutoUpgradeTester.UpgradeTestBackend import UpgradeTestBackend

class OutputThread(multiprocessing.Process):
    def __init__(self, filename):
        multiprocessing.Process.__init__(self)
        self.file = os.open(filename, os.O_RDONLY)
    def run(self):
        while True:
            # the read() seems to not block for some reason
            # but return "" ?!?
            s = os.read(self.file, 1024)
            if s:
                print s,
            else:
                time.sleep(0.1)

# FIXME: move this into the generic backend code
def login(backend):
    backend.bootstrap()
    backend.login()

def createBackend(backend_name, profile):
    try:
        backend_full_name = "AutoUpgradeTester.%s" % backend_name
        backend_modul = __import__(backend_full_name, fromlist="AutoUpgradeTester")
        backend_class = getattr(backend_modul, backend_name)
    except (ImportError, AttributeError, TypeError), e:
        print "Can not import: %s (%s) " % (backend_name, e)
        return None
    return backend_class(profile)
    
def testUpgrade(backend_name, profile, add_pkgs, quiet=False):
    backend = createBackend(backend_name, profile)
    assert(backend != None)
    if not os.path.exists(profile):
        print "ERROR: Can't find '%s' " % profile
        return False
    print "Storing the result in '%s'" % backend.resultdir
    # setup output
    outfile = os.path.join(backend.resultdir, "bootstrap.log")
    fd = os.open(outfile, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0644)
    out = OutputThread(outfile)
    out.daemon = True
    if not quiet:
        out.start()
    old_stdout = os.dup(1)
    old_stderr = os.dup(2)
    os.dup2(fd, 1)
    os.dup2(fd, 2)
    print "%s log started" % datetime.datetime.now()
    result = True
    try:
        # init the chroot
        if not backend.bootstrap():
            print "FAILED: bootstrap for '%s'" % profile
            raise "Failed to bootstrap '%s'" % profile
        if add_pkgs:
            if not backend.installPackages(add_pkgs):
                print "FAILED: installPacakges '%s'" % add_pkgs
                raise Exception, "Failed to install packages '%s'" % add_pkgs
        if not backend.upgrade():
            print "FAILED: upgrade for '%s'" % profile
            print "Failure logs can be found here:"
            for n in ["main.log", "apt.log"]:
                print "%s/%s" % (backend.resultdir, n)
            raise Exception, "Failed to upgrade %s" % profile
        if not backend.test():
            print "FAILED: test for '%s'" % profile
            raise Exception, "Failed in post upgrade test %s" % profile
        print "profile: %s worked" % profile
    except Exception, e:
        import traceback
        traceback.print_exc()
        print "Caught exception in testUpgrade for '%s' (%s)" % (profile, e)
        result = False
    finally:
        # print out result details
        print "Logs can be found here:"
        for n in ["main.log", "apt.log"]:
            print "%s/%s" % (backend.resultdir, n)
        # give the output time to settle and kill the daemon
        time.sleep(2)
        if out.is_alive():
            out.terminate()
            out.join()
    # close and restore file descriptors
    os.close(fd)
    os.dup2(old_stdout, 1)
    os.dup2(old_stderr, 2)
    return result

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

    parser = OptionParser()
    parser.add_option("--additinoal", "--additional-pkgs", dest="add_pkgs", 
                      default="",
                      help="add additional pkgs before running the upgrade")
    parser.add_option("-a", "--auto", dest="auto", default=False,
                      action="store_true",
                      help="run all tests in profile/ dir")
    parser.add_option("--bootstrap-only", "--bootstrap-only",dest="bootstrap_only", 
                      default=False,
                      action="store_true",
                      help="only bootstrap the image")
    parser.add_option("--tests-only", "", default=False,
                      action="store_true",
                      help="only run post_upgrade_tests in the image")
    parser.add_option("-l", "--login", dest="login", default=False,
                      action="store_true",
                      help="log into the a profile")
    parser.add_option("-b", "--backend", dest="backend",
                      default="UpgradeTestBackendQemu",
                      help="UpgradeTestBackend to use: UpgradeTestBackendChroot, UpgradeTestBackendQemu")
    parser.add_option("-d", "--diff", dest="gen_diff",
                      default=False, action="store_true",
                      help="generate a diff of the upgraded image versus a fresh installed image")
    parser.add_option("--quiet", "--quiet", default=False, action="store_true",
                      help="quiet operation")
    
    (options, args) = parser.parse_args()

    # save for later
    fd1 = os.dup(1)
    fd2 = os.dup(2)

    # FIXME: move this to a configuration
    base="/usr/share/auto-upgrade-tester/profiles/"
    # check if we have something to do
    profiles = args
    if not profiles and not options.auto:
        print sys.argv[0]
        print "No profile specified, available default profiles:"
        print "\n".join(os.listdir(base))
        print 
        print "Or use '-a' for 'auto' mode"
        sys.exit(1)

    # generic
    res = True
    add_pkgs = []
    if  options.add_pkgs:
        add_pkgs = options.add_pkgs.split(",")
    # auto mode
    if options.auto:
        print "running in auto-mode"
        for d in os.listdir(base):
            os.dup2(fd1, 1)
            os.dup2(fd2, 2)
            print "Testing '%s'" % d
            res &= testUpgrade(options.backend, base+d, add_pkgs)
            sys.exit(not res)
    # profile mode, test the given profiles
    for profile in profiles:
        if not "/" in profile:
            profile = base + profile
        try:
            if options.login:
                backend = createBackend(options.backend, profile)
                login(backend)
            elif options.bootstrap_only:
                backend = createBackend(options.backend, profile)
                backend.bootstrap(force=True)
            elif options.tests_only:
                backend = createBackend(options.backend, profile)
                backend.test()
            elif options.gen_diff:
                backend = createBackend(options.backend, profile)
                backend.genDiff()
            else:
                print "Testing '%s'" % profile
                current_res = testUpgrade(options.backend, profile, 
                                          add_pkgs, options.quiet)
                if current_res:
                    print "Profile '%s' worked" % profile
                else:
                    print "Profile '%s' FAILED" % profile
                res &= current_res
        except Exception, e:
            print "ERROR: exception caught '%s' " % e
            import traceback
            traceback.print_exc()
            res = False

    # return exit code
    sys.exit(not res)
