# HG changeset patch # User Sergey Kandaurov # Date 1401782949 -14400 # Node ID 17c5a1cc8757c18494a9ccb20720fe16bf453caa # Parent 9fe6fc05c1d16642a89836b9a65525bfb246365a Tests: upstream hash tests. diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm --- a/lib/Test/Nginx.pm +++ b/lib/Test/Nginx.pm @@ -109,6 +109,8 @@ sub has_module($) { empty_gif => '(?s)^(?!.*--without-http_empty_gif_module)', browser => '(?s)^(?!.*--without-http_browser_module)', + upstream_hash + => '(?s)^(?!.*--without-http_upstream_hash_module)', upstream_ip_hash => '(?s)^(?!.*--without-http_upstream_ip_hash_module)', upstream_least_conn diff --git a/upstream_hash.t b/upstream_hash.t new file mode 100644 --- /dev/null +++ b/upstream_hash.t @@ -0,0 +1,225 @@ +#!/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/); + +$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 /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 = 8083) { + return 502; + } + return 204; + } + } +} + +EOF + +$t->try_run('no upstream hash')->plan(11); + +############################################################################### + +# Ony 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)], 8082), 'inconsistent'); +ok(cmp_peers([iter('/c', 20)], [iter('/c2', 20)], 8082), 'consistent'); +ok(cmp_peers([iter('/cw', 20)], [iter('/cw2', 20)], 8082), 'consistent weight'); + +like(many('/?a=1', 10), qr/808\d: 10/, 'stable hash'); +like(many('/c?a=1', 10), qr/808\d: 10/, 'stable hash - consistent'); + +my @res = iter('/', 10); + +is(@res, 10, 'all hashed peers'); + +@res = grep { $_ != 8083 } @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 { $_ == 8081 } iter('/bad', 20); +is(@res, 20, 'all hashed peers - bad'); + +@res = grep { $_ == 8081 } iter('/cbad', 20); +is(@res, 20, 'all hashed peers - bad 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 my $i (1 .. $count) { + if (http_get($uri) =~ /X-Port: (\d+)/) { + $ports{$1} = 0 unless defined $ports{$1}; + $ports{$1}++; + } + } + + return join ', ', map { $_ . ": " . $ports{$_} } sort keys %ports; +} + +############################################################################### diff --git a/upstream_hash_memcached.t b/upstream_hash_memcached.t new file mode 100644 --- /dev/null +++ b/upstream_hash_memcached.t @@ -0,0 +1,164 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for upstream hash balancer module distribution consistency +# with Cache::Memcached and Cache::Memcached::Fast. + +############################################################################### + +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; + +eval { require Cache::Memcached; }; +plan(skip_all => 'Cache::Memcached not installed') if $@; +eval { require Cache::Memcached::Fast; }; +plan(skip_all => 'Cache::Memcached::Fast not installed') if $@; + +my $t = Test::Nginx->new()->has(qw/http rewrite memcached upstream_hash/) + ->has_daemon('memcached')->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + upstream memd { + hash $arg_a; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + server 127.0.0.1:8083; + } + + upstream memd_c { + hash $arg_a consistent; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + server 127.0.0.1:8083; + } + + upstream memd_w { + hash $arg_a; + server 127.0.0.1:8081 weight=2; + server 127.0.0.1:8082 weight=3; + server 127.0.0.1:8083; + } + + upstream memd_cw { + hash $arg_a consistent; + server 127.0.0.1:8081 weight=2; + server 127.0.0.1:8082 weight=3; + server 127.0.0.1:8083; + } + + server { + listen 127.0.0.1:8080; + server_name localhost; + + set $memcached_key $arg_a; + + location / { + memcached_pass memd; + } + location /c { + memcached_pass memd_c; + } + location /w { + memcached_pass memd_w; + } + location /cw { + memcached_pass memd_cw; + } + } +} + +EOF + +my $memhelp = `memcached -h`; +my @memopts = (); + +if ($memhelp =~ /repcached/) { + # repcached patch adds additional listen socket + push @memopts, '-X', '8082'; +} +if ($memhelp =~ /-U/) { + # UDP port is on by default in memcached 1.2.7+ + push @memopts, '-U', '0'; +} + +$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081', @memopts); +$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8082', @memopts); +$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8083', @memopts); +$t->try_run('no upstream hash')->plan(4); + +$t->waitforsocket('127.0.0.1:8081') or die "Can't start memcached"; +$t->waitforsocket('127.0.0.1:8082') or die "Can't start memcached"; +$t->waitforsocket('127.0.0.1:8083') or die "Can't start memcached"; + +############################################################################### + +my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]); +my $memd2 = Cache::Memcached->new(servers => [ '127.0.0.1:8082' ]); +my $memd3 = Cache::Memcached->new(servers => [ '127.0.0.1:8083' ]); + +for my $i (1 .. 20) { + $memd1->set($i, '8081') or die "can't put value into memcached: $!"; + $memd2->set($i, '8082') or die "can't put value into memcached: $!"; + $memd3->set($i, '8083') or die "can't put value into memcached: $!"; +} + +my $memd = new Cache::Memcached(servers => + [ '127.0.0.1:8081', '127.0.0.1:8082', '127.0.0.1:8083' ]); + +is_deeply(ngx('/'), mem($memd), 'cache::memcached'); + +$memd = new Cache::Memcached::Fast({ ketama_points => 160, servers => + [ '127.0.0.1:8081', '127.0.0.1:8082', '127.0.0.1:8083'] }); + +is_deeply(ngx('/c'), mem($memd), 'cache::memcached::fast'); + +$memd = new Cache::Memcached(servers => [ + [ '127.0.0.1:8081', 2 ], + [ '127.0.0.1:8082', 3 ], + [ '127.0.0.1:8083', 1 ]]); + +is_deeply(ngx('/w'), mem($memd), 'cache::memcached weight'); + +$memd = new Cache::Memcached::Fast({ ketama_points => 160, servers => [ + { address => '127.0.0.1:8081', weight => 2 }, + { address => '127.0.0.1:8082', weight => 3 }, + { address => '127.0.0.1:8083', weight => 1 }] }); + +is_deeply(ngx('/cw'), mem($memd), 'cache::memcached::fast weight'); + +############################################################################### + +sub ngx { + my ($uri) = @_; + [ map { http_get("/$uri?a=$_") =~ /^(\d+)/ms && $1; } (1 .. 20) ]; +} + +sub mem { + my ($memd) = @_; + [ map { $memd->get($_); } (1 .. 20) ]; +} + +###############################################################################