#!/usr/bin/python
# -*- coding: utf-8 -*-
# @brief   : 2018/10/10 16:56
# @File    : app ctrl
#  @Software: Software management infrastructure

import os
import shutil
import sys
import logging
import subprocess
import time
import yaml

logging.basicConfig(level=logging.INFO,
                    filename="/OSM/log/cur_debug/messages",
                    format='[%(asctime)s][%(levelname)s][%(message)s][%(filename)s, %(lineno)d]',
                    datefmt='%Y-%m-%d %H:%M:%S')

TOP_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
MY_NAME = os.path.basename(__file__)
REPO_DIR = os.path.join(TOP_DIR, "repo")
SERVICE_STATE_PATH = "/OSM/component/protocol/lib"
RUN_DIR = '/OSM/component/protocol/'
ONLINE_TIMEOUT = 60  # 进程升级后online的超时时间
RPM_DB_PATH = '/var/lib/protocol_base_rpm'
NFS_STATE_DIR = '/var/lib/nfs'
PROCESS_SCRIPT = '/OSM/component/protocol/script'
PROCESS_CONFIG = '/OSM/component/protocol/conf/snm_config'
OLD_VERSION_CFG = '/OSM/component/protocol/conf/manifest_base.yml'
SCRIPT_PATH = '/OSM/script'
RPCBIND_LISTEN_ID = 61

PROTOCOL_APP_INFOS = (
    {'Name':'cmr_store', 'PROCESS_ID':'58'},
    {'Name':'cmr_cm', 'PROCESS_ID':'59'},
    {'Name':'cmr_auth', 'PROCESS_ID':'50'},
    {'Name':'cmr_kagent', 'PROCESS_ID':'51'},
)

USAGE = '''
Usage:
{name:s} [option]

Options:
    start
    stop
    online
    offline   
    install
    uninstall
    check_status    
    '''.format(name=MY_NAME)


def print_time_by_type(desc, type):
    record_time = int(round(time.time()*1000))
    logging.info("PROTOCOL_COMP_UPGRADE: %s %s (cost time %d(ms))", desc, type, record_time)

def show_help():
    print(USAGE)
    return
    
def get_process_num(name):
    """
    get process num
    :return:
    """
    cmd = "ps -ef | grep %s | grep -v grep | wc -l" % (name)
    r = os.popen(cmd)
    str = r.read()
    r.close()
    return int(str)

def check_process_num():
    """
    check process num
    :return:
    """
    for process_info in PROTOCOL_APP_INFOS:
        print_time_by_type("check process %s num" % process_info['Name'], "start")
        check_time = 0
        while check_time < 15:
            process_num = get_process_num(process_info['Name'])
            if process_num == 1:
                print_time_by_type("check process %s num" % process_info['Name'], "end")
                break
            time.sleep(0.2)
            check_time = check_time + 1
            logging.error("PROTOCOL_CMP_UPGRADE: The process num have abnormal(%d), retry time(%d).",process_num, check_time)
            if (check_time == 15):  
                logging.error("PROTOCOL_CMP_UPGRADE: check protocol app num failed, retry more than max times.")
                return 1
    return 0

def wait_process_active(name, process_id):

    retry_time = 0
    while retry_time < 6:
        cmd = "ps -aux | grep %s | grep %s | grep -v grep | awk '{print $8}'" %(name, process_id)
        r = os.popen(cmd)
        state = r.read().strip('\n')
        r.close()
        if state == "D":
            time.sleep(0.5)
            retry_time = retry_time + 1
            logging.error("PROTOCOL_BASE_UPGRADE: The process state(%d) is not active, retry time(%d).", state, retry_time)
            continue
        return
    return

def check_process_state():

    print_time_by_type("check protocol app state", "start")
    if SERVICE_STATE_PATH not in sys.path:
        sys.path.append(SERVICE_STATE_PATH)
    import libsrv_client
    check_time = 0
    try:
        # 5秒起不来升级失败
        for process_info in PROTOCOL_APP_INFOS:
            while check_time < ONLINE_TIMEOUT:
                ret_code, state = libsrv_client.process_get_info(process_info['PROCESS_ID'])
                if ret_code == 0 and state == 2:
                    print_time_by_type("check protocol %s state" % process_info['Name'], "end")
                    break
                logging.error("PROTOCOL_BASE_UPGRADE: protocol app state(%d) is not normal，ret(%d), check time(%d)." % (state, ret_code, check_time))
                time.sleep(0.1)
                check_time += 1
    except Exception as e:
        logging.error("PROTOCOL_BASE_UPGRADE: check protocol app state except %s", e)
    return 0

