#!/usr/bin/python
# Chopu email_transform.xml upgrade script

import isi.gconfig
import isi.fs.quota
import xml.dom.minidom as xml
import isi.app.lib as applib

import logging
import os.path
import shutil
import sys
import syslog
import errno

IFS_BACKUP_PATH = "/ifs/.ifsvar/tmp"
MODULE_PATH = "/ifs/.ifsvar/modules/quotas"
LOG_FILE = os.path.join(IFS_BACKUP_PATH, "chopu-email-transform-upgrade.log")
UPGRADE_FLAG = os.path.join(MODULE_PATH, "email_upgraded")
UPGRADE_LOCK = UPGRADE_FLAG + ".lck"

LEGACY_OVERRIDE = '/etc/mcp/override/email_transform.xml'
LEGACY_BACKUP = os.path.join(MODULE_PATH, "email_transform.xml")

LEGACY_CONFIG_FILES = [
        "/etc/mcp/sys/files/email-transform-reconfig",
        "/etc/mcp/scripts/email_transform.py",
        LEGACY_OVERRIDE,
]

MAX_GCONF_COMMIT_RETRIES = 5

logger = logging.getLogger("chopu_email_transform_upgrade")

def synced_touch(path):
    f = open(path, "w")
    f.flush()
    os.fsync(f.fileno())
    f.close()

def needs_config_upgrade():
    ''' Check the config and return True if upgrade is necessary. '''
    # Just do an up-front check for file existence as we're protected by the
    # lock file.
    return not os.path.exists(UPGRADE_FLAG)

def config_upgrade_done():
    synced_touch(UPGRADE_FLAG)
    os.chmod(UPGRADE_FLAG, 0640)

def get_attr(node, attr, default):
    if node.hasAttribute(attr):
        return node.getAttribute(attr).encode("UTF-8")
    else:
        return default

def load_rules():
    doc = xml.parse(LEGACY_OVERRIDE)

    rules = []
    isi_data = doc.getElementsByTagName('isi-data')
    for isi_datum in isi_data:
        if get_attr(isi_datum, 'file', '') != 'email-transform':
            logger.warn("Invalid override file, wrong isi-data tag")
            continue
        for add_tag in isi_datum.childNodes:
            if add_tag.nodeName != 'add-tag':
                continue
            for source in add_tag.childNodes:
                if source.nodeName != 'source':
                    continue
                source_type = get_attr(source, 'type', '')
                if source_type not in ('local', 'ad', 'nis', 'ldap'):
                    logger.warn("Skipping unknown source type %s"%source_type)
                    continue
                for rule in source.childNodes:
                    if rule.nodeName != 'rule':
                        continue
                    transform = {
                            'source': source_type,
                            'prefix': get_attr(rule, 'prefix', ''),
                            'domain': get_attr(rule, 'for', ''),
                            'suffix': get_attr(rule, 'suffix', ''),
                    }
                    if get_attr(rule, 'disabled', False):
                        logger.warn("Skipping disabled rule %s",
                                repr(transform))
                        continue
                    
                    rules.append(transform)

    return rules

def upgrade_config():
    retries = 0
    while True:
        # All nodes will attempt the upgrade.  As soon as one
        # finishes, give up.
        if not needs_config_upgrade():
            logger.debug("Quota email transform config already upgraded")
            break

        # Attempt to update 
        rules = []
        try:
            rules = load_rules()
        except IOError as e:
            if e.errno == errno.ENOENT:
                pass
            else:
                raise

        mcfg = isi.gconfig.open(isi.fs.quota.gcfg)
        ctx = mcfg.ctx_new()
        email = ctx.read('email')
        email['rules'] = rules
        logger.info("Saving new rules: %s"%email)
        ctx.write('email', email)

        try:
            ctx.commit()
            logger.info("Successfully committed email transform config.")
        except isi.gconfig.CommitError:
            if retries >= MAX_GCONF_COMMIT_RETRIES:
                raise
            else:
                logger.info("Upgrade email transform config commit failed. "
                    "Retrying upgrade.")
                retries += 1
                continue

        # Backup the old config just in case.
        if os.path.exists(LEGACY_OVERRIDE) and \
             not os.path.exists(LEGACY_BACKUP):
                 shutil.copy(LEGACY_OVERRIDE, LEGACY_BACKUP)

        config_upgrade_done()
        break

    return True

def main():
    logHandler = logging.FileHandler(LOG_FILE, 'a')
    logHandler.setFormatter(
        logging.Formatter("[%(asctime)s] %(message)s"))
    logger.addHandler(logHandler)
    logger.setLevel(logging.DEBUG)

    try:
        os.chmod(LOG_FILE, 0640)
    except OSError, e:
        # log chmod errors and continue
        syslog.syslog(syslog.LOG_NOTICE,
                      "Error chmod'ing %s (errno: %d)" % (LOG_FILE, e.errno))

    # We need to block all other nodes from continue until we've finished
    # the upgrade
    sentry = None
    try:
        sentry = applib.safelockfile(UPGRADE_LOCK, 'w')
        upgrade_config()
        for file in LEGACY_CONFIG_FILES:
            if os.path.exists(file):
                os.unlink(file)
                logger.info("Removed old config file %s" % file)
    finally:
        if sentry:
            sentry.close()

    return 0

if __name__ == "__main__":
    try:
        sys.exit(main())
    except EnvironmentError, e:
        syslog.syslog(syslog.LOG_NOTICE,
                      "Error running email transform upgrade script, %s"%e)
        sys.exit(0)

