# -*-coding:utf-8-*-
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import re
import math
import ipaddress

import utils.common.log as logger
from utils.business.project_util import ProjectApi
from utils.common.param_check import check_param_ip
from utils.common.error.hcci_error_code import get_code_msg
from utils.business.project_condition_utils import get_project_conditions
from utils.business.project_condition_utils import get_project_condition_boolean
from plugins.DistributedStorage.Deploy.params.network_utils import get_subnet
from plugins.DistributedStorage.Deploy.params.network_utils import is_ip_in_net
from plugins.DistributedStorage.Deploy.params.network_utils import is_ip_net_ip
from plugins.DistributedStorage.Deploy.params.network_utils import is_ip_broadcast_ip
from plugins.DistributedStorage.Deploy.params.network_utils import is_ip_in_section
from plugins.DistributedStorage.Deploy.params.network_utils import does_net_match_mask
from plugins.DistributedStorage.Deploy.params.network_utils import START_ADDR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import START_ADDR_ERROR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import END_ADDR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import END_ADDR_ERROR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import IP_POOL_ERROR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import GW_ERROR_NOCONTAIN
from plugins.DistributedStorage.Deploy.params.network_utils import GW_NET_ERROR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import GW_BROADCAST_ERROR_MSG
from plugins.DistributedStorage.Deploy.params.network_utils import GW_ERROR_CONTAIN
from plugins.DistributedStorage.Deploy.params.network_utils import SUBNET_MASK_ERROR_MSG
from plugins.DistributedStorage.utils.common.deploy_constant import DeployConstant


class BaseParamCheckHook(object):
    def __init__(self, exist_list, ip_list, reachable_list):
        self.exist_list = exist_list
        self.ip_list = ip_list
        self.reachable_list = reachable_list

    def check(self, input_params, project_id):
        if not input_params:
            return True, ''
        temp_params = self.resolve_input_params(input_params)
        error_msg_dict = self.check_params(temp_params, project_id, self.exist_list, self.ip_list)
        if not error_msg_dict:
            logger.info('check param ok.')
            return True, ''
        return False, error_msg_dict

    def check_params(self, input_params, project_id, exist_list, ip_list):
        """
        检查参数是否填写、IP是否与工程选择的IPV4或IPV6、掩码和vlan是否合规
        input_params: [{"key": "bmc_ip", "value: "xxx.xxx.xxx.xxx"},
                       {"key":"bmc_passwd", "value": "******"}...]
        """
        error_msg_dict = {}
        # 将input_params转换成dict
        if ProjectApi().is_ipv6_project(project_id):
            ip_version = 6
        else:
            ip_version = 4
        for param_item in input_params:
            if param_item['key'] not in exist_list:
                continue
            # 非空flag
            no_empty_flag = True
            if param_item['key'] == "hostname":
                match_re = '^(?!-|\.)[A-Za-z0-9-\.]{1,64}(?!-|\.)$'
                if re.match(match_re, param_item['value']) is None:
                    error_msg_dict[param_item['key']] = get_code_msg('626272') % param_item['key']
                if ".." in param_item['value'] or ".-" in param_item['value'] or "-." in param_item['value']:
                    error_msg_dict[param_item['key']] = get_code_msg('626272') % param_item['key']
            # check not empty
            if not param_item['value']:
                no_empty_flag = False
                error_msg_dict[param_item['key']] = get_code_msg('626260') % param_item['key']
            # check is ip
            if no_empty_flag and param_item['key'] in ip_list:
                if not check_param_ip(param_item['value'], ip_version=ip_version):
                    error_msg_dict[param_item['key']] = \
                        get_code_msg('626261') % (param_item['key'], param_item['value'])
            # 基本参数检查：检查掩码、vlan
            error_dict = self.check_network_params(param_item, ip_version, no_empty_flag)
            error_msg_dict.update(error_dict)

        error_dict = self.check_network_ip_range(input_params, error_msg_dict)
        error_msg_dict.update(error_dict)
        return error_msg_dict

    def resolve_input_params(self, input_params):
        return input_params

    def check_network_params(self, param_item, ip_version, no_empty_flag):
        return {}

    def check_network_ip_range(self, input_params, error_msg_dict):
        return {}


class InstallOSServerListCheckHook(BaseParamCheckHook):
    """
    华为分布式存储分离部署参数检查
    """
    def resolve_input_params(self, input_params):
        """
        return: [{"key": "bmc_ip", "value: "xxx.xxx.xxx.xxx"}, ...]
        """
        return [{'key': key, "value": value} for key, value in input_params.items()]


