# -*- coding:utf-8 -*-
import requests
import sys
import os
import logging
import base64
import json
from FSSysconf import netPolicyParse

logger = logging.getLogger('__EBACKUP_CONFIG__')
logger.setLevel(level=logging.DEBUG)
logger.propagate = False
logger.handlers = []

log_file_path = os.path.realpath(__file__ + '/../dmk_driver.log')
if not os.path.isfile(log_file_path):
    os.mknod(log_file_path)

handler = logging.FileHandler(log_file_path)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
    '%(asctime)s [%(levelname)s] at %(filename)s,%(lineno)d: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

VALID_VOLUME_DRIVER = ['cinder.volume.drivers.dsware.HuaweiDswareDriver',
                       'cinder.volume.drivers.huawei.vrm.vrm_driver.VRMDriver',
                       'cinder.volume.drivers.huawei.HuaweiISCSIDriver']
global CPS_BASE_URL


def get_cps_server_base_url():
    base_url = ""
    try:
        floating_ip = netPolicyParse.getComponentFloatingIp(
            compName="cps-server")
        floating_port = netPolicyParse.getComponentFloatingPort(
            compName="cps-server")
        if floating_ip.find(":") > 0:
            base_url = '[' + floating_ip + ']' + ":" + floating_port
        else:
            base_url = floating_ip + ":" + floating_port
        logger.info("Get base url by interface: %s" % base_url)
        return base_url.replace(' ', '')
    except Exception as e:
        logger.info("not found cps-server maybe it's cascaded %s!" % e)
        file_path = "/etc/huawei/fusionsphere/cfg/serverIp.ini"
        if os.path.exists(file_path):
            fp = open(file_path, 'r')
            logger.info('use config file : %s' % fp)
            while True:
                line = fp.readline().strip('\n')
                if not line:
                    break
                key_value = line.split("=")
                if key_value[0].strip() == 'serverip':
                    base_url = key_value[1].strip()
                    base_url = base_url.replace("https://", "")
                    logger.info(
                        "Get base url from serverIP.ini: %s" % base_url)
            fp.close()
            return base_url.replace(' ', '')
        else:
            logger.error(file_path + "is not existed.")
            return ""


def get_cinder_backup_templates(cps_username, cps_password):
    global CPS_BASE_URL
    try:
        logger.info("Begin to query all cinder backup template.")
        cinder_backup_templates = []
        url = "https://%s/cps/v1/services/all/componenttemplates?" \
              "commit_state=commited" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }

        rsp = requests.get(url=url, headers=header, verify=False)
        if rsp.status_code == 200:
            rsp_dict = rsp.json()
            for service in rsp_dict["templates"]:
                if -1 == service["name"].find("cinder-backup"):
                    continue
                cinder_backup_templates.append(service["name"])
        else:
            logger.error("Response status code is not 200.The url is" + url)
            raise Exception("Response status code is not 200.")
        if 0 == len(cinder_backup_templates):
            logger.debug("There is no cinder backup template.")
            return True, []
        logger.info("Query all cinder backup template successfully.")
        return True, cinder_backup_templates
    except Exception as e:
        logger.error(
            "Query all cinder backup template failed.The reason is " + str(e))
        return False, []


