#!/usr/bin/env python2

import subprocess
import sys
import random
from distutils.util import strtobool
import argparse
import os
import time
import shutil


def dir_precheck(args):
    runningdir = os.path.abspath(args.install_prefix) + "/" + str(args.port)
    datadir = os.path.abspath(args.datadir_prefix) + "/" + str(args.port)
    logdir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)
    waldir = os.path.abspath(args.waldir_prefix) + "/" + str(args.port)

    # check dir exsistance, if already exists, return false
    ret = os.path.isdir(runningdir)
    if ret == True:
        sys.stderr.write(
            "{} already exists, can not proceeding the installation".format(runningdir)
        )
        return False
    ret = os.path.isdir(datadir)
    if ret == True:
        sys.stderr.write(
            "{} already exists, can not proceeding the installation".format(datadir)
        )
        return False
    ret = os.path.isdir(logdir)
    if ret == True:
        sys.stderr.write(
            "{} already exists, can not proceeding the installation".format(logdir)
        )
        return False
    ret = os.path.isdir(waldir)
    if ret == True:
        sys.stderr.write(
            "{} already exists, can not proceeding the installation".format(waldir)
        )
        return False

    return True


def localize_template_and_binary(args):
    # 1.prepare the related directory
    try:
        # 1.1 making the MySQL binary residential directory
        runing_dir = os.path.abspath(args.install_prefix) + "/" + str(args.port)
        os.makedirs(runing_dir, 0o777)

        # 1.2 make the MySQL data residential directory
        data_dir = os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/data"
        os.makedirs(data_dir, 0o777)

        # 1.3 make the MySQL log residential directory
        log_bin_dir = (
            os.path.abspath(args.logdir_prefix) + "/" + str(args.port) + "/binlog"
        )
        os.makedirs(log_bin_dir, 0o777)

        log_relay_dir = (
            os.path.abspath(args.logdir_prefix) + "/" + str(args.port) + "/relay"
        )
        os.makedirs(log_relay_dir, 0o777)

        # 1.4 make the MySQL wal log residential directory
        wallog_dir = (
            os.path.abspath(args.waldir_prefix) + "/" + str(args.port) + "/redolog"
        )
        os.makedirs(wallog_dir, 0o777)

        # 1.5 make the MySQL temp log residential directory
        temp_dir = os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/temp"
        os.makedirs(temp_dir, 0o777)

    except OSError as err:
        sys.stderr.write("Localize the Instance failed: {}".format(str(err)))
        return False

    # 2. Copy kunlun-storage to the Runing directorys
    file_cwd = os.path.dirname(os.path.realpath(__file__))
    package = os.path.dirname(file_cwd)
    cmd = "cp -r " + package + " " + runing_dir
    os.system(cmd)
    localized_path = runing_dir + "/" + str(args.prog_name)

    # 3. Localize the template file
    ret = localize_template_conf(args, localized_path)

def generate_server_id2(args):
    inargs = "{ipport}".format(ipport=(args.bind_address + "." + str(args.port)))
    tokens = inargs.split('.');

    rt1 = tokens[2];
    rt1 = int(rt1) << 24;
    rt2 = tokens[3];
    rt2 = int(rt2) << 16;
    rt3 = int(tokens[4]);
    return str(((rt1 | rt2) | rt3)).strip();

def generate_server_id(args):
    cmd = 'echo "{ipport}" | awk -F"." \'{{print or(or(lshift($3,24),lshift($4,16)),$5)}}\''.format(
        ipport=(args.bind_address + "." + str(args.port))
    )
    result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
    server_id = result
    return server_id.strip()


