# -*- coding: utf-8 -*-
__author__ = 'hzp'
import time
import yaml
import threading
import os
import traceback


from common import update_logger, DOWNLOAD_PATH, exec_cmd, init_logger, format_install_path
from deployer import Deployer
from reporters import deploy_reporter

INSTALL_BASE = "/usr/local/easyops"
DATA_BASE = "/data/easyops"

CONCURRENT_AFTER_UPDATE_START_TASKS = 1  # 最大并发执行update_start_postscript.sh线程数
_after_update_start_tasks_sem = threading.Semaphore(CONCURRENT_AFTER_UPDATE_START_TASKS)

_lock = threading.Lock()
_deploy_reporter = None   # 包内的变量，用来上报安装进度

current_path = os.path.dirname(os.path.abspath(__file__))
tar2pkg = {"python_linux": "python", "console-private": "console"}  # 潜规则, tar包名称和实际安装路径的名称不一样


class DeployAction:
    Deploy = "deploy"
    AfterInstallStart = "install_start_postscript"
    AfterUpdateStart = "update_start_postscript"


class ReportError(Exception):
    pass


class ExecShellError(Exception):
    pass


def _handle_install_status(pkg_name, start_time, msg, status, logger):
    global _deploy_reporter
    if status == deploy_reporter.DeployReporter.STATUS_FAILED or status == deploy_reporter.DeployReporter.STATUS_DEPENDENCY_ERROR:
        logger.error(msg)
    else:
        logger.info("deploy pkg({0}) done.".format(pkg_name))

    try:
        _deploy_reporter.finish_task(start_time, pkg_name, status, msg)
    except Exception, e:
        logger.warn("report error, uc server may have ended with some errors, e={0}".format(e))
        raise ReportError


def after_install_start(tar_pkg, pkg_path, install_path):
    """
    执行每个组件的install_start_postscript.sh
    :return:
    """
    global _deploy_reporter, _after_update_start_tasks_sem, tar2pkg
    pkg_name = tar2pkg.get(tar_pkg, tar_pkg)
    logger = init_logger(pkg_name)
    tar_path = "{0}/{1}.tar.gz".format(pkg_path, tar_pkg)  # tar安装包所在路径
    start_time = int(time.time())
    deployer = Deployer(tar_path, install_path, logger)
    try:
        _after_update_start_tasks_sem.acquire()
        is_ok, msg = deployer.after_install_start()
        msg = u"running {0} install_start_postscript.sh , msg={1}".format(pkg_name, msg)
        if not is_ok:
            logger.error(msg)
            status = deploy_reporter.DeployReporter.STATUS_FAILED
        else:
            status = deploy_reporter.DeployReporter.STATUS_OK
            logger.info(msg)
        _handle_install_status(pkg_name, start_time, msg, status, logger)
        if not is_ok:
            raise ExecShellError
    except ReportError, e:
        pass
    except ExecShellError, e:
        raise e
    except Exception, e:
        status = deploy_reporter.DeployReporter.STATUS_FAILED
        err_msg = u"execute pkg({0}) install_start_postscript.sh error. e={1}, traceback={2}".format(pkg_name, e, traceback.format_exc())
        logger.error(err_msg)
        _handle_install_status(pkg_name, start_time, err_msg, status, logger)
    finally:
        logger.info("end deploy %s elapse: %s", pkg_name, time.time() - start_time)
        _after_update_start_tasks_sem.release()


def after_update_start(tar_pkg, pkg_path, install_path):
    """
    执行每个组件的update_start_postscript.sh
    :return:
    """
    global _deploy_reporter, _after_update_start_tasks_sem, tar2pkg
    pkg_name = tar2pkg.get(tar_pkg, tar_pkg)
    logger = init_logger(pkg_name)
    tar_path = "{0}/{1}.tar.gz".format(pkg_path, tar_pkg)  # tar安装包所在路径
    start_time = int(time.time())
    deployer = Deployer(tar_path, install_path, logger)
    try:
        _after_update_start_tasks_sem.acquire()
        is_ok, msg = deployer.after_update_start()
        msg = u"running {0} update_start_postscript.sh , msg={1}".format(pkg_name, msg)
        if not is_ok:
            logger.error(msg)
            status = deploy_reporter.DeployReporter.STATUS_FAILED
        else:
            status = deploy_reporter.DeployReporter.STATUS_OK
            logger.info(msg)
        _handle_install_status(pkg_name, start_time, msg, status, logger)
        if not is_ok:
            raise ExecShellError()
    except ReportError, e:
        pass
    except ExecShellError, e:
        raise e
    except Exception, e:
        status = deploy_reporter.DeployReporter.STATUS_FAILED
        err_msg = u"execute pkg({0}) update_start_postscript.sh error. e={1}, traceback={2}".format(pkg_name, e, traceback.format_exc())
        logger.error(err_msg)
        _handle_install_status(pkg_name, start_time, err_msg, status, logger)
    finally:
        logger.info("end deploy %s elapse: %s", pkg_name, time.time() - start_time)
        _after_update_start_tasks_sem.release()


