# Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
"""配置仲裁服务器证书

从Pacific导出csr到仲裁服务器签发证书，将签发的证书导入Pacific，并在仲裁服务器添加白名单
"""
import os
import time
import pathlib
import stat
import traceback

import utils.common.log as logger
from utils.common.ssh_util import Ssh
from utils.common.fic_base import TestCase
from utils.common.exception import FCDException
from plugins.DistributedStorage.scripts.logic.InstallOperate import InstallOperate
from plugins.DistributedStorage.scripts.utils.common.DeployConstant import DeployConstant
from plugins.DistributedStorageReplication.scripts.common_utils.config_params import Params
from plugins.DistributedStorageReplication.scripts.common_utils.rest_operate import RestOperate
from plugins.DistributedStorageReplication.scripts.common_utils.rest_constant import RestConstant


class IssueHyperMetroCert(TestCase):
    def __init__(self, project_id, pod_id):
        super(IssueHyperMetroCert, self).__init__(project_id, pod_id)
        self.fs_args = Params(project_id, pod_id, DeployConstant.REP_SERVICE_NAME).get_params_dict()
        self.arb_user = DeployConstant.ARB_USER
        self.dm_user = DeployConstant.DM_LOGIN_USER
        self.arb_ip = self.fs_args.get("storage_arbitration_ip")
        self.arb_pwd = self.fs_args.get("storage_arbitration_root_password")
        self.remote_admin_pwd = self.fs_args.get("produce_admin_password")
        self.local_admin_pwd = self.fs_args.get("local_admin_password")
        self.fcd_cert_dir = DeployConstant.FS_DUMP_TMP_DIR
        self.fcd_local_crt_path = str(pathlib.Path(self.fcd_cert_dir, Constant.LOCAL_CRT))
        self.fcd_remote_crt_path = str(pathlib.Path(self.fcd_cert_dir, Constant.REMOTE_CRT))
        self.fcd_ca_path = str(pathlib.Path(self.fcd_cert_dir, Constant.CA_NAME))
        self.local_csr_path = str(pathlib.Path(self.fcd_cert_dir, Constant.LOCAL_CSR))
        self.remote_csr_path = str(pathlib.Path(self.fcd_cert_dir, Constant.REMOTE_CSR))
        self.local_opr = RestOperate(self.fs_args.get("local_storage_fsm_ip"))
        self.remote_opr = RestOperate(self.fs_args.get("remote_storage_fsm_ip"))

    @classmethod
    def get_rep_cluster_sn(cls, opr: RestOperate):
        """
        查询复制集群sn
        :param opr:
        :return:
        """
        logger.info("Start to query the replication cluster ID.")
        result = opr.query_control_cluster()
        control_cluster_id = result.get_control_cluster_id()
        if not control_cluster_id:
            err_msg = "Failed to query the replication cluster ID. detail: {}".format(result.res.json())
            logger.error(err_msg)
            opr.logout()
            raise FCDException(626278, RestConstant.DR_REST_URL, err_msg)
        logger.info("Start to query SN")
        result = opr.query_rep_cls_psk(control_cluster_id)
        cur_sn = result.get_rep_cls_sn()
        if not cur_sn:
            err_msg = "Failed to query SN. detail: {}".format(result.res.json())
            logger.error(err_msg)
            opr.logout()
            raise FCDException(626278, RestConstant.DR_REST_URL, err_msg)
        return cur_sn

    @classmethod
    def issue_crt(cls, client):
        logger.info('Issuance of certificate')
        Ssh.ssh_send_command(client, f"cd {Constant.QUORUM_CERT_DIR};chown quorumsvr:quorumsvr *", expect="#",
                             timeout=5)
        Ssh.ssh_send_command(client, "qsadmin", expect="admin:/", timeout=20)
        Ssh.ssh_send_command(client, "generate tls_cert_multi", expect=Constant.ISSUE_EXCEPT, timeout=20)
        Ssh.ssh_send_command(client, "exit", expect="#", timeout=10)

    @staticmethod
    def add_whitelist(client, sn):
        try:
            Ssh.ssh_send_command(client, f"add white_list sn={sn}", expect=Constant.EXCEPT, timeout=10)
        except Exception as e:
            if str(e).find("This SN is already in the certificate") >= 0:
                logger.info("This SN {} is already in the certificate, pass".format(sn))
            else:
                logger.error("Add whitelist failed, details:{}".format(e))
                raise e
        logger.info("Adding the whitelist succeeded, sn:{}".format(sn))

    def procedure(self):
        logger.info('Start to configure the certificate.')
        InstallOperate.create_tmp_dir(self.fcd_cert_dir)
        try:
            self.local_opr.login(DeployConstant.DM_LOGIN_USER, self.local_admin_pwd)
            local_sn = self.get_rep_cluster_sn(self.local_opr)
            self.get_csr_file(self.local_opr)
        finally:
            self.local_opr.logout()
        try:
            self.remote_opr.login(DeployConstant.DM_LOGIN_USER, self.remote_admin_pwd)
            remote_sn = self.get_rep_cluster_sn(self.remote_opr)
            self.get_csr_file(self.remote_opr, is_local=False)
        finally:
            self.remote_opr.logout()

        self.issue_cert_and_download_cert()

        self.import_cert_and_ca_file(self.local_opr, self.local_admin_pwd)
        self.import_cert_and_ca_file(self.remote_opr, self.remote_admin_pwd, is_local=False)

        self.add_quorum_server_whitelist(local_sn, remote_sn)
        logger.info("Certificate configured successfully.")

    def get_csr_file(self, opr: RestOperate, is_local=True):
        """
        下载证书请求文件csr到本地
        :param opr:
        :param is_local:
        :return:
        """
        logger.info('Export csr')
        csr_info = opr.export_csr(algorithm=1)
        csr_name = csr_info.get("CERTIFICATE_CSR_NAME")
        logger.info('Download the csr file to FCD. csr name：{}'.format(csr_name))
        csr = opr.download_csr(csr_info.get("CERTIFICATE_EXPORT_PATH"))
        csr_file = self.local_csr_path if is_local else self.remote_csr_path
        flags = os.O_CREAT | os.O_RDWR | os.O_EXCL
        mode = stat.S_IRUSR | stat.S_IWUSR
        with os.fdopen(os.open(csr_file, flags, mode), "wb") as f:
            f.write(csr)
        pathlib.Path(csr_file).chmod(0o644)

    def issue_cert_and_download_cert(self):
        """
        上传证书请求文件xx.csr到仲裁服务器，签发证书并下载证书
        :return:
        """
        logger.info("Request to issue a certificate.")
        client = Ssh.ssh_create_client(self.arb_ip, self.arb_user, self.arb_pwd, timeout=5)
        self._clear_quorum_server_csr_dir(client, Constant.PRE_CLEAR_CMD)
        try:
            self._put_csr_file()
        except Exception as e:
            logger.error("Failed to upload the csr file, Error:{}, details:{}".format(e, traceback.format_exc()))
            Ssh.ssh_close(client)
            raise e
        try:
            self.issue_crt(client)
        except Exception as e:
            logger.error("Failed to issue the certificate. Error:{}, details:{}".format(e, traceback.format_exc()))
            self._clear_quorum_server_csr_dir(client, Constant.AFTER_CLEAR_CMD)
            Ssh.ssh_close(client)
            raise e
        try:
            self._download_crt()
        except Exception as e:
            logger.error('Failed to download the certificate.Error:{}, details:{}'.format(e, traceback.format_exc()))
            raise e
        finally:
            self._clear_quorum_server_csr_dir(client, Constant.AFTER_CLEAR_CMD)
            Ssh.ssh_close(client)
        logger.info("Certificate issued successfully.")

    def import_cert_and_ca_file(self, opr: RestOperate, pwd, is_local=True):
        """
        调用rest接口上传证书并激活证书，上传证书后等待30秒证书生效后激活
        :param opr:
        :param pwd:
        :param is_local:
        :return:
        """
        cur_cert = self.fcd_local_crt_path if is_local else self.fcd_remote_crt_path
        logger.info('Uploading Certificates:{}, float ip:{}'.format(cur_cert, opr.float_ip))
        opr.login(self.dm_user, pwd, keep_session=True)
        cert_info = {
            "cert_type": "3",
            "file_type": "crt",
            "file_path": cur_cert,
            "file_name": "CERTIFICATE_NAME"
        }
        ca_info = {
            "cert_type": "3",
            "file_type": "crt",
            "file_path": self.fcd_ca_path,
            "file_name": "CA_CERTIFICATE_NAME"
        }
        try:
            logger.info('Start to import {} certificate'.format(cur_cert))
            rsp = opr.import_certificate(self.dm_user, pwd, cert_info)
            if rsp != 200:
                err_msg = "Failed to import {}. status:{}, float ip:{}".format(cur_cert, rsp, opr.float_ip)
                logger.error(err_msg)
                raise FCDException(626358, cur_cert, err_msg)
            rsp = opr.import_certificate(self.dm_user, pwd, ca_info)
            if rsp != 200:
                err_msg = "Failed to import {}. status:{}, float ip:{}".format(self.fcd_ca_path, rsp, opr.float_ip)
                logger.error(err_msg)
                raise FCDException(626358, cur_cert, err_msg)
            time.sleep(30)
            logger.info('Start to activate the {} certificate'.format(cur_cert))
            rsp, status_code = opr.activat_certificate(3)
            if status_code != 0:
                err_msg = "Failed to activate certificate:{}.Detail:{},float ip:{}".format(cur_cert, rsp, opr.float_ip)
                logger.error(err_msg)
                raise FCDException(626390, cur_cert, err_msg)
        finally:
            opr.logout()
        logger.info("{} certificate activated successfully.".format(cur_cert))

    def add_quorum_server_whitelist(self, local_sn, remote_sn):
        """
        添加仲裁白名单
        :param local_sn:
        :param remote_sn:
        :return:
        """
        logger.info('Adding a Quorum whitelist')
        client = Ssh.ssh_create_client(self.arb_ip, self.arb_user, self.arb_pwd, timeout=5)
        try:
            Ssh.ssh_send_command(client, "qsadmin", expect="admin:/", timeout=20)
            self.add_whitelist(client, local_sn)
            self.add_whitelist(client, remote_sn)
            Ssh.ssh_send_command(client, "exit", expect="#", timeout=10)
        finally:
            Ssh.ssh_close(client)

    def _put_csr_file(self):
        logger.info('Uploading the csr file: {}、{}'.format(self.local_csr_path, self.remote_csr_path))
        Ssh.put_file(self.arb_ip, self.arb_user, self.arb_pwd, self.local_csr_path,
                     destination=Constant.QUORUM_CERT_DIR)
        Ssh.put_file(self.arb_ip, self.arb_user, self.arb_pwd, self.remote_csr_path,
                     destination=Constant.QUORUM_CERT_DIR)

    def _download_crt(self):
        logger.info('Downloading Certificates and CA Certificates')
        Ssh.get_file(self.arb_ip, self.arb_user, self.arb_pwd, Constant.CA_FILE_PATH, self.fcd_ca_path)
        Ssh.get_file(self.arb_ip, self.arb_user, self.arb_pwd, Constant.LOCAL_CRT_FILE_PATH, self.fcd_local_crt_path)
        Ssh.get_file(self.arb_ip, self.arb_user, self.arb_pwd, Constant.REMOTE_CRT_FILE_PATH, self.fcd_remote_crt_path)

    def _clear_quorum_server_csr_dir(self, client, clear_cmd):
        logger.info('Clear the directory for storing csr files. cmd:{}'.format(clear_cmd))
        ret = Ssh.ssh_exec_command_return(client, clear_cmd, timeout=20)
        if str(ret).find('last_status=0') < 0:
            err_msg = 'Failed to clear the directory. details:{}'.format(ret)
            logger.error(err_msg)
            Ssh.ssh_close(client)
            raise FCDException(626329, self.arb_ip, err_msg)


