# -*- coding: utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
import time
import traceback
from utils.common.fic_base import TestCase
import utils.common.log as logger
from utils.common.message import Message
from utils.common.exception import FCDException
from plugins.DistributedStorage.scripts.logic.DeployOperate import DeployOperate
from plugins.DistributedStorage.scripts.utils.common.DeployConstant import DeployConstant
from plugins.DistributedStorage.scripts.implement.tc_config_storage_network import ConfigStorageNetwork
from plugins.DistributedStorage.scripts.implement.tc_add_node import AddNode
from plugins.DistributedStorage.scripts.logic.InstallOperate import InstallOperate


class InstallNode(TestCase):
    def __init__(self, project_id, pod_id, fs_args, **kwargs):
        super(InstallNode, self).__init__(project_id, pod_id)
        self.fs_args = fs_args
        self.update_pwd = fs_args.get("dm_update_pwd")
        self.fsa_list = fs_args.get('fsa_list')
        self.more_args = kwargs
        self.opr = DeployOperate({'float_ip': fs_args.get('float_ip')})
        self.install_operate = InstallOperate(self.project_id, self.pod_id, self.fs_args)

    @staticmethod
    def check_node_in_cluster(rsp_data, node_list):
        not_in_cluster_list = list()
        in_cluster_list = list()
        for server in rsp_data:
            if server.get('management_ip') not in node_list:
                continue
            status_add_cluster = server.get('in_cluster')
            if not status_add_cluster:
                not_in_cluster_list.append(server.get('management_ip'))
            if status_add_cluster:
                in_cluster_list.append(server.get('management_ip'))
        return not_in_cluster_list, in_cluster_list

    def procedure(self):
        logger.info("Start to install components to all nodes.")
        try:
            self.opr.login(DeployConstant.DM_LOGIN_USER, self.update_pwd)
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))

        if not self.fsa_list:
            logger.error('There is no node need install, return directly ')
            return Message()
        node_list = [fsa_node['om_ip'] for fsa_node in self.fsa_list]

        logger.info("Install components to all nodes[%s]." % node_list)
        try:
            self._deploy_service(node_list)
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))
        time.sleep(120)

        logger.info("Checking if nodes[%s] add to the cluster" % node_list)
        try:
            self.check_status_add_cluster(node_list)
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))
        self.opr.logout()
        return Message()

    def check_task_status(self, node_list):
        wait_time = 1200 + 300 * max(len(node_list) / 20, 1)
        try_times = 1
        while wait_time > 0 and try_times < 4:
            time.sleep(30)
            wait_time -= 30
            res = self.opr.query_task_status()
            status_code, error_code, error_des = res.get_res_code()
            if error_code != 0:
                err_msg = "Failed to query component installation status" \
                          ", Detail:[%s]%s" % (error_code, error_des)
                logger.error(err_msg)
                raise Exception(err_msg)
            task_status, server_result = res.get_res_data()
            if task_status == 'success':
                logger.info("Succeed to install component to all node.Detail:%s" % (server_result))
                break
            elif task_status == "failure":
                try_times = self._failure_status_opr(node_list, server_result, task_status, try_times)
            else:
                logger.info("Processing<%s> to install storage software "
                            "on node. Detail:%s" % (task_status, str(server_result)))
        if wait_time <= 0:
            err_msg = "Wait for installation end timeout"
            logger.error(err_msg)
            raise Exception(err_msg)
        logger.info("Install components successful.")

    def retry_install_node(self):
        logger.info("Retry to install components to all nodes.")
        try:
            self.login_deploy_manager()
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))

        logger.info("Try get current the status of installed servers")
        try:
            rsp_data = self.query_installed_servers()
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))

        node_list = [fsa_node.get('om_ip') for fsa_node in self.fsa_list]

        try:
            need_deploy_node = self.query_need_deploy_node(node_list, rsp_data)
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))

        if 0 != len(need_deploy_node):
            try:
                self._deploy_service(need_deploy_node)
            except FCDException as e:
                logger.error(traceback.format_exc())
                return Message(500, e)
            except Exception as e:
                logger.error(traceback.format_exc())
                return Message(500, FCDException(626066, str(e)))
        try:
            # 重新加入集群
            self.try_add_cluster(node_list)
        except FCDException as e:
            logger.error(traceback.format_exc())
            return Message(500, e)
        except Exception as e:
            logger.error(traceback.format_exc())
            return Message(500, FCDException(626066, str(e)))
        self.opr.logout()
        return Message()

    def query_need_deploy_node(self, node_list, rsp_data):
        logger.info("Query action_phase from servers%s " % rsp_data)
        action_phase_list = list()
        fail_action_phase_list = list()
        fail_list = list()
        need_deploy_node = list()
        # 12：通知NMS其它组件安装完成，17：通知NMS管理节点组件安装完成, 7:当前没有任务（存储工步重置失败重跑添加节点）
        for server in rsp_data:
            if server.get('management_internal_ip') not in node_list:
                continue
            action_phase = server.get('action_phase')
            action_phase_list.append(action_phase)
            if action_phase != 12 and action_phase != 17 and action_phase != 7:
                fail_list.append(server.get('management_internal_ip'))
                fail_action_phase_list.append(action_phase)
            if action_phase == 7:
                need_deploy_node.append(server.get('management_internal_ip'))
        logger.info("The current status of all nodes%s " % action_phase_list)
        logger.info("The current status of failed nodes%s " % fail_action_phase_list)
        if 0 != len(fail_action_phase_list):
            res = self.opr.deploy_service_retry(fail_list, 'all')
            status_code, error_code, error_des = res.get_res_code()
            if error_code != 0:
                err_msg = "Failed to notify NMS add nodes%s to cluster, Detail:[%s]%s" % (node_list, error_code,
                                                                                          error_des)
                logger.error(err_msg)
                raise Exception(err_msg)
            self.check_task_status(fail_list)
        return need_deploy_node

    def login_deploy_manager(self):
        status_code, error_code, error_des = self.opr.login(DeployConstant.DM_LOGIN_USER, self.update_pwd)
        if status_code != 200 or error_code != 0:
            err_msg = "Failed to login again, Detail:[status:%s," \
                      "code:%s]%s" % (status_code, error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)

    def query_installed_servers(self):
        rsp_obj = self.opr.query_deploy_servers()
        rsp_code, rsp_result, rsp_data = rsp_obj.get_rsp_data()
        error_code = int(rsp_result.get('code'))
        if rsp_code != 0 or error_code != 0:
            error_des = rsp_result.get('description')
            error_sgt = rsp_result.get('suggestion')
            err_msg = "Failed to query the status of installed servers, Detail:[%s]%s.Suggestion:%s" % \
                      (error_code, error_des, error_sgt)
            logger.error(err_msg)
            raise Exception(err_msg)
        return rsp_data

    def try_add_cluster(self, node_list):
        logger.info("Try to install component to all nodes again")
        rsp_obj = self.opr.query_cluster_servers()
        rsp_code, rsp_result, rsp_data = rsp_obj.get_rsp_data()
        error_code = rsp_result.get('code')
        if rsp_code != 0 or error_code != 0:
            error_des = rsp_result.get('description')
            error_sgt = rsp_result.get('suggestion')
            err_msg = "Failed to query the node to join the cluster, " \
                      "Detail:[%s]%s.Suggestion:%s" % \
                      (error_code, error_des, error_sgt)
            logger.error(err_msg)
            raise Exception(err_msg)

        logger.info("Query node id list from servers%s " % rsp_data)
        servers_id_list = list()
        for server in rsp_data:
            if server.get('management_ip') not in node_list:
                continue
            servers_id_list.append(server.get('id'))
        if not servers_id_list:
            return
        logger.info("Try add node%s to cluster again" % servers_id_list)
        rsp_obj = self.opr.add_cluster_node(servers_id_list)
        rsp_code, rsp_result, rsp_data = rsp_obj.get_rsp_data()
        error_code = rsp_result.get('code')
        if rsp_code != 0 or error_code != 0:
            error_des = rsp_result.get('description')
            error_sgt = rsp_result.get('suggestion')
            err_msg = "Failed again to add nodes to cluster. Detail:[%s]%s. " \
                      "Suggestion:%s" % (error_code, error_des, error_sgt)
            logger.error(err_msg)
            raise Exception(err_msg)

        logger.info("Checking if nodes[%s] add to the cluster" % node_list)
        self.check_status_add_cluster(node_list)

    def retry_install_all(self):
        logger.info("Step1 reset all nodes")
        AddNode(self.project_id, self.pod_id, self.fs_args).reset_all_nodes()
        logger.info("Step2 add nodes again")
        AddNode(self.project_id, self.pod_id, self.fs_args).procedure()
        logger.info("Step3 config storage network again")
        rtn_msg = ConfigStorageNetwork(
            self.project_id, self.pod_id, self.fs_args).procedure()
        if 200 != rtn_msg.status_code:
            return rtn_msg

        logger.info("Step4 install the component again")
        rtn_msg = self.procedure()
        return rtn_msg

    def check_status_add_cluster(self, node_list):
        rsp_data = None
        not_in_cluster_list = [0]
        cwait_time = 300 + min(len(node_list) * 60, 600)
        while cwait_time > 0:
            time.sleep(30)
            cwait_time -= 30
            rsp_obj = self.opr.query_cluster_servers()
            rsp_code, rsp_result, rsp_data = rsp_obj.get_rsp_data()
            error_code = rsp_result.get('code')
            if rsp_code != 0 or error_code != 0:
                error_des = rsp_result.get('description')
                error_sgt = rsp_result.get('suggestion')
                err_msg = "Failed to query the node to join the cluster, " \
                          "Detail:[%s]%s.Suggestion:%s" % \
                          (error_code, error_des, error_sgt)
                logger.error(err_msg)
                raise Exception(err_msg)
            logger.info("The current status of all nodes%s " % rsp_data)
            not_in_cluster_list, in_cluster_list = self.check_node_in_cluster(rsp_data, node_list)
            if len(not_in_cluster_list) == 0:
                logger.info("all nodes%s is added to cluster " %
                            in_cluster_list)
                break
            else:
                logger.info("Wait to add nodes%s in cluster " %
                            not_in_cluster_list)
        if 0 < len(not_in_cluster_list):
            err_msg = "Existing nodes%s are not add cluster after %s, " \
                      "Detail:%s." % (not_in_cluster_list, cwait_time,
                                      rsp_data)
            logger.error(err_msg)
            raise Exception(err_msg)
        logger.info("Succeed to the cluster")

    def _failure_status_opr(self, node_list, server_result, task_status, try_times):
        if try_times >= 3:
            err_msg = "Failed to install component for 3 times, Detail:[%s]%s" % (task_status, str(server_result))
            logger.error(err_msg)
            raise Exception(err_msg)
        else:
            logger.info("Try to install component to all node %s times.Detail:%s" % (try_times, server_result))
            res = self.opr.deploy_service_retry(node_list, 'all')
            status_code, error_code, error_des = res.get_res_code()
            if error_code != 0:
                err_msg = "Failed to deploy component on all node[%s], Detail:[%s]%s" % (
                    node_list, error_code, error_des)
                logger.error(err_msg)
                raise Exception(err_msg)
            try_times += 1
        return try_times

    def _deploy_service(self, node_list):
        res = self.opr.deploy_service(node_list, "all")
        status_code, error_code, error_des = res.get_res_code()
        if error_code != 0:
            err_msg = "Failed to deploy component on storage node[%s], " \
                      "Detail:[%s]%s" % (node_list, error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)
        self.check_task_status(node_list)
