# -*- coding: UTF-8 -*-
"""
@version: Toolkit V200R006C00
@time: 2019/10/08
@file: hardware_root_disk_check.py
@function:
@modify:
"""
import re
import traceback

from resource.resource import MESSAGES_DICT
from cbb.frame.base import baseUtil
from cbb.frame.cli import cliUtil
from cbb.frame.context import contextUtil
from cbb.frame.base.exception import UnCheckException
from cbb.frame.util import common
from cbb.frame.cli.execute_on_all_controllers import (
    ExecuteOnAllControllers,
    ResultType,
    FuncResult,
    ExeOnAllCtrlException,
)
from cbb.frame.cli.execute_on_all_controllers import ExeOnAllCtrlContext
from cbb.business.collect.products_adapter import compare_version
from com.huawei.ism.tool.obase.exception import ToolException

MOUNT = 'mount'
# 步骤6 执行命令：ls /OSM/coffer_log，ls /startup_disk/conf/conf_local，ls /startup_disk/image/boot检查目录信息；
CHECK_DIR_DICT = {'ls /OSM/coffer_log': '/OSM/coffer_log',
                  'ls /startup_disk/conf/conf_local': '/startup_disk/conf/conf_local',
                  'ls /startup_disk/image/boot': '/startup_disk/image/boot'}

RW_AUTH_DISK_DIR_LIST = ['/OSM/coffer_log', '/startup_disk/conf',
                         '/OSM/coffer_data']

FILE_EXITS = 'Y'
FILE_NOT_FOUNT = 'N'
RW_DIR_AUTH_ERROR = "AUTH_ERROR"

# TV2需要检查的系统型号
tv2_check_system_type = {'S5600T', 'S5800T', 'S6800T'}
mount_disk_check_dict = {'/startup_disk/image': 'N',
                         '/startup_disk/conf': 'N', '/OSM/coffer_log': 'N',
                         '/OSM/coffer_data': 'N'}


def execute(data_dict):
    lang = contextUtil.getLang(data_dict)
    try:
        cli = contextUtil.getCli(data_dict)
        logger = contextUtil.getLogger(data_dict)
        exe_context = ExeOnAllCtrlContext(data_dict)
        return CheckHardwareRootDisk(exe_context, logger, lang, cli).execute_check()
    except ExeOnAllCtrlException as e:
        return _get_error_result_info(lang, e.exception_msg)
    except ToolException as exception:
        return _get_error_result_info(lang, str(exception))


def _get_error_result_info(lang, err_msg):
    """
    根据异常回显获取结果信息
    :param lang    部署环境
    :param err_msg 异常回显
    """
    if "PwdCLIOverrunException" in err_msg:
        return (ResultType.NOT_FINISHED,
                baseUtil.getPyResource(lang, "login.clis.overrun.info", "", resource=MESSAGES_DICT),
                "")
    return (cliUtil.RESULT_NOCHECK,
             baseUtil.getPyResource(lang, "query.result.abnormal", "", resource=MESSAGES_DICT),
             "")