def get_supported_backup_templates(cinder_backup_templates, cps_username,
                                   cps_password):
    global CPS_BASE_URL
    try:
        logger.info("Begin to query supported backup template.")
        supported_templates = []
        m_url = "https://%s/cps/v1/services/cinder/componenttemplates" \
                "/EACH_TEMPLATE/params?commit_state=commited" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        for template in cinder_backup_templates:
            logger.info('start check template %s' % template)
            url = m_url.replace("EACH_TEMPLATE", template)
            logger.debug("The url is " + url)
            rsp = requests.get(url=url, headers=header, verify=False)
            if rsp.status_code == 200:
                template_params = rsp.json()["cfg"]
                volume_driver = template_params.get('volume_driver', '')
                other_storage_cfg = \
                    template_params.get('other_storage_cfg', '')
                logger.debug('volume_driver: %s' % volume_driver)
                logger.debug('other_storage_cfg: %s' % other_storage_cfg)
                valid_volume_driver = VALID_VOLUME_DRIVER
                other_volume_driver = ''
                if other_storage_cfg:
                    for poolid, poollist in other_storage_cfg.items():
                        for cfgkey, poolcfg in poollist.items():
                            if poolcfg.get('volume_driver', ''):
                                other_volume_driver = poolcfg.get(
                                    'volume_driver', '')
                logger.debug('other_volume_driver: %s' % other_volume_driver)
                if volume_driver in valid_volume_driver or \
                        (other_volume_driver in valid_volume_driver):
                    supported_templates.append(template)
                else:
                    logger.debug(
                        'There is not valid volume driver %s in '
                        'template %s' % (volume_driver, template))
            else:
                logger.error(
                    "Response status code is not 200.The url is" + url)
                raise Exception("Response status code is not 200.")
        # There is no supproted templates, which it is no need to
        # deploy eBackup service.
        if 0 == len(supported_templates):
            logger.error("There are no supported templates.")
            return True, []
        logger.info("The supported templates are: " + str(supported_templates))
        return True, supported_templates
    except Exception as e:
        logger.error("Query supported template failed,the reason is " + str(e))
        return False, []


def get_supported_role(supported_templates):
    supported_role = []
    for template in supported_templates:
        try:
            temp = template.split('-')
            if len(temp) > 2:
                supported_role.append("blockstorage-driver-" + temp[2])
            else:
                supported_role.append("blockstorage-driver")
        except Exception as e:
            logger.info(
                "An exception occurred when get supported"
                " role.The exception is " + str(e))
    return supported_role


def get_ebackup_driver_node(supported_role, cps_username, cps_password):
    global CPS_BASE_URL
    try:
        logger.info("Begin to query eBackup driver node by supported role.")
        url = "https://%s/cps/v1/hosts" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        ebackup_driver_nodes = []
        rsp = requests.get(url=url, headers=header, verify=False)
        if rsp.status_code == 200:
            host_dict = rsp.json()
            for host in host_dict["hosts"]:
                omip = host["omip"]
                if omip is not None and omip != '':
                    roles = str(host["roles"])
                    logger.debug(
                        "The roles of host(%s) are:%s" % (omip, str(roles)))
                    for role in supported_role:
                        if role in roles:
                            ebackup_driver_nodes.append(omip)
                            break
        else:
            logger.error("Response status code is not 200.The url is" + url)
            raise Exception("Response status code is not 200.")
        logger.info(
            "Query eBackup driver successfully.The eBackup"
            " driver nodes are " + str(ebackup_driver_nodes))
        if 0 == len(ebackup_driver_nodes):
            logger.error("There is no ebackup driver node.")
            return False, []
        logger.info(
            "Query eBackup driver node successfully.The eBackup driver "
            "node are:" + str(ebackup_driver_nodes))
        return True, ebackup_driver_nodes
    except Exception as e:
        logger.error(
            "Query eBackup driver node failed,the reason is " + str(e))
        return False, []


def get_ebackup_driver_node2(supported_templates, cps_username, cps_password):
    global CPS_BASE_URL
    try:
        logger.info(
            "Begin to query eBackup driver node by supported template.")
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        ebackup_driver_nodes = []
        for template in supported_templates:
            url = "https://%s/cps/v1/instances?service=cinder&template=%s" % (
                CPS_BASE_URL, template)
            rsp = requests.get(url=url, headers=header, verify=False)
            if rsp.status_code == 200:
                instance_list = rsp.json()["instances"]
                for host in instance_list:
                    omip = host["omip"]
                    if omip is not None and omip != '' and \
                            omip not in ebackup_driver_nodes:
                        ebackup_driver_nodes.append(omip)
            else:
                logger.error(
                    "Response status code is not 200.The url is" + url)
                raise Exception("Response status code is not 200.")

        if 0 == len(ebackup_driver_nodes):
            logger.error("There is no ebackup driver node.")
            return False, []
        logger.info(
            "Query eBackup driver node successfully.The eBackup "
            "driver node are:" + str(ebackup_driver_nodes))
        return True, ebackup_driver_nodes
    except Exception as e:
        logger.error(
            "Query eBackup driver node failed,the reason is " + str(e))
        return False, []