class InstallOSParamCheckHook(BaseParamCheckHook):
    """
    基本参数检查
    """
    def __init__(self, exist_list, ip_list, reachable_list, ip_format_list):
        super(InstallOSParamCheckHook, self).__init__(exist_list, ip_list, reachable_list)
        self.ip_format_list = ip_format_list
        self.params = {}

    def check_network_params(self, param_item, ip_version, not_empty_flag):
        error_dict = {}
        # check mask
        mask_error_dict = self.check_network_mask_params(param_item, ip_version, not_empty_flag)
        error_dict.update(mask_error_dict)
        # check vlan
        if not_empty_flag is True and param_item['key'] in self.ip_format_list and "_vlan" in param_item['key']:
            if str(param_item['value']).isdigit():
                if int(param_item['value']) < 0 or int(param_item['value']) > 4096:
                    error_dict[param_item['key']] = get_code_msg('626256') % (param_item['key'], param_item['value'])
            else:
                error_dict[param_item['key']] = get_code_msg('626256') % (param_item['key'], param_item['value'])
        return error_dict

    def check_network_mask_params(self, param_item, ip_version, not_empty_flag):
        error_dict = dict()
        if not_empty_flag is True and param_item['key'] in self.ip_format_list \
                and "_netmask" in param_item['key']:
            if ip_version == 4:
                mask_re = '^(254|252|248|240|224|192|128|0)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)' \
                          '\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|' \
                          '240|224|192|128|0)$'
                if re.match(mask_re, str(param_item['value'])) is None:
                    error_dict[param_item['key']] = get_code_msg('626254') % (param_item['key'], param_item['value'])
            if ip_version == 6:
                if not str(param_item['value']).isdigit() or \
                        str(param_item['value']).isdigit() and \
                        int(param_item['value']) < 32 or int(param_item['value']) > 126:
                    error_dict[param_item['key']] = get_code_msg('626255') % \
                                                    (param_item['key'], param_item['value'])
        return error_dict

    def get_network_set(self):
        network_set = set()
        for key in self.ip_format_list:
            tempkey = key.replace("_range", "").replace("_gateway", "").replace("_netmask", "").replace("_vlan", "")
            network_set.add(tempkey)
        return network_set

    def check_network_ip_range(self, input_params, error_msg_dict):
        error_dict = {}
        networks = self.get_network_set()
        for params_item in input_params:
            self.params[params_item["key"]] = params_item["value"]
        for network_key in networks:
            manage_net_range, manage_gateway, manage_netmask = self.get_manage_net_params(network_key)
            if manage_net_range not in self.params:
                continue
            # 规划的ip范围和掩码未填写，不做后续检查，避免脚本执行异常
            if manage_net_range in error_msg_dict or manage_netmask in error_msg_dict:
                continue
            if self.params.get(manage_net_range).find('-') == -1:
                error_dict[manage_net_range] = get_code_msg('626260') % manage_net_range
                err_msg = "Incorrect parameter format of manage_net_range[%s] ." \
                          "The parameter must contain a hyphen (-)." % self.params.get(manage_net_range)
                logger.error(err_msg)
                continue

            try:
                start_addr, end_addr = self.params[manage_net_range].split('-')
            except KeyError as e:
                logger.error('Key not exist. Detail:%s' % str(e))
                raise e
            subnet = get_subnet(start_addr, self.params.get(manage_netmask))
            # 检查start ip 和 end ip是否在子网内
            err_msg = self.check_ip_in_subnet(start_addr, end_addr, subnet, manage_net_range)
            error_dict.update(err_msg)

            # 检查网关是否在子网内，以及网关是否被包含在start ip和end ip内
            err_msg = self.check_gateway(*[manage_gateway, error_msg_dict, subnet, start_addr, end_addr])
            error_dict.update(err_msg)

            # 检查子网网段和掩码是否匹配
            if self.params.get(manage_netmask) and manage_netmask not in error_msg_dict:
                netmask = self.params.get(manage_netmask)
                if not does_net_match_mask(subnet, netmask):
                    error_dict[manage_netmask] = SUBNET_MASK_ERROR_MSG % (subnet, netmask)
        return error_dict

    def get_manage_net_params(self, network_key):
        manage_net_range = ""
        manage_gateway = ""
        manage_netmask = ""
        for key in self.ip_format_list:
            if network_key == key.replace("_range", ""):
                manage_net_range = key
            elif network_key == key.replace("_gateway", ""):
                manage_gateway = key
            elif network_key == key.replace("_netmask", ""):
                manage_netmask = key
        return manage_net_range, manage_gateway, manage_netmask

    def check_ip_in_subnet(self, start_addr, end_addr, subnet, manage_net_range):
        error_dict = {}
        if start_addr:
            if not (is_ip_in_net(start_addr, subnet)):
                error_dict[manage_net_range] = START_ADDR_MSG % subnet
            elif is_ip_net_ip(start_addr, subnet):
                error_dict[manage_net_range] = START_ADDR_ERROR_MSG % start_addr
        if end_addr:
            if not (is_ip_in_net(end_addr, subnet)):
                error_dict[manage_net_range] = END_ADDR_MSG % subnet
            elif is_ip_broadcast_ip(end_addr, subnet):
                error_dict[manage_net_range] = END_ADDR_ERROR_MSG % end_addr
            else:
                if ipaddress.ip_address(start_addr) > ipaddress.ip_address(end_addr):
                    error_dict[manage_net_range] = IP_POOL_ERROR_MSG % (start_addr, end_addr)
        return error_dict

    def check_gateway(self, *args):
        manage_gateway, error_msg_dict, subnet, start_addr, end_addr = args
        error_dict = {}
        if self.params.get(manage_gateway) and manage_gateway not in error_msg_dict:
            gateway = self.params.get(manage_gateway)
            if not (is_ip_in_net(gateway, subnet)):
                error_dict[manage_gateway] = GW_ERROR_NOCONTAIN % subnet
            elif is_ip_net_ip(gateway, subnet):
                error_dict[manage_gateway] = GW_NET_ERROR_MSG % gateway
            elif is_ip_broadcast_ip(gateway, subnet):
                error_dict[manage_gateway] = GW_BROADCAST_ERROR_MSG % gateway

            if end_addr and manage_gateway not in error_msg_dict:
                section = start_addr + '-' + end_addr
                if is_ip_in_section(gateway, section):
                    error_dict[manage_gateway] = GW_ERROR_CONTAIN % (gateway, section)
        return error_dict


class PoolCheck(object):
    def __init__(self):
        self.min_disk_num = 0
        self.max_disk_num = 0
        self.disk_num = 0

    def record_disk_num(self, disk_num):
        if self.min_disk_num == 0:
            self.min_disk_num = disk_num
        else:
            if disk_num < self.min_disk_num:
                self.min_disk_num = disk_num

        if self.max_disk_num == 0:
            self.max_disk_num = disk_num
        else:
            if disk_num > self.max_disk_num:
                self.max_disk_num = disk_num

        self.disk_num += disk_num

    def check_disk_num(self, pool_name):
        ret_msg = dict()
        disk_info = "Disk num of %s: min_disk_num:%s, max_disk_num:%s, disk_num:%s" % (
            pool_name, self.min_disk_num, self.max_disk_num, self.disk_num)
        logger.info(disk_info)
        if self.max_disk_num - self.min_disk_num > 2:
            err_msg = "The difference between maximal[%s] and minimal[%s] main " \
                      "storage number is more than 2." % (self.max_disk_num, self.min_disk_num)
            logger.error(err_msg)
            ret_msg['626268:{}'.format(pool_name)] = get_code_msg('626268') % pool_name
        try:
            percentage = (self.max_disk_num - self.min_disk_num) / float(self.max_disk_num)
        except ZeroDivisionError as e:
            err_msg = 'ZeroDivisionError, Detail:%s' % str(e)
            logger.error(err_msg)
            raise e
        if percentage > 0.3:
            percentage = percentage * 100
            err_msg = "The difference[%s%%] between maximal and minimal main storage number is " \
                      "more than 30%% of maximax number." % percentage
            logger.error(err_msg)
            ret_msg['626269:{}'.format(pool_name)] = get_code_msg('626269') % pool_name
        if self.disk_num < 12:
            err_msg = "The total main storage number is less than 12."
            logger.error(err_msg)
            ret_msg['626270:{}'.format(pool_name)] = get_code_msg('626270') % pool_name
        return ret_msg


