From 3467a288872a13ecb195c14ce5f3f9801d0ca1fe Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi Date: Thu, 7 Mar 2024 17:02:55 +0900 Subject: [PATCH] pg-rex operation tool 16.1 --- pg-rex_operation_tools/INSTALL | 39 + pg-rex_operation_tools/LICENSE | 26 + pg-rex_operation_tools/MANIFEST | 18 + pg-rex_operation_tools/Makefile.PL | 39 + .../bin/pg-rex_archivefile_delete | 448 +++++ .../bin/pg-rex_primary_start | 384 ++++ .../bin/pg-rex_standby_start | 1654 +++++++++++++++++ pg-rex_operation_tools/bin/pg-rex_stop | 208 +++ pg-rex_operation_tools/bin/pg-rex_switchover | 562 ++++++ .../lib.in/command-rhel8.pm | 59 + pg-rex_operation_tools/lib/PGRex/Po/en.pm | 389 ++++ pg-rex_operation_tools/lib/PGRex/Po/ja.pm | 395 ++++ pg-rex_operation_tools/lib/PGRex/common.pm | 1350 ++++++++++++++ pg-rex_operation_tools/man/Makefile | 19 + .../man/html/pg-rex_tools_manual-ja.html | 1148 ++++++++++++ .../man/pg-rex_tools_manual-ja.md | 693 +++++++ .../man/pg-rex_tools_manual.css | 607 ++++++ pg-rex_operation_tools/pg-rex_tools.conf | 77 + 18 files changed, 8115 insertions(+) create mode 100644 pg-rex_operation_tools/INSTALL create mode 100644 pg-rex_operation_tools/LICENSE create mode 100644 pg-rex_operation_tools/MANIFEST create mode 100644 pg-rex_operation_tools/Makefile.PL create mode 100644 pg-rex_operation_tools/bin/pg-rex_archivefile_delete create mode 100644 pg-rex_operation_tools/bin/pg-rex_primary_start create mode 100644 pg-rex_operation_tools/bin/pg-rex_standby_start create mode 100644 pg-rex_operation_tools/bin/pg-rex_stop create mode 100644 pg-rex_operation_tools/bin/pg-rex_switchover create mode 100644 pg-rex_operation_tools/lib.in/command-rhel8.pm create mode 100644 pg-rex_operation_tools/lib/PGRex/Po/en.pm create mode 100644 pg-rex_operation_tools/lib/PGRex/Po/ja.pm create mode 100644 pg-rex_operation_tools/lib/PGRex/common.pm create mode 100644 pg-rex_operation_tools/man/Makefile create mode 100644 pg-rex_operation_tools/man/html/pg-rex_tools_manual-ja.html create mode 100644 pg-rex_operation_tools/man/pg-rex_tools_manual-ja.md create mode 100644 pg-rex_operation_tools/man/pg-rex_tools_manual.css create mode 100644 pg-rex_operation_tools/pg-rex_tools.conf diff --git a/pg-rex_operation_tools/INSTALL b/pg-rex_operation_tools/INSTALL new file mode 100644 index 0000000..f01b62e --- /dev/null +++ b/pg-rex_operation_tools/INSTALL @@ -0,0 +1,39 @@ + =============================================================== + PG-REX 運用補助ツールをソースセットからインストールする方法 + =============================================================== + +PG-REX 運用補助ツールをソースセットからインストールする方法を記載します。 + +1) IO-Tty-1.11 と Net-OpenSSH-0.62 を以下の URL からダウンロードします + http://search.cpan.org/dist/IO-Tty/ + http://search.cpan.org/dist/Net-OpenSSH/ + + +2) IO-Tty-1.11 を解凍しインストールします + # tar zxvf IO-Tty-1.11.tar.gz + + # cd IO-Tty-1.11 + # perl Makefile.PL + # make + # make install + + +3) Net-OpenSSH-0.62 を解凍しインストールします + # tar zxvf Net-OpenSSH-0.62.tar.gz + + # cd Net-OpenSSH-0.62 + # perl Makefile.PL + # make + # make install + + +4) PG-REX 運用補助ツール一式を解凍し配置します + # tar zxvf PG-REX_operation_tools-.tar.gz + + # cd pg-rex_operation_tools- + # perl Makefile.PL + # make + # make pure_install + + +以上 diff --git a/pg-rex_operation_tools/LICENSE b/pg-rex_operation_tools/LICENSE new file mode 100644 index 0000000..b149a49 --- /dev/null +++ b/pg-rex_operation_tools/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the NIPPON TELEGRAPH AND TELEPHONE CORPORATION + (NTT) nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pg-rex_operation_tools/MANIFEST b/pg-rex_operation_tools/MANIFEST new file mode 100644 index 0000000..f2a9f98 --- /dev/null +++ b/pg-rex_operation_tools/MANIFEST @@ -0,0 +1,18 @@ +bin/pg-rex_archivefile_delete +bin/pg-rex_primary_start +bin/pg-rex_standby_start +bin/pg-rex_stop +bin/pg-rex_switchover +INSTALL +lib.in/command-rhel8.pm +lib/PGRex/command.pm +lib/PGRex/common.pm +lib/PGRex/Po/en.pm +lib/PGRex/Po/ja.pm +LICENSE +Makefile.PL +man/pg-rex_tools_manual.css +man/pg-rex_tools_manual-ja.md +man/html/pg-rex_tools_manual-ja.html +MANIFEST This list of files +pg-rex_tools.conf diff --git a/pg-rex_operation_tools/Makefile.PL b/pg-rex_operation_tools/Makefile.PL new file mode 100644 index 0000000..03f49e9 --- /dev/null +++ b/pg-rex_operation_tools/Makefile.PL @@ -0,0 +1,39 @@ +use strict; +use File::Copy "cp"; +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. + +my $release; +my $lib_command; + +if ($^O ne 'linux') { + die("$^O OS unsupported"); +} + +$release = `cat /etc/redhat-release 2> /dev/null` + or die("unsupported linux distribution other than Redhat"); + +if ($release !~ /Red Hat Enterprise Linux release (\d+)/ && + $release !~ /CentOS (?:Linux )?release (\d+)/) { + die("can't get RedHat Linux release"); +} +if (($1 eq '8') || ($1 eq '9')) { + $lib_command = 'lib.in/command-rhel8.pm'; +} +else { + die("RedHat Linux $1 unsupported"); +} + +cp($lib_command, 'lib/PGRex/command.pm') + or die("copy failed ($lib_command): $!"); + +WriteMakefile( + NAME => 'pg-rex_operation_tools', + VERSION => '16.0', + PREREQ_PM => { 'Net::OpenSSH' => 0.62, 'IO::Tty' => 1.11 }, + PREFIX => '/usr/local', + INST_BIN => 'bin', + INST_LIB => 'lib', + INST_MAN1DIR => 'man/html' +); diff --git a/pg-rex_operation_tools/bin/pg-rex_archivefile_delete b/pg-rex_operation_tools/bin/pg-rex_archivefile_delete new file mode 100644 index 0000000..32f9edc --- /dev/null +++ b/pg-rex_operation_tools/bin/pg-rex_archivefile_delete @@ -0,0 +1,448 @@ +#!/usr/bin/perl +##################################################################### +# Function: pg-rex_archivefile_delete +# +# +# 概要: +# PG-REX でのアーカイブ削除用の実行ツール。 +# 手順の簡易化を目的として作成している。 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use sigtrap qw(die normal-signals error-signals); +use Getopt::Long; +use PGRex::command; +use PGRex::common qw(read_config read_cib exec_command get_ssh_passwd + ssh_exec_command get_recoverywal get_node + get_pg_command_path printlog check_support_version); + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + } + } +}; + +$SIG{INT} = sub { + printlog("LOG", ARCHDELETE_MS0001); +}; + +main(); + +1; + +sub main{ + my $help_mode = 0; + my $version_mode = 0; + my $force_mode = 0; + my $mv_mode = 0; + my $rm_mode = 0; + my $dbcluster_mode = 0; + my $config_path = CONFIG_PATH.CONFIG_FILENAME; + my %config_value; + my $cib_path = CIB_PATH.CIB_FILENAME; + my %my_cib_value; + my $exec_user; + my %node_value; + my %command_path; + my $pg_command_user = "postgres"; + my $ssh_pass; + my $ssh_pass_backup_node; + my $input_node; + my $input_path; + my $dbcluster_path = ""; + my $backup_pgdata; + my $backup_node; + my $backup_path; + my $archive_dir; + my $result; + my @results; + my $exit_code; + my $kill_when_no_data = 1; + my $myself; + my $input; + + # 標準出力が途中で停止するのを防ぐ為に + # 標準出力のオートフラッシュを有効化 + $| = 1; + + # オプション解析 + foreach ( @ARGV ){ + if ( "$_" eq "-" || "$_" eq "--" ){ + $help_mode = 1; + } + } + $exit_code = GetOptions('help' => \$help_mode, + 'force' => \$force_mode, + 'move' => \$mv_mode, + 'remove' => \$rm_mode, + 'dbcluster=s' => \$dbcluster_mode, + 'additional_information' => \$PGRex::common::additional_information_mode, + 'version' => \$version_mode); + + $backup_pgdata = shift(@ARGV); + $myself = $0; + $myself =~ s/.*\///g; + + if ($help_mode || (!$mv_mode && !$rm_mode && !$version_mode) || !$exit_code){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + print "\n"; + printlog("USAGE", ARCHDELETE_USAGE); + exit(0); + } + + if ($version_mode){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + exit(0); + } + + # 実行ユーザの確認 + $exec_user = exec_command("$WHOAMI"); + chomp $exec_user; + + # 環境設定ファイルの読み込み + # ARCHDELETE_MS0003 を出力する前であるが、PostgreSQL のバージョンチェックに + # 環境設定ファイルの設定情報が必要なため、この時点で読み込む + %config_value = read_config($config_path); + $archive_dir = $config_value{'Archive_dir'}; + + # PostgreSQL のコマンドパスを取得 + %command_path = get_pg_command_path($config_value{'PGPATH'}, $exec_user); + + # Pacemaker と PostgreSQL がサポート対象バージョンであるかを確認 + check_support_version($command_path{'postgres'}, $exec_user); + + ### スクリプト実行準備 ### + + if ($mv_mode && $rm_mode){ + printlog("ERROR", ARCHDELETE_MS0002); + } + + printlog("LOG", ARCHDELETE_MS0003); + + my $mode = "rm"; + if ($mv_mode) { + $mode = "mv"; + printlog("LOG", ARCHDELETE_MS0004); + } + else { + printlog("LOG", ARCHDELETE_MS0005); + } + + if ($backup_pgdata){ + # backup path format : :/ or / + # には「:」や「@」を含まない + if ($backup_pgdata !~ /^(([^:@;|&]+):)?(\/.+)$/) { + printlog("ERROR", ARCHDELETE_MS0006, $backup_pgdata); + } + $backup_node = $2; + $backup_path = $3; + + if (!$backup_node){ + $backup_node = 'localhost'; + } + } + elsif (!$force_mode){ + printlog("LOG", ARCHDELETE_MS0007); + $input_node = ; + chomp $input_node; + # backup host name format : + # には「:」や「@」を含まない + if ($input_node){ + if ($input_node !~ /^[^:@\s;|&]+$/){ + printlog("ERROR", ARCHDELETE_MS0008, $input_node); + } + $backup_node = $input_node; + } + else{ + $backup_node = 'localhost'; + } + + printlog("LOG", ARCHDELETE_MS0009); + $input_path = ; + chomp $input_path; + if ($input_path){ + # backup path name format : / + if ($input_path !~ /^\/[^\s;|&]*$/){ + printlog("ERROR", ARCHDELETE_MS0010, $input_path); + } + } + $backup_path = $input_path; + + if ($backup_path){ + printlog("LOG", ARCHDELETE_MS0011, $backup_node, $backup_path); + $input = ; + chomp $input; + if ($input !~ m/^y$/i) { + printlog("LOG", ARCHDELETE_MS0012); + exit(0); + } + } + } + + # バックアップディレクトリパスの末尾の "/" がある場合それを削除 + if ($backup_path) { + $backup_path =~ s/\/+$//; + } + + printlog("LOG", ARCHDELETE_MS0013); + + # PG-REX相手ノードへの 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("$exec_user"); + $ssh_info->pass("$ssh_pass"); + + # ノード名の取得 + printlog("LOG", ARCHDELETE_MS0015); + %node_value = get_node($ssh_info); + + # rootユーザで実行している時のみ cib.xml ファイルを読み込み、ツールに必要な情報を取得 + if ($exec_user eq "root") { + printlog("LOG", ARCHDELETE_MS0016); + %my_cib_value = read_cib($cib_path, $config_value{'PG_REX_Primitive_ResourceID'}, $kill_when_no_data); + } + + # DBクラスタのパスを、オプション→cib.xmlファイル→環境変数の優先度で取得 + if ($dbcluster_mode){ + $dbcluster_path = $dbcluster_mode; + } + if (!$dbcluster_path && $exec_user eq "root"){ + $dbcluster_path = $my_cib_value{'pgdata'}; + } + if (!$dbcluster_path){ + $dbcluster_path = $ENV{'PGDATA'}; + } + if (!$dbcluster_path){ + printlog("ERROR", ARCHDELETE_MS0044); + } + + # バックアップの指定無し時の確認 + if (!$backup_path && !$force_mode){ + printlog("LOG", ARCHDELETE_MS0018, $node_value{'my_node'}, $node_value{'another_node'}, $dbcluster_path); + $input = ; + chomp $input; + if ($input !~ m/^y$/i) { + printlog("LOG", ARCHDELETE_MS0012); + exit(0); + } + } + + ### バックアップの pgdata の状態を取得 ### + printlog("LOG", ARCHDELETE_MS0019); + + my $command; + my $backup_wal = ''; + my $ssh_info_backup_node = new Ssh_info(); + if ($backup_path){ + printlog("LOG", ARCHDELETE_MS0020); + $command = "$LS -d $backup_path"; + if ($backup_node ne 'localhost'){ + # バックアップが存在するノードへの ssh 接続の為の情報を取得 + $ssh_pass_backup_node = get_ssh_passwd($backup_node, $config_value{'BACKUP_NODE_SSH_PASS_MODE'}, $config_value{'BACKUP_NODE_SSH_PASS_FILE'}); + $ssh_info_backup_node->address("$backup_node"); + $ssh_info_backup_node->user("$exec_user"); + $ssh_info_backup_node->pass("$ssh_pass_backup_node"); + @results = ssh_exec_command($ssh_info_backup_node, $command, "NO_EXIT"); + if (defined($results[2])){ + printlog("ERROR", ARCHDELETE_MS0046); + } + } + else { + $results[0] = exec_command($command); + chomp $results[0]; + } + if ($results[0] ne $backup_path){ + printlog("ERROR", ARCHDELETE_MS0021, $backup_path); + } + my $backup_label = $backup_path."\/backup_label"; + $command = "$CAT $backup_label"; + if ($backup_node ne 'localhost'){ + @results = ssh_exec_command($ssh_info_backup_node, $command, "NO_EXIT"); + if (defined($results[2])){ + printlog("ERROR", ARCHDELETE_MS0046); + } + } + else { + $results[0] = exec_command($command); + } + my @backup_label_strings = split(/\n/, $results[0]); + $backup_wal = get_start_wal_filename($backup_label, @backup_label_strings); + printlog("LOG", ARCHDELETE_MS0023, $backup_wal); + } + + # 自身のノードの pgdata の状態を取得 + # pg_controldata コマンドは "C" ロケールにしてから実行 + printlog("LOG", ARCHDELETE_MS0024, $node_value{'my_node'}, $dbcluster_path); + my $my_wal = ''; + + if ($exec_user eq "root") { + $result = exec_command("$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $dbcluster_path\""); + } + else{ + $result = exec_command("export LANG=C; $command_path{'pg_controldata'} $dbcluster_path"); + } + if ($result ne ''){ + my @my_controldata_strings = split(/\n/, $result); + $my_wal = get_recoverywal(@my_controldata_strings); + printlog("LOG", ARCHDELETE_MS0023, $my_wal); + } + else{ + printlog("ERROR", ARCHDELETE_MS0025); + } + + # 相手のノードの pgdata の状態を取得 + # pg_controldata コマンドは "C" ロケールにしてから実行 + printlog("LOG", ARCHDELETE_MS0026, $node_value{'another_node'}, $dbcluster_path); + my $another_wal = ''; + + if ($exec_user eq "root") { + @results = ssh_exec_command($ssh_info, "$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $dbcluster_path\""); + } + else{ + @results = ssh_exec_command($ssh_info, "export LANG=C; $command_path{'pg_controldata'} $dbcluster_path"); + } + if ($results[0] ne ''){ + my @another_controldata_strings = split(/\n/, $results[0]); + $another_wal = get_recoverywal(@another_controldata_strings); + printlog("LOG", ARCHDELETE_MS0023, $another_wal); + } + else{ + printlog("ERROR", ARCHDELETE_MS0025); + } + + ### 削除基準の WAL の算出 ### + # 削除基準は、指定したベースバックアップ、自身のノードの PGDATA 、相手先のノードの PGDATA の + # リカバリに必要な最初の WAL ファイルの中で一番小さいファイルを選ぶ + printlog("LOG", ARCHDELETE_MS0027); + + my $base_wal = ''; + my @compare_list = ($backup_wal, $my_wal, $another_wal); + foreach my $wal (@compare_list){ + if ($wal ne ''){ + if ($base_wal eq ''){ + $base_wal = $wal; + } + elsif ($wal lt $base_wal){ + $base_wal = $wal; + } + } + } + if ($base_wal eq ''){ + printlog("LOG", ARCHDELETE_MS0028); + exit(0); + } + else{ + printlog("LOG", ARCHDELETE_MS0029, $base_wal); + } + + ### アーカイブファイルリストの作成 ### + if ($mode eq "mv"){ + printlog("LOG", ARCHDELETE_MS0045); + } else { + printlog("LOG", ARCHDELETE_MS0030); + } + + my @file_list = (); + opendir(DIR, $archive_dir) or printlog("ERROR", ARCHDELETE_MS0031, $archive_dir); + while (my $archive_file = readdir DIR){ + # archive xlogfile name format : + # : + # は8桁の16進数 + # は16桁の16進数 + # + # archive partialfile name format : + # : .partial + # + # archive historyfile name format : + # : .history + # + # archive backupfile name format : + # : ..backup + # は8桁の16進数 + # は8桁の16進数 + if ($archive_file =~ /^[0-9A-F]{24}(\.partial)?$/){ + push(@file_list, $archive_file); + next; + } + if ($archive_file =~ /^[0-9A-F]{8}\.history$/){ + push(@file_list, $archive_file); + next; + } + if ($archive_file =~ /^[0-9A-F]{24}\.[0-9A-F]{8}\.backup$/){ + push(@file_list, $archive_file); + next; + } + } + closedir(DIR); + + # アーカイブ削除対象の算出 + + # historyfile name format : .history + # は8桁の16進数 + $base_wal =~ /^([0-9A-F]{8})/; + my $base_history = $1; + my @remove_list = (); + foreach my $file (@file_list){ + if ($file =~ /^[0-9A-F]{8}\.history$/) { + if ($file lt $base_history){ + printlog("LOG", ARCHDELETE_MS0032, $file); + push(@remove_list, $file); + } + } + elsif ($file lt $base_wal){ + printlog("LOG", ARCHDELETE_MS0032, $file); + push(@remove_list, $file); + } + } + if (!@remove_list){ + printlog("LOG", ARCHDELETE_MS0033); + exit(0); + } + + # アーカイブログの削除 + if ($mode eq "mv"){ + my ($sec, $min, $hour, $mday, $mon, $year) = localtime(time()); + my $date = sprintf("%04d%02d%02d_%02d%02d%02d", $year+1900, $mon+1, $mday, + $hour, $min, $sec); + my $move_dir = $archive_dir."/".$date; + if (-d $move_dir){ + printlog("ERROR", ARCHDELETE_MS0034, $move_dir); + } + mkdir($move_dir, 0700) or printlog("ERROR", ARCHDELETE_MS0035, $move_dir); + chown(26, 26, $move_dir) or printlog("ERROR", ARCHDELETE_MS0036, $move_dir); + printlog("LOG", ARCHDELETE_MS0037, $move_dir); + + foreach my $move_file (@remove_list){ + my $move_path = $archive_dir."/".$move_file; + rename($move_path, $move_dir."/".$move_file) or printlog("ERROR", ARCHDELETE_MS0038, $move_path); + printlog("LOG", ARCHDELETE_MS0039, $move_file); + } + + printlog("LOG", ARCHDELETE_MS0040, $move_dir); + } + else { + foreach my $remove_file (@remove_list){ + my $remove_path = $archive_dir."/".$remove_file; + unlink($remove_path) or printlog("ERROR", ARCHDELETE_MS0041, $remove_path); + printlog("LOG", ARCHDELETE_MS0042, $remove_file); + } + printlog("LOG", ARCHDELETE_MS0043); + } + exit(0); +} diff --git a/pg-rex_operation_tools/bin/pg-rex_primary_start b/pg-rex_operation_tools/bin/pg-rex_primary_start new file mode 100644 index 0000000..b3e9f54 --- /dev/null +++ b/pg-rex_operation_tools/bin/pg-rex_primary_start @@ -0,0 +1,384 @@ +#!/usr/bin/perl +##################################################################### +# Function: pg-rex_primary_start +# +# +# 概要: +# PG-REX での Primary 起動実行ツール。 +# 手順の簡易化を目的として作成している。 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use sigtrap qw(die normal-signals error-signals); +use Getopt::Long; +use PGRex::command; +use PGRex::common qw(pacemaker_online pgrex_failed_action primary_running + vip_running stonith_running ping_running + read_config read_cib exec_command + get_pg_command_path get_ssh_passwd check_support_version + check_user printlog check_dbcluster_access + create_pid_file unlink_pid_file); + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + } + } + + create_pid_file(); +}; + +END { + unlink_pid_file(); +}; + +$SIG{INT} = sub { + printlog("LOG", PRIMARYSTART_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 $hacf_path = HACF_PATH.HACF_FILENAME; + my $crm_path = ""; + my %crm_value; + my @crm_check_key_list = ("tmpdir", "pgdata", "repuser"); + my %node_value; + my %command_path; + my $pg_command_user = "postgres"; + my $resource_id; + my $tmpdir = RA_TMPDIR; + my $dbcluster_dir; + my $pgsql_data_status; + 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 $result; + my $kill_when_no_data = 1; + my $operation_num = 1; + my $myself; + + # 標準出力が途中で停止するのを防ぐ為に + # 標準出力のオートフラッシュを有効化 + $| = 1; + + # オプション解析 + foreach ( @ARGV ){ + if ( "$_" eq "-" || "$_" eq "--" ){ + $help_mode = 1; + } + } + $exit_code = GetOptions('help' => \$help_mode, + 'additional_information' => \$PGRex::common::additional_information_mode, + 'version' => \$version_mode); + + $myself = $0; + $myself =~ s/.*\///g; + + if ($help_mode || !$exit_code){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + print "\n"; + printlog("USAGE", PRIMARYSTART_USAGE); + exit(0); + } + if ($version_mode){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + exit(0); + } + + # 引数の確認 + if ($#ARGV >= 0){ + $crm_path = $ARGV[0]; + if (! -e $crm_path){ + printlog("ERROR", PRIMARYSTART_MS0002); + } + } + + # 実行ユーザの確認 + 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'}); + + ### スクリプト実行準備 ### + + # 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"); + + # 引数に xml ファイルが指定されている場合は xml ファイルを読み込む + # そうでない場合は cib.xml ファイルを読み込む + if ($crm_path ne ""){ + open (FILE, $crm_path) or printlog("ERROR", PRIMARYSTART_MS0004); + + # tmpdir に指定してある値を取得 + while (){ + my $key; + my $value; + if ($_ =~ /.*id=\"$config_value{'PG_REX_Primitive_ResourceID'}-instance_attributes-\S+\"/){ + if ($_ =~ /\s+name=\"([^\"\s]+)\"/){ + $key = $1; + } + if ($_ =~ /\s+value=\"([^\"]+)\"/){ + $value = $1; + } + $crm_value{$key} = $value; + } + } + close (FILE); + + foreach my $key (@crm_check_key_list) { + if ($key ne "tmpdir" && (!exists($crm_value{$key}) || $crm_value{$key} eq '')){ + printlog("ERROR", PRIMARYSTART_MS0033, $config_value{'PG_REX_Primitive_ResourceID'}, $key); + } + } + + $dbcluster_dir = $crm_value{'pgdata'}; + } + else { + %my_cib_value = read_cib($cib_path, $config_value{'PG_REX_Primitive_ResourceID'}, $kill_when_no_data); + $tmpdir = $my_cib_value{'tmpdir'}; + $dbcluster_dir = $my_cib_value{'pgdata'}; + $pgsql_data_status = $my_cib_value{'pgsql_data_status'}; + } + + # 自身のノードで他にDBクラスタにアクセスしているプロセスがないか確認 + check_dbcluster_access($dbcluster_dir); + + # コマンドを実行しているマシンのノード名ともう一台のノード名を取得 + %node_value = get_node($ssh_info); + + # corosync.confの存在有無確認 + if ($crm_path eq "" && ! -e $hacf_path){ + printlog("ERROR", PRIMARYSTART_MS0036, $hacf_path); + } + + ### Pacemaker 起動の為の準備 ### + # 自身のノードの Pacemaker 及び corosync の稼働確認 + printlog("LOG", PRIMARYSTART_MS0007, $operation_num++); + if (pacemaker_running()){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0009); + } + printlog("LOG", PRIMARYSTART_MS0010); + + ### 稼働中の Primary の存在確認 ### + printlog("LOG", PRIMARYSTART_MS0011, $operation_num++); + if (primary_running($node_value{'another_node'}, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'}, $ssh_info)){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0013); + } + printlog("LOG", PRIMARYSTART_MS0010); + + ### 初回起動以外の場合、Primary として稼働することが可能かの確認 ### + if ($crm_path eq ""){ + printlog("LOG", PRIMARYSTART_MS0027, $operation_num++); + if($pgsql_data_status && $pgsql_data_status ne "LATEST" && $pgsql_data_status ne "STREAMING|SYNC"){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0028, $config_value{'PG_REX_Primitive_ResourceID'}, $pgsql_data_status); + } + printlog("LOG", PRIMARYSTART_MS0010); + } + + ### 起動禁止フラグの存在確認 ### + printlog("LOG", PRIMARYSTART_MS0014, $operation_num++); + $lock_file = $tmpdir."/".LOCK_FILENAME; + if (-e $lock_file){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0015, $lock_file); + } + printlog("LOG", PRIMARYSTART_MS0010); + + if ($crm_path ne ""){ + my $command; + + ### HAクラスタの削除 ### + if (-e $hacf_path){ + my $cib_dir = CIB_PATH; + my $command; + + $cib_dir =~ s/\/$//g; + printlog("LOG", PRIMARYSTART_MS0016); + my $input = ; + chomp $input; + if ($input !~ m/^y$/i) { + printlog("LOG", PRIMARYSTART_MS0017); + exit(0); + } + printlog("LOG", PRIMARYSTART_MS0018, $operation_num++); + $cib_dir = CIB_PATH; + exec_command("$PCS cluster destroy --all --force"); + printlog("LOG", PRIMARYSTART_MS0010); + } + + ### HAクラスタの作成 ### + printlog("LOG", PRIMARYSTART_MS0035, $operation_num++); + if(defined($config_value{'Another_IC_LAN_IPAddress2'})){ + $command = "$PCS cluster setup $config_value{'HACLUSTER_NAME'} $node_value{'my_node'} addr=$config_value{'My_IC_LAN_IPAddress1'} addr=$config_value{'My_IC_LAN_IPAddress2'} $node_value{'another_node'} addr=$config_value{'Another_IC_LAN_IPAddress1'} addr=$config_value{'Another_IC_LAN_IPAddress2'}"; + } else { + $command = "$PCS cluster setup $config_value{'HACLUSTER_NAME'} $node_value{'my_node'} addr=$config_value{'My_IC_LAN_IPAddress1'} $node_value{'another_node'} addr=$config_value{'Another_IC_LAN_IPAddress1'}"; + } + exec_command("$command"); + printlog("LOG", PRIMARYSTART_MS0010); + } + + ### DBクラスタより、standby.signal及びrecovery.signalを削除 ### + exec_command("$SU - $pg_command_user -c \"$RM -f $dbcluster_dir/\"".STANDBY_SIGNAL); + exec_command("$SU - $pg_command_user -c \"$RM -f $dbcluster_dir/\"".RECOVERY_SIGNAL); + + ### Pacemaker 起動 ### + printlog("LOG", PRIMARYSTART_MS0019, $operation_num++); + + # postgresql.confに"# added by pg-rex_standby_start"がないことを確認 + $result = `$GREP "# added by pg-rex_standby_start" $dbcluster_dir/postgresql.conf`; + if ($result) { + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0034); + } + + # Pacemaker 起動(pcs quorum unblock 実行可能となるまでをスクリプト処理で待つため、waitオプションを指定する) + exec_command("$PCS cluster start --wait=60"); + printlog("LOG", PRIMARYSTART_MS0010); + + ### xml ファイルを引数で指定した場合 ### + if ($crm_path ne ""){ + printlog("LOG", PRIMARYSTART_MS0020, $operation_num++); + # xml ファイルを反映できる状態まで待機 + $starting_resource = "Pacemaker(not reflected crm file)"; + while (1){ + if (pacemaker_online($node_value{'my_node'})){ + last; + } + + sleep $monitor_interval; + $wait_time += $monitor_interval; + if ($wait_time >= $timeout){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0021, $timeout, $starting_resource); + } + } + sleep 5; + + # xml ファイルを反映 + $result = `$PCS cluster cib-push $crm_path`; + if ($? >> 8 != 0){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0032, $crm_path); + } + printlog("LOG", PRIMARYSTART_MS0010); + } + exec_command("$PCS quorum unblock --force"); + + ### Primary の起動確認 ### + printlog("LOG", PRIMARYSTART_MS0022, $operation_num++); + + # pcs status --full の結果を確認 + # 前回起動中に自ノードで PostgreSQL の制御エラーが発生していた場合、Pacemaker 起動から暫くの間は + # pcs status --fullの結果に PostgreSQL の制御エラー情報(Failed Resource Actions)が残っている可能性がある。そのため、 + # 起動確認の前に RA(pgsql) のモニタ間隔以上のディレイをおき、誤検知しないようにする。 + sleep $monitor_delay; + while (1){ + if ($wait_time >= $timeout){ + printlog("LOG", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0021, $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", PRIMARYSTART_MS0008); + printlog("ERROR", PRIMARYSTART_MS0023); + } + + # PING のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'PING_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'PING_ResourceID'})){ + $starting_resource = "PING"; + next; + } + + # STORAGE-MON のリソース ID 指定有りの場合、起動確認を行なう + # 起動確認の処理内容が同じため、ping_runningを呼び出して確認を行なう + if ($config_value{'STORAGE_MON_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'STORAGE_MON_ResourceID'})){ + $starting_resource = "STORAGE_MON"; + next; + } + + # STONITH 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'STONITH_ResourceID'} && !stonith_running($node_value{'my_node'}, $config_value{'STONITH_ResourceID'})){ + $starting_resource = "STONITH"; + next; + } + + if (!primary_running($node_value{'my_node'}, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'})){ + $starting_resource = "PostgreSQL"; + next; + } + + # IPADDR_PRIMARY のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_PRIMARY_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'IPADDR_PRIMARY_ResourceID'})){ + $starting_resource = "IPADDR_PRIMARY"; + next; + } + + # IPADDR_REPLICATION のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_REPLICATION_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'IPADDR_REPLICATION_ResourceID'})){ + $starting_resource = "IPADDR_REPLICATION"; + next; + } + + # IPADDR_STANDBY 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_STANDBY_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'IPADDR_STANDBY_ResourceID'})){ + $starting_resource = "IPADDR_STANDBY"; + next; + } + + # pcs status --full の結果が全て揃ったら無限ループを抜ける + last; + } + + printlog("LOG", PRIMARYSTART_MS0010); + printlog("LOG", PRIMARYSTART_MS0026, $node_value{'my_node'}); + exit(0); +} + diff --git a/pg-rex_operation_tools/bin/pg-rex_standby_start b/pg-rex_operation_tools/bin/pg-rex_standby_start new file mode 100644 index 0000000..6d16da0 --- /dev/null +++ b/pg-rex_operation_tools/bin/pg-rex_standby_start @@ -0,0 +1,1654 @@ +#!/usr/bin/perl +##################################################################### +# Function: pg-rex_standby_start +# +# +# 概要: +# PG-REX での Standby 起動実行ツール。 +# 手順の簡易化を目的として作成している。 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use sigtrap qw(die normal-signals error-signals); +use File::Compare; +use File::Spec; +use File::Basename; +use Getopt::Long; +use Class::Struct; +use IO::Socket::INET; +use PGRex::command; +use PGRex::common qw(pacemaker_online pgrex_failed_action primary_running + standby_running vip_running stonith_running ping_running + read_config read_cib get_ssh_passwd exec_command + ssh_exec_command scp_exec_command get_pg_command_path check_user + printlog get_node + check_support_version + get_pg_version_num get_controldata_value get_xlog_filename compare_lsn + get_pg_dir_state get_restore_archivewal_num get_start_wal_filename + check_dbcluster_access create_pid_file unlink_pid_file + get_sync_files receive_archive); + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + } + } + + create_pid_file(); +}; + +END { + unlink_pid_file(); +}; + +$SIG{INT} = sub { + printlog("LOG", STANDBYSTART_MS0001); +}; + +struct Check_result => { + version_is_match => '$', # B) + my_xlog_is_over => '$', # D) + full_page_writes => '$', # F) + same_branch_point => '$', # H) + pass_my_tli => '$', # I) + promote_before_my_current_lsn => '$', # J) + match_database_id => '$', # K) + gt_another_tli => '$', # L) + wal_log_hints => '$', # M) + status_is_shutdown => '$', # N) + checkpoint_before_branch => '$', # O) 'none' or 'latest' + is_conf_add_comment => '$', + my_archive_is_over => '$', + exists_future_archive => '$' +}; + +struct My_data => { + pg_version => '$', + cluster_version => '$', + pg_xlog_is_empty => '$', + current_xlog_location => '$', + controldata => '%', + latest_branch_tli => '$', + latest_branch_lsn => '$', + copy_base_lsn => '$' +}; + +struct Another_data => { + pg_xlog_is_empty => '$', + current_xlog_location => '$', + controldata => '%', + new_tli => '$', # P) 'same' or 'history' or 'pg_control' + history_file => '$', + history_tli => '$', + history_value => '%', + branch_lsn => '$', + pgport => '$' +}; + +use constant { + NONE => 0, + NORMAL => 1, + PG_REWIND => 2, + PG_BASEBACKUP => 4, + DB_SHUTDOWNED => "shut down", + DB_SHUTDOWNED_IN_RECOVERY => "shut down in recovery", + ADD_COMMENT => "# added by pg-rex_standby_start" +}; + +main(); + +1; + +sub main{ + my $config_path = CONFIG_PATH.CONFIG_FILENAME; + my %config_value; + my $myself; + + my $operation_num = 1; + my $operation_sub_num = 1; + my $exit_code; + my $result; + my @results; + my $command; + my $line; + + my $help_mode = 0; + my $check_only_mode = 0; + my $interactive_mode = 0; + my $dry_run_mode = 0; + my $shared_archive_directory_mode = 0; + my $version_mode = 0; + my $is_ready; + my $specified_mode = NONE; + my $available_mode = NONE; + my $startup_mode = NONE; + my $ssh_pass; + my %node_value; + my $copy_dir = "/tmp/"; + my $hacf_path = HACF_PATH.HACF_FILENAME; + my @another_iclan_ipaddr = (); + my $cib_path = CIB_PATH.CIB_FILENAME; + my $copy_cib_path = $copy_dir."cib.xml"; + my %primary_cib_value; + my $kill_when_no_data = 1; + my $lock_file = ""; + + my %command_path; + my $pg_command_user = "postgres"; + my $pg_xlog_dir; + + my $timeout = 300; + my $monitor_delay = 10; + my $monitor_interval = 2; + my $wait_time = 0; + my $starting_resource = ""; + + my $historyfile_path; + my @history_files; + my $tli; + my @controldata_strings; + + my $check_result = new Check_result(); + my $pg_dir_state = new Pg_dir_state(); + my $my_data = new My_data(); + my $another_data = new Another_data(); + + # 標準出力が途中で停止するのを防ぐ為に + # 標準出力のオートフラッシュを有効化 + $| = 1; + + # オプション解析 + foreach ( @ARGV ){ + if ( "$_" eq "-" || "$_" eq "--" ){ + $help_mode = 1; + } + } + { + no warnings 'once'; + + my $normal_mode = 0; + my $rewind_mode = 0; + my $basebackup_mode = 0; + $exit_code = GetOptions('help' => \$help_mode, + 'normal' => \$normal_mode, + 'rewind' => \$rewind_mode, + 'basebackup' => \$basebackup_mode, + 'check-only' => \$check_only_mode, + 'dry-run' => \$dry_run_mode, + 'shared-archive-directory' => \$shared_archive_directory_mode, + 'additional_information' => \$PGRex::common::additional_information_mode, + 'version' => \$version_mode); + + # ユーザが指定した起動モードのビット化 + if ($normal_mode) { + $specified_mode |= NORMAL; + } + if ($rewind_mode) { + $specified_mode |= PG_REWIND; + } + if ($basebackup_mode) { + $specified_mode |= PG_BASEBACKUP; + } + + # 下記の両方の条件を満たす場合に、NORMALモードで起動するようにするため、 + # $specified_modeにNORMALのビットを立てる。 + # - 非対話形式で-rを指定 + # - DBクラスタが通常起動が可能な状態 + # 非対話モードでREWINDが指定された場合、通常起動が可能な状態であれば、 + # 通常起動に切り替えるという例外があるが、対話モードの場合でREWINDが + # 指定される場合は事前のチェックで通常起動ができないことを確認済みで + # あるため、この場合に$available_modeにNORMALのビットが立つことはない。 + # よって、対話/非対話に関わらずREWINDモードが指定された場合に、 + # $specified_modeにNORMALのビットを立てても問題がない。 + if ($specified_mode & PG_REWIND) { + $specified_mode |= NORMAL; + } + } + + $myself = $0; + $myself =~ s/.*\///g; + if ($help_mode || !$exit_code){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + print "\n"; + printlog("USAGE", STANDBYSTART_USAGE); + exit(0); + } + if ($version_mode){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + exit(0); + } + + # 実行ユーザの確認 + 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'}); + + ### スクリプト実行準備 ### + + # 対話形式かどうかの確認 + if ($specified_mode == NONE) { + $interactive_mode = 1; + } elsif ($check_only_mode) { + # 非対話モードでかつ、チェックモードの場合、非対話オプションを無視する旨の警告を表示する + printlog("LOG", STANDBYSTART_MS0084); + } + + # 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の存在有無確認 + if ( ! -e $hacf_path){ + printlog("ERROR", STANDBYSTART_MS0089, $hacf_path); + } + + # 相手先のノードの IC-LAN のIPアドレスを配列に格納 + push(@another_iclan_ipaddr, $config_value{'Another_IC_LAN_IPAddress1'}); + if (defined($config_value{'Another_IC_LAN_IPAddress2'})){ + push(@another_iclan_ipaddr, $config_value{'Another_IC_LAN_IPAddress2'}); + } + + ### Pacemaker 起動の為の準備 ### + # 自身のノードの Pacemaker 及び corosync の稼働確認 + printlog("LOG", STANDBYSTART_MS0004, $operation_num++); + if (pacemaker_running()){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0006); + } + printlog("LOG", STANDBYSTART_MS0007); + + ### 稼働中の Primary の存在確認 ### + printlog("LOG", STANDBYSTART_MS0008, $operation_num++); + if (!primary_running($node_value{'another_node'}, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'}, $ssh_info)){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0010); + } + printlog("LOG", STANDBYSTART_MS0007); + + # Primary マシンにある cib.xml をコピーし読み込む + scp_exec_command($ssh_info, $cib_path, $copy_dir); + %primary_cib_value = read_cib($copy_cib_path, $config_value{'PG_REX_Primitive_ResourceID'}, $kill_when_no_data); + exec_command("$RM -f $copy_cib_path"); + + # 自身のノードで他にDBクラスタにアクセスしているプロセスがないか確認 + check_dbcluster_access($primary_cib_value{'pgdata'}); + + ### 起動禁止フラグの存在確認 ### + printlog("LOG", STANDBYSTART_MS0013, $operation_num++); + + # 起動禁止フラグが存在しないことを確認 + $lock_file = $primary_cib_value{'tmpdir'}."/".LOCK_FILENAME; + if (-e $lock_file){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0014, $lock_file); + } + + printlog("LOG", STANDBYSTART_MS0007); + + + ### DBクラスタの状態確認 ### + + printlog("LOG", STANDBYSTART_MS0034, $operation_num); + + ## DB クラスタの状態を収集する + # DB クラスタの及びアーカイブディレクトリの状態を収集する A)、E) + $pg_dir_state = get_pg_dir_state($primary_cib_value{'pgdata'}, $config_value{'Archive_dir'}); + + # 相手ノードのfull_page_writesを取得 F) + $command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SHOW full_page_writes\\\" 2> /dev/null \""; + @results = ssh_exec_command($ssh_info, $command); + if ($results[0] eq "on") { + $check_result->full_page_writes(1); + } else { + $check_result->full_page_writes(0); + } + $command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SHOW port\\\" 2> /dev/null \""; + @results = ssh_exec_command($ssh_info, $command); + $another_data->pgport($results[0]); + + # 相手のノードの pg_control と TLI hisotry ファイルの情報を収集する + # TLI hisotry ファイルの情報を収集 + $historyfile_path = $config_value{'Archive_dir'}."/*.history"; + @results = ssh_exec_command($ssh_info, "$LS -1r $historyfile_path* 2> /dev/null"); + if ($results[0] eq "") { + printlog("ERROR", STANDBYSTART_MS0042); + } + @history_files = split(/\n/, $results[0]); + $another_data->history_file($history_files[0]); + ($tli=$history_files[0]) =~ s/^.*\/([0-9A-F]+)\..*$/$1/; + $another_data->history_tli(hex($tli)); + @results = get_history_value($history_files[0], $ssh_info); + my %val_list ; + foreach $line (@results) { + if( $line =~ /^(\d+)\s+([0-9A-F\/]+)/ ) { + $val_list{$1} = $2; + } + } + $another_data->history_value(\%val_list); + + # pg_controlの情報を収集 + @results = ssh_exec_command($ssh_info, "$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $primary_cib_value{'pgdata'}\""); + @controldata_strings = split(/\n/, $results[0]); + $another_data->controldata(get_controldata_value(@controldata_strings)); + + # 相手ノードのhistoryファイルが最新であることを確認 G) + # -> historyのTLIが、pg_controlのTLIより小さい場合NG + # 処理を継続する意味がないため、ここで異常終了する + if ($another_data->history_tli < $another_data->controldata("Latest checkpoint's TimeLineID")) { + my $tli_string = sprintf("%08X", $another_data->controldata("Latest checkpoint's TimeLineID")); + printlog("LOG", STANDBYSTART_MS0087, + $another_data->controldata("Latest checkpoint's TimeLineID"), + File::Spec->catfile($config_value{'Archive_dir'}, $tli_string.".history")); + printlog("ERROR", STANDBYSTART_MS0065); + } + + # 相手のノードのアーカイブディレクトリに相手ノードの現在の WAL より + # 未来の WAL がアーカイブされていないことを確認 + # 相手のノードの現在の WAL のファイル名を取得 + # pg_current_wal_lsn() がセグメント境界上(xx000000)となった場合 pg_walfile_nameがすでにアーカイブされているファイルを返す + # pg_walfile_name が 000000 を前のセグメントだと判断することを逆補正するため 末尾を1に置換する + @results = ssh_exec_command($ssh_info, "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT pg_walfile_name((left(pg_current_wal_lsn()::text, -1)|| '1')::pg_lsn)\\\" 2> /dev/null \""); + my $m_curr_wal = $results[0]; + # 相手のノードの最新のアーカイブ WAL のファイル名を取得 + @results = ssh_exec_command($ssh_info, "$LS -1H $config_value{'Archive_dir'} | $GREP -P \"^[0-9A-F]{24}(?!\.partial\$)(?:\.[^\.]+)?\$\" | $TAIL -1"); + my $m_last_archive = $results[0]; + if (($m_curr_wal cmp $m_last_archive) <= 0) { + $check_result->exists_future_archive(1); + } + else { + $check_result->exists_future_archive(0); + } + + # wal_log_hints settingがonまたは、Data page checksum versionが0以外を確認 M) + if ($another_data->controldata("wal_log_hints setting") eq "on" + || $another_data->controldata("Data page checksum version") > 0) { + $check_result->wal_log_hints(1); + } else { + $check_result->wal_log_hints(0); + } + + # 相手のノードのTLIを比較 P) + # -> pg_rewind実行前にチェックポイントが必要かの判定および + # TLIが同じで、自身のノードのDatabase cluster stateが、 + # shut down以外の場合は、pg_rewind実行NGとする判定に必要なため + if ($another_data->history_tli == $another_data->controldata("Latest checkpoint's TimeLineID")) { + $another_data->new_tli('same'); + } elsif($another_data->history_tli > $another_data->controldata("Latest checkpoint's TimeLineID")) { + $another_data->new_tli('history'); + } else { + $another_data->new_tli('pg_control'); + } + + # 自身のノードのアーカイブにファイルがある場合、相手のノードより後の + # WALを含むセグメントファイルがないか確認 + $check_result->my_archive_is_over(0); + if (!$pg_dir_state->pgarch_empty) { + # 相手のノードの最新のTLIを設定 + my $s_tli = $another_data->history_tli; + # 相手のノードの最新のLSNを取得 + $command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT pg_current_wal_lsn()\\\" 2> /dev/null \""; + @results = ssh_exec_command($ssh_info, $command); + $results[0] =~ /^([0-9A-F]+)\/([0-9A-F]+)$/; + my ($s_logid, $s_segno) = (hex($1), hex($2)); + my $s_lsn = ($s_logid << 32) + ($s_segno << 24); + + # 相手のノードより後のセグメントファイル名を確認 + opendir(my $dir, $config_value{'Archive_dir'}) || die "Failed to open archive directory: $config_value{'Archive_dir'}"; + foreach my $file (sort readdir($dir)) { + next if ($file !~ /^(([0-9A-F]{8})([0-9A-F]{8})([0-9A-F]{8}))(\.partial)?(?:\.(.+))?$/); + my ($f_basename, $f_tli, $f_logid, $f_segno, $f_partial, $f_suffix) = + ($1, hex($2), hex($3), hex($4), defined $5, $6); + my $f_lsn = ($f_logid << 32) + ($f_segno << 24); + if ($f_tli > $s_tli || $f_lsn >= $s_lsn) { + $check_result->my_archive_is_over(1); + last; + } + } + closedir($dir); + } + + $check_result->is_conf_add_comment(0); + # 以降は自身のノードが存在する場合のみチェック + if ($pg_dir_state->pgdata_exist) { + # pg-rex_standby_startが追記した内容がpostgresql.confに存在するかチェック + $command = "$GREP \"".ADD_COMMENT."\" $primary_cib_value{'pgdata'}/postgresql.conf"; + $result = `$command`; + if ($result) { + $check_result->is_conf_add_comment(1); + } + + # 自身のノードの pg_control と TLI hisotry ファイルの情報を収集する + # TLI hisotry ファイルの情報を収集 + $historyfile_path = $config_value{"Archive_dir"}."/*.history"; + $result = `$LS -1r $historyfile_path* 2> /dev/null`; + my $exit_code = $? >> 8; + if ($exit_code != 0) { + # ダミーデータを設定 + $my_data->latest_branch_tli(1); + $my_data->latest_branch_lsn("0/00000000"); + } else { + @results = split(/\n/, $result); + my @history_files = split(/\n/, $results[0]); + @results = get_history_value($history_files[0]); + foreach $line (@results) { + # 最後のTLIの情報のみ収集 + if( $line =~ /^(\d+)\s+([0-9A-F\/]+)/) { + $my_data->latest_branch_tli($1); + $my_data->latest_branch_lsn($2); + } + } + } + + # pg_controlの情報を収集 + $result = exec_command("$SU - $pg_command_user -c \"export LANG=C; $command_path{'pg_controldata'} $primary_cib_value{'pgdata'}\""); + @controldata_strings = split(/\n/, $result); + $my_data->controldata(get_controldata_value(@controldata_strings)); + + ## 自身のノード単独の確認 + # クラスタのバージョンチェック B) + if (check_dbcluster_version($my_data, $primary_cib_value{'pgdata'}, $command_path{'postgres'}, $pg_command_user)) { + $check_result->version_is_match(0); + } else { + $check_result->version_is_match(1); + } + + # 自身のノードのDatabase cluster stateが、shut downまたは、 + # shut down in recoveryのいずれかであることを確認 N) + if ($my_data->controldata("Database cluster state") eq "shut down" + || $my_data->controldata("Database cluster state") eq "shut down in recovery") { + $check_result->status_is_shutdown(1); + } else { + $check_result->status_is_shutdown(0); + } + + # (STEP7)自身ノードの TLI が過去に通っていることを確認 I) + # 自身のノードのLatest checkpoint locationが分岐点前であることの確認 O) + my $target_tli; + if ($another_data->history_tli == $my_data->controldata("Latest checkpoint's TimeLineID")) { + $target_tli = $my_data->controldata("Latest checkpoint's TimeLineID") -1; + } elsif ($another_data->history_tli < $my_data->controldata("Latest checkpoint's TimeLineID")) { + $target_tli = $another_data->history_tli -1 ; + }else { + $target_tli = $my_data->controldata("Latest checkpoint's TimeLineID"); + } + + $another_data->branch_lsn($another_data->history_value($target_tli)); + if ($another_data->branch_lsn) { + $check_result->pass_my_tli(1); + + if (compare_lsn($another_data->branch_lsn, $my_data->controldata("Latest checkpoint location")) >= 0) { + $check_result->checkpoint_before_branch("latest"); + $my_data->copy_base_lsn($my_data->controldata("Latest checkpoint location")); + } else { + $check_result->checkpoint_before_branch("none"); + } + } else { + $check_result->pass_my_tli(0); + $check_result->checkpoint_before_branch("none"); + } + + ## 自身のノードと相手のノードの比較 + # (STEP 3)自身のノードと相手のノードのデータベース識別子が一致していることを確認 K) + if ($my_data->controldata("Database system identifier") + eq $another_data->controldata("Database system identifier")) { + $check_result->match_database_id(1); + } else { + $check_result->match_database_id(0); + } + + # (STEP 4)自身のノードの TLI が相手のノードを超過していないことを確認 L) + if ($my_data->controldata("Latest checkpoint's TimeLineID") + > $another_data->history_tli) { + $check_result->gt_another_tli(1); + } else { + $check_result->gt_another_tli(0); + } + + # 両ノードのpg_waldumpに指定する読取り終了セグメントファイル名を特定する + my $my_latest_wal_filename; + my $another_latest_walfile_name; + my $another_latest_checkpoint_walfile_path; + my $another_latest_checkpoint_walfile_name; + $command = "$LS -l $primary_cib_value{'pgdata'}/pg_wal/ | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1"; + $result = exec_command($command); + if ($result =~ /.*([0-9A-F]{24})$/){ + $my_latest_wal_filename = $1; + $my_data->pg_xlog_is_empty(0); + } else { + $my_data->pg_xlog_is_empty(1); + } + + $command = "$LS -l $primary_cib_value{'pgdata'}/pg_wal/ | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1"; + @results = ssh_exec_command($ssh_info, $command); + if ($results[0] =~ /.*([0-9A-F]{24})$/){ + $another_latest_walfile_name = $1; + $another_data->pg_xlog_is_empty(0); + + # 読取り開始セグメントファイル名を特定する + my $filename = $another_data->controldata("Latest checkpoint's REDO WAL file"); + $filename =~ s/^[0-9A-F]{8}([0-9A-F]{16})$/$1/; + $command = "$LS -1 $primary_cib_value{'pgdata'}/pg_wal/*$filename* | $GREP -P '[0-9A-F]{24}(\.partial)?'"; + @results = ssh_exec_command($ssh_info, $command); + my @filenames = split(/\n/, $results[0]); + # 最大2件しか返ってこない + if (($filename = $filenames[0]) =~ /.*\.partial/) { + $another_latest_checkpoint_walfile_path = $filenames[1]; + } else { + $another_latest_checkpoint_walfile_path = $filenames[0]; + } + } else { + $another_data->pg_xlog_is_empty(1); + } + + # 自身のノードのTLIが相手のノードを超過している場合、 + # 以降のLSN関連のチェックは行わない + if ($check_result->gt_another_tli) { + # 省略したチェック項目のエラーが出ないように値を設定 + $check_result->my_xlog_is_over(0); + $check_result->same_branch_point(1); + $check_result->promote_before_my_current_lsn(0); + $check_result->pass_my_tli(1); + + } else { + # (STEP 5)自身のノードの XLOG 位置が相手のノードを超過していないことを確認 D) + if ($another_data->pg_xlog_is_empty) { + $check_result->my_xlog_is_over(1); + } elsif ($my_data->pg_xlog_is_empty) { + # 自身のノードの pg_wal が空の場合は超過していないと判定する + $check_result->my_xlog_is_over(0); + } else { + $check_result->my_xlog_is_over(0); + + # 自身のノードの最新のLSNを取得する + $my_data->current_xlog_location(getLatestLsn($my_data->controldata("Latest checkpoint's REDO WAL file"), + $my_latest_wal_filename, + $primary_cib_value{'pgdata'}."/pg_wal", + $command_path{'pg_waldump'}, + $pg_command_user)); + + # 相手のノードの最新のLSNを取得する + # WALファイル名からTLIを取得 + $another_latest_checkpoint_walfile_path =~ /^.*([0-9A-F]{24}(\.partial)?)/; + $another_latest_checkpoint_walfile_name = $1; + my $another_latest_checkpoint_tli = substr($another_latest_checkpoint_walfile_name,0,8); + my $another_latest_tli = substr($another_latest_walfile_name,0,8); + + # pg_controlのTLIが最新のTLIより小さい場合、 + # pg_waldumpの読み取り開始ファイルを最新のTLIのpg_walに存在する最古のWALセグメントファイルにする + my $waldump_walfilename_start = $another_latest_checkpoint_walfile_name; + if ($another_latest_checkpoint_tli lt $another_latest_tli){ + $command = "$LS -1Hr $primary_cib_value{'pgdata'}/pg_wal/ | $GREP -P \"^${another_latest_tli}[0-9A-F]{16}\$\" | $TAIL -1"; + @results = ssh_exec_command($ssh_info, $command); + chomp $results[0]; + if ($results[0] eq ""){ + # pg_walにWALセグメントファイルが存在しない + printlog("ERROR", STANDBYSTART_MS0099); + } + $waldump_walfilename_start = $results[0]; + } + my $waldump_walfilepath_start = "$primary_cib_value{'pgdata'}/pg_wal/$waldump_walfilename_start"; + my $waldump_walfilepath_end = "$primary_cib_value{'pgdata'}/pg_wal/$another_latest_walfile_name"; + $command = "$SU - $pg_command_user -c \"$command_path{'pg_waldump'} $waldump_walfilepath_start $waldump_walfilepath_end 2> /dev/null | $TAIL -1\""; + @results = ssh_exec_command($ssh_info, $command); + if ($results[0] =~ /.*lsn:\s+([0-9A-F\/]+).*/){ + $another_data->current_xlog_location($1); + } + + # 取得した XLOG 位置を比較(自身のノードの XLOG 位置の方が進んでいないか確認) + $result = compare_lsn($my_data->current_xlog_location, + $another_data->current_xlog_location); + if ($result > 0){ + $check_result->my_xlog_is_over(1); + } + } + + # TLIのhistoryファイルを確認し、分岐点が一致していることを確認 H) + if ($my_data->latest_branch_tli >= $another_data->history_tli) { + $check_result->same_branch_point(0); + } elsif ($check_result->pass_my_tli + && compare_lsn($my_data->latest_branch_lsn, + $another_data->history_value($my_data->latest_branch_tli))) { + $check_result->same_branch_point(0); + } else { + $check_result->same_branch_point(1); + } + + # 両ノードで、TLI、分岐点が同じ場合、STEP8はチェック不要 + if ($another_data->history_tli == $my_data->controldata("Latest checkpoint's TimeLineID")) { + $check_result->promote_before_my_current_lsn(0); + } else { + # (STEP8) 既存のDBクラスタでそのまま組み込めるか判断するため、 + # 自身ノードの現在の LSN よりも先に promote されていないことを確認 J) + # + # 相手ノードの昇格したLSN(分岐点)より、昇格前のタイムラインで自身ノードが + # 先行する可能性がある場合、既存クラスタを組込めないと判断 + # + # [$check_result->pass_my_tliが0の場合] + # 自身ノードの TLI が相手ノードの過去の TLI を通っていないと判断 + # + # [$check_result->pass_my_tliが1の場合] + # 自身ノードの TLI が相手ノードの過去の TLI を通っているため、 + # 自身ノードの現在の LSN よりも先に promote されていなければ、既存クラスタ組込み可と判断 + # ただし、昇格後の相手ノードのWALセグメントファイルが自身ノードに存在するが、 + # 自身ノードのpg_controlが更新されておらず、昇格後の相手ノードのTLIより前の + # 情報だった場合でも、みかけ上自身ノードが先行しているようにみえるため組込めないと判断 + # (将来修正が必要) + if (!$check_result->pass_my_tli) { + $check_result->promote_before_my_current_lsn(0); + } else { + $result = compare_lsn($my_data->current_xlog_location, + $another_data->history_value($my_data->controldata("Latest checkpoint's TimeLineID"))); + if ($result < 0){ + $check_result->promote_before_my_current_lsn(0); + } else { + $check_result->promote_before_my_current_lsn(1); + } + } + } + } + } + + + ### 既存クラスタのチェック ### + # + # ・対話またはチェックモードの場合 + # ->チェックを実行する + # ・非対話モードの場合 + # ->NORMALのビットが立っている場合にチェックを実行する + # + printlog("LOG", STANDBYSTART_MS0050, $operation_num, $operation_sub_num++); + if (($specified_mode & NORMAL) || $check_only_mode || $interactive_mode) { + if ($pg_dir_state->pgdata_exist) { + # 共通の確認項目 + $is_ready = common_cluster_check($check_result, $pg_dir_state, + $my_data, $another_data); + + # (STEP 5)自身のノードの XLOG 位置が相手のノードを超過していないことを確認 D) + if ($check_result->my_xlog_is_over) { + if ($another_data->pg_xlog_is_empty) { + printlog("ERROR", STANDBYSTART_MS0040, $primary_cib_value{'pgdata'}."/pg_wal"); + } + printlog("LOG", STANDBYSTART_MS0041, + $my_data->current_xlog_location, + $another_data->current_xlog_location); + $is_ready = 0; + } + + # (STEP8) 自身ノードの現在の LSN よりも先に promote されていないことを確認 J) + if ($check_result->promote_before_my_current_lsn) { + printlog("LOG", STANDBYSTART_MS0044); + $is_ready = 0; + } + } else { + # (STEP 1)DB クラスタが存在していることを確認 A) + printlog("LOG", STANDBYSTART_MS0033); + $is_ready = 0; + } + + # 全モード共通のチェックを実施 + if (validate_check_result_common($check_result) != 0) { + $is_ready = 0; + } + + if ($is_ready) { + $available_mode |= NORMAL; + printlog("LOG", STANDBYSTART_MS0007); + } else { + printlog("LOG", STANDBYSTART_MS0005); + } + } else { + printlog("LOG", STANDBYSTART_MS0086); + } + + ### pg_rewind実行のためのチェック ### + # + # ・既にチェック済みのモードで起動可能なものがあれば、チェックをしない + # また、非対話モードではPG_REWINDのビットが立っていない場合もチェックをしない + # + printlog("LOG", STANDBYSTART_MS0051, $operation_num, $operation_sub_num++); + if (!($available_mode & NORMAL) && + (($specified_mode & PG_REWIND) || $check_only_mode || $interactive_mode)) { + if ($pg_dir_state->pgdata_exist) { + # 共通の確認項目 + $is_ready = common_cluster_check($check_result, $pg_dir_state, + $my_data, $another_data); + + # 相手ノード側のfull_page_writesがonであることを確認 F) + if (!$check_result->full_page_writes) { + printlog("LOG", STANDBYSTART_MS0055); + $is_ready = 0; + } + + # pg_controlのwal_log_hints settingがonまたは、 + # Data page checksum versionが0以外となっていることの確認 M) + if (!$check_result->wal_log_hints) { + printlog("LOG", STANDBYSTART_MS0056); + $is_ready = 0; + } + + # 自身のノードのLatest checkpoint locationが分岐点前であることの確認 O) + if ($another_data->branch_lsn) { + # デバッグ情報は、branch LSNがある場合のみ表示 + printlog("DEBUG", "branch LSN : [0]\n", + $another_data->branch_lsn); + printlog("DEBUG", "Latest checkpoint location : [0]\n", + $my_data->controldata("Latest checkpoint location")); + } + # チェックモードまたは対話モードの場合、分岐点とチェックポイント位置の確認をする。 + # 非対話モードでPG_REWINDのビットが立っている場合は、wal_keep_segmentsの設定によって + # 分岐点前の最終CHECKPOINT以降のWALがpg_walに存在するケースを考慮して + # この確認をスキップする。 + if ($check_only_mode || $interactive_mode) { + if ($check_result->checkpoint_before_branch eq "none") { + printlog("LOG", STANDBYSTART_MS0072); + $is_ready = 0; + } + } + + # 自身のノードと相手のノードのTLIが同じで、かつ自身のノードの + # Database cluster stateが"shut down","shut down in recovery" + # 以外となっていないか確認 + if ($another_data->new_tli eq "same" + && $my_data->controldata("Latest checkpoint's TimeLineID") == $another_data->history_tli + && $my_data->controldata("Database cluster state") ne DB_SHUTDOWNED + && $my_data->controldata("Database cluster state") ne DB_SHUTDOWNED_IN_RECOVERY) { + printlog("LOG", STANDBYSTART_MS0057); + $is_ready = 0; + } + + } else { + printlog("LOG", STANDBYSTART_MS0033); + $is_ready = 0; + } + + # 全モード共通のチェックを実施 + if (validate_check_result_common($check_result) != 0) { + $is_ready = 0; + } + + if ($is_ready) { + $available_mode |= PG_REWIND; + printlog("LOG", STANDBYSTART_MS0007); + } else { + printlog("LOG", STANDBYSTART_MS0005); + } + } else { + printlog("LOG", STANDBYSTART_MS0086); + } + + ### pg_basebackup実行のためのチェック ### + # + # ・対話またはチェックモードの場合 + # ->DBクラスタチェックを実行する + # ・非対話モードの場合 + # ->既にチェック済みのモードで起動可能なものがあれば、チェックをしない + # また、PG_BASEBACKUPのビットが立っていない場合もチェックをしない + # + printlog("LOG", STANDBYSTART_MS0052, $operation_num, $operation_sub_num++); + if ((($specified_mode & PG_BASEBACKUP) && $available_mode == NONE) || + $check_only_mode || $interactive_mode) { + $is_ready = 1; + + # アーカイブディレクトリ共有モードの場合はアーカイブ WAL の存在確認は行わない + if (!$shared_archive_directory_mode){ + if ($pg_dir_state->pgdata_exist){ + # Primary 側と DB 識別子が一致していない、かつアーカイブ + # WAL が存在する場合は異常終了する + # NOTE: 適用不可のアーカイブ WAL が残存している可能性があるため + if ($my_data->controldata("Database system identifier") + ne $another_data->controldata("Database system identifier") + && !$pg_dir_state->pgarch_empty){ + printlog("LOG", STANDBYSTART_MS0032, + $config_value{'Archive_dir'}); + $is_ready = 0; + } + } + else { + # DB クラスタが存在せず、かつアーカイブ WAL が存在する場合は + # 異常終了する + # NOTE: Standby初回起動時に不要のアーカイブWALが残存しているため + if (!$pg_dir_state->pgarch_empty){ + printlog("LOG", STANDBYSTART_MS0032, + $config_value{'Archive_dir'}); + $is_ready = 0; + } + } + } + + # 相手のノードより後のセグメントファイルがアーカイブに存在する場合は + # 異常終了する + if (!$pg_dir_state->pgarch_empty && $is_ready + && $check_result->my_archive_is_over) { + printlog("LOG", STANDBYSTART_MS0076); + $is_ready = 0; + } + + # 全モード共通のチェックを実施 + if (validate_check_result_common($check_result) != 0) { + $is_ready = 0; + } + + if ($is_ready) { + $available_mode |= PG_BASEBACKUP; + printlog("LOG", STANDBYSTART_MS0007); + } else { + printlog("LOG", STANDBYSTART_MS0005); + } + } else { + printlog("LOG", STANDBYSTART_MS0086); + } + $operation_num++; + + printlog("DEBUG", "specified_mode : $specified_mode\n"); + printlog("DEBUG", "available_mode : $available_mode\n"); + + ## 起動可能なモードが存在しない場合は選択肢が存在しない旨のメッセージを表示して終了する + if (!$available_mode) { + printlog("LOG", STANDBYSTART_MS0058); + exit(0); + } + + if ($check_only_mode || $interactive_mode) { + my $select_items = ""; + + ## 起動可能なモードの表示 + printlog("LOG", STANDBYSTART_MS0059); + + if ($available_mode & NORMAL) { + printlog("LOG", STANDBYSTART_MS0060); + $select_items .= "n/"; + } elsif ($available_mode & PG_REWIND) { + printlog("LOG", STANDBYSTART_MS0061); + $select_items .= "r/"; + } + if ($available_mode & PG_BASEBACKUP) { + printlog("LOG", STANDBYSTART_MS0062); + $select_items .= "b/"; + } + + # チェックモードの場合は終了する + if ($check_only_mode) { + exit(0); + } + + printlog("LOG", STANDBYSTART_MS0063); + $select_items .= "q"; + + while(1) { + printlog("LOG", STANDBYSTART_MS0064, $select_items); + + # 入力処理 + my $input = ; + chomp $input; + if ($input =~ m/^n$/i && $available_mode & NORMAL){ + $specified_mode = NORMAL; + last; + } elsif ($input =~ m/^r$/i && $available_mode & PG_REWIND){ + $specified_mode = PG_REWIND; + last; + } elsif ($input =~ m/^b$/i && $available_mode & PG_BASEBACKUP){ + $specified_mode = PG_BASEBACKUP; + last; + } elsif ($input =~ m/^q$/i){ + printlog("LOG", STANDBYSTART_MS0065); + exit(0); + } else { + printlog("LOG", STANDBYSTART_MS0066); + } + } + } + + ### IC-LAN が接続されていることを確認 ### + # 自身のノードと相手先のノードの IC-LAN IP アドレスを取得して接続確認を行う + # dry runの時は実際に確認を行わない + printlog("LOG", STANDBYSTART_MS0011, $operation_num++); + + if (!$dry_run_mode) { + foreach my $ipaddr (@another_iclan_ipaddr){ + `$PING -c 5 $ipaddr`; + $exit_code = $? >> 8; + if ($exit_code){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0012); + } + } + } + printlog("LOG", STANDBYSTART_MS0007); + + # 起動モード毎の処理 + # + # ・各種変数の説明 + # - $specified_mode : ユーザが指定した起動モード + # - $available_mode : 起動可能な起動モード + # - $startup_mode : 実際に起動する起動モード (必ず1ビット) + # + # ・startup_mode_mapの説明 + # - 配列番号 : ユーザが指定した起動モードの中で選択可能なモード + # ($specified_modeと$available_modeの論理積) + # - 配列要素 : $startup_modeの候補 + # + # +------------+---------------------+ + # | 要素番号 | 配列要素 | + # +------------+---------------------+ + # | 000 (なし) | 000 (NONE) | + # | 001 ( n ) | 001 (NORMAL) | + # | 010 ( r ) | 010 (PG_REWIND) | + # | 011 ( nr ) | 001 (NORMAL) | + # | 100 ( b ) | 100 (PG_BASEBACKUP) | + # | 101 ( nb ) | 001 (NORMAL) | + # | 110 ( rb ) | 010 (PG_REWIND) | + # | 111 ( nrb) | 001 (NORMAL) | + # +------------+---------------------+ + my @startup_mode_map = (NONE,NORMAL,PG_REWIND,NORMAL,PG_BASEBACKUP,NORMAL,PG_REWIND,NORMAL); + + # ユーザ指定したモードで、起動可能なものを選択する + $startup_mode = $startup_mode_map[$specified_mode & $available_mode]; + + if ($startup_mode == NORMAL) { + ## 既存のDBクラスタを用いたStandbyの起動の場合の処理 ### + if (!$interactive_mode) { + printlog("LOG", STANDBYSTART_MS0067); + } + + } elsif ($startup_mode == PG_REWIND) { + ## pg_rewind実行後Standby起動の場合の処理 ### + if (!$interactive_mode) { + printlog("LOG", STANDBYSTART_MS0068); + } + + ### 巻き戻し ### + printlog("LOG", STANDBYSTART_MS0070, $operation_num++); + printlog("LOG", STANDBYSTART_MS0085, $my_data->current_xlog_location, + $another_data->branch_lsn); + printlog("LOG", STANDBYSTART_MS0071); + + ### 相手のノードにcheckpointを実行 ### + if ($another_data->history_tli != $another_data->controldata("Latest checkpoint's TimeLineID") + && !$dry_run_mode) { + printlog("DEBUG", "execute \"checkpoint\" to peer node\n"); + $command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"CHECKPOINT\\\" 2> /dev/null \""; + @results = ssh_exec_command($ssh_info, $command); + } + + ### pg_rewindが相手のノードに接続するための設定 ### + if (!$dry_run_mode) { + # 使用するポートの検索 + my $port; + foreach ($another_data->pgport .. 60000) { + if ( my $socket = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $_, + Timeout => '1')) { + $port = $_; + close($socket); + last; + } + } + + # ポートフォワードの準備 + my $ssh; + if ($ssh_info->pass eq "") { + $ssh = Net::OpenSSH->new( + $config_value{'Another_D_LAN_IPAddress'}, + user => $ssh_info->user, + master_stderr_discard => 1); + } else { + $ssh = Net::OpenSSH->new( + $config_value{'Another_D_LAN_IPAddress'}, + user => $ssh_info->user, password => $ssh_info->pass, + master_stderr_discard => 1); + } + $ssh->error and printlog("ERROR", STANDBYSTART_MS0073, $ssh->error); + my $peer_port = $another_data->pgport; + my @out = $ssh->open_ex({stdin_pipe => 1, stdout_pipe => 1, stderr_pipe => 1, ssh_opts => "-L127.0.0.1:$port:127.0.0.1:$peer_port"}); + $ssh->error and printlog("ERROR", STANDBYSTART_MS0073, $ssh->error); + + ### pg_rewind実行 ### + $command = "$SU - $pg_command_user -c \"export LANG=C;$command_path{'pg_rewind'} -c -D $primary_cib_value{'pgdata'} --source-server='host=127.0.0.1 port=$port user=$pg_command_user' -P 2>&1\""; + # pg_rewindはエラーも標準出力に表示するため、エラー時の情報を + # 取得するため、exec_commandは使用しない + $result = `$command`; + $exit_code = $? >> 8; + if ($exit_code != 0) { + if ($result =~ /server closed the connection unexpectedly/){ + printlog("DEBUG", $result); + printlog("LOG", STANDBYSTART_MS0074); + } else { + printlog("LOG", $result); + } + printlog("ERROR", STANDBYSTART_MS0005); + } + printlog("LOG", $result); + + ### pg_rewindが相手のノードに接続するための設定の削除 ### + undef $ssh; + + ### 空のWALファイルと archive_status の .ready ファイルの削除 ### + # pg_rewind実行中に相手ノードから削除されたファイルは自身のノードに + # 空ファイルとして作成されることがある。これがWALファイルと + # それに対応するarchive_status の .ready ファイルの両方に該当した場合、 + # そのままStandbyを起動すると空のアーカイブWALが作成されてしまうため、 + # 当該ファイルを削除することで回避する + my $archive_status_dir = + File::Spec->catdir(($primary_cib_value{'pgdata'}, "pg_wal", "archive_status")); + # 自身のノードの archive_status から .ready ファイルを抽出し、 + # 対応するWALファイルが存在し、かつ不正なサイズである場合はこれらを削除する + opendir(my $dir, $archive_status_dir) || die "Failed to open archive_status directory: $archive_status_dir"; + foreach my $ready_file (grep { $_ =~ /^[0-9A-F]{24}\.ready$/ } readdir($dir)) { + my ($xlog_file) = $ready_file =~ /([0-9A-F]{24})/; + my $xlog_file_path = + File::Spec->catfile(($primary_cib_value{'pgdata'}, "pg_wal"), $xlog_file); + + if (-f $xlog_file_path && -s $xlog_file_path != $another_data->controldata("Bytes per WAL segment")) { + my $ready_file_path = File::Spec->catfile($archive_status_dir, $ready_file); + unlink($ready_file_path); + unlink($xlog_file_path); + } + } + closedir($dir); + } + + printlog("LOG", STANDBYSTART_MS0007); + + } elsif ($startup_mode == PG_BASEBACKUP) { + ## pg_basebackup実行後Standby起動の場合の処理 ### + if (!$interactive_mode) { + printlog("LOG", STANDBYSTART_MS0069); + } + + printlog("LOG", STANDBYSTART_MS0015, $operation_num++); + + # Standby 側の DB クラスタを削除 + if (!$dry_run_mode) { + exec_command("$SU - $pg_command_user -c \"$RM -rf $primary_cib_value{'pgdata'} \""); + + # Primary の pg_wal の場所を取得し、Standby の pg_wal を削除 + # ※readlink コマンドは、引数に指定されたファイルにシンボリックリンクが存在する場合、コマンドの戻り値に"0"を返す + # ※戻り値が"0"の場合のみ、pg_wal を削除する + @results = ssh_exec_command($ssh_info, "$READLINK $primary_cib_value{'pgdata'}/pg_wal "); + $exit_code = $results[1]; + if ($exit_code != 0 && $exit_code != 1){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0018); + } + elsif (!$exit_code){ + chomp $results[0]; + $pg_xlog_dir = $results[0]; + exec_command("$SU - $pg_command_user -c \"$RM -rf $pg_xlog_dir/*\""); + exec_command("$SU - $pg_command_user -c \" $command_path{'pg_basebackup'} --no-manifest -h $config_value{'Another_D_LAN_IPAddress'} -U $primary_cib_value{'repuser'} -D $primary_cib_value{'pgdata'} --waldir=$pg_xlog_dir -X none -P \""); + } + else { + $pg_xlog_dir = + File::Spec->catdir(($primary_cib_value{'pgdata'}, "pg_wal")); + exec_command("$SU - $pg_command_user -c \" $command_path{'pg_basebackup'} --no-manifest -h $config_value{'Another_D_LAN_IPAddress'} -U $primary_cib_value{'repuser'} -D $primary_cib_value{'pgdata'} -X none -P \""); + } + + # pg_wal が空であることを確認 + opendir(my $dir, $pg_xlog_dir) || die "Failed to open WAL directory: $pg_xlog_dir"; + if (grep { $_ ne '.' && $_ ne '..' && $_ ne 'archive_status' } readdir $dir) { + printlog("ERROR", STANDBYSTART_MS0080); + } + closedir($dir); + } + printlog("LOG", STANDBYSTART_MS0007); + + } elsif ($startup_mode == NONE) { + printlog("ERROR", STANDBYSTART_MS0058); + } else { + ## BUG ## + # 起動できる選択肢がないとして処理が終わっているはずなので、 + # ここに来るのはバグ以外ない + printlog("ERROR", STANDBYSTART_MS0099); + } + + + ### Primary のアーカイブディレクトリと同期 ### + # shared-archive-directory オプションが指定されている場合は実行しない + if (!$shared_archive_directory_mode) { + printlog("LOG", STANDBYSTART_MS0019, $operation_num++); + if (!$dry_run_mode) { + + if (archive_sync($ssh_info, $config_value{'Archive_dir'}, $another_data->controldata("Bytes per WAL segment")) != 0) { + printlog("ERROR", STANDBYSTART_MS0005); + } + + # normalモードの場合はアーカイブ欠損の発生をチェック + if ($startup_mode == NORMAL) { + if (check_archive_lost($primary_cib_value{'pgdata'}, + $config_value{'Archive_dir'}, + $my_data->controldata("Latest checkpoint's REDO WAL file"), + $pg_command_user, + $command_path{'pg_waldump'})) { + printlog("ERROR", STANDBYSTART_MS0083); + } + } + } + printlog("LOG", STANDBYSTART_MS0007); + } + + ### DBクラスタより、standby.signal及びrecovery.signalを削除 ### + if (!$dry_run_mode) { + exec_command("$SU - $pg_command_user -c \"$RM -f $primary_cib_value{'pgdata'}/\"".STANDBY_SIGNAL); + exec_command("$SU - $pg_command_user -c \"$RM -f $primary_cib_value{'pgdata'}/\"".RECOVERY_SIGNAL); + } + + ### DBクラスタより、standby.signal及びrecovery.signalを削除 ### + if (!$dry_run_mode) { + exec_command("$SU - $pg_command_user -c \"$RM -f $primary_cib_value{'pgdata'}/\"".STANDBY_SIGNAL); + exec_command("$SU - $pg_command_user -c \"$RM -f $primary_cib_value{'pgdata'}/\"".RECOVERY_SIGNAL); + } + + ### アーカイブリカバリ対象の WAL セグメント数を算出 + # backup_label が存在する場合は backup_label からリカバリが開始される WAL ファイル名を取得する + my $start_wal_filename; + my $backup_label_path = $primary_cib_value{'pgdata'}."/backup_label"; + if (-f $backup_label_path){ + $result = `$CAT $backup_label_path`; + my @backup_label_string = split(/\n/, $result); + $start_wal_filename = get_start_wal_filename($backup_label_path, @backup_label_string); + } + # backup_label が存在しない場合は pg_control から取得する + else { + $start_wal_filename = $my_data->controldata("Latest checkpoint's REDO WAL file"); + } + + my $wal_segment_num = get_restore_archivewal_num($primary_cib_value{'pgdata'}, $config_value{'Archive_dir'}, $start_wal_filename); + + ### Pacemaker 起動 ### + printlog("LOG", STANDBYSTART_MS0021, $operation_num++, $wal_segment_num); + if (!$dry_run_mode) { + exec_command("$PCS cluster start"); + } + printlog("LOG", STANDBYSTART_MS0007); + + ### Standby の起動確認 ### + printlog("LOG", STANDBYSTART_MS0022, $operation_num++); + + # Pacemaker 及び リソースの起動確認 + # 前回起動中に自ノードで PostgreSQL の制御エラーが発生していた場合、Pacemaker 起動から暫くの間は + # pcs status --fullの結果に PostgreSQL の制御エラー情報(Failed actions)が残っている可能性がある。そのため、 + # 起動確認の前にRA(pgsql) のモニタ間隔以上のディレイをおき、誤検知しないようにする。 + my $error_code = 0; + if (!$dry_run_mode) { + sleep $monitor_delay; + while (1){ + if ($wait_time >= $timeout){ + printlog("LOG", STANDBYSTART_MS0005); + printlog("LOG", STANDBYSTART_MS0046, $timeout); + if ($starting_resource eq "PostgreSQL" && $error_code == 2){ + printlog("ERROR", STANDBYSTART_MS0047, $starting_resource); + } + else { + printlog("ERROR", STANDBYSTART_MS0048, $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", STANDBYSTART_MS0005); + printlog("ERROR", STANDBYSTART_MS0024); + } + + # PING のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'PING_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'PING_ResourceID'})){ + $starting_resource = "PING"; + next; + } + + # STORAGE-MON のリソース ID 指定有りの場合、起動確認を行なう + # 起動確認の処理内容が同じため、ping_runningを呼び出して確認を行なう + if ($config_value{'STORAGE_MON_ResourceID'} && !ping_running($node_value{'my_node'}, $config_value{'STORAGE_MON_ResourceID'})){ + $starting_resource = "STORAGE_MON"; + next; + } + + # STONITH 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'STONITH_ResourceID'} && !stonith_running($node_value{'my_node'}, $config_value{'STONITH_ResourceID'})){ + $starting_resource = "STONITH"; + next; + } + + if (!standby_running($node_value{'my_node'}, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'}, \$error_code)){ + $starting_resource = "PostgreSQL"; + next; + } + + # printlog("LOG", "IPADDR_STANDBY"); + # IPADDR_STANDBY 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_STANDBY_ResourceID'} && !vip_running($node_value{'my_node'}, $config_value{'IPADDR_STANDBY_ResourceID'})){ + $starting_resource = "IPADDR_STANDBY"; + next; + } + + # pcs status --full の結果が全て揃ったら無限ループを抜ける + last; + } + } + + printlog("LOG", STANDBYSTART_MS0007); + printlog("LOG", STANDBYSTART_MS0029, $node_value{'my_node'}); + exit(0); +} + +sub get_history_value { + my ($history_file, $ssh_info) = @_; + my $ext; + my @results; + my $result; + my @ret; + + ($ext=$history_file) =~ s/^.*\.(.*)$/$1/; + if ($ext eq "history"){ # 圧縮なし + if ($ssh_info) { + @results = ssh_exec_command($ssh_info, "$CAT $history_file"); + @ret = split(/\n/, $results[0]); + } else { + $result = exec_command("$CAT $history_file"); + @ret = split(/\n/, $result); + } + } + elsif ($ext eq "gz"){ # GZIP圧縮 + if ($ssh_info) { + @results = ssh_exec_command($ssh_info, "$GZIP -dc $history_file"); + @ret = split(/\n/, $results[0]); + } else { + $result = exec_command("$GZIP -dc $history_file"); + @ret = split(/\n/, $result); + } + } + elsif ($ext eq "bz2"){ # BZIP2圧縮 + if ($ssh_info) { + @results = ssh_exec_command($ssh_info, "$BZIP2 -dc $history_file"); + @ret = split(/\n/, $results[0]); + } else { + $result = exec_command("$BZIP2 -dc $history_file"); + @ret = split(/\n/, $result); + } + } + else{ # 未サポートの拡張子 + printlog("ERROR", STANDBYSTART_MS0049, $ext); + } + + return @ret; +} + +sub common_cluster_check { + my ($check_result, $pg_dir_state, $my_data, $another_data) = @_; + my $is_ready = 1; + + # (STEP 2)DB クラスタのバージョンと PostgreSQL サーバのバージョンが一致していることを確認 B) + if (!$check_result->version_is_match) { + if (!$my_data->cluster_version) { + printlog("LOG", STANDBYSTART_MS0035); + } else { + printlog("LOG", STANDBYSTART_MS0036, $my_data->cluster_version, + $my_data->pg_version); + } + $is_ready = 0; + } + + # (STEP 3)自身のノードと相手のノードのデータベース識別子が一致していることを確認 K) + if (!$check_result->match_database_id) { + printlog("LOG", STANDBYSTART_MS0037, + $my_data->controldata("Database system identifier"), + $another_data->controldata("Database system identifier")); + $is_ready = 0; + } + + # (STEP 4)自身のノードの TLI が相手のノードを超過していないことを確認 L) + if ($check_result->gt_another_tli) { + printlog("LOG", STANDBYSTART_MS0038, + $my_data->controldata("Latest checkpoint's TimeLineID"), + $another_data->controldata("Latest checkpoint's TimeLineID")); + $is_ready = 0; + } + + # (STEP7)自身ノードの TLI が過去に通っていることを確認 I) + if (!$check_result->pass_my_tli) { + printlog("LOG", STANDBYSTART_MS0043, $another_data->history_file); + $is_ready = 0; + } + + # TLIのhistoryファイルを確認し、分岐点が一致していることを確認 H) + if (!$check_result->same_branch_point && !$pg_dir_state->pgarch_empty) { + printlog("LOG", STANDBYSTART_MS0053, $my_data->latest_branch_tli); + $is_ready = 0; + } + + # postgresql.confに"# added by pg-rex_standby_start"がないことを確認 + if ($check_result->is_conf_add_comment) { + printlog("LOG", STANDBYSTART_MS0075); + $is_ready = 0; + } + + # 相手のノードより後のセグメントファイルがアーカイブに存在するか確認 + if ($check_result->my_archive_is_over) { + printlog("LOG", STANDBYSTART_MS0076); + $is_ready = 0; + } + + return $is_ready; +} + +sub archive_sync { + my ($ssh_info, $archive_dir, $bytes_per_wal_segment) = @_; + my $last_archivelog_name; + my $last_archivelog_path; + + if ($archive_dir !~ /\/$/) { + $archive_dir = $archive_dir."/"; + } + + # Primaryのアーカイブディレクトリ内のファイル全て + # (*.history, *.partial, *.backup, アーカイブWAL)を + # Standbyのアーカイブディレクトリに同期する + # + # アーカイブディレクトリの同期は以下の順序で行う + # アーカイブWALの同期は、Primaryのアーカイブ中に同期した場合、不完全な + # アーカイブWALが同期される可能性あるため、アーカイブWALの同期は Primary の + # アーカイブWALの方が先行している場合にのみ行う。また、アーカイブWALの + # 同期後は最終アーカイブWALの完全性を確認し、最終アーカイブWALが不完全で + # あった場合は、当該ファイルの同期をリトライする + # 1. アーカイブWAL以外のファイル(*.history, *.partial, *.backup)を同期する + # 2. アーカイブWALを同期する + + my %sync_files = get_sync_files($ssh_info, $archive_dir, $archive_dir, 0); + my %maintenance_files = (); + my %archivewal_files = (); + + # アーカイブWAL以外のファイルの同期 + while (my ($name, $size) = each(%sync_files)){ + if ($name =~ /^(.*)(\.history|\.partial|\.backup)(\.gz|\.bz2)?$/){ + $maintenance_files{$name} = $size; + } + } + receive_archive($ssh_info, $archive_dir, $bytes_per_wal_segment, \%maintenance_files); + + # アーカイブWALの同期 + # アーカイブディレクトリを比較し、Standby が先行している場合は同期しない + if (archive_compare($ssh_info, $archive_dir) > 0) { + printlog("LOG", STANDBYSTART_MS0077); + return 0; + } + while (my ($name, $size) = each(%sync_files)){ + if ($name =~ /^([0-9A-F]{24})(\.gz|\.bz2)?$/){ + $archivewal_files{$name} = $size; + } + } + receive_archive($ssh_info, $archive_dir, $bytes_per_wal_segment, \%archivewal_files); + + $last_archivelog_name = exec_command( + "$LS -1H $archive_dir | $GREP -P \"^[0-9A-F]{24}(?!\.partial\$)(?:\.[^\.]+)?\$\" | $TAIL -1"); + chomp $last_archivelog_name; + + if (!${last_archivelog_name}) { + return 0; + } + + $last_archivelog_path = + File::Spec->catfile($archive_dir, $last_archivelog_name); + + # 最終アーカイブWALが不完全である場合は同期をリトライ + if (!is_valid_xlogfile($last_archivelog_path, $bytes_per_wal_segment)) { + for (my $retry = 0; $retry < 5; $retry++) { + sleep 0.2; # 200ms + + unlink($last_archivelog_path); + my %last_archivelog = (); + $last_archivelog{$last_archivelog_name} = $bytes_per_wal_segment; + receive_archive($ssh_info, $archive_dir, $bytes_per_wal_segment, \%last_archivelog); + + if (is_valid_xlogfile($last_archivelog_path, $bytes_per_wal_segment)) { + return 0; + } + } + unlink($last_archivelog_path); + printlog("LOG", STANDBYSTART_MS0078, $last_archivelog_name); + return 1; + } + + return 0; +} + +sub archive_compare { + my ($ssh_info, $archive_dir) = @_; + my $l_last_archivelog_name; + my $r_last_archivelog_name; + my $command = "$LS -1H $archive_dir | $GREP -P \"^[0-9A-F]{24}(?!\.partial\$)(?:\.[^\.]+)?\$\" | $TAIL -1"; + my @results; + + # ローカルとリモートのアーカイブディレクトリを比較し、 + # 比較結果を以下の条件で返却する。 + # - ローカルの最終アーカイブWALの方が進んでいる場合は「1」を返却する + # - リモートの最終アーカイブWALの方が進んでいる場合は「-1」を返却する + # - ローカルとリモートの最終アーカイブWALが同一の場合は「0」を返却する + $l_last_archivelog_name = exec_command($command); + @results = ssh_exec_command($ssh_info, $command); + $r_last_archivelog_name = $results[0]; + chomp $l_last_archivelog_name; + chomp $r_last_archivelog_name; + + return $l_last_archivelog_name cmp $r_last_archivelog_name; +} + +sub is_valid_xlogfile { + my ($xlogfilepath, $bytes_per_wal_segment) = @_; + my $xlogfilename = basename($xlogfilepath); + + # 引数に指定されたパスにファイルが存在しない場合は戻り値に「0」を返却する + if (!-f $xlogfilepath) { + return 0 + } + + # 引数に指定されたWALファイルの完全性を確認する + # ファイル名から拡張子を抽出し、拡張子毎に以下の判定を行う + # - 拡張子なしの場合、ファイルサイズが制御ファイルの「Bytes per WAL segment」であるか判定する + # - 拡張子が「gz」の場合、アーカイブが完全であるかを判定する + # - 拡張子が「bz2」の場合、アーカイブが完全であるかを判定する + # 判定結果が「正」の場合は、戻り値に「1」を返却する + # 判定結果が「否」の場合は、戻り値に「0」を返却する + my ($ext) = $xlogfilename =~ /\.([^\.]+)$/; + if (!defined($ext)) { # 圧縮なし + if (-s $xlogfilepath != $bytes_per_wal_segment) { + return 0; + } + } + elsif ($ext eq "gz") { # GZIP圧縮 + `$GZIP -t $xlogfilepath 2> /dev/null`; + my $exit_code = $? >> 8; + if ($exit_code != 0) { + return 0; + } + } + elsif ($ext eq "bz2") { # BZIP2圧縮 + `$BZIP2 -t $xlogfilepath 2> /dev/null`; + my $exit_code = $? >> 8; + if ($exit_code != 0) { + return 0; + } + } + else { # 未サポートの拡張子 + printlog("LOG", STANDBYSTART_MS0079, $xlogfilename, $ext); + } + + return 1; +} + +sub validate_check_result_common { + my ($check_result) = @_; + my $ret = 0; + + # Primary のアーカイブディレクトリに Primary の現在の WAL より未来の + # WAL がアーカイブされている + if ($check_result->exists_future_archive) { + printlog("LOG", STANDBYSTART_MS0081); + $ret = 1; + } + + return $ret; +} + +sub check_dbcluster_version { + my ($my_data, $pgdata_dir, $postgres_command_path, $pg_command_user) = @_; + + my $pg_version_file = File::Spec->catfile($pgdata_dir, "PG_VERSION"); + my $dbcluster_version = `$SU $pg_command_user -c "$CAT $pg_version_file"`; + chomp $dbcluster_version; + if ($dbcluster_version) { + $my_data->cluster_version($dbcluster_version); + + my $version_num = get_pg_version_num($postgres_command_path); + $my_data->pg_version(int($version_num / 10000)); + + if ($my_data->cluster_version eq $my_data->pg_version) { + return 0; + } + } + + return 1; +} + +sub check_archive_lost { + my ($pgdata_dir, $archive_dir, $checkpoint_xlog_name, $pg_command_user, $pg_waldump_path) = @_; + my $xlog_dir = File::Spec->catdir(($pgdata_dir, "pg_wal")); + my $last_archivelog_name; + my $last_archivelog_segno; + my $last_xlog_name; + my $latest_xlog_segno; + my $segno; + my $command; + my $result; + my $regex; + my $xlog_name; + my $ready_file_path; + my $ready_name; + + # 最新アーカイブWALの次のセグメント~最新オンラインWALの1つ前の + # セグメントを対象範囲として、範囲内の全てのセグメントファイルが存在し、 + # かつアーカイブステータスが「ready」であることを確認する + + # 最新アーカイブWALのファイル名からセグメント番号を取得 + $last_archivelog_name = `$LS -1H $archive_dir | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1`; + chomp($last_archivelog_name); + if (!$last_archivelog_name) { + # アーカイブWALが存在しない場合は欠損が起きないのでチェック終了 + return 0; + } + $last_archivelog_name =~ /[0-9A-F]{8}([0-9A-F]{8})([0-9A-F]{8})/; + $last_archivelog_segno = hex($1) * 0xFF + hex($2); + + # 最新オンラインWALを特定し、当該ファイルのセグメント番号を取得 + $last_xlog_name = `$LS -1H $xlog_dir | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1`; + chomp($last_xlog_name); + if (!$last_archivelog_name) { + # オンラインWALが存在しない場合は欠損が起きないのでチェック終了 + return 0; + } + $result = getLatestLsn($checkpoint_xlog_name, $last_xlog_name, $xlog_dir, $pg_waldump_path, $pg_command_user); + $result =~ /^([0-9A-F]+)\/([0-9A-F]+)$/; + $latest_xlog_segno = hex($1) * 0xFF + (hex($2) >> 24); + + # 最新アーカイブWALの次のセグメント~最新オンラインWALの1つ前のセグメントを + # 対象範囲として、セグメントファイルおよびアーカイブステータスを確認 + for ($segno = $last_archivelog_segno + 1; $segno < $latest_xlog_segno; $segno++) { + # セグメントファイルの存在を確認 + # (複数のTLIが存在する場合は最新のTLIのものを抽出) + $regex = sprintf("[0-9A-F]{8}%08X%08X", $segno / 0xFF, $segno % 0xFF); + $xlog_name = `$LS -1H $xlog_dir | $GREP -P $regex | $TAIL -1`; + if (!$xlog_name) { + # セグメントファイルが存在しない + return 1; + } + + # セグメントファイルのアーカイブステータスを確認 + chomp($xlog_name); + $ready_name = sprintf("%s.ready", $xlog_name); + $ready_file_path = File::Spec->catfile(($xlog_dir, "archive_status"), $ready_name); + if (!-f $ready_file_path) { + # アーカイブステータスが「ready」ではない(.readyが存在しない) + return 1; + } + } + + return 0; +} + +# 最新のLSNを取得する +sub getLatestLsn { + my ($latest_checkpoint_walfile_name, $latest_walfile_name, $pg_wal_dir, $pg_waldump_path, $pg_command_user) = @_; + + # WALファイル名からTLIを取得 + my $latest_checkpoint_tli = substr($latest_checkpoint_walfile_name,0,8); + my $latest_tli = substr($latest_walfile_name,0,8); + + # pg_controlのTLIが最新のTLIより小さい場合、 + # pg_waldumpの読み取り開始ファイルを最新のTLIのpg_walに存在する最古のWALセグメントファイルにする + if ($latest_checkpoint_tli lt $latest_tli){ + my @walfile_list = (); + + # 最新のTLIのpg_walに存在する最古のWALセグメントファイルの取得 + opendir(my $dir, $pg_wal_dir) || die "Failed to open pg_wal directory: $pg_wal_dir"; + foreach my $walfile (grep { $_ =~ /^$latest_tli[0-9A-F]{16}$/ } readdir($dir)) { + push(@walfile_list, $walfile); + next; + } + closedir($dir); + + if(@walfile_list){ + @walfile_list = sort @walfile_list; + $latest_checkpoint_walfile_name = $walfile_list[0]; + } else { + # pg_walにWALセグメントファイルが存在しない + printlog("ERROR", STANDBYSTART_MS0099); + } + } + + my $waldump_walfilepath_start = "$pg_wal_dir/$latest_checkpoint_walfile_name"; + my $waldump_walfilepath_end = "$pg_wal_dir/$latest_walfile_name"; + + my $command = "$SU - $pg_command_user -c \"$pg_waldump_path $waldump_walfilepath_start $waldump_walfilepath_end 2> /dev/null | $TAIL -1\""; + + my $result = exec_command($command); + if ($result =~ /.*lsn:\s+([0-9A-F\/]+).*/){ + return $1; + } else { + # pg_waldumpで最新のLSNの取得に失敗 + printlog("ERROR", STANDBYSTART_MS0099); + } +} + + diff --git a/pg-rex_operation_tools/bin/pg-rex_stop b/pg-rex_operation_tools/bin/pg-rex_stop new file mode 100644 index 0000000..cfe52d7 --- /dev/null +++ b/pg-rex_operation_tools/bin/pg-rex_stop @@ -0,0 +1,208 @@ +#!/usr/bin/perl +##################################################################### +# Function: pg-rex_stop +# +# +# 概要: +# PG-REX での停止実行ツール。 +# 手順の簡易化を目的として作成している。 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use sigtrap qw(die normal-signals error-signals); +use Getopt::Long; +use PGRex::command; +use PGRex::common qw(pacemaker_running standby_running read_config + exec_command get_pg_command_path check_user printlog + check_support_version create_pid_file unlink_pid_file); + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + } + } + + create_pid_file(); +}; + +END { + unlink_pid_file(); +}; + +$SIG{INT} = sub { + printlog("LOG", STOP_MS0001); +}; + +main(); + +1; + +sub main{ + my $help_mode = 0; + my $version_mode = 0; + my $fast_mode = 0; + my $config_path = CONFIG_PATH.CONFIG_FILENAME; + my %config_value; + my $my_node = ""; + my $another_node = ""; + my %command_path; + my $pg_command_user = "postgres"; + my $stop_target = "Stopped"; + my $timeout = 300; + my $monitor_time = 2; + my $wait_time = 0; + my $result; + my @results; + my $exit_code; + my $kill_when_no_data = 0; + my $myself; + + # 標準出力が途中で停止するのを防ぐ為に + # 標準出力のオートフラッシュを有効化 + $| = 1; + + # オプション解析 + foreach ( @ARGV ){ + if ( "$_" eq "-" || "$_" eq "--" ){ + $help_mode = 1; + } + } + $exit_code = GetOptions('help' => \$help_mode, + 'additional_information' => \$PGRex::common::additional_information_mode, + 'version' => \$version_mode, + 'fast' => \$fast_mode); + $myself = $0; + $myself =~ s/.*\///g; + if ($help_mode || !$exit_code){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + print "\n"; + printlog("USAGE", STOP_USAGE); + exit(0); + } + if ($version_mode){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + exit(0); + } + + # コマンドを実行しているマシンのノード名を取得 + # 実行ユーザの確認 + 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'}); + + ### スクリプト実行準備 ### + + $my_node = exec_command("$UNAME -n"); + chomp $my_node; + + ### Pacemaker 停止準備 ### + # Pacemaker 及び Corosync のプロセスを確認 + # Pacemaker 及び Corosync のプロセスが無かったら、処理を終了 + # ※Pacemaker または Corosync が停止していて、PostgreSQL が起動中の場合も処理を終了させる + if (!pacemaker_running()){ + printlog("LOG", STOP_MS0004); + exit(0); + } + + # 相手ノードのノード名を取得する + # Pacemaker 1.1.13の場合、crm_node の実行結果は下記となる。 + # <クラスタのノードID> <ノード名> + # Pacemaker 1.1.14の場合、crm_node の実行結果は下記となる。 + # <クラスタのノードID> <ノード名> <状態> + @results = split(/\n/, exec_command("$CRM_NODE -l")); + foreach my $record (@results){ + my @field_list = split(/\s/, $record); + if($field_list[1] ne $my_node){ + $another_node = $field_list[1]; + } + } + + $result = `$SU - $pg_command_user -c \" $command_path{'psql'} -t -c \\\"SELECT pg_is_in_recovery(); \\\" 2> /dev/null\"`; + chomp $result; + # SQL result format : + $result =~ s/\s//g; + if ($result eq "f"){ + $stop_target = "Primary"; + printlog("LOG", STOP_MS0006); + } + elsif ($result eq "t") { + $stop_target = "Standby"; + printlog("LOG", STOP_MS0007); + } + else { + printlog("LOG", STOP_MS0005); + } + + # Primary を停止する場合でかつ Standby が起動中の場合、ユーザに確認 + if ($stop_target eq "Primary" && $another_node && standby_running($another_node, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'})){ + printlog("LOG", STOP_MS0018); + printlog("LOG", STOP_MS0019); + printlog("LOG", STOP_MS0020); + my $input = ; + chomp $input; + if ($input !~ m/^y$/i) { + printlog("LOG", STOP_MS0009); + exit(0); + } + } + + # CHECKPOINT処理と sync コマンドの実行 + if (!$fast_mode){ + `$SU - $pg_command_user -c \"$command_path{'psql'} -c \\\"CHECKPOINT\\\"\" 2> /dev/null`; + `$SYNC`; + } + + ### Pacemaker 停止 ### + printlog("LOG", STOP_MS0010); + exec_command("$PCS cluster stop --force"); + printlog("LOG", STOP_MS0011); + + ### Pacemaker 停止確認 ### + printlog("LOG", STOP_MS0012); + # プロセスの確認 + while (1){ + # Pacemaker、Corosync のプロセス確認ができた場合、無限ループを抜ける + if (!pacemaker_running()){ + last; + } + + if ($wait_time >= $timeout){ + printlog("LOG", STOP_MS0013); + printlog("ERROR", STOP_MS0014, $timeout); + } + + sleep $monitor_time; + $wait_time += $monitor_time; + } + printlog("LOG", STOP_MS0011); + + if ($stop_target eq "Stopped"){ + printlog("LOG", STOP_MS0015, $my_node); + } + else { + printlog("LOG", STOP_MS0016, $stop_target, $my_node); + } + + exit(0); +} diff --git a/pg-rex_operation_tools/bin/pg-rex_switchover b/pg-rex_operation_tools/bin/pg-rex_switchover new file mode 100644 index 0000000..9ed0f7b --- /dev/null +++ b/pg-rex_operation_tools/bin/pg-rex_switchover @@ -0,0 +1,562 @@ +#!/usr/bin/perl +##################################################################### +# Function: pg-rex_switch_over +# +# +# 概要: +# PG-REX での高速スイッチオーバ実行ツール。 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use sigtrap qw(die normal-signals error-signals); +use Getopt::Long qw(:config no_ignore_case); +use PGRex::command; +use PGRex::common qw(pacemaker_online primary_running vip_running read_config + read_cib exec_command get_pg_command_path + get_ssh_passwd check_user printlog + ssh_exec_command pacemaker_running + standby_running check_support_version + create_pid_file unlink_pid_file get_controldata_value + get_sync_files receive_archive send_archive); + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + } + } + + create_pid_file(); +}; + +END { + unlink_pid_file(); +}; + +$SIG{INT} = sub { + printlog("LOG", SWITCHOVER_MS0001); +}; + +main(); + +1; + +sub main{ + my $help_mode = 0; + my $version_mode = 0; + my %config_value; + my $config_path = CONFIG_PATH.CONFIG_FILENAME; + my $ssh_pass; + my %command_path; + my %node_value; + my $pg_command_user = "postgres"; + my $monitor_delay = 10; + my $monitor_interval = 2; + my $wait_time = 0; + my $cib_path = CIB_PATH.CIB_FILENAME; + my $kill_when_no_data = 1; + my $timeout = 300; + my $starting_resource = ""; + my $lock_file; + my $operation_num = 1; + my $hacf_path = HACF_PATH.HACF_FILENAME; + my %my_cib_value; + my $exec_user; + my $primary_node = 0; + my $current_primary_node; + my $current_standby_node; + my @results; + my $result; + my $exit_code; + my @pacemaker_alive; + my @pacemaker_dead; + my $myself; + my @sync_state; + my $command; + my $command2; + my %postgres_status; + my $print_sync_state; + + # 標準出力が途中で停止するのを防ぐ為に + # 標準出力のオートフラッシュを有効化 + $| = 1; + + # オプション解析 + + foreach ( @ARGV ){ + if ( "$_" eq "-" || "$_" eq "--" ){ + $help_mode = 1; + } + } + $exit_code = GetOptions('help' => \$help_mode, + 'additional_information' => \$PGRex::common::additional_information_mode, + 'version' => \$version_mode); + $myself = $0; + $myself =~ s/.*\///g; + if ($help_mode || !$exit_code){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + print "\n"; + printlog("USAGE", SWITCHOVER_USAGE); + exit(0); + } + if ($version_mode){ + printlog("VERSION", VERSIONINFO, $myself, VERSIONNUM); + exit(0); + } + + # 実行ユーザの確認 + check_user(); + $exec_user = exec_command("$WHOAMI"); + chomp $exec_user; + + # 環境設定ファイルの読み込み + # SWITCHOVER_MS0003 を出力する前であるが、PostgreSQL のバージョンチェックに + # 環境設定ファイルの設定情報が必要なため、この時点で読み込む + %config_value = read_config($config_path); + + # PostgreSQL のコマンドパスを取得 + %command_path = get_pg_command_path($config_value{'PGPATH'}); + + # Pacemaker と PostgreSQL がサポート対象バージョンであるかを確認 + check_support_version($command_path{'postgres'}); + + ### スクリプト実行準備 ### + + # 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("$exec_user"); + $ssh_info->pass("$ssh_pass"); + + printlog("LOG", SWITCHOVER_MS0002); + printlog("LOG", SWITCHOVER_MS0003, $operation_num++); + + # PostgreSQL のコマンドパスを取得 + %command_path = get_pg_command_path($config_value{'PGPATH'}); + + # コマンドを実行しているマシンのノード名ともう一台のノード名を取得 + %node_value = get_node($ssh_info); + + # cib.xml ファイルを読み込む + %my_cib_value = read_cib($cib_path, $config_value{'PG_REX_Primitive_ResourceID'}, $kill_when_no_data); + + printlog("LOG", SWITCHOVER_MS0004); + + # 現在のクラスタ状態を確認 + printlog("LOG", SWITCHOVER_MS0008, $operation_num++); + + # Pacemaker の稼働状態を確認 + # ローカルノードで pcs status --full を実行 + $command = "$PCS status --full 2>&1"; + $results[0] = `$command`; + $exit_code = $? >> 8; + if ($exit_code == 1){ + push(@pacemaker_dead, $node_value{'my_node'}); + } + + # ローカルノードでのpcs status --fullの実行に失敗した場合はリモートノードで実行 + if (@pacemaker_dead){ + @results = ssh_exec_command($ssh_info, $command); + $exit_code = $results[1]; + if ($exit_code == 1){ + push(@pacemaker_dead, $node_value{'another_node'}); + } + } + + # pcs status --full実行結果を解析 + @results = split (/\n/, $results[0]); + foreach my $line (@results){ + if ($line =~ /Online\:/){ + # ローカルノードで pcs status --full の実行が失敗している場合は新たに + # 判定を行わない + $result = grep /^$node_value{'my_node'}$/, @pacemaker_dead; + if (!$result){ + if($line =~ /\s$node_value{'my_node'}\s/){ + push(@pacemaker_alive, $node_value{'my_node'}); + } + else { + push(@pacemaker_dead, $node_value{'my_node'}); + } + } + # リモートノードで pcs status --full の実行が失敗している場合は新たに + # 判定を行わない + $result = grep /^$node_value{'another_node'}$/, @pacemaker_dead; + if (!$result){ + if ($line =~ /\s$node_value{'another_node'}\s/){ + push(@pacemaker_alive, $node_value{'another_node'}); + } + else { + push(@pacemaker_dead, $node_value{'another_node'}); + } + } + } + } + + # ローカルノードの PostgreSQL の稼働状態を確認 + $command = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT pg_is_in_recovery()\\\"\" 2> /dev/null"; + $command2 = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT sync_state FROM pg_stat_replication WHERE client_addr = '$config_value{'Another_D_LAN_IPAddress'}' ORDER BY sync_state DESC\\\"\" 2> /dev/null"; + $result = `$command`; + $exit_code = $? >> 8; + chomp $result; + @results = split(/\|/, $result); + if ($exit_code){ + $postgres_status{'my_node'} = "Stopped"; + } + elsif ($results[0] eq "f"){ + $postgres_status{'my_node'} = "Primary"; + $result = `$command2`; + @sync_state = split(/\n/, $result); + } + elsif ($results[0] eq "t") { + $postgres_status{'my_node'} = "Standby"; + } + + # リモートノードの PostgreSQL の稼働状態を確認 + $command2 = "$SU - $pg_command_user -c \"$command_path{'psql'} -tAc \\\"SELECT sync_state FROM pg_stat_replication WHERE client_addr = '$config_value{'My_D_LAN_IPAddress'}' ORDER BY sync_state DESC\\\"\" 2> /dev/null"; + @results = ssh_exec_command($ssh_info, "$command"); + $exit_code = $results[1]; + chomp $results[0]; + @results = split(/\|/, $results[0]); + if ($exit_code){ + $postgres_status{'another_node'} = "Stopped"; + } + elsif ($results[0] eq "f"){ + $postgres_status{'another_node'} = "Primary"; + @results = ssh_exec_command($ssh_info, "$command2"); + @sync_state = split(/\n/, $results[0]); + } + elsif ($results[0] eq "t"){ + $postgres_status{'another_node'} = "Standby"; + } + + # PrimaryとStandbyの稼働状態を整理 + if ($postgres_status{'my_node'} eq "Primary"){ + $current_primary_node = $node_value{'my_node'}; + $primary_node = 1; + if ($postgres_status{'another_node'} eq "Primary"){ + $current_primary_node .= " ".$node_value{'another_node'}; + } + elsif ($postgres_status{'another_node'} eq "Standby"){ + $current_standby_node = $node_value{'another_node'}; + $print_sync_state = 1; + } + else { + $current_standby_node = $postgres_status{'another_node'}; + } + } + elsif ($postgres_status{'my_node'} eq "Standby"){ + $current_standby_node = $node_value{'my_node'}; + if ($postgres_status{'another_node'} eq "Primary"){ + $current_primary_node = $node_value{'another_node'}; + $print_sync_state = 1; + } + elsif ($postgres_status{'another_node'} eq "Standby"){ + $current_standby_node .= " ".$node_value{'another_node'}; + } + else { + $current_primary_node = $postgres_status{'another_node'}; + } + } + else { + if ($postgres_status{'another_node'} eq "Primary"){ + $current_primary_node = $node_value{'another_node'}; + $current_standby_node = $postgres_status{'my_node'}; + } + elsif ($postgres_status{'another_node'} eq "Standby"){ + $current_standby_node = $node_value{'another_node'}; + $current_primary_node = $postgres_status{'my_node'}; + } + else { + $current_primary_node = $postgres_status{'another_node'}; + $current_standby_node = $postgres_status{'my_node'}; + } + } + + # 現在のクラスタ状態でノード切り替えが可能であるかを確認し、 + # ノード切り替えが実行可能でない場合は現在のクラスタ状態を出力して異常終了 + if (@pacemaker_dead || + !($current_primary_node && $current_standby_node) || + ($current_primary_node eq "Stopped" || $current_standby_node eq "Stopped") || + (@sync_state == 0) || + ($sync_state[0] ne "sync")){ + printlog("LOG", SWITCHOVER_MS0005); + printlog("LOG", SWITCHOVER_MS0009); + print "\n"; + printlog("LOG", SWITCHOVER_MS0010); + print " "."Pacemaker\n"; + if (@pacemaker_alive){ + print " "."Online :"; + foreach (@pacemaker_alive){ + print " ".$_; + } + print "\n"; + } + if (@pacemaker_dead){ + print " "."OFFLINE:"; + foreach (@pacemaker_dead){ + print " ".$_; + } + print "\n"; + } + print "\n"; + print " "."PostgreSQL\n"; + if ($current_primary_node){ + print " "."Primary : $current_primary_node\n"; + } + if ($current_standby_node){ + print " "."Standby : $current_standby_node"; + if ($print_sync_state && @sync_state == 0){ + print " "."(not replication state)\n"; + } + elsif ($print_sync_state && @sync_state != 0){ + print " "."($sync_state[0])\n"; + } + else { + print "\n"; + } + } + print "\n"; + exit(1); + } + + # クラスタ状態が正常である場合は現在のクラスタ状態を出力して + # 実行するか否かを確認 + print "\n"; + printlog("LOG", SWITCHOVER_MS0011); + print " Primary : $current_primary_node -> $current_standby_node\n"; + print " Standby : $current_standby_node -> $current_primary_node\n"; + print "\n"; + printlog("LOG", SWITCHOVER_MS0012); + if (@sync_state >= 2) { + printlog("LOG", SWITCHOVER_MS0013, $current_standby_node); + } + printlog("LOG", SWITCHOVER_MS0014); + + my $input = ; + chomp $input; + if ($input !~ m/^y$/i){ + printlog("LOG", SWITCHOVER_MS0015); + exit(0); + } + print "\n"; + + # ノード切り替えを実行 + printlog("LOG", SWITCHOVER_MS0018); + + # Pacemaker の監視を一時停止する + printlog("LOG", SWITCHOVER_MS0019, $operation_num++); + exec_command("$PCS property set maintenance-mode=true 2> /dev/null"); + printlog("LOG", SWITCHOVER_MS0004); + + # Primary ノードの PostgreSQL を停止する + printlog("LOG", SWITCHOVER_MS0020, $operation_num++, $current_primary_node); + $command = "$SU - $pg_command_user -c \"$my_cib_value{'pgctl'} stop -m fast -t 600 -D $my_cib_value{'pgdata'}\""; + if ($primary_node){ + `$command`; + $exit_code = $? >> 8; + } + else { + @results = ssh_exec_command($ssh_info, $command); + $exit_code = $results[1]; + } + + if ($exit_code){ + printlog("LOG", SWITCHOVER_MS0005); + } + else { + printlog("LOG", SWITCHOVER_MS0004); + } + + # Pacemaker の監視を再開する + printlog("LOG", SWITCHOVER_MS0021, $operation_num++); + exec_command("$PCS property set maintenance-mode= 2> /dev/null"); + printlog("LOG", SWITCHOVER_MS0004); + + # Primary が故障検知されたことを確認 + my $tmp_node; + $tmp_node = $current_primary_node; + $current_primary_node = $current_standby_node; + $current_standby_node = $tmp_node; + + printlog("LOG", SWITCHOVER_MS0022, $operation_num++, $current_primary_node); + + while (1){ + if ($wait_time >= $timeout){ + printlog("LOG", SWITCHOVER_MS0005); + printlog("ERROR", SWITCHOVER_MS0023, $timeout, $starting_resource); + } + sleep $monitor_interval; + $wait_time += $monitor_interval; + + if (!pacemaker_online($current_primary_node)){ + $starting_resource = "Pacemaker ($current_primary_node)"; + next; + } + + if (!primary_running($current_primary_node, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'})){ + $starting_resource = "Primary ($current_primary_node)"; + next; + } + + # IPADDR_PRIMARY のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_PRIMARY_ResourceID'} && !vip_running($current_primary_node, $config_value{'IPADDR_PRIMARY_ResourceID'})){ + $starting_resource = "IPADDR_PRIMARY ($current_primary_node)"; + next; + } + + # IPADDR_REPLICATION のリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_REPLICATION_ResourceID'} && !vip_running($current_primary_node, $config_value{'IPADDR_REPLICATION_ResourceID'})){ + $starting_resource = "IPADDR_REPLICATION ($current_primary_node)"; + next; + } + + # IPADDR_STANDBY 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_STANDBY_ResourceID'} && !vip_running($current_primary_node, $config_value{'IPADDR_STANDBY_ResourceID'})){ + $starting_resource = "IPADDR_STANDBY ($current_primary_node)"; + next; + } + # pcs status --full の結果が全て揃ったら無限ループを抜ける + last; + } + + print "\n"; + printlog("LOG", SWITCHOVER_MS0033, $current_primary_node); + print "\n"; + + # 元 Primary ノードの Pacemaker を停止 + printlog("LOG", SWITCHOVER_MS0026, $operation_num++, $current_standby_node); + switch_exec_command($primary_node, $ssh_info, "$PCS cluster stop --force"); + + ### Pacemaker 停止確認 ### + # Pacemaker 、Corosync のプロセス確認ができた場合、無限ループを抜ける + $wait_time = 0; + while (1){ + # 元 Primary ノードがローカルノードの場合 + if ($primary_node){ + if (!pacemaker_running()){ + last; + } + } + # 元 Primary ノードがリモートノードの場合 + else{ + if (!pacemaker_running($ssh_info)){ + last; + } + } + if ($wait_time >= $timeout){ + printlog("LOG", SWITCHOVER_MS0005); + printlog("ERROR", SWITCHOVER_MS0027, $timeout); + } + sleep $monitor_interval; + $wait_time += $monitor_interval; + } + + printlog("LOG", SWITCHOVER_MS0004); + + # 元 Primary ノードで Standby の再組み込みを実行 + printlog("LOG", SWITCHOVER_MS0028, $operation_num++, $current_standby_node); + + # 起動禁止フラグの削除 + $lock_file = $my_cib_value{'tmpdir'}."/".LOCK_FILENAME; + @results = switch_exec_command($primary_node, $ssh_info, "$LS -l $lock_file"); + if ($results[0] =~ /[\s\S]*$lock_file[\s\S]*/){ + switch_exec_command($primary_node, $ssh_info, "$SU - $pg_command_user -c \"$RM -f $lock_file\""); + } + + my $archive_dir = $config_value{'Archive_dir'}."/"; + @results = switch_exec_command($primary_node, $ssh_info, "export LANG=C; $command_path{'pg_controldata'} $my_cib_value{'pgdata'}"); + my @controldata_strings = split(/\n/, $results[0]); + my $controldata = get_controldata_value(@controldata_strings); + my $bytes_per_wal_segment = $controldata->{"Bytes per WAL segment"}; + + # アーカイブディレクトリを同期する + # 新スレーブがローカルノードの場合 + if ($primary_node){ + my %sync_files = get_sync_files($ssh_info, $archive_dir, $archive_dir, 0); + receive_archive($ssh_info, $archive_dir, $bytes_per_wal_segment, \%sync_files); + } else { + # 新スレーブがリモートノードの場合 + my %sync_files = get_sync_files($ssh_info, $archive_dir, $archive_dir, 1); + send_archive($ssh_info, $archive_dir, $bytes_per_wal_segment, \%sync_files); + } + + # Pacemaker の起動 + switch_exec_command($primary_node, $ssh_info, "$PCS cluster start"); + + # 新 Standby が起動されたことを確認 + # pcs status --full の結果を確認 + # Pacemaker 起動から暫くの間は pcs status --full の結果に PostgreSQL の制御エラー情報(Failed Resource Actions) + # が残っている可能性がある。そのため、起動確認の前に RA(pgsql) のモニタ間隔以上のディレイをおき、 + # 誤検知しないようにする。 + sleep $monitor_delay; + $wait_time = 0; + while (1){ + if ($wait_time >= $timeout){ + printlog("LOG", SWITCHOVER_MS0005); + printlog("ERROR", SWITCHOVER_MS0023, $timeout, $starting_resource); + } + sleep $monitor_interval; + $wait_time += $monitor_interval; + + if (!pacemaker_online($current_standby_node)){ + $starting_resource = "Pacemaker ($current_standby_node)"; + next; + } + + if (!standby_running($current_standby_node, $config_value{'PG_REX_Primary_ResourceID'}, $config_value{'PG_REX_Primitive_ResourceID'})){ + $starting_resource = "Standby ($current_standby_node)"; + next; + } + + # IPADDR_STANDBY 環境有りかつリソース ID 指定有りの場合、起動確認を行なう + if ($config_value{'IPADDR_STANDBY_ResourceID'} && !vip_running($current_standby_node, $config_value{'IPADDR_STANDBY_ResourceID'})){ + $starting_resource = "IPADDR_STANDBY ($current_standby_node)"; + next; + } + # pcs status --full の結果が全て揃ったら無限ループを抜ける + last; + } + + print "\n"; + printlog("LOG", SWITCHOVER_MS0030, $current_standby_node); + print "\n"; + printlog("LOG", SWITCHOVER_MS0031); + print "\n"; + printlog("LOG", SWITCHOVER_MS0010); + print " Primary : $current_primary_node\n"; + print " Standby : $current_standby_node\n"; + print "\n"; + exit(0); +} + +sub switch_exec_command { + my ($primary_node, $ssh_info, $command) = @_; + my @results; + my $exit_code; + + if ($primary_node) { + $results[0] = exec_command($command); + } + else { + @results = ssh_exec_command($ssh_info, $command); + $exit_code = $results[1]; + if ($exit_code != 0){ + printlog("ERROR", SWITCHOVER_MS0032, $command); + } + } + return @results; +} diff --git a/pg-rex_operation_tools/lib.in/command-rhel8.pm b/pg-rex_operation_tools/lib.in/command-rhel8.pm new file mode 100644 index 0000000..539eebf --- /dev/null +++ b/pg-rex_operation_tools/lib.in/command-rhel8.pm @@ -0,0 +1,59 @@ +#!/usr/bin/perl +##################################################################### +# Function: command.pm +# +# +# 概要: +# PG-REX 運用補助ツールで使用するコマンドのパスの宣言の集まり +# (RHEL7向け) +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex::command; + +use warnings; +use strict; + +require Exporter; +our @ISA = qw (Exporter); +our @EXPORT = qw ($LS $CAT $CP $SU $RM $PS $GREP $ECHO $IFCONFIG $PING $READLINK + $MV $LN $UNAME $WHOAMI $WHICH $STTY + $CRM_ATTRIBUTE $CRM_RESOURCE $SYNC $TAIL + $CRM_NODE $GZIP $BZIP2 $LSOF $KILL $TAR $AWK $PACEMAKERD $PCS); + +our $LS = "/bin/ls"; +our $CAT = "/bin/cat"; +our $CP = "/bin/cp"; +our $SU = "/bin/su"; +our $RM = "/bin/rm"; +our $PS = "/bin/ps"; +our $GREP = "/bin/grep"; +our $ECHO = "/bin/echo"; +our $IFCONFIG = "/sbin/ifconfig"; +our $PING = "/bin/ping"; +our $READLINK = "/bin/readlink"; +our $MV = "/bin/mv"; +our $LN = "/bin/ln"; +our $UNAME = "/bin/uname"; +our $WHOAMI = "/usr/bin/whoami"; +our $WHICH = "/usr/bin/which"; +our $STTY = "/bin/stty"; +our $CRM_ATTRIBUTE = "/usr/sbin/crm_attribute"; +our $CRM_RESOURCE = "/usr/sbin/crm_resource"; +our $SYNC = "/bin/sync"; +our $TAIL = "/usr/bin/tail"; +our $CRM_NODE = "/usr/sbin/crm_node"; +our $GZIP = "/usr/bin/gzip"; +our $BZIP2 = "/usr/bin/bzip2"; +our $LSOF = "/usr/bin/lsof"; +our $KILL = "/usr/bin/kill"; +our $TAR = "/usr/bin/tar"; +our $AWK = "/usr/bin/awk"; +our $PACEMAKERD = "/usr/sbin/pacemakerd"; +our $PCS = "/usr/sbin/pcs"; + +1; diff --git a/pg-rex_operation_tools/lib/PGRex/Po/en.pm b/pg-rex_operation_tools/lib/PGRex/Po/en.pm new file mode 100644 index 0000000..3819a11 --- /dev/null +++ b/pg-rex_operation_tools/lib/PGRex/Po/en.pm @@ -0,0 +1,389 @@ +#!/usr/bin/perl +##################################################################### +# Function: en.pm +# +# +# Summary: +# List of Messages for non-Japanese locale used by PG-REX operation +# tools +# +# Note: +# none +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +use warnings; +use strict; + +use constant { + + PRIMARYSTART_USAGE => <<_PRIMARYSTART_USAGE_, +PG-REX primary start tool +This is executed on the node that will start as a primary. + +Usage: + pg-rex_primary_start [-h][-v][XmlFilePath] + +XmlFilePath reflect the xml file in the case of first startup + +Options: + -h, --help show this help, then quit + -v, --version show this version, then quit + +_PRIMARYSTART_USAGE_ + + PRIMARYSTART_MS0001 => "Ctrl-C is ignored.\n", + PRIMARYSTART_MS0002 => "No such xml file specified in the argument.\n", + PRIMARYSTART_MS0004 => "Failed to read xml file.\n", + PRIMARYSTART_MS0007 => "[0]. Checking Pacemaker and Corosync has stopped\n", + PRIMARYSTART_MS0008 => "...[NG]\n", + PRIMARYSTART_MS0009 => "Pacemaker or Corosync has already started on this node.\n", + PRIMARYSTART_MS0010 => "...[OK]\n", + PRIMARYSTART_MS0011 => "[0]. Checking primary has not started on another node\n", + PRIMARYSTART_MS0013 => "Primary has started on another node.\n", + PRIMARYSTART_MS0014 => "[0]. Checking PGSQL lock file\n", + PRIMARYSTART_MS0015 => "There is PGSQL lock file (\"[0]\").\n", + PRIMARYSTART_MS0016 => "HA cluster already exists.\n". + "These are recreated, but are you sure? (y/N) ", + PRIMARYSTART_MS0017 => "Exit.\n", + PRIMARYSTART_MS0018 => "[0]. Destroying the HA cluster\n", + PRIMARYSTART_MS0019 => "[0]. Starting Pacemaker\n", + PRIMARYSTART_MS0020 => "[0]. Reflecting xml file\n", + PRIMARYSTART_MS0021 => "[0]-seconds timeout occurred while checking the startup of the Pacemaker.\n". + "[1] is not running.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + PRIMARYSTART_MS0022 => "[0]. Checking primary has started\n", + PRIMARYSTART_MS0023 => "Resource has failed while the startup of the Pacemaker.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + PRIMARYSTART_MS0026 => "Primary has started on the node ([0]).\n", + PRIMARYSTART_MS0027 => "[0]. Checking to be able to start up as a primary\n", + PRIMARYSTART_MS0028 => "The value of [0]-data-status on this node is \"[1]\".\n". + "Pacemaker cannot start as a primary unless this value is \"LATEST\" or \"STREAMING|SYNC\", or has been registered for reasons as first startup.\n", + PRIMARYSTART_MS0029 => "root@[0]'s password:", + PRIMARYSTART_MS0030 => "\nPassword has been entered.\n", + PRIMARYSTART_MS0032 => "Failed to reflect xml file.\n". + "Should stop Pacemaker so check the content of \"[0]\".\n", + + PRIMARYSTART_MS0033 => "Should specify the parameter of \"[1]\" of ResourceID (\"[0]\") in xml file.\n", + PRIMARYSTART_MS0034 => "This database cluster is incompletely rewinded by pg-rex_standby_start. Startup failed.\n", + PRIMARYSTART_MS0035 => "[0]. Creating the HA cluster.\n", + PRIMARYSTART_MS0036 => "[0] does not exist on this node.\n", + + STANDBYSTART_USAGE => <<_STANDBYSTART_USAGE_, +PG-REX single-step starter for standby node +Run this script on the node to be standby to start. + +Usage: + pg-rex_standby_start [[-n] [-r] [-b] | -c] [-d] [-s] [-h] [-v] + +Options: + -n, --normal start this node as it is as standby. when + specified along with the options -r, -b, + the first available option is chosen in + the order of -n, -r, -b + -r, --rewind start this node as standby after running + pg_rewind + -b, --basebackup start this node as standby from a new basebackup + taken from the peer node as the primary + -d, --dry-run run without changing data and executing nodes + -c, --check-only show the condition of database clusters + -s, --shared-archive-directory assume that primary and standby share the same + WAL archive directory + -h, --help show this help, then quit + -v, --version show the version, then quit + +_STANDBYSTART_USAGE_ + + STANDBYSTART_MS0001 => "Ctrl-C is ignored.\n", + STANDBYSTART_MS0004 => "[0]. Checking Pacemaker and Corosync has stopped\n", + STANDBYSTART_MS0005 => "...[NG]\n", + STANDBYSTART_MS0006 => "Pacemaker or Corosync has already started on this node.\n", + STANDBYSTART_MS0007 => "...[OK]\n", + STANDBYSTART_MS0008 => "[0]. Checking primary has started on another node\n", + STANDBYSTART_MS0010 => "Primary has not started on another node.\n", + STANDBYSTART_MS0011 => "[0]. Checking connecting to another node with IC-LAN\n", + STANDBYSTART_MS0012 => "There is no connection response of IC-LAN.\n", + STANDBYSTART_MS0013 => "[0]. Checking PGSQL lock file\n", + STANDBYSTART_MS0014 => "There is PGSQL lock file (\"[0]\").\n". + "In the case, this node may have previously started as a primary.\n". + "Should check whether you really want to start as a standby.\n", + STANDBYSTART_MS0015 => "[0]. Taking a base backup from primary\n", + STANDBYSTART_MS0018 => "Failed to execute readlink command.\n", + STANDBYSTART_MS0019 => "[0]. Synchronizing with the archive directory on the primary\n", + STANDBYSTART_MS0021 => "[0]. Starting Pacemaker (Number of WAL segments for restore: [1])\n", + STANDBYSTART_MS0022 => "[0]. Checking standby has started\n", + STANDBYSTART_MS0024 => "Resource has failed while the startup of the Pacemaker.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + STANDBYSTART_MS0029 => "Standby has started on the node ([0]).\n", + STANDBYSTART_MS0032 => "Can not continue processing because file exists in archive directory: [0]\n", + STANDBYSTART_MS0033 => "Database cluster does not exist.\n", + STANDBYSTART_MS0034 => "[0]. Checking the status of database cluster\n", + STANDBYSTART_MS0035 => "Failed to get DB cluster version.\n", + STANDBYSTART_MS0036 => "DB cluster version ([0]) does not match the PostgreSQL version ([1]).\n", + STANDBYSTART_MS0037 => "Database identifier on this node is different from another ([0] != [1]).\n", + STANDBYSTART_MS0038 => "TimeLineID on this node is larger than another ([0] > [1]).\n", + STANDBYSTART_MS0040 => "Could not find wal file on another node: [0]\n", + STANDBYSTART_MS0041 => "This node is advancing in XLOG location against the peer node ([0] > [1]).\n", + STANDBYSTART_MS0042 => "No timeline history files found on another node.\n", + STANDBYSTART_MS0043 => "There are no history that has passed through TimeLineID on this node in the content of history file (\"[0]\") of TimeLineID on another node.\n", + STANDBYSTART_MS0044 => "There are no history that has passed through the location of xlog on this node in the content of history file (\"[0]\") of TimeLineID on another node.\n", + STANDBYSTART_MS0045 => "This node needs to be reinitialized from a new base backup.\n", + STANDBYSTART_MS0046 => "[0]-seconds timeout occurred while checking the startup of the Pacemaker.\n", + STANDBYSTART_MS0047 => "Synchronous replication of [0] is not established.\n". + "Should check the status of [0].\n", + STANDBYSTART_MS0048 => "[0] is not running.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + STANDBYSTART_MS0049 => "Failed to process archived history file (Unexpected extension: [0]).\n", + STANDBYSTART_MS0050 => "[0].[1] Checking if this node can start without change\n", + STANDBYSTART_MS0051 => "[0].[1] Checking if this node can start after running pg_rewind\n", + STANDBYSTART_MS0052 => "[0].[1] Checking if it is ready to run pg_basebackup\n", + STANDBYSTART_MS0053 => "Promoted LSNs for TimelineID = [0] on both nodes are not identical.\n", + STANDBYSTART_MS0055 => "full_page_writes on the peer node should be 'on'.\n", + STANDBYSTART_MS0056 => "This database cluster must be initialized with --data-checksums, otherwise wal_log_hints must be 'on'.\n", + STANDBYSTART_MS0057 => "pg_rewind is not available, try the other choices.\n", + STANDBYSTART_MS0058 => "\nNone of the specified methods are able to be performed.\n", + STANDBYSTART_MS0059 => "\nFollowing methods are available to start this node as standby\n", + STANDBYSTART_MS0060 => "n) Start as it is\n", + STANDBYSTART_MS0061 => "r) Start after running pg_rewind\n", + STANDBYSTART_MS0062 => "b) Start from a new base backup\n", + STANDBYSTART_MS0063 => "q) quit\n", + STANDBYSTART_MS0064 => "Make a choice among the options ([0]) ", + STANDBYSTART_MS0065 => "quit starting this node.\n", + STANDBYSTART_MS0066 => "Invalid value.\n", + STANDBYSTART_MS0067 => "\nStarting the database as it is.\n", + STANDBYSTART_MS0068 => "\nStarting after performing pg_rewind.\n", + STANDBYSTART_MS0069 => "\nStarting from a new base backup.\n", + STANDBYSTART_MS0070 => "[0]. Rewinding the database cluster\n", + STANDBYSTART_MS0071 => "Running pg_rewind.\n", + STANDBYSTART_MS0072 => "Unable to rewind to the promoted point of the peer node.pg_rewind not available.\n", + STANDBYSTART_MS0073 => "Failed to establish an ssh connection ([0]) - Aborting rewind\n", + STANDBYSTART_MS0074 => "pg_rewind failed to connect to the peer\n - TCP Forwarding may be disabled in sshd_config on the peer server\n", + STANDBYSTART_MS0075 => "This database cluster is incompletely rewinded by pg-rex_standby_start.\n", + STANDBYSTART_MS0076 => "WAL files with future XLOG location of the peer node found in archive directory.\n", + STANDBYSTART_MS0077 => "Skip synchronizing the archive directory because standby's archive directory is newer than primary.\n", + STANDBYSTART_MS0078 => "The archive log \"[0]\" that synchronize is incomplete.\n", + STANDBYSTART_MS0079 => "The archive log \"[0]\" is unexpected extension (Unexpected extension: [1]).\nSkipping the integrity check of the archive log.\n", + STANDBYSTART_MS0080 => "WAL directory of the base backup is not empty.\n", + STANDBYSTART_MS0081 => "Primary's archive directory has WAL that is advancing than current LSN of the primary node.\n", + STANDBYSTART_MS0083 => "WAL file of pg_wal is inappropriate. If start the Standby on this state, archive log will be lost.\n", + STANDBYSTART_MS0084 => "-c option is specified, non-interactive options(-n,-r,-b) are ignored.\n", + STANDBYSTART_MS0085 => "Peer node has promoted in the past of this node.\n", + STANDBYSTART_MS0086 => "...[SKIP]\n", + STANDBYSTART_MS0087 => "Timeline hitory file for timeline ID [0] not found on another node: [1]\n", + STANDBYSTART_MS0089 => "[0] does not exist on this node.\n", + STANDBYSTART_MS0099 => "Internal Error.\n", + + STOP_USAGE => <<_STOP_USAGE_, +PG-REX stop tool +This is executed on the node that will stop. + +Usage: + pg-rex_stop [-f][-h][-v] + +Options: + -f, --fast stop without executing CHECKPOINT and sync command + -h, --help show this help, then quit + -v, --version show this version, then quit + +_STOP_USAGE_ + + STOP_MS0001 => "Ctrl-C is ignored.\n", + STOP_MS0004 => "Pacemaker and Corosync has already stopped.\n", + STOP_MS0005 => "Could not check the status of PostgreSQL.\n". + "Stopping Pacemaker.\n", + STOP_MS0006 => "Stopping primary.\n", + STOP_MS0007 => "Stopping standby.\n", + STOP_MS0009 => "Exit.\n", + STOP_MS0010 => "1. Stopping Pacemaker\n", + STOP_MS0011 => "...[OK]\n", + STOP_MS0012 => "2. Checking Pacemaker has stopped\n", + STOP_MS0013 => "...[NG]\n", + STOP_MS0014 => "[0]-seconds timeout occurred while checking the stopping processing of the Pacemaker.\n". + "Should check these processes of Pacemaker and Corosync with ps command whether they have stopped successfully.\n", + STOP_MS0015 => "Pacemaker has stopped on the node ([0]).\n", + STOP_MS0016 => "[0] has stopped on the node ([1]).\n", + STOP_MS0018 => "Standby has already started.\n", + STOP_MS0019 => "we recommend to use pg-rex_switchover command when you want to stop primary with the purpose of switchover.\n", + STOP_MS0020 => "If you continue with this operation, then a failover occurs, but are you sure? (y/N) ", + + ARCHDELETE_USAGE => <<_ARCDELETE_USAGE_, +PG-REX remove archive log tool + +Usage: + pg-rex_archivefile_delete {-m|-r}[-f][-D DBclusterFilepath][-h][-v] + [[Hostname:]BasebackupPath] + +Hostname the hostname of remote server that exists a base backup + +BasebackupPath the fullpath of remote server that exists a base backup + Caution: You will not be able to use a base backup older than one specified. + +Options: + -m, --move execute as a move mode + -r, --remove execute as a remove mode + (You must specify either move mode or remove.) + -f, --force execute without contacts + -D, --dbcluster=DBclusterFilepath the fullpath of database cluster + -h, --help show this help, then quit + -v, --version show this version, then quit + +_ARCDELETE_USAGE_ + + ARCHDELETE_MS0001 => "Ctrl-C is ignored.\n", + ARCHDELETE_MS0002 => "Should specify move mode or remove in the argument.\n", + ARCHDELETE_MS0003 => "\n**** 1. Ready to run ****\n", + ARCHDELETE_MS0004 => "Running move mode.\n", + ARCHDELETE_MS0005 => "Running remove mode.\n", + ARCHDELETE_MS0006 => "The input format of the backup path is invalid : \"[0]\"\n", + ARCHDELETE_MS0007 => "Please input a remote server name that has a base backup.\n". + "(If empty, set \"localhost\")\n". + "> ", + ARCHDELETE_MS0008 => "The input format of the remote server is invalid : \"[0]\"\n", + ARCHDELETE_MS0009 => "Please input a fullpath of the base backup.\n". + "(If empty, set no backup path.\n". + " In the case, you cannot use a base backup older than one specified because the archive log is removed.)\n". + "> ", + ARCHDELETE_MS0010 => "The input format of the fullpath of this base backup is invalid : \"[0]\"\n", + ARCHDELETE_MS0011 => "Remote server \"[0]\", Backup path\"[1]\".\n". + "Are you sure? (y/N) : ", + ARCHDELETE_MS0012 => "Exit.\n", + ARCHDELETE_MS0013 => "Reading pg-rex_tools.conf.\n", + ARCHDELETE_MS0015 => "Getting both node names.\n", + ARCHDELETE_MS0016 => "Reading cib.xml.\n", + ARCHDELETE_MS0018 => "Remove the archive log on the basis of the current database cluster(\"[2]\") on this node(\"[0]\") or another(\"[1]\") if you run without specifying the backup path.\n". + "Do you want to delete the archive log? (y/N) : ", + ARCHDELETE_MS0019 => "\n**** 2. Get wal file names ****\n", + ARCHDELETE_MS0020 => "Getting first wal file name that needs to recovery from the backup specified.\n", + ARCHDELETE_MS0021 => "No such directory (\"[0]\").\n", + ARCHDELETE_MS0022 => "The format of fist line of the backup label file (\"[0]\") is invalid.\n", + ARCHDELETE_MS0023 => " \"[0]\" \n", + ARCHDELETE_MS0024 => "Getting first wal file name that needs to recovery from the database cluster (\"[1]\") on this node (\"[0]\").\n", + ARCHDELETE_MS0025 => "Failed to get first wal file name because the result of pg_controldata command is empty.\n", + ARCHDELETE_MS0026 => "Getting first wal file name that needs to recovery from the database cluster (\"[1]\") on another node (\"[0]\").\n", + ARCHDELETE_MS0027 => "\n**** 3. Calculate the deletion target ****\n", + ARCHDELETE_MS0028 => "No the deletion target.\n", + ARCHDELETE_MS0029 => "Setted \"[0]\" to the deletion target.\n", + ARCHDELETE_MS0030 => "\n**** 4. Remove the archive log ****\n", + ARCHDELETE_MS0031 => "Could not find the archive directory (\"[0]\").\n", + ARCHDELETE_MS0032 => "Added \"[0]\" to the list of the deletion target.\n", + ARCHDELETE_MS0033 => "The list of the deletion target is empty.\n", + ARCHDELETE_MS0034 => "The directory where you want to move the archive log (\"[0]\") already exist.\n", + ARCHDELETE_MS0035 => "Failed to creating the directory where you want to move the archive log (\"[0]\").\n", + ARCHDELETE_MS0036 => "Failed to change the owner of the directory where you want to move the archive log (\"[0]\").\n", + ARCHDELETE_MS0037 => "Created the directory where you want to move the archive log.\n", + ARCHDELETE_MS0038 => "Failed to move the file (\"[0]\").\n", + ARCHDELETE_MS0039 => " -- move -- [0] \n", + ARCHDELETE_MS0040 => "Succeeded moving the archive log.\n". + "These are in the directory (\"[0]\").\n", + ARCHDELETE_MS0041 => "Failed to remove the file (\"[0]\").\n", + ARCHDELETE_MS0042 => " -- remove -- [0] \n", + ARCHDELETE_MS0043 => "Succeeded removing the archive log.\n", + ARCHDELETE_MS0044 => "Failed to get DB cluster path.\n". + "Should specify the fullpath of DB cluster with \"-D\" option.\n", + ARCHDELETE_MS0045 => "\n**** 4. Move the archive log ****\n", + ARCHDELETE_MS0046 => "Failed to connect to the server that has a base backup by ssh.\n", + + SWITCHOVER_USAGE => <<_SWITCHOVER_USAGE_, +This is tool to switch primary and standby of PG-REX + +Usage: + pg-rex_switchover [-h][-v] + +Options: + -h, --help show this help, then quit + -v, --version show this version, then quit + +_SWITCHOVER_USAGE_ + + SWITCHOVER_MS0001 => "Ctrl-C is ignored.\n", + SWITCHOVER_MS0002 => "**** Ready to run ****\n", + SWITCHOVER_MS0003 => "[0]. Reading pg-rex_tools.conf and get both node names.\n", + SWITCHOVER_MS0004 => "...[OK]\n", + SWITCHOVER_MS0005 => "...[NG]\n", + SWITCHOVER_MS0008 => "[0]. Checking the HA cluster status of current and after switchover.\n", + SWITCHOVER_MS0009 => "Can not switchover because the HA cluster status does not meet the following conditions.\n". + " (1) Pacemaker, Corosync and PostgreSQL is running on both nodes\n". + " (2) Primary and standby are present\n". + " (3) PostgreSQL is the state of synchronous replication\n", + SWITCHOVER_MS0010 => "[ Current HA cluster status ]\n", + SWITCHOVER_MS0011 => "[ HA cluster status of current and after switchover ]\n", + SWITCHOVER_MS0012 => "Availability is not guaranteed during executing to switchover.\n", + SWITCHOVER_MS0013 => "In addition, There are multi standby on the node ([0]).\n", + SWITCHOVER_MS0014 => "Do you want to continue ? (y/N) ", + SWITCHOVER_MS0015 => "Exit.\n", + SWITCHOVER_MS0017 => "[0]. Executing CHECKPOINT.\n", + SWITCHOVER_MS0018 => "**** Execute to switchover ****\n", + SWITCHOVER_MS0019 => "[0]. Stopping monitoring by Pacemaker.\n", + SWITCHOVER_MS0020 => "[0]. Stopping PostgreSQL on the primary node ([1]).\n", + SWITCHOVER_MS0021 => "[0]. Starting monitoring by Pacemaker, and executing to switchover.\n", + SWITCHOVER_MS0022 => "[0]. Checking that [1] becomes the new primary.\n", + SWITCHOVER_MS0023 => "[0]-seconds timeout occurred while checking the startup of the Resources.\n". + "[1] is not running.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + SWITCHOVER_MS0026 => "[0]. Stopping Pacemaker on the node ([1]).\n", + SWITCHOVER_MS0027 => "[0]-seconds timeout occurred while checking the shutdown of Pacemaker and PostgreSQL.\n". + "If you check details of Pacemaker status, please execute pcs status --full command.\n", + SWITCHOVER_MS0028 => "[0]. Starting standby on the node ([1]).\n", + SWITCHOVER_MS0030 => "**** Standby has started on the node ([0]) ****\n", + SWITCHOVER_MS0031 => "***************************************\n". + "**** Switchover has been completed ****\n". + "***************************************\n", + SWITCHOVER_MS0032 => "Failed to execute \"[0]\" command.\n", + SWITCHOVER_MS0033 => "**** Primary has started on the node ([0]) ****\n", + + COMMON_MS0001 => "Failed to execute pcs status --full command.\n", + COMMON_MS0003 => "Should specify the parameter of \"[1]\" of ResourceID (\"[0]\") in cib.xml.\n", + COMMON_MS0004 => "Should specify [0] in pg-rex_tools.conf.\n", + COMMON_MS0005 => "Should specify [0] of fullpath in pg-rex_tools.conf.\n", + COMMON_MS0006 => "Should specify STONITH which is setting only enable in pg-rex_tools.conf.\n", + COMMON_MS0007 => "Should specify [0] which is setting two IPAddress separated by a comma in pg-rex_tools.conf.\n", + COMMON_MS0008 => "Failed to get the value of own IPAddress.\n", + COMMON_MS0009 => "No IP addresses for this node found in [0]: [1]\n", + COMMON_MS0010 => "No IP addresses for peer node found in [0]: [1]\n", + COMMON_MS0011 => "Failed to execute \"[0]\" command.\n", + COMMON_MS0013 => "Failed to parse the location of xlog.\n", + COMMON_MS0014 => "Failed to get the value by pg_controldata command.\n", + COMMON_MS0015 => "Failed to get the name of another node.\n", + COMMON_MS0016 => "Failed to connect to another node by ssh.\n", + COMMON_MS0018 => "Failed to execute scp command.\n", + COMMON_MS0021 => "Should specify IPADDR_STANDBY which is setting either enable or disable in pg-rex_tools.conf.\n", + COMMON_MS0022 => "Should specify STONITH_ResourceID which is setting two ResourceID separated by a comma in pg-rex_tools.conf.\n", + COMMON_MS0023 => "Failed to read cib.xml (\"[0]\").\n", + COMMON_MS0024 => "Failed to read config file (\"[0]\").\n", + COMMON_MS0026 => "You are running in \"[0]\" user.\n". + "Should try again in root user.\n", + COMMON_MS0028 => "Failed to write config file (\"[0]\").\n", + COMMON_MS0029 => "[0]@[1]'s password:", + COMMON_MS0030 => "\nPassword has been entered.\n", + COMMON_MS0031 => "Could not find password file (\"[0]\").\n", + COMMON_MS0032 => "Failed to read password file (\"[0]\").\n", + COMMON_MS0033 => "Content of password file (\"[0]\") is invalid.\n", + COMMON_MS0034 => "Should specify [0] which is setting either\n". + "manual, passfile or nopass in pg-rex_tools.conf.\n", + COMMON_MS0035 => "Authority of the password file \"[0]\" is not 600.\n", + COMMON_MS0036 => "Not found PostgreSQL commands in \"[0]\". : [1]\n", + COMMON_MS0037 => "PostgreSQL [0] is not supported.\n". + "Please use version 15.\n", + COMMON_MS0038 => "Pacemaker [0] is not supported.\n". + "Please use version 2.\n", + COMMON_MS0039 => "Not found network interface to correspond to bindnetaddr ([1]) on [0].\n", + COMMON_MS0040 => "Failed to get IP address belonging to bindnetaddr ([1]) on [0].\n", + COMMON_MS0044 => "Could not check the status of archive directory: [0]\n", + COMMON_MS0045 => "The format of fist line of the backup label file is invalid: [0]\n", + COMMON_MS0046 => "DB cluster status check failed: [0]\n", + COMMON_MS0047 => "There are processes accessing the DB cluster.\n[0]", + COMMON_MS0048 => "Failed to create lock file([0]). : [1]\n", + COMMON_MS0049 => "[0] has already started on this node.\n", + COMMON_MS0050 => "Failed to create lock file([0]).\n", + COMMON_MS0051 => "Failed to delete lock file([0]). : [1]\n", + COMMON_MS0052 => "[0] is not in IPv4 format.\n", + COMMON_MS0053 => "[0] not found. : [1]\n", + COMMON_MS0054 => "The size of archivefile([0]) is 0\n", + COMMON_MS0055 => "The size of archive file([0]) is not match the information of control file([1]).\n", + COMMON_MS0056 => "Could not parse output from lsof command.[0]\n", + COMMON_MS0057 => "The setting value of the setting parameter [0] is invalid.\n", + COMMON_MS0058 => "File [0] has invalid permissions. Should be 600.\n", +}; + +1; diff --git a/pg-rex_operation_tools/lib/PGRex/Po/ja.pm b/pg-rex_operation_tools/lib/PGRex/Po/ja.pm new file mode 100644 index 0000000..caf2a77 --- /dev/null +++ b/pg-rex_operation_tools/lib/PGRex/Po/ja.pm @@ -0,0 +1,395 @@ +#!/usr/bin/perl +##################################################################### +# Function: ja.pm +# +# +# 概要: +# PG-REX 便利ツールから呼び出すメッセージの集まり +# JPN ロケール用 +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +use warnings; +use strict; + +use constant { + + PRIMARYSTART_USAGE => <<_PRIMARYSTART_USAGE_, +PG-REX を Primary として起動するツールです +Primary として起動したいノードで実行します + +Usage: + pg-rex_primary_start [-h] [-v] [XmlFilePath] + +XmlFilePath 初回起動時のみリソース定義 xml ファイルのファイルパスを指定します + +Options: + -h, --help Usage を表示して終了します + -v, --version バージョン情報を表示して終了します + +_PRIMARYSTART_USAGE_ + + PRIMARYSTART_MS0001 => "Ctrl+C は本プログラムでは無効です\n", + PRIMARYSTART_MS0002 => "指定されたリソース定義 xml ファイルが存在しません\n", + PRIMARYSTART_MS0004 => "リソース定義 xml ファイルの読み込みに失敗したためスクリプトを終了します\n", + PRIMARYSTART_MS0007 => "[0]. Pacemaker および Corosync が停止していることを確認\n", + PRIMARYSTART_MS0008 => "...[NG]\n", + PRIMARYSTART_MS0009 => "自身のノードで Pacemaker または Corosync が稼働しています\n". + "Primary 起動処理を中止します\n", + PRIMARYSTART_MS0010 => "...[OK]\n", + PRIMARYSTART_MS0011 => "[0]. 稼働中の Primary が存在していないことを確認\n", + PRIMARYSTART_MS0013 => "相手のノードで Primary が稼働中です\n". + "Primary 起動処理を中止します\n", + PRIMARYSTART_MS0014 => "[0]. 起動禁止フラグの存在を確認\n", + PRIMARYSTART_MS0015 => "起動禁止フラグ \"[0]\" が存在するため Primary の起動処理を中止します\n", + PRIMARYSTART_MS0016 => "既に HAクラスタ があります\n". + "再作成しても宜しいでしょうか? (y/N) ", + PRIMARYSTART_MS0017 => "スクリプトを終了します\n", + PRIMARYSTART_MS0018 => "[0]. HAクラスタ の破棄\n", + PRIMARYSTART_MS0019 => "[0]. Pacemaker 起動\n", + PRIMARYSTART_MS0020 => "[0]. リソース定義 xml ファイルの反映\n", + PRIMARYSTART_MS0021 => "Pacemaker 起動確認処理のタイムアウト時間([0]秒)を経過したためスクリプトを終了します\n". + "[1] が起動していません\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドで確認してください\n", + PRIMARYSTART_MS0022 => "[0]. Primary の起動確認\n", + PRIMARYSTART_MS0023 => "Pacemaker の起動中にリソース故障が発生しました\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドを用いて確認してください\n", + PRIMARYSTART_MS0026 => "ノード([0])が Primary として起動しました\n", + PRIMARYSTART_MS0027 => "[0]. Primary として稼働することが出来るかを確認\n", + PRIMARYSTART_MS0028 => "自身のノードの [0]-data-status の値が \"[1]\" になっています\n". + "[0]-data-status が初回起動などでまだ登録されていない状態か \"LATEST\" か \"STREAMING|SYNC\" の値でないと\n". + "DB クラスタが最新でない可能性があるため、Primary として稼働することができません\n". + "Primary 起動処理を中止します\n", + PRIMARYSTART_MS0029 => "root@[0]'s password:", + PRIMARYSTART_MS0030 => "\nパスワードが入力されました\n", + PRIMARYSTART_MS0032 => "リソース定義 xml ファイルの Pacemaker への反映に失敗したためスクリプトを終了します\n". + "Pacemaker を停止し、[0] の内容を確認してください\n", + PRIMARYSTART_MS0033 => "リソース定義 xml ファイルにリソース ID \"[0]\" の \"[1]\" のパラメータが存在していません\n", + PRIMARYSTART_MS0034 => "pg-rex_standby_startで巻き戻しに失敗したDBクラスタのため起動できません。\n", + PRIMARYSTART_MS0035 => "[0]. HAクラスタ の作成\n", + PRIMARYSTART_MS0036 => "自身のノードの \"[0]\" が存在しません\n", + + + STANDBYSTART_USAGE => <<_STANDBYSTART_USAGE_, +PG-REX を Standby として起動するツールです +Standby として起動したいノードで実行します + +Usage: + pg-rex_standby_start [[-n] [-r] [-b] | -c] [-d] [-s] [-h] [-v] + +Options: + -n, --normal 現在のDBクラスタを使用して Standby を起動します + -r, --rewind 現在のDBクラスタに相手 (Primary) ノードと + 同期したうえでレプリケーションを行えるように、 + 巻き戻し後、Standby を起動します + -b, --basebackup 相手ノードをPrimaryとしてベースバックアップを + 取得後、Standby として起動します + -d, --dry-run データの変更とノードの起動の実行を伴わず、 + 表示のみを行います + -c, --check-only DB クラスタの状態確認までを実施します + -s, --shared-archive-directory Primary と Standby でアーカイブディレクトリを + 共有しているものとして動作します + -h, --help Usage を表示して終了します + -v, --version バージョン情報を表示して終了します + +_STANDBYSTART_USAGE_ + + STANDBYSTART_MS0001 => "Ctrl+C は本プログラムでは無効です\n", + STANDBYSTART_MS0004 => "[0]. Pacemaker および Corosync が停止していることを確認\n", + STANDBYSTART_MS0005 => "...[NG]\n", + STANDBYSTART_MS0006 => "自身のノードで Pacemaker または Corosync が稼働しています\n". + "Standby の起動処理を中止します\n", + STANDBYSTART_MS0007 => "...[OK]\n", + STANDBYSTART_MS0008 => "[0]. 稼働中の Primary が存在していることを確認\n", + STANDBYSTART_MS0010 => "相手のノードで Primary が稼働していません\n". + "Standby の起動処理を中止します\n", + STANDBYSTART_MS0011 => "[0]. IC-LAN が接続されていることを確認\n", + STANDBYSTART_MS0012 => "IC-LAN の接続応答がありません\n", + STANDBYSTART_MS0013 => "[0]. 起動禁止フラグが存在しないことを確認\n", + STANDBYSTART_MS0014 => "起動禁止フラグ \"[0]\" が存在するため Standby の起動処理を中止します\n". + "前回停止時に Primary として稼働していた可能性があるので Standby として起動していいかどうかを確認してください\n", + STANDBYSTART_MS0015 => "[0]. Primary からベースバックアップ取得\n", + STANDBYSTART_MS0018 => "readlink コマンドの実行に失敗しました\n", + STANDBYSTART_MS0019 => "[0]. Primary のアーカイブディレクトリと同期\n", + STANDBYSTART_MS0021 => "[0]. Standby の起動 (アーカイブリカバリ対象 WAL セグメント数: [1])\n", + STANDBYSTART_MS0022 => "[0]. Standby の起動確認\n", + STANDBYSTART_MS0024 => "Pacemaker の起動中にリソース故障が発生しました\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドで確認してください\n", + STANDBYSTART_MS0029 => "ノード([0])が Standby として起動しました\n", + STANDBYSTART_MS0032 => "アーカイブディレクトリにファイルが存在するため処理を継続できません: [0]\n", + STANDBYSTART_MS0033 => "DB クラスタが存在していません\n", + STANDBYSTART_MS0034 => "[0]. DB クラスタの状態を確認\n", + STANDBYSTART_MS0035 => "DB クラスタのバージョンが取得できません\n", + STANDBYSTART_MS0036 => "DB クラスタのバージョン ([0]) が PostgreSQL サーバのバージョン ([1]) と一致していません\n", + STANDBYSTART_MS0037 => "自身のノードと相手のノードでデータベース識別子が異なっています ([0] != [1])\n", + STANDBYSTART_MS0038 => "自身のノードの タイムラインID のほうが相手のノードの タイムラインID よりも大きくなっています ([0] > [1])\n", + STANDBYSTART_MS0040 => "相手のノードに WAL ファイルが存在しません: [0]\n", + STANDBYSTART_MS0041 => "自身のノードの XLOG の位置のほうが相手のノードの XLOG の位置よりも進んでいます ([0] > [1])\n", + STANDBYSTART_MS0042 => "相手のノードにタイムライン履歴ファイルが存在しません\n", + STANDBYSTART_MS0043 => "相手のノードのタイムラインIDに対応するタイムライン履歴ファイル \"[0]\" に自身のノードのタイムラインIDを通った履歴が残っていません\n", + STANDBYSTART_MS0044 => "相手ノードは自身のノードのLSNより以前に昇格しています\n", + STANDBYSTART_MS0045 => "自身のノードをベースバックアップから再構築してください\n", + STANDBYSTART_MS0046 => "Pacemaker 起動確認処理のタイムアウト時間([0]秒)を経過したためスクリプトを終了します\n", + STANDBYSTART_MS0047 => "[0] の同期レプリケーション状態が確立されていません\n". + "[0] の状態を確認してください\n", + STANDBYSTART_MS0048 => "[0] が起動していません\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドで確認してください\n", + STANDBYSTART_MS0049 => "アーカイブディレクトリのタイムライン履歴ファイルが処理できません (未知の拡張子: [0])\n", + STANDBYSTART_MS0050 => "[0].[1] 現在のDBクラスタのまま起動が可能か確認\n", + STANDBYSTART_MS0051 => "[0].[1] 巻き戻しを実行することで起動が可能か確認\n", + STANDBYSTART_MS0052 => "[0].[1] ベースバックアップを取得することが可能か確認\n", + STANDBYSTART_MS0053 => "自身のノードと相手のノードのタイムラインID([0])の分岐点が異なっています\n", + STANDBYSTART_MS0055 => "相手ノードの full_page_writes は有効である必要があります\n", + STANDBYSTART_MS0056 => "DBクラスタが --data-checksums 付きで作成されているか、wal_log_hints が on である必要があります\n", + STANDBYSTART_MS0057 => "pg_rewind を実行可能な状態にできません - pg_rewind 以外の方法を使ってください\n", + STANDBYSTART_MS0058 => "\n指定された起動方法に実行可能なものがありませんでした\n", + STANDBYSTART_MS0059 => "\n以下の方法で起動が可能です\n", + STANDBYSTART_MS0060 => "n) 現在のDBクラスタのままStandbyを起動\n", + STANDBYSTART_MS0061 => "r) 現在のDBクラスタを巻き戻してStandbyを起動\n", + STANDBYSTART_MS0062 => "b) ベースバックアップを取得してStandbyを起動\n", + STANDBYSTART_MS0063 => "q) Standbyの起動を中止する\n", + STANDBYSTART_MS0064 => "起動方法を選択してください([0]) ", + STANDBYSTART_MS0065 => "Standby の起動処理を中止します\n", + STANDBYSTART_MS0066 => "選択範囲外の入力がありました\n", + STANDBYSTART_MS0067 => "\n現在のDBクラスタのまま起動します。\n\n", + STANDBYSTART_MS0068 => "\n巻き戻し実行後に起動します。\n\n", + STANDBYSTART_MS0069 => "\nベースバックアップ取得後に起動します。\n\n", + STANDBYSTART_MS0070 => "[0]. DBクラスタの巻き戻し\n", + STANDBYSTART_MS0071 => "自身のノードを巻き戻します\n", + STANDBYSTART_MS0072 => "相手ノードとの分岐点まで状態を戻すことができないため pg_rewind は利用できません\n", + STANDBYSTART_MS0073 => "ssh の接続に失敗しました([0])\n - 巻き戻しを中断します\n", + STANDBYSTART_MS0074 => "pg_rewind が相手のノードへの接続に失敗しました\n - 相手ノードの sshd_config で TCP Forwarding が無効になっている可能性があります\n", + STANDBYSTART_MS0075 => "pg-rex_standby_startで巻き戻しに失敗したDBクラスタです。\n", + STANDBYSTART_MS0076 => "アーカイブディレクトリに相手のノードの XLOG の位置よりも進んだ WAL ファイルが存在します\n", + STANDBYSTART_MS0077 => "Standby のアーカイブディレクトリのほうが新しいため、同期をスキップします\n", + STANDBYSTART_MS0078 => "アーカイブログ \"[0]\" が不完全であるため、スクリプトを終了します\n", + STANDBYSTART_MS0079 => "アーカイブログ \"[0]\" の拡張子が未知のため、完全性が確認できませんでした (未知の拡張子: [1])\n完全性の確認をスキップします\n", + STANDBYSTART_MS0080 => "WAL ファイルが存在するため、スクリプトを終了します\n", + STANDBYSTART_MS0081 => "Primary のアーカイブディレクトリに Primary の現在の LSN よりも進んだ WAL ファイルが存在します\n", + STANDBYSTART_MS0083 => "pg_wal の WAL ファイルの状態が不適切です。このまま Standby を起動するとアーカイブログが欠損するためスクリプトを終了します\n", + STANDBYSTART_MS0084 => "-c オプションが指定されているため、非対話オプション(-n,-r,-b)を無視します\n", + STANDBYSTART_MS0085 => "自身のノードの XLOG の位置のほうが相手のノードのタイムラインの分岐点よりも進んでいます ([0] > [1])\n", + STANDBYSTART_MS0086 => "...[SKIP]\n", + STANDBYSTART_MS0087 => "タイムラインID [0] に対応するタイムライン履歴ファイルが相手のノードに存在しません: [1]\n", + STANDBYSTART_MS0089 => "自身のノードの \"[0]\" が存在しません\n", + STANDBYSTART_MS0099 => "内部エラー\n", + + STOP_USAGE => <<_STOP_USAGE_, +PG-REX の Primary または Standby を停止するツールです +停止したいノードで実行します + +Usage: + pg-rex_stop [-f] [-h] [-v] + +Options: + -f, --fast 停止前に CHECKPOINT と sync コマンドを実行しません + -h, --help Usage を表示して終了します + -v, --version バージョン情報を表示して終了します + +_STOP_USAGE_ + + STOP_MS0001 => "Ctrl+C は本プログラムでは無効です\n", + STOP_MS0004 => "既に Pacemaker および Corosync は停止しているため停止処理を終了します\n", + STOP_MS0005 => "PostgreSQL の状態を確認できませんでした\n". + "Pacemaker を停止します\n", + STOP_MS0006 => "Primary を停止します\n", + STOP_MS0007 => "Standby を停止します\n", + STOP_MS0009 => "スクリプトを停止します\n", + STOP_MS0010 => "1. Pacemaker 停止\n", + STOP_MS0011 => "...[OK]\n", + STOP_MS0012 => "2. Pacemaker 停止確認\n", + STOP_MS0013 => "...[NG]\n", + STOP_MS0014 => "Pacemaker の停止確認処理のタイムアウト時間([0]秒)を経過したのでスクリプトを終了します\n". + "Pacemaker および Corosync のプロセスが正常に停止されているかを確認してください\n", + STOP_MS0015 => "ノード([0])で Pacemaker を停止しました\n", + STOP_MS0016 => "PG-REX の [0] ([1])を停止しました\n", + STOP_MS0018 => "Standby がまだ起動しています\n", + STOP_MS0019 => "ノード切り替えが目的の場合は pg-rex_switchover コマンドの使用を推奨します\n", + STOP_MS0020 => "今停止すると F/O しますが本当に停止しても宜しいですか? (y/N) ", + + ARCHDELETE_USAGE => <<_ARCDELETE_USAGE_, +PG-REX 運用中に作成された、不要なアーカイブログを削除するツールです + +Usage: + pg-rex_archivefile_delete {-m|-r} [-f] [-D DBclusterFilepath] [-h] [-v] + [[Hostname:]BasebackupPath] + +Hostname ベースバックアップが存在するリモートサーバを指定します + +BasebackupPath ベースバックアップの場所の絶対パスを指定します + +Options: + -m, --move 移動モードで実行します + -r, --remove 削除モードで実行します + -f, --force アーカイブログの削除を問い合わせ無しで + 実行します + -D, --dbcluster=DBclusterFilepath 両ノードで使用している DB クラスタの場所の + 絶対パスを指定します + -h, --help Usage を表示して終了します + -v, --version バージョン情報を表示して終了します + +_ARCDELETE_USAGE_ + + ARCHDELETE_MS0001 => "Ctrl+C は本プログラムでは無効です\n", + ARCHDELETE_MS0002 => "移動モードか 削除モードのどちらか片方を指定してください\n", + ARCHDELETE_MS0003 => "\n**** 1. 実行準備 ****\n", + ARCHDELETE_MS0004 => "移動モードで実行します\n", + ARCHDELETE_MS0005 => "削除モードで実行します\n", + ARCHDELETE_MS0006 => "バックアップパスのフォーマットが不正です : \"[0]\"\n", + ARCHDELETE_MS0007 => "ベースバックアップが存在するリモートサーバを入力してください\n". + "(入力しなければ \"localhost\" を設定します)\n". + "> ", + ARCHDELETE_MS0008 => "リモートサーバの入力が不正です : \"[0]\"\n", + ARCHDELETE_MS0009 => "ベースバックアップの場所の絶対パスを入力してください\n". + "(入力しなければバックアップ指定無しとして実行されアーカイブが削除されるため、\n". + " 以前に取得したベースバックアップが使用できなくなります)\n". + "> ", + ARCHDELETE_MS0010 => "ベースバックアップの場所の絶対パスの入力が不正です : \"[0]\"\n", + ARCHDELETE_MS0011 => "リモートサーバ \"[0]\" 、ベースバックアップの場所 \"[1]\" を指定しました\n". + "よろしいでしょうか (y/N) : ", + ARCHDELETE_MS0012 => "スクリプトを終了します\n", + ARCHDELETE_MS0013 => "環境設定ファイル (pg-rex_tools.conf) を読み込みます\n", + ARCHDELETE_MS0015 => "両ノードの名前を取得します\n", + ARCHDELETE_MS0016 => "cib.xml ファイルを読み込みます\n", + ARCHDELETE_MS0018 => "ベースバックアップの場所を指定せずに実行すると、\n". + "自身のノード \"[0]\" と相手のノード \"[1]\" の\n". + "現時点の PGDATA \"[2]\" を基準にしてアーカイブログを削除することになります\n". + "アーカイブログを削除しますか (y/N) : ", + ARCHDELETE_MS0019 => "\n**** 2. WAL ファイル名の取得 ****\n", + ARCHDELETE_MS0020 => "指定されたバックアップからリカバリを行うために必要な最初の WAL ファイル名を取得します\n", + ARCHDELETE_MS0021 => "ディレクトリ \"[0]\" が存在しません\n", + ARCHDELETE_MS0022 => "バックアップラベルファイル \"[0]\" の一行目のフォーマットが正しくありません\n", + ARCHDELETE_MS0023 => " \"[0]\" \n", + ARCHDELETE_MS0024 => "自身のノード \"[0]\" の現時点の PGDATA \"[1]\" からリカバリに必要な最初の WAL ファイル名を取得します\n", + ARCHDELETE_MS0025 => "pg_controldata の結果が空のためリカバリに必要な最初の WAL ファイル名を取得できません\n", + ARCHDELETE_MS0026 => "相手のノード \"[0]\" の現時点の PGDATA \"[1]\" からリカバリに必要な最初の WAL ファイル名を取得します\n", + ARCHDELETE_MS0027 => "\n**** 3. 削除基準の算出 ****\n", + ARCHDELETE_MS0028 => "削除基準がないためスクリプトを終了します\n", + ARCHDELETE_MS0029 => "削除基準を \"[0]\" としました\n", + ARCHDELETE_MS0030 => "\n**** 4. アーカイブログの削除 ****\n", + ARCHDELETE_MS0031 => "アーカイブディレクトリ \"[0]\" の確認に失敗しました\n", + ARCHDELETE_MS0032 => "削除対象のリストに \"[0]\" を追加します\n", + ARCHDELETE_MS0033 => "削除対象のリストが空のためスクリプトを終了します\n", + ARCHDELETE_MS0034 => "移動先ディレクトリ \"[0]\" が既に存在しています\n", + ARCHDELETE_MS0035 => "移動先ディレクトリ \"[0]\" の作成に失敗しました\n", + ARCHDELETE_MS0036 => "移動先ディレクトリ \"[0]\" の postgres ユーザへの変更に失敗しました\n", + ARCHDELETE_MS0037 => "移動先ディレクトリ \"[0]\" を作成しました\n", + ARCHDELETE_MS0038 => "ファイル \"[0]\" の移動に失敗しました\n", + ARCHDELETE_MS0039 => " -- 移動 -- [0] \n", + ARCHDELETE_MS0040 => "アーカイブログの移動に成功しました\n". + "移動モード実行のため、移動したファイルは \"[0]\" に格納されています\n", + ARCHDELETE_MS0041 => "ファイル \"[0]\" の削除に失敗しました\n", + ARCHDELETE_MS0042 => " -- 削除 -- [0] \n", + ARCHDELETE_MS0043 => "アーカイブログの削除に成功しました\n", + ARCHDELETE_MS0044 => "DB クラスタの場所の絶対パスを取得できませんでした\n". + "-D オプションで DB クラスタの場所の絶対パスを指定してください\n", + ARCHDELETE_MS0045 => "\n**** 4. アーカイブログの移動 ****\n", + ARCHDELETE_MS0046 => "ベースバックアップ格納先サーバへの ssh 接続に失敗しました\n", + + SWITCHOVER_USAGE => <<_SWITCHOVER_USAGE_, +PG-REX のノード切り替えを実行するツールです + +Usage: + pg-rex_switchover [-h] [-v] + +Options: + -h, --help Usage を表示して終了します + -v, --version バージョン情報を表示して終了します + +_SWITCHOVER_USAGE_ + + SWITCHOVER_MS0001 => "Ctrl+C は本プログラムでは無効です\n", + SWITCHOVER_MS0002 => "**** 実行準備 ****\n", + SWITCHOVER_MS0003 => "[0]. 環境設定ファイル (pg-rex_tools.conf) の読み込みと両ノードの名前を取得\n", + SWITCHOVER_MS0004 => "...[OK]\n", + SWITCHOVER_MS0005 => "...[NG]\n", + SWITCHOVER_MS0008 => "[0]. 現在およびノード切り替え後のHAクラスタ状態を確認\n", + SWITCHOVER_MS0009 => "HAクラスタ状態が以下の条件を満たしていないためノード切り替えを実行できません\n". + " (1) Pacemaker、Corosync および PostgreSQL が両ノードで稼働している\n". + " (2) Primary と Standby が存在している\n". + " (3) PostgreSQL が同期レプリケーション状態である\n", + SWITCHOVER_MS0010 => "[ 現在のHAクラスタ状態 ]\n", + SWITCHOVER_MS0011 => "[ 現在 / ノード切り替え後のHAクラスタ状態 ]\n", + SWITCHOVER_MS0012 => "ノード切り替え中は可用性が保証されません\n", + SWITCHOVER_MS0013 => "また、現在 [0] に複数の Standby が存在しています\n", + SWITCHOVER_MS0014 => "ノード切り替えを実行してもよろしいでしょうか? (y/N) ", + SWITCHOVER_MS0015 => "スクリプトを終了します\n", + SWITCHOVER_MS0017 => "[0]. CHECKPOINT の実行\n", + SWITCHOVER_MS0018 => "**** ノード切り替えを実行 ****\n", + SWITCHOVER_MS0019 => "[0]. Pacemaker の監視を停止\n", + SWITCHOVER_MS0020 => "[0]. Primary ([1]) の PostgreSQL を停止\n", + SWITCHOVER_MS0021 => "[0]. Pacemaker の監視を再開しノード切り替えを実行\n", + SWITCHOVER_MS0022 => "[0]. [1] が新 Primary になったことを確認\n", + SWITCHOVER_MS0023 => "リソース起動確認処理のタイムアウト時間([0]秒)を経過したためスクリプトを終了します\n". + "[1] が起動していません\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドで確認してください\n", + SWITCHOVER_MS0026 => "[0]. [1] の Pacemaker を停止\n", + SWITCHOVER_MS0027 => "Pacemaker の停止確認処理のタイムアウト時間([0]秒)を経過したのでスクリプトを終了します\n". + "Pacemaker の状態の詳細は、pcs status --full コマンドで確認してください\n", + SWITCHOVER_MS0028 => "[0]. [1] で Standby を起動\n", + SWITCHOVER_MS0030 => "**** [0] が Standby として起動しました ****\n", + SWITCHOVER_MS0031 => "********************************************\n". + "**** ノード切り替えが正常に完了しました ****\n". + "********************************************\n", + SWITCHOVER_MS0032 => "\"[0]\" のコマンドの実行に失敗しました\n", + SWITCHOVER_MS0033 => "**** [0] が Primary として起動しました ****\n", + + COMMON_MS0001 => "pcs status --full の実行に失敗しました\n", + COMMON_MS0003 => "cib.xml ファイルにリソース ID \"[0]\" の \"[1]\" のパラメータが存在していません\n", + COMMON_MS0004 => "環境設定ファイル (pg-rex_tools.conf) の [0] の指定がありません\n", + COMMON_MS0005 => "環境設定ファイル (pg-rex_tools.conf) の [0] の設定は絶対パスで指定してください\n", + COMMON_MS0006 => "環境設定ファイル (pg-rex_tools.conf) の STONITH の設定は enable 以外設定できません\n", + COMMON_MS0007 => "環境設定ファイル (pg-rex_tools.conf) の [0] の設定は2つの IP アドレスをカンマ区切りで指定してください\n", + COMMON_MS0008 => "自身のノードの IP アドレスの取得に失敗しました\n", + COMMON_MS0009 => "[0] に自ノードのIPアドレスが含まれていません: [1]\n", + COMMON_MS0010 => "[0] に相手ノードのIPアドレスが含まれていません: [1]\n", + COMMON_MS0011 => "\"[0]\" のコマンドの実行に失敗しました\n", + COMMON_MS0013 => "XLOG 位置の情報をパースできません\n", + COMMON_MS0014 => "pg_controldata の値の取得に失敗しました\n", + COMMON_MS0015 => "相手のノード名の取得に失敗しました\n", + COMMON_MS0016 => "相手ノードへの ssh 接続に失敗しました\n", + COMMON_MS0018 => "scp コマンドの実行に失敗しました\n", + COMMON_MS0021 => "環境設定ファイル (pg-rex_tools.conf) の IPADDR_STANDBY の設定は enable または disable で指定してください\n", + COMMON_MS0022 => "環境設定ファイル (pg-rex_tools.conf) の STONITH_ResourceID の設定は2つのリソース ID をカンマ区切りで指定してください\n", + COMMON_MS0023 => "cib.xml \"[0]\" の読み込みに失敗しました\n", + COMMON_MS0024 => "環境設定ファイル \"[0]\" の読み込みに失敗しました\n", + COMMON_MS0026 => "[0] ユーザで実行されているためスクリプトを終了します\n". + "root ユーザで再度実行してください\n", + COMMON_MS0028 => "環境設定ファイル \"[0]\" の書き込みに失敗しました\n", + COMMON_MS0029 => "[0]@[1]'s password:", + COMMON_MS0030 => "\nパスワードが入力されました\n", + COMMON_MS0031 => "パスワードファイル \"[0]\" が存在しません\n", + COMMON_MS0032 => "パスワードファイル \"[0]\" の読み込みに失敗しました\n", + COMMON_MS0033 => "パスワードファイル \"[0]\" の内容が不正です\n", + COMMON_MS0034 => "環境設定ファイル (pg-rex_tools.conf) の [0] の設定は、\n". + "manual、passfile、nopass のいずれかを指定してください\n", + COMMON_MS0035 => "パスワードファイル \"[0]\" の権限が 600 ではありません\n", + COMMON_MS0036 => "[0] に PostgreSQL コマンドが見つかりません。: [1]\n", + COMMON_MS0037 => "PostgreSQL [0] には対応していません\n". + "PostgreSQL 15 を使用してください\n", + COMMON_MS0038 => "Pacemaker [0] には対応していません\n". + "Pacemaker 2 を使用してください\n", + COMMON_MS0039 => "[0] で bindnetaddr ([1]) に対応するネットワークインタフェースが見つかりませんでした\n", + COMMON_MS0040 => "[0] で bindnetaddr ([1]) に属する IP アドレスの取得に失敗しました\n", + COMMON_MS0044 => "アーカイブディレクトリの状態を確認できませんでした: [0]\n", + COMMON_MS0045 => "バックアップラベルファイルの一行目のフォーマットが正しくありません: [0]\n", + COMMON_MS0046 => "DBクラスタの状態確認に失敗しました。: [0]\n", + COMMON_MS0047 => "DBクラスタにアクセス中のプロセスが存在します。\n[0]", + COMMON_MS0048 => "ロックファイル([0])の作成に失敗しました。: [1]\n", + COMMON_MS0049 => "自身のノードで [0] が稼働しています。起動処理を中止します。\n", + COMMON_MS0050 => "ロックファイル([0])の作成に失敗しました。\n", + COMMON_MS0051 => "ロックファイル([0])の削除に失敗しました。: [1]\n", + COMMON_MS0052 => "[0] はIPv4の形式ではありません。\n", + COMMON_MS0053 => "[0] が見つかりません: [1]\n", + COMMON_MS0054 => "アーカイブファイル \"[0]\" のサイズが0のため、スクリプトを終了します\n", + COMMON_MS0055 => "アーカイブログ \"[0]\" のサイズが制御ファイルの情報([1])と一致しないため、スクリプトを終了します\n", + COMMON_MS0056 => "lsofコマンドの出力を読み取れません。[0]\n", + COMMON_MS0057 => "設定パラメータ [0] の設定値が不正です。\n", + COMMON_MS0058 => "ファイル [0] のパーミッションが不正です。600でなければなりません。\n", +}; + +1; diff --git a/pg-rex_operation_tools/lib/PGRex/common.pm b/pg-rex_operation_tools/lib/PGRex/common.pm new file mode 100644 index 0000000..08aedce --- /dev/null +++ b/pg-rex_operation_tools/lib/PGRex/common.pm @@ -0,0 +1,1350 @@ +#!/usr/bin/perl +##################################################################### +# Function: common.pm +# +# 概要: +# PG-REX 便利ツールから呼び出すモジュールの集まり +# +# 特記事項: +# なし +# +# Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +# +##################################################################### +package PGRex; + +use warnings; +use strict; +use Net::OpenSSH; +use PGRex::command; +use Class::Struct; +use Fcntl; +use Fcntl ':mode'; +use Errno; + + +BEGIN { + if ($ENV{'LANG'} =~ m/ja/i){ + eval qq{ + use PGRex::Po::ja; + }; + } + else{ + eval qq{ + use PGRex::Po::en; + }; + } +}; + +struct Ssh_info => { + address => '$', + user => '$', + pass => '$' +}; + +struct Pg_dir_state => { + pgdata_exist => '$', + pgarch_empty => '$' +}; + +use constant { + VERSIONNUM => "16.0", + VERSIONINFO => "[0] (pg-rex_operation_tools) [1]\n", + CONFIG_PATH => "/etc/", + CONFIG_FILENAME => "pg-rex_tools.conf", + CIB_PATH => "/var/lib/pacemaker/cib/", + CIB_FILENAME => "cib.xml", + HACF_PATH => "/etc/corosync/", + HACF_FILENAME => "corosync.conf", + RA_TMPDIR => "/var/lib/pgsql/tmp", + LOCK_FILENAME => "PGSQL.lock", + PID_FILENAME => "pg-rex_tools.pid", + PID_FILEDIR => "/var/run", + STANDBY_SIGNAL => "standby.signal", + RECOVERY_SIGNAL => "recovery.signal" +}; + +our $additional_information_mode = 0; + +sub pacemaker_running{ + my ($ssh_info) = @_; + my $result; + my @results; + my $commnad; + + $commnad = "$PS aux | $GREP -P \"(pacemakerd|corosync)\" | $GREP -v \"grep\""; + + if ($ssh_info) { + @results = ssh_exec_command($ssh_info, $commnad); + $result = $results[0]; + } + else { + $result = `$commnad`; + } + + # Pacemaker または Corosync のプロセスを発見したら1を返却する + if ($result ne ""){ + return 1; + } + return 0; +} + + +sub pacemaker_online{ + my ($my_node) = @_; + my @results; + my @crm_results; + my $exit_code; + + $results[0] = `$PCS status --full 2>&1`; + $results[1] = $? >> 8; + + $exit_code = $results[1]; + # Pacemaker が起動している場合は $exit_code = 0 、起動していない場合は $exit_code = 1 + if ($exit_code != 0 && $exit_code != 1){ + printlog("ERROR", COMMON_MS0001); + } + + # pcs status --full の結果を行ごとの配列に格納する + @crm_results = split (/\n/, $results[0]); + + # Pacemaker が Online だったら1を返却する + foreach my $line (@crm_results){ + # pcs status --full の結果に「Online: [ ~ <マシンのホスト名>」の行が存在すると OK + if ($line =~ /Online\:\s+\[(\s+\S+)?\s.*$my_node\s+/ + || $line =~ /Node $my_node .*: online,/){ + return 1; + } + } + return 0; + +} + + +sub pgrex_failed_action{ + my ($target_node, $pg_primitive_resource_id) = @_; + my @results; + my @crm_results; + my $exit_code; + + $results[0] = `$PCS status --full 2>&1`; + $results[1] = $? >> 8; + + $exit_code = $results[1]; + # Pacemaker が起動している場合は $exit_code = 0 、起動していない場合は $exit_code = 1 + if ($exit_code != 0 && $exit_code != 1){ + printlog("ERROR", COMMON_MS0001); + } + + # pcs status --full の結果を行ごとの配列に格納する + @crm_results = split (/\n/, $results[0]); + + my $line_count = 0; + my $line_tmp = 0; + # pcs status --full の結果に failed Action の内容が存在していたら1を返却する + foreach my $line (@crm_results){ + $line_count++; + + # pcs status --full の結果に「Failed Resource Actions:」の行が存在した後、 + # 1行後に「 ~ on <マシンのホスト名>」の行が存在する場合、 + # PG-REXリソースの故障が発生したと判断し、1 を返却する + if ($line =~ /Failed\s+Resource\s+Actions:/){ + $line_tmp = $line_count; + } + if ($line =~ /${pg_primitive_resource_id}_\S+_[0-9]+\s+on\s+$target_node/ && $line_count == $line_tmp + 1){ + return 1; + } + } + return 0; + +} + +sub primary_running{ + my ($my_node, $primary_resource_id, $pg_primitive_resource_id, $ssh_info) = @_; + my @results; + my $command; + + ## + # 以下の条件を満たしたとき結果を OK とする + # * コマンド + # 「crm_resource -r -W 2> /dev/null | grep " <マシンのホスト名> "」 + # の結果が"resource is running on: <マシンのホスト名> Primary"である + # * コマンド + # 「crm_attribute -l forever -N <マシンのホスト名> --name -data-status -G -q」 + # の結果が"LATEST"である + # * コマンド + # 「crm_attribute -t status -N <マシンのホスト名> --name -status -G -q」 + # の結果が"PRI"である + ## + + $command = "$CRM_RESOURCE -r $primary_resource_id -W 2> /dev/null | $GREP \" $my_node \""; + if ($ssh_info){ + @results = ssh_exec_command($ssh_info, $command); + } + else { + $results[0] = `$command`; + chomp $results[0]; + } + if ($results[0] ne "resource $primary_resource_id is running on: $my_node Promoted"){ + return 0; + } + + $command = "$CRM_ATTRIBUTE -l forever -N $my_node --name ${pg_primitive_resource_id}-data-status -G -q 2> /dev/null"; + if ($ssh_info){ + @results = ssh_exec_command($ssh_info, $command); + } + else { + $results[0] = `$command`; + chomp $results[0]; + } + if ($results[0] ne "LATEST"){ + return 0; + } + + $command = "$CRM_ATTRIBUTE -t status -N $my_node --name ${pg_primitive_resource_id}-status -G -q 2> /dev/null"; + if ($ssh_info){ + @results = ssh_exec_command($ssh_info, $command); + } + else { + $results[0] = `$command`; + chomp $results[0]; + } + if ($results[0] ne "PRI"){ + return 0; + } + + return 1; +} + + +sub standby_running{ + my ($my_node, $primary_resource_id, $pg_primitive_resource_id, $error_code) = @_; + my $command; + my $result; + my $pgsql_data_status; + my $pgsql_status; + + ## + # 以下の条件を満たしたとき結果を OK とする + # * コマンド + # 「crm_resource -r -W 2> /dev/null | grep " <マシンのホスト名>"」 + # の結果が"resource is running on: <マシンのホスト名>"である + # * コマンド + # 「crm_attribute -l forever -N <マシンのホスト名> --name -data-status -G -q」 + # の結果が"STREAMING|SYNC"である + # * コマンド + # 「crm_attribute -t status -N <マシンのホスト名> --name -status -G -q」 + # の結果が"HS:SYNC"である + ## + + $command = "$CRM_RESOURCE -r $primary_resource_id -W 2> /dev/null | $GREP \" $my_node\$\""; + $result = `$command`; + chomp $result; + + if ($result ne "resource $primary_resource_id is running on: $my_node"){ + if ($error_code){ + $$error_code = 1; + } + return 0; + } + + $command = "$CRM_ATTRIBUTE -l forever -N $my_node --name ${pg_primitive_resource_id}-data-status -G -q 2> /dev/null"; + $pgsql_data_status = `$command`; + chomp $pgsql_data_status; + + $command = "$CRM_ATTRIBUTE -t status -N $my_node --name ${pg_primitive_resource_id}-status -G -q 2> /dev/null"; + $pgsql_status = `$command`; + chomp $pgsql_status; + + if ($pgsql_data_status ne "STREAMING|SYNC" || $pgsql_status ne "HS:sync"){ + if ($error_code){ + $$error_code = 2; + } + return 0; + } + + return 1; + +} + + +sub vip_running{ + my ($my_node, $resource_id) = @_; + my $result; + + ## + # 以下の条件を満たしたとき結果を OK とする + # * コマンド + # 「crm_resource -r <リソースID> -W 2> /dev/null」 + # の結果が"resource <リソースID> is running on: <マシンのホスト名>"である + ## + + $result = `$CRM_RESOURCE -r $resource_id -W 2> /dev/null`; + chomp $result; + if ($result ne "resource $resource_id is running on: $my_node"){ + return 0; + } + + return 1; + +} + + +sub stonith_running{ + my ($my_node, $resource_id) = @_; + my $result; + + ## + # 以下の条件を満たしたとき結果を OK とする + # * コマンド + # 「crm_resource -r <リソースID_1> -W 2> /dev/null」 + # 「crm_resource -r <リソースID_2> -W 2> /dev/null」 + # のどちらも実行して、どちらかの結果が + # "resource <指定したリソースID> is running on: <マシンのホスト名>"である + ## + + $result = `$CRM_RESOURCE -r $resource_id->[0] -W 2> /dev/null`; + chomp $result; + if ($result eq "resource $resource_id->[0] is running on: $my_node"){ + return 1; + } + + $result = `$CRM_RESOURCE -r $resource_id->[1] -W 2> /dev/null`; + chomp $result; + if ($result eq "resource $resource_id->[1] is running on: $my_node"){ + return 1; + } + + return 0; + +} + + +sub ping_running{ + my ($my_node, $resource_id) = @_; + my $result; + my @resource_id; + my $array_num; + my $resource_check_count = 0; + + ## + # 以下の条件を指定されたリソースの数だけ満たしたとき結果を OK とする + # * コマンド + # 「crm_resource -r <リソースID> -W 2> /dev/null | grep " <マシンのホスト名>"」 + # の結果が"resource <リソースID> is running on: <マシンのホスト名>"である + ## + + @resource_id = split(/\s*,\s*/, $resource_id); + $array_num = scalar(@resource_id); + + foreach my $id (@resource_id){ + $result = `$CRM_RESOURCE -r $id -W 2> /dev/null | $GREP \" $my_node\"`; + chomp $result; + if ($result eq "resource $id is running on: $my_node"){ + $resource_check_count ++; + } + } + + if ($resource_check_count == $array_num){ + return 1; + } + + return 0; + +} + +sub read_cib{ + my ($cib_path, $pg_primitive_resource_id, $kill_when_no_data) = @_; + my @cib_strings; + my %cib_value; + my $my_node; + my $my_node_id; + my $my_pgsql_data_status_id; + my @check_key_list = ("pgdata", "repuser"); + my $result; + my $param_name; + my $param_value; + + $my_node = exec_command("$UNAME -n"); + chomp $my_node; + + # tmpdir のデフォルト値を設定する + $cib_value{'tmpdir'} = RA_TMPDIR; + + open (FILE, $cib_path) or printlog("ERROR", COMMON_MS0023, $cib_path); + @cib_strings = ; + close (FILE); + + # PostgreSQL のリソース部分からパラメータ値を取得 + foreach my $line (@cib_strings){ + # -instance_attributes format : + # + # ※neme, value, id タグの順番が変わっても対応できるようにパラメータ値を取得する + if ($line =~ /.*id=\"$pg_primitive_resource_id-instance_attributes-\S+\"/){ + if ($line =~ /\s+name=\"([^\"\s]+)\"/){ + $param_name = $1; + } + if ($line =~ /\s+value=\"([^\"]+)\"/){ + $param_value = $1; + } + if ($param_name && $param_value) { + $cib_value{$param_name} = $param_value; + } + + } + # node id format : + # + # ※()の中のタグの順番は不定 + if ($line =~ /uname=\"$my_node\"/ && $line =~ /id=\"([^\"\s]+)\"/){ + $my_node_id = $1; + $my_pgsql_data_status_id = "nodes-".$my_node_id."-".$pg_primitive_resource_id."-data-status"; + } + } + + foreach my $line (@cib_strings){ + if ($my_pgsql_data_status_id && $line =~ /id=\"$my_pgsql_data_status_id\"/ && $line =~ /value=\"([^\"\s]+)\"/){ + $cib_value{'pgsql_data_status'} = $1; + } + } + + foreach my $key (@check_key_list){ + if (!exists($cib_value{$key}) && $kill_when_no_data){ + printlog("ERROR", COMMON_MS0003, $pg_primitive_resource_id, $key); + } + } + + return %cib_value; +} + + +sub read_config{ + my ($config_path) = @_; + my @config_strings; + my %config_value; + my @check_key_list = ("Archive_dir", "D_LAN_IPAddress", + "PG_REX_Primary_ResourceID","PG_REX_Primitive_ResourceID","IC_LAN_IPAddress","HACLUSTER_NAME"); + my $result; + + $config_value{'STONITH'} = "enable"; + $config_value{'IPADDR_STANDBY'} = "enable"; + $config_value{'PEER_NODE_SSH_PASS_MODE'} = "manual"; + $config_value{'BACKUP_NODE_SSH_PASS_MODE'} = "manual"; + + open (FILE, $config_path) or printlog("ERROR", COMMON_MS0024, $config_path); + @config_strings = ; + close (FILE); + + foreach my $line (@config_strings){ + # コメントの削除 + # comment format : # + $line =~ s/\#.*//g; + + # 行末の空白の削除 + # space format : <\n> + $line =~ s/\s+$//g; + + # 全角の空白を半角空白に変換 + # two-byte characterspace format : + $line =~ s/ / /g; + + # environment setting value format : < = > or < = ,> + if ($line =~ /^\s*(\S+)\s*=\s*(.+)$/){ + $config_value{$1} = $2; + } + } + + foreach my $key (@check_key_list) { + if (!exists($config_value{$key})){ + printlog("ERROR", COMMON_MS0004, $key); + } + } + if ($config_value{'Archive_dir'} !~ /^\//){ + printlog("ERROR", COMMON_MS0005, "Archive_dir"); + } + if ($config_value{'STONITH'} ne "enable"){ + printlog("ERROR", COMMON_MS0006); + } + if ($config_value{'IPADDR_STANDBY'} ne "enable" && $config_value{'IPADDR_STANDBY'} ne "disable"){ + printlog("ERROR", COMMON_MS0021); + } + if ($config_value{'PEER_NODE_SSH_PASS_MODE'} ne "manual" && $config_value{'PEER_NODE_SSH_PASS_MODE'} ne "passfile" + && $config_value{'PEER_NODE_SSH_PASS_MODE'} ne "nopass" ){ + printlog("ERROR", COMMON_MS0034, "PEER_NODE_SSH_PASS_MODE"); + } + if ($config_value{'BACKUP_NODE_SSH_PASS_MODE'} ne "manual" && $config_value{'BACKUP_NODE_SSH_PASS_MODE'} ne "passfile" + && $config_value{'BACKUP_NODE_SSH_PASS_MODE'} ne "nopass" ){ + printlog("ERROR", COMMON_MS0034, "BACKUP_NODE_SSH_PASS_MODE"); + } + if ($config_value{'PEER_NODE_SSH_PASS_MODE'} eq "passfile"){ + if (!defined($config_value{'PEER_NODE_SSH_PASS_FILE'})){ + printlog("ERROR", COMMON_MS0004, "PEER_NODE_SSH_PASS_FILE"); + } + if ($config_value{'PEER_NODE_SSH_PASS_FILE'} !~ /^\//){ + printlog("ERROR", COMMON_MS0005, "PEER_NODE_SSH_PASS_FILE"); + } + my $mode = (stat($config_value{'PEER_NODE_SSH_PASS_FILE'} ))[2]; + my $passfile_rwx = sprintf ("%o", S_IMODE($mode)); + if ($passfile_rwx ne "600"){ + printlog("ERROR", COMMON_MS0058, $config_value{'PEER_NODE_SSH_PASS_FILE'}); + } + } + if ($config_value{'BACKUP_NODE_SSH_PASS_MODE'} eq "passfile"){ + if (!defined($config_value{'BACKUP_NODE_SSH_PASS_FILE'})){ + printlog("ERROR", COMMON_MS0004, "BACKUP_NODE_SSH_PASS_FILE"); + } + if ($config_value{'BACKUP_NODE_SSH_PASS_FILE'} !~ /^\//){ + printlog("ERROR", COMMON_MS0005, "BACKUP_NODE_SSH_PASS_FILE"); + } + my $mode = (stat($config_value{'BACKUP_NODE_SSH_PASS_FILE'} ))[2]; + my $passfile_rwx = sprintf ("%o", S_IMODE($mode)); + if ($passfile_rwx ne "600"){ + printlog("ERROR", COMMON_MS0058, $config_value{'BACKUP_NODE_SSH_PASS_FILE'}); + } + } + # IPADDR_STANDBY が disable の場合、IPADDR_STANDBY_ResourceID の値を空にする + if ($config_value{'IPADDR_STANDBY'} eq "disable"){ + $config_value{'IPADDR_STANDBY_ResourceID'} = ""; + } + + # STONITH が enable の場合、 Group のリソース ID を配列に分解しハッシュに格納する + # また、disable の場合、STONITH_ResourceID の値を空にする + if ($config_value{'STONITH'} eq "enable" && exists($config_value{'STONITH_ResourceID'})){ + my @stonith_resources = split(/\s*,\s*/, $config_value{'STONITH_ResourceID'}); + if (scalar(@stonith_resources) != 2){ + printlog("ERROR", COMMON_MS0022); + } + $config_value{'STONITH_ResourceID'} = [$stonith_resources[0], $stonith_resources[1]]; + } + else{ + $config_value{'STONITH_ResourceID'} = ""; + } + + my @ifconfig_ip = (); + # ","で区切られた D-LAN の IP アドレスをそれぞれ配列に格納 + my @dlan_ipaddr = split(/\s*,\s*/, $config_value{'D_LAN_IPAddress'}); + if (scalar(@dlan_ipaddr) != 2){ + printlog("ERROR", COMMON_MS0007, "D_LAN_IPAddress"); + } + + # IC_LAN_IPAddressのパース + my $iclan_ipaddr1; + my $iclan_ipaddr2; + # IC_LAN_IPAddressのIC-LANが1つで、下記の形式の場合 + # (IPアドレス) + if ( $config_value{'IC_LAN_IPAddress'} =~ /^\s*\(\s*([^\)]+)\s*\)\s*$/){ + $iclan_ipaddr1 = $1; + $iclan_ipaddr1 =~ s/^ *(.*?) *$/$1/; + # IC_LAN_IPAddressのIC-LANが2つで、下記の形式の場合 + # (IPアドレス,IPアドレス) + } elsif ( $config_value{'IC_LAN_IPAddress'} =~ /^\s*\(\s*([^\)]+)\s*\)\s*,\s*\(\s*([^\)]+)\s*\)\s*$/ ) { + $iclan_ipaddr1 = $1; + $iclan_ipaddr2 = $2; + $iclan_ipaddr1 =~ s/^ *(.*?) *$/$1/; + $iclan_ipaddr2 =~ s/^ *(.*?) *$/$1/; + } else { + printlog("ERROR", COMMON_MS0057, "IC_LAN_IPAddress"); + } + + # ","で区切られた IC-LAN の IP アドレスをそれぞれ配列に格納 + my @iclan_ipaddr1 = split(/\s*,\s*/, $iclan_ipaddr1); + if (scalar(@iclan_ipaddr1) != 2){ + printlog("ERROR", COMMON_MS0007, "IC_LAN_IPAddress"); + } + my @iclan_ipaddr2; + if(defined($iclan_ipaddr2)){ + @iclan_ipaddr2 = split(/\s*,\s*/, $iclan_ipaddr2); + if (scalar(@iclan_ipaddr2) != 2){ + printlog("ERROR", COMMON_MS0007, "IC_LAN_IPAddress"); + } + } + + # コマンド実行マシンに設定してある IP アドレスを配列に格納 + $result = `$IFCONFIG | $GREP -P "inet (addr:)?"`; + my @ifconfig_strings = split(/\n/, $result); + foreach my $line (@ifconfig_strings){ + # IP address format(RHEL6) : > + # IP address format(RHEL7) : > + if ($line =~ /inet\s(addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/) { + push (@ifconfig_ip , $2); + } + } + if (!scalar(@ifconfig_ip)){ + printlog("ERROR", COMMON_MS0008); + } + + foreach my $line (@dlan_ipaddr){ + if (grep {$_ eq $line} @ifconfig_ip){ + $config_value{'My_D_LAN_IPAddress'} = $line; + } + else { + $config_value{'Another_D_LAN_IPAddress'} = $line; + } + } + + if (!exists($config_value{'My_D_LAN_IPAddress'})){ + printlog("ERROR", COMMON_MS0009, "D_LAN_IPAddress", $config_value{'D_LAN_IPAddress'}); + } + if (!exists($config_value{'Another_D_LAN_IPAddress'})){ + printlog("ERROR", COMMON_MS0010, "D_LAN_IPAddress", $config_value{'D_LAN_IPAddress'}); + } + + foreach my $line (@iclan_ipaddr1){ + if (grep {$_ eq $line} @ifconfig_ip){ + $config_value{'My_IC_LAN_IPAddress1'} = $line; + } + else { + $config_value{'Another_IC_LAN_IPAddress1'} = $line; + } + } + + if (!exists($config_value{'My_IC_LAN_IPAddress1'})){ + printlog("ERROR", COMMON_MS0009, "IC_LAN_IPAddress", "($iclan_ipaddr1)"); + } + if (!exists($config_value{'Another_IC_LAN_IPAddress1'})){ + printlog("ERROR", COMMON_MS0010, "IC_LAN_IPAddress", "($iclan_ipaddr1)"); + } + + if (defined($iclan_ipaddr2)){ + foreach my $line (@iclan_ipaddr2){ + if (grep {$_ eq $line} @ifconfig_ip){ + $config_value{'My_IC_LAN_IPAddress2'} = $line; + } + else { + $config_value{'Another_IC_LAN_IPAddress2'} = $line; + } + } + + if (!exists($config_value{'My_IC_LAN_IPAddress2'})){ + printlog("ERROR", COMMON_MS0009, "IC_LAN_IPAddress", "($iclan_ipaddr2)"); + } + if (!exists($config_value{'Another_IC_LAN_IPAddress2'})){ + printlog("ERROR", COMMON_MS0010, "IC_LAN_IPAddress", "($iclan_ipaddr2)"); + } + } + if ( $config_value{'HACLUSTER_NAME'} !~ /^[_0-9A-Za-z]+[\-_0-9A-Za-z]*$/){ + printlog("ERROR", COMMON_MS0057, "HACLUSTER_NAME"); + } + + return %config_value; +} + + +sub exec_command { + my ($command) = @_; + my $result; + my $exit_code; + + $result = `$command`; + $exit_code = $? >> 8; + if ($exit_code != 0){ + printlog("ERROR", COMMON_MS0011, $command); + } + + return $result; +} + + +sub ssh_exec_command { + my ($ssh_info, $command, $no_exit_flag) = @_; + my @results; + my $ssh; + + if ($ssh_info->pass() eq ""){ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user()); + } + else{ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user(), password => $ssh_info->pass()); + } + if (defined($no_exit_flag)){ + $ssh->error and $results[2] = $ssh->error; + } else { + $ssh->error and printlog("ERROR", COMMON_MS0016); + } + + $results[0] = $ssh->capture($command); + $results[1] = $? >> 8; + if ($results[0]){ + chomp $results[0]; + } + + return @results; +} + +sub scp_exec_command { + my ($ssh_info, $remote, $local) = @_; + my $ssh; + + if ($ssh_info->pass() eq ""){ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user()); + } + else{ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user(), password => $ssh_info->pass()); + } + $ssh->error and printlog("ERROR", COMMON_MS0016); + + $ssh-> scp_get({copy_attrs => 1}, $remote, $local) or $ssh->error and printlog("ERROR", COMMON_MS0018); + + return 0; +} + + +sub get_xlog_filename{ + my ($xlog_location, $this_time_line_id) = @_; + my $uxlog_id; + my $uxrec_off; + my $xlog_id; + my $xlog_seg; + my $xlog_filename; + + # XLOG 位置の情報をパース + # xlog location format : / + # は16進数 + if ($xlog_location !~ /^([0-9A-F]+)\/([0-9A-F]+)$/){ + printlog("ERROR", COMMON_MS0013); + } + $uxlog_id = $1; + $uxrec_off = $2; + + # 一つ前のセグメントを取得 + # $xlog_segは、上で取得した16進数の値から1を引いて5桁以下をシフトした値になる + $xlog_id = hex($uxlog_id); + my $uxrec_off_tmp = hex($uxrec_off); + $xlog_seg = sprintf("%d", ($uxrec_off_tmp - 1)/(16 ** 6)); + + # 一つ前のセグメントの WAL ファイル名を取得 + $xlog_filename = sprintf("%08X%08X%08X", $this_time_line_id, $xlog_id, $xlog_seg); + + return $xlog_filename; +} + +sub compare_lsn{ + my ($lsn1, $lsn2) = @_; + my $lsn1_left_field; + my $lsn1_right_field; + my $lsn2_left_field; + my $lsn2_right_field; + + if ($lsn1 !~ /^([0-9A-F]+)\/([0-9A-F]+)$/){ + printlog("ERROR", COMMON_MS0013); + } + $lsn1_left_field = hex($1); + $lsn1_right_field = hex($2); + + if ($lsn2 !~ /^([0-9A-F]+)\/([0-9A-F]+)$/){ + printlog("ERROR", COMMON_MS0013); + } + $lsn2_left_field = hex($1); + $lsn2_right_field = hex($2); + + if ($lsn1_left_field > $lsn2_left_field){ + return 1; + } elsif ($lsn1_left_field == $lsn2_left_field){ + if ($lsn1_right_field > $lsn2_right_field){ + return 1; + } elsif ($lsn1_right_field == $lsn2_right_field){ + return 0; + } else { + return -1; + } + } else { + return -1; + } +} + +sub get_controldata_value{ + my (@controldata_strings) = @_; + my %controldata_value; + my @check_key_list = ("pg_control last modified", "Database system identifier", "Latest checkpoint's TimeLineID", "Latest checkpoint's REDO location", "Latest checkpoint's REDO WAL file"); + + my @key_val; + + foreach my $line (@controldata_strings) { + @key_val = split(/:/, $line); + ($controldata_value{$key_val[0]} = $key_val[1]) =~ s/^ *(.*?) *$/$1/; + } + + foreach my $key (@check_key_list) { + if (!exists($controldata_value{$key})){ + printlog("ERROR", COMMON_MS0014); + } + } + + + return \%controldata_value; +} + +sub get_start_wal_filename { + my ($backup_label_path, @backup_label_strings) = @_; + + # backup_label file first line format : START WAL LOCATION: (file ) + if (!$backup_label_strings[0] || $backup_label_strings[0] !~ /^START\s+WAL\s+LOCATION:\s+[0-9A-F\/]+\s+\(file\s([0-9A-F]+)\)\s*$/){ + printlog("ERROR", COMMON_MS0045, $backup_label_path); + } + return $1; +} + +sub get_recoverywal { + my (@controldata_strings) = @_; + my $wal = ''; + + my $controldata_value = (get_controldata_value(@controldata_strings)); + + $wal = get_xlog_filename($controldata_value->{"Latest checkpoint's REDO location"}, $controldata_value->{"Latest checkpoint's TimeLineID"}); + + return $wal; +} + +sub get_restore_archivewal_num { + my ($dbcluster_dir, $archive_dir, $start_wal_filename) = @_; + my $latest_archive_walfile; + my $xlog_id1 = 0; + my $xlog_id2 = 0; + my $segment_id1 = 0; + my $segment_id2 = 0; + my $result; + my $wal_segment_num; + + $result = `$LS -lH $archive_dir | $GREP -P \"[0-9A-F]{24}\$\" | $TAIL -1`; + if ($result && $result =~ /.*([0-9A-F]{24})$/){ + $latest_archive_walfile = $1; + chomp $latest_archive_walfile; + } + + if ($latest_archive_walfile && $latest_archive_walfile =~ /^[0-9A-F]{8}([0-9A-F]{8})([0-9A-F]{8})$/){ + $xlog_id1 = $1; + $segment_id1 = $2; + } + + if ($start_wal_filename && $start_wal_filename =~ /^[0-9A-F]{8}([0-9A-F]{8})([0-9A-F]{8})$/){ + $xlog_id2 = $1; + $segment_id2 = $2; + } + + $wal_segment_num = (256 * (hex($xlog_id1) - hex($xlog_id2)) + hex($segment_id1) - hex($segment_id2) + 1); + if ($wal_segment_num < 0){ + $wal_segment_num = 0; + } + return $wal_segment_num; +} + +sub get_node{ + my ($ssh_info) = @_; + my @results; + my $exec_user; + my %node_value = ("my_node" => '', + "another_node" => ''); + + $node_value{'my_node'} = exec_command("$UNAME -n"); + chomp $node_value{'my_node'}; + + if ($ssh_info->user()){ + $exec_user = $ssh_info->user(); + } + else{ + $exec_user = "root"; + } + + @results = ssh_exec_command($ssh_info, "$UNAME -n"); + $node_value{'another_node'} = $results[0]; + chomp $node_value{'another_node'}; + + if (!$node_value{'another_node'}){ + printlog("ERROR", COMMON_MS0015); + } + + return %node_value; +} + + +sub get_pg_version_num { + my ($postgres_path, $exec_user) = @_; + my $result; + my $pg_command_user = "postgres"; + my $version_num; + + if (defined($exec_user) && $exec_user ne "root"){ + $result = exec_command("$postgres_path --version"); + } else { + $result = exec_command("$SU - $pg_command_user -c \"$postgres_path --version\""); + } + + $result =~ /(\d+)(?:[^\.]+)?(?:\.(\d+)(?:[^\.]+)?)?(?:\.(\d+))?$/; + if ($1 >= 10){ + # PostgreSQL 10以上の場合 + # $1 : Major Version + # $2 : Minor Version + # $2が未定義の場合は、betaまたはrc版 + if (!defined($2)){ + $version_num = 10000 * $1; + } + else{ + $version_num = 10000 * $1 + $2; + } + } + else{ + # PostgreSQL 9.6以下の場合 + # $1.$2 : Major Version + # $3 : Minor Version + # $3が未定義の場合は、betaまたはrc版 + if (!defined($3)){ + $version_num = 100 * (100 * $1 + $2); + } + else{ + $version_num = 100 * (100 * $1 + $2) + $3; + } + } + return $version_num; +} + +sub check_support_version { + my ($postgres_path, $exec_user) = @_; + my @version_factor; + my $version_num; + + # Pacemaker がサポート対象バージョンであるかを確認する + @version_factor = split(/\./, get_pm_version()); + if (!($version_factor[0] == 2)){ + printlog("ERROR", COMMON_MS0038, get_pm_version()); + } + + # PostgreSQL がサポート対象バージョンであるかを確認する + $version_num = get_pg_version_num($postgres_path, $exec_user); + if (int($version_num / 10000) != 16){ + if ($version_num >= 100000) { + printlog("ERROR", COMMON_MS0037, int($version_num / 10000)); + } else { + printlog("ERROR", COMMON_MS0037, sprintf("%d.%d", + int($version_num / 10000), int(($version_num / 100) % 100))); + } + } +} + +sub get_pg_command_path { + my ($pg_path, $exec_user) = @_; + my @command_list = ("postgres", "psql", "pg_controldata", "pg_basebackup", "pg_waldump", "pg_rewind"); + my $pg_command_user = "postgres"; + my %command_path; + my $pg_config_path; + my $pgbin; + my @missing_commands = (); + my $exit_code; + + # PostgreSQL の bin ディレクトリのパスを pg_config から取得する + # PGPATH が指定されている場合は PGPATH 配下の pg_config を使用する + # PGPATH が指定されていない場合は PATH 経由の pg_config を使用する + if ($pg_path) { + $pg_config_path = File::Spec->catfile($pg_path, "pg_config"); + $pgbin = `$pg_config_path --bindir 2> /dev/null`; + } else { + $pg_config_path = "pg_config"; + if (!$exec_user || $exec_user eq "root") { + $pgbin = `$SU - $pg_command_user -c "$pg_config_path --bindir 2> /dev/null"`; + } else { + $pgbin = `$pg_config_path --bindir 2> /dev/null`; + } + } + $exit_code = $? >> 8; + if ($exit_code != 0) { + printlog("ERROR", COMMON_MS0011, $pg_config_path); + } + chomp $pgbin; + + # PostgreSQLコマンドのコマンドパスを生成して返却する + # 存在しないコマンドがある場合は異常終了する + foreach (@command_list) { + $command_path{$_} = File::Spec->catfile($pgbin, $_); + `$WHICH $command_path{$_} 2> /dev/null`; + $exit_code = $? >> 8; + if ($exit_code != 0) { + push(@missing_commands, $_); + } + } + + printlog("DEBUG", "PostgreSQL command path :\n[0]\n", join("\n", values %command_path)); + + if (@missing_commands) { + printlog("ERROR", COMMON_MS0036, $pgbin, join(',', @missing_commands)); + } + + return %command_path; +} + +sub get_pm_version{ + my $result; + my @results; + + $result = exec_command("$PACEMAKERD --version"); + + @results = split(/\s/,$result); + chomp $results[1]; + + return $results[1]; +} + +sub get_pg_dir_state { + my ($dbcluster_dir, $archive_dir) = @_; + my %pg_dir_state; + + my $pg_dir_state = new Pg_dir_state(); + + $pg_dir_state->pgdata_exist(0); + $pg_dir_state->pgarch_empty(0); + + if (-d $dbcluster_dir && -f $dbcluster_dir."/postgresql.conf"){ + $pg_dir_state->pgdata_exist(1); + } + + if (-d $archive_dir){ + opendir my $dh, $archive_dir or printlog("ERROR", COMMON_MS0044, $archive_dir); + if (! grep {$_ ne '.' && $_ ne '..'} readdir $dh){ + $pg_dir_state->pgarch_empty(1); + } + closedir($dh); + } + else { + $pg_dir_state->pgarch_empty(1); + } + return $pg_dir_state; +} + +sub check_user{ + my $result; + + $result = exec_command("$WHOAMI"); + chomp $result; + if ($result ne "root"){ + printlog("ERROR", COMMON_MS0026, $result); + } +} + + +sub printlog { + my ($log_level, $message, @values) = @_; + + for (my $count=0; $count < scalar(@values); $count++){ + $message =~ s/\[$count\]/$values[$count]/g; + } + + if ($log_level eq "ERROR"){ + print $message; + exit(1); + } + elsif ($log_level eq "DEBUG"){ + if ($PGRex::common::additional_information_mode) { + print $message; + } + } + else{ + print $message; + } + +} + + +sub get_ssh_passwd { + # PG-REX相手ノードへの ssh 接続の為の情報を取得 + my ($another_dlan_ipaddr, $ssh_pass_mode, $ssh_pass_file) = @_; + my $exec_user; + my $passwd; + my @file_stat; + my $file_permission; + + $exec_user = exec_command("$WHOAMI"); + chomp $exec_user; + + if ($ssh_pass_mode eq "manual"){ + printlog("LOG", COMMON_MS0029, $exec_user, $another_dlan_ipaddr); + `$STTY -echo`; + $passwd = ; + chomp $passwd; + `$STTY echo`; + printlog("LOG", COMMON_MS0030); + } + elsif ($ssh_pass_mode eq "nopass"){ + $passwd = ""; + } + else { + if (! -f $ssh_pass_file){ + printlog("ERROR", COMMON_MS0031, $ssh_pass_file); + } + # パスワードファイルの権限が600に設定されているかの確認 + @file_stat = stat($ssh_pass_file); + $file_permission = substr((sprintf "%03o", $file_stat[2]), -3); + if ($file_permission ne "600"){ + printlog("ERROR", COMMON_MS0035, $ssh_pass_file); + } + + open (FILE, $ssh_pass_file) or printlog("ERROR", COMMON_MS0032, $ssh_pass_file); + $passwd = ; + close (FILE); + + if (!defined($passwd)){ + printlog("ERROR", COMMON_MS0033, $ssh_pass_file); + } + + chomp $passwd; + + if ( $passwd eq "" ){ + printlog("ERROR", COMMON_MS0033, $ssh_pass_file); + } + } + + return $passwd; +} + + +sub check_dbcluster_access { + my ($cluster_path) = @_; + my %procs; + my $result = ""; + + stat $LSOF || printlog("ERROR", COMMON_MS0053, $LSOF, $!); + + open my $in, '-|', "$LSOF +D $cluster_path -F cuLR0 2> /dev/null" || printlog("ERROR", COMMON_MS0046, $!); + # Suppress display of stderr only, because error of lsof command is unknown + + my $pid = 0; + while (<$in>) { + while (/([a-zA-Z])([^\x00]+)\x00/g) { + if ($1 eq "p") { + $pid = $2; + } elsif ($pid == 0 || ($1 ne 'f' && defined $procs{$pid}{$1})) { + printlog("ERROR", COMMON_MS0056, ": $_\n"); + } else { + $procs{$pid}{$1} .= ', ' if (defined $procs{$pid}{$1}); + $procs{$pid}{$1} .= $2; + } + } + } + close $in; + + if (keys %procs) { + foreach my $key (keys %procs) { + # filter out forked children + next if (defined $procs{$procs{$key}{R}}); + + $result .= "PID: $key / COMMAND: $procs{$key}{c}, USER=$procs{$key}{L}($procs{$key}{u})\n"; + } + printlog("ERROR", COMMON_MS0047, $result); + } +} + + +sub create_pid_file { + my $pid_fullname = PID_FILEDIR."/".PID_FILENAME; + my $pid; + my $exit_code; + + for(my $ntries = 0; ; $ntries++) { + + # create pid file + if (sysopen my $handle, "$pid_fullname", O_WRONLY|O_CREAT|O_EXCL) { + print $handle "$$"; + close $handle; + return; + } elsif ($! != Errno::EEXIST) { + printlog("ERROR", COMMON_MS0048, PID_FILENAME, $!); + } + + if ($ntries > 5) { + last; + } + $ntries != 0 && sleep 1; + + # check pid file + open FILE, "< $pid_fullname" || continue; + $pid = ; # If it can not read, $pid is UNDEF + close FILE; + + # other pg-rex_tool has already started + if (defined($pid)) { + chomp $pid; + if ($pid ne "") { + `$KILL -s 0 $pid 2> /dev/null`; + $exit_code = $? >> 8; + if ($exit_code == 0) { + printlog("ERROR", COMMON_MS0049, $pid); + } + } + } + + # unlink pid file + unlink $pid_fullname; + if ($! == Errno::EISDIR) { + printlog("ERROR", COMMON_MS0051, $!); + } + } + + printlog("ERROR", COMMON_MS0050, PID_FILENAME); +} + +sub unlink_pid_file { + my $pid_fullname = PID_FILEDIR."/".PID_FILENAME; + + if (!unlink $pid_fullname) { + printlog("LOG", COMMON_MS0051, PID_FILENAME, $!); + } +} + +sub is_ipv4_format{ + # 引数に指定されたIPアドレスがIPv4の形式であるかを確認する + # IPv4の形式の場合は、戻り値に「0」を返却する + # IPv4の形式でない場合は、戻り値に「1」を返却する + # チェック対象のIPアドレスは、コマンド実行結果から取得しているため、 + # 厳密なIPv4の正規表現でIPアドレスであるかは確認しない + my ($ipaddr) = @_; + if ($ipaddr =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/){ + return 1; # OK + } else { + return 0; # NG + } +} + +# ローカルとリモートのディレクトリの差分から、サイズ情報を含む同期対象ファイルを取得する +# 返却値のsync_filesはkeyが同期対象ファイル名、valueがファイルサイズのハッシュ +sub get_sync_files { + my ($ssh_info, $remote_dir, $local_dir, $local_flag) = @_; + my @local_ls = (); + my @remote_ls = (); + my @sync_filelist_to_remote = (); + my @sync_filelist_to_local = (); + my %cnt = (); + my %sync_files = (); + + # ローカルのアーカイブ一覧取得 + @local_ls = `$LS -go $local_dir | $AWK '{print \$3,\$7}'`; + chomp @local_ls; + shift(@local_ls); + + # リモートのアーカイブ一覧取得 + my $command = "$LS -go $remote_dir | $AWK '{print \$3,\$7}'"; + my @results = ssh_exec_command($ssh_info, $command); + @remote_ls = split(/\n/, $results[0]); + shift(@remote_ls); + + # ハッシュの初期化 + map { $cnt{$_}-- } @remote_ls; + + # ローカルにのみ存在するものおよびファイルサイズが異なるものを抽出 + @sync_filelist_to_remote = grep { ++$cnt{$_} == 1 } @local_ls; + + # ローカルを基準にする場合は、上記で抽出したリストから返却値用のハッシュを生成する + # ローカルを基準にしない場合は、リモートにのみ存在するものおよびファイルサイズが異なるものを抽出し、 + # 返却値用のハッシュを生成する + if ($local_flag) { + foreach (@sync_filelist_to_remote){ + if ($_ =~ /^\s*(\d+)\s(.+)$/ ) { + $sync_files{$2} = $1; + } + } + } else { + @sync_filelist_to_local = grep { $cnt{$_} == -1 } @remote_ls; + foreach (@sync_filelist_to_local){ + if ($_ =~ /^\s*(\d+)\s(.+)$/ ) { + $sync_files{$2} = $1; + } + } + } + + return %sync_files; +} + +# ローカルのアーカイブファイルをリモートへ送信する +sub send_archive { + my ($ssh_info, $dir, $bytes_per_wal_segment, $sync_targets) = @_; + my $ssh; + + if ($ssh_info->pass() eq ""){ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user()); + } else { + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user(), password => $ssh_info->pass()); + } + $ssh->error and printlog("ERROR", COMMON_MS0016); + + foreach my $name (sort keys %$sync_targets){ + my $size = $$sync_targets{$name}; + if ((my $ret = check_archivefile_size($name, $size, $bytes_per_wal_segment)) > 0 ){ + if ($ret == 1) { + # アーカイブWALのサイズが制御ファイルの"Bytes per WAL segment"と一致しない + printlog("ERROR", COMMON_MS0055, $name, $bytes_per_wal_segment); + } + if ($ret == 2) { + # アーカイブWAL以外のサイズが0 + printlog("ERROR", COMMON_MS0054, $name); + } + } + open(my $LOCAL, "$TAR czf - -C $dir $name |"); + my $REMOTE = $ssh->pipe_in("$TAR mzxvf - -C $dir"); + while (my $line = <$LOCAL>) { + print($REMOTE $line); + } + close $LOCAL; + close $REMOTE; + } + undef $ssh; +} + +# リモートのアーカイブファイルをローカルに受信する +sub receive_archive { + my ($ssh_info, $dir, $bytes_per_wal_segment, $sync_targets) = @_; + my $ssh; + + if ($ssh_info->pass() eq ""){ + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user()); + } else { + $ssh = Net::OpenSSH->new($ssh_info->address(), user => $ssh_info->user(), password => $ssh_info->pass()); + } + $ssh->error and printlog("ERROR", COMMON_MS0016); + + foreach my $name (sort keys %$sync_targets){ + my $size = $$sync_targets{$name}; + if ((my $ret = check_archivefile_size($name, $size, $bytes_per_wal_segment)) > 0 ){ + if ($ret == 1) { + # アーカイブWALのサイズが制御ファイルの"Bytes per WAL segment"と一致しない + printlog("ERROR", COMMON_MS0055, $name, $bytes_per_wal_segment); + } + if ($ret == 2) { + # アーカイブWAL以外のサイズが0 + printlog("ERROR", COMMON_MS0054, $name); + } + } + my $REMOTE = $ssh->pipe_out("$TAR czf - -C $dir $name"); + open(my $LOCAL, "| $TAR mzxvf - -C $dir"); + while (my $line = <$REMOTE>) { + print($LOCAL $line); + } + close $REMOTE; + close $LOCAL; + } + undef $ssh; +} + +# アーカイブファイルのサイズの妥当性を確認する (正常であれば0を返却する) +# - アーカイブWALの場合、指定されたサイズと一致することを確認する +# 一致しない場合は1を返却する +# - アーカイブWAL以外の場合、サイズが0でないことを確認する +# サイズが0の場合は2を返却する +# +# ※ 圧縮されたアーカイブWALはサイズが0でないことを確認する +sub check_archivefile_size { + my ($name, $size, $expected_size) = @_; + + # アーカイブWALのサイズ確認 + if ($name =~ /^[0-9A-F]{24}$/) { + if ($size != $expected_size){ + return 1; + } + } + + # アーカイブWAL以外のサイズ確認 + if ($name =~ /^([0-9A-F]{24})(\.gz|\.bz2)$/ || + $name =~ /^(.*)(\.history|\.partial|\.backup)(\.gz|\.bz2)?$/) { + if ($size == 0){ + return 2; + } + } + + return 0; +} + +1; diff --git a/pg-rex_operation_tools/man/Makefile b/pg-rex_operation_tools/man/Makefile new file mode 100644 index 0000000..86490c7 --- /dev/null +++ b/pg-rex_operation_tools/man/Makefile @@ -0,0 +1,19 @@ +# + +PANDOC = pandoc +PANDOC_OPT = --self-contained --metadata=$(METADATA) --css=$(STYLE) +METADATA = "pagetitle:PG-REX運用補助ツール $(VERSION) 利用マニュアル" + +VERSION = 16 +STYLE = pg-rex_tools_manual.css +SOURCE = pg-rex_tools_manual-ja.md +MANUAL = html/pg-rex_tools_manual-ja.html + +all: $(MANUAL) + +$(MANUAL) : $(SOURCE) + $(PANDOC) $(PANDOC_OPT) $< -o $@ + +clean : + - $(RM) $(MANUAL) + diff --git a/pg-rex_operation_tools/man/html/pg-rex_tools_manual-ja.html b/pg-rex_operation_tools/man/html/pg-rex_tools_manual-ja.html new file mode 100644 index 0000000..59d190f --- /dev/null +++ b/pg-rex_operation_tools/man/html/pg-rex_tools_manual-ja.html @@ -0,0 +1,1148 @@ + + + + + + + PG-REX運用補助ツール 16 利用マニュアル + + + + + +