def localize_template_conf(args, work_path):
    # work_path ..../7789/kunlun-storage-0.9.2/

    server_id = generate_server_id2(args)
    print("MySQL Instance {pt}'s serverid is {seid}".format(pt=str(args.port),seid=server_id))
    localized_datadir = (
        os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/data"
    )
    localized_logdir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)

    localized_binlogdir = (
        os.path.abspath(args.logdir_prefix) + "/" + str(args.port) + "/binlog"
    )

    localized_relaydir = (
        os.path.abspath(args.logdir_prefix) + "/" + str(args.port) + "/relay"
    )

    localized_waldir = (
        os.path.abspath(args.waldir_prefix) + "/" + str(args.port) + "/redolog"
    )
    localized_tmpdir = (
        os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/temp"
    )
    # 1. copy template to the ./etc/
    if args.db_cfg == 1:
        cmd = "cp {wp}/dba_tools/template-rbr-small.cnf {wp}/etc/{portnum}.cnf".format(
            wp=work_path, portnum=str(args.port)
        )
    else:
        cmd = "cp {wp}/dba_tools/template-rbr.cnf {wp}/etc/{portnum}.cnf".format(
            wp=work_path, portnum=str(args.port)
        )
    os.system(cmd)

    cnf_file = "{wp}/etc/{portnum}.cnf".format(wp=work_path, portnum=str(args.port))

    # 2. replace the place holder of the template by SED
    cmd = "cat {cnf} ".format(cnf=cnf_file)
    cmd += "| sed -e 's|place_holder_port|{dst}|g' ".format(dst=str(args.port))
    cmd += "| sed -e 's|prod_dir|{dst}|g' ".format(dst=localized_logdir)
    cmd += "| sed -e 's|place_holder_ip|{dst}|g' ".format(dst=args.bind_address)
    cmd += "| sed -e 's|place_holder_server_id|{dst}|g' ".format(dst=server_id)
    cmd += "| sed -e 's|base_dir|{dst}|g' ".format(dst=work_path)
    cmd += "| sed -e 's|place_holder_user|{dst}|g' ".format(dst=args.user)
    cmd += "| sed -e 's|data_dir|{dst}|g' ".format(dst=localized_datadir)
    cmd += "| sed -e 's|log_dir|{dst}|g' ".format(dst=localized_logdir)
    cmd += "| sed -e 's|log_bin_arg|{dst}|g' ".format(dst=localized_binlogdir)
    cmd += "| sed -e 's|log_relay|{dst}|g' ".format(dst=localized_relaydir)
    cmd += "| sed -e 's|log_arch|{dst}|g' ".format(dst=localized_waldir)
    cmd += "| sed -e 's|tmp_dir|{dst}|g' ".format(dst=localized_tmpdir)
    cmd += "| sed -e 's|place_holder_innodb_buffer_pool_size|{dst}|g' ".format(
        dst=str(args.innodb_buffer_poll_size_M) + "M"
    )
    cmd += "| sed -e 's|place_holder_rocksdb_block_cache_size|{dst}|g' ".format(
        dst=str(args.rocksdb_block_cache_size_M) + "M"
    )
    cmd += "| sed -e 's|place_holder_thread_concurrency|{dst}|g' ".format(dst="0")
    cmd += "> {wp}/etc/{portnum}.cnf.tmp".format(wp=work_path, portnum=str(args.port))

    os.system(cmd)

    cmd = "mv {wp}/etc/{portnum}.cnf {wp}/etc/{portnum}.cnf.template; mv {wp}/etc/{portnum}.cnf.tmp {wp}/etc/{portnum}.cnf".format(
        wp=work_path, portnum=str(args.port)
    )
    os.system(cmd)


def initialize(args):
    datadir = os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/data"
    workpath = "{prefix}/{pt}/{prgname}".format(
        prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
    )

    exprt="export LD_LIBRARY_PATH={wp}/lib:$LD_LIBRARY_PATH".format(wp=workpath)

    # initialize
    cmd = "{ep};{wp}/bin/mysqld --defaults-file={wp}/etc/{pt}.cnf --user={user} --initialize".format(
        wp=workpath, pt=str(args.port), user=args.user, ep=exprt
    )
    os.system(cmd)
    time.sleep(3)

    # mv cnf file to the innodbdatadir
    cmd = "cat {wp}/etc/{pt}.cnf "
    
    if args.install_rocksdb == 1:
        cmd += "| sed -e 's|^#plugin_load|plugin_load|g' "
        cmd += "| sed -e 's|^#rocksdb_|rocksdb_|g' "
    
    cmd += "> {dr}/{pt}.cnf"
    os.system(cmd.format(wp=workpath, pt=str(args.port), dr=datadir))

    # prepare etc/instance_list.txt
    cmd = "echo '{pt}==>{dr}/{pt}.cnf' > {wp}/etc/instances_list.txt".format(
        wp=workpath, dr=datadir, pt=str(args.port)
    )
    os.system(cmd)