def get_process_id(name):

    cmd = "ps -ef | grep -w \"%s\" | grep -v grep | awk {'print $2,$12'} | tr \"\n\" \" \"" % (name)
    r = os.popen(cmd)
    process_info = r.read()
    r.close()
    logging.info("PROTOCOL_BASE_UPGRADE: Upgrade current protocol app info(%s).", process_info)
    return process_info.split(' ')[0]

def checkPlatformTypeIsX86():
    cmd = "arch"
    result = os.popen(cmd)
    arch = result.read()
    result.close()
    if arch.startswith('x86_64'):
        return 1
    return 0

def checkRpcbindStatus():
    cmd = "ps -ef | grep rpcbind | grep -v grep| wc -l"
    result = os.popen(cmd)
    text = result.read()
    result.close()
    return int(text)

def checkAlreadyListenRpcbind():
    cmd = "ps -ef|grep 'rpc_start_snm.sh' |grep -v grep |wc -l"
    result = os.popen(cmd)
    text = result.read()
    result.close()
    return int(text)

def startRpcbindListen():
    arch = checkPlatformTypeIsX86()
    if arch:
        return 0
    num = checkAlreadyListenRpcbind()
    if num != 0:
        logging.error("Already listen rpcbind status. Num :%d", num)
        return 0
    cmd = "{0}/snm.sh -s {1}/rpc_start_snm.sh -l {2} -y shell".format(SCRIPT_PATH, SCRIPT_PATH, RPCBIND_LISTEN_ID)
    rpcbind_ret = os.system(cmd)
    if rpcbind_ret:
        logging.error("start rpcbind listen failed. cmd :%s", cmd)
        return 1
    check_time = 10
    while check_time > 0:
        listenProcess = checkAlreadyListenRpcbind()
        rpcProcess = checkRpcbindStatus()
        if listenProcess and rpcProcess:
            break
        time.sleep(0.5)
        check_time -= 1

    logging.info("start rpcbind listen success. cmd :%s", cmd)
    return 0

def stopRpcbindListen():
    arch = checkPlatformTypeIsX86()
    if arch:
        return 0
    cmd = "{0}/snm.sh -t {1}/rpc_stop_snm.sh -l {2} -y shell".format(SCRIPT_PATH, SCRIPT_PATH, RPCBIND_LISTEN_ID)
    rpcbind_ret = os.system(cmd)
    if rpcbind_ret:
        logging.error("stop rpcbind listen failed. cmd :%s", cmd)
        return 1
    check_time = 10
    while check_time > 0:
        listenProcess = checkAlreadyListenRpcbind()
        rpcProcess = checkRpcbindStatus()
        if listenProcess or rpcProcess:
            time.sleep(0.5)
        check_time -= 1
    logging.info("stop rpcbind listen success. cmd :%s", cmd)
    return 0

def start():
    print_time_by_type("start process app", "start")
    ret = startRpcbindListen()
    if ret:
        return ret
    
    for process_info in PROTOCOL_APP_INFOS:
        cmd = "snm.sh -s {0}/protocol_snm_base.py -f {1}/{2}_snm_config_new.xml".format(PROCESS_SCRIPT, PROCESS_CONFIG, process_info['Name'])
        ret_code = os.system(cmd)
        if ret_code:
            logging.error("start %s failed.", process_info['Name'])
            return 1
        check_time = 25
        while check_time > 0:
            sys_cmd = "ps afx|grep -E '%s -l [0-9]+'|grep -v grep" % (process_info['Name'])
            ret_code_1 = os.system(sys_cmd)
            if ret_code_1:
                logging.error("process not existed!")
                check_time -= 1
                time.sleep(0.2)
                continue
            else:
                logging.info("process %s start success!" % (process_info['Name']))
                break
    print_time_by_type("start process app", "end")
    time.sleep(30)
    return 0   

