# HG changeset patch # User Sergey Kandaurov # Date 1529060348 -10800 # Node ID fda743e92b31c6fe8dcac677ee8fd123eb77fbeb # Parent 99a83f7e7755742f0360b608e54d3f06e784791f Tests: upstream random tests. diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm --- a/lib/Test/Nginx.pm +++ b/lib/Test/Nginx.pm @@ -155,6 +155,8 @@ sub has_module($) { => '(?s)^(?!.*--without-http_upstream_ip_hash_module)', upstream_least_conn => '(?s)^(?!.*--without-http_upstream_least_conn_mod)', + upstream_random + => '(?s)^(?!.*--without-http_upstream_random_module)', upstream_keepalive => '(?s)^(?!.*--without-http_upstream_keepalive_modu)', upstream_zone @@ -186,6 +188,8 @@ sub has_module($) { => '(?s)^(?!.*--without-stream_upstream_hash_module)', stream_upstream_least_conn => '(?s)^(?!.*--without-stream_upstream_least_conn_m)', + stream_upstream_random + => '(?s)^(?!.*--without-stream_upstream_random_modul)', stream_upstream_zone => '(?s)^(?!.*--without-stream_upstream_zone_module)', ); diff --git a/stream_upstream_random.t b/stream_upstream_random.t new file mode 100644 --- /dev/null +++ b/stream_upstream_random.t @@ -0,0 +1,309 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for upstream random balancer module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_end /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/stream stream_upstream_zone stream_upstream_random/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 2; + +events { +} + +stream { + upstream u { + zone z 1m; + random; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + } + + upstream lc { + zone lc 1m; + random two; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + } + + upstream w { + zone w 1m; + random two least_conn; + server 127.0.0.1:8081; + server 127.0.0.1:8082 weight=2; + } + + upstream mc { + zone mc 1m; + random; + server 127.0.0.1:8081 max_conns=2; + server 127.0.0.1:8082 max_conns=1; + } + + upstream mc2 { + zone mc 1m; + random two; + server 127.0.0.1:8081 max_conns=2; + server 127.0.0.1:8082 max_conns=1; + } + + upstream one { + random; + server 127.0.0.1:8081; + } + + upstream two { + random two; + server 127.0.0.1:8081; + } + + upstream zone { + zone z 1m; + random; + server 127.0.0.1:8081; + } + + upstream ztwo { + zone z 1m; + random two; + server 127.0.0.1:8081; + } + + upstream fail { + zone fail 1m; + random; + server 127.0.0.1:8096; + server 127.0.0.1:8083 down; + server 127.0.0.1:8082; + } + + upstream fail2 { + zone fail2 1m; + random two; + server 127.0.0.1:8096; + server 127.0.0.1:8083 down; + server 127.0.0.1:8082; + } + + server { + listen 127.0.0.1:8080; + proxy_pass u; + } + + server { + listen 127.0.0.1:8083; + proxy_pass lc; + } + + server { + listen 127.0.0.1:8084; + proxy_pass w; + } + + server { + listen 127.0.0.1:8085; + proxy_pass mc; + } + + server { + listen 127.0.0.1:8086; + proxy_pass mc2; + } + + server { + listen 127.0.0.1:8087; + proxy_pass one; + } + + server { + listen 127.0.0.1:8088; + proxy_pass two; + } + + server { + listen 127.0.0.1:8089; + proxy_pass zone; + } + + server { + listen 127.0.0.1:8090; + proxy_pass ztwo; + } + + proxy_connect_timeout 1s; + + server { + listen 127.0.0.1:8091; + proxy_pass fail; + } + + server { + listen 127.0.0.1:8092; + proxy_pass fail2; + } +} + +EOF + +$t->run_daemon(\&http_daemon, port(8081)); +$t->run_daemon(\&http_daemon, port(8082)); +$t->try_run('no upstream random')->plan(12); + +$t->waitforsocket('127.0.0.1:' . port(8081)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +my @ports = my ($port1, $port2) = (port(8081), port(8082)); + +like(get(8080, '/'), qr/X-Port: ($port1|$port2)/, 'random'); +like(get(8083, '/'), qr/X-Port: ($port1|$port2)/, 'random two'); + +my $s = get(8083, '/w', start => 1, sleep => 0.2); +my $r = get(8083, '/'); +my ($p) = http_end($s) =~ /X-Port: (\d+)/; +like($r, qr/X-Port: (?!$p)/, 'random wait'); + +SKIP: { +skip 'long test', 3 unless $ENV{TEST_NGINX_UNSAFE}; + +is(parallel(8084, '/w', 3), "$port1: 1, $port2: 2", 'random weight'); + +is(parallel(8085, '/w', 4), "$port1: 2, $port2: 1", 'max_conns'); +is(parallel(8086, '/w', 4), "$port1: 2, $port2: 1", 'max_conns two'); + +} + +# single variants + +like(get(8087, '/'), qr/X-Port: $port1/, 'single one'); +like(get(8088, '/'), qr/X-Port: $port1/, 'single two'); +like(get(8089, '/'), qr/X-Port: $port1/, 'zone one'); +like(get(8090, '/'), qr/X-Port: $port1/, 'zone two'); + +like(many(8091, '/', 10), qr/$port2: 10/, 'failures'); +like(many(8092, '/', 10), qr/$port2: 10/, 'failures two'); + +############################################################################### + +sub get { + my ($port, $uri, %opts) = @_; + my $s = IO::Socket::INET->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1', + PeerPort => port($port), + ) + or die "Can't connect to nginx: $!\n"; + + http_get($uri, socket => $s, %opts); +} + +sub many { + my ($port, $uri, $count, %opts) = @_; + my %ports; + + for (1 .. $count) { + if (get($port, $uri) =~ /X-Port: (\d+)/) { + $ports{$1} = 0 unless defined $ports{$1}; + $ports{$1}++; + } + + select undef, undef, undef, $opts{delay} if $opts{delay}; + } + + my @keys = map { my $p = $_; grep { $p == $_ } keys %ports } @ports; + return join ', ', map { $_ . ": " . $ports{$_} } @keys; +} + +sub parallel { + my ($port, $uri, $n) = @_; + my %ports; + + my @s = map { get($port, $uri, start => 1, sleep => 0.1) } (1 .. $n); + + for (@s) { + if (http_end($_) =~ /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; +} + +############################################################################### + +sub http_daemon { + my ($port) = @_; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/w') { + Test::Nginx::log_core('||', "$port: sleep(2.5)"); + select undef, undef, undef, 2.5; + } + + if ($uri eq '/close' && $port == port(8081)) { + next; + } + + Test::Nginx::log_core('||', "$port: response, 200"); + print $client <new()->has(qw/http proxy upstream_zone upstream_random/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_processes 2; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + upstream u { + zone z 1m; + random; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + server 127.0.0.1:8083 down; + } + + upstream lc { + zone lc 1m; + random two; + server 127.0.0.1:8081; + server 127.0.0.1:8082; + server 127.0.0.1:8083 down; + } + + upstream w { + zone w 1m; + random two least_conn; + server 127.0.0.1:8081; + server 127.0.0.1:8082 weight=2; + } + + upstream mc { + zone mc 1m; + random; + server 127.0.0.1:8081 max_conns=2; + server 127.0.0.1:8082 max_conns=1; + } + + upstream mc2 { + zone mc 1m; + random two; + server 127.0.0.1:8081 max_conns=2; + server 127.0.0.1:8082 max_conns=1; + } + + upstream one { + random; + server 127.0.0.1:8081; + } + + upstream two { + random two; + server 127.0.0.1:8081; + } + + upstream zone { + zone z 1m; + random; + server 127.0.0.1:8081; + } + + upstream ztwo { + zone z 1m; + random two; + server 127.0.0.1:8081; + } + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://u; + } + + location /lc/ { + proxy_pass http://lc/; + } + + location /w { + proxy_pass http://w; + } + + location /mc/ { + proxy_pass http://mc/; + } + + location /mc2/ { + proxy_pass http://mc2/; + } + + location /one { + proxy_pass http://one; + } + + location /two { + proxy_pass http://two; + } + + location /zone { + proxy_pass http://zone; + } + + location /ztwo { + proxy_pass http://ztwo; + } + } +} + +EOF + +$t->run_daemon(\&http_daemon, port(8081)); +$t->run_daemon(\&http_daemon, port(8082)); +$t->try_run('no upstream random')->plan(12); + +$t->waitforsocket('127.0.0.1:' . port(8081)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +my @ports = my ($port1, $port2) = (port(8081), port(8082)); + +like(http_get('/'), qr/X-Port: ($port1|$port2)/, 'random'); +like(http_get('/lc/'), qr/X-Port: ($port1|$port2)/, 'random two'); + +my $s = http_get('/lc/w', start => 1, sleep => 0.2); +my $r = http_get('/lc/'); +my ($p) = http_end($s) =~ /X-Port: (\d+)/; +like($r, qr/X-Port: (?!$p)/, 'random wait'); + +SKIP: { +skip 'long test', 3 unless $ENV{TEST_NGINX_UNSAFE}; + +is(parallel('/w', 3), "$port1: 1, $port2: 2", 'random weight'); + +is(parallel('/mc/w', 4), "$port1: 2, $port2: 1", 'max_conns'); +is(parallel('/mc2/w', 4), "$port1: 2, $port2: 1", 'max_conns two'); + +} + +# single variants + +like(http_get('/one'), qr/X-Port: $port1/, 'single one'); +like(http_get('/two'), qr/X-Port: $port1/, 'single two'); +like(http_get('/zone'), qr/X-Port: $port1/, 'zone one'); +like(http_get('/ztwo'), qr/X-Port: $port1/, 'zone two'); + +like(many('/close', 10), qr/$port2: 10/, 'failures'); +like(many('/lc/close', 10), qr/$port2: 10/, 'failures two'); + +############################################################################### + +sub many { + my ($uri, $count, %opts) = @_; + my %ports; + + for (1 .. $count) { + if (http_get($uri) =~ /X-Port: (\d+)/) { + $ports{$1} = 0 unless defined $ports{$1}; + $ports{$1}++; + } + + select undef, undef, undef, $opts{delay} if $opts{delay}; + } + + my @keys = map { my $p = $_; grep { $p == $_ } keys %ports } @ports; + return join ', ', map { $_ . ": " . $ports{$_} } @keys; +} + +sub parallel { + my ($uri, $n) = @_; + my %ports; + + my @s = map { http_get($uri, start => 1, sleep => 0.1) } (1 .. $n); + + for (@s) { + if (http_end($_) =~ /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; +} + +############################################################################### + +sub http_daemon { + my ($port) = @_; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/w') { + Test::Nginx::log_core('||', "$port: sleep(2.5)"); + select undef, undef, undef, 2.5; + } + + if ($uri eq '/close' && $port == port(8081)) { + next; + } + + Test::Nginx::log_core('||', "$port: response, 200"); + print $client <