The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Functions | Variables
redis.c File Reference

conf functions for interacting with Redis cluster via Hiredis. More...

#include <freeradius-devel/redis/base.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/value.h>
+ Include dependency graph for redis.c:

Go to the source code of this file.

Functions

fr_redis_rcode_t fr_redis_command_status (fr_redis_conn_t *conn, redisReply *reply)
 Check the reply for errors. More...
 
fr_redis_rcode_t fr_redis_get_version (char *out, size_t out_len, fr_redis_conn_t *conn)
 Get the version of Redis running on the remote server. More...
 
fr_redis_rcode_t fr_redis_pipeline_result (unsigned int *pipelined, fr_redis_rcode_t *rcode, redisReply *out[], size_t out_len, fr_redis_conn_t *conn)
 Simplifies handling of pipelined commands with Redis cluster. More...
 
void fr_redis_reply_print (fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
 Print the response data in a useful treelike form. More...
 
int fr_redis_reply_to_map (TALLOC_CTX *ctx, map_list_t *out, request_t *request, redisReply *key, redisReply *op, redisReply *value)
 Convert a pair of redis reply objects to a map. More...
 
int fr_redis_reply_to_value_box (TALLOC_CTX *ctx, fr_value_box_t *out, redisReply *reply, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, bool box_error, bool shallow)
 Convert a string or integer type to fr_value_box_t of specified type. More...
 
int fr_redis_tuple_from_map (TALLOC_CTX *pool, char const *out[], size_t out_len[], map_t *map)
 Add a single map pair to an existing command string as three elements. More...
 
uint32_t fr_redis_version_num (char const *version)
 Convert version string into a 32bit unsigned integer for comparisons. More...
 
void fr_redis_version_print (void)
 Print the version of libhiredis the server was built against. More...
 

Variables

fr_table_num_sorted_t const redis_rcodes []
 
size_t redis_rcodes_len = NUM_ELEMENTS(redis_rcodes)
 
fr_table_num_sorted_t const redis_reply_types []
 
size_t redis_reply_types_len = NUM_ELEMENTS(redis_reply_types)
 

Detailed Description

conf functions for interacting with Redis cluster via Hiredis.

Common functions for interacting with Redis via hiredis.

Id
0f86002214f06e4b2e32226734906322318e31b3
Author
Arran Cudbard-Bell

Overview

Read and understand this http://redis.io/topics/cluster-spec first, else the text below will not be useful.

Using the cluster's public API

The two functions used at runtime to issue commands are fr_redis_cluster_state_init and fr_redis_cluster_state_next.

fr_redis_cluster_state_init initialises the structure we use to track which node we're communicating with, and uses a key (string) to determine which node we should try and contact first.

fr_redis_cluster_state_next examines the result of the last command, and either gets a new connection, or errors out.

In both cases the connection should not be released by the caller, only by fr_redis_cluster_state_next.

Below is the sequence of calls required to use the cluster:

1. During initialization allocate a cluster configuration structure with
   #fr_redis_cluster_alloc.  This holds the cluster configuration and state.
2. Declare a #fr_redis_cluster_state_t in the function that needs to issue
   commands against the cluster.
3. Call #fr_redis_cluster_state_init with relevant arguments including a pointer
   to the #fr_redis_cluster_state_t struct, and a key/key_len used for initial
   lookup.  For most commands the key is the value of the second argument.
   #fr_redis_cluster_state_init will then and reserve/pass back a connection for
   the pool associated with the node associated with the key.
4. Use the connection that was passed back, to issue a Redis command against.
5. Use #fr_redis_command_status to translate the result of the command into
   a #fr_redis_rcode_t value.
6. Call #fr_redis_cluster_state_next with relevant arguments including a pointer
   to the #fr_redis_cluster_state_t struct and the #fr_redis_rcode_t value for the
   last command.
7. If #fr_redis_cluster_state_next returns 0, repeat steps 4-7.  Otherwise
   stop and analyse the return value.

See fr_redis_cluster_state_init for example code.

Structures

This code maintains a series structures for efficient lookup and lockless operations.

The important ones are:

Each fr_redis_cluster_node_t contains a master ID, and an array of slave IDs. The IDs are array indexes in the fr_redis_cluster_t.node array. We use 8bit unsigned integers instead of pointers to save space. Using pointers, the node[] array would need 784K, using IDs it uses 112K. Still not light on memory, but a bit more acceptable. Currently the key_slot array is shadowed by key_slot_pending, used to stage new key_slot mappings. This doubles the memory used. We may want to consider allocating key_slot_pending only during remappings and freeing it after.

Mapping/Remapping the cluster

On startup, and during cluster operation, a remap may be performed. A remap involves the following steps:

  1. Executing the Redis 'cluster slots' command.
  2. Validating the result of this command. We need to do extensive validation to avoid SEGV on invalid data, due to the way libhiredis presents the result.
  3. Determining the intersection between nodes described in the result, and those already in our fr_rb_tree_t.
  4. Connecting to nodes that were in the result, but not in the tree. Note: If we can't connect to any of the masters, we count the map as invalid, roll back any newly connected nodes, and error out. Slave failure is OK.
  5. Mapping keyslot ranges to nodes in the key_slot_pending array.
  6. Verifying there are no holes in the ranges (if there are, we roll back and error out).
  7. Applying the new keyslot ranges.
  8. Removing nodes no longer used by the key slots, and adding them back to the free nodes queue.

cluster_map_get and cluster_map_apply, perform the operations described above. The get function, issues the 'cluster slots' command and performs validation, the apply function processes and applies the map.

Failing to apply a map is not a fatal error at runtime, and is only fatal on startup if pool.start > 0.

The cluster client can continue to operate, albeit inefficiently, with a stale cluster map by following '-ASK' and '-MOVE' redirects.

Remaps are limited to one per second. If any operation sets the remap_needed flag, or attempts a remap directly, the remap may be skipped if one occurred recently.

Processing '-ASK' and '-MOVE' redirects

The code treats '-ASK' (temporary redirect) and '-MOVE' (permanent redirect) responses similarly. If the node is known, then a connection is reserved from its pool, if the node is not known, a new pool is established, and a connection reserved.

The difference between '-ASK' and '-MOVE' is that '-MOVE' attempts a cluster remap before following the redirect.

The data from '-MOVE' responses, is not used to alter the cluster map. That is only done on successful remap.

Processing '-TRYAGAIN'

If the cluster is in a state of flux, a node may return '-TRYAGAIN' to indicated that we should attempt the operation again. The cluster spec says we should attempt the operation after some time. This time is configurable.

Id
33c5b1728ce1bd90211e8bbfb691d185ed35b50e

Definition in file redis.c.

Function Documentation

◆ fr_redis_command_status()

fr_redis_rcode_t fr_redis_command_status ( fr_redis_conn_t conn,
redisReply *  reply 
)

Check the reply for errors.

Parameters
[in]connused to issue the command.
[in]replyto process.
Returns
  • REDIS_RCODE_TRY_AGAIN - If the operation should be retries.
  • REDIS_RCODE_MOVED - If the key has been permanently moved.
  • REDIS_RCODE_ASK - If the key has been temporarily moved.
  • REDIS_RCODE_SUCCESS - if no errors.
  • REDIS_RCODE_ERROR - on command/server error.
  • REDIS_RCODE_NO_SCRIPT - script specified by evalsha doesn't exist.
  • REDIS_RCODE_RECONNECT - on connection error (probably needs reconnecting).

Definition at line 71 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_get_version()

fr_redis_rcode_t fr_redis_get_version ( char *  out,
size_t  out_len,
fr_redis_conn_t conn 
)

Get the version of Redis running on the remote server.

This can be useful for some modules, as it allows adaptive behaviour, or early termination.

Parameters
[out]outWhere to write the version string.
[in]out_lenLength of the version string buffer.
[in]connUsed to query the version string.
Returns

Definition at line 638 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_pipeline_result()

fr_redis_rcode_t fr_redis_pipeline_result ( unsigned int *  pipelined,
fr_redis_rcode_t rcode,
redisReply *  out[],
size_t  out_len,
fr_redis_conn_t conn 
)

Simplifies handling of pipelined commands with Redis cluster.

Retrieve all available pipelined responses, and write them to the array.

On encountering an error, all previously retrieved responses are freed, and the reply containing the error is written to the first element of out. All responses after the error are also freed.

If the number of responses != pipelined, that's also an error, a very serious one, in libhiredis or Redis. We can't really do much here apart from error out.

Parameters
[out]pipelinedNumber of pipelined commands we sent to the server.
[out]rcodeStatus of the first errored response, or REDIS_RCODE_SUCCESS if all responses were processed.
[out]outWhere to write the replies from pipelined commands. Will contain exactly 1 element on error WHICH MUST BE FREED, else the number passed in pipelined.
[in]out_lennumber of elements in out.
[in]connthe pipelined commands were issued on.
Returns

Definition at line 535 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_reply_print()

void fr_redis_reply_print ( fr_log_lvl_t  lvl,
redisReply *  reply,
request_t request,
int  idx 
)

Print the response data in a useful treelike form.

Parameters
[in]lvlto print data at.
[in]replyto print.
[in]requestThe current request.
[in]idxResponse number.

Definition at line 141 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_reply_to_map()

int fr_redis_reply_to_map ( TALLOC_CTX *  ctx,
map_list_t *  out,
request_t request,
redisReply *  key,
redisReply *  op,
redisReply *  value 
)

Convert a pair of redis reply objects to a map.

The maps can then be applied using map_to_request.

Parameters
[in,out]ctxto allocate maps in.
[out]outWhere to write the head of the new maps list.
[in]requestThe current request.
[in]keyto process.
[in]opto process.
[in]valueto process.
Returns
  • 0 on success.
  • -1 on failure.

Definition at line 365 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_reply_to_value_box()

int fr_redis_reply_to_value_box ( TALLOC_CTX *  ctx,
fr_value_box_t out,
redisReply *  reply,
fr_type_t  dst_type,
fr_dict_attr_t const *  dst_enumv,
bool  box_error,
bool  shallow 
)

Convert a string or integer type to fr_value_box_t of specified type.

Will work with REDIS_REPLY_STRING (which is converted to FR_TYPE_STRING then cast to dst_type), or REDIS_REPLY_INTEGER (which is converted to FR_TYPE_UINT64, then cast to dst_type).

Note
Any unsupported types will trigger an assert. You must check the reply type prior to calling this function.
Parameters
[in,out]ctxto allocate any buffers in.
[out]outWhere to write the cast type.
[in]replyto process.
[in]dst_typeto convert to. May be FR_TYPE_VOID to infer type.
[in]dst_enumvUsed to convert string types to integers for attribute with enumerated values.
[in]box_errorIf true then REDIS_REPLY_ERROR will be copied to a box, otherwise we'll return and error with the contents of the error available on the thread local error stack.
[in]shallowIf true, we shallow copy strings.
Returns
  • 1 if we received a NIL reply.
  • 0 on success.
  • -1 on cast or parse failure.

Definition at line 206 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_tuple_from_map()

int fr_redis_tuple_from_map ( TALLOC_CTX *  pool,
char const *  out[],
size_t  out_len[],
map_t map 
)

Add a single map pair to an existing command string as three elements.

  • Integer types will be encoded as integers.
  • Strings and octets will be encoded in their raw form.
  • Other types will be converted to their printable form and will be encoded as strings.
Note
lhs must be a TMPL_TYPE_ATTR.
rhs must be a TMPL_TYPE_DATA.
Parameters
poolto allocate any buffers in.
outWhere to write pointers to the member of the tuple. Unused elements should be a multiple of three, and it should have at least three unused elements.
out_lenWhere to write the size of the data pointed to by the equivalent index in the out array.
mapto convert.
Returns
0 on success. -1 on failure.

Definition at line 459 of file redis.c.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fr_redis_version_num()

uint32_t fr_redis_version_num ( char const *  version)

Convert version string into a 32bit unsigned integer for comparisons.

Parameters
[in]versionstring to parse.
Returns
32bit unsigned integer representing the version string.

Definition at line 688 of file redis.c.

+ Here is the caller graph for this function:

◆ fr_redis_version_print()

void fr_redis_version_print ( void  )

Print the version of libhiredis the server was built against.

Definition at line 53 of file redis.c.

+ Here is the caller graph for this function:

Variable Documentation

◆ redis_rcodes

fr_table_num_sorted_t const redis_rcodes[]
Initial value:
= {
{ L("ask"), REDIS_RCODE_ASK },
{ L("error"), REDIS_RCODE_ERROR },
{ L("move"), REDIS_RCODE_MOVE },
{ L("reconnect"), REDIS_RCODE_RECONNECT },
{ L("success"), REDIS_RCODE_SUCCESS },
{ L("try again"), REDIS_RCODE_TRY_AGAIN }
}
#define L(_str)
Helper for initialising arrays of string literals.
Definition: build.h:207
@ REDIS_RCODE_RECONNECT
Transitory error, caller should retry the operation with a new connection.
Definition: base.h:91
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition: base.h:88
@ REDIS_RCODE_MOVE
Attempt operation on an alternative node with remap.
Definition: base.h:94
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
Definition: base.h:90
@ REDIS_RCODE_ASK
Attempt operation on an alternative node.
Definition: base.h:93
@ REDIS_RCODE_ERROR
Unrecoverable library/server error.
Definition: base.h:89

Definition at line 40 of file redis.c.

◆ redis_rcodes_len

size_t redis_rcodes_len = NUM_ELEMENTS(redis_rcodes)

Definition at line 48 of file redis.c.

◆ redis_reply_types

fr_table_num_sorted_t const redis_reply_types[]
Initial value:
= {
{ L("array"), REDIS_REPLY_ARRAY },
{ L("error"), REDIS_REPLY_ERROR },
{ L("integer"), REDIS_REPLY_INTEGER },
{ L("nil"), REDIS_REPLY_NIL },
{ L("status"), REDIS_REPLY_STATUS },
{ L("string"), REDIS_REPLY_STRING }
}

Definition at line 30 of file redis.c.

◆ redis_reply_types_len

size_t redis_reply_types_len = NUM_ELEMENTS(redis_reply_types)

Definition at line 38 of file redis.c.