changeset 411:17c5a1cc8757

Tests: upstream hash tests.
author Sergey Kandaurov <pluknet@nginx.com>
date Tue, 03 Jun 2014 12:09:09 +0400
parents 9fe6fc05c1d1
children 995f3476202e
files lib/Test/Nginx.pm upstream_hash.t upstream_hash_memcached.t
diffstat 3 files changed, 391 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- 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
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;
+}
+
+###############################################################################
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) ];
+}
+
+###############################################################################