#!/usr/bin/python
# JAWS SMB upgrade script

import os
import syslog
import time
import logging
import subprocess
import shutil

import sys

import isi.app.lib.upgrade_api as uapi
import isi.config as iconf
import isi.sys.version as sysver
from isi.app.lib import safelockfile

from ctypes import *
smb_upgrade_lib = cdll.LoadLibrary('/usr/lib/libisi_smb_upgrade.so')

IFS_BACKUP_PATH = "/ifs/.ifsvar/tmp"

MAIN_GCONFIG_FILE = "/ifs/.ifsvar/main_config.gc"
MAIN_GCONFIG_BACKUP_FILE = os.path.join(IFS_BACKUP_PATH, "main_config_gc.bak")

LOG_FILE = os.path.join(IFS_BACKUP_PATH, "jaws-smb-upgrade.log")

SMB_UPGRADE_CLEANUP_FILE = os.path.join(IFS_BACKUP_PATH, "jaws-smb-upgraded.cleaning")
SMB_UPGRADE_WAIT_FILE = os.path.join(IFS_BACKUP_PATH, "jaws-smb-upgraded.waiting")
SMB_UPGRADE_FILE = os.path.join(IFS_BACKUP_PATH, "jaws-smb-upgraded")

SMB_UPGRADE_CONFIG_UPDATE_FILE = "/etc/mcp/sys/files/jaws-smb-rolling-upgrade"

class update_share_backend:
    def __init__(self, logger, rolling, verbose, force):
        self.logger = logger
        self.rolling = rolling
        self.verbose = verbose
        self.force = force

    def needs_upgrade(self):
        # Check if an upgrade is necessary
        nstat = sysver.Node_Status()
        pVer = nstat.get_previous_version()

        # This upgrade always needs to run only if we're upgrading across JAWS
        # -mao: it is also run on fresh install (pVer == None) to pick up /ifs
        if pVer is None or pVer < sysver.JAWS_7111_VERSION:
            return True

        return False

    def run(self, logger = None):
        ret = smb_upgrade_lib.smb_upgrade_jaws(c_char_p(LOG_FILE),
                                       c_ulonglong(sysver.JAWS_VERSION),
                                       c_uint(self.rolling),
                                       c_uint(self.verbose),
                                       c_uint(self.force))

        if ret:
            raise Exception

def run_upgrade(test, logger, rolling, verbose, force):
    upgrade = test(logger, rolling, verbose, force)
    if not upgrade.needs_upgrade() and not force:
        return 0

    name = upgrade.__class__.__name__

    if rolling:
        name = "rolling upgrade"
    logger.info("Running jaws-smb-upgrade:%s" % name)

    try:
        # XXXmao: upgrade.run never fails.  It should return error code
        upgrade.run(logger)
        logger.info("Successfully ran %s" % name)
    except Exception as e:
        syslog.syslog(syslog.LOG_ERR, "Error during %s" % name)
        logger.error("Error during %s" % name)

def _setup_logger():
    lnn = "Node %d -" % iconf.getNode()['node id']
    lfmt = lnn + "%(asctime)s - %(levelname)s - %(message)s"

    logger = logging.getLogger("jaws_smb_upgade")
    logger.setLevel(logging.DEBUG)
    fh = logging.FileHandler(LOG_FILE, 'a')
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(logging.Formatter(lfmt))
    logger.addHandler(fh)

    return logger

