125 lines
4.8 KiB
Perl
125 lines
4.8 KiB
Perl
# Copyright (c) 2023, PostgreSQL Global Development Group
|
|
use strict;
|
|
use warnings;
|
|
use Config;
|
|
use PostgreSQL::Test::Utils;
|
|
use PostgreSQL::Test::Cluster;
|
|
use Test::More;
|
|
|
|
if ($ENV{PG_TEST_EXTRA} !~ /\bload_balance\b/)
|
|
{
|
|
plan skip_all =>
|
|
'Potentially unsafe test load_balance not enabled in PG_TEST_EXTRA';
|
|
}
|
|
|
|
# This tests loadbalancing based on a DNS entry that contains multiple records
|
|
# for different IPs. Since setting up a DNS server is more effort than we
|
|
# consider reasonable to run this test, this situation is instead immitated by
|
|
# using a hosts file where a single hostname maps to multiple different IP
|
|
# addresses. This test requires the adminstrator to add the following lines to
|
|
# the hosts file (if we detect that this hasn't happend we skip the test):
|
|
#
|
|
# 127.0.0.1 pg-loadbalancetest
|
|
# 127.0.0.2 pg-loadbalancetest
|
|
# 127.0.0.3 pg-loadbalancetest
|
|
#
|
|
# Windows or Linux are required to run this test because these OSes allow
|
|
# binding to 127.0.0.2 and 127.0.0.3 addresess by default, but other OSes
|
|
# don't. We need to bind to different IP addresses, so that we can use these
|
|
# different IP addresses in the hosts file.
|
|
#
|
|
# The hosts file needs to be prepared before running this test. We don't do it
|
|
# on the fly, because it requires root permissions to change the hosts file. In
|
|
# CI we set up the previously mentioned rules in the hosts file, so that this
|
|
# load balancing method is tested.
|
|
|
|
# Cluster setup which is shared for testing both load balancing methods
|
|
my $can_bind_to_127_0_0_2 = $Config{osname} eq 'linux' || $PostgreSQL::Test::Utils::windows_os;
|
|
|
|
# Checks for the requirements for testing load balancing method 2
|
|
if (!$can_bind_to_127_0_0_2) {
|
|
plan skip_all => 'load_balance test only supported on Linux and Windows';
|
|
}
|
|
|
|
my $hosts_path;
|
|
if ($windows_os) {
|
|
$hosts_path = 'c:\Windows\System32\Drivers\etc\hosts';
|
|
}
|
|
else
|
|
{
|
|
$hosts_path = '/etc/hosts';
|
|
}
|
|
|
|
my $hosts_content = PostgreSQL::Test::Utils::slurp_file($hosts_path);
|
|
|
|
my $hosts_count = () = $hosts_content =~ /127\.0\.0\.[1-3] pg-loadbalancetest/g;
|
|
if ($hosts_count != 3) {
|
|
# Host file is not prepared for this test
|
|
plan skip_all => "hosts file was not prepared for DNS load balance test"
|
|
}
|
|
|
|
$PostgreSQL::Test::Cluster::use_tcp = 1;
|
|
$PostgreSQL::Test::Cluster::test_pghost = '127.0.0.1';
|
|
my $port = PostgreSQL::Test::Cluster::get_free_port();
|
|
my $node1 = PostgreSQL::Test::Cluster->new('node1', port => $port);
|
|
my $node2 = PostgreSQL::Test::Cluster->new('node2', port => $port, own_host => 1);
|
|
my $node3 = PostgreSQL::Test::Cluster->new('node3', port => $port, own_host => 1);
|
|
|
|
# Create a data directory with initdb
|
|
$node1->init();
|
|
$node2->init();
|
|
$node3->init();
|
|
|
|
# Start the PostgreSQL server
|
|
$node1->start();
|
|
$node2->start();
|
|
$node3->start();
|
|
|
|
# load_balance_hosts=disable should always choose the first one.
|
|
$node1->connect_ok("host=pg-loadbalancetest port=$port load_balance_hosts=disable",
|
|
"load_balance_hosts=disable connects to the first node",
|
|
sql => "SELECT 'connect2'",
|
|
log_like => [qr/statement: SELECT 'connect2'/]);
|
|
|
|
|
|
# Statistically the following loop with load_balance_hosts=random will almost
|
|
# certainly connect at least once to each of the nodes. The chance of that not
|
|
# happening is so small that it's negligible: (2/3)^50 = 1.56832855e-9
|
|
foreach my $i (1 .. 50) {
|
|
$node1->connect_ok("host=pg-loadbalancetest port=$port load_balance_hosts=random",
|
|
"repeated connections with random load balancing",
|
|
sql => "SELECT 'connect1'");
|
|
}
|
|
|
|
my $node1_occurences = () = $node1->log_content() =~ /statement: SELECT 'connect1'/g;
|
|
my $node2_occurences = () = $node2->log_content() =~ /statement: SELECT 'connect1'/g;
|
|
my $node3_occurences = () = $node3->log_content() =~ /statement: SELECT 'connect1'/g;
|
|
|
|
my $total_occurences = $node1_occurences + $node2_occurences + $node3_occurences;
|
|
|
|
ok($node1_occurences > 1, "received at least one connection on node1");
|
|
ok($node2_occurences > 1, "received at least one connection on node2");
|
|
ok($node3_occurences > 1, "received at least one connection on node3");
|
|
ok($total_occurences == 50, "received 50 connections across all nodes");
|
|
|
|
$node1->stop();
|
|
$node2->stop();
|
|
|
|
# load_balance_hosts=disable should continue trying hosts until it finds a
|
|
# working one.
|
|
$node3->connect_ok("host=pg-loadbalancetest port=$port load_balance_hosts=disable",
|
|
"load_balance_hosts=disable continues until it connects to the a working node",
|
|
sql => "SELECT 'connect3'",
|
|
log_like => [qr/statement: SELECT 'connect3'/]);
|
|
|
|
# Also with load_balance_hosts=random we continue to the next nodes if previous
|
|
# ones are down. Connect a few times to make sure it's not just lucky.
|
|
foreach my $i (1 .. 5) {
|
|
$node3->connect_ok("host=pg-loadbalancetest port=$port load_balance_hosts=random",
|
|
"load_balance_hosts=random continues until it connects to the a working node",
|
|
sql => "SELECT 'connect4'",
|
|
log_like => [qr/statement: SELECT 'connect4'/]);
|
|
}
|
|
|
|
done_testing();
|