#!/usr/bin/perl
#####################################################################
# Function: pg-rex_slave_start
#
#
# 概要:
# PG-REX での Slave 起動実行ツール。
# 手順の簡易化を目的として作成している。
# 
# 特記事項:
# なし
#
# Copyright (c) 2012-2014, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
#####################################################################
use warnings;
use strict;
use Getopt::Long;
use lib qw(/usr/local/share/pg-rex);
use include::command;
use common qw(pacemaker_online pgrex_failed_action network_running master_running
              slave_running vip_running stonith_running ping_running diskd_running
              read_config read_cib read_hacf get_ssh_passwd exec_command 
              ssh_exec_command scp_exec_command get_pg_command_path check_user
              printlog get_node get_another_iclan_netaddr get_iclan_ipaddr
              rsync_exec_command check_support_version);

BEGIN {
    if ($ENV{'LANG'} =~ m/ja/i){
        eval qq{
            use po::ja;
        };
    }
    else{
        eval qq{
            use po::en;
        }
    }
};

$SIG{INT} = sub {
    printlog("LOG", SLAVESTART_MS0001);
};

main();

1;

sub main{
    my $help_mode = 0;
    my $version_mode = 0;
    my $nobasebackup_mode = 0;
    my $force_mode = 0;
    my $config_path = CONFIG_PATH.CONFIG_FILENAME;
    my %config_value;
    my $cib_path = CIB_PATH.CIB_FILENAME;
    my %master_cib_value;
    my $hacf_path = HACF_PATH.HACF_FILENAME;
    my %hacf_value;
    my $copy_dir = "/tmp";
    my $copy_cib_path = $copy_dir."/cib.xml";
    my %node_value;
    my %command_path;
    my $pg_command_user = "postgres";
    my $pg_xlog_dir;
    my $symbolic_link = 0;
    my $timeout = 300;
    my $monitor_delay = 10;
    my $monitor_interval = 2;
    my $wait_time = 0;
    my $lock_file = "";
    my $exit_code;
    my $ssh_pass;
    my $starting_resource = "";
    my @results;
    my $kill_when_no_data = 1;
    my $operation_num = 1;
    my $myself;
    my @another_iclan_netaddr = ();
    my %iclan_ipaddr;

    # 標準出力が途中で停止するのを防ぐ為に
    # 標準出力のオートフラッシュを有効化
    $| = 1;

    # 実行ユーザの確認
    check_user();

    # 環境設定ファイルの読み込み
    %config_value = read_config($config_path);

    # PostgreSQL のコマンドパスを取得
    %command_path = get_pg_command_path($config_value{'PGPATH'});

    # Pacemaker と PostgreSQL がサポート対象バージョンであるかを確認
    check_support_version($command_path{'postgres'});

    ### スクリプト実行準備 ###

    # オプション解析
    foreach ( @ARGV ){
        if ( "$_" eq "-" || "$_" eq "--" ){
            $help_mode = 1;
        }
    }
    $exit_code = GetOptions('help'         => \$help_mode,
                         'nobasebackup' => \$nobasebackup_mode,
                         'force'        => \$force_mode,
                         'version'      => \$version_mode);
    $myself = $0;
    $myself =~ s/.*\///g;
    if ($help_mode || !$exit_code){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        print "\n";
        printlog("USAGE", SLAVESTART_USAGE);
        exit(0);
    }
    if ($version_mode){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        exit(0);
	}

    # ssh 接続の為の情報の取得
    $ssh_pass = get_ssh_passwd($config_value{'Another_D-LAN_IPAddress'}, $config_value{'PEER_NODE_SSH_PASS_MODE'}, $config_value{'PEER_NODE_SSH_PASS_FILE'});
    my $ssh_info = new Ssh_info();
    $ssh_info->address("$config_value{'Another_D-LAN_IPAddress'}");
    $ssh_info->user("root");
    $ssh_info->pass("$ssh_pass");

    # コマンドを実行しているマシンのノード名ともう一台のノード名を取得
    %node_value = get_node($ssh_info);

    # corosync.conf の読み込み
    %hacf_value = read_hacf($hacf_path, "start");

    # 相手先のノードの corosync.conf に設定されている bindnetaddr を取得する 
    @another_iclan_netaddr = get_another_iclan_netaddr($hacf_path, $ssh_info);

    ### Pacemaker 起動の為の準備 ###
    # 自身のノードの Pacemaker 及び corosync の稼働確認
    printlog("LOG", SLAVESTART_MS0004, $operation_num++);
    if (pacemaker_running()){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0006);
    }
    printlog("LOG", SLAVESTART_MS0007);

    ### 稼働中の Master の存在確認 ###
    printlog("LOG", SLAVESTART_MS0008, $operation_num++);
    if (!master_running($node_value{'another_node'}, $config_value{'PG-REX_Master_ResourceID'}, $config_value{'PG-REX_Primitive_ResourceID'}, $ssh_info)){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0010);
    }
    printlog("LOG", SLAVESTART_MS0007);

    ### IC-LAN が接続されていることを確認 ###
    # 自身のノードと相手先のノードの IC-LAN IP アドレスを取得して接続確認を行う
    printlog("LOG", SLAVESTART_MS0011, $operation_num++);

    %iclan_ipaddr = get_iclan_ipaddr(\@{$hacf_value{iclan_netaddr}}, \@another_iclan_netaddr, \%node_value, $ssh_info);

    foreach my $ipaddr (@{$iclan_ipaddr{'another_node'}}){
        `$PING -c 5 $ipaddr`;
        $exit_code = $? >> 8;
        if ($exit_code){
            printlog("LOG", SLAVESTART_MS0005);
            printlog("ERROR", SLAVESTART_MS0012);
        }
    }
    printlog("LOG", SLAVESTART_MS0007);

    ### 起動禁止フラグの存在確認 ###
    printlog("LOG", SLAVESTART_MS0013, $operation_num++);

    # Master マシンにある cib.xml をコピーし読み込む
    scp_exec_command($ssh_info, $cib_path, $copy_dir);
    %master_cib_value = read_cib($copy_cib_path, $config_value{'PG-REX_Primitive_ResourceID'}, $kill_when_no_data, $config_path, $config_value{'ARCHIVE_DELETE_DBCLUSTER_DIR'});
    exec_command("$RM -f $copy_cib_path");

    # 起動禁止フラグが存在することを確認
    $lock_file = $master_cib_value{'tmpdir'}."/".LOCK_FILENAME;
    if (-e $lock_file){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0014, $lock_file);
    }

    printlog("LOG", SLAVESTART_MS0007);
    
    ### Master からベースバックアップ取得・展開 ###
    # ベースバックアップを取得・展開するかどうかをユーザに確認
    if (!$nobasebackup_mode){
        printlog("LOG", SLAVESTART_MS0015, $operation_num++);

        if (!$force_mode){
            printlog("LOG", SLAVESTART_MS0016);
            my $input = <STDIN>;
            chomp $input;
            if ($input =~ m/^n$/i){
                # 以降 --nobasebackup オプション付与時と同じ動作を行う
                $nobasebackup_mode = 1;
                printlog("LOG", SLAVESTART_MS0007);
            }
            elsif ($input !~ m/^y$/i){
                printlog("LOG", SLAVESTART_MS0017);
                exit(0);
            }
        }
    }

    # --nobasebackup オプションが指定されていなかったら、pg_basebackup 、pg_xlog のシンボリックリンク作成を行なう
    if (!$nobasebackup_mode){
        # Slave 側の DB クラスタを削除
        exec_command("$SU - $pg_command_user -c \"$RM -rf $master_cib_value{'pgdata'} \"");

        # Master の pg_xlog の場所を取得し、Slave の pg_xlog を削除
        # ※readlink コマンドは、引数に指定されたファイルにシンボリックリンクが存在する場合、コマンドの戻り値に"0"を返す
        # ※戻り値が"0"の場合のみ、pg_xlog を削除する
        @results = ssh_exec_command($ssh_info, "$READLINK $master_cib_value{'pgdata'}/pg_xlog ");
        $exit_code = $results[1];
        if ($exit_code != 0 && $exit_code != 1){
            printlog("LOG", SLAVESTART_MS0005);
            printlog("ERROR", SLAVESTART_MS0018);
        }
        elsif (!$exit_code){
            chomp $results[0];
            # pg_xlog format : ~/pg_xlog
            $results[0] =~ s/\/pg_xlog$//;
            $pg_xlog_dir = $results[0];
            exec_command("$SU - $pg_command_user -c \"$RM -rf $pg_xlog_dir/pg_xlog\"");
            $symbolic_link = 1;
        }

        exec_command("$SU - $pg_command_user -c \" $command_path{'pg_basebackup'} -h $master_cib_value{'master_ip'} -U $master_cib_value{'repuser'} -D $master_cib_value{'pgdata'} -X stream -P \"");

        # pg_xlog のシンボリックリンクを作成
        if ($symbolic_link){
            exec_command("$SU - $pg_command_user -c \"$MV $master_cib_value{'pgdata'}/pg_xlog $pg_xlog_dir \"");
            exec_command("$SU - $pg_command_user -c \"$LN -s $pg_xlog_dir/pg_xlog $master_cib_value{'pgdata'}/pg_xlog \"");
        }
        printlog("LOG", SLAVESTART_MS0007);
    }

    ### Master のアーカイブディレクトリと同期 ###
    # su コマンドとリモート先を指定する rsync コマンドを併用するとマシンによっては実行できないため、
    # ここでは root ユーザで実行する
    printlog("LOG", SLAVESTART_MS0019, $operation_num++);
    rsync_exec_command($ssh_info, $config_value{'Archive_dir'}."/", $config_value{'Archive_dir'}."/");
    printlog("LOG", SLAVESTART_MS0007);

    ### /var/lib/pacemaker/cib 配下のファイルの削除 ###
    my $cib_dir = CIB_PATH;
    $cib_dir =~ s/\/$//g;
    printlog("LOG", SLAVESTART_MS0020, $operation_num++, $cib_dir);
    $cib_dir = CIB_PATH;
    exec_command("$RM -f $cib_dir"."*");
    printlog("LOG", SLAVESTART_MS0007);

    ### Pacemaker 起動 ###
    printlog("LOG", SLAVESTART_MS0021, $operation_num++);
    exec_command("$INITCTL start pacemaker.combined");
    printlog("LOG", SLAVESTART_MS0007);

    ### Slave の起動確認 ###
    printlog("LOG", SLAVESTART_MS0022, $operation_num++);

    # Pacemaker 及び リソースの起動確認
    # 前回起動中に自ノードで PostgreSQL の制御エラーが発生していた場合、Pacemaker 起動から暫くの間は
    # crm_monの結果に PostgreSQL の制御エラー情報(Failed actions)が残っている可能性がある。そのため、
    # 起動確認の前にRA(pgsql) のモニタ間隔以上のディレイをおき、誤検知しないようにする。
    sleep $monitor_delay;
    while (1){
        if ($wait_time >= $timeout){
            printlog("LOG", SLAVESTART_MS0005);
            printlog("ERROR", SLAVESTART_MS0023, $timeout, $starting_resource);
        }
        sleep $monitor_interval;
        $wait_time += $monitor_interval;

        if (!pacemaker_online($node_value{'my_node'})){
            $starting_resource = "Pacemaker";
            next;
        }

        if (pgrex_failed_action($node_value{'my_node'}, $config_value{'PG-REX_Primitive_ResourceID'})){
            printlog("LOG", SLAVESTART_MS0005);
            printlog("ERROR", SLAVESTART_MS0024);
        }

        if (!network_running($node_value{'my_node'}, $node_value{'another_node'}, \%iclan_ipaddr)){
            $starting_resource = "IC-LAN monitor";
            next;
        }

        # PING のリソース ID 指定有りの場合、起動確認を行なう
        if ($config_value{'PING_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'PING_ResourceID'})){
            $starting_resource = "PING";
            next;
        }

        # DISKD のリソース ID 指定有りの場合、起動確認を行なう
        if ($config_value{'DISKD_ResourceID'} && !diskd_running($node_value{'my_node'}, $config_value{'DISKD_ResourceID'})){
            $starting_resource = "DISKD";
            next;
        }

        # STONITH 環境有りかつリソース ID 指定有りの場合、起動確認を行なう
        if ($config_value{'STONITH_ResourceID'} && !stonith_running($node_value{'my_node'}, $config_value{'STONITH_ResourceID'})){
            $starting_resource = "STONITH";
            next;
        }

        if (!slave_running($node_value{'my_node'}, $config_value{'PG-REX_Master_ResourceID'}, $config_value{'PG-REX_Primitive_ResourceID'})){
            $starting_resource = "PostgreSQL";
            next;
        }

        # VIP-SLAVE 環境有りかつリソース ID 指定有りの場合、起動確認を行なう
        if ($config_value{'VIP-SLAVE_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'VIP-SLAVE_ResourceID'})){
            $starting_resource = "VIP-SLAVE";
            next;
        }

        # crm_mon の結果が全て揃ったら無限ループを抜ける
        last;
    }

    # PostgreSQL のプロセス確認
    while(1){
        # postmaster process format : </bin/postgres -D (DB cluster)>
        # crm ファイルに登録してある DB クラスタを含む postmaster プロセスを検索
        # PostgreSQL の postmaster プロセスが存在したら無限ループを抜ける
        if (postgres_running($master_cib_value{'pgdata'})){
            last;
        }

        if ($wait_time >= $timeout){
            printlog("LOG", SLAVESTART_MS0005);
            printlog("ERROR", SLAVESTART_MS0025, $timeout);
        }
        sleep $monitor_interval;
        $wait_time += $monitor_interval;
    }

    # PostgreSQL の DB 接続確認
    `$SU - $pg_command_user -c \" $command_path{'psql'} -c 'SELECT 1' > /dev/null \" `;
    $exit_code = $? >> 8;
    if ($exit_code){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0026);
    }

    # Master の PostgreSQL の wal sender プロセスの確認
    @results = ssh_exec_command($ssh_info, "$PS aux | $GREP \"wal sender \" | $GREP -v \"grep\"");
    # wal sender process format : <postgres: wal sender process <repuser name>>
    if ($results[0] !~ /postgres:\swal\ssender\sprocess\s$master_cib_value{'repuser'}/){
        printlog("LOG", SLAVESTART_MS0005);
        printlog("ERROR", SLAVESTART_MS0027);
    }

    printlog("LOG", SLAVESTART_MS0007);

    printlog("LOG", SLAVESTART_MS0029, $node_value{'my_node'});
    exit(0);
}

