#!/usr/bin/env python
"""Audit Rolling upgrade Jaws -> Moby."""

import isi.app.lib.upgrade_api as uapi
from contextlib import contextmanager
from collections import namedtuple
import isi.config.lwsm as lwsm
import isi.gconfig as gconfig
import functools
import itertools
import time
import sys

AUDIT_NFS_NAME = "flt_audit_nfs"
AUDIT_SMB_NAME = "flt_audit"
ONEFS_NFS_NAME = "onefs_nfs"
ONEFS_SMB_NAME = "onefs"

LWSM_PATH_MAP = {
    AUDIT_NFS_NAME: "registry.Services.%s" % AUDIT_NFS_NAME,
    AUDIT_SMB_NAME: "registry.Services.%s" % AUDIT_SMB_NAME,
    ONEFS_NFS_NAME: "registry.Services.%s" % ONEFS_NFS_NAME,
    ONEFS_SMB_NAME: "registry.Services.%s" % ONEFS_SMB_NAME
}

LW_SERVICE_CLS = namedtuple("LwService", "name registry_path")

def mk_lw_tuple(service_name):
    """Returns a namedtuple of name, registry_path."""
    return LW_SERVICE_CLS(service_name, LWSM_PATH_MAP[service_name])

AUDIT_NFS = mk_lw_tuple(AUDIT_NFS_NAME)
AUDIT_SMB = mk_lw_tuple(AUDIT_SMB_NAME)
ONEFS_NFS = mk_lw_tuple(ONEFS_NFS_NAME)
ONEFS_SMB = mk_lw_tuple(ONEFS_SMB_NAME)

@contextmanager
def gconfig_ctx_changes(cfg_tree):
    """Context to make changes to passed in gconfig tree.
    Changes are commited after the with statement.
    """
    ctx = gconfig.open(cfg_tree).ctx_new()
    yield ctx
    ctx.commit()

def retry_while_failure(delay=1, max_tries=None):
    """Decorator that calls a function and while that function throws an
    exception it retries calling the function after the given delay."""
    def inner(func):
        """Returns the wrapped function."""
        if max_tries is None:
            gen_num_tries = itertools.repeat(True)
        else:
            gen_num_tries = xrange(max_tries)
        @functools.wraps(func)
        def wrapped(*args, **kwargs):
            """Calls a function and while that function throws an
            exception it retries calling the function after the delay
            in its closure."""
            for dummy in gen_num_tries:
                try:
                    return func(*args, **kwargs)
                except BaseException:
                    pass
                time.sleep(delay)
        return wrapped
    return inner


def get_gconfig_for_path(gconfig_path):
    """Returns gconfig settings for path."""
    return gconfig.open(gconfig.gcfg_main).ctx_new().read(gconfig_path)

def put_dependency_if_absent(ctx, service_path, dependency_name):
    """Adds the passed in the dependency_name to the dependency list of
    the service path passed in if its not already there.
    """
    dependency_path = service_path + ".Dependencies"
    dependency_str = ctx.read(dependency_path)
    dependency_list = dependency_str.split(" ")
    if dependency_name not in dependency_list:
        dependency_list.append(dependency_name)
    ctx.write(dependency_path, " ".join(dependency_list))

def set_enabled_on(ctx, service_path):
    """Sets Enabled on for the service path passed if its disabled."""
    enabled_path = service_path + ".Enabled"
    enabled = ctx.read(enabled_path)
    if not enabled:
        ctx.write(enabled_path, 1)

def is_smb_audit_enabled():
    """Return bool value for if smb audit is enabled."""
    smb_audit_gconfig = get_gconfig_for_path(AUDIT_SMB.registry_path)
    return smb_audit_gconfig.get("Enabled", 0) == 1

def enable_nfs_audit():
    """Enabled flt_audit_nfs. Adds nfs audit to dependency list of onefs_nfs,
    sets flt_audit_nfs's Enabled on, and then it starts up flt_audit_nfs. """
    with gconfig_ctx_changes(gconfig.gcfg_main) as ctx:
        put_dependency_if_absent(ctx, ONEFS_NFS.registry_path, AUDIT_NFS.name)
        set_enabled_on(ctx, AUDIT_NFS.registry_path)
    lwsm_start_refresh(AUDIT_NFS.name)

@retry_while_failure(delay=1, max_tries=10)
def lwsm_start_refresh(service_name):
    """Calls lwsm start 'service_name' then lwsm refresh 'service_name'.
    If an exception is thrown it retires until its successful.
    """
    lwsm.start(service_name)
    lwsm.refresh(service_name)

@retry_while_failure(delay=1)
def upgrade():
    """Audit upgrade from Jaws -> Moby.
    if smb auditing is enabled then enabled nfs audit. """
    if uapi.disk_enabled(uapi.MOBY_UNFS) and is_smb_audit_enabled():
        enable_nfs_audit()

def main():
    """Run audit's upgrade."""
    upgrade()

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