class BusinessParametersCheckUtils(object):
    def __init__(self):
        pass

    @staticmethod
    def check_system_user_info(project_id, param_item, error_dict):
        msg_dict = {}
        if "creuser" in error_dict:
            return msg_dict
        product_account_white_list = DeployConstant.ACCOUNT_WHITE_LIST
        system_account_password_list = param_item["creuser"].split(",")
        # 系统帐户密码不是两组帐户时出错
        if len(system_account_password_list) != 4:
            msg_dict["system_account_password"] = get_code_msg('626020') % "system_account_password"
            return msg_dict

        # 系统帐户密码的第一个必须是root帐户
        if system_account_password_list[0] != 'root':
            msg_dict["system_account_password"] = get_code_msg('626021') % "system_account_password"
            return msg_dict

        # 系统帐户密码不合法字符检查
        exist_illegal_chr = re.search(DeployConstant.ACCOUNT_ILLEGAL_CHARACTERS, system_account_password_list[1])
        if exist_illegal_chr:
            msg_dict["system_account_password"] = get_code_msg('626251') % "system_account_password"
            return msg_dict

        # 系统用户不能是产品内置用户
        if get_project_condition_boolean(project_id, '!TenantStorFB_Heterogeneous'):
            com_user = system_account_password_list[2]
            if com_user in product_account_white_list:
                msg_dict["system_account_password"] = get_code_msg('626106') % (com_user, product_account_white_list)
        return msg_dict

    @staticmethod
    def rack_status_and_node_number_check(fb_rack_ha, pool_info_list, input_params):
        """
        机柜级安全，机柜数和节点数要求：
            EC：
                1、单个存储池中：最少机柜数 按推荐配置(N+M)/M + 1， 最大机柜数<=32
                   当机柜数等于(N+M)/M时，错误信息中给出可跳过提示。
                2、单个存储池中: 服务器数>=12
                3、单个机柜中: 服务器数>=3
            3副本：
                1、单个存储池中，4<=机柜数<=12
                2、单个机柜中，3<=服务器数<=24
                3、单个存储池中，服务器数>=12
        """
        err_msg = {}
        if not fb_rack_ha:
            return err_msg
        for pool_info in pool_info_list:
            redundancy_policy_list = [
                (input_param.get("storagepool_redundancy_policy"),
                 input_param.get("ec_verify_fragments"),
                 input_param.get("ec_data_fragments"))
                for input_param in input_params
                if input_param.get('storage_pool_name_and_slot') == pool_info['name']
            ]
            redundancy_policy, ec_vf, ec_df = redundancy_policy_list[0]
            logger.info("pool data:%s" % pool_info)
            if redundancy_policy.upper() == 'EC':
                logger.info('pool:{}, EC policy:{}'.format(pool_info.get('name'), redundancy_policy))

                # 校验单个存储池中机柜数量:
                # 机柜数小于(n+m)/m+1时：如果机柜数为n+m/m，则报错，但是报错信息给出可跳过提示；否则，抛错误信息
                # 机柜数大于32时：抛错误信息
                min_rack = math.ceil((int(ec_df) + int(ec_vf)) / int(ec_vf))
                recommend_rack = math.ceil((int(ec_df) + int(ec_vf)) / int(ec_vf)) + 1
                if len(pool_info.get("rack_status")) < recommend_rack:
                    if len(pool_info.get("rack_status")) == min_rack:
                        # 机柜数为n+m/m时：报错，但是报错信息给出可跳过提示
                        err_msg["rack_id_number"] = get_code_msg('626402') % min_rack
                    else:
                        err_msg["rack_id_number"] = get_code_msg('626061') % "rack_id_number"
                if len(pool_info.get("rack_status")) > 32:
                    err_msg["rack_id_number"] = get_code_msg('626061') % "rack_id_number"

                for rack_info in pool_info.get("rack_status"):
                    if int(rack_info.get("server_num") < 3):
                        # 校验单个机柜中的服务器数量
                        err_msg["server_in_cabinets"] = get_code_msg('626063') % "server_in_cabinets"
                if int(pool_info.get("servers_num")) < 12:
                    # 校验单个存储池中服务器数量
                    err_msg["server_number"] = get_code_msg('626062') % "server"

            elif redundancy_policy.upper() == '3REDUNDANCY':
                logger.info('pool:{}, redundancy policy:{}'.format(pool_info.get('name'), redundancy_policy))
                if len(pool_info.get("rack_status")) < 4 or len(pool_info.get("rack_status")) > 12:
                    # 校验单个存储池中机柜数量
                    err_msg["rack_id_number"] = get_code_msg('626061') % "rack_id_number"
                for rack_info in pool_info.get("rack_status"):
                    if int(rack_info.get("server_num") < 3) or int(rack_info.get("server_num")) > 24:
                        # 校验单个机柜中的服务器数量
                        err_msg["server_in_cabinets"] = get_code_msg('626063') % "server_in_cabinets"
                if int(pool_info.get("servers_num")) < 12:
                    # 校验单个存储池中服务器数量
                    err_msg["server_number"] = get_code_msg('626062') % "server"

            else:
                logger.error('pool:{}, None policy'.format(pool_info.get('name')))
                err_msg["storagepool_redundancy_policy"] = get_code_msg('626299') % "storagepool_redundancy_policy"
        if err_msg:
            return err_msg

        # 校验存储池内机柜间节点数是否满足：节点数最多相差2个，且百分比最多相差小于33%
        for pool_info in pool_info_list:
            min_node, max_node = float("inf"), float("-inf")
            for rack_status in pool_info.get("rack_status"):
                min_node = min(min_node, rack_status.get("server_num"))
                max_node = max(max_node, rack_status.get("server_num"))
            if (max_node - min_node) > 2 or ((max_node - min_node) / max_node >= 0.33):
                err_msg["server_num"] = get_code_msg('626403') % (
                    (max_node - min_node), "{:.2f}%".format(((max_node - min_node) / max_node) * 100)
                )
        return err_msg

    @staticmethod
    def storage_pool_and_backend_pool_name_check(backend_pool_list, storage_pool_name_list):
        output = {}
        if not backend_pool_list or not storage_pool_name_list:
            return True, output
        backend_unfurl_pool_list = list()
        for backend_pools in backend_pool_list:
            backend_pools_list = backend_pools.split(',')
            backend_unfurl_pool_list.extend(backend_pools_list)
        # 检查1.2 business_storage_pool_x与FusionStorageparameters里存储池名的个数是否一致
        if len(backend_unfurl_pool_list) != len(storage_pool_name_list):
            storage_pool_data = "storage_pool_name_and_slot:%s" % ",".join(storage_pool_name_list)
            backend_pool_data = "business_storage_pool:%s" % ",".join(backend_unfurl_pool_list)
            output["storage_pool_name_and_slot and business_storage_pool"] = \
                get_code_msg('626022') % (storage_pool_data, backend_pool_data)
            return False, output
        # 检查1.2与FusionStorageparameters里存储池名是否一致
        for name in backend_unfurl_pool_list:
            same_flag = "False"
            for pool_name in storage_pool_name_list:
                if name.strip() == pool_name.strip():
                    same_flag = "True"
                    logger.info("The pool_name:%s in FusionStorageparameters is exist in Basic_Parameters" % pool_name)
            if same_flag != "True":
                err_msg = "The pool_name in FusionStorage parameters must be smae with Basic_Parameters"
                logger.error(err_msg)
                storage_pool_data = "storage_pool_name_and_slot:%s" % ",".join(storage_pool_name_list)
                backend_pool_data = "business_storage_pool:%s" % ",".join(backend_unfurl_pool_list)
                output["storage_pool_name_and_slot and business_storage_pool"] = \
                    get_code_msg('626022') % (storage_pool_data, backend_pool_data)
                return False, output
        return True, output

    @staticmethod
    def check_threetier_vlan(project_id, params, error_params):
        storage_vlan_key = "fusionstorage_service_vlan"
        error_dic = {}
        if storage_vlan_key in error_params:
            return error_dic
        # 获取计算节点vlan信息
        compute_vlan = params.get("service_storage_data_vlan_id", None)
        storage_vlan = params.get(storage_vlan_key, None)
        if compute_vlan and storage_vlan:
            if get_project_condition_boolean(project_id, "TenantStor_ThreeTierNetwork"):
                if storage_vlan == compute_vlan:
                    error_dic[storage_vlan_key] = get_code_msg('626204') % storage_vlan_key
            else:
                if storage_vlan != compute_vlan:
                    error_dic[storage_vlan_key] = get_code_msg('626205') % storage_vlan_key
                # 获取计算节点网段信息
        return error_dic

    @staticmethod
    def zk_slot_check(param_item, zk_slot, error_dict):
        msg_dict = {}
        # zk槽位是否填写检查
        if not param_item.get("zk_slot"):
            msg_dict["zk_slot"] = get_code_msg('626207')
            return msg_dict, zk_slot

        # zk节点zk槽位格式检测
        if not str(param_item["zk_slot"]).isdigit() and "zk_slot" not in error_dict:
            msg_dict["zk_slot"] = get_code_msg('626209')
            return msg_dict, zk_slot

        # zk节点zk槽位一致性检测
        if not zk_slot:
            zk_slot = param_item["zk_slot"]
        if zk_slot != param_item["zk_slot"] and "zk_slot" not in error_dict:
            msg_dict["zk_slot"] = get_code_msg('626208')
        return msg_dict, zk_slot

    @staticmethod
    def primary_slot_check(param_item, error_dict):
        msg_dict = {}
        # 主存槽位是否填写检测
        if not param_item.get("primary_slot"):
            msg_dict["primary_slot"] = get_code_msg('626212')
            return msg_dict
        if "primary_slot" in error_dict:
            return msg_dict

        # 主存槽位格式检查
        pattern = re.compile("^(?:[0-9]{1}|[1-9]{1}\d+)-\d+$")
        primary_slot = param_item["primary_slot"]
        if not pattern.match(primary_slot):
            msg_dict["primary_slot"] = get_code_msg('626213')
            return msg_dict

        # 单个节点槽位数量检查
        start_slot, end_slot = primary_slot.split("-")
        if int(end_slot) - int(start_slot) < 3:
            msg_dict["primary_slot"] = get_code_msg('626213')
        return msg_dict

    @staticmethod
    def primary_disk_type_check(param_item, primary_disk_type, disk_type, error_dict):
        msg_dict = {}
        if error_dict.get("primary_type"):
            return msg_dict, primary_disk_type
        if not param_item.get("primary_type"):
            msg_dict["primary_type"] = get_code_msg(626344)
            return msg_dict, primary_disk_type
        if primary_disk_type is None:
            primary_disk_type = param_item["primary_type"]
        if primary_disk_type not in disk_type or primary_disk_type != param_item["primary_type"]:
            msg_dict["primary_type"] = get_code_msg(626345) % disk_type
        return msg_dict, primary_disk_type

    @staticmethod
    def cache_type_check(param_item, support_cache, cache_type, error_dict):
        msg_dict = {}
        if error_dict.get("cache_type"):
            return msg_dict, cache_type
        if cache_type is None:
            cache_type = param_item.get("cache_type")
        if cache_type not in support_cache or cache_type != param_item.get("cache_type"):
            msg_dict["cache_type"] = get_code_msg(626012) % support_cache
        return msg_dict, cache_type

    @staticmethod
    def zk_disk_type_check(param_item, zk_type, disk_type, error_dict):
        msg_dict = {}
        if error_dict.get("zk_type"):
            return msg_dict, zk_type
        if not param_item.get("zk_type"):
            msg_dict["zk_type"] = get_code_msg(626347)
            return msg_dict, zk_type
        if param_item["zk_type"] not in disk_type:
            msg_dict["zk_type"] = get_code_msg(626343) % disk_type
            return msg_dict, zk_type
        if not zk_type:
            zk_type = param_item["zk_type"]
        if zk_type != param_item["zk_type"]:
            msg_dict["zk_type"] = get_code_msg(626343) % disk_type
        return msg_dict, zk_type

    @staticmethod
    def check_zk_num(output, zk_node_num, osd_node_list):
        # 元数据节点数量检查：[3, 5, 7, 9]
        logger.info("The number of metadata nodes is:%s" % str(zk_node_num))
        if zk_node_num not in [3, 5, 7, 9]:
            output["is_metadata_node"] = get_code_msg('626206') % str(zk_node_num)
        if len(osd_node_list) >= 5 and zk_node_num not in [5, 7, 9]:
            output["is_metadata_node"] = get_code_msg('626405') % str(zk_node_num)

    @staticmethod
    def redundancy_policy_check(param_item, pool, redundancy_policy, error_dict):
        msg_dict = {}
        if error_dict.get("storagepool_redundancy_policy"):
            return msg_dict, redundancy_policy
        if redundancy_policy is None:
            redundancy_policy = param_item["storagepool_redundancy_policy"]
        if redundancy_policy != param_item["storagepool_redundancy_policy"]:
            msg_dict["storagepool_redundancy_policy"] = get_code_msg('626013') % pool
        return msg_dict, redundancy_policy

    @staticmethod
    def ec_data_fragment_check(param_item, ec_data_fragments, error_dict):
        msg_dict = {}
        if param_item["storagepool_redundancy_policy"].upper() != "EC" or error_dict.get("ec_data_fragments"):
            return msg_dict, ec_data_fragments
        if not param_item.get("ec_data_fragments"):
            msg_dict["ec_data_fragments"] = get_code_msg(626092)
            return msg_dict, ec_data_fragments
        logger.info("ec_data_fragments={}".format(ec_data_fragments))
        if ec_data_fragments is None:
            ec_data_fragments = param_item.get("ec_data_fragments")
        if ec_data_fragments != param_item.get("ec_data_fragments"):
            msg_dict["ec_data_fragments"] = get_code_msg(626092)
        return msg_dict, ec_data_fragments

    @staticmethod
    def ec_combination_check(output, param_item, pool):
        """基于性能考虑，建议EC数据分片不大于8,数据分片不小于校验分片的两倍，除了+4:6的场景"""
        if param_item.get("storagepool_redundancy_policy").upper() != "EC" \
                or "ec_data_fragments" in output \
                or "ec_verify_fragments" in output:
            return
        ec_data_fragments = int(param_item.get("ec_data_fragments"))
        ec_verify_fragments = int(param_item.get("ec_verify_fragments"))
        if ec_data_fragments > 8:
            output["ec_data_fragments"] = get_code_msg('626392') % (pool, ec_data_fragments)
        if ec_data_fragments // ec_verify_fragments < 2:
            if ec_data_fragments != 6 or ec_verify_fragments != 4:
                output["ec_data_fragments"] = get_code_msg('626401') % (pool, ec_data_fragments, ec_verify_fragments)

    @staticmethod
    def ec_verify_fragment_check(param_item, ec_verify_fragments, error_dict):
        msg_dict = {}
        if param_item["storagepool_redundancy_policy"].upper() != "EC" or error_dict.get("ec_verify_fragments"):
            return msg_dict, ec_verify_fragments
        if not param_item.get("ec_verify_fragments"):
            msg_dict["ec_verify_fragments"] = get_code_msg(626094)
            return msg_dict, ec_verify_fragments
        logger.info("ec_verify_fragments={}".format(ec_verify_fragments))
        if ec_verify_fragments is None:
            ec_verify_fragments = param_item.get("ec_verify_fragments")
        if ec_verify_fragments != param_item.get("ec_verify_fragments"):
            msg_dict["ec_verify_fragments"] = get_code_msg(626094)
        return msg_dict, ec_verify_fragments

    @staticmethod
    def storage_pool_type_check(param_item: dict, pool_type: str, error_dict: dict):
        msg_dict = {}
        if error_dict.get("storage_pool_type"):
            return msg_dict, pool_type
        _pool_type = param_item.get("storage_pool_type")
        if pool_type is None:
            pool_type = _pool_type
        if pool_type != _pool_type:
            msg_dict["storage_pool_type"] = get_code_msg(626353)
        return msg_dict, pool_type

    @staticmethod
    def all_flash_primary_type_check(node_info, error_dict):
        msg_dict = {}
        for param_item in node_info:
            if error_dict.get("primary_type") or error_dict.get("cache_type"):
                break
            if param_item.get("cache_type") != 'none':
                break
            if param_item.get("primary_type") not in DeployConstant.LLD_DISK_TYPE[2:]:
                msg_dict["primary_type"] = get_code_msg(626348) % DeployConstant.LLD_DISK_TYPE[2:]
        return msg_dict

    @staticmethod
    def zk_slot_and_primary_slot_conflict_check(param_item, error_dict):
        msg_dict = {}
        if "primary_slot" in error_dict or "zk_slot" in error_dict:
            return msg_dict
        zk_slot = param_item.get("zk_slot")
        if not zk_slot:
            return msg_dict
        primary_slot = param_item.get("primary_slot")
        primary_slot_start, primary_slot_end = primary_slot.split('-')
        if int(primary_slot_start) <= int(zk_slot) <= int(primary_slot_end):
            msg_dict["primary_slot"] = get_code_msg('626208')
        return msg_dict

    @staticmethod
    def hostname_check(param_item, hostname, error_dict):
        msg_dict = {}
        if "hostname" in error_dict:
            return msg_dict
        hostname["node_num"] += 1
        hostname["hostname_set"].add(param_item.get('hostname'))
        if hostname["node_num"] != len(hostname["hostname_set"]):
            msg_dict["hostname"] = get_code_msg('626108') % hostname["hostname_set"]
        return msg_dict

    @staticmethod
    def get_rack_ha_pool_info_list(param_item, storage_pool_name, storage_pool_name_set, pool_info_list):
        """
        :param param_item:
        :param storage_pool_name:
        :param storage_pool_name_set:
        :param pool_info_list:
        :return: 存储池信息列表pool_info_list = [存储池信息字典1pool_info_dict1, 存储池信息字典2, ...]
                存储池信息字典pool_info_dict {
                            存储池名称name: "storage_pool_name1",
                            高可用状态rack_status: [rack_status1, rack_status2],
                            服务器数量servers_num: 1
                            }
                高可用状态 rack_status= {"rack_id高可用id": 'abc', "server_num": 1}
        """
        pool_info_dict = {}
        if storage_pool_name not in storage_pool_name_set:
            rack_status = dict()
            pool_info_dict["name"] = storage_pool_name
            pool_info_dict["rack_status"] = []
            rack_status['rack_id'] = param_item["rack_id"]
            rack_status['server_num'] = 1
            pool_info_dict.get("rack_status").append(rack_status)
            pool_info_dict["servers_num"] = 1
            pool_info_list.append(pool_info_dict)
            return pool_info_list
        # 刷新pool_info_list，存储池名称固定
        for pool_info in pool_info_list:
            if storage_pool_name != pool_info.get("name"):
                continue
            # 存储池服务器数+1
            pool_info["servers_num"] += 1
            # 判断机柜信息是否存在，不存在则进行记录
            logger.info("pool info data:%s" % pool_info)
            rack_id_list = [rack_info.get("rack_id") for rack_info in pool_info.get("rack_status")]
            if param_item["rack_id"] not in rack_id_list:
                status = dict()
                status['rack_id'] = param_item["rack_id"]
                status['server_num'] = 1
                pool_info["rack_status"].append(status)
                logger.info("add new rack id:%s" % status)
                continue
            for rack_info in pool_info["rack_status"]:
                rack_id_list.append(rack_info["rack_id"])
                if rack_info["rack_id"] == param_item["rack_id"]:
                    rack_info["server_num"] += 1
                    logger.info("The rack[%s] increases by 1." % rack_info)
                    break
        return pool_info_list

    @staticmethod
    def get_backend_pool(project_id, input_params):
        logger.info("Start to query list of backend pool")
        backend_pool_list = list()
        if not input_params:
            return backend_pool_list
        backend_count = BusinessParametersCheckUtils.get_business_backend_count(project_id)
        logger.info('backend count: %s' % backend_count)
        business_pool_key_list = list()
        for index in range(1, backend_count + 1):
            if index == 1:
                business_pool_key_list.append("business_storage_pool")
            else:
                business_pool_key_list.append('{}_{}'.format("business_storage_pool", index))
        logger.info("backend key list:%s" % business_pool_key_list)
        for param_item in input_params:
            if param_item['key'] in business_pool_key_list:
                backend_pool_list.append(param_item['value'])
        logger.info("")
        logger.info("backend value list:%s" % business_pool_key_list)
        return backend_pool_list

    @staticmethod
    def get_business_backend_count(project_id):
        project_conditions = get_project_conditions(project_id)
        backend_count = 1
        for index in range(2, DeployConstant.MAX_BUSINESS_BACKEND + 1):
            if project_conditions.get('MultiTenantFB80StoragePool{}'.format(index)):
                backend_count += 1
            else:
                break
        return backend_count

    def storage_pool_params_check(self, storage_pool_name_list, osd_node_list, disk_type, support_cache):
        error_dict = {}
        for pool in storage_pool_name_list:
            primary_disk_type = None
            pool_redundancy_policy = None
            cache_type, pool_type = None, None
            ec_data_fragments, ec_verify_fragments = None, None
            for osd_node_item in osd_node_list:
                if pool != osd_node_item["storage_pool_name_and_slot"]:
                    continue
                # 主存盘类型检查
                logger.info("start to check primary disk type")
                msg_dict, primary_disk_type = self.primary_disk_type_check(
                    osd_node_item, primary_disk_type, disk_type, error_dict)
                error_dict.update(msg_dict)
                logger.info("check primary disk type complete, result: {}".format(msg_dict))
                # 缓存类型检查
                msg_dict, cache_type = self.cache_type_check(osd_node_item, support_cache, cache_type, error_dict)
                error_dict.update(msg_dict)
                # 存储资源池冗余策略检查
                msg_dict, pool_redundancy_policy = self.redundancy_policy_check(
                    osd_node_item, pool, pool_redundancy_policy, error_dict)
                error_dict.update(msg_dict)
                # ec数据分片检查
                msg_dict, ec_data_fragments = self.ec_data_fragment_check(osd_node_item, ec_data_fragments, error_dict)
                error_dict.update(msg_dict)
                # ec校验分片检查
                msg_dict, ec_verify_fragments = self.ec_verify_fragment_check(
                    osd_node_item, ec_verify_fragments, error_dict)
                error_dict.update(msg_dict)
                # ec数据和校验分片组合拦截
                self.ec_combination_check(error_dict, osd_node_item, pool)
                # 存储池类型检查
                msg_dict, pool_type = self.storage_pool_type_check(osd_node_item, pool_type, error_dict)
                error_dict.update(msg_dict)
        return error_dict


