#!/usr/bin/python

import errno
import os
import os.path
import sys
import syslog
import time
import traceback
import optparse

import isi.app.lib.confParser as confParser
import isi.gconfig as gcfg
import isi.smartpools.diskpools as spdp

# XXX everything we do in this script must be idempotent

# We need to keep out all upgrades including mav, they all use this lock
MAV_UPGRADE_FILE = os.path.join(spdp.SMARTPOOLS_MODULE_DIR, "mav_upgrade")
NUM_ATTEMPTS = 10
RETRY_DELAY = 2 # Sleep for a couple seconds between retries

OVERRIDE_SYSCTL_CONF = "/etc/mcp/override/sysctl.conf"
VHS_SYSCTL = "efs.bam.disk_pool_min_vhs_pct"
SPILLOVER_SYSCTL = "efs.bam.layout.disk_pool_global_spill"
GCONF_SYSCTL_TREE = "sysctls.entries"

def global_upgrade():
    attempts = 0
    success = False
    while not success:
        try:
            dp_cfg = spdp.open_config()
            if dp_cfg.upgraded_7_1_0():
                return

            # L3 should not be enabled by default for upgraded clusters
            l3_cfg = gcfg.open(gcfg.gcfg_l3_cache)
            l3_cfg_ctx = l3_cfg.ctx_new()
            l3_cfg_ctx.write('default_enabled', False)
            l3_cfg_ctx.commit()

            if not dp_cfg.purposedb_fully_populated():
                syslog.syslog(syslog.LOG_NOTICE,
                        "Postponing SmartPools 7.1.0 upgrade until the purpose "
                        "db is fully populated (should occur after all nodes "
                        "come up)")
                return

            dp_cfg.upgrade_7_1_0()
            dp_cfg.save(ignore_underprovisioned=False)
        except OSError, e:
            if e.errno in [errno.ESTALE, errno.EAGAIN, errno.ENOENT]:
                time.sleep(RETRY_DELAY)
                attempts += 1
                if attempts == NUM_ATTEMPTS:
                    raise
                continue
            else:
                raise
        success = True

    syslog.syslog(syslog.LOG_NOTICE, "Smartpools upgrade to 7.1.0 complete")

def post_first_boot_upgrade():
    attempts = 0
    success = False
    while not success:
        try:
            dp_cfg = spdp.open_config()
            if not dp_cfg.upgraded_7_2_0():
                dp_cfg.upgrade_7_2_0()
                dp_cfg.save(ignore_underprovisioned=False)
        except OSError, e:
            if e.errno in [errno.ESTALE, errno.EAGAIN, errno.ENOENT]:
                time.sleep(RETRY_DELAY)
                attempts += 1
                if attempts == NUM_ATTEMPTS:
                    raise
                continue
            else:
                raise
        success = True

    syslog.syslog(syslog.LOG_NOTICE, "Smartpools upgrade to 7.2.0 complete")

def migrate_sysctl(sysctl):
    if not os.path.exists(OVERRIDE_SYSCTL_CONF):
        return False

    override = confParser.ConfParser(OVERRIDE_SYSCTL_CONF)
    if not override.values.has_key(sysctl):
        return False

    # Move existing sysctl value from user override file to gconfig
    cfg = gcfg.open(gcfg.gcfg_main)
    ctx = cfg.ctx_new()
    entries = ctx.read(GCONF_SYSCTL_TREE)

    # If there is already a gconfig entry for this setting, as well as a user
    # override setting, that means the user manually overrode our setting after
    # upgrade by editing /etc/mcp/override/sysctl.conf. In that case, do
    # nothing.
    #
    # XXX RJH: Counterintuitively, until bug 104131 is fixed, the gconfig entry
    # will override the user override setting. However, since it's unclear how
    # to resolve a conflict in that case, we should still do nothing here.
    if sysctl in [e.get("name") for e in entries]:
        return False

    # Otherwise, add the value we previously set in override.
    entry = {
        'name': sysctl,
        'value': override.values[sysctl].strip()
    }
    entries.append(entry)
    ctx.write(GCONF_SYSCTL_TREE, entries)
    ctx.commit()

    # Now that we've successfully set the value in gcfg, remove it from
    # the user override file
    del override.values[sysctl]
    override.write_and_rename(OVERRIDE_SYSCTL_CONF + ".old")
    return True

def sysctl_upgrade():
    # Move some SmartPools sysctls from override/sysctl.conf to gconfig. Don't
    # bother catching ConfParserError; our main exception handler will take
    # care of logging that.
    migrated_vhs = migrate_sysctl(VHS_SYSCTL)
    migrated_spill = migrate_sysctl(SPILLOVER_SYSCTL)

    if migrated_vhs or migrated_spill:
        syslog.syslog(syslog.LOG_NOTICE,
                      "Smartpools sysctl upgrade to 7.1.0 complete")


if __name__ == '__main__':
    lockfd = None
    try:
        parser = optparse.OptionParser();
        parser.add_option("--postupgrade", "-p", action='store_true',
                          default=False)

        opts, args = parser.parse_args()
        lockfd = os.open(MAV_UPGRADE_FILE, \
                        os.O_RDONLY | os.O_CREAT | os.O_EXLOCK, 0400)

        if opts.postupgrade:
            post_first_boot_upgrade()
        else:
            global_upgrade()
            sysctl_upgrade()
    except Exception, e:
        syslog.syslog(syslog.LOG_WARNING, "%s failed: %s" % (sys.argv[0],
                traceback.format_exc()))
        sys.exit(1)
    finally:
        if lockfd is not None:
            os.close(lockfd)