def deploy_one(args):
    """
    部署单个组件
    :param args.pkg: 包名
    :param args.install_path: 安装路径
    :param args.tar_path: 包所在路径(相对）
    :param args.deploy_action: 部署动作， deploy - 部署， after_install_start - 安装启动后脚本， after_update_start - 升级启动后脚本
    """
    global _deploy_reporter
    _deploy_reporter = deploy_reporter.DeployReporter(
        1, args.org, args.client_ip, args.server)

    pkg_name = args.pkg
    install_path = args.install_path
    deploy_action = args.deploy_action
    tar_path = os.path.join(DOWNLOAD_PATH, args.tar_path)
    start_time = int(time.time())
    logger = init_logger(pkg_name)

    try:
        logger.info("begin deploy %s %s", pkg_name, start_time)
        if pkg_name == "deploy_init":
            if deploy_action == DeployAction.Deploy:
                is_ok, msg = _deploy_deploy_init()
            else:
                is_ok, msg = True, ""
        else:
            deployer = Deployer(tar_path, install_path, logger)
            if deploy_action == DeployAction.Deploy:
                is_ok, msg = deployer.deploy()
            elif deploy_action == DeployAction.AfterInstallStart:
                is_ok, msg = deployer.after_install_start()
            elif deploy_action == DeployAction.AfterUpdateStart:
                is_ok, msg = deployer.after_update_start()
            else:
                raise Exception("no action named {0}".format(deploy_action))

        msg = u"deploy {0}, msg={1}".format(pkg_name, msg)
        if not is_ok:
            logger.error(msg)
            status = deploy_reporter.DeployReporter.STATUS_FAILED
        else:
            status = deploy_reporter.DeployReporter.STATUS_OK
            logger.info(msg)
        _handle_install_status(pkg_name, start_time, msg, status, logger)
        if not is_ok:
            raise ExecShellError
    except ReportError, e:
        pass
    except ExecShellError, e:
        raise e
    except Exception, e:
        status = deploy_reporter.DeployReporter.STATUS_FAILED
        err_msg = u"deploy pkg({0}) error. e={1}, traceback={2}".format(pkg_name, e, traceback.format_exc())
        logger.error(err_msg)
        _handle_install_status(pkg_name, start_time, err_msg, status, logger)
        raise e
    finally:
        logger.info("end deploy %s elapse: %s", pkg_name, time.time() - start_time)

def _deploy_deploy_init():
    """
    部署deploy_init
    :return:
    """
    pkg_name = 'deploy_init'
    logger = init_logger(pkg_name)
    deploy_init_path = "{0}/{1}".format(DOWNLOAD_PATH, pkg_name)
    if not os.path.exists(deploy_init_path):
        return 1, "deploy_init not exists"

    if not os.path.exists(os.path.join(INSTALL_BASE, pkg_name)):
        cmd = ["/bin/cp -rf {0} /usr/local/easyops/; " \
               "cd /usr/local/easyops/deploy_init; " \
               "chown -R easyops:easyops /usr/local/easyops/deploy_init; " \
               "cd /usr/local/easyops/deploy_init/; " \
               "/bin/bash /usr/local/easyops/deploy_init/deploy/install_postscript.sh;".format(deploy_init_path)]
        code, out = exec_cmd(cmd, logger=logger)
    else:
        cmd = ["/bin/cp -rf {0} /usr/local/easyops/; " \
               "cd /usr/local/easyops/deploy_init; " \
               "chown -R easyops:easyops /usr/local/easyops/deploy_init; " \
               "cd /usr/local/easyops/deploy_init/; " \
               "/bin/bash /usr/local/easyops/deploy_init/deploy/update_postscript.sh;".format(deploy_init_path)]
        code, out = exec_cmd(cmd, logger=logger)

    return code == 0, out

def _get_pkg_confs(pkg_path):
    """
    获取包的package.conf.yaml内容
    :param pkg_path: 包所在路径
    :return:
    """
    pkg_conf_map = {}
    for pkg_name in os.listdir(pkg_path):
        pkg_conf_path = "{0}/{1}/package.conf.yaml".format(pkg_path, pkg_name)
        if not os.path.isfile(pkg_conf_path):
            continue
        with open(pkg_conf_path) as f:
            pkg_conf = yaml.load(f)
            pkg_conf_map[pkg_name] = pkg_conf
    return pkg_conf_map