PG-REX運用補助ツール 16 +利用マニュアル

+

目次

+ +

運用補助ツールとは?

+

PG-REX運用補助ツールとは、PG-REXの運用手順の簡易化を目的とした以下のコマンド群です。

+

機能概要

+

各コマンドの概要を以下に示します。

+
    +
  1. pg-rex_primary_start

    +

    本コマンドを実行したノードで、PG-REXをPrimaryとして起動する。

  2. +
  3. pg-rex_standby_start

    +

    本コマンドを実行したノードで、PG-REXをStandbyとして起動する。

  4. +
  5. pg-rex_stop

    +

    PG-REXのPrimaryまたはStandbyを停止する。

  6. +
  7. pg-rex_archivefile_delete

    +

    データベースの復旧に必要のないアーカイブログを移動または削除する。

  8. +
  9. pg-rex_switchover

    +

    PG-REXのノード切り替えを実施する。

  10. +
+

導入方法

+

インストール

+

動作確認済み環境

+
    +
  • OS : Red Hat Enterprise Linux 9.2 +
      +
    • perl-IO-Tty : 1.16-4
    • +
  • +
  • PG-REX : 16 +
      +
    • DBMS : PostgreSQL 16.0
    • +
    • HA : Pacemaker 2.1.5-9, pcs 0.11.4-7, pm_extra_tools 1.5
    • +
  • +
