#!/usr/bin/env python
import argparse
import ConfigParser
import glob
import os
import time

import mysql_backup
import mysql_cnf_builder
import mysql_grants
from lib import backup
from lib import environment_specific
from lib import host_utils
from lib import mysql_lib

DIRS_TO_CLEAR = ['log_bin', 'datadir', 'tmpdir']
DIRS_TO_CREATE = ['datadir', 'log_bin', 'log_error',
                  'slow_query_log_file', 'tmpdir']
# in MySQL 5.5+, log_slow_queries is deprecated in favor of
# slow_query_log_file
FILES_TO_CLEAR = ['log_slow_queries', 'log_error', 'slow_query_log_file']

# If MySQL 5.7+, don't use mysql_install_db
MYSQL_INSTALL_DB = '/usr/bin/mysql_install_db'
MYSQL_INITIALIZE = '/usr/sbin/mysqld --initialize-insecure'
log = environment_specific.setup_logging_defaults(__name__)


def main():
    description = 'Initialize a MySQL serer'
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('-p',
                        '--port',
                        help='Port to act on, default is 3306',
                        default='3306')
    parser.add_argument('--skip_production_check',
                        help=('DANGEROUS! Skip check of whether the instance '
                              'to be initialized is already in use'),
                        default=False,
                        action='store_true')
    parser.add_argument('--skip_backup',
                        help=('Do not run a backup once the instance is '
                              'setup'),
                        default=False,
                        action='store_true')
    args = parser.parse_args()

    instance = host_utils.HostAddr(':'.join((host_utils.HOSTNAME,
                                             args.port)))
    mysql_init_server(instance,
                      args.skip_production_check,
                      skip_backup=args.skip_backup)


def mysql_init_server(instance,
                      skip_production_check=False,
                      skip_backup=True, lock_handle=None):
    """ Remove any data and initialize a MySQL instance

    Args:
    instance - A hostaddr object pointing towards localhost to act upon
    skip_production_check - Dangerous! will not run safety checks to protect
                            production data
    skip_backup - Don't run a backup after the instance is setup
    lock_handle - If the caller already locked the system, pass in the
                  lock handle, as we may need to release and reacquire
                  to prevent mysqld from keeping it.
    """
    if lock_handle is None:
        # Take a lock to prevent multiple restores from running concurrently
        log.info('Taking a lock to block race conditions')
        lock_handle = host_utils.bind_lock_socket(backup.STD_BACKUP_LOCK_SOCKET)
    else:
        log.info('Lock already exists from caller.')

    try:
        # sanity check
        zk = host_utils.MysqlZookeeper()
        if (not skip_production_check and
                instance in zk.get_all_mysql_instances()):
            raise Exception("It appears {instance} is in use. This is"
                            " very dangerous!".format(instance=instance))

        log.info('Checking host for mounts, etc...')
        basic_host_sanity()

        log.info('(re)Generating MySQL cnf files')
        mysql_cnf_builder.build_cnf()

        log.info('Creating any missing directories')
        create_and_chown_dirs(instance.port)

        log.info('Shutting down MySQL (if applicable)')
        host_utils.stop_mysql(instance.port)

        log.info('Deleting existing MySQL data')
        delete_mysql_data(instance.port)

        log.info('Creating MySQL privileges tables')
        init_privileges_tables(instance.port)

        log.info('Clearing innodb log files')
        delete_innodb_log_files(instance.port)

        log.info('Starting up instance')
        host_utils.start_mysql(instance.port)

        log.info('Importing MySQL users')
        mysql_grants.manage_mysql_grants(instance, 'nuke_then_import')

        log.info('Creating test database')
        mysql_lib.create_db(instance, 'test')

        log.info('Setting up query response time plugins')
        mysql_lib.setup_response_time_metrics(instance)

        log.info('Setting up semi-sync replication plugins')
        mysql_lib.setup_semisync_plugins(instance)

        log.info('Setting up audit log plugin')
        mysql_lib.setup_audit_plugin(instance)

        log.info('Restarting pt daemons')
        host_utils.manage_pt_daemons(instance.port)

        log.info('MySQL initalization complete')

    finally:
        # We have to do this, ugly though it may be, to ensure that
        # the running MySQL process doesn't maintain a hold on the lock
        # socket after the script exits.  We reacquire the lock after
        # the restart and pass it back to the caller.
        #
        if lock_handle:
            log.info('Restarting MySQL, releasing lock.')
            host_utils.stop_mysql(instance.port)
            log.info('Sleeping 5 seconds.')
            time.sleep(5)
            host_utils.release_lock_socket(lock_handle)
            host_utils.start_mysql(instance.port)
            log.info('Reacquiring lock.')
            lock_handle = host_utils.bind_lock_socket(backup.STD_BACKUP_LOCK_SOCKET)

    if not skip_backup:
        log.info('Taking a backup')
        mysql_backup.mysql_backup(instance, initial_build=True,
                                  lock_handle=lock_handle)

    return lock_handle


