summaryrefslogtreecommitdiff
path: root/t/proxyunits.t
diff options
context:
space:
mode:
Diffstat (limited to 't/proxyunits.t')
-rw-r--r--t/proxyunits.t516
1 files changed, 516 insertions, 0 deletions
diff --git a/t/proxyunits.t b/t/proxyunits.t
new file mode 100644
index 0000000..0914b98
--- /dev/null
+++ b/t/proxyunits.t
@@ -0,0 +1,516 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Test::More;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use Carp qw(croak);
+use MemcachedTest;
+use IO::Socket qw(AF_INET SOCK_STREAM);
+
+if (!supports_proxy()) {
+ plan skip_all => 'proxy not enabled';
+ exit 0;
+}
+
+# Set up some server sockets.
+sub mock_server {
+ my $port = shift;
+ my $srv = IO::Socket->new(
+ Domain => AF_INET,
+ Type => SOCK_STREAM,
+ Proto => 'tcp',
+ LocalHost => '127.0.0.1',
+ LocalPort => $port,
+ ReusePort => 1,
+ Listen => 5) || die "IO::Socket: $@";
+ return $srv;
+}
+
+# Put a version command down the pipe to ensure the socket is clear.
+# client version commands skip the proxy code
+sub check_version {
+ my $ps = shift;
+ print $ps "version\r\n";
+ like(<$ps>, qr/VERSION /, "version received");
+}
+
+my @mocksrvs = ();
+diag "making mock servers";
+for my $port (11411, 11412, 11413) {
+ my $srv = mock_server($port);
+ ok(defined $srv, "mock server created");
+ push(@mocksrvs, $srv);
+}
+
+my $p_srv = new_memcached('-o proxy_config=./t/proxyunits.lua -l 127.0.0.1', 11410);
+my $ps = $p_srv->sock;
+$ps->autoflush(1);
+
+# set up server backend sockets.
+my @mbe = ();
+diag "accepting mock backends";
+for my $msrv (@mocksrvs) {
+ my $be = $msrv->accept();
+ $be->autoflush(1);
+ ok(defined $be, "mock backend created");
+ push(@mbe, $be);
+}
+
+diag "validating backends";
+for my $be (@mbe) {
+ like(<$be>, qr/version/, "received version command");
+ print $be "VERSION 1.0.0-mock\r\n";
+}
+
+diag "ready for main tests";
+# Target a single backend, validating basic syntax.
+# Should test all command types.
+# uses /b/ path for "basic"
+{
+ # Test invalid route.
+ print $ps "set /invalid/a 0 0 2\r\nhi\r\n";
+ is(scalar <$ps>, "SERVER_ERROR no set route\r\n");
+
+ # Testing against just one backend. Results should make sense despite our
+ # invalid request above.
+ my $be = $mbe[0];
+ my $cmd;
+
+ # TODO: add more tests for the varying response codes.
+
+ # Basic set.
+ $cmd = "set /b/a 0 0 2";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "set passthrough");
+ is(scalar <$be>, "hi\r\n", "set value");
+ print $be "STORED\r\n";
+
+ is(scalar <$ps>, "STORED\r\n", "got STORED from set");
+
+ # Basic get
+ $cmd = "get /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "get passthrough");
+ print $be "VALUE /b/a 0 2\r\nhi\r\nEND\r\n";
+
+ is(scalar <$ps>, "VALUE /b/a 0 2\r\n", "get rline");
+ is(scalar <$ps>, "hi\r\n", "get data");
+ is(scalar <$ps>, "END\r\n", "get end");
+
+ # touch
+ $cmd = "touch /b/a 50\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "touch passthrough");
+ print $be "TOUCHED\r\n";
+
+ is(scalar <$ps>, "TOUCHED\r\n", "got touch response");
+
+ # gets
+ $cmd = "gets /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "gets passthrough");
+ print $be "VALUE /b/a 0 2 2\r\nhi\r\nEND\r\n";
+
+ is(scalar <$ps>, "VALUE /b/a 0 2 2\r\n", "gets rline");
+ is(scalar <$ps>, "hi\r\n", "gets data");
+ is(scalar <$ps>, "END\r\n", "gets end");
+
+ # gat
+ $cmd = "gat 10 /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "gat passthrough");
+ print $be "VALUE /b/a 0 2\r\nhi\r\nEND\r\n";
+
+ is(scalar <$ps>, "VALUE /b/a 0 2\r\n", "gat rline");
+ is(scalar <$ps>, "hi\r\n", "gat data");
+ is(scalar <$ps>, "END\r\n", "gat end");
+
+ # gats
+ $cmd = "gats 11 /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "gats passthrough");
+ print $be "VALUE /b/a 0 2 1\r\nhi\r\nEND\r\n";
+
+ is(scalar <$ps>, "VALUE /b/a 0 2 1\r\n", "gats rline");
+ is(scalar <$ps>, "hi\r\n", "gats data");
+ is(scalar <$ps>, "END\r\n", "gats end");
+
+ # cas
+ $cmd = "cas /b/a 0 0 2 5";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "cas passthrough");
+ is(scalar <$be>, "hi\r\n", "cas value");
+ print $be "STORED\r\n";
+
+ is(scalar <$ps>, "STORED\r\n", "got STORED from cas");
+
+ # add
+ $cmd = "add /b/a 0 0 2";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "add passthrough");
+ is(scalar <$be>, "hi\r\n", "add value");
+ print $be "STORED\r\n";
+
+ is(scalar <$ps>, "STORED\r\n", "got STORED from add");
+
+ # delete
+ $cmd = "delete /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "delete passthrough");
+ print $be "DELETED\r\n";
+
+ is(scalar <$ps>, "DELETED\r\n", "got delete response");
+
+ # incr
+ $cmd = "incr /b/a 1\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "incr passthrough");
+ print $be "2\r\n";
+
+ is(scalar <$ps>, "2\r\n", "got incr response");
+
+ # decr
+ $cmd = "decr /b/a 1\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "decr passthrough");
+ print $be "10\r\n";
+
+ is(scalar <$ps>, "10\r\n", "got decr response");
+
+ # append
+ $cmd = "append /b/a 0 0 2";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "append passthrough");
+ is(scalar <$be>, "hi\r\n", "append value");
+ print $be "STORED\r\n";
+
+ is(scalar <$ps>, "STORED\r\n", "got STORED from append");
+
+ # prepend
+ $cmd = "prepend /b/a 0 0 2";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "prepend passthrough");
+ is(scalar <$be>, "hi\r\n", "prepend value");
+ print $be "STORED\r\n";
+
+ is(scalar <$ps>, "STORED\r\n", "got STORED from prepend");
+
+ # [meta commands]
+ # testing the bare meta commands.
+ # TODO: add more tests for tokens and changing response codes.
+ # mg
+ $cmd = "mg /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "mg passthrough");
+ print $be "HD\r\n";
+
+ is(scalar <$ps>, "HD\r\n", "got mg response");
+ # ms
+ $cmd = "ms /b/a 2";
+ print $ps "$cmd\r\nhi\r\n";
+ is(scalar <$be>, "$cmd\r\n", "ms passthrough");
+ is(scalar <$be>, "hi\r\n", "ms value");
+ print $be "HD\r\n";
+
+ is(scalar <$ps>, "HD\r\n", "got HD from ms");
+
+ # md
+ $cmd = "md /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "md passthrough");
+ print $be "HD\r\n";
+
+ is(scalar <$ps>, "HD\r\n", "got HD from md");
+ # ma
+ $cmd = "ma /b/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "ma passthrough");
+ print $be "HD\r\n";
+
+ is(scalar <$ps>, "HD\r\n", "got HD from ma");
+ # mn?
+ # me?
+}
+
+# run a cleanser check between each set of tests.
+check_version($ps);
+
+{
+ # multiget syntax
+ # - gets broken into individual gets on backend
+ my $be = $mbe[0];
+ my $cmd = "get /b/a /b/b /b/c\r\n";
+ print $ps $cmd;
+ # NOTE: the proxy ends up reversing the keys to the backend, but returns keys in the
+ # proper order. This is undesireable but not problematic: because of how
+ # ascii multiget syntax works the server cannot start responding until all
+ # answers are resolved anyway.
+ is(scalar <$be>, "get /b/c\r\n", "multiget breakdown c");
+ is(scalar <$be>, "get /b/b\r\n", "multiget breakdown b");
+ is(scalar <$be>, "get /b/a\r\n", "multiget breakdown a");
+
+ print $be "VALUE /b/c 0 1\r\nc\r\n",
+ "END\r\n",
+ "VALUE /b/b 0 1\r\nb\r\n",
+ "END\r\n",
+ "VALUE /b/a 0 1\r\na\r\n",
+ "END\r\n";
+
+ for my $key ('a', 'b', 'c') {
+ is(scalar <$ps>, "VALUE /b/$key 0 1\r\n", "multiget res $key");
+ is(scalar <$ps>, "$key\r\n", "multiget value $key");
+ }
+ is(scalar <$ps>, "END\r\n", "final END from multiget");
+}
+
+check_version($ps);
+
+{
+ # noreply tests.
+ # - backend should receive with noreply/q stripped or mangled
+ # - backend should reply as normal
+ # - frontend should get nothing; to test issue another command and ensure
+ # it only gets that response.
+ my $be = $mbe[0];
+ my $cmd = "set /b/a 0 0 2 noreply\r\nhi\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, "set /b/a 0 0 2 noreplY\r\n", "set received with broken noreply");
+ is(scalar <$be>, "hi\r\n", "set payload received");
+
+ print $be "STORED\r\n";
+
+ # To ensure success, make another req and ensure res isn't STORED
+ $cmd = "touch /b/a 50\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "canary touch received");
+ print $be "TOUCHED\r\n";
+
+ is(scalar <$ps>, "TOUCHED\r\n", "got TOUCHED instread of STORED");
+
+ # TODO: meta quiet cases
+ # - q should be turned into a space on the backend
+ # - errors should still pass through to client
+}
+
+check_version($ps);
+# TODO: Test specifically responding to a get but missing the END\r\n. it
+# should time out and not leak to the client.
+
+# Test Lua request API
+{
+ my $be = $mbe[0];
+
+ # fetching the key.
+ print $ps "get /getkey/testkey\r\n";
+ # look for the key to be slightly different to ensure we hit lua.
+ is(scalar <$ps>, "VALUE |/getkey/testkey 0 2\r\n", "request:key()");
+ is(scalar <$ps>, "ts\r\n", "request:key() value");
+ is(scalar <$ps>, "END\r\n", "request:key() END");
+
+ # rtrimkey
+ # this overwrites part of the key with spaces, which should be skipped by
+ # a valid protocol parser.
+ print $ps "get /rtrimkey/onehalf\r\n";
+ is(scalar <$be>, "get /rtrimkey/one \r\n", "request:rtrimkey()");
+ print $be "END\r\n";
+ is(scalar <$ps>, "END\r\n", "rtrimkey END");
+
+ # ltrimkey
+ print $ps "get /ltrimkey/test\r\n";
+ is(scalar <$be>, "get test\r\n", "request:ltrimkey()");
+ print $be "END\r\n";
+ is(scalar <$ps>, "END\r\n", "ltrimkey END");
+
+ # token(n) fetch
+ # token(n, "replacement")
+ # token(n, "") removal
+ # ntokens()
+ # command() integer
+ #
+ # meta:
+ # has_flag("F")
+ # test has_flag() against non-meta command
+ # flag_token("F") with no token (bool, nil|token)
+ # flag_token("F") with token
+ # flag_token("F", "FReplacement")
+ # flag_token("F", "") removal
+ # flag_token("F", "FReplacement") -> flag_token("F") test repeated fetch
+
+ # mcp.request() - has a few modes to test
+ # - allows passing in an existing request to clone/edit
+ # - passing in value blob
+}
+
+check_version($ps);
+# Test Lua response API
+#{
+ # elapsed()
+ # ok()
+ # hit()
+ # vlen()
+ # code()
+ # line()
+#}
+
+# Test requests land in proper backend in basic scenarios
+{
+ # TODO: maybe should send values to ensure the right response?
+ # I don't think this test is very useful though; probably better to try
+ # harder when testing error conditions.
+ for my $tu (['a', $mbe[0]], ['b', $mbe[1]], ['c', $mbe[2]]) {
+ my $be = $tu->[1];
+ my $cmd = "get /zonetest/" . $tu->[0] . "\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "routed proper zone: " . $tu->[0]);
+ print $be "END\r\n";
+ is(scalar <$ps>, "END\r\n", "end from zone fetch");
+ }
+ my $cmd = "get /zonetest/invalid\r\n";
+ print $ps $cmd;
+ is(scalar <$ps>, "END\r\n", "END from invalid route");
+}
+
+check_version($ps);
+# Test re-requests in lua.
+# - fetch zones.z1() then fetch zones.z2()
+# - return z1 or z2 or netiher
+# - fetch all three zones
+# - hit the same zone multiple times
+
+# Test out of spec commands from client
+# - wrong # of tokens
+# - bad key size
+# - etc
+
+# Test errors/garbage from server
+# - certain errors pass through to the client, most close the backend.
+
+# Test delayed read (timeout)
+
+# Test Lua logging (see t/watcher.t)
+{
+ my $be = $mbe[0];
+ my $watcher = $p_srv->new_sock;
+ print $watcher "watch proxyuser proxyreqs\n";
+ is(<$watcher>, "OK\r\n", "watcher enabled");
+
+ # log(msg)
+ print $ps "get /logtest/a\r\n";
+ like(<$watcher>, qr/ts=(\S+) gid=\d+ type=proxy_user msg=testing manual log messages/,
+ "log a manual message");
+ is(scalar <$ps>, "END\r\n", "logtest END");
+
+ # log_req(r, res)
+ my $cmd = "get /logreqtest/a\r\n";
+ print $ps $cmd;
+ is(scalar <$be>, $cmd, "got passthru for log");
+ print $be "END\r\n";
+ is(scalar <$ps>, "END\r\n", "got END from log test");
+ like(<$watcher>, qr/ts=(\S+) gid=\d+ type=proxy_req elapsed=\d+ type=105 code=17 status=0 be=127.0.0.1:11411 detail=logreqtest req=get \/logreqtest\/a/, "found request log entry");
+
+ # test log_req with nil res (should be 0's in places)
+ # log_reqsample()
+}
+
+# Basic proxy stats validation
+
+# Test user stats
+
+check_version($ps);
+# Test await arguments (may move to own file?)
+# TODO: the results table from mcp.await() contains all of the results so far,
+# regardless of the mode.
+# need some tests that show this.
+{
+ my $cmd;
+ # await(r, p)
+ # this should hit all three backends
+ my $key = "/awaitbasic/a";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitbasic backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+ is(scalar <$ps>, "VALUE $key 0 11\r\n", "response from await");
+ is(scalar <$ps>, "hit hit hit\r\n", "hit responses from await");
+ is(scalar <$ps>, "END\r\n", "end from await");
+ # repeat above test but with different combo of results
+
+ # await(r, p, 1)
+ $key = "/awaitone/a";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitone backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+ is(scalar <$ps>, "VALUE $key 0 1\r\n", "response from await");
+ is(scalar <$ps>, "1\r\n", "looking for a single response");
+ is(scalar <$ps>, "END\r\n", "end from await");
+
+ # await(r, p(3+), 2)
+ $key = "/awaitone/b";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitone backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+ is(scalar <$ps>, "VALUE $key 0 1\r\n", "response from await");
+ is(scalar <$ps>, "2\r\n", "looking two responses");
+ is(scalar <$ps>, "END\r\n", "end from await");
+
+ # await(r, p, 1, mcp.AWAIT_GOOD)
+ $key = "/awaitgood/a";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitgood backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+ is(scalar <$ps>, "VALUE $key 0 1\r\n", "response from await");
+ is(scalar <$ps>, "1\r\n", "looking for a single response");
+ is(scalar <$ps>, "END\r\n", "end from await");
+ # should test above with first response being err, second good, third
+ # miss, and a few similar iterations.
+
+ # await(r, p, 2, mcp.AWAIT_ANY)
+ $key = "/awaitany/a";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitany backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+ is(scalar <$ps>, "VALUE $key 0 1\r\n", "response from await");
+ is(scalar <$ps>, "2\r\n", "looking for a two responses");
+ is(scalar <$ps>, "END\r\n", "end from await");
+
+ # await(r, p, 2, mcp.AWAIT_OK)
+ # await(r, p, 1, mcp.AWAIT_FIRST)
+ # more AWAIT_FIRST tests? to see how much it waits on/etc.
+ # await(r, p, 2, mcp.AWAIT_FASTGOOD)
+ # - should return 1 res on good, else wait for N non-error responses
+ # - test three pools, but third returns good. should have returned already
+ # await(r, p, 1, mcp.AWAIT_BACKGROUND) - ensure res without waiting
+ $key = "/awaitbg/a";
+ $cmd = "get $key\r\n";
+ print $ps $cmd;
+ # check we can get a response _before_ the backends are consulted.
+ is(scalar <$ps>, "VALUE $key 0 1\r\n", "response from await");
+ is(scalar <$ps>, "0\r\n", "looking for zero responses");
+ is(scalar <$ps>, "END\r\n", "end from await");
+ for my $be (@mbe) {
+ is(scalar <$be>, $cmd, "awaitbg backend req");
+ print $be "VALUE $key 0 2\r\nok\r\nEND\r\n";
+ }
+
+ # test hitting a pool normally then hit mcp.await()
+ # test hitting mcp.await() then a pool normally
+}
+
+check_version($ps);
+done_testing();