+

パッケージのインストール

+

PG-REX運用補助ツールを使用するためにインストール必須のRPMパッケージを以下に示します。
+バージョンは適宜読み替えてください。

+
    +
  1. pg-rex_operation_tools_script-16.0-1.el9.noarch.rpm
  2. +
  3. Net_OpenSSH-0.62-1.el8.x86_64.rpm
  4. +
  5. perl-IO-Tty-1.16-4.el9.x86_64.rpm
  6. +
+

Net_OpenSSHは、pg-rex_operation_tools_scriptとセットで提供されますが、perl-IO-Ttyは提供されません。
+標準リポジトリには含まれていないため、以下のリポジトリから入手する必要があります。

+
    +
  • Red Hat Enterprise Linuxの場合は、「Red Hat CodeReady Linux Builder +for RHEL 9 x86_64」リポジトリ
  • +
  • Rocky Linuxの場合は、「Rocky Linux 9 - CRB」リポジトリ
  • +
+

PG-REX運用補助ツールのRPMをインストールします。

+
# dnf install pg-rex_operation_tools_script-16.0-1.el8.noarch.rpm Net_OpenSSH-0.62-1.el8.x86_64.rpm perl-IO-Tty-1.16-4.el9.x86_64.rpm
+

PG-REX運用補助ツールのRPMパッケージをインストールすると、コマンドと設定ファイルは以下のように配置されます。