def get_cps_server_node(cps_username, cps_password):
    global CPS_BASE_URL
    try:
        logger.info("Begin to query cps server nodes.")
        url = "https://%s/cps/v1/instances?service=cps-server&template=" \
              "cps-server" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        cps_server = []
        rsp = requests.get(url=url, headers=header, verify=False)

        if rsp.status_code == 200:
            rsp_dict = rsp.json()
            for cps in rsp_dict["instances"]:
                omip = cps["omip"]
                if omip is not None and omip != '':
                    cps_server.append(omip)
        else:
            logger.info("The cps-server service name is cps")
            url = "https://%s/cps/v1/instances?service=cps&" \
                  "template=cps-server" % CPS_BASE_URL
            rsp = requests.get(url=url, headers=header, verify=False)
            if rsp.status_code == 200:
                rsp_dict = rsp.json()
                for cps in rsp_dict["instances"]:
                    omip = cps["omip"]
                    if omip is not None and omip != '':
                        cps_server.append(omip)
            else:
                logger.error(
                    "Response status code is not 200.The url is" + url)
                raise Exception("Response status code is not 200.")
        if 0 == len(cps_server):
            logger.error("There is no cps server.")
            return False, []
        logger.info(
            "Query cps server nodes successfully.The cps server are:" + str(
                cps_server))
        return True, cps_server
    except Exception as e:
        logger.error("Query cps server nodes failed,the reason is " + str(e))
        return False, []


def generate_host_file(operation, output_file, driver_host, cps_server=None):
    try:
        logger.info("Begin to generate host file")
        fp = open(output_file, 'w+')
        if operation == "upgrade":
            fp.write("[cpsnode]" + '\n')
            index = 1
            for ip in cps_server:
                fp.write("CpsServer%s ansible_ssh_host='%s'" % (
                    str(index), ip) + '\n')
                index += 1
            fp.write("[eBackupDriver]" + '\n')
        elif operation == "install":
            fp.write("[FspBecascadeDriver]" + '\n')

        index = 1
        for ip in driver_host:
            fp.write("eBackupDriver%s ansible_ssh_host='%s'" % (
                str(index), ip) + '\n')
            index += 1
        fp.close()
        logger.info("Generate host file successfully!")
        return True
    except Exception:
        logger.info("Generate host file failed!")
        return False


def check_cps_admin_password(cps_username, cps_password):
    global CPS_BASE_URL
    try:
        logger.info(
            "Begin to check the password of the %s user." % cps_username)
        url = "https://%s/cps/v1/haproxylocalurl" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }

        rsp = requests.get(url=url, headers=header, verify=False)
        if rsp.status_code == 200:
            rsp_dict = rsp.json()
            if -1 != str(rsp_dict).lower().find("failed"):
                logger.error("Authenticate Failed!")
                return False
        else:
            logger.error("Response status code is not 200.The url is" + url)
            raise Exception("Response status code is not 200.")

        logger.info(cps_username + "Authenticate successfully.")
        return True
    except Exception as e:
        logger.error("Authenticate Failed!The reason is: " + str(e))
        return False


