# coding=utf-8
import re
import traceback

import utils.common.log as logger
from utils.DBAdapter.DBConnector import BaseOps
from utils.common.exception import HCCIException
from utils.common.ssh_util import Ssh as SshUtil
from utils.common.error.hcci_error_code import get_code_msg
from utils.business.project_condition_utils import get_project_condition_boolean
from plugins.DistributedStorage.logic.DeployOperate import FusionStorageOperate
from plugins.DistributedStorage.utils.common.DeployConstant import DeployConstant
from plugins.ResourceCheck.common.libs.BMCCmd import BMCCmdExc
from plugins.DistributedStorage.Deploy.scripts.PreCheck.implement.Job_Disk_Check import \
    DiskCheck


class CheckPoolNodeSSD(object):
    def __init__(self, project_id, pod_id, fs_args):
        self.project_id = project_id
        self.pod_id = pod_id
        self.db = BaseOps()
        self.cmd_exe = BMCCmdExc()
        self.fs_args = fs_args
        self.fsm_float_ip = fs_args.get('fsm_float_ip')
        self.fsm_admin_passwd = fs_args.get('fsm_admin_password')
        self.ex_osd_list = fs_args.get('ex_osd_list')
        self.operate = FusionStorageOperate(self.fsm_float_ip)

    def procedure(self):
        server_list = self.ex_osd_list
        if not server_list:
            message = "No osd node needs to be added to the storage pool."
            logger.info(message)
            return
        pool_id, cache_type, target_pool_nodes = self.get_exist_pool_info()
        logger.info("The target pool id : %s cache type : %s" % (pool_id, cache_type))
        pool_nvme_type = list()
        error_dic = {'not_exit_ssd_card_node': [], 'multiple_ssd_card_type_node': [],
                     'different_cache_type_node': [], 'different_nvme_cache_type_node': [],
                     'not_exit_ssd_disk_node': []}

        for node in server_list:
            bmc_client = BMCCmdExc()
            bmc_ip = node.get("bmc_ip")
            bmc_cache_type = node.get("cache_type")
            if bmc_cache_type != cache_type:
                err_msg = "The cache type of the target storage pool[%s] is inconsistent with " \
                          "that of the node[%s] to be added." % (cache_type, bmc_cache_type)
                logger.error(err_msg)
                error_dic['different_cache_type_node'].append(bmc_ip)
                continue
            if bmc_cache_type == "ssd_card":
                # 管理存储支持不同型号 NVMe 共池，故只检查业务存储
                if get_project_condition_boolean(self.project_id, "ExpansionServiceStorage&TenantStorNewNode"):
                    pool_nvme_type = self.get_pool_node_ssd_type(
                        target_pool_nodes)
                    if get_project_condition_boolean(self.project_id, '!TenantStorFB_Heterogeneous'):
                        node_nvme_type = self.get_bmc_node_ssd(bmc_client, bmc_ip)
                    # 手动装机场景
                    else:
                        node_nvme_type = self.get_node_ssd(node)
                    if not node_nvme_type:
                        err_msg = "The node %s has no NVMe, " \
                                  "check whether the cache type in the LLD is correct." % bmc_ip
                        logger.error(err_msg)
                        error_dic['not_exit_ssd_card_node'].append(bmc_ip)
                        continue
                    if len(node_nvme_type) != 1:
                        err_msg = "NVMe type of the same node to be expanded must be the same."
                        logger.error(err_msg)
                        error_dic['multiple_ssd_card_type_node'].append(bmc_ip)
                    elif node_nvme_type != pool_nvme_type:
                        err_msg = "The ssd of the storage pool : %s, the node to be expanded: %s" \
                                  % (pool_nvme_type, node_nvme_type)
                        logger.error(err_msg)
                        error_dic['different_nvme_cache_type_node'].append((bmc_ip, node_nvme_type))
            elif bmc_cache_type == "ssd_disk":
                ssd_disk_list = DiskCheck(self.project_id, self.pod_id).get_ssd_disk(bmc_client, bmc_ip)
                ssd_num = len(ssd_disk_list)
                if ssd_num <= 0:
                    logger.error(
                        "Not found sas ssd or stata ssd on node[%s]" % bmc_ip)
                    error_dic['not_exit_ssd_disk_node'].append(bmc_ip)
            else:
                logger.info("none cache type, bmc[%s], bmc_cache:%s " % (bmc_ip, bmc_cache_type))

        if error_dic.get('different_cache_type_node'):
            error_dic['different_cache_type_node'] = \
                get_code_msg(627611) % (error_dic.get('different_cache_type_node'), cache_type)
        if error_dic.get('different_nvme_cache_type_node'):
            error_dic['different_nvme_cache_type_node'] = \
                get_code_msg(627312) % (pool_nvme_type, error_dic.get('different_nvme_cache_type_node'))
        if error_dic.get('not_exit_ssd_card_node'):
            error_dic['not_exit_ssd_card_node'] = get_code_msg(627601) % error_dic.get('not_exit_ssd_card_node')
        if error_dic.get('multiple_ssd_card_type_node'):
            error_dic['multiple_ssd_card_type_node'] = \
                get_code_msg(627612) % error_dic.get('multiple_ssd_card_type_node')
        if error_dic.get('not_exit_ssd_disk_node'):
            error_dic['not_exit_ssd_disk_node'] = get_code_msg(627602) % error_dic.get('not_exit_ssd_disk_node')
        all_error_nodes = [value.strip() for value in error_dic.values() if value]
        if all_error_nodes:
            logger.error(all_error_nodes)
            raise Exception(all_error_nodes)

    def get_exist_pool_info(self):
        """
        获取原有管理存储池信息
        """
        login_user = 'admin'
        login_passwd = self.fs_args.get('fsm_admin_password')
        status_code, error_code, error_des = self.operate.login(
            login_user, login_passwd)
        if status_code != 200 or error_code != 0:
            err_msg = "Failed to login deploy manager, " \
                      "Detail:[status:%s,code:%s]%s" \
                      % (status_code, error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)

        logger.info("Query storage pool data.")
        res_pool = self.operate.query_storage_pool()
        pool_data = res_pool.get_query_data()
        pool_info_list = pool_data.get('storagePools')
        if len(pool_info_list) == 0:
            logger.error('Check pool fail...')
            raise Exception("Check pool fail...")
        target_pool_name = self.ex_osd_list[0].get("storage_pool_name_and_slot")
        if not target_pool_name and len(pool_info_list) == 1:
            target_pool_name = self.fs_args.get("default_pool_name")
        pool_info = {}
        if target_pool_name:
            logger.info("The name of the target storage pool is : %s." % target_pool_name)
            for pool in pool_info_list:
                if pool.get("poolName") == target_pool_name:
                    pool_info = pool
                    break
        else:
            err_msg = "No storage pool name is available in the service storage scenario."
            logger.error(err_msg)
            raise Exception(err_msg)
        if not pool_info:
            err_msg = "No storage pool named %s is found in the cluster." % target_pool_name
            logger.error(err_msg)
            raise Exception(err_msg)
        cache_type = pool_info.get('cacheMediaType')
        pool_id = pool_info.get('poolId')
        rsp_data = self.operate.query_storage_node_by_pool_id(str(pool_id))
        pool_data = rsp_data.get_query_data()
        node_data_list = pool_data.get('nodeInfo')
        node_mgr_ip_list = [node.get('nodeMgrIp') for node in node_data_list]
        return pool_id, cache_type, node_mgr_ip_list

    def get_pool_node_ssd_type(self, exist_pool_nodes):
        """
        登录存储池中一个节点，获取节点的NVME类型
        :return: ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        """
        nvme_type_list = list()
        success_login_node = list()
        login_user = self.fs_args.get("login_user")
        login_passwd = self.fs_args.get("login_passwd")
        for om_ip in exist_pool_nodes:
            try:
                ssh_client = SshUtil.ssh_create_client(om_ip, login_user, login_passwd)
            except Exception:
                logger.error('create ssh client failed, ip:{}, details::{}'.format(om_ip, traceback.format_exc()))
                continue
            success_login_node.append(om_ip)
            for nvme_type, query_opr in DeployConstant.NVME_TYPE_CONDITION.items():
                cmd, pattern = query_opr.get("cmd"), query_opr.get("pattern")
                ret_cmd = SshUtil.ssh_exec_command_return(ssh_client, cmd)
                logger.info("Run query cmd[%s],result:%s" % (cmd, ret_cmd))
                ret_cmd = "".join(ret_cmd)
                if re.findall(pattern, ret_cmd):
                    nvme_type_list.append(nvme_type)
                    logger.info("pool node: %s, nvme type:%s" % (om_ip, nvme_type))
            SshUtil.ssh_close(ssh_client)
            break
        if not success_login_node:
            err_msg = 'login failure, nodes:{}'.format(exist_pool_nodes)
            raise HCCIException(626309, err_msg)
        return nvme_type_list

    @staticmethod
    def get_bmc_node_ssd(bmc_client, bmc_ip):
        """
        获取bmc节点实际SSD类型
        :return: ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        """
        node_ssd_type = list()
        nvme_type_list = ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        for nvme_type in nvme_type_list:
            nvme_condition = DeployConstant.NVME_TYPE_CONDITION.get(nvme_type)
            cmd = nvme_condition.get("cmd")
            result = bmc_client.run([bmc_ip], cmd)
            if result[bmc_ip]["result"] != "0":
                logger.error("Failed to get [%s] nvme from node[%s]." % (
                    nvme_type, bmc_ip))
                continue
            logger.info("bmc node: %s, nvme type:%s" % (bmc_ip, nvme_type))
            node_ssd_type.append(nvme_type)
        if not node_ssd_type:
            err_msg = "node %s has no nvme ssd or connection timed out." % bmc_ip
            logger.error(err_msg)
        return node_ssd_type

    @staticmethod
    def get_node_ssd(node):
        """
        通过管理ip登录ssh_client查询节点ssd类型
        :return: ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        """
        nvme_type_list = list()
        om_ip = node.get("manageIp")
        account_info = node["creuser"].split(',')
        user = account_info[2]
        passwd = account_info[3]
        ssh_client = SshUtil.ssh_create_client(om_ip, user, passwd)
        type_list = ["es3000", "intel_p4610", "white_label_A28", 'samsung_nvme']
        for type in type_list:
            logger.info("Check whether the node[%s] contains the [%s] type." % (om_ip, type))
            type_condition = DeployConstant.NVME_TYPE_CONDITION.get(type)
            cmd = type_condition.get("cmd")
            pattern = type_condition.get("pattern")
            ret_cmd = SshUtil.ssh_exec_command_return(ssh_client, cmd)
            logger.info("Run query cmd[%s],result:%s" % (cmd, str(ret_cmd)))
            ret_cmd = "".join(ret_cmd)
            if re.findall(pattern, ret_cmd):
                nvme_type_list.append(type)
                logger.info("Expanded node: %s, nvme type:%s" % (om_ip, type))
        SshUtil.ssh_close(ssh_client)
        return list(set(nvme_type_list))