def rollback_installation(args):
    workpath = "{prefix}/{pt}/{prgname}".format(
        prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
    )
    dba_tools_path = "{wp}/dba_tools".format(wp=workpath)

    file_cwd = os.path.dirname(os.path.realpath(__file__))
    package = os.path.dirname(file_cwd)
    program_path = os.path.dirname(package)
    install_err_log_mysql = "{pp}/install_mysqld_err".format(pp=program_path)
    if os.path.isdir(install_err_log_mysql) == False:
        os.makedirs(install_err_log_mysql, 0o777)

    # 1. Stop MySQL instance if already running by invoking stop_mysql.sh
    try:
        cmd_stop_mysql = "cd {dp};sh ./stopmysql.sh {port}".format(
            dp=dba_tools_path, port=str(args.port)
        )
        os.system(cmd_stop_mysql)
    except OSError as err:
        sys.stdout.write("Stop MySQL instance failed: {}".format(str(err)))

    # 2. save the mysqld.err file
    log_dir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)
    mysqderr_file = "{ld}/mysqld.err".format(ld=log_dir)
    cmd = "cp {mf} {inl}/{pt}_mysqld.err".format(mf=mysqderr_file, inl=install_err_log_mysql, pt=str(args.port))
    os.system(cmd)

    # 3. remove the related directory
    try:
        # 3.1 remove the MySQL binary residential directory
        runing_dir = os.path.abspath(args.install_prefix) + "/" + str(args.port)
        shutil.rmtree(runing_dir, ignore_errors=True)

        # 3.2 remove the MySQL data residential directory
        data_dir = os.path.abspath(args.datadir_prefix) + "/" + str(args.port)
        shutil.rmtree(data_dir, ignore_errors=True)

        # 3.3 remove the MySQL log residential directory
        log_dir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)
        shutil.rmtree(log_dir, ignore_errors=True)

        # 3.4 remove the MySQL wal log residential directory
        wallog_dir = os.path.abspath(args.waldir_prefix) + "/" + str(args.port)
        shutil.rmtree(wallog_dir, ignore_errors=True)

    except OSError as err:
        sys.stdout.write("Remove the Instance data and log failed: {}".format(str(err)))


def get_root_init_pwd(args):
    localized_logdir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)
    mysqld_err_file = open(localized_logdir + "/mysqld.err", "r")
    lines = mysqld_err_file.readlines()
    for line in lines:
        if "A temporary password is generated for root@localhost" in line:
            ret = line.split("root@localhost: ")[1][:-1]
            return ret


def boot_mysql_instance(args):
    workpath = "{prefix}/{pt}/{prgname}".format(
        prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
    )
    cmd = 'cd {wp}/dba_tools;./startmysql.sh {pt}'.format(
        wp=workpath, pt=str(args.port)
    )
    print(cmd)
    result = ""
    try:
        result = subprocess.check_output(
            cmd, shell=True, stderr=subprocess.STDOUT
        )
    except subprocess.CalledProcessError as e:
        print("startmysql.sh failed: {rt},{et}".format(rt=str(result),et=str(e)))

# def boot_mysql_instance(args):
#     workpath = "{prefix}/{pt}/{prgname}".format(
#         prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
#     )
#     cmd = 'cd {wp}/dba_tools;./startmysql.sh {pt} 2>&1 | grep -v "Warning"'.format(
#         wp=workpath, pt=str(args.port)
#     )
#     print(cmd)
#     os.system(cmd)