def main(argv):
    rolling_upgrade = 0
    verbose = 0
    force = 0
    help = 0
    delete_it = 0
    upgrade_sentry = None
    wait_sentry = None
    if (len(argv) > 1):
        for i in range(1, len(argv)):
            if  (argv[i] == "-r"):
                rolling_upgrade = 1
            elif(argv[i] == "-v"):
                verbose = 1
            elif (argv[i] == "-f"):
                force = 1
            elif (argv[i] == "-d"):
                delete_it = 1
            elif (argv[i] == "-h"):
                help = 1

    if help > 0:
        print("Usage: jaws-smb-upgrade [-r|-v|-f|-h]")
        print("\t-r: Perform rolling upgrade")
        print("\t-v: Verbose output")
        print("\t-f: Force upgrade (ignore versioning)")
        print("\t-h: Print usage message and exit")
        print("\t-d: Perform cleanup step on old SMB information in gconfig")
        return 0

    if rolling_upgrade > 0:
        if delete_it > 0:
            syslog.syslog(syslog.LOG_NOTICE, "Incorrect usage of jaws-smb-upgrade: -r and -d cannot be used together")
            return 1
        # check if we need to do rolling upgrades
        if uapi.disk_enabled(uapi.JAWS_RU):
            # Rolling upgrade is done
            if force == 0:
                return 0

        # If the cleanup sentry file has been created, at least one node
        # has entered the cleanup loop. At this point we should stop
        # queueing new config updates.
        if os.path.exists(SMB_UPGRADE_CLEANUP_FILE):
            if force == 0:
                return 0

        try:
            # The waiter lock is so that we one person queued to
            # do next upgrade while a thread is doing current upgrade, as more
            # changes could have occurred during the middle of current upgrade.
            # If someone else is already waiting to do next upgrade, we are done.
            wait_sentry = safelockfile(SMB_UPGRADE_WAIT_FILE, 'w', blocking=False)
        except IOError, e:
            if e.errno == os.errno.EACCES or e.errno == os.errno.EAGAIN:
                # Someone else is already waiting, we are done.
                return 0
            syslog.syslog(syslog.LOG_NOTICE,
                          "Error running smb upgrade script, %s" % e)
            return 0

    # Set up a logger object
    logger = _setup_logger()

    if delete_it > 0:
        # We want to make sure no other rolling upgrade steps are
        # still running, so take locks on the sentry files.
        cleanup_sentry = None
        wait_sentry = safelockfile(SMB_UPGRADE_WAIT_FILE, 'w')
        upgrade_sentry = safelockfile(SMB_UPGRADE_FILE, 'w')

        try:
            if os.path.exists(SMB_UPGRADE_CLEANUP_FILE):
                # we have already cleaned up
                if force == 0:
                    return 0

            logger.info("Running final cleanup stage")

            if verbose:
                logger.info("uapi.disk_enabled: %s" %
                            str(uapi.disk_enabled(uapi.JAWS_RU)))

            # Once we have the locks, we should be in the last step, but we
            # do not want to clean anything if the initial copy did not finish.
            if not smb_upgrade_lib.smb_verify_config_version(c_ulonglong(sysver.JAWS_VERSION)):
                raise Exception("Major error detected; cleanup loop entered "\
                                "without initial upgrade phase completed.")

            # Remove MCP script responsible for starting rolling upgrade
            # config updates
            logger.info("Cleaning rolling upgrade config update script")
            if os.path.exists(SMB_UPGRADE_CONFIG_UPDATE_FILE):
                os.system("isi_for_array rm -f " + SMB_UPGRADE_CONFIG_UPDATE_FILE)
            else:
                logger.warning("config update script does not exist, "\
                        "skipping cleanup")

            # set cleanup file to indicate that we have cleaned up.
            try:
                cleanup_sentry = safelockfile(SMB_UPGRADE_CLEANUP_FILE, 'w', blocking=False)
            except Exception as e:
                if e.errno == os.errno.EACCES or e.errno == os.errno.EAGAIN:
                    # Someone else is already cleaning up
                    logger.info("Cleanup sentry file already locked, someone "\
                            "beat us here. Exiting.")
                    return 0
                else:
                    raise e

            # Make a copy of the old gconfig file
            logger.info("Backing up old share config")
            if not os.path.exists(MAIN_GCONFIG_BACKUP_FILE):
                if (os.path.exists(MAIN_GCONFIG_FILE)):
                    shutil.copy2(MAIN_GCONFIG_FILE, MAIN_GCONFIG_BACKUP_FILE)
            else:
                logger.warning("backup already exists, skipping backup")

            # Delete the old share config
            logger.info("Deleting share info from old config store")
            res = smb_upgrade_lib.smb_delete_gconfig_share_info(c_char_p(LOG_FILE))
            if (res > 0):
                raise Exception("failed to delete old share info, error = %d" % res)

            logger.info("Final cleanup stage complete")

        except Exception as e:
            logger.error("Error during smb upgrade cleanup: %s" % e)
            syslog.syslog(syslog.LOG_NOTICE,
                    "Error during smb upgrade cleanup: %s" % e)
            raise e

        finally:
            if cleanup_sentry:
                cleanup_sentry.close()
            if upgrade_sentry:
                upgrade_sentry.close()
            if wait_sentry:
                wait_sentry.close()

        return 0


    # We need to block all other nodes from continuing until we have finished
    # the upgrade
    try:
        upgrade_sentry = safelockfile(SMB_UPGRADE_FILE, 'w')
    except EnvironmentError, e:
        syslog.syslog(syslog.LOG_NOTICE,
                      "Error running smb upgrade script, %s" % e)
        if wait_sentry:
            wait_sentry.close()
        return 0

    # Unlock waiter lock once we start upgrade
    if wait_sentry:
        wait_sentry.close()

    if os.path.exists(SMB_UPGRADE_CLEANUP_FILE):
        if force == 0:
            # we have already cleaned up.  No more upgrading
            upgrade_sentry.close()
            return 0

    # Run any upgrades/updates
    run_upgrade(update_share_backend, logger, rolling_upgrade, verbose, force)

    upgrade_sentry.close()

    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))
