#!/usr/bin/env perl

# Use mandatory external modules
use strict;
use Cwd;
use File::Basename;
use Data::Dumper;
use POSIX;
use Config;
use Getopt::Long;

# Determine installation dir name
our $SELF_DIR = dirname(dirname(Cwd::abs_path(__FILE__)));

# Include parts of the system
require $SELF_DIR . '/lib/config.pm';
require $SELF_DIR . '/lib/log.pm';
require $SELF_DIR . '/lib/db.pm';

#-----------------------------------------------------------------
#Parse options

my $config_file = "mmm_lvm.conf";
my $host_name;
my $clone_mode;
my $copy_method;
my $dest_dir;

GetOptions( "config=s"      => \$config_file,
            "host=s"        => \$host_name,
            "clone-mode=s"  => \$clone_mode,
            "copy-method=s" => \$copy_method,
            "dest-dir=s"    => \$dest_dir);

#-----------------------------------------------------------------
# Read config file
our $config = ReadConfig($config_file);

# Directories info parsing
my @clone_dirs : shared = split(/\,/, $config->{clone_dirs});
$config->{clone_dirs} = \@clone_dirs;
$config->{dest_dir} = $dest_dir if ($dest_dir ne '');

# Get params
$copy_method = $config->{default_copy_method} unless ($copy_method);

# Check host name
my $clone_host = $config->{host}->{$host_name};
unless ($clone_host) {
    print "Error: Invalid host name!\n\n";
    PrintUsage();
}

if ($host_name eq $config->{this}) {
    print "Error: Invalid host name: We can't clone ourselves!\n\n";
    PrintUsage();
}

# Check clone mode
if ($clone_mode ne 'slave-slave' && $clone_mode ne 'master-slave' && $clone_mode ne 'master-master') {
    print "Error: Unknown clone mode: $clone_mode\n\n";
    PrintUsage();
}

# Check copy method
my $method = $config->{copy_method}->{$copy_method};
unless ($method) {
    print "Error: Invalid copy method!\n\n";
    PrintUsage();
}

LogNotice("Host: '$host_name'");
LogNotice("Clone mode: '$clone_mode'");
LogNotice("Copy method: '$copy_method'");
LogNotice("Destination: '$config->{dest_dir}'");
LogNotice("Dirs to clone: '" . join(', ', @clone_dirs) . "'");

#-----------------------------------------------------------------
# Create config shortcuts
my $this = $config->{this};
my $host = $config->{host}->{$this};
my $my_cnf = $host->{my_cnf};

if (!$my_cnf) {
    LogWarn("Warning: No my.cnf file defined for $this host. Using default location...");
    $my_cnf = "/etc/my.cnf" 
}

LogNotice("-----------------------------------------------------------------");
# Check local mysql process and shutdown it if it is running
my $res = ShutdownLocalMysql();
unless ($res =~ /^OK/) {
    LogError("Can't stop local mysql daemon: $res");
    exit(1);
}

LogNotice("-----------------------------------------------------------------");
# Executing copying process
my $res = system("$SELF_DIR/sbin/mmm_get_dump --host $host_name --copy-method $copy_method --dest-dir $config->{dest_dir}");
if ($res) {
    LogError("Error: Can't copy data from remote server");
    exit(1);
}


LogNotice("-----------------------------------------------------------------");
#Loading status info from data dir
my $status = {};
my $res = LoadStatusInfo(\$status);
unless ($res =~ /^OK/) {
    LogError("Error: Can't load status info from dumped data dir: $res");
    exit(1);
}
LogDebug("Status: " . Dumper($status));

LogNotice("-----------------------------------------------------------------");
# Cleaning dump from master.info and binary logs
my $res = CleanupDump();
unless ($res =~ /^OK/) {
    LogError("Error: Can't cleanup dump from binary logs: $res");
    exit(1);
}

#-----------------------------------------------------------------
my $master_info = {};
$master_info->{'master_user'} = $config->{'replication_user'};
$master_info->{'master_pass'} = $config->{'replication_password'};

if ($clone_mode eq 'slave-slave') {
    $master_info->{'master_host'} = $status->{slave}->{'Master_Host'};
    $master_info->{'master_port'} = $status->{slave}->{'Master_Port'};
    $master_info->{'master_log'} = $status->{slave}->{'Relay_Master_Log_File'};
    $master_info->{'master_pos'} = $status->{slave}->{'Exec_Master_Log_Pos'};
}

if ($clone_mode eq 'master-slave' || $clone_mode eq 'master-master') {
    $master_info->{'master_host'} = $clone_host->{'mysql_host'};
    $master_info->{'master_port'} = $clone_host->{'mysql_port'};
    $master_info->{'master_log'} = $status->{master}->{'File'};
    $master_info->{'master_pos'} = $status->{master}->{'Position'};
}