def post_install(args, origin_root_passwd):
    workpath = "{prefix}/{pt}/{prgname}".format(
        prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
    )
    localized_logdir = os.path.abspath(args.logdir_prefix) + "/" + str(args.port)
    socket_file = "{lgdir}/mysql.sock".format(lgdir=localized_logdir)
    #my_env = os.environ.copy()
    #n_ld = "{wp}/lib:{orig}".format(wp=workpath,orig=str(my_env['LD_LIBRARY_PATH']))
    #my_env['LD_LIBRARY_PATH']= n_ld
    #print(my_env['LD_LIBRARY_PATH'])
    exprt="export LD_LIBRARY_PATH={wp}/lib:$LD_LIBRARY_PATH".format(wp=workpath)


    # 1. Reset root password
    change_rootpwd = '{ep};{wp}/bin/mysql -uroot --connect-expired-password --socket={sk_file} -p\'{orig_pwd}\' -e "{sql}" 2>&1 | grep -v "Warning"'.format(
        wp=workpath,
        sk_file=socket_file,
        orig_pwd=origin_root_passwd,
        sql="set sql_log_bin=0;ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';select 'success';",
        ep=exprt,
    )

    # prepare startmysql.sh again
    workpath = "{prefix}/{pt}/{prgname}".format(
        prefix=os.path.abspath(args.install_prefix), pt=str(args.port), prgname=str(args.prog_name)
    )
    startmysql_cmd = 'cd {wp}/dba_tools;./startmysql.sh {pt} 2>&1 | grep -v "Warning"'.format(
        wp=workpath, pt=str(args.port)
    )
    
    # wait 10 minuts
    retry_num = 600
    while retry_num > 0:
        try:
            result = subprocess.check_output(
                #change_rootpwd, shell=True, stderr=subprocess.STDOUT, env=my_env
                change_rootpwd, shell=True, stderr=subprocess.STDOUT
            )
            if result.find("success") >= 0:
                break
            else:
                retry_num = retry_num - 1
                time.sleep(1)
                print(result)
                print(change_rootpwd)
                print(startmysql_cmd)
                os.system(startmysql_cmd)
                continue
        except subprocess.CalledProcessError as e:
            retry_num = retry_num - 1
            time.sleep(1)
            print(startmysql_cmd)
            os.system(startmysql_cmd)
            print("change_rootpwd failed: {rt}. Will retry 600 times".format(rt=str(result)))
            continue
    if retry_num <= 0:
        return False

    # 2. Continue to create and grant rest admin users
    sql0 = (
        "set sql_log_bin=0; create user clustmgr identified by 'clustmgr_pwd'; create user monitor identified by 'monitor'; "
        + "GRANT ROLE_ADMIN, SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES,"
        + "CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, "
        + "ALTER ROUTINE, EVENT, TRIGGER, BINLOG_ADMIN, GROUP_REPLICATION_ADMIN, REPLICATION_SLAVE_ADMIN, PERSIST_RO_VARIABLES_ADMIN, "
        + "SYSTEM_VARIABLES_ADMIN, XA_RECOVER_ADMIN on *.* to clustmgr@'%' with grant option; flush privileges;"
        + "GRANT USAGE on *.* to monitor@'%'; flush privileges;"
        + "set sql_log_bin=0; create user repl identified by 'repl_pwd'; "
        + "grant replication slave,replication client, BACKUP_ADMIN, CLONE_ADMIN on *.* to 'repl'@'%' ; flush privileges;"
        + "set sql_log_bin=0; create user agent@localhost identified by 'agent_pwd'; "
        + "grant all on *.* to 'agent'@'localhost' with grant option;flush privileges;select version();"
        + "set sql_log_bin=0; create user pgx identified by 'pgx_pwd' ; "
        + "grant CREATE USER,ROLE_ADMIN,Select,Insert,Update,Delete,Create,Drop,Process,References,Index,Alter,"
        + "SHOW DATABASES,CREATE TEMPORARY TABLES,LOCK TABLES,Execute,CREATE VIEW,SHOW VIEW,CREATE ROUTINE,"
        + "ALTER ROUTINE,Event,Trigger, reload, REPLICATION SLAVE, SYSTEM_VARIABLES_ADMIN, PERSIST_RO_VARIABLES_ADMIN, "
        + "BINLOG_ADMIN, GROUP_REPLICATION_ADMIN, REPLICATION_SLAVE_ADMIN, XA_RECOVER_ADMIN, BACKUP_ADMIN, REPLICATION CLIENT on *.* to  'pgx'@'%'; flush privileges;"
        + "set sql_log_bin=0; delete from mysql.db where Db='test\_%' and Host='%' ;"
        + "delete from mysql.db where Db='test' and Host='%';flush privileges;"
    )
    create_admin_users = '{ep};{wp}/bin/mysql -uroot --connect-expired-password --socket={sk_file} -p\'root\' -e "{sql}"  2>&1 | grep -v "Warning"'.format(
        wp=workpath,
        sk_file=socket_file,
        sql=sql0,
        ep=exprt,
    )
    os.system(create_admin_users)

    # 3. Init kunlun_sys_db
    script_sysdb = "{wp}/dba_tools/sys_db_table.sql".format(wp=workpath)
    create_sysdb = "{ep};{wp}/bin/mysql -uroot --connect-expired-password --socket={sk_file} -p'root' < {script} 2>&1 | grep -v \"Warning\"".format(
        wp=workpath,
        sk_file=socket_file,
        script=script_sysdb,
        ep=exprt,
    )
    os.system(create_sysdb)

    # 4. Init seq_reserve_vals
    script_seq = "{wp}/dba_tools/seq_reserve_vals.sql".format(wp=workpath)
    create_seq = "{ep};{wp}/bin/mysql -uroot --connect-expired-password --socket={sk_file} -p'root' < {script} 2>&1 | grep -v \"Warning\"".format(
        wp=workpath,
        sk_file=socket_file,
        script=script_seq,
        ep=exprt,
    )
    os.system(create_seq)

    # 5. Init metadb_info
    fill_metadb_info_sql = "set sql_log_bin=0; Insert into kunlun_sysdb.metadb_info (conn_addr) values ('{meta_addrs}'); ".format(
        meta_addrs=args.meta_addrs
    )
    do_fill_meta_info = '{ep};{wp}/bin/mysql -uroot --connect-expired-password --socket={sk_file} -p\'root\' -e "{sql}" 2>&1 | grep -v "Warning"'.format(
        wp=workpath,
        sk_file=socket_file,
        sql=fill_metadb_info_sql,
        ep=exprt,
    )
    os.system(do_fill_meta_info)

    # set super_read_only=on in cnf file
    localized_datadir = (
        os.path.abspath(args.datadir_prefix) + "/" + str(args.port) + "/data"
    )
    cnf0 = "{datadir}/{pt}.cnf".format(datadir=localized_datadir, pt=str(args.port))
    cmd = "sed -i s/#super_read_only=OFF/super_read_only=on/g {cnf}".format(cnf=cnf0)
    os.system(cmd)