+
/usr
+   └-local
+      └-bin
+         └-pg-rex_primary_start
+         └-pg-rex_standby_start
+         └-pg-rex_stop
+         └-pg-rex_archivefile_delete
+         └-pg-rex_switchover
+
+/etc
+   └- pg-rex_tools.conf
+

設定ファイルの編集

+

環境にあわせて、/etc/pg-rex_tools.confの設定を行います。各項目の詳細は設定ファイルを参照してください。設定例を以下に示します。

+
$ cat /etc/pg-rex_tools.conf
+D_LAN_IPAddress = 192.168.2.1 , 192.168.2.2
+IC_LAN_IPAddress = (192.168.1.1, 192.168.1.2) , (192.168.3.1, 192.168.3.2)
+Archive_dir = /dbfp/pgarch/arc1
+IPADDR_STANDBY = enable
+PGPATH = /usr/pgsql-15/bin
+PEER_NODE_SSH_PASS_MODE = passfile
+PEER_NODE_SSH_PASS_FILE = /root/.pgrex/peer_passwd
+BACKUP_NODE_SSH_PASS_MODE = passfile
+BACKUP_NODE_SSH_PASS_FILE = /root/.pgrex/bkup_passwd
+PG_REX_Primary_ResourceID = pgsql-clone
+PG_REX_Primitive_ResourceID = pgsql
+IPADDR_PRIMARY_ResourceID = ipaddr-primary
+IPADDR_REPLICATION_ResourceID = ipaddr-replication
+IPADDR_STANDBY_ResourceID = ipaddr-standby
+PING_ResourceID = ping-clone
+STORAGE_MON_ResouceID = storage-mon-clone
+STONITH_ResourceID = fence1-ipmilan , fence2-ipmilan
+HACLUSTER_NAME = pgrex_hacluster
+