LogDebug("Master info: " . Dumper($master_info));

#FIXME: need to update my_cnf to remove replication options

LogNotice("-----------------------------------------------------------------");
# Run local local mysql process
my $res = StartLocalMysql();
unless ($res =~ /^OK/) {
    LogError("Can't start local mysql daemon: $res");
    exit(1);
}

LogNotice("-----------------------------------------------------------------");

my $res = ChangeMasterTo($master_info);
unless ($res =~ /^OK/) {
    LogError("Error: Can't set master for local server: $res");
    exit(1);
}

if ($clone_mode eq 'master-master') {
    my $res = ChangePeerMasterTo();
    unless ($res =~ /^OK/) {
        LogError("Error: Can't set master for peer server: $res");
        exit(1);
    }
}

LogNotice("-----------------------------------------------------------------");
LogNotice("Clone process done!\n\n\n");

exit(0);

#-----------------------------------------------------------------
sub PrintUsage() {
    print "Usage: $0 [--config <config file>] --host <host> --clone-mode <mode> [--copy-method <method>] [--dest-dir <dir>]\n";
    print "Where:\n";
    print "  Host = " . join('|', grep {$_ ne $config->{this}} keys(%{$config->{host}})) . "\n";
    print "  Clone mode: slave-slave|master-slave|master-master\n";
    print "  Copy method = " . join('|', keys(%{$config->{copy_method}})) . " (default = $config->{default_copy_method})\n\n";
    exit(1);
}

#-----------------------------------------------------------------
sub ShutdownLocalMysql() {
    my $pid_file = $host->{pid_file};
    
    # Check pid file
    if ($pid_file eq "") {
        LogWarn("Warning: Can't find pid-file option in config file.");
        $pid_file = "/var/run/mysqld/mysqld.pid";
    }
    
    my $cnt;
    my $pid;
    if (-f $pid_file) {
        # Read mysql pid
        open(PID, $pid_file) || return "ERROR: Can't read mysql pid file ($pid_file)";
        chomp($pid = <PID>);
        close(PID);
    
        # Check process
        $cnt = kill(0, $pid);
    }
    
    if ($cnt == 0) {
        LogWarn("MySql is not running now, skipping shutdown step...");
        return  "OK: Mysql is not running";
    }
    
    LogNotice("MySql is running now. Going to stop it...");
    
    #Stop mysql
    my $rc_script = $host->{rc_script};
    $rc_script = "/etc/init.d/mysql" if ($rc_script eq "");
    
    my $res = system($rc_script, "stop");
    if ($res) {
        return "ERROR: Can't stop local MySql server!";
    }

    # Check process
    my $wait = 15;
    LogDebug("Waiting mysql process with $pid to shutdown: ");
    while ($wait--) {
        $cnt = kill(0, $pid);
        last if ($cnt == 0);
        LogDebug(".");
        sleep(1);
    }

    if ($cnt != 0) {
        PrintError("MySql Shutdown Failed!");
        return "ERROR: MySql is running with PID $pid after shutdown request!";
    }
    
    LogDebug("Mysql Shutdown done: OK");
    return "OK: MySql server is down now!";
}

#-----------------------------------------------------------------
sub LoadStatusInfo($) {
    my $status_info = shift;
    
    LogDebug("Loading status info...");
    unless ($config->{dest_dir} && -d $config->{dest_dir}) {
        return "ERROR: Destination dir ($config->{dest_dir}) does not exists!";
    }

    my $status_file = $config->{dest_dir} . "/_mmm/status.txt";
    unless (-f $status_file && -r $status_file) {
        return "ERROR: Status file ($status_file) does not exists or is not readable!";
    }
    
    my $status_data = `cat $status_file`;

    my $VAR1;
    eval($status_data);
    if ($@) {
        return "ERROR: Can't parse status info: $@";
    }
    
    $$status_info = $VAR1;
    return "OK!";
}

#-----------------------------------------------------------------
sub StartLocalMysql() {
    # Get pid file location
    my $pid_file = $host->{pid_file};

    # Check pid file
    if ($pid_file eq "") {
        LogWarn("Warining: Can't find pid-file option in config file.");
        $pid_file = "/var/run/mysqld/mysqld.pid";
    }
    
    if (-f $pid_file) {
        # Read mysql pid
        open(PID, $pid_file) || return "ERROR: Can't read mysql pid file ($pid_file)";
        chomp(my $pid = <PID>);
        close(PID);
    
        # Check process
        my $cnt = kill(0, $pid);
        if ($cnt) {
            return "ERROR: Local mysql is running now with pid = $pid!";
        }
    }
    
    LogNotice("MySql is not running now. Going to start it...");
    
    # Start mysql
    my $rc_script = $host->{rc_script};
    $rc_script = "/etc/init.d/mysql" if ($rc_script eq "");
    
    my $res = system($rc_script, "start");
    if ($res) {
        return "ERROR: Can't start local MySql server!";
    }
    
    return "OK: MySql server has been started!";
}