class Constant:
    EXCEPT = "Command executed successfully"
    ISSUE_EXCEPT = "failed 0 files"
    QUORUM_CERT_DIR = "/opt/quorum_server/export_import/"
    LOCAL_CSR = "local.csr"
    LOCAL_CRT = "local.crt"
    REMOTE_CSR = "remote.csr"
    REMOTE_CRT = "remote.crt"
    CA_NAME = "cps_ca.crt"
    CA_FILE_PATH = f"{QUORUM_CERT_DIR}{CA_NAME}"
    LOCAL_CRT_FILE_PATH = f"{QUORUM_CERT_DIR}{LOCAL_CRT}"
    REMOTE_CRT_FILE_PATH = f"{QUORUM_CERT_DIR}{REMOTE_CRT}"
    PRE_CLEAR_CMD = "if [ ! -d {0} ];" \
                    "then mkdir -p {0};" \
                    "chown -R quorumsvr:quorumsvr /opt/quorum_server/;" \
                    "chmod -R 700 /opt/quorum_server/;" \
                    "else rm -f {0}{1};" \
                    "rm -f {0}{2};" \
                    "rm -f {0}{3};" \
                    "rm -f {0}{4};" \
                    "fi;" \
                    "echo last_status=$?".format(QUORUM_CERT_DIR, LOCAL_CSR, LOCAL_CRT, REMOTE_CSR, REMOTE_CRT)
    AFTER_CLEAR_CMD = "rm -f {0}{1};" \
                      "rm -f {0}{2};" \
                      "rm -f {0}{3};" \
                      "rm -f {0}{4};" \
                      "echo last_status=$?".format(QUORUM_CERT_DIR, LOCAL_CSR, LOCAL_CRT, REMOTE_CSR, REMOTE_CRT)
