#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import collections

import IPy

from utils.business.param_util import ParamUtil
from utils.business.vm_util import can_vm_pinged_to
from utils.common.exception import FCDException
from utils.common.fic_base import StepBaseInterface, TestCase
from utils.common.message import Message
import utils.common.log as logger
from utils.common.ssh_util import Ssh as ssh
from platforms.project.ProjectUtils import get_project_condition_boolean
from plugins.DistributedStorageReplication.scripts.common_utils.ping_utils import PingSSHclient, client_can_pinged_to
from plugins.DistributedStorageReplication import RestOperate
from plugins.DistributedStorageReplication.scripts.common_utils.config_params import Params
from plugins.DistributedStorage.scripts.utils.common.DeployConstant \
    import DeployConstant
from plugins.DistributedStorage.scripts.logic.InstallOperate import InstallOperate


class ConfigRoute(StepBaseInterface):
    def __init__(self, project_id, pod_id):
        self.project_id = project_id
        self.pod_id = pod_id

    def pre_check(self, project_id, pod_id):
        """
        插件内部接口：安装rpm依赖包，该接口由execute接口调用，工具框架不会直接调用此接口。
        :param project_id:
        :param pod_id:
        :return:
        """
        return Message()

    def execute(self, project_id, pod_id):
        try:
            ConfigRouteOne(project_id, pod_id).procedure()
        except FCDException as e:
            logger.error(e)
            return Message(500, e)
        except Exception as e:
            logger.error(e)
            return Message(500, FCDException(627177, str(e)))
        return Message(200)

    def rollback(self, project_id, pod_id):
        """
        标准调用接口：执行回滚
        :param project_id:
        :param pod_id:
        :return:Message类对象
        """
        return Message()

    def retry(self, project_id, pod_id):
        """
        标准调用接口：重试
        :return: Message类对象
        """
        return self.execute(project_id, pod_id)

    def check(self, project_id, pod_id):
        """
        标准调用接口：重试
        :param project_id:
        :param pod_id:
        :return:
        """
        return Message()