def get_host_ip_by_id(cps_username, cps_password, old_host_ids, new_host_ids):
    global CPS_BASE_URL
    try:
        logger.info("Begin to get host ips by ids.")
        logger.info("The old host ids:" + str(old_host_ids))
        logger.info("The new host ids:" + str(new_host_ids))
        url = "https://%s/cps/v1/hosts" % CPS_BASE_URL
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        old_host_ips = []
        new_host_ips = []
        rsp = requests.get(url=url, headers=header, verify=False)
        if rsp.status_code == 200:
            host_dict = rsp.json()
            for host in host_dict["hosts"]:
                if host["id"] in old_host_ids:
                    old_host_ips.append(host["omip"])
                    logger.info("old_host_id:%s, old_host_ip:%s" %
                                (host["id"], host["omip"]))
                if host["id"] in new_host_ids:
                    new_host_ips.append(host["omip"])
                    logger.info("new_host_id:%s, new_host_ip:%s" %
                                (host["id"], host["omip"]))
        else:
            logger.error("Response status code is not 200.The url is" + url)
            raise Exception("Response status code is not 200.")

        logger.info("The new ip is:" + str(new_host_ips))
        logger.info("The old ip is:" + str(old_host_ips))
        logger.info("Get host ip by id successfully.")
        return True, old_host_ips, new_host_ips
    except Exception as e:
        logger.error("Get host ips by host ids failed: " + str(e))
        return False, []


def get_role_changed_host_ids():
    try:
        logger.info("Begin to get role changed host ids")
        old_host_ids = []
        new_host_ids = []
        role_change_file = os.path.realpath(
            __file__ + '/../' + "name_modification.json")
        with open(role_change_file) as fp:
            context = json.load(fp)
        for item in context:
            if item["type"] == "role":
                for az_role in item["details"]:
                    if -1 != az_role["old_name"].find("blockstorage-driver"):
                        old_host_ids += az_role["old_hosts"]
                        new_host_ids += az_role["new_hosts"]
                        logger.info("old role is:%s, old id is:%s" %
                                    (az_role["old_name"],
                                     az_role["old_hosts"]))
                        logger.info("new role is:%s, new id is:%s" %
                                    (az_role["new_name"],
                                     az_role["new_hosts"]))
        os.remove(role_change_file)
        logger.info("Get role changed host ids successfully.")
        return True, old_host_ids, new_host_ids
    except Exception as e:
        logger.error("Get role changed host ids failed: " + str(e))
        return False, [], []


def get_blockstorage_driver_nodes(cps_username, cps_password, driver_nodes):
    try:
        logger.info("Begin to get all blockstorage driver nodes")
        blockstorage_driver_nodes = driver_nodes
        is_true, old_host_ids, new_host_ids = get_role_changed_host_ids()
        if not is_true:
            logger.error("Get role changed host ids failed.")
            return False, []

        is_true, old_host_ips, new_host_ips = get_host_ip_by_id(cps_username,
                                                                cps_password,
                                                                old_host_ids,
                                                                new_host_ids)
        if not is_true:
            logger.error("Get host ip by id failed.")
            return False, []

        for ip in old_host_ips:
            if ip in blockstorage_driver_nodes:
                blockstorage_driver_nodes.remove(ip)
                logger.info(
                    "The role name of the node %s will be modified "
                    "after cutover." % str(ip))
        for ip in new_host_ips:
            if ip not in blockstorage_driver_nodes:
                blockstorage_driver_nodes.append(ip)
                logger.info(
                    "The node %s will get a new role name after"
                    " cutover." % str(ip))
        logger.info("Get all blockstorage driver nodes successfully.")
        return True, blockstorage_driver_nodes

    except Exception as e:
        logger.error("Get blockstorage driver nodes failed: " + str(e))
        return False, []


def check_cinder_backup_cutovered(supported_templates, cps_username,
                                  cps_password):
    global CPS_BASE_URL
    try:
        logger.info(
            "Begin to check if all cinder backup deploy info "
            "has been cutovered.")
        header = {
            "Content-Type": "application/json",
            "X-Auth-User": cps_username,
            "X-Auth-Password": cps_password
        }
        for template in supported_templates:
            url = "https://%s/cps/v1/instances?service=cinder&" \
                  "template=%s" % (CPS_BASE_URL, template)
            rsp = requests.get(url=url, headers=header, verify=False)
            if rsp.status_code == 200:
                instance_list = rsp.json()["instances"]
                is_completed = (0 != len(instance_list))
                if not is_completed:
                    logger.error(
                        "Cinder backup deploy info cutover is not completed")
                    return False, {"Error": "Cutover-unfinished"}
            else:
                logger.error(
                    "Response status code is not 200.The url is" + url)
                raise Exception("Response status code is not 200.")

        logger.info("All cinder backup deploy info has been cutovered.")
        return True, {}
    except Exception as e:
        logger.error(
            "check if all cinder backup deploy info has been cutovered "
            "failed,the reason is " + str(e))
        return False, {"Error": "Cutover-exception"}


