1#!/usr/bin/env perl
2
3use strict;
4use warnings;
5use Test::More;
6use FindBin qw($Bin);
7use lib "$Bin/lib";
8use Carp qw(croak);
9use MemcachedTest;
10use IO::Socket qw(AF_INET SOCK_STREAM);
11use IO::Select;
12
13if (!supports_proxy()) {
14    plan skip_all => 'proxy not enabled';
15    exit 0;
16}
17
18# Don't want to write two distinct set of tests, and extstore is a default.
19if (!supports_extstore()) {
20    plan skip_all => 'extstore not enabled';
21    exit 0;
22}
23
24my $ext_path = "/tmp/proxyinternal.$$";
25
26# Set up some server sockets.
27sub mock_server {
28    my $port = shift;
29    my $srv = IO::Socket->new(
30        Domain => AF_INET,
31        Type => SOCK_STREAM,
32        Proto => 'tcp',
33        LocalHost => '127.0.0.1',
34        LocalPort => $port,
35        ReusePort => 1,
36        Listen => 5) || die "IO::Socket: $@";
37    return $srv;
38}
39
40# Put a version command down the pipe to ensure the socket is clear.
41# client version commands skip the proxy code
42sub check_version {
43    my $ps = shift;
44    print $ps "version\r\n";
45    like(<$ps>, qr/VERSION /, "version received");
46}
47
48my $p_srv = new_memcached("-o proxy_config=./t/proxyinternal.lua,ext_item_size=500,ext_item_age=1,ext_path=$ext_path:64m,ext_max_sleep=100000 -t 1");
49my $ps = $p_srv->sock;
50$ps->autoflush(1);
51
52{
53    print $ps "ms /b/a 2\r\nhi\r\n";
54    is(scalar <$ps>, "HD\r\n", "bare ms command works");
55
56    print $ps "ms /b/a 2 T100\r\nhi\r\n";
57    is(scalar <$ps>, "HD\r\n", "set ms with a TTL");
58    print $ps "mg /b/a t\r\n";
59    isnt(scalar <$ps>, "HD t-1\r\n");
60}
61
62note "ascii multiget";
63{
64    # First test all miss.
65    my @keys = ();
66    for (0 .. 50) {
67        push(@keys, "/b/" . $_);
68    }
69    print $ps "get ", join(' ', @keys), "\r\n";
70    is(scalar <$ps>, "END\r\n", "all misses from multiget");
71    # No extra END's after the solitary one.
72    check_version($ps);
73
74    for (@keys) {
75        print $ps "set $_ 0 0 2\r\nhi\r\n";
76        is(scalar <$ps>, "STORED\r\n", "successful set");
77    }
78    check_version($ps);
79    print $ps "get ", join(' ', @keys), "\r\n";
80    for (@keys) {
81        is(scalar <$ps>, "VALUE $_ 0 2\r\n", "resline matches");
82        is(scalar <$ps>, "hi\r\n", "value matches");
83    }
84    is(scalar <$ps>, "END\r\n", "final END from multiget");
85    check_version($ps);
86}
87
88note "ascii basic";
89{
90    # Ensure all of that END removal we do in multiget doesn't apply to
91    # non-multiget get mode.
92    print $ps "get /b/miss\r\n";
93    is(scalar <$ps>, "END\r\n", "basic miss");
94    check_version($ps);
95}
96
97#diag "object too large"
98{
99    my $data = 'x' x 2000000;
100    print $ps "set /b/toolarge 0 0 2000000\r\n$data\r\n";
101    is(scalar <$ps>, "SERVER_ERROR object too large for cache\r\n", "set too large");
102
103    print $ps "ms /b/toolarge 2000000 T30\r\n$data\r\n";
104    is(scalar <$ps>, "SERVER_ERROR object too large for cache\r\n", "ms too large");
105}
106
107#diag "basic tests"
108{
109    print $ps "set /b/foo 0 0 2\r\nhi\r\n";
110    is(scalar <$ps>, "STORED\r\n", "int set");
111    print $ps "get /b/foo\r\n";
112    is(scalar <$ps>, "VALUE /b/foo 0 2\r\n", "get response");
113    is(scalar <$ps>, "hi\r\n", "get value");
114    is(scalar <$ps>, "END\r\n", "get END");
115    check_version($ps);
116}
117
118subtest 'basic large item' => sub {
119    my $data = 'x' x 500000;
120    print $ps "set /b/beeeg 0 0 500000\r\n$data\r\n";
121    is(scalar <$ps>, "STORED\r\n", "big item stored");
122
123    print $ps "get /b/beeeg\r\n";
124    is(scalar <$ps>, "VALUE /b/beeeg 0 500000\r\n", "got large response");
125    is(scalar <$ps>, "$data\r\n", "got data portion back");
126    is(scalar <$ps>, "END\r\n", "saw END");
127
128    print $ps "delete /b/beeeg\r\n";
129    is(scalar <$ps>, "DELETED\r\n");
130    check_version($ps);
131};
132
133subtest 'basic chunked item' => sub {
134    my $data = 'x' x 900000;
135    print $ps "set /b/chunked 0 0 900000\r\n$data\r\n";
136    is(scalar <$ps>, "STORED\r\n", "big item stored");
137
138    print $ps "get /b/chunked\r\n";
139    is(scalar <$ps>, "VALUE /b/chunked 0 900000\r\n", "got large response");
140    is(scalar <$ps>, "$data\r\n", "got data portion back");
141    is(scalar <$ps>, "END\r\n", "saw END");
142
143    print $ps "delete /b/chunked\r\n";
144    is(scalar <$ps>, "DELETED\r\n");
145    check_version($ps);
146};
147
148subtest 'fetch from extstore' => sub {
149    my $data = 'x' x 1000;
150    print $ps "set /b/ext 0 0 1000\r\n$data\r\n";
151    is(scalar <$ps>, "STORED\r\n", "int set for extstore");
152    wait_ext_flush($ps);
153
154    print $ps "get /b/ext\r\n";
155    is(scalar <$ps>, "VALUE /b/ext 0 1000\r\n", "get response from extstore");
156    is(scalar <$ps>, "$data\r\n", "got data from extstore");
157    is(scalar <$ps>, "END\r\n", "get END");
158};
159
160#diag "flood memory"
161{
162    # ensure we don't have a basic reference counter leak
163    my $data = 'x' x 500000;
164    for (1 .. 200) {
165        print $ps "set /b/$_ 0 0 500000\r\n$data\r\n";
166        is(scalar <$ps>, "STORED\r\n", "flood set");
167    }
168    for (1 .. 200) {
169        print $ps "ms /b/$_ 500000 T30\r\n$data\r\n";
170        is(scalar <$ps>, "HD\r\n", "flood ms");
171    }
172}
173
174subtest 'watch deletions' => sub {
175    my $watcher = $p_srv->new_sock;
176    print $watcher "watch deletions\n";
177    is(<$watcher>, "OK\r\n", "deletions watcher enabled");
178
179    # verify watcher for delete
180    print $ps "set vfoo 0 0 4\r\nvbar\r\n";
181    is(<$ps>, "STORED\r\n", "stored the key");
182
183    print $ps "delete vfoo\r\n";
184    is(<$ps>, "DELETED\r\n", "key was deleted");
185
186    like(<$watcher>, qr/ts=\d+\.\d+\ gid=\d+ type=deleted key=vfoo cmd=delete .+ size=4/,
187        "delete command logged with correct size");
188
189    # verify watcher for md
190    print $ps "set vfoo 0 0 4\r\nvbar\r\n";
191    is(<$ps>, "STORED\r\n", "stored the key");
192
193    print $ps "md vfoo\r\n";
194    is(<$ps>, "HD\r\n", "key was deleted");
195
196    like(<$watcher>, qr/ts=\d+\.\d+\ gid=\d+ type=deleted key=vfoo cmd=md .+ size=4/,
197        "meta-delete command logged with correct size");
198};
199
200done_testing();
201
202END {
203    unlink $ext_path if $ext_path;
204}
205