def stop():
    print_time_by_type("stop protocol app", "start")
    for process_info in PROTOCOL_APP_INFOS:
        cmd = "snm.sh -t {0}/protocol_snm_base.py -f {1}/{2}_snm_config.xml".format(PROCESS_SCRIPT, PROCESS_CONFIG, process_info['Name'])
        ret_code = os.system(cmd)
        if ret_code:
            logging.error("stop protocol_app %s failed.", process_info['Name'])
            return 1
        cmd = "snm.sh -t {0}/protocol_snm_base.py -f {1}/{2}_snm_config_new.xml".format(PROCESS_SCRIPT, PROCESS_CONFIG, process_info['Name'])
        ret_code = os.system(cmd)
        if ret_code:
            logging.error("stop protocol_app %s failed.", process_info['Name'])
            return 1

        check_time = 15
        while check_time > 0:
            cmd = "ps afx|grep -E '%s -l [0-9]+'|grep -v grep".format(process_info['Name'])
            ret_code = os.system(cmd)
            if ret_code:
                logging.info("process %s stop success!" % (process_info['Name']))
                break
            else:
                logging.error("process %s existed!" % (process_info['Name']))
                check_time -= 1
                time.sleep(0.2)
                continue
    print_time_by_type("stop protocol app", "end")
    ret = stopRpcbindListen()
    if ret:
        return ret
    return 0

def online():
    return 0


def get_online_result():
    if check_process_state():
        logging.error('PROTOCOL_BASE_UPGRADE: Sys app state not normal')
        return 1
    return 0

def offline():
    return 0


def create_rpm_dir():
    """
    创建rpm的db目录
    :return:
    """
    if not os.path.exists(RPM_DB_PATH):
        cmd = "mkdir -p %s" % RPM_DB_PATH
        os.system(cmd)

def remove_base_mainfest():
    """
    删除nfs的配置文件
    :return:
    """
    if os.path.exists(OLD_VERSION_CFG):
        cmd = "rm -rf %s" % OLD_VERSION_CFG
        os.system(cmd)

def create_nfs_dir():
    """
    创建rpm的db目录
    :return:
    """
    if not os.path.exists(NFS_STATE_DIR):
        cmd = "mkdir -p %s" % NFS_STATE_DIR
        os.system(cmd)

def install():
    print_time_by_type("install protocol apps", "start")
    create_rpm_dir()
    create_nfs_dir()
    # 到repo目录下取出rpm包来进行安装
    for rpm_file in os.listdir(REPO_DIR):
        retry_time = 0
        cmd = "rpm -ivh --nodeps --force --dbpath=%s %s" % (RPM_DB_PATH, os.path.join(REPO_DIR, rpm_file))
        ret_code = os.system(cmd)
        while ret_code:
            if retry_time > 3:
                return 1
            logging.error("PROTOCOL_BASE_UPGRADE: install protocol service failed(%d), retry time(%d)." % (ret_code, retry_time))
            retry_time = retry_time + 1
            ret_code = os.system(cmd)
    os.chmod("%s/manifest.yml" % TOP_DIR, 0o640)
    ret_code = os.system('cp -af %s/manifest.yml %s/conf/manifest_base.yml' % (TOP_DIR, RUN_DIR))
    if ret_code:
        logging.error("PROTOCOL_BASE_UPGRADE: copy manifest.yml file failed.")
        return 1
    ret_code = os.system('cp -af %s/manifest.yml %s/conf/manifest_base.yml' % (TOP_DIR, RUN_DIR))
    if ret_code:
        logging.error("PROTOCOL_BASE_UPGRADE: copy manifest.yml file failed.")
        return 1
    print_time_by_type("install protocol apps", "end")
    return 0

def uninstall():
    print_time_by_type("uninstall protocol apps", "start")
    create_rpm_dir()
    create_nfs_dir()
    if not os.path.exists(OLD_VERSION_CFG):
        logging.info("PROTOCOL_BASE_UPGRADE: base_comp not exsit ,no need to uninstall.")
        return 0
    remove_base_mainfest()
    # 到repo目录下取出rpm包来卸载
    for rpm_file in os.listdir(REPO_DIR):
        rpm_name = rpm_file[0:rpm_file.index(".rpm")]
        cmd = "rpm -e --nodeps --dbpath=%s %s" % (RPM_DB_PATH, rpm_name)
        ret_code = os.system(cmd)
        if ret_code:
            logging.error("PROTOCOL_BASE_UPGRADE: uninstall protocol service failed")
            return 1
    print_time_by_type("uninstall protocol apps", "end")
    return 0


def check_status():
    return 0