ネットワーク接続の登録

+

PG-REX運用補助ツールが相手ノードの操作や確認をD-LANを使用して行うため、事前に両ノードのrootユーザの.ssh/known_hostsに相手先のD-LANのIPアドレスに対する接続登録をする必要があります。

+
# ssh 192.168.2.2   ←相手先のD-LANのIPアドレスを指定
+The authenticity of host '192.168.2.2 (192.168.2.2)' can't be established.
+ECDSA key fingerprint is *******
+Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
+↑[yes]を入力し[Enter]キーを押下
+Warning: Permanently added '192.168.2.2' (ECDSA) to the list of known hosts.
+root@192.168.2.2's password:    ←[Ctrl]キーと[C]キーを同時に押下
+

アンインストール

+

RPMパッケージのアンインストールを行います。

+
# dnf remove pg-rex_operation_tools_script-16.0-1.el8.noarch.rpm Net_OpenSSH-0.62-1.el8.x86_64.rpm perl-IO-Tty-1.16-4.el9.x86_64.rpm
+

コマンドリファレンス

+
    +
  1. pg-rex_primary_start
  2. +
  3. pg-rex_standby_start
  4. +
  5. pg-rex_stop
  6. +
  7. pg-rex_archivefile_delete
  8. +
  9. pg-rex_switchover
  10. +