if __name__ == "__main__":
    random.seed(str(sys.argv[1:]))
    parser = argparse.ArgumentParser(description="Install the MySQL Instance.")
    parser.add_argument("--user", type=str, help="MySQL install user", required=True)
    parser.add_argument(
        "--port", type=int, help="MySQL Instance Lisening port", required=True
    )
    parser.add_argument(
        "--bind_address", type=str, help="MySQL Instance binding address", required=True
    )
    parser.add_argument(
        "--innodb_buffer_poll_size_M",
        type=int,
        help="INNODB buffer poll size in xx M",
        required=True,
    )
    parser.add_argument(
        "--rocksdb_block_cache_size_M",
        type=int,
        help="rocksdb block cache size in xx M",
        default=16384
    )
    parser.add_argument(
        "--datadir_prefix",
        type=str,
        help="data directory prefix",
        default="",
        required=True,
    )
    parser.add_argument(
        "--logdir_prefix",
        type=str,
        help="log directory prefix",
        required=True,
    )
    parser.add_argument(
        "--waldir_prefix", type=str, help="redo log directory prefix", required=True
    )
    parser.add_argument(
        "--install_prefix",
        type=str,
        help="MySQL installation directory prefix",
        required=True,
    )
    parser.add_argument(
        "--prog_name",
        type=str,
        help="MySQL Package name",
        required=True,
    )
    parser.add_argument(
        "--meta_addrs",
        type=str,
        help="KunlunBase Metadatadb connection address (ip:port,ip:port)",
        required=True,
    )
    parser.add_argument(
        "--db_cfg",
        type=int,
        default=0,
        help="MySQL installation conf file",
    )
    parser.add_argument(
        "--install_rocksdb",
        type=int,
        default=1,
        help="MySQL installation conf file",
    )
    args = parser.parse_args()

    # Check the directory is avilable
    ret = dir_precheck(args)
    if ret == False:
        os._exit(1)

    # localize the template.cnf
    ret = localize_template_and_binary(args)
    if ret == False:
        rollback_installation(args)
        os._exit(1)

    # Initialize the MySQL instance
    initialize(args)

    # Get Original root password
    origin_root_passwd = get_root_init_pwd(args)
    if origin_root_passwd == None:
        sys.stderr.write(
            "Install MySQL instance on {pt} failed after get_root_init_pwd, Please Check the mysqld.err for more detail".format(
                pt=str(args.port)
            )
        )
        rollback_installation(args)
        os._exit(1)

    # Boot MySQL instance
    boot_mysql_instance(args)

    # Post install operation
    ret = post_install(args, origin_root_passwd)
    if ret == False:
        sys.stderr.write(
            "Install MySQL instance on {pt} failed after post_install, Please Check the mysqld.err for more detail".format(
                pt=str(args.port)
            )
        )
        rollback_installation(args)
        os._exit(1)
