Skip to content

Commit

Permalink
proxy: match, token = req:match_res(res)
Browse files Browse the repository at this point in the history
If req has 'k' and/or 'O' flags, match against the result object.
  • Loading branch information
dormando committed Jun 11, 2024
1 parent 90f1d91 commit 433fa01
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 1 deletion.
1 change: 1 addition & 0 deletions proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@ int mcplib_request_flag_set(lua_State *L);
int mcplib_request_flag_replace(lua_State *L);
int mcplib_request_flag_del(lua_State *L);
int mcplib_request_gc(lua_State *L);
int mcplib_request_match_res(lua_State *L);
void mcp_request_cleanup(LIBEVENT_THREAD *t, mcp_request_t *rq);

// response interface
Expand Down
1 change: 1 addition & 0 deletions proxy_lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,7 @@ int proxy_register_libs(void *ctx, LIBEVENT_THREAD *t, void *state) {
{"flag_set", mcplib_request_flag_set},
{"flag_replace", mcplib_request_flag_replace},
{"flag_del", mcplib_request_flag_del},
{"match_res", mcplib_request_match_res},
{"__tostring", NULL},
{"__gc", mcplib_request_gc},
{NULL, NULL}
Expand Down
78 changes: 78 additions & 0 deletions proxy_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,84 @@ int mcplib_request_flag_del(lua_State *L) {
return 1;
}

// local match, token = req:match_res(res)
// checks if req has `k` or `O`. If so, checks response for `K` or `O`
// returns true, nil if matches
// returns false, res token if not match.
//
int mcplib_request_match_res(lua_State *L) {
mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");
mcp_resp_t *rs = luaL_checkudata(L, 2, "mcp.response");

const char *opaque_token = NULL;
size_t opaque_len = 0;

// requests all have keys. check for an opaque.
mcp_request_find_flag_token(rq, 'O', &opaque_token, &opaque_len);

// scan the response line for tokens, since we don't have a reciprocal API
// yet. When we do this code will be replaced with a function call like
// the above.
const char *p = rs->resp.rline;
// TODO: Think this is an off-by-one in mcmc.
const char *e = p + rs->resp.rlen - 1;
if (!p) {
// happens if the result line is blank (ie; 'HD\r\n')
lua_pushboolean(L, 0);
lua_pushnil(L);
return 2;
}

int matched = 0;
while (p != e) {
if (*p == ' ') {
p++;
} else if (*p == 'k' || *p == 'O') {
const char *rq_token = NULL;
int rq_len = 0;
if (*p == 'k') {
rq_token = MCP_PARSER_KEY(rq->pr);
rq_len = rq->pr.klen;
} else if (*p == 'O') {
rq_token = opaque_token;
rq_len = opaque_len;
}
if (rq_token == NULL) {
lua_pushboolean(L, 0);
lua_pushnil(L);
return 2;
}

p++; // skip flag and start comparing token
const char *rs_token = p;

// find end of token
while (p != e && !isspace(*p)) {
p++;
}

int rs_len = p - rs_token;
if (rq_len != rs_len || memcmp(rq_token, rs_token, rs_len) != 0) {
// FAIL, keys aren't the same length or don't match.
lua_pushboolean(L, 0);
lua_pushlstring(L, rs_token, rs_len);
return 2;
} else {
matched = 1;
}
} else {
// skip token
while (p != e && *p != ' ') {
p++;
}
}
}

lua_pushboolean(L, matched);
lua_pushnil(L);
return 2;
}

void mcp_request_cleanup(LIBEVENT_THREAD *t, mcp_request_t *rq) {
// During nread c->item is the malloc'ed buffer. not yet put into
// rq->buf - this gets freed because we've also set c->item_malloced if
Expand Down
5 changes: 4 additions & 1 deletion t/lib/MemcachedTest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,11 @@ sub be_recv_c {

my $l = $self->_be_list($list);
my $cmd = $self->{_cmd};
my @cmds = split(/(?<=\r\n)/, $cmd);
for my $be (@$l) {
Test::More::is(scalar <$be>, $cmd, $detail);
for my $c (@cmds) {
Test::More::is(scalar <$be>, $c, $detail);
}
}
}

Expand Down
32 changes: 32 additions & 0 deletions t/proxymatch.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
function mcp_config_pools()
mcp.backend_connect_timeout(60)
mcp.backend_read_timeout(60)
mcp.backend_retry_timeout(60)
local b1 = mcp.backend('b1', '127.0.0.1', 12171)
return mcp.pool({b1})
end

function mcp_config_routes(p)
local fg = mcp.funcgen_new()
local h = fg:new_handle(p)
fg:ready({
n = "match", f = function(rctx)
return function(r)
local res = rctx:enqueue_and_wait(r, h)
local match, token = r:match_res(res)
if match then
mcp.log("match succeeded")
else
if token then
mcp.log("match failed: " .. token)
else
mcp.log("match failed: no token")
end
end
return res
end
end
})
mcp.attach(mcp.CMD_MG, fg)
mcp.attach(mcp.CMD_MS, fg)
end
250 changes: 250 additions & 0 deletions t/proxymatch.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#!/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);
use IO::Select;
use Data::Dumper qw/Dumper/;

if (!supports_proxy()) {
plan skip_all => 'proxy not enabled';
exit 0;
}

my $t = Memcached::ProxyTest->new(servers => [12171]);

my $p_srv = new_memcached('-o proxy_config=./t/proxymatch.lua -t 1');
my $ps = $p_srv->sock;
$ps->autoflush(1);

$t->set_c($ps);
$t->accept_backends();

my $w = $p_srv->new_sock;
print $w "watch proxyuser proxyevents\n";
is(<$w>, "OK\r\n", "watcher enabled");

subtest 'ms no tokens' => sub {
my $cmd = "ms foo 2\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

# successful tests.
subtest 'ms with k' => sub {
my $cmd = "ms foo 2 k\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kfoo\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

subtest 'ms with O' => sub {
my $cmd = "ms foo 2 k O1234\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kfoo O1234\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

subtest 'ms with k and O' => sub {
my $cmd = "ms foo 2 k O4321\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kfoo O4321\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

subtest 'ms with O and k' => sub {
my $cmd = "ms foo 2 O9876 k\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O9876 kfoo\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

# ensure the parser works with unrelated tokens at the beginning/end of the string
subtest 'ms with O and k and c' => sub {
my $cmd = "ms foo 2 T99 O9876 k c\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O9876 kfoo c2\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

# test some failures.
subtest 'ms with empty k' => sub {
my $cmd = "ms foo 2 k\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD k\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with empty O' => sub {
my $cmd = "ms foo 2 O\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with empty O c' => sub {
my $cmd = "ms foo 2 O c\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O c2\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with wrong k' => sub {
my $cmd = "ms foo 2 k\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kbar\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with wrong len k' => sub {
my $cmd = "ms foo 2 k\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kfoob\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with wrong O' => sub {
my $cmd = "ms foo 2 O1234\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O4321\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with wrong len O' => sub {
my $cmd = "ms foo 2 O1234\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD O43210\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with right k wrong O' => sub {
my $cmd = "ms foo 2 k O1234\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kfoo O5678\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'ms with right O wrong k' => sub {
my $cmd = "ms foo 2 k O1234\r\nok\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD kbar O1234\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

# don't need to test mg as hard since the match code is the same. just ensure
# the rline handling is correct for both code paths.

# mg successful tests
subtest 'mg with k' => sub {
my $cmd = "mg foo t k\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t99 kfoo\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

subtest 'mg with O' => sub {
my $cmd = "mg foo t O1234\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t99 O1234\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

subtest 'mg with O and k' => sub {
my $cmd = "mg foo t O1234 k c\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t98 O1234 kfoo c2\r\n");
$t->c_recv_be();
like(<$w>, qr/match succeeded/);
$t->clear();
};

# mg failures
subtest 'mg with wrong k' => sub {
my $cmd = "mg foo t k\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t97 kbar\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'mg with wrong O' => sub {
my $cmd = "mg foo t O1234 c\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t97 O4321 c2\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

subtest 'mg no matchable tokens' => sub {
my $cmd = "mg foo t c\r\n";
$t->c_send($cmd);
$t->be_recv_c(0);
$t->be_send(0, "HD t97 c2\r\n");
$t->c_recv_be();
like(<$w>, qr/match failed: /);
$t->clear();
};

done_testing();

0 comments on commit 433fa01

Please sign in to comment.