blob: 8edbe953e1bc7a0ca7e6050f88e8c405ea9d3b1f [file] [log] [blame]
#!/usr/bin/perl
# (C) Sergey Kandaurov
# (C) Nginx, Inc.
# Tests for upstream hash balancer module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http proxy rewrite upstream_hash/)->plan(15);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
upstream u {
hash $arg_a;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
upstream u2 {
hash $arg_a;
server 127.0.0.1:8081;
server 127.0.0.1:8083;
}
upstream cw {
hash $arg_a consistent;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083 weight=10;
}
upstream cw2 {
hash $arg_a consistent;
server 127.0.0.1:8081;
server 127.0.0.1:8083 weight=10;
}
upstream c {
hash $arg_a consistent;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
upstream c2 {
hash $arg_a consistent;
server 127.0.0.1:8081;
server 127.0.0.1:8083;
}
upstream bad {
hash $arg_a;
server 127.0.0.1:8081;
server 127.0.0.1:8084;
}
upstream cbad {
hash $arg_a consistent;
server 127.0.0.1:8081;
server 127.0.0.1:8084;
}
server {
listen 127.0.0.1:8080;
server_name localhost;
location / {
proxy_pass http://u;
}
location /2 {
proxy_pass http://u2;
}
location /cw {
proxy_pass http://cw;
}
location /cw2 {
proxy_pass http://cw2;
}
location /c {
proxy_pass http://c;
}
location /c2 {
proxy_pass http://c2;
}
location /bad {
proxy_pass http://bad;
}
location /cbad {
proxy_pass http://cbad;
}
location /busy {
proxy_pass http://bad/busy;
add_header X-IP $upstream_addr always;
}
location /cbusy {
proxy_pass http://cbad/busy;
add_header X-IP $upstream_addr always;
}
location /pnu {
proxy_pass http://u/;
proxy_next_upstream http_502;
}
}
server {
listen 127.0.0.1:8081;
listen 127.0.0.1:8082;
listen 127.0.0.1:8083;
server_name localhost;
add_header X-Port $server_port;
location / {
return 204;
}
location /502 {
if ($server_port = %%PORT_8083%%) {
return 502;
}
return 204;
}
location /busy {
return 444;
}
}
server {
listen 127.0.0.1:8084;
server_name localhost;
return 444;
}
}
EOF
$t->run();
###############################################################################
my @ports = my ($p1, $p2, $p3) = (port(8081), port(8082), port(8083));
# Only requests for absent peer are moved to other peers if hash is consistent.
# Check this by comparing two upstreams with different number of peers.
ok(!cmp_peers([iter('/', 20)], [iter('/2', 20)], $p2), 'inconsistent');
ok(cmp_peers([iter('/c', 20)], [iter('/c2', 20)], $p2), 'consistent');
ok(cmp_peers([iter('/cw', 20)], [iter('/cw2', 20)], $p2), 'consistent weight');
like(many('/?a=1', 10), qr/($p1|$p2|$p3): 10/, 'stable hash');
like(many('/c?a=1', 10), qr/($p1|$p2|$p3): 10/, 'stable hash - consistent');
# fallback to round-robin
TODO: {
local $TODO = 'not yet' unless $t->has_version('1.17.1');
like(many('/?a=', 6), qr/$p1: 2, $p2: 2, $p3: 2/, 'empty key');
like(many('/c?a=', 6), qr/$p1: 2, $p2: 2, $p3: 2/, 'empty key - consistent');
}
my @res = iter('/', 10);
is(@res, 10, 'all hashed peers');
@res = grep { $_ != $p3 } @res;
my @res2 = iter('/502', 10);
is_deeply(\@res, \@res2, 'no proxy_next_upstream');
isnt(@res2, 10, 'no proxy_next_upstream peers');
is(iter('/pnu/502', 10), 10, 'proxy_next_upstream peers');
@res = grep { $_ == $p1 } iter('/bad', 20);
is(@res, 20, 'all hashed peers - bad');
@res = grep { $_ == $p1 } iter('/cbad', 20);
is(@res, 20, 'all hashed peers - bad consistent');
like(http_get('/busy'), qr/X-IP: 127.0.0.1:$p1, bad/,
'upstream name - busy');
like(http_get('/cbusy'), qr/X-IP: 127.0.0.1:$p1, cbad/,
'upstream name - busy consistent');
###############################################################################
# Returns true if two arrays follow consistency, i.e., they may only differ
# by @args present in $p, but absent in $p2, for the same indices.
sub cmp_peers {
my ($p, $p2, @args) = @_;
for my $i (0 .. $#$p) {
next if @{$p}[$i] == @{$p2}[$i];
next if (grep $_ == @{$p}[$i], @args);
return 0;
}
return 1;
}
# series of requests, each with unique hash key
sub iter {
my ($uri, $count) = @_;
my @res;
for my $i (1 .. $count) {
if (http_get("$uri/?a=$i") =~ /X-Port: (\d+)/) {
push @res, $1 if defined $1;
}
}
return @res;
}
sub many {
my ($uri, $count) = @_;
my %ports;
for (1 .. $count) {
if (http_get($uri) =~ /X-Port: (\d+)/) {
$ports{$1} = 0 unless defined $ports{$1};
$ports{$1}++;
}
}
my @keys = map { my $p = $_; grep { $p == $_ } keys %ports } @ports;
return join ', ', map { $_ . ": " . $ports{$_} } @keys;
}
###############################################################################