class ManageStorageParamsCheckUtils(object):
    def __init__(self):
        pass

    @staticmethod
    def not_ha_primary_capacity_check(param_item, primary_capacity, err_msg_dict):
        msg_dict = {}
        if not param_item["primary_capacity"]:
            msg_dict["managestorfb_primary_capacity"] = get_code_msg("626230")
        else:
            if "managestorfb_primary_capacity" not in err_msg_dict:
                if 0 == primary_capacity:
                    primary_capacity = param_item["primary_capacity"]
                elif primary_capacity != param_item["primary_capacity"]:
                    msg_dict["managestorfb_primary_capacity"] = get_code_msg("626231")
        return primary_capacity, msg_dict

    @staticmethod
    def not_ha_cache_check(param_item, manage_cache_type, cache_type, cache_capacity, err_msg_dict):
        cur_err_msg_dict = {}
        if err_msg_dict.get("managestorfb_cache_type") or err_msg_dict.get("managestorfb_cache_capacity"):
            return cache_type, cache_capacity, cur_err_msg_dict
        if cache_type is None:
            cache_type = param_item["cache_type"]
        if param_item["cache_type"] not in manage_cache_type or param_item["cache_type"] != cache_type:
            cur_err_msg_dict["managestorfb_cache_type"] = get_code_msg('626236') % manage_cache_type
            return cache_type, cache_capacity, cur_err_msg_dict
        if param_item["cache_type"] == 'none':
            return cache_type, cache_capacity, cur_err_msg_dict
        if cache_capacity is None:
            cache_capacity = param_item["cache_capacity"]
        elif param_item["cache_capacity"] != cache_capacity:
            cur_err_msg_dict["managestorfb_cache_capacity"] = get_code_msg("626233")
        return cache_type, cache_capacity, cur_err_msg_dict

    @staticmethod
    def not_ha_primary_slot_check(param_item, error_dict):
        msg_dict = {}
        # 主存槽位是否填写检测
        if not param_item.get("primary_slot"):
            msg_dict["managestorfb_primary_slot"] = get_code_msg("626234")
            return msg_dict
        if "managestorfb_primary_slot" in error_dict:
            return msg_dict
        # 主存槽位格式检查
        pattern = re.compile("^(?:[0-9]{1}|[1-9]{1}\d+)-\d+$")
        primary_slot = param_item["primary_slot"]
        if not pattern.match(primary_slot):
            msg_dict["managestorfb_primary_slot"] = get_code_msg("626235")
            return msg_dict

        # 单个节点槽位数量检查
        start_slot, end_slot = primary_slot.split("-")
        if int(end_slot) - int(start_slot) < 3:
            msg_dict["managestorfb_primary_slot"] = get_code_msg("626235")
        return msg_dict

    @staticmethod
    def zk_slot_check(param_item, zk_slot, error_dict):
        msg_dict = {}
        if not param_item["zk_slot"]:
            return zk_slot, msg_dict

        # zk节点zk槽位格式检测
        if not str(param_item["zk_slot"]).isdigit() and "managestorfb_zk_slot" not in error_dict:
            msg_dict["managestorfb_zk_slot"] = get_code_msg("626228")
            return zk_slot, msg_dict

            # zk节点zk槽位一致性检测
        if not zk_slot:
            zk_slot = param_item["zk_slot"]
        if zk_slot != param_item["zk_slot"] and "managestorfb_zk_slot" not in error_dict:
            msg_dict["managestorfb_zk_slot"] = get_code_msg("626229")
        return zk_slot, msg_dict

    @staticmethod
    def zk_slot_and_primary_slot_conflict_check(param_item, error_dict):
        msg_dict = {}
        if "managestorfb_primary_slot" in error_dict or "managestorfb_zk_slot" in error_dict:
            return error_dict
        zk_slot = param_item.get("zk_slot")
        if not zk_slot:
            return msg_dict
        primary_slot = param_item.get("primary_slot")
        primary_slot_start, primary_slot_end = primary_slot.split('-')
        if int(primary_slot_start) <= int(zk_slot) <= int(primary_slot_end):
            msg_dict["managestorfb_conflict_slot"] = get_code_msg("626229")
        return msg_dict

    @staticmethod
    def dc_check_primary_slot(param_item, err_msg_dict, site_name, key_name):
        # 主存槽位的检测
        msg_dict = {}
        if not param_item.get("primary_slot"):
            msg_dict[key_name] = get_code_msg('626248') % site_name
            return msg_dict
        if "primary_slot" in err_msg_dict:
            return msg_dict

        # 主存槽位格式检查
        pattern = re.compile("^(?:[0-9]{1}|[1-9]{1}\d+)-\d+$")
        primary_slot = param_item["primary_slot"]
        if not pattern.match(primary_slot):
            msg_dict["primary_slot"] = get_code_msg('626249') % site_name
            return msg_dict

        # 单个节点槽位数量检查
        start_slot, end_slot = primary_slot.split("-")
        if int(end_slot) - int(start_slot) < 3:
            msg_dict["primary_slot"] = get_code_msg('626249') % site_name
        return msg_dict

    @staticmethod
    def dc_check_primary_capacity(param_item, err_msg_dict, site_name, key_name, primary_capacity):
        # 主存盘容量的检测
        msg_dict = {}
        if not param_item["primary_capacity"]:
            msg_dict[key_name] = get_code_msg('626243') % site_name
        else:
            if key_name not in err_msg_dict:
                if primary_capacity == 0:
                    primary_capacity = param_item["primary_capacity"]
                elif primary_capacity != param_item["primary_capacity"]:
                    msg_dict[key_name] = get_code_msg('626244') % site_name
        return primary_capacity, msg_dict

    @staticmethod
    def dc_check_cache_type(*args):
        param_item, manage_cache_type, cache_type, site, err_msg_dict, err_key = args
        msg_dict = {}
        if err_msg_dict.get(err_key):
            return cache_type, msg_dict
        if cache_type is None:
            cache_type = param_item["cache_type"]
        elif param_item["cache_type"] not in manage_cache_type or cache_type != param_item["cache_type"]:
            msg_dict[err_key] = get_code_msg('626247') % (manage_cache_type, site)
        return cache_type, msg_dict

    @staticmethod
    def dc_check_cache_capacity(*args):
        param_item, err_msg_dict, site_name, cache_capacity, capacity_key_name, type_key_name = args
        # 缓存（卡、盘）总容量(GB)的检测
        msg_dict = {}
        if param_item["cache_type"] == 'none' or err_msg_dict.get(type_key_name) or err_msg_dict.get(capacity_key_name):
            none_cache_type = None
            return none_cache_type, msg_dict
        if not param_item["cache_capacity"]:
            msg_dict[capacity_key_name] = get_code_msg('626245') % site_name
        else:
            if cache_capacity == 0:
                cache_capacity = param_item["cache_capacity"]
            elif cache_capacity != param_item["cache_capacity"]:
                msg_dict[capacity_key_name] = get_code_msg('626246') % site_name
        return cache_capacity, msg_dict

    @staticmethod
    def check_region_ha_pool_disk_num(site, pool, server_list):
        pool_check = PoolCheck()
        for server in server_list:
            if not ("first_node" in server["bmc_role"] or "controller" in server["bmc_role"] or
                    ("cloud_service" in server["bmc_role"] and "osd" in server["ref_component"])):
                continue
            if server["site"] != site:
                continue
            disk_slot = server["primary_slot"]
            if disk_slot:
                disk_num = int(disk_slot.split('-')[1]) - int(disk_slot.split('-')[0]) + 1
                pool_check.record_disk_num(disk_num)
        return pool_check.check_disk_num(pool)