def basic_host_sanity():
    """ Confirm basic sanity (mounts, etc) on localhost """
    if host_utils.get_pinfo_cloud() != host_utils.TESTING_PINFO_CLOUD:
        for path in host_utils.REQUIRED_MOUNTS:
            found = False
            for choice in path.split(':'):
                if os.path.ismount(choice):
                    found = True
                    break
            if not found:
                raise Exception('No acceptable options for {path} '
                                'are mounted'.format(path=path))

    for path in host_utils.ZK_CACHE:
        if not os.path.isfile(path):
            raise Exception('ZK updater path {path} '
                            'is not present'.format(path=path))

    if not os.path.isfile(MYSQL_INSTALL_DB):
        raise Exception('MySQL install script {script} is not present'
                        ''.format(script=mysql_init_server.MYSQL_INSTALL_DB))


def create_and_chown_dirs(port):
    """ Create and chown any missing directories needed for mysql """
    for variable in DIRS_TO_CREATE:
        try:
            path = os.path.dirname(host_utils.get_cnf_setting(variable, port))
        except ConfigParser.NoOptionError:
            # Not defined, so must not matter
            return
        if not os.path.isdir(path):
            log.info('Creating and chowning {path}'.format(path=path))
            os.makedirs(path)
            host_utils.change_owner(path, 'mysql', 'mysql')


def delete_mysql_data(port):
    """ Purge all data on disk for a MySQL instance

    Args:
    port - The port on which to act upon on localhost
    """
    for dir_key in DIRS_TO_CLEAR:
        directory = host_utils.get_cnf_setting(dir_key, port)
        if not os.path.isdir(directory):
            directory = os.path.dirname(directory)
        log.info('Removing contents of {dir}'.format(dir=directory))
        host_utils.clean_directory(directory)

    # This should not bomb if one of the files to truncate
    # isn't specified in the config file.
    for file_keys in FILES_TO_CLEAR:
        try:
            del_file = host_utils.get_cnf_setting(file_keys, port)
            log.info('Truncating {del_file}'.format(del_file=del_file))
            open(del_file, 'w').close()
            host_utils.change_owner(del_file, 'mysql', 'mysql')
        except Exception:
            log.warning('Option {f} not specified '
                        'in my.cnf - continuing.'.format(f=file_keys))


def delete_innodb_log_files(port):
    """ Purge ib_log files

    Args:
    port - the port on which to act on localhost
    """
    try:
        ib_logs_dir = host_utils.get_cnf_setting('innodb_log_group_home_dir',
                                                 port)
    except ConfigParser.NoOptionError:
        ib_logs_dir = host_utils.get_cnf_setting('datadir',
                                                 port)
    glob_path = os.path.join(ib_logs_dir, 'ib_logfile')
    final_glob = ''.join((glob_path, '*'))
    for del_file in glob.glob(final_glob):
        log.info('Clearing {del_file}'.format(del_file=del_file))
        os.remove(del_file)


def init_privileges_tables(port):
    """ Bootstap a MySQL instance

    Args:
    port - the port on which to act upon on localhost
    """
    version = mysql_lib.get_installed_mysqld_version()
    if version[0:3] < '5.7':
        install_command = MYSQL_INSTALL_DB
    else:
        install_command = MYSQL_INITIALIZE

    datadir = host_utils.get_cnf_setting('datadir', port)
    cmd = ('{MYSQL_INSTALL_DB} --datadir={datadir}'
           ' --user=mysql'.format(MYSQL_INSTALL_DB=install_command,
                                  datadir=datadir))
    log.info(cmd)
    (std_out, std_err, return_code) = host_utils.shell_exec(cmd)
    if return_code:
        raise Exception("Return {return_code} != 0 \n"
                        "std_err:{std_err}\n"
                        "std_out:{std_out}".format(return_code=return_code,
                                                   std_err=std_err,
                                                   std_out=std_out))


if __name__ == "__main__":
    main()