1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "testutil.h"
18#include "apr.h"
19#include "apu.h"
20#include "apr_general.h"
21#include "apr_strings.h"
22#include "apr_hash.h"
23#include "apr_memcache.h"
24#include "apr_network_io.h"
25
26#if APR_HAVE_STDLIB_H
27#include <stdlib.h>		/* for exit() */
28#endif
29
30#define HOST "localhost"
31#define PORT 11211
32
33/* the total number of items to use for set/get testing */
34#define TDATA_SIZE 3000
35
36/* some smaller subset of TDATA_SIZE used for multiget testing */
37#define TDATA_SET 100
38
39/* our custom hash function just returns this all the time */
40#define HASH_FUNC_RESULT 510
41
42/* all keys will be prefixed with this */
43const char prefix[] = "testmemcache";
44
45/* text for values we store */
46const char txt[] =
47"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis at"
48"lacus in ligula hendrerit consectetuer. Vestibulum tristique odio"
49"iaculis leo. In massa arcu, ultricies a, laoreet nec, hendrerit non,"
50"neque. Nulla sagittis sapien ac risus. Morbi ligula dolor, vestibulum"
51"nec, viverra id, placerat dapibus, arcu. Curabitur egestas feugiat"
52"tellus. Donec dignissim. Nunc ante. Curabitur id lorem. In mollis"
53"tortor sit amet eros auctor dapibus. Proin nulla sem, tristique in,"
54"convallis id, iaculis feugiat cras amet.";
55
56/*
57 * this datatype is for our custom server determination function. this might
58 * be useful if you don't want to rely on simply hashing keys to determine
59 * where a key belongs, but instead want to write something fancy, or use some
60 * other kind of configuration data, i.e. a hash plus some data about a
61 * namespace, or whatever. see my_server_func, and test_memcache_user_funcs
62 * for the examples.
63 */
64typedef struct {
65  const char *someval;
66  apr_uint32_t which_server;
67} my_hash_server_baton;
68
69
70/* this could do something fancy and return some hash result.
71 * for simplicity, just return the same value, so we can test it later on.
72 * if you wanted to use some external hashing library or functions for
73 * consistent hashing, for example, this would be a good place to do it.
74 */
75static apr_uint32_t my_hash_func(void *baton, const char *data,
76                                 apr_size_t data_len)
77{
78
79  return HASH_FUNC_RESULT;
80}
81
82/*
83 * a fancy function to determine which server to use given some kind of data
84 * and a hash value. this example actually ignores the hash value itself
85 * and pulls some number from the *baton, which is a struct that has some
86 * kind of meaningful stuff in it.
87 */
88static apr_memcache_server_t *my_server_func(void *baton,
89                                             apr_memcache_t *mc,
90                                             const apr_uint32_t hash)
91{
92  apr_memcache_server_t *ms = NULL;
93  my_hash_server_baton *mhsb = (my_hash_server_baton *)baton;
94
95  if(mc->ntotal == 0) {
96    return NULL;
97  }
98
99  if(mc->ntotal < mhsb->which_server) {
100    return NULL;
101  }
102
103  ms = mc->live_servers[mhsb->which_server - 1];
104
105  return ms;
106}
107
108apr_uint16_t firsttime = 0;
109static int randval(apr_uint32_t high)
110{
111    apr_uint32_t i = 0;
112    double d = 0;
113
114    if (firsttime == 0) {
115	srand((unsigned) (getpid()));
116	firsttime = 1;
117    }
118
119    d = (double) rand() / ((double) RAND_MAX + 1);
120    i = (int) (d * (high - 0 + 1));
121
122    return i > 0 ? i : 1;
123}
124
125/*
126 * general test to make sure we can create the memcache struct and add
127 * some servers, but not more than we tell it we can add
128 */
129
130static void test_memcache_create(abts_case * tc, void *data)
131{
132  apr_pool_t *pool = p;
133  apr_status_t rv;
134  apr_memcache_t *memcache;
135  apr_memcache_server_t *server, *s;
136  apr_uint32_t max_servers = 10;
137  apr_uint32_t i;
138  apr_uint32_t hash;
139
140  rv = apr_memcache_create(pool, max_servers, 0, &memcache);
141  ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
142
143  for (i = 1; i <= max_servers; i++) {
144    apr_port_t port;
145
146    port = PORT + i;
147    rv =
148      apr_memcache_server_create(pool, HOST, PORT + i, 0, 1, 1, 60, &server);
149    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
150
151    rv = apr_memcache_add_server(memcache, server);
152    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
153
154    s = apr_memcache_find_server(memcache, HOST, port);
155    ABTS_PTR_EQUAL(tc, server, s);
156
157    rv = apr_memcache_disable_server(memcache, s);
158    ABTS_ASSERT(tc, "server disable failed", rv == APR_SUCCESS);
159
160    rv = apr_memcache_enable_server(memcache, s);
161    ABTS_ASSERT(tc, "server enable failed", rv == APR_SUCCESS);
162
163    hash = apr_memcache_hash(memcache, prefix, strlen(prefix));
164    ABTS_ASSERT(tc, "hash failed", hash > 0);
165
166    s = apr_memcache_find_server_hash(memcache, hash);
167    ABTS_PTR_NOTNULL(tc, s);
168  }
169
170  rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
171  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
172
173  rv = apr_memcache_add_server(memcache, server);
174  ABTS_ASSERT(tc, "server add should have failed", rv != APR_SUCCESS);
175
176}
177
178/* install our own custom hashing and server selection routines. */
179
180static int create_test_hash(apr_pool_t *p, apr_hash_t *h)
181{
182  int i;
183
184  for (i = 0; i < TDATA_SIZE; i++) {
185    char *k, *v;
186
187    k = apr_pstrcat(p, prefix, apr_itoa(p, i), NULL);
188    v = apr_pstrndup(p, txt, randval((apr_uint32_t)strlen(txt)));
189
190    apr_hash_set(h, k, APR_HASH_KEY_STRING, v);
191  }
192
193  return i;
194}
195
196static void test_memcache_user_funcs(abts_case * tc, void *data)
197{
198  apr_pool_t *pool = p;
199  apr_status_t rv;
200  apr_memcache_t *memcache;
201  apr_memcache_server_t *found;
202  apr_uint32_t max_servers = 10;
203  apr_uint32_t hres;
204  apr_uint32_t i;
205  my_hash_server_baton *baton =
206    apr_pcalloc(pool, sizeof(my_hash_server_baton));
207
208  rv = apr_memcache_create(pool, max_servers, 0, &memcache);
209  ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
210
211  /* as noted above, install our custom hash function, and call
212   * apr_memcache_hash. the return value should be our predefined number,
213   * and our function just ignores the other args, for simplicity.
214   */
215  memcache->hash_func = my_hash_func;
216
217  hres = apr_memcache_hash(memcache, "whatever", sizeof("whatever") - 1);
218  ABTS_INT_EQUAL(tc, HASH_FUNC_RESULT, hres);
219
220  /* add some servers */
221  for(i = 1; i <= 10; i++) {
222    apr_memcache_server_t *ms;
223
224    rv = apr_memcache_server_create(pool, HOST, i, 0, 1, 1, 60, &ms);
225    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
226
227    rv = apr_memcache_add_server(memcache, ms);
228    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
229  }
230
231  /*
232   * set 'which_server' in our server_baton to find the third server
233   * which should have the same port.
234   */
235  baton->which_server = 3;
236  memcache->server_func = my_server_func;
237  memcache->server_baton = baton;
238  found = apr_memcache_find_server_hash(memcache, 0);
239  ABTS_ASSERT(tc, "wrong server found", found->port == baton->which_server);
240}
241
242/* test non data related commands like stats and version */
243static void test_memcache_meta(abts_case * tc, void *data)
244{
245    apr_pool_t *pool = p;
246    apr_memcache_t *memcache;
247    apr_memcache_server_t *server;
248    apr_memcache_stats_t *stats;
249    char *result;
250    apr_status_t rv;
251
252    rv = apr_memcache_create(pool, 1, 0, &memcache);
253    ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
254
255    rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
256    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
257
258    rv = apr_memcache_add_server(memcache, server);
259    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
260
261    rv = apr_memcache_version(server, pool, &result);
262    ABTS_PTR_NOTNULL(tc, result);
263
264    rv = apr_memcache_stats(server, p, &stats);
265    ABTS_PTR_NOTNULL(tc, stats);
266
267    ABTS_STR_NEQUAL(tc, stats->version, result, 5);
268
269    /*
270     * no way to know exactly what will be in most of these, so
271     * just make sure there is something.
272     */
273
274    ABTS_ASSERT(tc, "pid", stats->pid >= 0);
275    ABTS_ASSERT(tc, "time", stats->time >= 0);
276    /* ABTS_ASSERT(tc, "pointer_size", stats->pointer_size >= 0); */
277    ABTS_ASSERT(tc, "rusage_user", stats->rusage_user >= 0);
278    ABTS_ASSERT(tc, "rusage_system", stats->rusage_system >= 0);
279
280    ABTS_ASSERT(tc, "curr_items", stats->curr_items >= 0);
281    ABTS_ASSERT(tc, "total_items", stats->total_items >= 0);
282    ABTS_ASSERT(tc, "bytes", stats->bytes >= 0);
283
284    ABTS_ASSERT(tc, "curr_connections", stats->curr_connections >= 0);
285    ABTS_ASSERT(tc, "total_connections", stats->total_connections >= 0);
286    ABTS_ASSERT(tc, "connection_structures",
287                stats->connection_structures >= 0);
288
289    ABTS_ASSERT(tc, "cmd_get", stats->cmd_get >= 0);
290    ABTS_ASSERT(tc, "cmd_set", stats->cmd_set >= 0);
291    ABTS_ASSERT(tc, "get_hits", stats->get_hits >= 0);
292    ABTS_ASSERT(tc, "get_misses", stats->get_misses >= 0);
293
294    /* ABTS_ASSERT(tc, "evictions", stats->evictions >= 0); */
295
296    ABTS_ASSERT(tc, "bytes_read", stats->bytes_read >= 0);
297    ABTS_ASSERT(tc, "bytes_written", stats->bytes_written >= 0);
298    ABTS_ASSERT(tc, "limit_maxbytes", stats->limit_maxbytes >= 0);
299
300    /* ABTS_ASSERT(tc, "threads", stats->threads >= 0); */
301}
302
303/* test add and replace calls */
304
305static void test_memcache_addreplace(abts_case * tc, void *data)
306{
307 apr_pool_t *pool = p;
308 apr_status_t rv;
309 apr_memcache_t *memcache;
310 apr_memcache_server_t *server;
311 apr_hash_t *tdata;
312 apr_hash_index_t *hi;
313 char *result;
314 apr_size_t len;
315
316  rv = apr_memcache_create(pool, 1, 0, &memcache);
317  ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
318
319  rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
320  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
321
322  rv = apr_memcache_add_server(memcache, server);
323  ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
324
325  tdata = apr_hash_make(p);
326  create_test_hash(pool, tdata);
327
328  for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
329    const void *k;
330    void *v;
331    const char *key;
332
333    apr_hash_this(hi, &k, NULL, &v);
334    key = k;
335
336    /* doesn't exist yet, fail */
337    rv = apr_memcache_replace(memcache, key, v, strlen(v) - 1, 0, 27);
338    ABTS_ASSERT(tc, "replace should have failed", rv != APR_SUCCESS);
339
340    /* doesn't exist yet, succeed */
341    rv = apr_memcache_add(memcache, key, v, strlen(v), 0, 27);
342    ABTS_ASSERT(tc, "add failed", rv == APR_SUCCESS);
343
344    /* exists now, succeed */
345    rv = apr_memcache_replace(memcache, key, "new", sizeof("new") - 1, 0, 27);
346    ABTS_ASSERT(tc, "replace failed", rv == APR_SUCCESS);
347
348    /* make sure its different */
349    rv = apr_memcache_getp(memcache, pool, key, &result, &len, NULL);
350    ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
351    ABTS_STR_NEQUAL(tc, result, "new", 3);
352
353    /* exists now, fail */
354    rv = apr_memcache_add(memcache, key, v, strlen(v), 0, 27);
355    ABTS_ASSERT(tc, "add should have failed", rv != APR_SUCCESS);
356
357    /* clean up */
358    rv = apr_memcache_delete(memcache, key, 0);
359    ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
360  }
361}
362
363/* basic tests of the increment and decrement commands */
364static void test_memcache_incrdecr(abts_case * tc, void *data)
365{
366 apr_pool_t *pool = p;
367 apr_status_t rv;
368 apr_memcache_t *memcache;
369 apr_memcache_server_t *server;
370 apr_uint32_t new;
371 char *result;
372 apr_size_t len;
373 apr_uint32_t i;
374
375  rv = apr_memcache_create(pool, 1, 0, &memcache);
376  ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
377
378  rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
379  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
380
381  rv = apr_memcache_add_server(memcache, server);
382  ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
383
384  rv = apr_memcache_set(memcache, prefix, "271", sizeof("271") - 1, 0, 27);
385  ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
386
387  for( i = 1; i <= TDATA_SIZE; i++) {
388    apr_uint32_t expect;
389
390    rv = apr_memcache_getp(memcache, pool, prefix, &result, &len, NULL);
391    ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
392
393    expect = i + atoi(result);
394
395    rv = apr_memcache_incr(memcache, prefix, i, &new);
396    ABTS_ASSERT(tc, "incr failed", rv == APR_SUCCESS);
397
398    ABTS_INT_EQUAL(tc, expect, new);
399
400    rv = apr_memcache_decr(memcache, prefix, i, &new);
401    ABTS_ASSERT(tc, "decr failed", rv == APR_SUCCESS);
402    ABTS_INT_EQUAL(tc, atoi(result), new);
403
404  }
405
406  rv = apr_memcache_getp(memcache, pool, prefix, &result, &len, NULL);
407  ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
408
409  ABTS_INT_EQUAL(tc, 271, atoi(result));
410
411  rv = apr_memcache_delete(memcache, prefix, 0);
412  ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
413}
414
415/* test the multiget functionality */
416static void test_memcache_multiget(abts_case * tc, void *data)
417{
418  apr_pool_t *pool = p;
419  apr_pool_t *tmppool;
420  apr_status_t rv;
421  apr_memcache_t *memcache;
422  apr_memcache_server_t *server;
423  apr_hash_t *tdata, *values;
424  apr_hash_index_t *hi;
425  apr_uint32_t i;
426
427  rv = apr_memcache_create(pool, 1, 0, &memcache);
428  ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
429
430  rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
431  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
432
433  rv = apr_memcache_add_server(memcache, server);
434  ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
435
436  values = apr_hash_make(p);
437  tdata = apr_hash_make(p);
438
439  create_test_hash(pool, tdata);
440
441  for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
442    const void *k;
443    void *v;
444    const char *key;
445
446    apr_hash_this(hi, &k, NULL, &v);
447    key = k;
448
449    rv = apr_memcache_set(memcache, key, v, strlen(v), 0, 27);
450    ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
451  }
452
453  rv = apr_pool_create(&tmppool, pool);
454  for (i = 0; i < TDATA_SET; i++)
455    apr_memcache_add_multget_key(pool,
456                                 apr_pstrcat(pool, prefix,
457                                             apr_itoa(pool, i), NULL),
458                                 &values);
459
460  rv = apr_memcache_multgetp(memcache,
461                             tmppool,
462                             pool,
463                             values);
464
465  ABTS_ASSERT(tc, "multgetp failed", rv == APR_SUCCESS);
466  ABTS_ASSERT(tc, "multgetp returned too few results",
467              apr_hash_count(values) == TDATA_SET);
468
469  for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
470    const void *k;
471    const char *key;
472
473    apr_hash_this(hi, &k, NULL, NULL);
474    key = k;
475
476    rv = apr_memcache_delete(memcache, key, 0);
477    ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
478  }
479
480}
481
482/* test setting and getting */
483
484static void test_memcache_setget(abts_case * tc, void *data)
485{
486    apr_pool_t *pool = p;
487    apr_status_t rv;
488    apr_memcache_t *memcache;
489    apr_memcache_server_t *server;
490    apr_hash_t *tdata;
491    apr_hash_index_t *hi;
492    char *result;
493    apr_size_t len;
494
495    rv = apr_memcache_create(pool, 1, 0, &memcache);
496    ABTS_ASSERT(tc, "memcache create failed", rv == APR_SUCCESS);
497
498    rv = apr_memcache_server_create(pool, HOST, PORT, 0, 1, 1, 60, &server);
499    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
500
501    rv = apr_memcache_add_server(memcache, server);
502    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
503
504    tdata = apr_hash_make(pool);
505
506    create_test_hash(pool, tdata);
507
508    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
509	const void *k;
510	void *v;
511        const char *key;
512
513	apr_hash_this(hi, &k, NULL, &v);
514        key = k;
515
516	rv = apr_memcache_set(memcache, key, v, strlen(v), 0, 27);
517	ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
518	rv = apr_memcache_getp(memcache, pool, key, &result, &len, NULL);
519	ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
520    }
521
522    rv = apr_memcache_getp(memcache, pool, "nothere3423", &result, &len, NULL);
523
524    ABTS_ASSERT(tc, "get should have failed", rv != APR_SUCCESS);
525
526    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
527	const void *k;
528	const char *key;
529
530	apr_hash_this(hi, &k, NULL, NULL);
531	key = k;
532
533	rv = apr_memcache_delete(memcache, key, 0);
534	ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
535    }
536}
537
538/* use apr_socket stuff to see if there is in fact a memcached server
539 * running on PORT.
540 */
541static apr_status_t check_mc(void)
542{
543  apr_pool_t *pool = p;
544  apr_status_t rv;
545  apr_socket_t *sock = NULL;
546  apr_sockaddr_t *sa;
547  struct iovec vec[2];
548  apr_size_t written;
549  char buf[128];
550  apr_size_t len;
551
552  rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, 0, pool);
553  if(rv != APR_SUCCESS) {
554    return rv;
555  }
556
557  rv = apr_sockaddr_info_get(&sa, HOST, APR_INET, PORT, 0, pool);
558  if(rv != APR_SUCCESS) {
559    return rv;
560  }
561
562  rv = apr_socket_timeout_set(sock, 1 * APR_USEC_PER_SEC);
563  if (rv != APR_SUCCESS) {
564    return rv;
565  }
566
567  rv = apr_socket_connect(sock, sa);
568  if (rv != APR_SUCCESS) {
569    return rv;
570  }
571
572  rv = apr_socket_timeout_set(sock, -1);
573  if (rv != APR_SUCCESS) {
574    return rv;
575  }
576
577  vec[0].iov_base = "version";
578  vec[0].iov_len  = sizeof("version") - 1;
579
580  vec[1].iov_base = "\r\n";
581  vec[1].iov_len  = sizeof("\r\n") -1;
582
583  rv = apr_socket_sendv(sock, vec, 2, &written);
584  if (rv != APR_SUCCESS) {
585    return rv;
586  }
587
588  len = sizeof(buf);
589  rv = apr_socket_recv(sock, buf, &len);
590  if(rv != APR_SUCCESS) {
591    return rv;
592  }
593
594  if(strncmp(buf, "VERSION", sizeof("VERSION")-1) != 0) {
595    rv = APR_EGENERAL;
596  }
597
598  apr_socket_close(sock);
599  return rv;
600}
601
602abts_suite *testmemcache(abts_suite * suite)
603{
604    apr_status_t rv;
605    suite = ADD_SUITE(suite);
606    /* check for a running memcached on the typical port before
607     * trying to run the tests. succeed if we don't find one.
608     */
609    rv = check_mc();
610    if (rv == APR_SUCCESS) {
611      abts_run_test(suite, test_memcache_create, NULL);
612      abts_run_test(suite, test_memcache_user_funcs, NULL);
613      abts_run_test(suite, test_memcache_meta, NULL);
614      abts_run_test(suite, test_memcache_setget, NULL);
615      abts_run_test(suite, test_memcache_multiget, NULL);
616      abts_run_test(suite, test_memcache_addreplace, NULL);
617      abts_run_test(suite, test_memcache_incrdecr, NULL);
618    }
619    else {
620        abts_log_message("Error %d occurred attempting to reach memcached "
621                         "on %s:%d.  Skipping apr_memcache tests...",
622                         rv, HOST, PORT);
623    }
624
625    return suite;
626}
627