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