+

pg-rex_primary_start

+
概要
+

本コマンドを実行したノードで、PG-REXをPrimaryとして起動します。

+
形式
+
pg-rex_primary_start [-h][-v][XmlFilePath]
+
引数
+
    +
  • -h, –help

    +

    Usageを表示して終了します

  • +
  • -v, –version

    +

    バージョン情報を表示して終了します

  • +
  • XmlFilePath

    +

    リソース定義xmlファイルのファイルパスを指定します(初回起動時に使用する)

  • +
+
実行例
+
    +
  • 引数なしの実行例
  • +
+
# pg-rex_primary_start
+root@192.168.2.2's password:
+パスワードが入力されました
+1. Pacemaker および Corosync が停止していることを確認
+...[OK]
+2. 稼働中の Primary が存在していないことを確認
+...[OK]
+3. Primary として稼働することが出来るかを確認
+...[OK]
+4. 起動禁止フラグの存在を確認
+...[OK]
+5. Pacemaker 起動
+...[OK]
+6. Primary の起動確認
+...[OK]
+ノード(pgrex01)が Primary として起動しました
+
    +
  • 引数ありの実行例
  • +
+
# pg-rex_primary_start pm_pcsgen_env.xml
+root@192.168.2.2's password:
+パスワードが入力されました
+1. Pacemaker および Corosync が停止していることを確認
+...[OK]
+2. 稼働中の Primary が存在していないことを確認
+...[OK]
+3. 起動禁止フラグの存在を確認
+...[OK]
+既に HAクラスタ があります
+再作成しても宜しいでしょうか? (y/N) y
+4. HAクラスタ の破棄
+...[OK]
+5. HAクラスタ の作成
+...[OK]
+6. Pacemaker 起動
+...[OK]
+7. リソース定義 xml ファイルの反映
+...[OK]
+8. Primary の起動確認
+...[OK]
+ノード(pgrex01)が Primary として起動しました
+