class CheckHardwareRootDisk:
    def __init__(self, context, logger, lang, cli):
        self.all_ret_list = list()
        self.error_msg_list = list()
        self.logger = logger
        self.lang = lang
        self.cli = cli
        self.exec_context = context
        self.dev = context._original_context.get("dev")
        self.all_cli_ret = []
        self.all_engine_check_result = []

    def execute_check(self):
        try:
            # 获取系统版本 , 版本判断是否为风险版本
            product_model, product_version = self.get_product_version_and_model()
            if not self.check_system_type_version(product_model, product_version):
                return True, "", self.get_all_ret()

            # 验证权限是否为超级管理员
            self.check_super_administrator_auth(product_version)

            # 所有引擎，所有控制器下系统盘检查
            self.logger.info("check_all_engine_root_disk start.")
            self.check_all_engine_root_disk_status(self.exec_context)

            # 计算所有引擎结果，输出检查结果和异常提示
            engine_check_flag, all_engine_check_error = self.analysis_all_engine_result()
            return (engine_check_flag, all_engine_check_error, self.get_all_ret())
        except UnCheckException as uncheck:
            self.logger.error("Failed to check root disk uncheck:{}".format(str(uncheck.errorMsg)))
            return (cliUtil.RESULT_NOCHECK, uncheck.errorMsg, self.get_all_ret())
        except Exception:
            self.logger.error("Failed to check root disk e:{}".format(str(traceback.format_exc())))
            return (
                cliUtil.RESULT_NOCHECK, cliUtil.getMsg(self.lang, "query.result.abnormal"), self.get_all_ret()
            )

    def get_all_ret(self):
        return "\n".join(self.all_cli_ret)

    def get_product_version_and_model(self):
        """
        获取系统版本信息
        :return: product_model, product_version
        """
        # show upgrade package
        version_flag, version_cli_ret, error_msg, product_version = \
            common.get_product_version_by_upgrade_package(self.cli, self.lang)
        self.all_cli_ret.append(version_cli_ret)
        product_model = str(self.dev.getDeviceType())
        product_version = str(self.dev.getProductVersion())
        self.logger.info("productModel is: {}, productVersion:{}".format(product_model, product_version))
        return product_model, product_version

    def check_super_administrator_auth(self, product_version):
        """
        验证权限是否为超级管理员
        :param product_version: 版本信息。
        :return:
        """

        self.logger.info("Check whether the permission is super administrator.")
        user_flag, cli_ret, user_privilege, err_msg = cliUtil.get_user_privilege_with_cli_ret(self.cli, self.lang)
        self.all_cli_ret.append(cli_ret)
        if user_flag is not True:
            raise UnCheckException(err_msg)
        self.logger.info("user_privilege:{} ,hotPatchVersion:{}".format(user_privilege, self.dev.getHotPatchVersion()))
        if user_privilege.lower() != "super_admin" and not \
                common.is_support_read_only_user_enter_debug(product_version, str(
                    self.dev.getHotPatchVersion())):
            raise UnCheckException(cliUtil.getMsg(self.lang, "loginUser.name.level.must.be.super.admin"))

    def analysis_all_engine_result(self):
        """
        计算所有引擎结果，输出检查结果和异常提示
        :return:
        """
        # [[flag, engine, controller,mount_check_dict, check_error_type]...]
        error = []
        all_check_flag = True
        # 重新计算所有错误，汇总错误信息
        for each_result in self.all_engine_check_result:
            if len(each_result) < 4:
                return False, ""
            flag = each_result[0]
            engine = each_result[1]
            controller = each_result[2]
            ctrl_err_msg = each_result[3]
            if flag == ResultType.SUCCESS:
                continue
            all_check_flag = False
            error.append(self.get_each_ctrl_check_error(engine, controller, ctrl_err_msg))
        return all_check_flag, "\n".join(error)

    def check_system_type_version(self, product_model, product_version):
        """
        涉及产品版本及型号判断,
            18000V1系列：V100R001C30及后续版本
            TV2系列：V200R002C00及后续版本,型号S5600T&S5800T&S6800T
            V3系列：V300R001C00及后续版本
            V5系列：V500R007C00-V5R7C60(不含) 以及 V5R7C61
            DoradoV3系列：V300R001C00及后续版本
        :param product_model: 型号
        :param product_version: 版本
        :return: 是否涉及T,F
        """
        model = product_model.upper()
        version = product_version.upper()

        # TV2
        if version.startswith('V200R') and compare_version(version, 'V200R002C00') >= 0 \
                and model in tv2_check_system_type:
            return True
        # V3
        if version.startswith('V300R') and compare_version(version, 'V300R001C00') >= 0:
            return True
        # V5
        if all([version.startswith('V500R'),
                compare_version(version, 'V500R007C00') >= 0,
                (compare_version(version, 'V500R007C60') < 0 or compare_version(version, 'V500R007C61') >= 0)]):
            return True
        return False

    @ExecuteOnAllControllers
    def check_all_engine_root_disk_status(self, exec_context):
        """
        检查所有引擎所有控制器系统盘状态
        :param exec_context: exec_context
        :return:FuncResult
        """

        cli = exec_context.dev_info.cli
        lang = exec_context.lang
        logger = exec_context.logger
        current_engine = ""
        current_ctrl = ""
        # 获取当前引擎控制器信息
        try:
            logger.info("check_all_engine_root_disk_status start.")
            flag, err_msg, cli_ret, current_ctrl, engine_ctrl_mapping_dict, \
                current_engine, node_num = common.get_engine_ctrl_node_info(cli, logger, lang)
            logger.info("current_ctrl id is:{}".format(current_ctrl))
            current_ctrl = self.calc_ctrl_name_from_id(current_engine, current_ctrl,
                                                       engine_ctrl_mapping_dict)
            logger.info("current_engine is: {}, current_ctrl:{}, engine_ctrl_mapping_dict is:{}".format(
                current_engine, current_ctrl, engine_ctrl_mapping_dict))

            # 检查单个控制器，得到检查结果
            check_flag, err_msg = self.check_each_ctrl_root_disk_status(cli)
        except UnCheckException as uncheck:
            self.logger.error("check_all_engine_root_disk_status uncheck exception:{}".format(uncheck.errorMsg))
            check_flag = ResultType.NOT_FINISHED
            err_msg = uncheck.errorMsg
        finally:
            cliUtil.enterCliModeFromSomeModel(cli, lang)
        # 将引擎检查结果装入待分析集合中
        self.build_engine_ctrl_check_result(check_flag,
                                            current_engine, current_ctrl, err_msg)
        # 需检查所有引擎下所有控制器系统盘
        return FuncResult(check_flag, "", "")

    def build_engine_ctrl_check_result(self, flag, engine, controller, err_msg):
        """
        每控检查结果
        :param flag: 检查结果
        :param engine: 当前引擎
        :param controller: 当前控制器
        :param err_msg: mount检查结果
        :return:
        """

        engine_check = [flag, engine, controller, err_msg]
        self.all_engine_check_result.append(engine_check)

    def check_each_ctrl_root_disk_status(self, cli):
        """
        检查某控下系统盘是否正常
        :param cli:cli
        :return: ResultType,error
        """
        # 在minisystem模式下执行mount
        self.logger.info("excuteCmdInMinisystemModel mount start.")
        mount_flag, mount_cli_ret, err_msg = cliUtil.excuteCmdInMinisystemModel(cli, MOUNT, self.lang)
        self.all_cli_ret.append(mount_cli_ret)
        if mount_flag is not True:
            raise UnCheckException(err_msg)

        # mount命令检查系统盘目录挂载情况，及权限问题。
        self.check_root_disk_mount(mount_cli_ret)
        # CHECK_DIR_DICT字典中目录检查
        flag, err_msg = self.check_coffer_log_directory_empty(cli)
        self.logger.info(
            "check_coffer_log_directory_empty check_result:{},dir_empty_error:{}".format(
                flag, err_msg))
        if flag is False:
            raise UnCheckException(err_msg)
        return True, ""

    def check_coffer_log_directory_empty(self, cli):
        """
        执行：CHECK_DIR_DICT字典中的命令
        步骤6中，若所有控制器CHECK_DIR_DICT字典中目录的内容不为空，则检查通过，否则检查不通过；
        :param cli: cli
        :return: falg, err_msg
        """
        self.logger.info("check_coffer_log_directory_empty start.")
        empty_dirs = [dir_name for cmd, dir_name in CHECK_DIR_DICT.items()
                      if self._check_one_directory_empty(cli, cmd)]
        if empty_dirs:
            return False, cliUtil.getMsg(self.lang, "root.disk.check.mount.directory.empty", ",".join(empty_dirs))
        return True, ""

    def _check_one_directory_empty(self, cli, cmd):
        """
        检查步骤6中单个目录的是否为空
        :param cmd: 命令
        :param dir_name: 目录名
        :return: 是否为空
        """
        coffer_log_flag, coffer_log_cli_ret, coffer_log_error = \
            cliUtil.excuteCmdInMinisystemModel(
                cli, cmd, self.lang)
        self.all_cli_ret.append(coffer_log_cli_ret)
        if coffer_log_flag is not True:
            return False
        elif not self.check_if_directory_empty(coffer_log_cli_ret, cmd):
            return False
        return True

    def get_each_ctrl_check_error(self, current_engine, current_ctrl, error):
        return cliUtil.getMsg(self.lang,
                              "root.disk.check.engine.controller.disk.error",
                              (current_engine, current_ctrl, error))

    def check_root_disk_mount(self, mount_cli_ret):
        """
        mount命令检查系统盘目录挂载情况

        :param mount_cli_ret:mount_cli_ret
        :return: 有异常则抛出异常。
        """
        self.logger.info("check_root_disk_mount start.")
        res_lines = mount_cli_ret.splitlines()
        check_dict = mount_disk_check_dict.copy()
        for line in res_lines:
            self.mount_disk_check(check_dict, line)

        # 处理目录挂载情况的检查结果
        mount_flag, err_msg = self.mount_disk_check_result(check_dict)
        if mount_flag is not True:
            raise UnCheckException(err_msg)

    def mount_disk_check(self, check_dict, line):
        """
        检查每一行是否包含待检测目录，及权限是否正确
        1 步骤5中，若所有控制器
        /startup_disk/image、/startup_disk/conf、/OSM/coffer_log、/OSM/coffer_data都存在，
        且/OSM/coffer_log和/startup_disk/conf权限均为rw，
        则继续检查，否则检查结果为不通过；
        :param check_basic_dict: mount需要检测的文件夹
        :param check_dict:待检测目录
        :param line:mount命令回显行
        :return: no return
        """

        self.logger.info("mount_disk_check start.")
        for dir_name in check_dict:
            # 该目录已经判断存在，跳过
            if check_dict[dir_name] == FILE_EXITS:
                continue
            # /startup_disk/image、/startup_disk/conf、/OSM/coffer_log、/OSM/coffer_data都存在，
            if self.find_directory_name(dir_name, line):
                check_dict[dir_name] = FILE_EXITS
                # 判断/OSM/coffer_log，/startup_disk/conf权限均为rw，
                self.check_is_read_write_directory(dir_name, line, check_dict)

    def check_is_read_write_directory(self, dir_name, line, check_dict):
        """
        做判断/OSM/coffer_log，/startup_disk/conf文件夹是否存在
        :param line: 行
        :return: 结果
        """
        if dir_name in RW_AUTH_DISK_DIR_LIST:
            if "rw" in line:
                check_dict[dir_name] = FILE_EXITS
            else:
                check_dict[dir_name] = RW_DIR_AUTH_ERROR

    def find_directory_name(self, dic_name, line):
        """
        全匹配文件名是否存在
        :param dic_name: 文件名
        :param line: 行
        :return: T F
        """
        return bool(re.search(r'\s+' + dic_name + r'\s+', line))

    def mount_disk_check_result(self, check_dict):
        """
        分析mount命令结果是否有异常，有异常则放回失败，否则成功。
        若所有控制器/startup_disk/image、/startup_disk/conf、/OSM/coffer_log、/OSM/coffer_data都存在，
        且/OSM/coffer_log和/startup_disk/conf权限均为rw
        :param check_dict:待检测目录
        :return:mount检查是否正常
        """
        mount_error_list = []
        for dir_name in check_dict:
            if check_dict[dir_name] == FILE_NOT_FOUNT:
                # 处理目录为挂载的问题
                error_msg = cliUtil.getMsg(self.lang, "root.disk.check.mount.error.nodirectory", dir_name)
                mount_error_list.append(error_msg)

            if check_dict[dir_name] == RW_DIR_AUTH_ERROR:
                # 处理目录权限rw的场景。
                mount_error_list.append(
                    cliUtil.getMsg(self.lang, "root.disk.check.mount.coffer.log.auth.error", dir_name))

        if mount_error_list:
            return False, "\n".join(mount_error_list)
        return True, ""

    def calc_ctrl_name_from_id(self, engine_id, control_id, engine_ctrl_mapping_dict):
        """
        通过引擎，控制器ID计算其控制器名称
        :param engine_ctrl_mapping_dict:引擎管理控制器字典
        :param engine_id:引擎ID
        :param control_id:控制器ID
        :return: 控制器名称
        """
        engine_ctrl_list = []
        if engine_id is not None:
            engine_ctrl_list = sorted(engine_ctrl_mapping_dict[engine_id])
        if not bool(engine_ctrl_list) or control_id not in engine_ctrl_list:
            return ''
        index = engine_ctrl_list.index(control_id)
        return str(engine_id) + str(chr(index + 65))

    def check_if_directory_empty(self, cli_ret, cmd):
        """
        判断ls查询目录回显的子目录是否为空
        :param cli_ret:ls xxx 回显
        :param cmd:ls xxx命令
        :return: 是否为空
        """
        cli_lines = cli_ret.splitlines()
        for line in cli_lines:
            # 如果返回的是权限异常直接返回 False。
            if 'permission denied' in line.lower():
                return False
            # 过滤回显命令行和命令行执行异常
            if cmd in line:
                continue
            # 包含结束符，和异常结果关键字，跳过
            if 'minisystem>' in line.lower() or 'no such file or directory' in line.lower():
                continue
            # 正常目录
            if bool(line.strip()):
                return False
        return True