#-----------------------------------------------------------------
sub CleanupDump() {
    LogNotice("Cleaning dump from master.info and binary logs...");
    
    my $master_log = $status->{master}->{File};
    unless ($master_log =~ /^(.*)\.(\d+)$/) {
        return "ERROR: Unknown master binary log file name format ($master_log)!";
    }
    
    LogDebug("Deleting master binary logs: $1.*");
    system("find $config->{dest_dir} -name '$1.*' | xargs rm -vf");
    
    if ($status->{slave} && $status->{slave}->{Relay_Log_File} ne '') {
        my $slave_log = $status->{slave}->{Relay_Log_File};
        unless ($slave_log =~ /^(.*)\.(\d+)$/) {
            return "ERROR: Unknown relay binary log file name format ($slave_log)!";
        }
        LogDebug("Deleting relay binary logs: $1.*");
        system("find $config->{dest_dir} -name '$1.*' | xargs rm -vf");
    }
    
    
    LogDebug("Deleting .info and .pid  files...");
    system("find $config->{dest_dir} -name master.info | xargs rm -vf");
    system("find $config->{dest_dir} -name relay-log.info | xargs rm -vf");
    system("find $config->{dest_dir} -name '*.pid' | xargs rm -vf");
    
    LogDebug("Changing permissions on mysql data dir...");
    system("chown -R mysql:mysql $config->{dest_dir}");
    
    return "OK";
}

#-----------------------------------------------------------------
sub ChangeMasterTo($) {
    my $master = shift;

    LogNotice("Changing master info for local server...");

    # get connection info
    my $my_host = $host->{mysql_host};
    my $my_port = $host->{mysql_port};
    my $my_user = $host->{mysql_user};
    my $my_pass = $host->{mysql_password};

    my $dbh = MysqlConnect($my_host, $my_port, $my_user, $my_pass);
    if (!$dbh) {
        return "ERROR: Can't connect to local mysql (host = $my_host:$my_port, user = $my_user)!";
    }

    # Stop slave
    my $res = ExecuteQuery($dbh, "STOP SLAVE");
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);

    # Change master
    my $sql = "CHANGE MASTER TO " .
              "  MASTER_HOST='$master->{master_host}'," .
              "  MASTER_PORT=$master->{master_port}," .
              "  MASTER_USER='$master->{master_user}'," .
              "  MASTER_PASSWORD='$master->{master_pass}'," .
              "  MASTER_LOG_FILE='$master->{master_log}'," .
              "  MASTER_LOG_POS=$master->{master_pos}";
    my $res = ExecuteQuery($dbh, $sql);
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);
    
    # Start slave
    $res = ExecuteQuery($dbh, "START SLAVE");
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);

    # disconnect
    $dbh->disconnect;

    return "OK";
}

#-----------------------------------------------------------------
sub ChangePeerMasterTo() {
    LogNotice("Changing master info for peer server...");

    my $peer_host = $config->{host}->{$host_name};
    
    # get connection info
    my $my_host = $peer_host->{mysql_host};
    my $my_port = $peer_host->{mysql_port};
    my $my_user = $peer_host->{mysql_user};
    my $my_pass = $peer_host->{mysql_password};

    my $dbh = MysqlConnect($my_host, $my_port, $my_user, $my_pass);
    if (!$dbh) {
        return "ERROR: Can't connect to local mysql (host = $my_host:$my_port, user = $my_user)!";
    }

    # Stop slave
    my $res = ExecuteQuery($dbh, "STOP SLAVE");
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);

    # Force deletion of obsolete master.info, relay-log.info and relay logs.
    my $res = ExecuteQuery($dbh, "RESET SLAVE");
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);

    # Change master
    my $sql = "CHANGE MASTER TO " .
              "  MASTER_HOST='$host->{mysql_host}'," .
              "  MASTER_PORT=$host->{mysql_port}," .
              "  MASTER_USER='$config->{replication_user}'," .
              "  MASTER_PASSWORD='$config->{replication_password}'";
    
    my $res = ExecuteQuery($dbh, $sql);
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);
    
    # Start slave
    $res = ExecuteQuery($dbh, "START SLAVE");
    return "ERROR: SQL Query Error: " . $dbh->errstr unless($res);

    # disconnect
    $dbh->disconnect;

    return "OK";
}