pg-rex_standby_start

+
概要
+

本コマンドを実行したノードで、PG-REXをStandbyとして起動します。

+
形式
+
pg-rex_standby_start [[-n] [-r] [-b] | -c] [-d] [-s] [-h] [-v]
+
引数
+
    +
  • -n, –normal

    +

    現在のDBクラスタを使用してStandby を起動します

  • +
  • -r, –rewind

    +

    現在のDBクラスタに相手 (Primary) +ノードと同期したうえでレプリケーションを行えるように、巻き戻し後、Standbyを起動します

  • +
  • -b, –basebackup

    +

    相手ノードをPrimaryとしてベースバックアップを取得後、Standbyとして起動します

  • +
  • -d, –dry-run

    +

    データの変更とノードの起動の実行を伴わず、表示のみを行います

  • +
  • -c, –check-only

    +

    DBクラスタの状態確認までを実施します

  • +
  • -s, –shared-archive-directory

    +

    Primary と Standby +でアーカイブディレクトリを共有しているものとして動作します

  • +
  • -h, –help

    +

    Usageを表示して終了します

  • +
  • -v, –version

    +

    バージョン情報を表示して終了します

  • +
+
実行例
+
# pg-rex_standby_start
+root@192.168.2.1's password:
+パスワードが入力されました
+1. Pacemaker および Corosync が停止していることを確認
+...[OK]
+2. 稼働中の Primary が存在していることを確認
+...[OK]
+3. 起動禁止フラグが存在しないことを確認
+...[OK]
+4. DB クラスタの状態を確認
+4.1 現在のDBクラスタのまま起動が可能か確認
+DB クラスタが存在していません
+...[NG]
+4.2 巻き戻しを実行することで起動が可能か確認
+DB クラスタが存在していません
+...[NG]
+4.3 ベースバックアップを取得することが可能か確認
+...[OK]
+
+以下の方法で起動が可能です
+b) ベースバックアップを取得してStandbyを起動
+q) Standbyの起動を中止する
+起動方法を選択してください(b/q) b
+5. IC-LAN が接続されていることを確認
+...[OK]
+6. Primary からベースバックアップ取得
+22631/22631 kB (100%), 1/1 tablespace
+NOTICE:  pg_stop_backup complete, all required WAL segments have been archived
+...[OK]
+7. Primary のアーカイブディレクトリと同期
+000000010000000000000002.partial
+00000002.history
+000000020000000000000003.00000028.backup
+000000010000000000000001
+000000020000000000000002
+000000020000000000000003
+...[OK]
+8. Standby の起動 (アーカイブリカバリ対象 WAL セグメント数: 1)
+...[OK]
+9. Standby の起動確認
+...[OK]
+ノード(pgrex02)が Standby として起動しました
+

pg-rex_stop

+
概要
+

本コマンドを実行したノードで、PG-REXのPrimaryまたはStandbyを停止します。

+
形式
+
pg-rex_stop [-f][-h][-v]
+
引数
+
    +
  • -f, –fast

    +

    停止前にCHECKPOINTとsyncコマンドを実行しません

  • +
  • -h, –help

    +

    Usageを表示して終了します

  • +
  • -v, –version

    +

    バージョン情報を表示して終了します

  • +
+
実行例
+
# pg-rex_stop
+Primary を停止します
+1. Pacemaker 停止
+...[OK]
+2. Pacemaker 停止確認
+...[OK]
+PG-REX の Primary (pgrex01)を停止しました
+

pg-rex_archivefile_delete

+
概要
+

本コマンドを実行したノードで不要なアーカイブログを削除します。不要なアーカイブログとは、PG-REXのPrimaryとStandbyのDBクラスタ、およびコマンド実行時に指定したベースバックアップのリカバリに不要なアーカイブログです。指定したベースバックアップの取得時点よりも過去に取得したベースバックアップは使用できなくなることに注意してください。PrimaryとStandbyの両方がSSH接続可能である必要があります。

+

コマンド実行時にベースバックアップの場所の指定を省略した場合は、対話形式での指定となります。対話形式でも省略した場合は、PrimaryとStandbyのみを対象にして不要なアーカイブログを削除します。ベースバックアップの場所がリモートサーバの場合は、環境設定ファイルのBACKUP_NODE_SSH_PASS_MODEに設定した認証方式でリモートサーバにアクセスします。

+

本コマンドには、不要なアーカイブログを削除するモード(削除モード)と移動するモード(移動モード)があります。移動モードを指定した場合は、アーカイブログ格納ディレクトリ直下に現在日時のディレクトリを作成し、当該ディレクトリに不要なアーカイブログが移動されます。

+
形式
+
pg-rex_archivefile_delete {-m|-r}[-f][-D DBclusterFilepath][-h][-v] [[Hostname:]BasebackupPath]
+
+
引数
+
    +
  • -m, –move

    +

    移動モードで実行します

  • +
  • -r, –remove

    +

    削除モードで実行します

    +

    ※ +移動モードまたは削除モードはどちらか片方を必ず指定してください

  • +
  • -f, –force

    +

    アーカイブログの削除を問い合わせ無しで実行します

  • +
  • -h, –help

    +

    Usageを表示して終了します

  • +
  • -v, –version

    +

    バージョン情報を表示して終了します

  • +
  • -D, –dbcluster=DBclusterFilepath

    +

    両ノードで使用しているDBクラスタの場所の絶対パスを指定します

    +

    指定を省略した場合は、以下の優先度で値が適用されます

    +
      +
    1. rootユーザでの実行の場合、リソース定義xmlの「pgdata」
    2. +
    3. 環境変数の「PGDATA」
    4. +
  • +
  • Hostname

    +

    ベースバックアップが存在するリモートサーバを指定します

    +

    Hostnameを省略した場合は”localhost”が適用されます

  • +
  • BasebackupPath

    +

    ベースバックアップの場所を絶対パスで指定します

  • +
+
実行例
+
    +
  • 移動モードでベースバックアップの指定をしない場合
  • +
+
# pg-rex_archivefile_delete -m
+
+**** 1. 実行準備 ****
+移動モードで実行します
+ベースバックアップが存在するリモートサーバを入力してください
+(入力しなければ "localhost" を設定します)
+>
+ベースバックアップの場所の絶対パスを入力してください
+(入力しなければバックアップ指定無しとして実行されアーカイブが削除されるため、
+以前に取得したベースバックアップが使用できなくなります)
+>
+環境設定ファイル (pgrex_tools.conf) を読み込みます
+root@192.168.2.2's password:
+パスワードが入力されました
+両ノードの名前を取得します
+cib.xml ファイルを読み込みます
+ベースバックアップの場所を指定せずに実行すると、
+自身のノード "pgrex01" と相手のノード "pgrex02" の
+現時点の PGDATA "/dbfp/pgdata/data" を基準にしてアーカイブログを削除することになります
+アーカイブログを削除しますか (y/N) : y
+
+**** 2. WAL ファイル名の取得 ****
+自身のノード "pgrex01" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します
+"00000002000000000000000C"
+相手のノード "pgrex02" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します
+"000000020000000000000003"
+
+**** 3. 削除基準の算出 ****
+削除基準を "000000020000000000000003" としました
+
+**** 4. アーカイブログの移動 ****
+削除対象のリストに "000000010000000000000002" を追加します
+削除対象のリストに "000000020000000000000002" を追加します
+削除対象のリストに "000000010000000000000001" を追加します
+移動先ディレクトリ "/dbfp/pgarch/arc1/20130826_163510" を作成しました
+-- 移動 -- 000000010000000000000002
+-- 移動 -- 000000020000000000000002
+-- 移動 -- 000000010000000000000001
+アーカイブログの移動に成功しました
+移動モード実行のため、移動したファイルは"/dbfp/pgarch/arc1/20130826_163510" に格納されています
+
    +
  • 削除モードで、ベースバックアップを指定した場合
  • +
+
# pg-rex_archivefile_delete -r pgrex03:/pgdata/backup_data
+
+**** 1. 実行準備 ****
+削除モードで実行します
+環境設定ファイル (pg-rex_tools.conf) を読み込みます
+root@192.168.2.2's password:
+パスワードが入力されました
+両ノードの名前を取得します
+cib.xml ファイルを読み込みます
+
+**** 2. WAL ファイル名の取得 ****
+指定されたバックアップからリカバリを行うために必要な最初の WAL ファイル名を取得します
+root@pgrex03's password:
+パスワードが入力されました
+"000000020000000000000004"
+自身のノード "pgrex01" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します
+"000000020000000000000003"
+相手のノード "pgrex02" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します
+"00000002000000000000000C"
+
+**** 3. 削除基準の算出 ****
+削除基準を "000000020000000000000003" としました
+
+**** 4. アーカイブログの削除 ****
+削除対象のリストに "000000010000000000000001" を追加します
+削除対象のリストに "000000010000000000000002" を追加します
+削除対象のリストに "000000020000000000000002" を追加します
+-- 削除 -- 000000010000000000000001
+-- 削除 -- 000000010000000000000002
+-- 削除 -- 000000020000000000000002
+アーカイブログの削除に成功しました
+

pg-rex_switchover

+
概要
+

Standbyの再組み込み時にベースバックアップを取得せずにPG-REXのノード切り替えを実行します。ベースバックアップを取得しないことで、ノード切り替え時間の短縮を実現します。

+

本コマンドは、PG-REXのPrimaryとStandbyのどちらのノードでも実行することができます。

+
形式
+
pg-rex_switchover [-h][-v]
+
引数
+
    +
  • -h, –help

    +

    Usageを表示して終了します

  • +
  • -v, –version

    +

    バージョン情報を表示して終了します

  • +
+
実行例
+
# pg-rex_switchover
+root@192.168.2.2's password:
+パスワードが入力されました
+**** 実行準備 ****
+1. 環境設定ファイル (pg-rex_tools.conf) の読み込みと両ノードの名前を取得
+...[OK]
+2. 現在およびノード切り替え後のHAクラスタ状態を確認
+
+[ 現在 / ノード切り替え後のHAクラスタ状態 ]
+ Primary : pgrex01 -> pgrex02
+ Standby : pgrex02 -> pgrex01
+
+ノード切り替え中は可用性が保証されません。
+ノード切り替えを実行してもよろしいでしょうか? (y/N) y
+
+**** ノード切り替えを実行 ****
+3. Pacemaker の監視を停止
+...[OK]
+4. Primary (pgrex01) の PostgreSQL を停止
+...[OK]
+5. Pacemaker の監視を再開しノード切り替えを実行
+...[OK]
+6. pgrex02 が新 Primary になったことを確認
+
+**** pgrex02 が Primary として起動しました ****
+
+7. pgrex01 の Pacemaker を停止
+...[OK]
+8. pgrex01 で Standby を起動
+00000011000000000000000C
+00000012000000000000000E
+00000013.history
+
+**** pgrex01 が Standby として起動しました ****
+
+****************************************
+**** ノード切り替えが正常に完了しました ****
+****************************************
+
+[ 現在のHAクラスタ状態 ]
+ Primary : pgrex02
+ Standby : pgrex01
+

設定ファイル

+

PG-REX運用補助ツールで利用する設定ファイルについて以下に示します。

+
    +
  1. 格納場所 : /etc
  2. +
  3. ファイル名 : pg-rex_tools.conf
  4. +
+

設定項目一覧

+
    +
  • D_LAN_IPAddress +
      +
    • 両ノードのD-LANのIPアドレスを指定します。IPアドレスはカンマで区切って指定します。指定必須の項目です。
    • +
  • +
  • IC_LAN_IPAddress +
      +
    • IC-LAN系統ごとに括弧で囲ったIPアドレスの組を記述します。指定必須の項目です。 +
        +
      • (IC-LAN1 アドレス1,IC-LAN1 アドレス2)[,(IC-LAN2 アドレス1 ,IC-LAN2 +アドレス2)]
      • +
    • +
  • +
  • Archive_dir +
      +
    • アーカイブディレクトリの絶対パスを指定します。指定必須の項目です。
    • +
  • +
  • STONITH +
      +
    • STONITHの設定値は常にenableを指定します。
    • +
  • +
  • IPADDR_STANDBY +
      +
    • Standby側接続用の仮想IPを使用する環境の場合はenable、それ以外の場合はdisableを指定します。省略した場合はenableとなります。
    • +
  • +
  • PGPATH +
      +
    • PostgreSQLコマンドへの絶対パスを指定します。設定を省略した場合はpostgresユーザログイン時に設定される環境変数のPATHからPostgreSQLコマンドへのパスを取得します。
    • +
  • +
  • PEER_NODE_SSH_PASS_MODE +
      +
    • manualpassfilenopassのいずれかを指定します。運用補助ツールは実行時に相手ノードにsshで接続する場合があり、このパラメータでssh接続のパスワード取得方法を変更することができます。設定値はセキュリティの観点からmanualを推奨します。指定必須の項目です。 +
        +
      • manual : パスワードを手動で入力する。
      • +
      • passfile : +PEER_NODE_SSH_PASS_FILEに指定したファイル内のパスワードを参照する。
      • +
      • nopass:公開鍵認証を使用する。
      • +
    • +
  • +
  • PEER_NODE_SSH_PASS_FILE +
      +
    • 相手ノードへのssh接続に必要なパスワードのみが記述されたファイルの絶対パスを指定します。 +PEER_NODE_SSH_PASS_MODEにpassfileを指定している場合は、設定が必須となります。passfile以外を指定している場合、この設定は参照されません。
    • +
  • +
  • BACKUP_NODE_SSH_PASS_MODE +
      +
    • manual、passfile、nopassのいずれかを設定します。pg-rex_archivefile_deleteコマンドは実行時にDBクラスタのバックアップ格納先ノードにsshで接続する場合があり、このパラメータでssh接続のパスワード取得方法を変更することができます。設定値はセキュリティの観点からmanualを推奨します。指定必須の項目です。 +
        +
      • manual : パスワードを手動で入力する。
      • +
      • passfile :BACKUP_NODE_SSH_ +PASS_FILEに指定したファイル内のパスワードを参照する。
      • +
      • nopass : 公開鍵認証を使用する。
      • +
    • +
  • +
  • BACKUP_NODE_SSH_PASS_FILE +
      +
    • DBクラスタのバックアップ格納先ノードへのssh接続に必要なパスワードのみが記述されたファイルの絶対パスを指定します。BACKUP_NODE_SSH_PASS_MODEにpassfileを指定している場合は、設定が必須となります。passfile以外を指定している場合、この設定は参照されません。
    • +
  • +
  • PG_REX_Primary_ResourceID +
      +
    • 環境定義書のPromotableのリソースIDを指定します。指定必須の項目です。
    • +
  • +
  • PG_REX_Primitive_ResourceID +
      +
    • 環境定義書のPostgreSQL制御のリソースIDを指定します。指定必須の項目です。
    • +
  • +
  • IPADDR_PRIMARY_ResourceID +
      +
    • Primary側接続用の仮想IPの起動確認を行う場合、環境定義書の仮想IP定義のうちから、Primary側接続用のリソースIDを指定します。
    • +
  • +
  • IPADDR_REPLICATION_ResourceID +
      +
    • レプリケーション受付用の仮想IPの起動確認を行う場合、環境定義書の仮想IP定義のうちから、レプリケーション受付用のリソースIDを指定します。
    • +
  • +
  • IPADDR_STANDBY_ResourceID +
      +
    • Standby側接続用の仮想IPの起動確認を行う場合、環境定義書の仮想IP定義のうちから、Standby側接続用のリソースIDを指定します。
    • +
  • +
  • PING_ResourceID +
      +
    • PINGリソースの起動確認を行う場合、環境定義書のネットワーク監視のリソースIDを指定します。複数指定する場合はカンマ区切りで指定します。
    • +
  • +
  • STORAGE_MON_ResourceID +
      +
    • STORAGE-MONリソースの起動確認を行う場合、環境定義書のディスク監視リソースIDを指定します。
    • +
  • +
  • STONITH_ResourceID +
      +
    • STONITHリソースの起動確認を行う場合、環境定義書のハードウェア制御STONITHプラグインのリソースIDをカンマ区切りで2つ指定します。
    • +
  • +
  • HACLUSTER_NAME +
      +
    • Pacemakerで管理するHAクラスタ名を指定します。HAクラスタ名には英数字とアンダースコア, +およびハイフンのみ使用可です。ただし先頭にはハイフンは使用できません。
    • +
  • +
+

使用上の注意と制約

+

PG-REX運用補助ツール利用時の制約を以下に示します。

+
    +
  1. pg-rex_switchoverによるノード切り替えでは、Primaryの停止後からPrimaryの切り替え(新Primaryの起動)が完了するまでの間は一時的にサービスが停止した状態となる。
  2. +
  3. pg-rex_switchoverによるノード切り替えの実施中に、pg-rex_switchoverが異常終了した場合のHAクラスタ状態は不確定であり、サービスが停止している可能性がある。この場合、元の状態への復旧は自動で実施されないため、HAクラスタ状態を確認し、手動復旧を試みること。
  4. +
  5. 起動確認はPostgreSQLやIPaddr2、Ping、STONITHなどの固有のリソースにしか確認を行わないため、Apacheなど新しくリソースを追加したとしてもその確認を行わない。
  6. +
  7. 両ノードの状態確認にネットワークの通信を用いるので、ツールが使用するLAN(デフォルトはD-LAN)切断時は、それ以外のLANが繋がっていても実行に失敗する。
  8. +
  9. PG-REXでインストールしたファイルのディレクトリ構成が2つのノードで同一であること。
  10. +
  11. アーカイブログを圧縮する場合、圧縮方式に対応した拡張子を付与しなければならない。サポートする圧縮方式はgzip +(拡張子.gz)のみである。
  12. +
+

よくあるQ&A

+

PG-REX運用補助ツール利用時における、よくある質問について以下に示します。

+
    +
  • Q1. 運用補助ツールコマンドのタイムアウトの時間を変更したい。 +
      +
    • pg-rex_primary_start、pg-rex_standby_start、pg-rex_stop、pg-rex_switchoverコマンドの起動/停止確認時間のタイムアウト値は300秒に設定してあります。これを変更したい場合は、/usr/local/bin配下にある上記コマンドの”my +$timeout = 300”と記述されている箇所の数値(秒単位)を変更して下さい。
    • +
  • +
  • Q2. +Standby起動時にベースバックアップを取得しなくてよいパターンとは具体的にどういう場合か。 +
      +
    • 例えば、フェイルオーバ後に旧PrimaryをStandbyとして起動する場合や、Standbyを一旦停止して再び起動する場合はベースバックアップを取得する必要はありません。ただし、どちらの場合でもStandby起動時のPostgreSQLのリカバリに必要なWALファイルを消去していないことが前提となります。
    • +
  • +
  • Q3. +PG-REXでインストールするファイルのディレクトリ構成を2つのノードで異なる構成にしたい。 +
      +
    • PG-REXではディレクトリ構成が2つのノードで同一であることを前提としています。ディレクトリ構成が両ノードで同一でない場合、運用補助ツールは正常に動作しません。
    • +
  • +
  • Q4. 運用補助ツールが表示するメッセージに日本語と英語が混在する。 +
      +
    • 運用補助ツールではPostgreSQLのコマンドをpostgresユーザに切り替えて実行しています。そのため、PostgreSQLのコマンドが出力するメッセージはpostgresユーザの言語設定に沿って表示されます。
    • +
  • +
+

PG-REX運用補助ツール +15からの変更点

+
    +
  • 対応するPG-REXのバージョンは16です。
  • +
  • 対応するOSはRed Hat Enterprise Linux 9.2です。
  • +
+
+

Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE +CORPORATION