if __name__ == "__main__":
    if sys.argv.__len__() != 4:
        logger.error("Parameter is Invalid.")
        print("ERROR: Parameter is Invalid.")
        exit(1)
    CPS_BASE_URL = get_cps_server_base_url()
    cps_username = sys.argv[1]
    cps_password = sys.argv[2]
    operation = sys.argv[3]
    try:
        cps_password = base64.b64decode(cps_password)
        is_true = check_cps_admin_password(cps_username, cps_password)
        if not is_true:
            logger.error(
                "Check the password of the %s user failed." % cps_username)
            print(
                "ERROR: Authenticate Failed!Please check the password "
                "of the %s user" % cps_username)
            exit(1)
    except Exception:
        logger.error("Decode the password of the cps_admin user failed.")
        print("ERROR: Decode the password of the cps_admin user failed.")
        exit(1)
    valid_operation = ["upgrade", "install", "query_role", "upgrade_cascading",
                       "check_cutover"]
    if operation not in valid_operation:
        logger.error("Operation is invalid.")
        print("ERROR: Operation is invalid.The valid operation "
              "are:" + str(valid_operation))
        exit(1)

    is_true, backup_templates = get_cinder_backup_templates(cps_username,
                                                            cps_password)
    if not is_true:
        logger.error("Get cinder backup templates failed.")
        print("ERROR: Get cinder backup templates failed.")
        exit(1)
    elif 0 == len(backup_templates):
        logger.debug("There is no cinder backup templates")
        print("DEBUG: Unsupported-eBackup.")
        exit(0)
    is_true, supported_templates = get_supported_backup_templates(
        backup_templates, cps_username, cps_password)
    if not is_true:
        logger.error(
            "Get cinder backup templates which support eBackup failed.")
        print(
            "ERROR: Get cinder backup templates which support eBackup failed.")
        exit(1)
    elif 0 == len(supported_templates):
        logger.debug("There is no supported cinder backup templates")
        print("DEBUG: Unsupported-eBackup.")
        exit(0)
    supported_role = get_supported_role(supported_templates)
    if 0 == len(supported_role):
        logger.error("Get supported role failed.")
        print("ERROR: Get supported role failed.")
        exit(1)
    if operation == "query_role":
        print(supported_role)
        exit(0)
    elif operation == "check_cutover":
        is_true, error_msg = check_cinder_backup_cutovered(supported_templates,
                                                           cps_username,
                                                           cps_password)
        if not is_true:
            print(json.dumps(error_msg))
            exit(1)
        exit(0)
    else:
        is_true, driver_nodes = get_ebackup_driver_node2(supported_templates,
                                                         cps_username,
                                                         cps_password)
        if 0 == len(driver_nodes):
            logger.error("Get ebackup driver node failed.")
            print("ERROR: Get ebackup driver node failed.")
            exit(1)
        if operation == "upgrade":
            is_true, cps_server = get_cps_server_node(cps_username,
                                                      cps_password)
            if not is_true:
                logger.error("Get cps server failed.")
                print("ERROR: Get cps server failed.")
                exit(1)

            print(json.dumps({"cpsnode": cps_server}))
            print(json.dumps({"eBackupDriver": driver_nodes}))
            exit(0)
        elif operation == "install":

            print(json.dumps({"eBackupDriver": driver_nodes}))
            exit(0)
        elif operation == "upgrade_cascading":
            is_true, blockstorage_driver_nodes = get_blockstorage_driver_nodes(
                cps_username, cps_password, driver_nodes)
            if not is_true:
                logger.error("Get blockstorage_driver nodes failed.")
                print("ERROR: Get blockstorage_driver nodes failed.")
                exit(1)
            print(json.dumps({"eBackupDriver": blockstorage_driver_nodes}))
            exit(0)
