# -*- coding: utf-8 -*-
import time
import traceback
from tenacity import retry, stop_after_attempt, wait_fixed

import utils.common.log as logger
from utils.common.ssh_util import Ssh
from utils.common.fic_base import TestCase
from utils.client.FSPAuthClient import FSPAuthClient
from utils.Driver.CloudDC.OpenStack.get_host_info import GetHostInfo
from utils.business.project_condition_utils import get_project_condition_boolean
from plugins.DistributedStorage.logic.DeployOperate import DeployOperate
from plugins.DistributedStorage.logic.InstallOperate import InstallOperate
from plugins.DistributedStorage.utils.common.DeployConstant import DeployConstant


class AddNode(TestCase):
    def __init__(self, project_id, pod_id, fs_args, condition=None, metadata=None, **kwargs):
        super(AddNode, self).__init__(project_id, pod_id)
        self.fs_args = fs_args
        self.condition = condition
        self.metadata = metadata
        self.more_args = kwargs
        self.project_id = project_id
        self.init_pwd = self.fs_args['dm_init_pwd']
        self.update_pwd = self.fs_args['dm_update_pwd']
        self.vbs_list = self.fs_args.get('vbs_list', list())
        self.osd_list = self.fs_args.get("osd_list", list())
        self.opr = DeployOperate(self.fs_args)
        self.install_operate = InstallOperate(self.project_id, self.pod_id, self.fs_args)
        # TC_Add_Node 新增参数
        self.role_for_osd = self.fs_args.get("tc_add_node_role_for_osd", None)
        self.role_for_vbs = self.fs_args.get("tc_add_node_role_for_vbs", None)
        self.error_msg = {}

    def procedure(self):
        logger.info("Start to add nodes.")
        if len(self.osd_list) == 0 and len(self.vbs_list) == 0:
            logger.error('There is no node for adding, return directly')
            return
        try:
            # 定义待部署节点IP列表和节点信息列表
            node_deploy_list = list()
            node_deploy_info = list()
            # 获取预安装和常规安装节点信息列表,以及待部署IP列表
            nodes_to_be_added, node_deploy_ip_list = self._get_node_to_be_added()
            node_deploy_list.extend(node_deploy_ip_list)
            # 配置节点ssh-rsa协议
            node_deploy_info.extend(nodes_to_be_added)
            self.install_operate.add_sshd_host_key(nodes_to_be_added)
            self.opr.first_login(DeployConstant.DM_LOGIN_USER, "", self.update_pwd)
            self.node_validity_method(nodes_to_be_added)
            # dio直通读关闭
            self.config_global_dio_switch()

            # 添加节点
            logger.info("Adding nodes %s" % (str(node_deploy_list)))
            self.adding_node(node_deploy_info)
            time.sleep(60)

            # 部署网络管理组件
            logger.info("Install network manage component nodes")
            self.install_manage_component_node(node_deploy_list)
            time.sleep(120)

            logger.info("Query network manage component installation status")
            self.query_component_installation_status(node_deploy_info)

            # 配置节点iBMC用户名密码
            logger.info('start to config iBMC account information')
            self.config_bmc_account_info()

        except Exception as e:
            logger.error(traceback.format_exc())
            raise Exception(e)
        finally:
            self.opr.logout()

    def config_global_dio_switch(self):
        """
        场景：部署、扩AZ新建、新增存储集群、改造
        查询系统中 global_ssd_dio_switch 和 global_hdd_dio_switch 开关是否开启，如果已开启，则关闭该两项配置
        """
        if not get_project_condition_boolean(self.project_id,
                                             '(ManageStorFB80|(ManageStorFB80&RegionConHA))&ProjectDeploy'
                                             '|(TenantStorFB80|TenantStorFBHCI80)&(!OpenStack_BMS)&ProjectDeploy'
                                             '|TenantStorFB80&(ExpansionAZ_KVM|ExpansionServiceStorage)'
                                             '|(ManageStorFB80&ProjectModifactionRegionConHA&RegionConHA)'):
            return
        logger.info('Start to config switch: [global_ssd_dio_switch, global_hdd_dio_switch]')
        parameter_lst = self.opr.get_dsware_global_params()
        if not parameter_lst:
            err_msg = 'Failed to query global parameters, details: {}'.format(parameter_lst)
            logger.error(err_msg)
            raise Exception(err_msg)

        for parameter in parameter_lst:
            cur_param = parameter['paraName']
            if cur_param not in ['global_ssd_dio_switch', 'global_hdd_dio_switch']:
                continue
            # value=0表示关闭，value=1表示开启
            if parameter['value'] == '0':
                logger.info('status: {}'.format(parameter))
                continue
            logger.info('Close [{}] switch'.format(cur_param))
            res = self.opr.modify_dsware_global_params(cur_param)
            logger.info('Close [{}] switch result:{}'.format(cur_param, res))

    def config_bmc_account_info(self):
        """
        配置节点iBMC用户名密码,存储版本宽适配，811、810版本无该接口，配置失败只打印error日志
        """
        server_list = []
        if not self.fs_args.get('osd_bmc_infos'):
            return
        for node in self.fs_args.get('osd_bmc_infos'):
            cur_node = {
                "management_ip": node.get('om_ip'),
                "user_name": node.get('bmc_name'),
                "password": node.get('bmc_passwd')
            }
            server_list.append(cur_node)
        request_data = {"server_list": server_list}
        try:
            details = self.opr.config_osd_node_bmc_account_info(request_data)
        except Exception as e:
            logger.error('Config iBMC account information failed, Details:{}'.format(e))
        else:
            logger.info('The iBMC account information is configured successfully, Details:{}'.format(details))

    def adding_node(self, node_deploy_info):
        res = self.opr.add_servers('post', node_deploy_info, timeout=1900)
        status_code, error_code, error_des = res.get_res_code()
        if error_code != 0:
            err_msg = "Failed to add hosts, Detail:[%s]%s" % (error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)

    def install_manage_component_node(self, node_deploy_list):
        res = self.opr.deploy_service(node_deploy_list, 'agent')
        status_code, error_code, error_des = res.get_res_code()
        if error_code != 0 and 33697317 != error_code:
            err_msg = "Failed to install network manage component , Detail:[%s]%s" % (error_code, error_des)
            logger.error(err_msg)
            raise Exception(err_msg)

    def query_component_installation_status(self, node_deploy_info):
        wait_time = 14400 + 300 * max(len(node_deploy_info) / 200, 1)
        task_status = None
        retry = 3
        while wait_time > 0:
            try:
                res = self.opr.query_task_status('agent', timeout=300)
            except Exception as e:
                err_msg = "Failed to query network manage component installation status , Detail:%s" % e
                logger.error(err_msg)
                time.sleep(30)
                wait_time -= 30
                retry -= 1
                if retry <= 0:
                    raise Exception(err_msg)
                continue
            status_code, error_code, error_des = res.get_res_code()
            if error_code != 0:
                err_msg = "Failed to query network manage 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 network manage component to all node.Detail:%s" % server_result)
                break
            elif task_status == "failure":
                err_msg = "Failed to install network manage component, Detail:[%s]%s" % (
                    task_status, str(server_result))
                logger.error(err_msg)
                raise Exception(err_msg)
            else:
                logger.info("Processing<%s> to install network manage component on node. Detail:%s" % (
                    task_status, str(server_result)))
            time.sleep(30)
            wait_time -= 30
        if wait_time <= 0 and task_status != 'success' and task_status != 'failure':
            err_msg = "Wait for installation end timeout"
            logger.error(err_msg)
            raise Exception(err_msg)

    def cleanup(self):
        if self.vbs_list:
            vbs_bmc_ip_list = [vbs.get('bmc_ip') for vbs in self.vbs_list]
            cps_web_client = FSPAuthClient.get_cps_web_client(self.db, self.project_id, self.pod_id)
            cps_host_client = GetHostInfo(cps_web_client)
            vbs_host_id_list, vbs_host_ip_list = cps_host_client.get_host_info(vbs_bmc_ip_list)
            cps_client = FSPAuthClient.get_cps_rest_client(self.db, self.project_id, self.pod_id)
            # 强制清理前把角色停掉
            logger.info("Stop fusionstorage-blockXXX role nodes%s" % str(vbs_host_ip_list))
            self.install_operate.stop_fusionstorage_block_template(cps_client, vbs_host_id_list)
        else:
            pass
        tc_add_node_node_to_be_added, node_deploy_ip_list = self._get_node_to_be_added()
        node_wait_to_clean_list = node_deploy_ip_list
        if len(tc_add_node_node_to_be_added) == 0:
            logger.info("no node to be clear")
            return
        # 清理节点不区分预装和常规安装节点，都支持清理
        logger.info("Start to force clear node%s" % node_wait_to_clean_list)
        clear_fail_list = list()
        for fsa in tc_add_node_node_to_be_added:
            fsa_ip = fsa.get('management_internal_ip')
            ret_clr = self.install_operate.clear_fsa(
                fsa_ip, fsa.get('user_name'),
                fsa.get('password'), fsa.get('root_password'))
            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 restore factory setting.")
        self.opr.first_login(DeployConstant.DM_LOGIN_USER, "", self.update_pwd)
        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. Successful 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 retore factory setting%s." % success_nodes_list)
        self.opr.logout()

    def reset_all_nodes(self):
        logger.info("Step1 restore factory setting all nodes")
        self.restore_factory()
        if self.vbs_list:
            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)
        else:
            pass

    def _get_node_to_be_added(self):
        node_list = list()
        node_deploy_list = list()
        last_storage_cabinet = list()
        self.error_msg = {}
        self.install_operate.parallel_run(self.preinstall_mark_check, self.osd_list, node_type=True)
        if self.error_msg:
            logger.error("Preinstall mark check failed, Detail: %s" % self.error_msg)
            raise Exception(self.error_msg)
        self.get_osd_info(last_storage_cabinet, node_list, node_deploy_list)
        logger.info('osd node cabinet list: {}'.format(last_storage_cabinet))

        self.error_msg = {}
        self.install_operate.parallel_run(self.preinstall_mark_check, self.vbs_list, node_type=False)
        if self.error_msg:
            logger.error("Preinstall mark check failed, Detail: %s" % self.error_msg)
            raise Exception(self.error_msg)
        last_compute_cabinet = list()
        self.get_vbs_info(last_compute_cabinet, node_list, node_deploy_list)
        logger.info('compute node cabinet list: {}'.format(last_compute_cabinet))
        logger.info("Get all node deploy list<%s>" % node_deploy_list)
        return node_list, node_deploy_list

    def get_osd_info(self, last_storage_cabinet, node_list, node_deploy_list):
        for osd in self.osd_list:
            hostname = osd.get('hostname')
            storage_cabinet = InstallOperate.get_cabinet_no(
                osd.get('bmc_ip'),
                osd.get('cabinet'),
                'storage_group',
                last_storage_cabinet
            )
            cur_role = self.role_for_osd
            osd_ip = osd.get('om_ip')
            osd_user = osd.get('user')
            osd_pwd = osd.get('passwd')
            osd_root = osd.get('root_pwd')
            # 判断节点是否有预装标识，返回值为Ture,则有标识，该节点是预安装
            mark_check_boolean = osd.get("pre_mark")
            authentication_mode = "production_preinstall" if mark_check_boolean else "password"
            node_osd = self.structure_of_node_information(
                hostname, osd_ip, cur_role, storage_cabinet,
                authentication_mode, osd_user, osd_pwd, osd_root
            )
            node_list.append(node_osd)
            node_deploy_list.append(osd_ip)

    def get_vbs_info(self, last_compute_cabinet, node_list, node_deploy_list):
        for vbs in self.vbs_list:
            vbs_ip = vbs.get('om_ip')
            # 如果该VBS节点已作为存储节点，则跳过
            if vbs_ip in node_deploy_list:
                continue
            hostname = vbs.get('hostname')
            if hostname == "":
                hostname = ''
            compute_cabinet = InstallOperate.get_cabinet_no(
                vbs.get('bmc_ip'),
                vbs.get('cabinet'),
                'compute_group',
                last_compute_cabinet
            )
            cur_role = self.role_for_vbs
            vbs_user = vbs.get('user')
            vbs_pwd = vbs.get('passwd')
            vbs_root = vbs.get('root_pwd')
            mark_check_boolean = vbs.get("pre_mark")
            authentication_mode = "preinstall" if mark_check_boolean else "password"
            node_vbs = self.structure_of_node_information(
                hostname, vbs_ip, cur_role, compute_cabinet,
                authentication_mode, vbs_user, vbs_pwd, vbs_root
            )
            node_list.append(node_vbs)
            node_deploy_list.append(vbs_ip)

    def structure_of_node_information(
            self, name, ip, role, cabinet, authentication_mode,
            user_name, password, root_password):
        node_info = {"serial_number": "",
                     "name": name,
                     "management_internal_ip": ip,
                     "om_ip": ip,
                     "role": role,
                     "cabinet": cabinet,
                     "authentication_mode": authentication_mode,
                     "user_name": user_name,
                     "password": password,
                     "root_password": root_password}
        return node_info

    @retry(stop=stop_after_attempt(3), wait=wait_fixed(3), reraise=True)
    def node_validity_method(self, node):
        res = self.opr.node_check_validity('post', node, timeout=900)
        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 check validity on nodes%s. Successful 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)

    def preinstall_mark_check(self, om_ip, user, passwd, root_pwd, node_type, **kwargs):
        """
        预装标识判断

        存储节点预装标识吗，两个文件要同时存在：/opt/fusionstorage/.production_preinstall 与 /opt/fusionstorage/deploy.ini
        计算节点预装标识，两个文件存在其一即为预装节点：/home/.preinstall 或 /opt/fusionstorage/.preinstall
        """
        if node_type:
            mark_file1 = '/opt/fusionstorage/.production_preinstall'
            mark_file2 = '/opt/fusionstorage/deploy.ini'
            cur_symbol = '&&'
        else:
            mark_file1 = '/home/.preinstall'
            mark_file2 = '/opt/fusionstorage/.preinstall'
            cur_symbol = '||'
        mark = True
        ssh_client = self.install_operate.create_ssh_root_client(om_ip, user, passwd, root_pwd)
        cmd_echo = "echo '"
        cmd_python_file = "' > /home/preinstall_mark_check_file.sh"
        cmd_res = 'echo last_result=$?'
        cmd = ";".join(
            [cmd_echo + shell_expression.format(mark_file1, mark_file2, cur_symbol) + cmd_python_file, cmd_res]
        )
        cmd_result = Ssh.ssh_exec_command_return(ssh_client, cmd)
        cmd_result = str(cmd_result)
        if cmd_result.find('last_result=0') == -1:
            Ssh.ssh_close(ssh_client)
            self.error_msg[om_ip] = "cmd error: %s" % cmd_result
            raise Exception("cmd error: %s" % cmd_result)
        cmd = ";".join(['bash /home/preinstall_mark_check_file.sh', 'echo last_result=$?'])
        cmd_result = Ssh.ssh_exec_command_return(ssh_client, cmd)
        cmd_result = str(cmd_result)
        if cmd_result.find('last_result=0') == -1:
            Ssh.ssh_close(ssh_client)
            self.error_msg[om_ip] = "cmd error: %s" % cmd_result
            raise Exception("cmd error: %s" % cmd_result)
        if cmd_result.find('not exist mark file') != -1:
            mark = False
        cmd = ";".join(['rm -rf /home/preinstall_mark_check_file.sh', 'echo last_result=$?'])
        cmd_result = Ssh.ssh_exec_command_return(ssh_client, cmd)
        cmd_result = str(cmd_result)
        if cmd_result.find('last_result=0') == -1:
            Ssh.ssh_close(ssh_client)
            self.error_msg[om_ip] = "cmd error: %s" % cmd_result
            raise Exception("cmd error: %s" % cmd_result)
        Ssh.ssh_close(ssh_client)
        nodes = self.osd_list if node_type else self.vbs_list
        for node in nodes:
            if node.get("om_ip") == om_ip:
                node.update({"pre_mark": mark})
                break


shell_expression = """#!/usr/bin/bash
mark_file1="{}"
mark_file2="{}"
if [[ ! -f $mark_file1 ]] {} [[ ! -f $mark_file2 ]];then
echo "not exist mark file"
fi
"""
