#!/usr/bin/perl
#####################################################################
# Function: pg-rex_dbcluster_check
#
#
# 概要:
# PG-REX の Slave 起動時には
# ベースバックアップの取得・展開が必要かどうかを確認する必要がある。
# その確認を自動的に行うツール。
# 
# 特記事項:
# なし
#
# Copyright (c) 2012-2014, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
#####################################################################
use warnings;
use strict;
use Getopt::Long;
use File::Compare;
use lib qw(/usr/local/share/pg-rex);
use include::command;
use common qw(master_running read_config read_cib exec_command
              get_ssh_passwd ssh_exec_command scp_exec_command
              get_xlog_filename get_controldata_value get_node
              get_pg_version get_pg_command_path check_user
              printlog check_support_version compare_lsn);

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

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

main();

1;

sub main{
    my $help_mode = 0;
    my $version_mode = 0;
    my $config_path = CONFIG_PATH.CONFIG_FILENAME;
    my %config_value;
    my $cib_path = CIB_PATH.CIB_FILENAME;
    my %my_cib_value;
    my %node_value;
    my %command_path;
    my $pg_command_user = "postgres";
    my $ssh_pass;
    my $postgresql_conf;
    my $archive_dir;
    my $lock_file;
    my $result;
    my @results;
    my $exit_code;
    my $kill_when_no_data = 1;
    my $operation_num = 1;
    my $myself;
    my $my_current_xlog_filename;
    my $another_current_xlog_filename;
    my $my_current_xlog_location;
    my $another_current_xlog_location;

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

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

    # 環境設定ファイルの読み込み
    # DBCHECK_MS0003 を出力する前であるが、PostgreSQL のバージョンチェックに
    # 環境設定ファイルの設定情報が必要なため、この時点で読み込む
    %config_value = read_config($config_path);
    $archive_dir = $config_value{'Archive_dir'};

    # 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,
                         'version' => \$version_mode);
	$myself = $0;
	$myself =~ s/.*\///g;
    if ($help_mode || !$exit_code){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        print "\n";
        printlog("USAGE", DBCHECK_USAGE);
        exit(0);
    }
    if ($version_mode){
        printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM);
        exit(0);
	}

    ### 動作環境の確認 ###
    printlog("LOG", DBCHECK_MS0002);

    # 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");

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

    # コマンド実行ノードの Pacemaker の稼働確認
    printlog("LOG", DBCHECK_MS0006, $operation_num++);
    if (pacemaker_running()){
        printlog("LOG", DBCHECK_MS0007);
        printlog("ERROR", DBCHECK_MS0008);
    }
    printlog("LOG", DBCHECK_MS0005);

    printlog("LOG", DBCHECK_MS0009, $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", DBCHECK_MS0007);
        printlog("ERROR", DBCHECK_MS0011);
    }
    printlog("LOG", DBCHECK_MS0005);

    ### 以降確認を行う ###
    printlog("LOG", DBCHECK_MS0012);
    $operation_num = 1;

    ### 確認1「初回起動時の場合」 ###
    printlog("LOG", DBCHECK_MS0013, $operation_num++);
    if (! -f $cib_path){
        printlog("LOG", DBCHECK_MS0014, $cib_path);
        ng_result();
    }
    printlog("LOG", DBCHECK_MS0005);

    ### 確認2「 DB クラスタが存在しない場合」 ###
    #  (STEP1) cib.xml ファイル読み込み
    printlog("LOG", DBCHECK_MS0015, $operation_num++);
    %my_cib_value = read_cib($cib_path, $config_value{'PG-REX_Primitive_ResourceID'}, $kill_when_no_data, $config_path, $config_value{'ARCHIVE_DELETE_DBCLUSTER_DIR'});

    #  (STEP2) DB クラスタの中のpostgresql.confの有無を確認
    $postgresql_conf = $my_cib_value{'pgdata'}."/postgresql.conf";
    if (! -f $postgresql_conf){
        printlog("LOG", DBCHECK_MS0017, $postgresql_conf);
        ng_result();
    }

    #  (STEP3) DB クラスタのバージョンと PostgreSQL サーバのバージョンが一致していることを確認
    #  DB クラスタのバージョンチェックについては、自身のノードと相手ノードの DB クラスタのバージョンが
    #  一致しないケースも考えられるが、PG-REXの運用上起こり得ないケースであるため当該ケースはチェックしない。
    #  なお、運用補助ツールにおける PostgreSQL のバージョンチェックの方針は以下とする。
    #   (a) 運用補助ツールの機能に必要なチェックのみ行う
    #   (b) PG-REXの運用上起こり得ない環境(状態)まで考慮しない
    #   (c) PostgreSQL は RPM によりインストールされた環境を前提とする
    my $pg_version_file = $my_cib_value{'pgdata'}."/PG_VERSION";
    my $dbcluster_version = `$SU $pg_command_user -c "$CAT $pg_version_file"`;
    chomp $dbcluster_version;
    if (! $dbcluster_version) {
        printlog("LOG", DBCHECK_MS0036);
        ng_result();
    }
    my @version_factor = split(/\./, get_pg_version($command_path{'postgres'}));
    my $pg_major_version = $version_factor[0].".".$version_factor[1];
    if ($dbcluster_version ne $pg_major_version){
            printlog("LOG", DBCHECK_MS0037, $dbcluster_version, $pg_major_version);
            ng_result();
    }

    # 両系の pg_controldata から以降のチェックに使用する情報を取得
    # pg_controldata は "C" ロケールにしてから実行
    $result = exec_command("$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $my_cib_value{'pgdata'}\"");
    my @my_controldata_strings = split(/\n/, $result);
    my %my_controldata_value = get_controldata_value(@my_controldata_strings);

    @results = ssh_exec_command($ssh_info, "$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $my_cib_value{'pgdata'}\"");
    my @another_controldata_strings = split(/\n/, $results[0]);
    my %another_controldata_value = get_controldata_value(@another_controldata_strings);
    
    printlog("LOG", DBCHECK_MS0005);
    
    # 自身のノードの DB クラスタの最新チェックポイント日時を出力
    printlog("LOG", DBCHECK_MS0029, $my_controldata_value{'checkpoint_time'});

    ### 確認3「起動禁止フラグが存在する場合」 ###
    printlog("LOG", DBCHECK_MS0018, $operation_num++);
    $lock_file = $my_cib_value{'tmpdir'}."/PGSQL.lock";
    if (-f $lock_file){
        printlog("LOG", DBCHECK_MS0019, $lock_file);
        ng_result();
    }
    printlog("LOG", DBCHECK_MS0005);

    ### 確認4「 DB クラスタが非常に古い場合」 ###
    # ※確認せず※

    ### 確認5「稼働中の Master とレプリケーション接続が確立できない状態である場合」 ###
    printlog("LOG", DBCHECK_MS0020, $operation_num++);

    #  (STEP1) データベース識別子を比較
    if ($my_controldata_value{'database'} ne $another_controldata_value{'database'}){
        printlog("LOG", DBCHECK_MS0021, $my_controldata_value{'database'}, $another_controldata_value{'database'});
        ng_result();
    }

    #  (STEP2) TLI を比較
    if ($my_controldata_value{'time_line_id'} > $another_controldata_value{'time_line_id'}){
        printlog("LOG", DBCHECK_MS0022, $my_controldata_value{'time_line_id'}, $another_controldata_value{'time_line_id'});
        ng_result();
    }

    #  (STEP3) XLOG の位置を比較
    #  両系における現在のWALファイル名を特定する
    my $command = "$LS -l $my_cib_value{'pgdata'}/pg_xlog/ | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1";
    $result = exec_command($command);
    if ($result =~ /.*([0-9A-F]{24})$/){
            $my_current_xlog_filename = $1;
    } else {
        printlog("ERROR", DBCHECK_MS0032, $my_cib_value{'pgdata'}."/pg_xlog");
    }

    @results = ssh_exec_command($ssh_info, $command);
    if ($results[0] =~ /.*([0-9A-F]{24})$/){
            $another_current_xlog_filename = $1;
    } else {
        printlog("ERROR", DBCHECK_MS0033, $my_cib_value{'pgdata'}."/pg_xlog");
    }

    # 両系における最新チェックポイントの REDO 位置から WAL ファイル名を算出する
    my $my_redo_xlog_filename = get_xlog_filename($my_controldata_value{'xlog_location'}, $my_controldata_value{'time_line_id'});
    my $another_redo_xlog_filename = get_xlog_filename($another_controldata_value{'xlog_location'}, $another_controldata_value{'time_line_id'});

    # pg_xlogdump の出力結果から現在の XLOG 位置を取得する
    $command = "$SU - $pg_command_user -c \"$command_path{'pg_xlogdump'} $my_cib_value{'pgdata'}/pg_xlog/$my_redo_xlog_filename $my_cib_value{'pgdata'}/pg_xlog/$my_current_xlog_filename 2> /dev/null | $TAIL -1\"";
    $result = exec_command($command);
    if ($result =~ /.*lsn:\s+([0-9A-F\/]+).*/){
        $my_current_xlog_location = $1;
    }

    $command = "$SU - $pg_command_user -c \"$command_path{'pg_xlogdump'} $my_cib_value{'pgdata'}/pg_xlog/$another_redo_xlog_filename $my_cib_value{'pgdata'}/pg_xlog/$another_current_xlog_filename 2> /dev/null | $TAIL -1\"";
    @results = ssh_exec_command($ssh_info, $command);
    if ($results[0] =~ /.*lsn:\s+([0-9A-F\/]+).*/){
        $another_current_xlog_location = $1;
    }
    
    #  取得した XLOG 位置を比較する(自身のノードの XLOG 位置の方が進んでいないか確認する)
    $result = compare_lsn($my_current_xlog_location, $another_current_xlog_location);
    if ($result > 0){
        printlog("LOG", DBCHECK_MS0023, $my_current_xlog_location, $another_current_xlog_location);
        ng_result();
    }

    #  (STEP4) 相手ノードのアーカイブから、自身のノードの TLI の時系列履歴ファイルを取得
    my $my_tli_historyfile = sprintf("%08X.history", $my_controldata_value{'time_line_id'});
    my $my_tli_history_path = $archive_dir ."/".$my_tli_historyfile;
    my $copy_dir = "/tmp/";
    @results = ssh_exec_command($ssh_info, "$LS -l $my_tli_history_path 2> /dev/null");
    if ($results[0] eq "") {
        printlog("ERROR", DBCHECK_MS0035, $my_tli_history_path);
    }
    scp_exec_command($ssh_info, $my_tli_history_path, $copy_dir);

    #  (STEP5) 自身ノードの TLI の時系列履歴ファイルを比較
    my $copy_tli_historyfile = $copy_dir.$my_tli_historyfile;
    $result = `$LS -l $my_tli_history_path 2> /dev/null`;
    if ($result eq ""){
        printlog("ERROR", DBCHECK_MS0034, $my_tli_history_path);
    }
    if (compare($my_tli_history_path, $copy_tli_historyfile)){
        printlog("LOG", DBCHECK_MS0024, $my_tli_history_path);
        ng_result();
    }

    #  (STEP5後処理) /tmp 配下にコピーした時系列履歴ファイルの削除
    exec_command("$RM -f $copy_tli_historyfile");

    #  STEP6 以降は STEP2 で両系の TLI が同じでないときに確認する
    if ($my_controldata_value{'time_line_id'} < $another_controldata_value{'time_line_id'}){

        #  相手ノードの TLI の時系列ファイルを内容を見て確認する
        #    (STEP6) 相手ノードのアーカイブから、相手ノードの TLI の時系列履歴ファイルを取得
        my $another_tli_historyfile = sprintf("%08X.history", $another_controldata_value{'time_line_id'});
        my $another_tli_history_path = $archive_dir ."/".$another_tli_historyfile;
        @results = ssh_exec_command($ssh_info, "$CAT $another_tli_history_path");

        #    (STEP7) 自身ノードの TLI が過去に通っていることを確認
        my $my_tli_promote_xloglocation = '';
        my @another_tli_history_strings = split(/\n/, $results[0]);

        foreach my $line (@another_tli_history_strings){
            if ($line =~ /^(\d+)\s+([0-9A-F\/]+)/){
                 if ($my_controldata_value{'time_line_id'} eq $1){
                     $my_tli_promote_xloglocation = $2;
                 }
            }
        }
        
        if (!$my_tli_promote_xloglocation){
            printlog("LOG", DBCHECK_MS0025, $another_tli_historyfile);
            ng_result();
        }

        #    (STEP8) 自身ノードの XLOG の位置よりも先に promote されていないことを確認
        $result = compare_lsn($my_current_xlog_location, $my_tli_promote_xloglocation);
        if ($result >= 0){
            printlog("LOG", DBCHECK_MS0026, $another_tli_historyfile);
            ng_result();
        }
    }
    
    printlog("LOG", DBCHECK_MS0005);

    # ベースバックアップ取得不要を返却し、スクリプトを終了する
    printlog("LOG", DBCHECK_MS0027);
    exit(0);
}


sub ng_result {

    printlog("LOG", DBCHECK_MS0007);
    printlog("ERROR", DBCHECK_MS0028);

}