class TenantStorFBHCIParamsCheckUtils(object):
    def __init__(self):
        pass

    @staticmethod
    def zk_slot_check(param_item, zk_slot, error_dict):
        msg_dict = {}
        if not param_item["zk_slot"]:
            msg_dict["tenantstorfbhci_zk_slot"] = get_code_msg('626215')
            return zk_slot, msg_dict

        # zk节点zk槽位格式检测
        if not str(param_item["zk_slot"]).isdigit() and "tenantstorfbhci_zk_slot" not in error_dict:
            msg_dict["tenantstorfbhci_zk_slot"] = get_code_msg("626216")
            return zk_slot, msg_dict

        # zk节点zk槽位一致性检测
        if not zk_slot:
            zk_slot = param_item["zk_slot"]
        if zk_slot != param_item["zk_slot"] and "tenantstorfbhci_zk_slot" not in error_dict:
            msg_dict["tenantstorfbhci_zk_slot"] = get_code_msg("626217")
        return zk_slot, msg_dict

    @staticmethod
    def primary_slot_check(param_item, error_dict):
        msg_dict = {}
        # 主存槽位是否填写检测
        if not param_item.get("primary_slot"):
            msg_dict["tenantstorfbhci_primary_slot"] = get_code_msg("626218")
            return msg_dict
        if "tenantstorfbhci_primary_slot" in error_dict:
            return msg_dict
        # 主存槽位格式检查
        pattern = re.compile("^(?:[0-9]{1}|[1-9]{1}\d+)-\d+$")
        primary_slot = param_item["primary_slot"]
        if not pattern.match(primary_slot):
            msg_dict["tenantstorfbhci_primary_slot"] = get_code_msg("626219")
            return msg_dict
        # 单个节点槽位数量检查
        start_slot, end_slot = primary_slot.split("-")
        if int(end_slot) - int(start_slot) < 3:
            msg_dict["tenantstorfbhci_primary_slot"] = get_code_msg("626219")
        return msg_dict

    @staticmethod
    def primary_capacity_check(param_item, primary_capacity, err_msg_dict):
        msg_dict = {}
        if not param_item["primary_capacity"]:
            msg_dict["tenantstorfbhci_primary_capacity"] = get_code_msg('626220')
        else:
            if "tenantstorfbhci_primary_capacity" not in err_msg_dict:
                if 0 == primary_capacity:
                    primary_capacity = param_item["primary_capacity"]
                elif primary_capacity != param_item["primary_capacity"]:
                    msg_dict["tenantstorfbhci_primary_capacity"] = get_code_msg('626221')
        return primary_capacity, msg_dict

    @staticmethod
    def cache_type_check(param_item, cache_type, err_msg_dict):
        msg_dict = {}
        if not param_item["cache_type"]:
            err_msg_dict["tenantstorfbhci_cache_type"] = get_code_msg('626222')
        else:
            if "tenantstorfbhci_cache_type" not in err_msg_dict:
                if not cache_type:
                    cache_type = param_item["cache_type"]
                elif cache_type != param_item["cache_type"]:
                    err_msg_dict["tenantstorfbhci_cache_type"] = get_code_msg('626223')
        return cache_type, msg_dict

    @staticmethod
    def cache_capacity_check(param_item, cache_capacity, err_msg_dict):
        msg_dict = {}
        if not param_item["cache_capacity"]:
            msg_dict["tenantstorfbhci_cache_capacity"] = get_code_msg('626224')
        else:
            if "tenantstorfbhci_cache_capacity" not in err_msg_dict:
                if 0 == cache_capacity:
                    cache_capacity = param_item["cache_capacity"]
                elif cache_capacity != param_item["cache_capacity"]:
                    msg_dict["tenantstorfbhci_cache_capacity"] = get_code_msg('626225')
        return cache_capacity, msg_dict

    @staticmethod
    def zk_slot_and_primary_slot_conflict_check(param_item, error_dict):
        msg_dict = {}
        if "tenantstorfbhci_primary_slot" in error_dict or "tenantstorfbhci_zk_slot" in error_dict:
            return error_dict
        zk_slot = param_item.get("zk_slot")
        if not zk_slot:
            return msg_dict
        primary_slot = param_item.get("primary_slot")
        primary_slot_start, primary_slot_end = primary_slot.split('-')
        if int(primary_slot_start) <= int(zk_slot) <= int(primary_slot_end):
            msg_dict["managestorfb_conflict_slot"] = get_code_msg("626216")
        return msg_dict