+ + diff --git a/pg-rex_operation_tools/man/pg-rex_tools_manual-ja.md b/pg-rex_operation_tools/man/pg-rex_tools_manual-ja.md new file mode 100644 index 0000000..9779854 --- /dev/null +++ b/pg-rex_operation_tools/man/pg-rex_tools_manual-ja.md @@ -0,0 +1,693 @@ +# PG-REX運用補助ツール 16 利用マニュアル + +## 目次 + +- [運用補助ツールとは?](#運用補助ツールとは) +- [導入方法](#導入方法) +- [コマンドリファレンス](#コマンドリファレンス) +- [設定ファイル](#設定ファイル) +- [使用上の注意と制約](#使用上の注意と制約) +- [よくあるQ&A](#よくあるQA) +- [PG-REX運用補助ツール 15からの変更点](#PG-REX運用補助ツール15からの変更点) + +## 運用補助ツールとは? {#運用補助ツールとは} + +PG-REX運用補助ツールとは、PG-REXの運用手順の簡易化を目的とした以下のコマンド群です。 + +### 機能概要 + +各コマンドの概要を以下に示します。 + +1. pg-rex_primary_start + + 本コマンドを実行したノードで、PG-REXをPrimaryとして起動する。 + +2. pg-rex_standby_start + + 本コマンドを実行したノードで、PG-REXをStandbyとして起動する。 + +3. pg-rex_stop + + PG-REXのPrimaryまたはStandbyを停止する。 + +4. pg-rex_archivefile_delete + + データベースの復旧に必要のないアーカイブログを移動または削除する。 + +5. pg-rex_switchover + + PG-REXのノード切り替えを実施する。 + + + +## 導入方法 + +### インストール + +#### 動作確認済み環境 + +- OS : Red Hat Enterprise Linux 9.2 + - perl-IO-Tty : 1.16-4 +- PG-REX : 16 + - DBMS : PostgreSQL 16.0 + - HA : Pacemaker 2.1.5-9, pcs 0.11.4-7, pm_extra_tools 1.5 + + + +#### パッケージのインストール + +PG-REX運用補助ツールを使用するためにインストール必須のRPMパッケージを以下に示します。 +バージョンは適宜読み替えてください。 + +1. pg-rex_operation_tools_script-16.0-1.el9.noarch.rpm +2. Net_OpenSSH-0.62-1.el8.x86_64.rpm +3. perl-IO-Tty-1.16-4.el9.x86_64.rpm + +Net_OpenSSHは、pg-rex_operation_tools_scriptとセットで提供されますが、perl-IO-Ttyは提供されません。 \ +標準リポジトリには含まれていないため、以下のリポジトリから入手する必要があります。 + +- Red Hat Enterprise Linuxの場合は、「Red Hat CodeReady Linux Builder for RHEL 9 x86_64」リポジトリ +- Rocky Linuxの場合は、「Rocky Linux 9 - CRB」リポジトリ + + + + +PG-REX運用補助ツールのRPMをインストールします。 + +``` +# dnf install pg-rex_operation_tools_script-16.0-1.el8.noarch.rpm Net_OpenSSH-0.62-1.el8.x86_64.rpm perl-IO-Tty-1.16-4.el9.x86_64.rpm +``` + +PG-REX運用補助ツールのRPMパッケージをインストールすると、コマンドと設定ファイルは以下のように配置されます。 + +``` +/usr + └-local + └-bin + └-pg-rex_primary_start + └-pg-rex_standby_start + └-pg-rex_stop + └-pg-rex_archivefile_delete + └-pg-rex_switchover + +/etc + └- pg-rex_tools.conf +``` + + + +#### 設定ファイルの編集 + +環境にあわせて、`/etc/pg-rex_tools.conf`の設定を行います。各項目の詳細は[設定ファイル](#設定ファイル)を参照してください。設定例を以下に示します。 + +``` +$ cat /etc/pg-rex_tools.conf +D_LAN_IPAddress = 192.168.2.1 , 192.168.2.2 +IC_LAN_IPAddress = (192.168.1.1, 192.168.1.2) , (192.168.3.1, 192.168.3.2) +Archive_dir = /dbfp/pgarch/arc1 +IPADDR_STANDBY = enable +PGPATH = /usr/pgsql-15/bin +PEER_NODE_SSH_PASS_MODE = passfile +PEER_NODE_SSH_PASS_FILE = /root/.pgrex/peer_passwd +BACKUP_NODE_SSH_PASS_MODE = passfile +BACKUP_NODE_SSH_PASS_FILE = /root/.pgrex/bkup_passwd +PG_REX_Primary_ResourceID = pgsql-clone +PG_REX_Primitive_ResourceID = pgsql +IPADDR_PRIMARY_ResourceID = ipaddr-primary +IPADDR_REPLICATION_ResourceID = ipaddr-replication +IPADDR_STANDBY_ResourceID = ipaddr-standby +PING_ResourceID = ping-clone +STORAGE_MON_ResouceID = storage-mon-clone +STONITH_ResourceID = fence1-ipmilan , fence2-ipmilan +HACLUSTER_NAME = pgrex_hacluster +``` + + + +#### ネットワーク接続の登録 + +PG-REX運用補助ツールが相手ノードの操作や確認をD-LANを使用して行うため、事前に両ノードのrootユーザの`.ssh/known_hosts`に相手先のD-LANのIPアドレスに対する接続登録をする必要があります。 + +``` +# ssh 192.168.2.2 ←相手先のD-LANのIPアドレスを指定 +The authenticity of host '192.168.2.2 (192.168.2.2)' can't be established. +ECDSA key fingerprint is ******* +Are you sure you want to continue connecting (yes/no/[fingerprint])? yes +↑[yes]を入力し[Enter]キーを押下 +Warning: Permanently added '192.168.2.2' (ECDSA) to the list of known hosts. +root@192.168.2.2's password: ←[Ctrl]キーと[C]キーを同時に押下 +``` + + + +### アンインストール + +RPMパッケージのアンインストールを行います。 + +``` +# dnf remove pg-rex_operation_tools_script-16.0-1.el8.noarch.rpm Net_OpenSSH-0.62-1.el8.x86_64.rpm perl-IO-Tty-1.16-4.el9.x86_64.rpm +``` + + + +## コマンドリファレンス + +1. [pg-rex_primary_start](#pg-rex_primary_start) +2. [pg-rex_standby_start](#pg-rex_standby_start) +3. [pg-rex_stop](#pg-rex_stop) +4. [pg-rex_archivefile_delete](#pg-rex_archivefile_delete) +5. [pg-rex_switchover](#pg-rex_switchover) + +### pg-rex_primary_start + +##### 概要 + +本コマンドを実行したノードで、PG-REXをPrimaryとして起動します。 + +##### 形式 + +``` +pg-rex_primary_start [-h][-v][XmlFilePath] +``` + +##### 引数 + +- -h, --help + + Usageを表示して終了します + +- -v, --version + + バージョン情報を表示して終了します + +- *XmlFilePath* + + リソース定義xmlファイルのファイルパスを指定します(初回起動時に使用する) + +##### 実行例 + +- 引数なしの実行例 + +``` +# pg-rex_primary_start +root@192.168.2.2's password: +パスワードが入力されました +1. Pacemaker および Corosync が停止していることを確認 +...[OK] +2. 稼働中の Primary が存在していないことを確認 +...[OK] +3. Primary として稼働することが出来るかを確認 +...[OK] +4. 起動禁止フラグの存在を確認 +...[OK] +5. Pacemaker 起動 +...[OK] +6. Primary の起動確認 +...[OK] +ノード(pgrex01)が Primary として起動しました +``` + +- 引数ありの実行例 + +``` +# pg-rex_primary_start pm_pcsgen_env.xml +root@192.168.2.2's password: +パスワードが入力されました +1. Pacemaker および Corosync が停止していることを確認 +...[OK] +2. 稼働中の Primary が存在していないことを確認 +...[OK] +3. 起動禁止フラグの存在を確認 +...[OK] +既に HAクラスタ があります +再作成しても宜しいでしょうか? (y/N) y +4. HAクラスタ の破棄 +...[OK] +5. HAクラスタ の作成 +...[OK] +6. Pacemaker 起動 +...[OK] +7. リソース定義 xml ファイルの反映 +...[OK] +8. Primary の起動確認 +...[OK] +ノード(pgrex01)が Primary として起動しました +``` + + + +### pg-rex_standby_start + +##### 概要 + +本コマンドを実行したノードで、PG-REXをStandbyとして起動します。 + +##### 形式 + +``` +pg-rex_standby_start [[-n] [-r] [-b] | -c] [-d] [-s] [-h] [-v] +``` + +##### 引数 + +- -n, --normal + + 現在のDBクラスタを使用してStandby を起動します + +- -r, --rewind + + 現在のDBクラスタに相手 (Primary) ノードと同期したうえでレプリケーションを行えるように、巻き戻し後、Standbyを起動します + +- -b, --basebackup + + 相手ノードをPrimaryとしてベースバックアップを取得後、Standbyとして起動します + +- -d, --dry-run + + データの変更とノードの起動の実行を伴わず、表示のみを行います + +- -c, --check-only + + DBクラスタの状態確認までを実施します + +- -s, --shared-archive-directory + + Primary と Standby でアーカイブディレクトリを共有しているものとして動作します + +- -h, --help + + Usageを表示して終了します + +- -v, --version + + バージョン情報を表示して終了します + +##### 実行例 + +``` +# pg-rex_standby_start +root@192.168.2.1's password: +パスワードが入力されました +1. Pacemaker および Corosync が停止していることを確認 +...[OK] +2. 稼働中の Primary が存在していることを確認 +...[OK] +3. 起動禁止フラグが存在しないことを確認 +...[OK] +4. DB クラスタの状態を確認 +4.1 現在のDBクラスタのまま起動が可能か確認 +DB クラスタが存在していません +...[NG] +4.2 巻き戻しを実行することで起動が可能か確認 +DB クラスタが存在していません +...[NG] +4.3 ベースバックアップを取得することが可能か確認 +...[OK] + +以下の方法で起動が可能です +b) ベースバックアップを取得してStandbyを起動 +q) Standbyの起動を中止する +起動方法を選択してください(b/q) b +5. IC-LAN が接続されていることを確認 +...[OK] +6. Primary からベースバックアップ取得 +22631/22631 kB (100%), 1/1 tablespace +NOTICE: pg_stop_backup complete, all required WAL segments have been archived +...[OK] +7. Primary のアーカイブディレクトリと同期 +000000010000000000000002.partial +00000002.history +000000020000000000000003.00000028.backup +000000010000000000000001 +000000020000000000000002 +000000020000000000000003 +...[OK] +8. Standby の起動 (アーカイブリカバリ対象 WAL セグメント数: 1) +...[OK] +9. Standby の起動確認 +...[OK] +ノード(pgrex02)が Standby として起動しました +``` + + + +### pg-rex_stop + +##### 概要 + +本コマンドを実行したノードで、PG-REXのPrimaryまたはStandbyを停止します。 + +##### 形式 + +``` +pg-rex_stop [-f][-h][-v] +``` + +##### 引数 + +- -f, --fast + + 停止前にCHECKPOINTとsyncコマンドを実行しません + +- -h, --help + + Usageを表示して終了します + +- -v, --version + + バージョン情報を表示して終了します + +##### 実行例 + +``` +# pg-rex_stop +Primary を停止します +1. Pacemaker 停止 +...[OK] +2. Pacemaker 停止確認 +...[OK] +PG-REX の Primary (pgrex01)を停止しました +``` + + + +### pg-rex_archivefile_delete + +##### 概要 + +本コマンドを実行したノードで不要なアーカイブログを削除します。不要なアーカイブログとは、PG-REXのPrimaryとStandbyのDBクラスタ、およびコマンド実行時に指定したベースバックアップのリカバリに不要なアーカイブログです。指定したベースバックアップの取得時点よりも過去に取得したベースバックアップは使用できなくなることに注意してください。PrimaryとStandbyの両方がSSH接続可能である必要があります。 + +コマンド実行時にベースバックアップの場所の指定を省略した場合は、対話形式での指定となります。対話形式でも省略した場合は、PrimaryとStandbyのみを対象にして不要なアーカイブログを削除します。ベースバックアップの場所がリモートサーバの場合は、環境設定ファイルのBACKUP_NODE_SSH_PASS_MODEに設定した認証方式でリモートサーバにアクセスします。 + +本コマンドには、不要なアーカイブログを削除するモード(削除モード)と移動するモード(移動モード)があります。移動モードを指定した場合は、アーカイブログ格納ディレクトリ直下に現在日時のディレクトリを作成し、当該ディレクトリに不要なアーカイブログが移動されます。 + +##### 形式 + +``` +pg-rex_archivefile_delete {-m|-r}[-f][-D DBclusterFilepath][-h][-v] [[Hostname:]BasebackupPath] + +``` + +##### 引数 + +- -m, --move + + 移動モードで実行します + +- -r, --remove + + 削除モードで実行します + + ※ 移動モードまたは削除モードはどちらか片方を必ず指定してください + + +- -f, --force + + アーカイブログの削除を問い合わせ無しで実行します + +- -h, --help + + Usageを表示して終了します + +- -v, --version + + バージョン情報を表示して終了します + +- -D, --dbcluster=*DBclusterFilepath* + + 両ノードで使用しているDBクラスタの場所の絶対パスを指定します + + 指定を省略した場合は、以下の優先度で値が適用されます + + 1. rootユーザでの実行の場合、リソース定義xmlの「pgdata」 + 2. 環境変数の「PGDATA」 + +- *Hostname* + + ベースバックアップが存在するリモートサーバを指定します + + Hostnameを省略した場合は"localhost"が適用されます + +- *BasebackupPath* + + ベースバックアップの場所を絶対パスで指定します + +##### 実行例 + +- 移動モードでベースバックアップの指定をしない場合 + +``` +# pg-rex_archivefile_delete -m + +**** 1. 実行準備 **** +移動モードで実行します +ベースバックアップが存在するリモートサーバを入力してください +(入力しなければ "localhost" を設定します) +> +ベースバックアップの場所の絶対パスを入力してください +(入力しなければバックアップ指定無しとして実行されアーカイブが削除されるため、 +以前に取得したベースバックアップが使用できなくなります) +> +環境設定ファイル (pgrex_tools.conf) を読み込みます +root@192.168.2.2's password: +パスワードが入力されました +両ノードの名前を取得します +cib.xml ファイルを読み込みます +ベースバックアップの場所を指定せずに実行すると、 +自身のノード "pgrex01" と相手のノード "pgrex02" の +現時点の PGDATA "/dbfp/pgdata/data" を基準にしてアーカイブログを削除することになります +アーカイブログを削除しますか (y/N) : y + +**** 2. WAL ファイル名の取得 **** +自身のノード "pgrex01" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します +"00000002000000000000000C" +相手のノード "pgrex02" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します +"000000020000000000000003" + +**** 3. 削除基準の算出 **** +削除基準を "000000020000000000000003" としました + +**** 4. アーカイブログの移動 **** +削除対象のリストに "000000010000000000000002" を追加します +削除対象のリストに "000000020000000000000002" を追加します +削除対象のリストに "000000010000000000000001" を追加します +移動先ディレクトリ "/dbfp/pgarch/arc1/20130826_163510" を作成しました +-- 移動 -- 000000010000000000000002 +-- 移動 -- 000000020000000000000002 +-- 移動 -- 000000010000000000000001 +アーカイブログの移動に成功しました +移動モード実行のため、移動したファイルは"/dbfp/pgarch/arc1/20130826_163510" に格納されています +``` + +- 削除モードで、ベースバックアップを指定した場合 + +``` +# pg-rex_archivefile_delete -r pgrex03:/pgdata/backup_data + +**** 1. 実行準備 **** +削除モードで実行します +環境設定ファイル (pg-rex_tools.conf) を読み込みます +root@192.168.2.2's password: +パスワードが入力されました +両ノードの名前を取得します +cib.xml ファイルを読み込みます + +**** 2. WAL ファイル名の取得 **** +指定されたバックアップからリカバリを行うために必要な最初の WAL ファイル名を取得します +root@pgrex03's password: +パスワードが入力されました +"000000020000000000000004" +自身のノード "pgrex01" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します +"000000020000000000000003" +相手のノード "pgrex02" の現時点の PGDATA "/dbfp/pgdata/data" からリカバリに必要な最初の WAL ファイル名を取得します +"00000002000000000000000C" + +**** 3. 削除基準の算出 **** +削除基準を "000000020000000000000003" としました + +**** 4. アーカイブログの削除 **** +削除対象のリストに "000000010000000000000001" を追加します +削除対象のリストに "000000010000000000000002" を追加します +削除対象のリストに "000000020000000000000002" を追加します +-- 削除 -- 000000010000000000000001 +-- 削除 -- 000000010000000000000002 +-- 削除 -- 000000020000000000000002 +アーカイブログの削除に成功しました +``` + + + +### pg-rex_switchover + +##### 概要 + +Standbyの再組み込み時にベースバックアップを取得せずにPG-REXのノード切り替えを実行します。ベースバックアップを取得しないことで、ノード切り替え時間の短縮を実現します。 + +本コマンドは、PG-REXのPrimaryとStandbyのどちらのノードでも実行することができます。 + +##### 形式 + +``` +pg-rex_switchover [-h][-v] +``` + +##### 引数 + +- -h, --help + + Usageを表示して終了します + +- -v, --version + + バージョン情報を表示して終了します + +##### 実行例 + +``` +# pg-rex_switchover +root@192.168.2.2's password: +パスワードが入力されました +**** 実行準備 **** +1. 環境設定ファイル (pg-rex_tools.conf) の読み込みと両ノードの名前を取得 +...[OK] +2. 現在およびノード切り替え後のHAクラスタ状態を確認 + +[ 現在 / ノード切り替え後のHAクラスタ状態 ] + Primary : pgrex01 -> pgrex02 + Standby : pgrex02 -> pgrex01 + +ノード切り替え中は可用性が保証されません。 +ノード切り替えを実行してもよろしいでしょうか? (y/N) y + +**** ノード切り替えを実行 **** +3. Pacemaker の監視を停止 +...[OK] +4. Primary (pgrex01) の PostgreSQL を停止 +...[OK] +5. Pacemaker の監視を再開しノード切り替えを実行 +...[OK] +6. pgrex02 が新 Primary になったことを確認 + +**** pgrex02 が Primary として起動しました **** + +7. pgrex01 の Pacemaker を停止 +...[OK] +8. pgrex01 で Standby を起動 +00000011000000000000000C +00000012000000000000000E +00000013.history + +**** pgrex01 が Standby として起動しました **** + +**************************************** +**** ノード切り替えが正常に完了しました **** +**************************************** + +[ 現在のHAクラスタ状態 ] + Primary : pgrex02 + Standby : pgrex01 +``` + + + +## 設定ファイル + +PG-REX運用補助ツールで利用する設定ファイルについて以下に示します。 + +1. 格納場所 : /etc +2. ファイル名 : pg-rex_tools.conf + + +### 設定項目一覧 + + +- D_LAN_IPAddress + - **両ノードのD-LANのIPアドレス**を指定します。IPアドレスはカンマで区切って指定します。**指定必須**の項目です。 +- IC_LAN_IPAddress + - **IC-LAN系統ごとに括弧で囲ったIPアドレスの組**を記述します。**指定必須**の項目です。 + - (IC-LAN1 アドレス1,IC-LAN1 アドレス2)[,(IC-LAN2 アドレス1 ,IC-LAN2 アドレス2)] +- Archive_dir + - **アーカイブディレクトリの絶対パス**を指定します。**指定必須**の項目です。 +- STONITH + - STONITHの設定値は常に**enable**を指定します。 +- IPADDR_STANDBY + - Standby側接続用の仮想IPを使用する環境の場合は**enable**、それ以外の場合は**disable**を指定します。省略した場合は**enable**となります。 +- PGPATH + - **PostgreSQLコマンドへの絶対パス**を指定します。設定を省略した場合はpostgresユーザログイン時に設定される環境変数のPATHからPostgreSQLコマンドへのパスを取得します。 +- PEER_NODE_SSH_PASS_MODE + - **manual**、**passfile**、**nopass**のいずれかを指定します。運用補助ツールは実行時に相手ノードにsshで接続する場合があり、このパラメータでssh接続のパスワード取得方法を変更することができます。設定値はセキュリティの観点から**manual**を推奨します。**指定必須**の項目です。 + - manual : パスワードを手動で入力する。 + - passfile : PEER_NODE_SSH_PASS_FILEに指定したファイル内のパスワードを参照する。 + - nopass:公開鍵認証を使用する。 +- PEER_NODE_SSH_PASS_FILE + - **相手ノードへのssh接続に必要なパスワードのみが記述されたファイルの絶対パス**を指定します。 **PEER_NODE_SSH_PASS_MODEにpassfileを指定している場合**は、**設定が必須**となります。passfile以外を指定している場合、この設定は参照されません。 +- BACKUP_NODE_SSH_PASS_MODE + - **manual、passfile、nopass**のいずれかを設定します。pg-rex_archivefile_deleteコマンドは実行時にDBクラスタのバックアップ格納先ノードにsshで接続する場合があり、このパラメータでssh接続のパスワード取得方法を変更することができます。設定値はセキュリティの観点から**manual**を推奨します。**指定必須**の項目です。 + - manual : パスワードを手動で入力する。 + - passfile :BACKUP_NODE_SSH_ PASS_FILEに指定したファイル内のパスワードを参照する。 + - nopass : 公開鍵認証を使用する。 +- BACKUP_NODE_SSH_PASS_FILE + - **DBクラスタのバックアップ格納先ノードへのssh接続に必要なパスワードのみが記述されたファイルの絶対パス**を指定します。**BACKUP_NODE_SSH_PASS_MODEにpassfileを指定している場合**は、**設定が必須**となります。passfile以外を指定している場合、この設定は参照されません。 +- PG_REX_Primary_ResourceID + - **環境定義書のPromotableのリソースID**を指定します。**指定必須**の項目です。 +- PG_REX_Primitive_ResourceID + - **環境定義書のPostgreSQL制御のリソースID**を指定します。**指定必須**の項目です。 +- IPADDR_PRIMARY_ResourceID + - Primary側接続用の仮想IPの起動確認を行う場合、**環境定義書の仮想IP定義のうちから、Primary側接続用のリソースID**を指定します。 +- IPADDR_REPLICATION_ResourceID + - レプリケーション受付用の仮想IPの起動確認を行う場合、**環境定義書の仮想IP定義のうちから、レプリケーション受付用のリソースID**を指定します。 +- IPADDR_STANDBY_ResourceID + - Standby側接続用の仮想IPの起動確認を行う場合、**環境定義書の仮想IP定義のうちから、Standby側接続用のリソースID**を指定します。 +- PING_ResourceID + - PINGリソースの起動確認を行う場合、**環境定義書のネットワーク監視のリソースID**を指定します。複数指定する場合はカンマ区切りで指定します。 +- STORAGE_MON_ResourceID + - STORAGE-MONリソースの起動確認を行う場合、**環境定義書のディスク監視リソースID**を指定します。 +- STONITH_ResourceID + - STONITHリソースの起動確認を行う場合、**環境定義書のハードウェア制御STONITHプラグインのリソースID**をカンマ区切りで2つ指定します。 +- HACLUSTER_NAME + - **Pacemakerで管理するHAクラスタ名**を指定します。HAクラスタ名には英数字とアンダースコア, およびハイフンのみ使用可です。ただし先頭にはハイフンは使用できません。 + + +## 使用上の注意と制約 + +PG-REX運用補助ツール利用時の制約を以下に示します。 + +1. pg-rex_switchoverによるノード切り替えでは、Primaryの停止後からPrimaryの切り替え(新Primaryの起動)が完了するまでの間は一時的にサービスが停止した状態となる。 +2. pg-rex_switchoverによるノード切り替えの実施中に、pg-rex_switchoverが異常終了した場合のHAクラスタ状態は不確定であり、サービスが停止している可能性がある。この場合、元の状態への復旧は自動で実施されないため、HAクラスタ状態を確認し、手動復旧を試みること。 +3. 起動確認はPostgreSQLやIPaddr2、Ping、STONITHなどの固有のリソースにしか確認を行わないため、Apacheなど新しくリソースを追加したとしてもその確認を行わない。 +4. 両ノードの状態確認にネットワークの通信を用いるので、ツールが使用するLAN(デフォルトはD-LAN)切断時は、それ以外のLANが繋がっていても実行に失敗する。 +5. PG-REXでインストールしたファイルのディレクトリ構成が2つのノードで同一であること。 +6. アーカイブログを圧縮する場合、圧縮方式に対応した拡張子を付与しなければならない。サポートする圧縮方式はgzip (拡張子.gz)のみである。 + + + +## よくあるQ&A {#よくあるQA} + +PG-REX運用補助ツール利用時における、よくある質問について以下に示します。 + +- Q1. 運用補助ツールコマンドのタイムアウトの時間を変更したい。 + - pg-rex_primary_start、pg-rex_standby_start、pg-rex_stop、pg-rex_switchoverコマンドの起動/停止確認時間のタイムアウト値は300秒に設定してあります。これを変更したい場合は、/usr/local/bin配下にある上記コマンドの”my $timeout = 300”と記述されている箇所の数値(秒単位)を変更して下さい。 + + +- Q2. Standby起動時にベースバックアップを取得しなくてよいパターンとは具体的にどういう場合か。 + - 例えば、フェイルオーバ後に旧PrimaryをStandbyとして起動する場合や、Standbyを一旦停止して再び起動する場合はベースバックアップを取得する必要はありません。ただし、どちらの場合でもStandby起動時のPostgreSQLのリカバリに必要なWALファイルを消去していないことが前提となります。 + + +- Q3. PG-REXでインストールするファイルのディレクトリ構成を2つのノードで異なる構成にしたい。 + - PG-REXではディレクトリ構成が2つのノードで同一であることを前提としています。ディレクトリ構成が両ノードで同一でない場合、運用補助ツールは正常に動作しません。 + + +- Q4. 運用補助ツールが表示するメッセージに日本語と英語が混在する。 + - 運用補助ツールではPostgreSQLのコマンドをpostgresユーザに切り替えて実行しています。そのため、PostgreSQLのコマンドが出力するメッセージはpostgresユーザの言語設定に沿って表示されます。 + + + +## PG-REX運用補助ツール 15からの変更点 {#PG-REX運用補助ツール15からの変更点} + +- 対応するPG-REXのバージョンは16です。 +- 対応するOSはRed Hat Enterprise Linux 9.2です。 + + +------ + +Copyright (c) 2012-2024, NIPPON TELEGRAPH AND TELEPHONE CORPORATION diff --git a/pg-rex_operation_tools/man/pg-rex_tools_manual.css b/pg-rex_operation_tools/man/pg-rex_tools_manual.css new file mode 100644 index 0000000..3fc8eec --- /dev/null +++ b/pg-rex_operation_tools/man/pg-rex_tools_manual.css @@ -0,0 +1,607 @@ +@charset "UTF-8"; + +pre, dl, ol, p, blockquote { line-height:130%; } + +blockquote { margin-left:32px; } + +body,td { + color:black; + background-color:white; + margin-left:2%; + margin-right:2%; + font-size:100%; + font-family:verdana, arial, helvetica, Sans-Serif; +} + +a:link { + color:#215dc6; + background-color:inherit; + text-decoration:none; +} + +a:active { + color:#215dc6; + background-color:#CCDDEE; + text-decoration:none; +} + +a:visited { + color:#a63d21; + background-color:inherit; + text-decoration:none; +} + +a:hover { + color:#215dc6; + background-color:#CCDDEE; + text-decoration:underline; +} + +h1, h2 { + font-family:verdana, arial, helvetica, Sans-Serif; + color:inherit; + background-color:#DDEEFF; + padding:.3em; + border:0px; + margin:0px 0px .5em 0px; +} +h3 { + font-family:verdana, arial, helvetica, Sans-Serif; + border-bottom: 3px solid #DDEEFF; + border-top: 1px solid #DDEEFF; + border-left: 10px solid #DDEEFF; + border-right: 5px solid #DDEEFF; + + color:inherit; + background-color:#FFFFFF; + padding:.3em; + margin:0px 0px .5em 0px; +} +h4 { + font-family:verdana, arial, helvetica, Sans-Serif; + border-left: 18px solid #DDEEFF; + + color:inherit; + background-color:#FFFFFF; + padding:.3em; + margin:0px 0px .5em 0px; +} +h5, h6 { + font-family:verdana, arial, helvetica, Sans-Serif; + color:inherit; + background-color:#DDEEFF; + padding:.3em; + border:0px; + margin:0px 0px .5em 0px; +} + +h1.title { + font-size: 30px; + font-weight:bold; + background-color:transparent; + padding: 12px 0px 0px 0px; + border: 0px; + margin: 12px 0px 0px 0px; +} + +dt { + font-weight:bold; + margin-top:1em; + margin-left:1em; +} + +pre { + border-top:#DDDDEE 1px solid; + border-bottom:#888899 1px solid; + border-left:#DDDDEE 1px solid; + border-right:#888899 1px solid; + padding:.5em; + margin-left:1em; + margin-right:2em; + white-space:pre; + color:black; + background-color:#F0F8FF; +} + +img { + border:none; + vertical-align:middle; +} + +ul { + margin-top:.5em; + margin-bottom:.5em; + line-height:130%; +} + +em { font-style:italic; } + +strong { font-weight:bold; } + +thead td.style_td, +tfoot td.style_td { + color:inherit; + background-color:#D0D8E0; +} +thead th.style_th, +tfoot th.style_th { + color:inherit; + background-color:#E0E8F0; +} +.style_table { + padding:0px; + border:0px; + margin:auto; + text-align:left; + color:inherit; + background-color:#ccd5dd; +} +.style_th { + padding:5px; + margin:1px; + text-align:center; + color:inherit; + background-color:#EEEEEE; +} +.style_td { + padding:5px; + margin:1px; + color:inherit; + background-color:#EEF5FF; +} + +ul.list1 { list-style-type:disc; } +ul.list2 { list-style-type:circle; } +ul.list3 { list-style-type:square; } +ol.list1 { list-style-type:decimal; } +ol.list2 { list-style-type:lower-roman; } +ol.list3 { list-style-type:lower-alpha; } + +div.ie5 { text-align:center; } + +span.noexists { + color:inherit; + background-color:#FFFACC; +} + +.small { font-size:80%; } + +.super_index { + color:#DD3333; + background-color:inherit; + font-weight:bold; + font-size:60%; + vertical-align:super; +} + +a.note_super { + color:#DD3333; + background-color:inherit; + font-weight:bold; + font-size:60%; + vertical-align:super; +} + +div.jumpmenu { + font-size:60%; + text-align:right; +} + +hr.full_hr { + border-style:ridge; + border-color:#333333; + border-width:1px 0px; +} +hr.note_hr { + width:90%; + border-style:ridge; + border-color:#333333; + border-width:1px 0px; + text-align:center; + margin:1em auto 0em auto; +} + +span.size1 { + font-size:xx-small; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size2 { + font-size:x-small; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size3 { + font-size:small; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size4 { + font-size:medium; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size5 { + font-size:large; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size6 { + font-size:x-large; + line-height:130%; + text-indent:0px; + display:inline; +} +span.size7 { + font-size:xx-large; + line-height:130%; + text-indent:0px; + display:inline; +} + +/* html.php/catbody() */ +strong.word0 { + background-color:#FFFF66; + color:black; +} +strong.word1 { + background-color:#A0FFFF; + color:black; +} +strong.word2 { + background-color:#99FF99; + color:black; +} +strong.word3 { + background-color:#FF9999; + color:black; +} +strong.word4 { + background-color:#FF66FF; + color:black; +} +strong.word5 { + background-color:#880000; + color:white; +} +strong.word6 { + background-color:#00AA00; + color:white; +} +strong.word7 { + background-color:#886800; + color:white; +} +strong.word8 { + background-color:#004699; + color:white; +} +strong.word9 { + background-color:#990099; + color:white; +} + +/* html.php/edit_form() */ +.edit_form { clear:both; } + +/* pukiwiki.skin.php */ +div#header { + padding:0px; + margin:0px; +} + +div#navigator { + clear:both; + padding:4px 0px 0px 0px; + margin:0px; +} + +td.menubar { + width:9em; + vertical-align:top; +} + +div#menubar { + width:9em; + padding:0px; + margin:4px; + word-break:break-all; + font-size:90%; + overflow:hidden; +} + +div#menubar ul { + margin:0px 0px 0px .5em; + padding:0px 0px 0px .5em; +} + +div#menubar ul li { line-height:110%; } + +div#menubar h4 { font-size:110%; } + +div#body { + padding:0px; + margin:0px 0px 0px .5em; +} + +div#note { + clear:both; + padding:0px; + margin:0px; +} + +div#attach { + clear:both; + padding:0px; + margin:0px; +} + +div#toolbar { + clear:both; + padding:0px; + margin:0px; + text-align:right; +} + +div#lastmodified { + font-size:80%; + padding:0px; + margin:0px; +} + +div#related { + font-size:80%; + padding:0px; + margin:16px 0px 0px 0px; +} + +div#footer { + font-size:70%; + padding:0px; + margin:16px 0px 0px 0px; +} + +div#banner { + float:right; + margin-top:24px; +} + +div#preview { + color:inherit; + background-color:#F5F8FF; +} + +img#logo { + float:left; + margin-right:20px; +} + +/* aname.inc.php */ +.anchor {} +.anchor_super { + font-size:xx-small; + vertical-align:super; +} + +/* br.inc.php */ +br.spacer {} + +/* calendar*.inc.php */ +.style_calendar { + padding:0px; + border:0px; + margin:3px; + color:inherit; + background-color:#CCD5DD; + text-align:center; +} +.style_td_caltop { + padding:5px; + margin:1px; + color:inherit; + background-color:#EEF5FF; + font-size:80%; + text-align:center; +} +.style_td_today { + padding:5px; + margin:1px; + color:inherit; + background-color:#FFFFDD; + text-align:center; +} +.style_td_sat { + padding:5px; + margin:1px; + color:inherit; + background-color:#DDE5FF; + text-align:center; +} +.style_td_sun { + padding:5px; + margin:1px; + color:inherit; + background-color:#FFEEEE; + text-align:center; +} +.style_td_blank { + padding:5px; + margin:1px; + color:inherit; + background-color:#EEF5FF; + text-align:center; +} +.style_td_day { + padding:5px; + margin:1px; + color:inherit; + background-color:#EEF5FF; + text-align:center; +} +.style_td_week { + padding:5px; + margin:1px; + color:inherit; + background-color:#DDE5EE; + font-size:80%; + font-weight:bold; + text-align:center; +} + +/* calendar_viewer.inc.php */ +div.calendar_viewer { + color:inherit; + background-color:inherit; + margin-top:20px; + margin-bottom:10px; + padding-bottom:10px; +} +span.calendar_viewer_left { + color:inherit; + background-color:inherit; + float:left; +} +span.calendar_viewer_right { + color:inherit; + background-color:inherit; + float:right; +} + +/* clear.inc.php */ +.clear { + margin:0px; + clear:both; +} + +/* counter.inc.php */ +div.counter { font-size:70%; } + +/* diff.inc.php */ +span.diff_added { + color:blue; + background-color:inherit; +} + +span.diff_removed { + color:red; + background-color:inherit; +} + +/* hr.inc.php */ +hr.short_line { + text-align:center; + width:80%; + border-style:solid; + border-color:#333333; + border-width:1px 0px; +} + +/* include.inc.php */ +h5.side_label { text-align:center; } + +/* navi.inc.php */ +ul.navi { + margin:0px; + padding:0px; + text-align:center; +} +li.navi_none { + display:inline; + float:none; +} +li.navi_left { + display:inline; + float:left; + text-align:left; +} +li.navi_right { + display:inline; + float:right; + text-align:right; +} + +/* new.inc.php */ +span.comment_date { font-size:x-small; } +span.new1 { + color:red; + background-color:transparent; + font-size:x-small; +} +span.new5 { + color:green; + background-color:transparent; + font-size:xx-small; +} + +/* popular.inc.php */ +span.counter { font-size:70%; } +ul.popular_list { +} + +/* recent.inc.php,showrss.inc.php */ +ul.recent_list { +} + +/* ref.inc.php */ +div.img_margin { + margin-left:32px; + margin-right:32px; +} + +/* vote.inc.php */ +td.vote_label { + color:inherit; + background-color:#FFCCCC; +} +td.vote_td1 { + color:inherit; + background-color:#DDE5FF; +} +td.vote_td2 { + color:inherit; + background-color:#EEF5FF; +} + +table { + background: #f9f9f9; + border: 1px solid #aaa; + border-collapse: collapse; +} + +th, td { + border: 1px solid #aaa; + padding: 0.2em; +} + +thead th { + background: #f2f2f2; + text-align: center; +} + +tbody th { + background: #f2f2f2; + text-align: left; +} + +div.index { + float:right; + border:thin solid black; + background-color: white; + padding-top: 0.2em; + padding-bottom: 0.2em; + padding-left: 0em; + padding-right: 1em; + margin-left: 0.5em; +} + +div.index ol { counter-reset: item; } +div.index li { display: block; } +div.index li:before { + content: counters(item, ".") ". "; + counter-increment: item; +} diff --git a/pg-rex_operation_tools/pg-rex_tools.conf b/pg-rex_operation_tools/pg-rex_tools.conf new file mode 100644 index 0000000..7da8cf5 --- /dev/null +++ b/pg-rex_operation_tools/pg-rex_tools.conf @@ -0,0 +1,77 @@ +D_LAN_IPAddress = # 両ノードの D-LAN の IP アドレスをカンマ区切りで指定します。 + # (例) 192.168.2.1 , 192.168.2.2 + # 省略することはできません。必ず指定してください。 + +IC_LAN_IPAddress = # IC-LAN の IP アドレス設定を系統ごとに指定します。 + # (例) (192.168.1.1, 192.168.1.2) , (192.168.3.1, 192.168.3.2) + # 省略することはできません。必ず指定してください。 + +Archive_dir = # アーカイブディレクトリを絶対パスで指定します。 + # 省略することはできません。必ず指定してください。 + +STONITH = enable # STONITHの設定値は常に enable を指定します。 + +IPADDR_STANDBY = enable # Standby 側接続用の仮想 IP を使用する環境の場合は enable、 + # それ以外の場合は disable を指定します。 + # 省略した場合は enable となります。 + +PGPATH = /usr/pgsql-16/bin # psql , pg_basebackup , pg_controldata などの PostgreSQL コマンドパスを指定します。 + # 省略した場合は postgres ユーザログイン時に設定される環境変数の PATH から + # PostgreSQL コマンドパスを取得します。 + +PEER_NODE_SSH_PASS_MODE = manual # 相手ノードへの ssh 接続時のパスワード入力モードを指定します。 + # 指定出来るモードは以下の3つです。 + # manual : ユーザが手動でパスワードを入力するモード。 + # passfile : パスワードが書かれているファイルを読み込むモード。 + # PEER_NODE_SSH_PASS_FILE に指定されたファイルを読み込む。 + # nopass : パスワード入力を行なわないモード。 + # 省略した場合は manual が指定されます。 + +PEER_NODE_SSH_PASS_FILE = # 相手ノードへの ssh 接続に必要なパスワードが記述されたファイルを絶対パスで指定します。 + # PEER_NODE_SSH_PASS_MODE の設定を passfile にしている場合は省略が出来ません。 + # passfile 以外を設定した場合はこの設定は無視されます。 + +BACKUP_NODE_SSH_PASS_MODE = manual # DBクラスタのバックアップを格納しているノードへの ssh 接続時の + # パスワード入力モードを指定します。 + # 指定出来るモードは以下の3つです。 + # manual : ユーザが手動でパスワードを入力するモード。 + # passfile : パスワードが書かれているファイルを読み込むモード。 + # PEER_NODE_SSH_PASS_FILE に指定されたファイルを読み込む。 + # nopass : パスワード入力を行なわないモード。 + # 省略した場合は manual が指定されます。 + + +BACKUP_NODE_SSH_PASS_FILE = # DBクラスタのバックアップを格納しているノードへの ssh接続に + # 必要なパスワードが記述されたファイルを絶対パスで指定します。 + # BACKUP_NODE_SSH_PASS_MODE の設定を passfile にしている場合は省略が出来ません。 + # passfile 以外を設定した場合はこの設定は無視されます。 + +# 以下は起動確認に用いるリソース ID の設定になります。 + +PG_REX_Primary_ResourceID = pgsql-clone # 環境定義書の Promotable のリソース ID を指定します。 + # 省略することはできません。必ず指定してください。 + +PG_REX_Primitive_ResourceID = pgsql # 環境定義書の PostgreSQL 制御のリソース ID を指定します。 + # 省略することはできません。必ず指定してください。 + +IPADDR_PRIMARY_ResourceID = ipaddr-primary # Primary 側接続用の仮想 IP の起動確認を行う場合、 + # 環境定義書の仮想 IP 定義のうちから、 Primary 側接続用のリソース ID を指定します。 + +IPADDR_REPLICATION_ResourceID = ipaddr-replication # レプリケーション受付用の仮想 IP の起動確認を行う場合、 + # 環境定義書の仮想 IP 定義のうちから、レプリケーション受付用のリソース ID を指定します。 + +IPADDR_STANDBY_ResourceID = ipaddr-standby # Standby 側接続用の仮想 IP の起動確認を行う場合、 + # 環境定義書の仮想 IP 定義のうちから、 Standby 側接続用のリソース ID を指定します。 + +PING_ResourceID = ping-clone # PING リソースの起動確認を行う場合、環境定義書のネットワーク監視のリソース ID を指定します。 + # 複数指定する場合はカンマ区切りで指定します。 + +STORAGE_MON_ResourceID = storage-mon-clone # STORAGE-MON リソースの起動確認を行う場合、 + # 環境定義書のディスク監視のリソース ID を指定します。 + +STONITH_ResourceID = fence1-ipmilan , fence2-ipmilan # STONITH リソースの起動確認を行う場合、 + # 環境定義書のハードウェア制御 STONITH プラグインのリソース ID をカンマ区切りで2つ指定します。 + +HACLUSTER_NAME = pgrex_cluster # Pacemaker で管理する HA クラスタ名を指定します。 + # 省略することはできません。必ず指定してください。 +