# -*- 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.client.FSPAuthClient import FSPAuthClient
from utils.Driver.CloudDC.OpenStack.get_host_info import GetHostInfo
from plugins.DistributedStorage.logic.deploy_operate import DeployOperate
from plugins.DistributedStorage.utils.common.deploy_constant import DeployConstant
from plugins.DistributedStorage.logic.install_operate import InstallOperate
from plugins.DistributedStorage.implement.tc_add_node import AddNode
from plugins.DistributedStorage.Deploy.scripts.ManageConverge.implement.tc_config_storage_network import \
    ConfigStorageNetwork


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 = self.fs_args['dm_update_pwd']
        self.fsa_list = self.fs_args['fsa_list']
        self.vbs_list = self.fs_args.get('vbs_list')
        self.opr = DeployOperate(self.fs_args)
        self.install_operate = InstallOperate(self.project_id, self.pod_id, self.fs_args)
        self.more_args = kwargs

    def procedure(self):
        logger.info("Start to install components to all nodes.")
        self.opr.login(DeployConstant.DM_LOGIN_USER, self.update_pwd)
        if not self.fsa_list:
            err_msg = "fsa list is none"
            logger.error(err_msg)
            return
        node_list = [fsa_node['om_ip'] for fsa_node in self.fsa_list]
        logger.info("Install components to all nodes[%s]." % node_list)
        self.install_component(node_list)
        time.sleep(120)
        self.query_installation_result(node_list)
        logger.info("Install components successful.")
        logger.info("Checking if nodes[%s] add to the cluster" % node_list)
        self.check_status_add_cluster(node_list)
        self.opr.logout()

    def query_installation_result(self, node_list):
        wait_time = self.query_status_cyclic(node_list)
        if wait_time <= 0:
            err_msg = "Wait for installation end timeout"
            logger.error(err_msg)
            raise Exception(err_msg)

    def query_status_cyclic(self, node_list):
        wait_time = 5400
        try_times = 1
        while wait_time > 0 and try_times < 4:
            time.sleep(30)
            wait_time -= 30
            res = self.query_task_status()
            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":
                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))
                    self.retry_deploy_service_for_all(node_list)
                    try_times += 1
            else:
                logger.info("Processing[%s] to install storage software on node. "
                            "Detail:%s" % (task_status, str(server_result)))
        return wait_time

    def retry_deploy_service_for_all(self, node_list):
        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)

    def query_task_status(self):
        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)
        return res

    def install_component(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)

    def cleanup(self):
        logger.info("Start to clear fsa on node%s" % self.fsa_list)
        clear_fail_list = list()
        for fsa in self.fsa_list:
            fsa_ip = fsa.get('om_ip')
            ret_clr = self.install_operate.clear_fsa(fsa_ip, fsa.get('user'), fsa.get('passwd'), fsa.get('root_pwd'))
            if ret_clr != 0:
                clear_fail_list.append(fsa_ip)
                logger.error("Failed to clean up FSA node[%s]. Please clean it by manually." % fsa_ip)
        if len(clear_fail_list) > 0:
            err_msg = "Failed to clean up nodes%s, Please clean it by manually" % clear_fail_list
            logger.error(err_msg)
            raise Exception(err_msg)

    def restore_factory(self):
        logger.info("Start to retore factory setting.")
        res = self.opr.nodes_restore_factory()
        ret_code, ret_data = res.get_nodes_data()
        fail_nodes_list = ret_data.get('data').get('failed_nodes')
        success_nodes_list = ret_data.get('data').get('successful_nodes')
        if ret_code != 0 or len(fail_nodes_list) > 0:
            code = ret_data.get('result').get('code')
            err_des = ret_data.get('result').get('code')
            suggestion = ret_data.get('result').get('code')
            err_msg = "Failed to restore factory setting on nodes%s. Succeeful nodes%s. DetaiL:[%s]%s. Suggestion:%s." \
                      % (fail_nodes_list, success_nodes_list, code, err_des, suggestion)
            logger.error(err_msg)
            raise Exception(err_msg)
        logger.info("Succeed to restore factory setting%s." % success_nodes_list)
        self.cleanup()

    def retry_install_node(self):
        logger.info("Retry to install components to all nodes.")
        self.opr.login(DeployConstant.DM_LOGIN_USER, self.update_pwd)

        logger.info("Try get current the status of installed servers")
        rsp_data = self.query_current_install_status()

        if not self.fsa_list:
            err_msg = "fsa list is none"
            logger.error(err_msg)
            return
        node_list = [fsa_node.get('om_ip') for fsa_node in self.fsa_list]
        
        logger.info("Query action_phase from servers%s " % rsp_data)
        fail_action_phase_list = self.query_action_phase(node_list, rsp_data)
        if len(fail_action_phase_list) == 0:
            self.try_add_cluster(node_list)
            self.opr.logout()
            return
        task_status = self.query_install_task_status()
        if task_status == 'success':
            logger.info("Renotify NMS to add nodes%s to the cluster." % node_list)
            self.retry_deploy_service(node_list)
        else:
            logger.info("Try to install all nodes%s from add host again" % node_list)
            try:
                self.retry_install_all()
            except Exception as e:
                logger.error(traceback.format_exc())
                raise e
        self.opr.logout()

    def retry_deploy_service(self, node_list):
        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 notify NMS add nodes%s to cluster, Detail:[%s]%s" \
                      % (node_list, error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)

    def query_install_task_status(self):
        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 last 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()
        return task_status

    def query_action_phase(self, node_list, rsp_data):
        action_phase_list = list()
        fail_action_phase_list = list()
        # 12：通知NMS其它组件安装完成，17：通知NMS管理节点组件安装完成
        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:
                fail_action_phase_list.append(action_phase)
        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)
        return fail_action_phase_list

    def query_current_install_status(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'))

        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 retore factory setting all nodes")
        self.restore_factory()

        cps_web_client = FSPAuthClient.get_cps_web_client(self.db, self.project_id, self.pod_id)
        cps_host_client = GetHostInfo(cps_web_client)
        vbs_bmc_ip_list = [vbs.get('bmc_ip') for vbs in self.vbs_list]
        vbs_host_id_list, vbs_host_ip_list = cps_host_client.get_host_info(vbs_bmc_ip_list)
        logger.info("Step2 restart FusionStorage-AgentXXX service on OpenStack node%s" % vbs_host_ip_list)
        cps_client = FSPAuthClient.get_cps_rest_client(self.db, self.project_id, self.pod_id)
        self.install_operate.restart_fusionstorage_block_template(cps_client, vbs_host_id_list)

        logger.info("Step3 add nodes again")
        AddNode(self.project_id, self.pod_id, self.fs_args).procedure()

        logger.info("Step4 config storage network again")
        try:
            ConfigStorageNetwork(self.project_id, self.pod_id, self.fs_args).procedure()
        except Exception as e:
            logger.error(traceback.format_exc())
            raise e

        logger.info("Step5 install the component again")
        self.procedure()

    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 = 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'))
            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")