class ConfigRouteOne(TestCase):
    def __init__(self, project_id, pod_id):
        super(ConfigRouteOne, self).__init__(project_id, pod_id)
        self.pod_id = pod_id
        self.project_id = project_id
        self.fsa_info = self.db.get_install_os_list_info(self.pod_id, 'rep')
        self.service_name = "FusionStorageBlockReplication"
        self.param_util = ParamUtil()
        parameters = Params(self.project_id, self.pod_id, self.service_name)
        self.fs_args = parameters.get_params_dict()
        self.user_name = DeployConstant.DM_LOGIN_USER
        self.update_pwd = self.fs_args.get("local_admin_password")
        self.remote_admin_pwd = self.fs_args.get("produce_admin_password")
        self.remote_float_ip = self.fs_args.get("remote_storage_fsm_ip")
        self.storage_arbitration_ip = self.fs_args.get("storage_arbitration_ip")
        self.install_operate = InstallOperate(self.project_id, self.pod_id)
        self.fsa_info = list()
        self.float_ip = self.fs_args.get("local_storage_fsm_ip")
        self.opr = RestOperate(self.float_ip)
        self.remote_opr = None

    @classmethod
    def get_network_specification(cls, ip_range: str, net_mask: str):
        start_ip = ip_range.split('-')[0]
        return IPy.IP(start_ip).make_net(net_mask)

    @staticmethod
    def config_rep_static_route(opt, ip_list, gateway, quorum_server_mask, quorum_server_ip):
        nodes = list()
        for mgmt_ip in ip_list:
            node = dict()
            node["routes"] = [{
                "mask": quorum_server_mask,
                "destination": quorum_server_ip,
                "gateway": gateway,
                "port_name": "rep_arb"
            }]
            node["management_ip"] = mgmt_ip
            nodes.append(node)
        data = dict()
        data["nodes"] = nodes
        logger.info("Config rep node arb static route, node infos:%s" % data)
        rsp_result, _ = opt.add_static_route(data)
        code = rsp_result.get("code")
        if code != 0:
            error_msg = "Failed to config static route, details:%s" % rsp_result
            logger.error(error_msg)
            raise Exception(error_msg)
        logger.info("Success to config static route for host:%s" % ip_list)

    @staticmethod
    def check_connect_boolean(ip_list):
        fail_list = []
        for ip in ip_list:
            if not can_vm_pinged_to(ip):
                fail_list.append(ip)
        if fail_list:
            return False, fail_list
        return True, ''

    @staticmethod
    def check_level(client):
        for num in range(200, 255):
            cmd = "cat /etc/iproute2/rt_tables |grep %s" % num
            cmd = cmd + ";echo last cmd result: $?\n"
            result = ssh.execute_command(client, cmd, 180, 3)
            if str(result).find('last cmd result: 0') < 0:
                return num, num + 1
        raise Exception()

    def procedure(self):
        logger.info("Start to config policy route to rep node.")
        self.login_deploy_manager()
        log_module_name = self.args_dict['log_module_name']
        logger.init(log_module_name)
        # 连通性校验
        fsa_ip_list = [info['manageIp'] for info in self.fsa_info]
        flag, result = self.check_connect_boolean(fsa_ip_list)
        if not flag:
            logger.error('Some node can not connect.detail:%s' % str(result))
            raise FCDException(627175, str(result))
        logger.info('nodes connect check success.')
        logger.info('Start to get net info, netmask, gw.')
        net_info_list = self.get_net_info()
        logger.info('End to get net info,detail:%s' % str(net_info_list))
        # 复制平面采用策略路由，调用rest接口进行配置
        route_infos = self.get_rep_policy_route_info(fsa_ip_list, net_info_list)
        self.config_rep_policy_route(route_infos)
        if get_project_condition_boolean(self.project_id, 'CSHAStorage_TFB&!DRStorage_TFB_PD'):
            # 复制节点仲裁路由采用静态路由，调用rest接口进行配置
            self.config_arbitration_static_route(fsa_ip_list)
        self.connect_check()

    def config_arbitration_static_route(self, fsa_ip_list):
        """
        配置复制节点仲裁网口静态路由，使用32位掩码指定目标地址
        :param fsa_ip_list:
        :return:
        """
        quorum_server_mask = "255.255.255.255"
        local_gateway = self.param_util.get_param_value(
            self.pod_id, self.service_name, 'fusionstorage_arb_gateway')
        self.remote_opr_login()
        remote_ip_list = self.query_remote_rep_ip_list()
        remote_gateway = self.param_util.get_param_value(self.pod_id, self.service_name, 'produce_storage_arb_gateway')
        self.config_rep_static_route(self.opr, fsa_ip_list, local_gateway,
                                     quorum_server_mask, self.storage_arbitration_ip)
        self.config_rep_static_route(self.remote_opr, remote_ip_list, remote_gateway,
                                     quorum_server_mask, self.storage_arbitration_ip)

    def query_remote_rep_ip_list(self):
        rsp_result, rsp_data = self.remote_opr.query_network_servers()
        code = rsp_result.get("code")
        if code != 0:
            error_msg = "Failed to query remote cluster server info, detail:%s" % rsp_result
            logger.error(error_msg)
            raise Exception(error_msg)
        rep_node_mgmt_ip_list = set()
        for data in rsp_data:
            ip_address_list = data.get("ip_address_list")
            for item in ip_address_list:
                port_name = item.get("port_name")
                if port_name.startswith("rep"):
                    rep_node_mgmt_ip_list.add(data.get("management_internal_ip"))
        return list(rep_node_mgmt_ip_list)

    def query_remote_gateway(self, ip_list, port_name="rep_arb"):
        remote_gw = dict()
        for mgmt_ip in ip_list:
            data = {
                "manage_ip": mgmt_ip,
                "port_name": port_name
            }
            rsp_result, rsp_data = self.remote_opr.query_policy_route(data)
            routes = rsp_data.get("routes")
            if not routes:
                error_msg = "Failed query host[%s] arb policy route info." % mgmt_ip
                logger.error(error_msg)
                continue
            _gw = routes[0].get("gateway")
            remote_gw[mgmt_ip] = _gw
        if not remote_gw:
            error_msg = "Failed query host[%s] arb policy route info." % ip_list
            logger.error(error_msg)
            raise Exception(error_msg)
        return remote_gw

    def del_policy_route(self, ip_list, port_name="rep_arb"):
        data = {
            "nodeMgrIps": ip_list,
            "port_name": port_name
        }
        logger.info("Del policy route: %s" % data)
        rsp_result = self.remote_opr.delete_policy_route(data)
        if rsp_result != 0:
            error_msg = "Failed to del cluster[%s] port rep_arb policy route for host %s" % \
                        (self.remote_float_ip, ip_list)
            logger.error(error_msg)
            raise FCDException(627455, error_msg)

    def config_rep_policy_route(self, route_infos):
        """
        配置复制节点策略路由
        :param route_infos:
                           nodeMgrIps:复制节点管理IP
                           portName1：rep0
                           gateway1：网关
                           srcIp1：源IP（复制ip）
        :return: None
        """
        for route_info in route_infos:
            rsp_result, rsp_data = self.opr.add_policy_route(route_info)
            if rsp_result != 0:
                err_msg = "Add rep policy route failed, Detail:[result: %s]" % rsp_data
                logger.error(err_msg)
                raise Exception(err_msg)
            logger.info("Add rep policy route success. %s" % route_info)

    def get_rep_policy_route_info(self, fsa_ip_list, net_info_list):
        """
        获取复制节点网络信息
        :param fsa_ip_list: 复制节点管理IP
        :param net_info_list: 复制节点网络规划信息[网关，网络平面（192.168.1.0/24）]
        :return: 配置策略路由网络信息[[portName1:rep1, gateway1:gw, srcIp1:复制节点复制ip,nodeMgrIps:管理ip],...]
        """
        route_infos = list()
        for _, om_ip in enumerate(fsa_ip_list):
            route_info = self.get_one_node_route_info(net_info_list, om_ip)
            route_infos.extend(route_info)
        logger.info("Success to query rep policy route info, detail: %s" % route_infos)
        return route_infos

    def get_one_node_route_info(self, net_info_list, om_ip):
        """
        获取一个复制节点复制网口（生产端仲裁网口配置策略路由，用于保存仲裁网关，后续在灾备端配置静态路由需要使用）信息，用配置策略路由
        :param net_info_list: 复制节点仲裁和复制网络信息
        :param om_ip: 复制节点管理IP
        :return: [rep1网路信息,rep0网络信息,rep_arb网络信息]
        """
        rep_gw0 = net_info_list[0]
        rep_gw1 = net_info_list[2]
        rsp_result, rsp_data = self.opr.get_network_serive(om_ip)
        route_infos = list()
        code = rsp_result.get("code")
        if code != 0:
            err_msg = "Failed to query network info of host[%s], detail: %s" % (om_ip, rsp_result)
            logger.error(err_msg)
            raise Exception(err_msg)
        ip_address_list = rsp_data[0].get("ip_address_list")
        for ip_address in ip_address_list:
            route_info = dict()
            route_info["nodeMgrIps"] = [om_ip]
            if ip_address.get("port_name") == "rep0":
                route_info["portName1"] = "rep0"
                route_info["gateway1"] = rep_gw0
                route_info["srcIp1"] = ip_address.get("ip_address")
                route_infos.append(route_info)
            if ip_address.get("port_name") == "rep1":
                route_info["portName1"] = "rep1"
                route_info["gateway1"] = rep_gw1
                route_info["srcIp1"] = ip_address.get("ip_address")
                route_infos.append(route_info)
        return route_infos

    def connect_check(self):
        fail_list = []
        rep_gw1 = self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_gateway1')
        rep_gw2 = self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_gateway2')
        arb_gw = self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_arb_gateway')
        for node in self.fsa_info:
            creuser = node['creuser'].split(',')
            client = PingSSHclient(host=node['manageIp'], username=creuser[2], pwd=creuser[3], root_pwd=creuser[1])
            client.switch_root(creuser[1])
            if not client_can_pinged_to(client, rep_gw1) or not client_can_pinged_to(client, rep_gw2):
                msg = "bmcip:%s,replication plane check fail" % node['bmc_ip']
                fail_list.append(msg)
            if get_project_condition_boolean(self.project_id, 'CSHAStorage_TFB'):
                if not client_can_pinged_to(client, arb_gw):
                    msg = "bmcip:%s,arbitration plane check fail" % node['bmc_ip']
                    fail_list.append(msg)
            client.close()
        if fail_list:
            logger.error('connect check fail,detail:%s' % str(fail_list))
            raise FCDException(627178, str(fail_list))
        return True

    def get_net_info(self, flag=True):
        """return示例: ['192.168.15.1', IP('192.168.15.0/27'), '192.168.15.32', IP('192.168.15.32/27')]"""
        rep_net_info = collections.OrderedDict()
        if flag:
            rep_net_info['rep0'] = \
                [self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_range1'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_netmask1'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_gateway1')]
            rep_net_info['rep1'] = \
                [self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_range2'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_netmask2'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_rep_gateway2')]
        else:
            rep_net_info['arb'] = \
                [self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_arb_range'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_arb_netmask'),
                 self.param_util.get_param_value(self.pod_id, self.service_name, 'fusionstorage_arb_gateway')]
        ret = []
        for _, (ip_range, net_mask, gw) in rep_net_info.items():
            ret.append(gw)
            ret.append(self.get_network_specification(ip_range, net_mask))
        return ret

    def login_deploy_manager(self):
        """
        登录本端DM
        :return:
        """
        status_code, error_code, error_des = self.opr.login(
            self.user_name, self.update_pwd)
        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)

    def remote_opr_login(self):
        """
        登录远端DM
        :return:
        """
        self.remote_opr = RestOperate(self.remote_float_ip)
        remote_admin_pwd = self.param_util.get_param_value(
            self.pod_id, self.service_name, "produce_storage_fsm_admin_password")
        status_code, error_code, error_des = self.remote_opr.login(
            self.user_name, remote_admin_pwd)
        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)