# 检查组件升级模式
def check_upgrade():
    old_cfg_path = os.path.join(RUN_DIR, 'conf', 'manifest_base.yml')
    new_cfg_path = os.path.join(TOP_DIR, 'manifest.yml')
    old_cfg = None
    new_cfg = None
    if not os.path.exists(old_cfg_path):
        logging.info("PROTOCOL_BASE_UPGRADE: The old version not exsit, need upgrade.")
        return 2
    with open(old_cfg_path, 'r') as fp:
        content = fp.read()
        old_cfg = yaml.safe_load(content)
    with open(new_cfg_path, 'r') as fp:
        content = fp.read()
        new_cfg = yaml.safe_load(content)
    if not (old_cfg and new_cfg):
        logging.error("PROTOCOL_BASE_UPGRADE: Get yaml info failed(old: %s, new: %s).", old_cfg, new_cfg)
        return 0
    old_ver = old_cfg.get('Version')
    new_ver = new_cfg.get('Version')
    if old_ver == new_ver:
        logging.info("PROTOCOL_BASE_UPGRADE: The old version is same(%s) with new version, no need upgrade.", old_ver)
        return 1
    logging.info("PROTOCOL_BASE_UPGRADE: The old version(%s) is different with new version(%s), need upgrade.", old_ver, new_ver)
    return 2


def execute_upgrade():

    # 打印升级信息
    for process_info in PROTOCOL_APP_INFOS:
        process_pid = get_process_id(process_info['Name'])
        wait_process_active(process_info['Name'], process_pid)
    # 杀protocol apps
    ret_code = stop()
    if ret_code:
        return 1
    # 开始拉起进程
    ret_code = start()
    if ret_code:
        return 1
    return 0
    
def upgrade():
    print_time_by_type("protocol apps upgrade", "start")
    if os.path.exists(OLD_VERSION_CFG):
        logging.info("PROTOCOL_BASE_UPGRADE: The old version exsit, need check process status.")
        ret_code = check_process_num()
        if ret_code:
            return 1
    # 安装rpm包
    ret_code = install()
    if ret_code:
        return 1

    retry_time = 0
    while retry_time < 6:
        ret_code = execute_upgrade()
        if ret_code == 0:
            print_time_by_type("protocol apps upgrade", "end")
            return 0
        time.sleep(0.5)
        retry_time = retry_time + 1
        logging.error("PROTOCOL_BASE_UPGRADE: protocol app upgrade is failed, retry time(%d).", retry_time)
    logging.error("PROTOCOL_BASE_UPGRADE: protocol app upgrade is failed, retry more than max times.")
    return 1

def execute_rollback():

    # 开始拉起进程
    ret_code = start()
    if ret_code:
        return 1
    return 0

def rollback():
    print_time_by_type("protocol app rollback", "start")
    ret_code = install()
    if ret_code:
        return 1
    retry_time = 0
    while retry_time < 6:
        ret_code = execute_rollback()
        if ret_code == 0:
            print_time_by_type("protocol app rollback", "end")
            return 0
        time.sleep(0.5)
        retry_time = retry_time + 1
        logging.error("PROTOCOL_BASE_UPGRADE: rollback protocol app failed, retry time(%d).", retry_time)
    logging.error("PROTOCOL_BASE_UPGRADE: rollback protocol app failed, retry more than max times.")
    return 1


def pre_upgrade():
    print_time_by_type("pre upgrade check", "start")
    # 检查进程数量
    if os.path.exists(OLD_VERSION_CFG):
        logging.info("PROTOCOL_BASE_UPGRADE: The old version exsit, need check process status.")
        ret_code = check_process_num()
        if ret_code:
            return 1

    return 0

def pre_rollback():
    print_time_by_type("stop protocol app", "start")
    remove_base_mainfest()
    for process_info in PROTOCOL_APP_INFOS:
        cmd = "snm.sh -t {0}/protocol_snm_base.py -f {1}/{2}_snm_config_new.xml".format(PROCESS_SCRIPT, PROCESS_CONFIG, process_info['Name'])
        ret_code = os.system(cmd)
        if ret_code:
            logging.error("stop protocol_app %s failed.", process_info['Name'])
            return 1
        check_time = 15
        while check_time > 0:
            cmd = "ps afx|grep -E '%s -l [0-9]+'|grep -v grep".format(process_info['Name'])
            ret_code = os.system(cmd)
            if ret_code:
                logging.info("process %s stop success!" % (process_info['Name']))
                break
            else:
                logging.error("process %s existed!" % (process_info['Name']))
                check_time -= 1
                time.sleep(0.2)
                continue
    print_time_by_type("stop protocol app", "end")
    ret = stopRpcbindListen()
    if ret:
        return ret
    return 0


def exec_build(args):
    """

    :param args:
    :return:
    """
    if 2 != len(args):
        show_help()
        return -1
    func_name = args[1]
    this_module = sys.modules[__name__]
    try:
        func = getattr(this_module, func_name)
    except Exception as e:
        logging.exception("PROTOCOL_BASE_UPGRADE: param is error %s", e)
        return -1
    if func == None:
        return -1
    return func()


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