testredis.c revision 362181
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_redis.h"
24#include "apr_network_io.h"
25
26#include <stdio.h>
27#if APR_HAVE_STDLIB_H
28#include <stdlib.h>		/* for exit() */
29#endif
30
31#define HOST "localhost"
32#define PORT 6379
33
34/* the total number of items to use for set/get testing */
35#define TDATA_SIZE 3000
36
37/* some smaller subset of TDATA_SIZE used for multiget testing */
38#define TDATA_SET 100
39
40/* our custom hash function just returns this all the time */
41#define HASH_FUNC_RESULT 510
42
43/* all keys will be prefixed with this */
44static const char prefix[] = "testredis";
45
46/* text for values we store */
47static const char txt[] =
48"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Duis at"
49"lacus in ligula hendrerit consectetuer. Vestibulum tristique odio"
50"iaculis leo. In massa arcu, ultricies a, laoreet nec, hendrerit non,"
51"neque. Nulla sagittis sapien ac risus. Morbi ligula dolor, vestibulum"
52"nec, viverra id, placerat dapibus, arcu. Curabitur egestas feugiat"
53"tellus. Donec dignissim. Nunc ante. Curabitur id lorem. In mollis"
54"tortor sit amet eros auctor dapibus. Proin nulla sem, tristique in,"
55"convallis id, iaculis feugiat cras amet.";
56
57/*
58 * this datatype is for our custom server determination function. this might
59 * be useful if you don't want to rely on simply hashing keys to determine
60 * where a key belongs, but instead want to write something fancy, or use some
61 * other kind of configuration data, i.e. a hash plus some data about a
62 * namespace, or whatever. see my_server_func, and test_redis_user_funcs
63 * for the examples.
64 */
65typedef struct {
66  const char *someval;
67  apr_uint32_t which_server;
68} my_hash_server_baton;
69
70
71/* this could do something fancy and return some hash result.
72 * for simplicity, just return the same value, so we can test it later on.
73 * if you wanted to use some external hashing library or functions for
74 * consistent hashing, for example, this would be a good place to do it.
75 */
76static apr_uint32_t my_hash_func(void *baton, const char *data,
77                                 apr_size_t data_len)
78{
79
80  return HASH_FUNC_RESULT;
81}
82
83/*
84 * a fancy function to determine which server to use given some kind of data
85 * and a hash value. this example actually ignores the hash value itself
86 * and pulls some number from the *baton, which is a struct that has some
87 * kind of meaningful stuff in it.
88 */
89static apr_redis_server_t *my_server_func(void *baton,
90                                             apr_redis_t *mc,
91                                             const apr_uint32_t hash)
92{
93  apr_redis_server_t *ms = NULL;
94  my_hash_server_baton *mhsb = (my_hash_server_baton *)baton;
95
96  if(mc->ntotal == 0) {
97    return NULL;
98  }
99
100  if(mc->ntotal < mhsb->which_server) {
101    return NULL;
102  }
103
104  ms = mc->live_servers[mhsb->which_server - 1];
105
106  return ms;
107}
108
109static apr_uint16_t firsttime = 0;
110static int randval(apr_uint32_t high)
111{
112    apr_uint32_t i = 0;
113    double d = 0;
114
115    if (firsttime == 0) {
116	srand((unsigned) (getpid()));
117	firsttime = 1;
118    }
119
120    d = (double) rand() / ((double) RAND_MAX + 1);
121    i = (int) (d * (high - 0 + 1));
122
123    return i > 0 ? i : 1;
124}
125
126/*
127 * general test to make sure we can create the redis struct and add
128 * some servers, but not more than we tell it we can add
129 */
130
131static void test_redis_create(abts_case * tc, void *data)
132{
133  apr_pool_t *pool = p;
134  apr_status_t rv;
135  apr_redis_t *redis;
136  apr_redis_server_t *server, *s;
137  apr_uint32_t max_servers = 10;
138  apr_uint32_t i;
139  apr_uint32_t hash;
140
141  rv = apr_redis_create(pool, max_servers, 0, &redis);
142  ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
143
144  for (i = 1; i <= max_servers; i++) {
145    apr_port_t port;
146
147    port = PORT + i;
148    rv =
149      apr_redis_server_create(pool, HOST, PORT + i, 0, 1, 1, 60, 60, &server);
150    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
151
152    rv = apr_redis_add_server(redis, server);
153    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
154
155    s = apr_redis_find_server(redis, HOST, port);
156    ABTS_PTR_EQUAL(tc, server, s);
157
158    rv = apr_redis_disable_server(redis, s);
159    ABTS_ASSERT(tc, "server disable failed", rv == APR_SUCCESS);
160
161    rv = apr_redis_enable_server(redis, s);
162    ABTS_ASSERT(tc, "server enable failed", rv == APR_SUCCESS);
163
164    hash = apr_redis_hash(redis, prefix, strlen(prefix));
165    ABTS_ASSERT(tc, "hash failed", hash > 0);
166
167    s = apr_redis_find_server_hash(redis, hash);
168    ABTS_PTR_NOTNULL(tc, s);
169  }
170
171  rv = apr_redis_server_create(pool, HOST, PORT, 0, 1, 1, 60, 60, &server);
172  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
173
174  rv = apr_redis_add_server(redis, server);
175  ABTS_ASSERT(tc, "server add should have failed", rv != APR_SUCCESS);
176
177}
178
179/* install our own custom hashing and server selection routines. */
180
181static int create_test_hash(apr_pool_t *p, apr_hash_t *h)
182{
183  int i;
184
185  for (i = 0; i < TDATA_SIZE; i++) {
186    char *k, *v;
187
188    k = apr_pstrcat(p, prefix, apr_itoa(p, i), NULL);
189    v = apr_pstrndup(p, txt, randval((apr_uint32_t)strlen(txt)));
190
191    apr_hash_set(h, k, APR_HASH_KEY_STRING, v);
192  }
193
194  return i;
195}
196
197static void test_redis_user_funcs(abts_case * tc, void *data)
198{
199  apr_pool_t *pool = p;
200  apr_status_t rv;
201  apr_redis_t *redis;
202  apr_redis_server_t *found;
203  apr_uint32_t max_servers = 10;
204  apr_uint32_t hres;
205  apr_uint32_t i;
206  my_hash_server_baton *baton =
207    apr_pcalloc(pool, sizeof(my_hash_server_baton));
208
209  rv = apr_redis_create(pool, max_servers, 0, &redis);
210  ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
211
212  /* as noted above, install our custom hash function, and call
213   * apr_redis_hash. the return value should be our predefined number,
214   * and our function just ignores the other args, for simplicity.
215   */
216  redis->hash_func = my_hash_func;
217
218  hres = apr_redis_hash(redis, "whatever", sizeof("whatever") - 1);
219  ABTS_INT_EQUAL(tc, HASH_FUNC_RESULT, hres);
220
221  /* add some servers */
222  for(i = 1; i <= 10; i++) {
223    apr_redis_server_t *ms;
224
225    rv = apr_redis_server_create(pool, HOST, i, 0, 1, 1, 60, 60, &ms);
226    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
227
228    rv = apr_redis_add_server(redis, ms);
229    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
230  }
231
232  /*
233   * set 'which_server' in our server_baton to find the third server
234   * which should have the same port.
235   */
236  baton->which_server = 3;
237  redis->server_func = my_server_func;
238  redis->server_baton = baton;
239  found = apr_redis_find_server_hash(redis, 0);
240  ABTS_ASSERT(tc, "wrong server found", found->port == baton->which_server);
241}
242
243/* test non data related commands like stats and version */
244static void test_redis_meta(abts_case * tc, void *data)
245{
246    apr_pool_t *pool = p;
247    apr_redis_t *redis;
248    apr_redis_server_t *server;
249    apr_redis_stats_t *stats;
250    char *result;
251    apr_status_t rv;
252
253    rv = apr_redis_create(pool, 1, 0, &redis);
254    ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
255
256    rv = apr_redis_server_create(pool, HOST, PORT, 0, 1, 1, 60, 60, &server);
257    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
258
259    rv = apr_redis_add_server(redis, server);
260    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
261
262    rv = apr_redis_version(server, pool, &result);
263    ABTS_PTR_NOTNULL(tc, result);
264
265    rv = apr_redis_stats(server, p, &stats);
266    ABTS_PTR_NOTNULL(tc, stats);
267
268    /*
269     * no way to know exactly what will be in most of these, so
270     * just make sure there is something.
271     */
272    ABTS_ASSERT(tc, "major", stats->major >= 1);
273    ABTS_ASSERT(tc, "minor", stats->minor >= 0);
274    ABTS_ASSERT(tc, "patch", stats->patch >= 0);
275    ABTS_ASSERT(tc, "process_id", stats->process_id >= 0);
276    ABTS_ASSERT(tc, "uptime_in_seconds", stats->uptime_in_seconds >= 0);
277    ABTS_ASSERT(tc, "arch_bits", stats->arch_bits >= 0);
278    ABTS_ASSERT(tc, "connected_clients", stats->connected_clients >= 0);
279    ABTS_ASSERT(tc, "blocked_clients", stats->blocked_clients >= 0);
280    ABTS_ASSERT(tc, "maxmemory", stats->maxmemory >= 0);
281    ABTS_ASSERT(tc, "used_memory", stats->used_memory >= 0);
282    ABTS_ASSERT(tc, "total_system_memory", stats->total_system_memory >= 0);
283    ABTS_ASSERT(tc, "total_connections_received", stats->total_connections_received >= 0);
284    ABTS_ASSERT(tc, "total_commands_processed", stats->total_commands_processed >= 0);
285    ABTS_ASSERT(tc, "total_net_input_bytes", stats->total_net_input_bytes >= 0);
286    ABTS_ASSERT(tc, "total_net_output_bytes", stats->total_net_output_bytes >= 0);
287    ABTS_ASSERT(tc, "keyspace_hits", stats->keyspace_hits >= 0);
288    ABTS_ASSERT(tc, "keyspace_misses", stats->keyspace_misses >= 0);
289    ABTS_ASSERT(tc, "role", stats->role >= 0);
290    ABTS_ASSERT(tc, "connected_slaves", stats->connected_slaves >= 0);
291    ABTS_ASSERT(tc, "used_cpu_sys", stats->used_cpu_sys >= 0);
292    ABTS_ASSERT(tc, "used_cpu_user", stats->used_cpu_user >= 0);
293    ABTS_ASSERT(tc, "cluster_enabled", stats->cluster_enabled >= 0);
294}
295
296
297/* basic tests of the increment and decrement commands */
298static void test_redis_incrdecr(abts_case * tc, void *data)
299{
300 apr_pool_t *pool = p;
301 apr_status_t rv;
302 apr_redis_t *redis;
303 apr_redis_server_t *server;
304 apr_uint32_t new;
305 char *result;
306 apr_size_t len;
307 apr_uint32_t i;
308
309  rv = apr_redis_create(pool, 1, 0, &redis);
310  ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
311
312  rv = apr_redis_server_create(pool, HOST, PORT, 0, 1, 1, 60, 60, &server);
313  ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
314
315  rv = apr_redis_add_server(redis, server);
316  ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
317
318  rv = apr_redis_set(redis, prefix, "271", sizeof("271") - 1, 27);
319  ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
320
321  for( i = 1; i <= TDATA_SIZE; i++) {
322    apr_uint32_t expect;
323
324    rv = apr_redis_getp(redis, pool, prefix, &result, &len, NULL);
325    ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
326
327    expect = i + atoi(result);
328
329    rv = apr_redis_incr(redis, prefix, i, &new);
330    ABTS_ASSERT(tc, "incr failed", rv == APR_SUCCESS);
331
332    ABTS_INT_EQUAL(tc, expect, new);
333
334    rv = apr_redis_decr(redis, prefix, i, &new);
335    ABTS_ASSERT(tc, "decr failed", rv == APR_SUCCESS);
336
337    ABTS_INT_EQUAL(tc, atoi(result), new);
338
339  }
340
341  rv = apr_redis_getp(redis, pool, prefix, &result, &len, NULL);
342  ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
343
344  ABTS_INT_EQUAL(tc, 271, atoi(result));
345
346  rv = apr_redis_delete(redis, prefix, 0);
347  ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
348}
349
350
351/* test setting and getting */
352
353static void test_redis_setget(abts_case * tc, void *data)
354{
355    apr_pool_t *pool = p;
356    apr_status_t rv;
357    apr_redis_t *redis;
358    apr_redis_server_t *server;
359    apr_hash_t *tdata;
360    apr_hash_index_t *hi;
361    char *result;
362    apr_size_t len;
363
364    rv = apr_redis_create(pool, 1, 0, &redis);
365    ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
366
367    rv = apr_redis_server_create(pool, HOST, PORT, 0, 1, 1, 60, 60, &server);
368    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
369
370    rv = apr_redis_add_server(redis, server);
371    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
372
373    tdata = apr_hash_make(pool);
374
375    create_test_hash(pool, tdata);
376
377    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
378	const void *k;
379	void *v;
380        const char *key;
381
382	apr_hash_this(hi, &k, NULL, &v);
383        key = k;
384
385	rv = apr_redis_set(redis, key, v, strlen(v), 27);
386	ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
387	rv = apr_redis_getp(redis, pool, key, &result, &len, NULL);
388	ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
389    }
390
391    rv = apr_redis_getp(redis, pool, "nothere3423", &result, &len, NULL);
392
393    ABTS_ASSERT(tc, "get should have failed", rv != APR_SUCCESS);
394
395    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
396	const void *k;
397	const char *key;
398
399	apr_hash_this(hi, &k, NULL, NULL);
400	key = k;
401
402	rv = apr_redis_delete(redis, key, 0);
403	ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
404    }
405}
406
407/* test setting and getting */
408
409static void test_redis_setexget(abts_case * tc, void *data)
410{
411    apr_pool_t *pool = p;
412    apr_status_t rv;
413    apr_redis_t *redis;
414    apr_redis_server_t *server;
415    apr_hash_t *tdata;
416    apr_hash_index_t *hi;
417    char *result;
418    apr_size_t len;
419
420    rv = apr_redis_create(pool, 1, 0, &redis);
421    ABTS_ASSERT(tc, "redis create failed", rv == APR_SUCCESS);
422
423    rv = apr_redis_server_create(pool, HOST, PORT, 0, 1, 1, 60, 60, &server);
424    ABTS_ASSERT(tc, "server create failed", rv == APR_SUCCESS);
425
426    rv = apr_redis_add_server(redis, server);
427    ABTS_ASSERT(tc, "server add failed", rv == APR_SUCCESS);
428
429    tdata = apr_hash_make(pool);
430
431    create_test_hash(pool, tdata);
432
433    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
434    const void *k;
435    void *v;
436        const char *key;
437
438    apr_hash_this(hi, &k, NULL, &v);
439        key = k;
440
441    rv = apr_redis_ping(server);
442    ABTS_ASSERT(tc, "ping failed", rv == APR_SUCCESS);
443    rv = apr_redis_setex(redis, key, v, strlen(v), 10, 27);
444    ABTS_ASSERT(tc, "set failed", rv == APR_SUCCESS);
445    rv = apr_redis_getp(redis, pool, key, &result, &len, NULL);
446    ABTS_ASSERT(tc, "get failed", rv == APR_SUCCESS);
447    }
448
449    rv = apr_redis_getp(redis, pool, "nothere3423", &result, &len, NULL);
450
451    ABTS_ASSERT(tc, "get should have failed", rv != APR_SUCCESS);
452
453    for (hi = apr_hash_first(p, tdata); hi; hi = apr_hash_next(hi)) {
454    const void *k;
455    const char *key;
456
457    apr_hash_this(hi, &k, NULL, NULL);
458    key = k;
459
460    rv = apr_redis_delete(redis, key, 0);
461    ABTS_ASSERT(tc, "delete failed", rv == APR_SUCCESS);
462    }
463}
464
465/* use apr_socket stuff to see if there is in fact a Redis server
466 * running on PORT.
467 */
468static apr_status_t check_redis(void)
469{
470  apr_pool_t *pool = p;
471  apr_status_t rv;
472  apr_socket_t *sock = NULL;
473  apr_sockaddr_t *sa;
474  struct iovec vec[2];
475  apr_size_t written;
476  char buf[128];
477  apr_size_t len;
478
479  rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, 0, pool);
480  if(rv != APR_SUCCESS) {
481    return rv;
482  }
483
484  rv = apr_sockaddr_info_get(&sa, HOST, APR_INET, PORT, 0, pool);
485  if(rv != APR_SUCCESS) {
486    return rv;
487  }
488
489  rv = apr_socket_timeout_set(sock, 1 * APR_USEC_PER_SEC);
490  if (rv != APR_SUCCESS) {
491    return rv;
492  }
493
494  rv = apr_socket_connect(sock, sa);
495  if (rv != APR_SUCCESS) {
496    return rv;
497  }
498
499  rv = apr_socket_timeout_set(sock, -1);
500  if (rv != APR_SUCCESS) {
501    return rv;
502  }
503
504  vec[0].iov_base = "PING";
505  vec[0].iov_len  = sizeof("PING") - 1;
506
507  vec[1].iov_base = "\r\n";
508  vec[1].iov_len  = sizeof("\r\n") -1;
509
510  rv = apr_socket_sendv(sock, vec, 2, &written);
511  if (rv != APR_SUCCESS) {
512    return rv;
513  }
514
515  len = sizeof(buf);
516  rv = apr_socket_recv(sock, buf, &len);
517  if(rv != APR_SUCCESS) {
518    return rv;
519  }
520  if(strncmp(buf, "+PONG", sizeof("+PONG")-1) != 0) {
521    rv = APR_EGENERAL;
522  }
523
524  apr_socket_close(sock);
525  return rv;
526}
527
528abts_suite *testredis(abts_suite * suite)
529{
530    apr_status_t rv;
531    suite = ADD_SUITE(suite);
532    /* check for a running redis on the typical port before
533     * trying to run the tests. succeed if we don't find one.
534     */
535    rv = check_redis();
536    if (rv == APR_SUCCESS) {
537        abts_run_test(suite, test_redis_create, NULL);
538        abts_run_test(suite, test_redis_user_funcs, NULL);
539        abts_run_test(suite, test_redis_meta, NULL);
540        abts_run_test(suite, test_redis_setget, NULL);
541        abts_run_test(suite, test_redis_setexget, NULL);
542        /* abts_run_test(suite, test_redis_multiget, NULL); */
543        abts_run_test(suite, test_redis_incrdecr, NULL);
544    }
545    else {
546        abts_log_message("Error %d occurred attempting to reach Redis "
547                         "on %s:%d.  Skipping apr_redis tests...",
548                         rv, HOST, PORT);
549    }
550
551    return suite;
552}
553