The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
cluster_async.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: a33d8ea32ebe04e7a4e48da3dfab6c36ee4d1dca $
19 * @file cluster_async.c
20 * @brief conf functions for interacting asynchronously with Redis cluster via Hiredis.
21 *
22 * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 *
24 * @copyright 2026 Network RADIUS (legal@networkradius.com)
25 *
26 * Overview
27 * ========
28 *
29 * Read and understand this http://redis.io/topics/cluster-spec first, else the text below
30 * will not be useful.
31 *
32 * Using the cluster's public API
33 * ------------------------------
34 *
35 * The cluster requires use of a coordinator to fetch the cluster map.
36 *
37 * Any module using a Redis cluster should register a coordinator which uses
38 * the `redis` virtual server.
39 *
40 * In `mod_coord_attach`, #fr_redis_ct_map_bootstrap can be used to initiate
41 * the loading of the cluster map. Typically this should not be called if
42 * the pool start is set to zero.
43 * In that case, the first attempt to enqueue a command set will indicate
44 * that the cluster map need to be bootstrapped.
45 *
46 * At runtime the function #fr_redis_async_cmd_start is used to enqueue a set of
47 * commands, and the statis it returns should be checked with the macro
48 * REDIS_ASYNC_START_RCODE_PROCESS to initiate the cluster bootstrap or get
49 * an updated map if needed.
50 *
51 * With calling Redis using it's async API, the majority of results processing has to
52 * be done in a callback called by hiredis - the `redisReply` structure is freed
53 * after the callback is called.
54 *
55 * This callback is associated with the individual commands in a Redis command set
56 * as they are added to the command set with the fr_redis_command_*_add functions.
57 *
58 * Structures
59 * ----------
60 *
61 * This code maintains a series structures for efficient lookup and lockless operations.
62 *
63 * The important ones are:
64 * - An array of #fr_redis_cluster_node_t. These are pre-allocated on startup and are
65 * never added to, or removed from.
66 * - An #fr_fifo_t. This contains the queue of nodes that may be re-used.
67 * - An #fr_rb_tree_t. This contains a tree of nodes which are active. The tree is built on IP
68 * address and port.
69 *
70 * Each #fr_redis_cluster_node_t contains a master ID, and an array of slave IDs. The IDs are array
71 * indexes in the fr_redis_cluster_t.node array. We use 8bit unsigned integers instead of
72 * pointers to save space. Using pointers, the node[] array would need 784K, using IDs
73 * it uses 112K. Still not light on memory, but a bit more acceptable.
74 * Currently the key_slot array is shadowed by key_slot_pending, used to stage new key_slot
75 * mappings. This doubles the memory used. We may want to consider allocating key_slot_pending
76 * only during re-mappings and freeing it after.
77 *
78 * Mapping/Remapping the cluster
79 * -----------------------------
80 *
81 * On startup, and during cluster operation, a remap may be performed. A remap involves
82 * the following steps:
83 *
84 * 1. Request the cluster map from the coordinator.
85 * 2. The coordinator:
86 * a. Checks to see when it last fetched the cluster map. If it was less than 1 second ago,
87 * replies with the most recently fetched data.
88 * b. Executes the Redis 'cluster info' on all known nodes to establish which nodes believe
89 * they can see a working cluster, from the `cluster_state` response and which nodes have
90 * the most up to date representation of the cluster, from the `cluster_current_epoch`
91 * response.
92 * c. Nodes reporting the cluster is OK are issued the Redis 'cluster slots' or 'cluster shards'
93 * command depending on the Redis server version.
94 * d. Validating the result of this command. We need to do extensive validation to
95 * avoid SEGV on invalid data, due to the way libhiredis presents the result.
96 * e. Return the cluster map to the workers.
97 * 4. Determining the intersection between nodes described in the result, and those already
98 * in our #fr_rb_tree_t.
99 * 5. Creating trunk connections to nodes that were in the result, but not in the tree.
100 * 6. Mapping keyslot ranges to nodes in the key_slot_pending array.
101 * 7. Verifying there are no holes in the ranges (if there are, we roll back and error out).
102 * 8. Applying the new keyslot ranges.
103 * 9. Removing nodes no longer used by the key slots, and adding them back to the free
104 * nodes queue.
105 *
106 * #fr_redis_ct_map_get is used to request an updated map from the coordinator and
107 * #fr_redis_ct_map_update is used to process the message received from the coordinator to update
108 * the thread local copy of the cluster map.
109 *
110 * The cluster client can continue to operate, albeit inefficiently, with a stale cluster map
111 * by following '-ASK' and '-MOVE' redirects.
112 *
113 * Remaps are limited to one per second. If any operation sets the remap_needed flag, or
114 * attempts a remap directly, the remap may be skipped if one occurred recently.
115 *
116 *
117 * Processing '-ASK' and '-MOVE' redirects
118 * ---------------------------------------
119 *
120 * Resume functions which are run after an async Redis command set has completed should
121 * fetch the rcode with #fr_redis_command_set_rcode.
122 * If the rcode indicates MOVE or ASK, then #fr_redis_async_cmd_redirect should be used
123 * to re-enqueue the command set on the indicated node. In addition, if the response
124 * was MOVE, then #fr_redis_ct_map_get should be used to initiate a refresh of the cluster map.
125 *
126 * The data from '-MOVE' responses, is not used to alter the cluster map. That is only done
127 * on successful remap.
128 *
129 *
130 * Processing '-TRYAGAIN'
131 * ----------------------
132 *
133 * If the cluster is in a state of flux, a node may return '-TRYAGAIN' to indicated that we
134 * should attempt the operation again. #fr_redis_async_cmd_resend can be used to re-enqueue
135 * the command set.
136 *
137 */
138
139#include <freeradius-devel/util/debug.h>
140
141#include "config.h"
142#include "attrs.h"
143#include "cluster_async.h"
144#include "crc16.h"
145
146#ifndef WITH_TLS
147# undef HAVE_REDIS_SSL
148#endif
149
150#ifdef HAVE_REDIS_SSL
151#include <freeradius-devel/tls/strerror.h>
152#include <hiredis/hiredis_ssl.h>
153#endif
154
155#define MAX_REPLICAS 5 //!< Maximum number of replicas associated
156 //!< with a keyslot.
158 uint8_t replica[MAX_REPLICAS]; //!< Array of ids of replica nodes
159 uint8_t num_replicas; //!< Number of replica nodes
160 uint8_t master; //!< id of the master node.
161};
162
163typedef enum {
164 CLUSTER_INIT = 0, //!< Cluster has been initialised.
165 CLUSTER_MAP_FETCHING, //!< The cluster map is currently being fetched.
166 CLUSTER_READY, //!< The cluster is available to handle requests.
167 CLUSTER_FAIL, //!< The coordinator reported a failed cluster map update.
169
170/** Thread local state for a cluster
171 *
172 */
174 uint16_t cluster_id; //!< Number assigned to the cluster by coordinator.
176 trunk_conf_t const *tconf; //!< Configuration for all trunks in the cluster.
177 bool delay_start; //!< Prevent connections from spawning immediately.
178 fr_redis_conf_t const *conf; //!< Redis configuration for the cluster.
179 CONF_SECTION const *tls_cs; //!< TLS CONF_SECTION
180
181 fr_redis_trunk_active_t active; //!< Callback to run when the trunk becomes active.
182 void *active_uctx; //!< Uctx to pass to active callback.
183 bool active_oneshot; //!< Should the callback only be called once.
184
185#ifdef HAVE_REDIS_SSL
186 SSL_CTX *ssl_ctx; //!< SSL context.
187#endif
188
189 fr_redis_ct_node_t *node; //!< Array of nodes in this cluster.
190 fr_fifo_t *free_nodes; //!< Nodes not currently active.
191 fr_rb_tree_t *used_nodes; //!< Active nodes.
192
194
195 fr_redis_ct_state_t state; //!< State of the cluster.
196 fr_dlist_head_t pend_cmds; //!< Commands awaiting cluster map.
197 fr_dlist_head_t pend_reqs; //!< Requests awaiting cluster map.
198 fr_time_t map_updated; //!< Time the cluster last updated.
199};
200
202 fr_rb_node_t rbnode; //!< Entry in the tree of used nodes
203 char name[INET6_ADDRSTRLEN];
204 uint8_t id; //!< Array offset in the array of available nodes.
205
206 bool is_active; //!< Is this node currently active.
207 bool is_master; //!< Is this node currently a master.
208
209 fr_redis_ct_t *rtcluster; //!< Cluster this node belongs to
210 fr_redis_io_conf_t ioconf; //!< Connection config for this node.
211 fr_redis_trunk_t *trunk; //!< Trunk connection to this node.
212 fr_pair_list_t trigger_args; //!< Pairs to pass to trigger functions.
213};
214
215/** Structure for holding the state of an async redis command set.
216 *
217 */
219 request_t *request; //!< Request this command set relates to.
220 fr_redis_ct_t *rtcluster; //!< Cluster this command set is running on.
221 fr_redis_trunk_t *rtrunk; //!< Trunk the command set is currently running on.
222 fr_redis_command_set_t *cmds; //!< Command set to run.
223 uint8_t const *key; //!< Key used to identify key slot.
224 size_t key_len; //!< Length of key.
225 fr_redis_ct_key_slot_t const *key_slot; //!< Key slot identified from the command key.
226 bool read_only; //!< Should this command be run read only.
227 uint8_t replica_no; //!< Current replica number being used.
228 fr_dlist_t entry; //!< Entry in the list of commands waiting for a cluster remap.
229 fr_redis_ct_node_t *node; //!< Specific node to run command set on.
230};
231
232/** Structure to record that a request is waiting for the cluster map.
233 *
234 */
235typedef struct {
236 request_t *request; //!< The request waiting for the map.
237 fr_dlist_t entry; //!< Entry in the list of pending requests.
238 fr_redis_ct_t *rtcluster; //!< Cluster the request is waiting for.
240
241#define CONFIGURE_NODE(_node, _addr, _port) \
242do { \
243 _node->ioconf = (fr_redis_io_conf_t) { \
244 .port = _port, \
245 .database = rtcluster->conf->database, \
246 .username = rtcluster->conf->username, \
247 .password = rtcluster->conf->password, \
248 .use_tls = rtcluster->conf->use_tls, \
249 }; \
250 _node->ioconf.hostname = talloc_strdup(rtcluster, _addr); \
251 _node->ioconf.log_prefix = talloc_asprintf(rtcluster, "%s %s:%d", rtcluster->conf->log_prefix, \
252 _addr, _node->ioconf.port); \
253 if (rtcluster->conf->trunk_conf.conn_triggers) { \
254 module_trigger_args_build(rtcluster, &_node->trigger_args, NULL, \
255 &(module_trigger_args_t) { \
256 .module = rtcluster->conf->module_name, \
257 .name = rtcluster->conf->inst_name, \
258 .server = _addr, \
259 .port = _node->ioconf.port \
260 }); \
261 } \
262 _node->trunk = fr_redis_trunk_alloc(rtcluster, &_node->ioconf, &_node->trigger_args, rtcluster->active, \
263 rtcluster->active_uctx, rtcluster->active_oneshot); \
264 if (!_node->trunk) goto error; \
265} while (0)
266
267/** Resolve key to key slot index
268 *
269 * Identical to the example implementation, except it uses memchr which will
270 * be faster, and isn't so needlessly complex.
271 *
272 * @param[in] key to resolve.
273 * @param[in] key_len length of key.
274 * @return key slot index for the key.
275 */
276static uint16_t cluster_key_hash(uint8_t const *key, size_t key_len)
277{
278 uint8_t *p, *q;
279
280 p = memchr(key, '{', key_len);
281 if (!p) {
282 all:
283 return fr_crc16_xmodem(key, key_len) & (KEY_SLOTS - 1);
284 }
285
286 q = memchr(p, '}', key_len - (p - key)); /* look for } after { */
287 if (!q || (q == p + 1)) goto all; /* no } or {}, hash everything */
288
289 p++; /* skip '{' */
290
291 return fr_crc16_xmodem(p, q - p) & (KEY_SLOTS - 1); /* hash stuff between { and } */
292}
293
294/** Resolve key to key slot
295 *
296 * @param[in] rtcluster to resolve the key slot in.
297 * @param[in] request Current request (for debugging).
298 * @param[in] key to resolve.
299 * @param[in] key_len length of key.
300 * @return key slot for the key.
301 */
303 uint8_t const *key, size_t key_len)
304{
305 fr_redis_ct_key_slot_t *key_slot;
306
307 if (!key || (key_len == 0)) {
308 key_slot = &rtcluster->key_slot[(uint16_t)(fr_rand() & (KEY_SLOTS - 1))];
309 ROPTIONAL(RDEBUG2, DEBUG2, "Key rand() -> slot %zu", key_slot - rtcluster->key_slot);
310
311 return key_slot;
312 }
313
314 /*
315 * Avoid CRC16 if we're operating with one cluster node or
316 * without clustering.
317 */
318 if (fr_rb_num_elements(rtcluster->used_nodes) > 1) {
319 key_slot = &rtcluster->key_slot[cluster_key_hash(key, key_len)];
320 ROPTIONAL(RDEBUG2, DEBUG2, "Key \"%pV\" -> slot %zu",
321 fr_box_strvalue_len((char const *)key, key_len), key_slot - rtcluster->key_slot);
322
323 return key_slot;
324 }
325 ROPTIONAL(RDEBUG3, DEBUG3, "Single node available, skipping key selection");
326
327 return &rtcluster->key_slot[0];
328}
329
330/** Return the master node that would be used for a particular key slot
331 *
332 * @param[in] rtcluster To resolve key slot in.
333 * @param[in] key_slot to resolve to node.
334 * @return
335 * - The current master node.
336 * - NULL if no master node is currently assigned to a particular key slot.
337 */
339 fr_redis_ct_key_slot_t const *key_slot)
340{
341 return &rtcluster->node[key_slot->master];
342}
343
344/** Return the replica node that would be used for a particular key slot
345 *
346 * @param[in] rtcluster To resolve key slot in.
347 * @param[in] key_slot To resolve to node.
348 * @param[in] replica_num 0..n.
349 * @return
350 * - A replica node.
351 * - NULL if no replica node is assigned, or is at the specific key slot.
352 *
353 */
355 fr_redis_ct_key_slot_t const *key_slot, uint8_t replica_num)
356{
357 if (replica_num >= key_slot->num_replicas) return NULL; /* No replica available */
358
359 return &rtcluster->node[key_slot->replica[replica_num]];
360}
361
362/** Return the ipaddr of a particular node
363 *
364 * @param[in] node to get ip address from.
365 * @return
366 * - IP address of node
367 * - NULL on failure (node is NULL).
368 */
369char const * fr_redis_ct_ipaddr(fr_redis_ct_node_t const *node)
370{
371 if (!node) return NULL;
372
373 return node->ioconf.hostname;
374}
375
376/** Return the port of a particular node
377 *
378 * @param[out] out Port of the node.
379 * @param[in] node to get ip address from.
380 * @return
381 * - 0 on success.
382 * - -1 on failure (node is NULL).
383 */
385{
386 if (!node) return -1;
387
388 *out = node->ioconf.port;
389
390 return 0;
391}
392
393/** Enqueue a command set on a node identified by the key.
394 *
395 */
397{
398 fr_redis_ct_t *rtcluster = cmd->rtcluster;
400 bool dst_unavail = false;
401
402 if (likely(!cmd->node)) cmd->key_slot = fr_redis_ct_slot_by_key(rtcluster, cmd->request, cmd->key, cmd->key_len);
403
404 if (unlikely(cmd->node != NULL)) {
405 cmd->rtrunk = cmd->node->trunk;
406 }
407 /*
408 * Read only commands start on the first replica, if there are any.
409 */
410 else if (cmd->read_only && cmd->key_slot->num_replicas) {
411 cmd->rtrunk = rtcluster->node[cmd->key_slot->replica[0]].trunk;
412 } else {
413 cmd->rtrunk = rtcluster->node[cmd->key_slot->master].trunk;
414 }
415
416 if (unlikely(!cmd->rtrunk)) return REDIS_ASYNC_RCODE_ERROR;
417
418again:
419 ret = redis_command_set_enqueue(cmd->rtrunk, cmd->cmds);
420
421 switch (ret) {
423 /*
424 * If one or more nodes reported failed to enqueue with
425 * destination unavailable, tell the caller that the cluster
426 * map should be updated.
427 */
429
431 if (cmd->node) return REDIS_ASYNC_RCODE_ERROR;
432 dst_unavail = true;
433 if (cmd->replica_no < cmd->key_slot->num_replicas) {
434 cmd->rtrunk = rtcluster->node[cmd->key_slot->replica[cmd->replica_no]].trunk;
435 cmd->replica_no++;
436 goto again;
437 }
438 /*
439 * Read only commands can also try the master node.
440 * Non-read only first tried the master.
441 */
442 if (cmd->read_only && (cmd->rtrunk != rtcluster->node[cmd->key_slot->master].trunk)) {
443 cmd->rtrunk = rtcluster->node[cmd->key_slot->master].trunk;
444 goto again;
445 }
447
448 default:
450 }
451
452}
453
455{
456 if (!fr_dlist_entry_in_list(&cmd->entry)) return 0;
458 return 0;
459}
460
461/** Start running a command set on an async redis cluster
462 *
463 * @param ctx to allocate tracking structure.
464 * @param request current request.
465 * @param rcode Where to write the result code.
466 * @param rtcluster to start the command set on
467 * @param key to identify the cluster slot.
468 * @param key_len Length of key.
469 * @param cmds Command set to run.
470 * @param read_only Should the command set be run on read only nodes.
471 * @param node Specific node to run the command set on.
472 * @return The async redis command
473 */
475 fr_redis_ct_t *rtcluster, uint8_t const *key, size_t key_len,
476 fr_redis_command_set_t *cmds, bool read_only, fr_redis_ct_node_t *node)
477{
479
480 MEM(cmd = talloc(ctx, fr_redis_async_cmd_t));
481
482 *cmd = (fr_redis_async_cmd_t) {
483 .request = request,
484 .rtcluster = rtcluster,
485 .cmds = cmds,
486 .read_only = read_only,
487 .key = key,
488 .key_len = key_len,
489 .node = node,
490 };
491
492 switch (rtcluster->state) {
493 case CLUSTER_INIT:
494 /*
495 * If the cluster has not bootstrapped, that must be done first.
496 */
497 fr_dlist_insert_tail(&rtcluster->pend_cmds, cmd);
498 talloc_set_destructor(cmd, _fr_redis_async_cmd_free);
500 break;
501
503 fr_dlist_insert_tail(&rtcluster->pend_cmds, cmd);
504 talloc_set_destructor(cmd, _fr_redis_async_cmd_free);
506 break;
507
508 case CLUSTER_FAIL:
509 /*
510 * The coordinator reported a failed cluster.
511 */
512 *rcode = REDIS_ASYNC_RCODE_FAIL;
513 fr_strerror_const("Cluster failed");
514 talloc_free(cmd);
515 return NULL;
516
517 default:
518 *rcode = fr_redis_async_cmd_enqueue(cmd);
519 break;
520 }
521
522 return cmd;
523}
524
525/** Cancel a Redis async command.
526 *
527 */
532
533/** Fetch the redis trunk a command is associated with.
534 *
535 */
540
541/** Fetch the cluster node a command was last sent to
542 */
544 if (!cmd->key_slot) return NULL;
545 if (cmd->replica_no == 0) return &cmd->rtcluster->node[cmd->key_slot->master];
546 return &cmd->rtcluster->node[cmd->key_slot->replica[cmd->replica_no - 1]];
547}
548
549/** Re-submit a redis async command set on a different node
550 *
551 * Using the node returned by a MOVED / ASK response.
552 * @param cmd Async command set to redirect
553 * @return fr_redis_async_rcode_t
554 */
556{
557 fr_redis_ct_node_t find, *cluster_node;
558
560
561 fr_rb_find((void **)&cluster_node, cmd->rtcluster->used_nodes, &find);
562 if (!cluster_node) {
563 ERROR("Asked to redirect to a node not in the current cluster map");
565 }
566
568 cmd->node = cluster_node;
569 return fr_redis_async_cmd_enqueue(cmd);
570}
571
572/** Re-submit a redis async command set
573 *
574 * To be used following TRYAGAIN responses
575 * @param cmd Async command set to redirect
576 * @return fr_redis_async_rcode_t
577 */
583
584/** Compare two redis nodes to check equality
585 *
586 * @param[in] one first node.
587 * @param[in] two second node.
588 * @return CMP(one, two)
589 */
590static fr_cmp_ret_t _cluster_thread_node_cmp(void const *one, void const *two)
591{
592 fr_redis_ct_node_t const *a = one;
593 fr_redis_ct_node_t const *b = two;
594 int ret;
595
596 ret = strcmp(a->ioconf.hostname, b->ioconf.hostname);
597 if (ret != 0) return CMP(ret, 0);
598
599 return CMP(a->ioconf.port, b->ioconf.port);
600}
601
602#ifdef HAVE_REDIS_SSL
603static int _redis_cluster_thread_free(fr_redis_ct_t *rtcluster)
604{
605 if (rtcluster->ssl_ctx) SSL_CTX_free(rtcluster->ssl_ctx);
606 return 0;
607}
608#endif
609
610/** Allocate per-thread, per-cluster instance
611 *
612 * This structure represents all the connections for a given thread for a given cluster.
613 * The structures holds the trunk connections to talk to each cluster member.
614 *
615 */
617 fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
618{
619 fr_redis_ct_t *rtcluster;
620 trunk_conf_t *our_tconf;
621 uint8_t i;
622 uint32_t s, num_nodes;
623
624 MEM(rtcluster = talloc_zero(ctx, fr_redis_ct_t));
625 *rtcluster = (fr_redis_ct_t) {
626 .el = el,
627 .conf = conf,
628 .tls_cs = tls_cs,
629 .active = active,
630 .active_uctx = active_uctx,
631 .active_oneshot = active_oneshot
632 };
633 MEM(our_tconf = talloc_memdup(rtcluster, &conf->trunk_conf, sizeof(conf->trunk_conf)));
634 our_tconf->always_writable = true;
635
636 rtcluster->tconf = our_tconf;
639
640 if (conf->max_nodes == UINT8_MAX) {
641 ERROR("%s - Maximum number of connected nodes allowed is %i", conf->log_prefix, UINT8_MAX - 1);
642 error:
643 talloc_free(rtcluster);
644 return NULL;
645 }
646
647 if (conf->max_nodes == 0) {
648 ERROR("%s - Minimum number of nodes allowed is 1", conf->log_prefix);
649 goto error;
650 }
651
652 MEM(rtcluster->node = talloc_zero_array(rtcluster, fr_redis_ct_node_t, conf->max_nodes + 1));
653 MEM(rtcluster->used_nodes = fr_rb_inline_alloc(rtcluster, fr_redis_ct_node_t, rbnode, _cluster_thread_node_cmp, NULL));
654 MEM(rtcluster->free_nodes = fr_fifo_create(rtcluster, conf->max_nodes, NULL));
655
656 /*
657 * Node id 0 is reserved, so we can detect misconfigured
658 * clusters.
659 */
660 for (i = 1; i <= conf->max_nodes; i++) {
661 rtcluster->node[i].id = i;
662 rtcluster->node[i].rtcluster = rtcluster;
663 fr_pair_list_init(&rtcluster->node[i].trigger_args);
664
665 /* Push them all into the queue */
666 fr_fifo_push(rtcluster->free_nodes, &rtcluster->node[i]);
667 }
668
669 if (conf->use_tls) {
670#ifdef HAVE_REDIS_SSL
671 fr_tls_conf_t *tls_conf;
672 if (!tls_cs) {
673 ERROR("%s - Missing TLS configuration", conf->log_prefix);
674 goto error;
675 }
676
677 tls_conf = fr_tls_conf_parse_client(tls_cs);
678 if (!tls_conf) {
679 ERROR("%s - Failed to parse TLS configuration", conf->log_prefix);
680 goto error;
681 }
682
683 rtcluster->ssl_ctx = fr_tls_ctx_alloc(tls_conf, true);
684 if (!rtcluster->ssl_ctx) {
685 ERROR("%s - Failed to allocate SSL context", conf->log_prefix);
686 goto error;
687 }
688 talloc_set_destructor(rtcluster, _redis_cluster_thread_free);
689#else
690 WARN("%s - No redis SSL support, ignoring \"use_tls = yes\"", conf->log_prefix);
691#endif
692 }
693
694 if (conf->use_cluster_map) return rtcluster;
695
696 /*
697 * If we are not using a cluster map, just configure nodes from
698 * the bootstrap list and distribute them through the key slots.
699 */
700 for (s = 0; s < talloc_array_length(conf->hostname); s++) {
701 fr_redis_ct_node_t *cluster_node;
702 fr_ipaddr_t addr;
703 uint16_t port;
705
706 cluster_node = fr_fifo_pop(rtcluster->free_nodes);
707 if (!cluster_node) {
708 ERROR("Reached maximum connected nodes");
709 goto error;
710 }
711 if (fr_inet_pton_port(&addr, &port,
712 conf->hostname[s], talloc_strlen(conf->hostname[s]), AF_UNSPEC, true, true) < 0) {
713 PERROR("Failed parsing %s", conf->hostname[s]);
714 goto error;
715 }
716 if (port == 0) port = conf->port;
717 fr_inet_ntop(buff, sizeof(buff), &addr);
718 CONFIGURE_NODE(cluster_node, buff, port);
719 fr_rb_insert(rtcluster->used_nodes, cluster_node);
720 cluster_node->is_active = true;
721 cluster_node->is_master = true;
722 }
723
724 num_nodes = fr_rb_num_elements(rtcluster->used_nodes);
725 if (!num_nodes) {
726 ERROR("%s - No bootstrap servers configured", conf->log_prefix);
727 goto error;
728 }
729
730 for (s = 0; s < KEY_SLOTS; s++) rtcluster->key_slot[s].master = (s % (uint16_t) num_nodes) + 1;
731
732 rtcluster->state = CLUSTER_READY;
733
734 return rtcluster;
735}
736
738{
739 return rtcluster->el;
740}
741
743{
744 return rtcluster->tconf;
745}
746
747#ifdef HAVE_REDIS_SSL
748SSL_CTX *fr_redis_ct_ssl_ctx(fr_redis_ct_t *rtcluster)
749{
750 return rtcluster->ssl_ctx;
751}
752#endif
753
754/** Update a Redis cluster map from a pair list returned from a coordinator
755 *
756 * @param rtcluster Cluster to update
757 * @param list pairs sent by a coordinator
758 * @return
759 * - 0 om success
760 * - -1 on error
761 */
763{
764 fr_pair_t *vp, *shard = NULL, *slot, *start, *end, *node, *role, *node_ip, *node_port;
765 uint16_t i;
766 uint8_t r = 0;
767 uint8_t rollback[UINT8_MAX]; // Set of nodes to re-add to the queue on failure.
768 bool active[UINT8_MAX]; // Set of nodes active in the new cluster map.
769 bool master[UINT8_MAX]; // Master nodes.
770
771 fr_redis_ct_node_t find, *cluster_node;
772 fr_redis_ct_key_slot_t tmp_slot;
773 fr_redis_ct_key_slot_t key_slot_pending[KEY_SLOTS];
775 fr_redis_ct_pend_req_t *pend_req;
776
777#define SET_INACTIVE(_node) \
778do { \
779 (_node)->is_active = false; \
780 (_node)->is_master = false; \
781 talloc_const_free((_node)->ioconf.log_prefix); \
782 (_node)->ioconf.log_prefix = NULL; \
783 TALLOC_FREE((_node)->trunk); \
784 fr_pair_list_free(&(_node)->trigger_args); \
785 fr_rb_delete(rtcluster->used_nodes, _node); \
786 fr_fifo_push(rtcluster->free_nodes, _node); \
787} while (0)
788
789#define SET_ACTIVE(_node) \
790do { \
791 fr_rb_insert(rtcluster->used_nodes, _node); \
792 fr_fifo_pop(rtcluster->free_nodes); \
793 (_node)->is_active = true; \
794 active[(_node)->id] = true; \
795 rollback[r++] = (_node)->id; \
796} while (0)
797
799 if (unlikely(!vp)) {
800 ERROR("Missing cluster ID");
801 return -1;
802 }
803 if (rtcluster->cluster_id == 0) rtcluster->cluster_id = vp->vp_uint16;
804
805 if (rtcluster->cluster_id != vp->vp_uint16) {
806 ERROR("Got map for cluster ID %d, expected ID %d", vp->vp_uint16, rtcluster->cluster_id);
807 return -1;
808 }
809
810 DEBUG3("Updating cluster %d", rtcluster->cluster_id);
811
812 memset(&key_slot_pending, 0, sizeof(key_slot_pending));
813 memset(active, 0, sizeof(active));
814 memset(master, 0, sizeof(master));
815
816 while ((shard = fr_pair_find_by_da(list, shard, attr_redis_shard))) {
817 cluster_node = NULL;
818 memset(&tmp_slot, 0, sizeof(fr_redis_ct_key_slot_t));
819 node = NULL;
820 while ((node = fr_pair_find_by_da(&shard->vp_group, node, attr_redis_node))) {
821 role = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_role);
822 if (unlikely(!role)) continue;
823 if (role->vp_uint8 == 1) {
824 DEBUG3("Master node %pP", node);
825
826 node_ip = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_endpoint);
827 if (unlikely(!node_ip)) continue;
828 find.ioconf.hostname = node_ip->vp_strvalue;
829 node_port = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_port);
830 if (unlikely(!node_port)) continue;
831 find.ioconf.port = node_port->vp_uint16;
832
833 fr_rb_find((void **)&cluster_node, rtcluster->used_nodes, &find);
834 break;
835 }
836 }
837
838 if (!node) {
839 ERROR("Missing master node");
840 error:
841 for (i = 0; i < r; i++) SET_INACTIVE(&rtcluster->node[rollback[i]]);
842 return -1;
843 }
844
845 if (!cluster_node) {
846 cluster_node = fr_fifo_peek(rtcluster->free_nodes);
847 if (!cluster_node) {
848 out_of_nodes:
849 fr_strerror_const("Reached maximum connected nodes");
850 goto error;
851 }
852 CONFIGURE_NODE(cluster_node, find.ioconf.hostname, find.ioconf.port);
853 SET_ACTIVE(cluster_node);
854 } else {
855 active[cluster_node->id] = true;
856 }
857 master[cluster_node->id] = true;
858 tmp_slot.master = cluster_node->id;
859
860 node = NULL;
861 while ((node = fr_pair_find_by_da(&shard->vp_group, node, attr_redis_node))) {
862 role = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_role);
863 if (tmp_slot.num_replicas >= MAX_REPLICAS) break;
864 if (role->vp_uint8 != 2) continue;
865
866 DEBUG3("Replica node %pP", node);
867 node_ip = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_endpoint);
868 if (unlikely(!node_ip)) continue;
869 find.ioconf.hostname = node_ip->vp_strvalue;
870 node_port = fr_pair_find_by_da(&node->vp_group, NULL, attr_redis_node_port);
871 if (unlikely(!node_port)) continue;
872 find.ioconf.port = node_port->vp_uint16;
873
874 fr_rb_find((void **)&cluster_node, rtcluster->used_nodes, &find);
875
876 if (cluster_node) {
877 tmp_slot.replica[tmp_slot.num_replicas++] = cluster_node->id;
878 active[cluster_node->id] = true;
879 continue;
880 }
881
882 cluster_node = fr_fifo_peek(rtcluster->free_nodes);
883 if (!cluster_node) goto out_of_nodes;
884
885 CONFIGURE_NODE(cluster_node, find.ioconf.hostname, find.ioconf.port);
886 tmp_slot.replica[tmp_slot.num_replicas++] = cluster_node->id;
887 SET_ACTIVE(cluster_node);
888 }
889
890 slot = NULL;
891 while ((slot = fr_pair_find_by_da(&shard->vp_group, slot, attr_redis_slot))) {
892 start = fr_pair_find_by_da(&slot->vp_group, NULL, attr_redis_slot_start);
893 if (unlikely(!start)) {
894 ERROR("Missing slot start");
895 goto error;
896 }
897 if (unlikely(start->vp_uint16 >= KEY_SLOTS)) {
898 ERROR("Value of %d for slot start greater than expected maximum %d",
899 start->vp_uint16, KEY_SLOTS);
900 goto error;
901 }
902 end = fr_pair_find_by_da(&slot->vp_group, NULL, attr_redis_slot_end);
903 if (unlikely(!end)) {
904 ERROR("Missing slot end");
905 goto error;
906 }
907 if (unlikely(end->vp_uint16 >= KEY_SLOTS)) {
908 ERROR("Value of %d for slot end greater than expected maximum %d",
909 end->vp_uint16, KEY_SLOTS);
910 goto error;
911 }
912 if (unlikely(end->vp_uint16 < start->vp_uint16)) {
913 ERROR("Value of %d for slot end less than value of %d for slot start",
914 end->vp_uint16, start->vp_uint16);
915 goto error;
916 }
917 DEBUG4("Setting nodes for slots %d to %d", start->vp_uint16, end->vp_uint16);
918 for (i = start->vp_uint16; i <= end->vp_uint16; i++) {
919 memcpy(&key_slot_pending[i], &tmp_slot, sizeof(*key_slot_pending));
920 }
921 }
922 }
923
924 memcpy(&rtcluster->key_slot, &key_slot_pending, sizeof(rtcluster->key_slot));
925
926 /*
927 * Anything not in the active set of nodes gets
928 * added back into the queue, to be re-used.
929 *
930 * We start at 1, as node 0 is reserved.
931 */
932 for (i = 1; i <= rtcluster->conf->max_nodes; i++) {
933#ifndef NDEBUG
934 fr_redis_ct_node_t *found;
935
936 if (rtcluster->node[i].is_active) {
937 /* Sanity check for duplicates that are active */
938 fr_rb_find((void **)&found, rtcluster->used_nodes, &rtcluster->node[i]);
939 fr_assert(found);
940 fr_assert(found->is_active);
941 fr_assert(found->id == i);
942 }
943#endif
944
945 if (!active[i] && rtcluster->node[i].is_active) {
946 SET_INACTIVE(&rtcluster->node[i]);
947
948 /*
949 * Only change the masters once we've successfully
950 * remapped the cluster.
951 */
952 } else if (master[i]) {
953 rtcluster->node[i].is_master = true;
954 } else {
955 rtcluster->node[i].is_master = false;
956 }
957 }
958
959 rtcluster->state = CLUSTER_READY;
960 rtcluster->map_updated = fr_time();
961
962 /*
963 * Enqueue any commands which were waiting for the cluster remap.
964 */
965 while ((cmd = fr_dlist_pop_head(&rtcluster->pend_cmds))) {
967 }
968
969 /*
970 * Resume any requests which were waiting for the cluster remap.
971 */
972 while ((pend_req = fr_dlist_pop_head(&rtcluster->pend_reqs))) {
974 talloc_free(pend_req);
975 }
976
977 return 0;
978}
979
980/** Process a cluster map fail message from the coordinator.
981 *
982 * @param rtcluster Cluster to update
983 * @param list pairs sent by a coordinator
984 */
986{
987 DEBUG3("Cluster %d failed", rtcluster->cluster_id);
988 rtcluster->state = CLUSTER_FAIL;
989 return 0;
990}
991
992/** Initiate bootstrapping of the cluster map
993 *
994 * To be used when a module first wants to fetch a cluster map
995 *
996 * @param rtcluster Cluster to fetch map for
997 * @param cw Coord worker to launch request
998 * @param coord_pair_reg Coord pair registration
999 * @return
1000 * - 0 on success.
1001 * - -1 on failure.
1002 */
1004{
1005 fr_redis_conf_t const *conf = rtcluster->conf;
1006 fr_pair_list_t list;
1007 fr_pair_t *vp;
1008 TALLOC_CTX *local = talloc_new(NULL);
1009 int ret;
1010 size_t i;
1011
1012 fr_pair_list_init(&list);
1014 if (!vp) {
1015 error:
1016 talloc_free(local);
1017 return -1;
1018 }
1019
1020 if (fr_pair_append_by_da(local, &vp, &list, attr_redis_log_prefix) < 0) goto error;
1021 if (fr_value_box_strdup(vp, &vp->data, NULL, conf->log_prefix, false) < 0) goto error;
1022
1023 fr_pair_list_append_by_da(local, vp, &list, attr_redis_max_nodes, conf->max_nodes, false);
1024 if (!vp) goto error;
1025
1026 for (i = 0; i < talloc_array_length(conf->hostname); i++) {
1027 if (fr_pair_append_by_da(local, &vp, &list, attr_redis_bootstrap_node) < 0) goto error;
1028 if (fr_value_box_strdup(vp, &vp->data, NULL, conf->hostname[i], false) < 0) goto error;
1029 }
1030
1031 fr_pair_list_append_by_da(local, vp, &list, attr_redis_bootstrap_port, conf->port, false);
1032 if (!vp) goto error;
1033
1034 if (conf->password) {
1035 if (fr_pair_append_by_da(local, &vp, &list, attr_redis_password) < 0) goto error;
1036 if (fr_value_box_strdup(vp, &vp->data, NULL, conf->password, false) < 0) goto error;
1037 if (conf->username) {
1038 if (fr_pair_append_by_da(local, &vp, &list, attr_redis_username) < 0) goto error;
1039 if (fr_value_box_strdup(vp, &vp->data, NULL, conf->username, false) < 0) goto error;
1040 }
1041 }
1042
1043 if (conf->use_tls) {
1044 uintptr_t tls_conf = (uintptr_t)rtcluster->tls_cs;
1045 fr_pair_list_append_by_da(local, vp, &list, attr_redis_use_tls, false, false);
1046 if (!vp) goto error;
1047 fr_pair_list_append_by_da(local, vp, &list, attr_redis_tls_conf, (uint64_t)tls_conf, false);
1048 if (!vp) goto error;
1049 }
1050
1051 ret = fr_worker_to_coord_pair_send(cw, coord_pair_reg, &list);
1052 talloc_free(local);
1053
1054 if (ret < 0) return -1;
1055 rtcluster->state = CLUSTER_MAP_FETCHING;
1056
1057 return 0;
1058}
1059
1060/** Initiate updating of the cluster map
1061 *
1062 * To be used when a command returns MOVED
1063 */
1065 fr_coord_pair_reg_t *coord_pair_reg, bool force)
1066{
1067 fr_pair_list_t list;
1068 fr_pair_t *vp;
1069 TALLOC_CTX *local;
1070 int ret;
1071
1072 if (rtcluster->cluster_id == 0) return REDIS_ASYNC_RCODE_BOOTSTRAP;
1073
1074 /*
1075 * The update request has already been sent.
1076 */
1077 if ((rtcluster->state == CLUSTER_MAP_FETCHING) && !force) return REDIS_ASYNC_RCODE_SUCCESS;
1078
1079 /*
1080 * If the cluster was updated less than 1 sec ago, don't ask.
1081 */
1082 if (((fr_time_to_sec(fr_time()) == fr_time_to_sec(rtcluster->map_updated))) && !force) return REDIS_ASYNC_RCODE_SUCCESS;
1083
1084 DEBUG3("Requesting updated map for cluster %d", rtcluster->cluster_id);
1085
1086 local = talloc_new(NULL);
1087 fr_pair_list_init(&list);
1089 if (!vp) {
1090 error:
1091 talloc_free(local);
1093 }
1094
1095 fr_pair_list_append_by_da(local, vp, &list, attr_redis_cluster_id, rtcluster->cluster_id, false);
1096 if (!vp) goto error;
1097
1098 if (force) {
1099 fr_pair_list_append_by_da(local, vp, &list, attr_redis_force_update, true, false);
1100 }
1101
1102 ret = fr_worker_to_coord_pair_send(cw, coord_pair_reg, &list);
1103 talloc_free(local);
1104
1105 if (ret < 0) return REDIS_ASYNC_RCODE_ERROR;
1106 rtcluster->state = CLUSTER_MAP_FETCHING;
1107
1109}
1110
1112{
1113 fr_redis_ct_node_t find, *found;
1114
1115 find.ioconf.hostname = ioconf->hostname;
1116 find.ioconf.port = ioconf->port;
1117 fr_rb_find((void **)&found, rtcluster->used_nodes, &find);
1118 return found;
1119}
1120
1122 fr_redis_ct_t *rtcluster, bool is_master, bool is_replica)
1123{
1124 uint64_t in_use = fr_rb_num_elements(rtcluster->used_nodes);
1126 fr_redis_ct_node_t *node;
1127 uint8_t count = 0;
1128 fr_redis_io_conf_t *found;
1129
1130 switch (rtcluster->state) {
1131 case CLUSTER_INIT:
1133
1136
1137 default:
1138 break;
1139 }
1140
1141 if (in_use == 0) {
1142 *out = NULL;
1143 *count_out = 0;
1145 }
1146
1147 found = talloc_zero_array(ctx, fr_redis_io_conf_t, in_use);
1148 if (!found) {
1149 fr_strerror_const("Out of memory");
1151 }
1152
1153 for (node = fr_rb_iter_init_inorder(rtcluster->used_nodes, &iter);
1154 node;
1155 node = fr_rb_iter_next_inorder(rtcluster->used_nodes, &iter)) {
1156 if ((is_master && node->is_master) || (is_replica && !node->is_master)) found[count++] = node->ioconf;
1157 }
1158
1159 if (count == 0) {
1160 *out = NULL;
1161 talloc_free(found);
1162 } else {
1163 *out = found;
1164 }
1165 *count_out = count;
1167}
1168
1169/** Ensure pending request is removed from the list on freeing.
1170 */
1172{
1173 if (!fr_dlist_entry_in_list(&pend_req->entry)) return 0;
1174 fr_dlist_remove(&pend_req->rtcluster->pend_reqs, pend_req);
1175 return 0;
1176}
1177
1178/** Add a request to the list of those waiting for the cluster map
1179 *
1180 */
1181void fr_redis_ct_request_yield(TALLOC_CTX *ctx, fr_redis_ct_t *rtcluster, request_t *request)
1182{
1183 fr_redis_ct_pend_req_t *pend_req;
1184
1185 MEM(pend_req = talloc_zero(ctx, fr_redis_ct_pend_req_t));
1186 pend_req->request = request;
1187 pend_req->rtcluster = rtcluster;
1188 fr_dlist_insert_tail(&rtcluster->pend_reqs, pend_req);
1189 talloc_set_destructor(pend_req, _fr_redis_ct_pend_req_free);
1190}
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:113
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
fr_redis_conf_t const * conf
Redis configuration for the cluster.
static int _fr_redis_async_cmd_free(fr_redis_async_cmd_t *cmd)
fr_redis_ct_t * rtcluster
Cluster the request is waiting for.
fr_redis_ct_state_t state
State of the cluster.
#define SET_ACTIVE(_node)
bool read_only
Should this command be run read only.
void fr_redis_ct_request_yield(TALLOC_CTX *ctx, fr_redis_ct_t *rtcluster, request_t *request)
Add a request to the list of those waiting for the cluster map.
int fr_redis_ct_map_bootstrap(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg)
Initiate bootstrapping of the cluster map.
request_t * request
The request waiting for the map.
fr_rb_tree_t * used_nodes
Active nodes.
request_t * request
Request this command set relates to.
bool active_oneshot
Should the callback only be called once.
size_t key_len
Length of key.
fr_redis_async_cmd_t * fr_redis_async_cmd_start(TALLOC_CTX *ctx, request_t *request, fr_redis_async_rcode_t *rcode, fr_redis_ct_t *rtcluster, uint8_t const *key, size_t key_len, fr_redis_command_set_t *cmds, bool read_only, fr_redis_ct_node_t *node)
Start running a command set on an async redis cluster.
uint16_t cluster_id
Number assigned to the cluster by coordinator.
static uint16_t cluster_key_hash(uint8_t const *key, size_t key_len)
Resolve key to key slot index.
trunk_conf_t const * tconf
Configuration for all trunks in the cluster.
fr_redis_async_rcode_t fr_redis_ct_node_addr_by_role(TALLOC_CTX *ctx, fr_redis_io_conf_t *out[], uint8_t *count_out, fr_redis_ct_t *rtcluster, bool is_master, bool is_replica)
fr_dlist_head_t pend_reqs
Requests awaiting cluster map.
trunk_conf_t const * fr_redis_ct_trunk_conf(fr_redis_ct_t *rtcluster)
fr_redis_trunk_t * rtrunk
Trunk the command set is currently running on.
fr_redis_ct_node_t * node
Array of nodes in this cluster.
fr_redis_async_rcode_t fr_redis_ct_map_get(fr_redis_ct_t *rtcluster, fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg, bool force)
Initiate updating of the cluster map.
fr_dlist_t entry
Entry in the list of pending requests.
fr_redis_ct_node_t * node
Specific node to run command set on.
void fr_redis_async_cmd_cancel(fr_redis_async_cmd_t *cmd)
Cancel a Redis async command.
uint8_t num_replicas
Number of replica nodes.
fr_redis_ct_key_slot_t key_slot[KEY_SLOTS]
fr_redis_ct_node_t * fr_redis_ct_node_by_addr(fr_redis_ct_t *rtcluster, fr_redis_io_conf_t *ioconf)
fr_redis_async_rcode_t fr_redis_async_cmd_resend(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set.
fr_redis_trunk_t * trunk
Trunk connection to this node.
#define MAX_REPLICAS
Maximum number of replicas associated with a keyslot.
fr_redis_ct_node_t const * fr_redis_ct_replica(fr_redis_ct_t *rtcluster, fr_redis_ct_key_slot_t const *key_slot, uint8_t replica_num)
Return the replica node that would be used for a particular key slot.
fr_redis_ct_node_t * fr_redis_async_cmd_node(fr_redis_async_cmd_t *cmd)
Fetch the cluster node a command was last sent to.
fr_redis_trunk_t * fr_redis_async_cmd_trunk(fr_redis_async_cmd_t *cmd)
Fetch the redis trunk a command is associated with.
#define SET_INACTIVE(_node)
fr_redis_ct_key_slot_t const * fr_redis_ct_slot_by_key(fr_redis_ct_t *rtcluster, request_t *request, uint8_t const *key, size_t key_len)
Resolve key to key slot.
CONF_SECTION const * tls_cs
TLS CONF_SECTION.
fr_rb_node_t rbnode
Entry in the tree of used nodes.
#define CONFIGURE_NODE(_node, _addr, _port)
fr_pair_list_t trigger_args
Pairs to pass to trigger functions.
fr_redis_ct_key_slot_t const * key_slot
Key slot identified from the command key.
static int _fr_redis_ct_pend_req_free(fr_redis_ct_pend_req_t *pend_req)
Ensure pending request is removed from the list on freeing.
fr_redis_async_rcode_t fr_redis_async_cmd_redirect(fr_redis_async_cmd_t *cmd)
Re-submit a redis async command set on a different node.
uint8_t master
id of the master node.
uint8_t replica_no
Current replica number being used.
fr_redis_ct_t * rtcluster
Cluster this command set is running on.
fr_time_t map_updated
Time the cluster last updated.
fr_redis_ct_t * fr_redis_ct_alloc(TALLOC_CTX *ctx, CONF_SECTION *tls_cs, fr_event_list_t *el, fr_redis_conf_t *conf, fr_redis_trunk_active_t active, void *active_uctx, bool active_oneshot)
Allocate per-thread, per-cluster instance.
fr_redis_ct_state_t
@ CLUSTER_FAIL
The coordinator reported a failed cluster map update.
@ CLUSTER_MAP_FETCHING
The cluster map is currently being fetched.
@ CLUSTER_READY
The cluster is available to handle requests.
@ CLUSTER_INIT
Cluster has been initialised.
static fr_cmp_ret_t _cluster_thread_node_cmp(void const *one, void const *two)
Compare two redis nodes to check equality.
uint8_t replica[MAX_REPLICAS]
Array of ids of replica nodes.
uint8_t const * key
Key used to identify key slot.
char name[INET6_ADDRSTRLEN]
fr_redis_ct_t * rtcluster
Cluster this node belongs to.
fr_fifo_t * free_nodes
Nodes not currently active.
int fr_redis_ct_port(uint16_t *out, fr_redis_ct_node_t const *node)
Return the port of a particular node.
bool is_master
Is this node currently a master.
fr_redis_ct_node_t const * fr_redis_ct_master(fr_redis_ct_t *rtcluster, fr_redis_ct_key_slot_t const *key_slot)
Return the master node that would be used for a particular key slot.
fr_redis_command_set_t * cmds
Command set to run.
bool delay_start
Prevent connections from spawning immediately.
fr_event_list_t * fr_redis_ct_el(fr_redis_ct_t *rtcluster)
fr_event_list_t * el
bool is_active
Is this node currently active.
fr_dlist_t entry
Entry in the list of commands waiting for a cluster remap.
fr_redis_trunk_active_t active
Callback to run when the trunk becomes active.
fr_dlist_head_t pend_cmds
Commands awaiting cluster map.
int fr_redis_ct_map_update(fr_redis_ct_t *rtcluster, fr_pair_list_t const *list)
Update a Redis cluster map from a pair list returned from a coordinator.
void * active_uctx
Uctx to pass to active callback.
int fr_redis_ct_map_fail(fr_redis_ct_t *rtcluster, UNUSED fr_pair_list_t const *list)
Process a cluster map fail message from the coordinator.
char const * fr_redis_ct_ipaddr(fr_redis_ct_node_t const *node)
Return the ipaddr of a particular node.
uint8_t id
Array offset in the array of available nodes.
fr_redis_io_conf_t ioconf
Connection config for this node.
static fr_redis_async_rcode_t fr_redis_async_cmd_enqueue(fr_redis_async_cmd_t *cmd)
Enqueue a command set on a node identified by the key.
Structure for holding the state of an async redis command set.
Structure to record that a request is waiting for the cluster map.
Thread local state for a cluster.
Redis asynchronous cluster management.
#define KEY_SLOTS
Maximum number of keyslots (should not change).
struct fr_redis_async_cmd_s fr_redis_async_cmd_t
The worker end of worker <-> coordinator communication.
Definition coord.c:73
int fr_worker_to_coord_pair_send(fr_coord_worker_t *cw, fr_coord_pair_reg_t *coord_pair_reg, fr_pair_list_t *list)
Send a pair list from a worker to a coordinator.
Definition coord_pair.c:820
struct fr_coord_pair_reg_s fr_coord_pair_reg_t
Definition coord_pair.h:32
uint16_t fr_crc16_xmodem(uint8_t const *in, size_t in_len)
CRC16 implementation according to CCITT standards.
Definition crc16.c:91
#define MEM(x)
Definition debug.h:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:620
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
Definition dlist.h:145
static void * fr_dlist_pop_head(fr_dlist_head_t *list_head)
Remove the head item in a list.
Definition dlist.h:654
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:360
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:257
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
void * fr_fifo_peek(fr_fifo_t *fi)
Examine the next element that would be popped.
Definition fifo.c:158
int fr_fifo_push(fr_fifo_t *fi, void *data)
Push data onto the fifo.
Definition fifo.c:111
void * fr_fifo_pop(fr_fifo_t *fi)
Pop data off of the fifo.
Definition fifo.c:135
#define fr_fifo_create(_ctx, _max_entries, _node_free)
Creates a fifo.
Definition fifo.h:66
talloc_free(hp)
int fr_inet_pton_port(fr_ipaddr_t *out, uint16_t *port_out, char const *value, ssize_t inlen, int af, bool resolve, bool mask)
Parses IPv4/6 address + port, to fr_ipaddr_t and integer (port)
Definition inet.c:944
char * fr_inet_ntop(char out[static FR_IPADDR_STRLEN], size_t outlen, fr_ipaddr_t const *addr)
Print the address portion of a fr_ipaddr_t.
Definition inet.c:1025
#define FR_IPADDR_STRLEN
Like INET6_ADDRSTRLEN but includes space for the textual Zone ID.
Definition inet.h:89
IPv4/6 prefix.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:2008
fr_dict_attr_t const * attr_redis_node_role
Definition redis.c:74
fr_dict_attr_t const * attr_redis_bootstrap_node
Definition redis.c:62
fr_dict_attr_t const * attr_redis_use_tls
Definition redis.c:76
fr_dict_attr_t const * attr_redis_slot_end
Definition redis.c:70
fr_dict_attr_t const * attr_redis_force_update
Definition redis.c:75
fr_dict_attr_t const * attr_redis_packet_type
Definition redis.c:59
fr_dict_attr_t const * attr_redis_log_prefix
Definition redis.c:60
fr_dict_attr_t const * attr_redis_node
Definition redis.c:71
fr_dict_attr_t const * attr_redis_node_port
Definition redis.c:73
fr_dict_attr_t const * attr_redis_slot
Definition redis.c:68
fr_dict_attr_t const * attr_redis_slot_start
Definition redis.c:69
fr_dict_attr_t const * attr_redis_max_nodes
Definition redis.c:61
fr_dict_attr_t const * attr_redis_password
Definition redis.c:65
fr_dict_attr_t const * attr_redis_bootstrap_port
Definition redis.c:63
fr_dict_attr_t const * attr_redis_cluster_id
Definition redis.c:66
fr_dict_attr_t const * attr_redis_tls_conf
Definition redis.c:77
fr_dict_attr_t const * attr_redis_username
Definition redis.c:64
fr_dict_attr_t const * attr_redis_node_endpoint
Definition redis.c:72
fr_dict_attr_t const * attr_redis_shard
Definition redis.c:67
char const * hostname
Definition io.h:51
uint16_t port
Definition io.h:52
#define PERROR(_fmt,...)
Definition log.h:233
#define DEBUG3(_fmt,...)
Definition log.h:271
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:545
#define RDEBUG3(fmt,...)
Definition log.h:360
#define DEBUG4(_fmt,...)
Definition log.h:272
#define fr_time()
Definition event.c:60
Stores all information relating to an event list.
Definition event.c:377
unsigned short uint16_t
unsigned int uint32_t
unsigned char uint8_t
#define UINT8_MAX
fr_cmp_ret_t
Result of an ordering comparison.
Definition misc.h:50
int fr_pair_append_by_da(TALLOC_CTX *ctx, fr_pair_t **out, fr_pair_list_t *list, fr_dict_attr_t const *da)
Alloc a new fr_pair_t (and append)
Definition pair.c:1471
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:707
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
fr_redis_pipeline_status_t redis_command_set_enqueue(fr_redis_trunk_t *rtrunk, fr_redis_command_set_t *cmds)
Enqueue a command set on a specific trunk.
Definition pipeline.c:532
int fr_redis_command_set_reset(fr_redis_command_set_t *cmds)
Reset a command set to it's state before enqueuing.
Definition pipeline.c:1008
void fr_redis_command_set_next_node(fr_redis_command_set_t *cmds, fr_redis_io_conf_t *ioconf)
Extract the next node address and port from a command set.
Definition pipeline.c:997
void fr_redis_command_set_cancel(fr_redis_command_set_t *cmds)
Cancel a command set.
Definition pipeline.c:557
Represents a collection of pipelined commands.
Definition pipeline.c:94
fr_redis_pipeline_status_t
Definition pipeline.h:43
@ FR_REDIS_PIPELINE_OK
No failure.
Definition pipeline.h:44
@ FR_REDIS_PIPELINE_DST_UNAVAILABLE
Cluster or host is down.
Definition pipeline.h:46
void(* fr_redis_trunk_active_t)(fr_redis_trunk_t *rtrunk, void *uctx)
Definition pipeline.h:55
VQP attributes.
#define fr_assert(_expr)
Definition rad_assert.h:37
#define RDEBUG2(fmt,...)
#define DEBUG2(fmt,...)
#define WARN(fmt,...)
static rs_t * conf
Definition radsniff.c:52
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:104
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:807
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:586
void * fr_rb_iter_init_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Initialise an in-order iterator.
Definition rb.c:850
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:637
void * fr_rb_iter_next_inorder(UNUSED fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Return the next node.
Definition rb.c:876
#define fr_rb_inline_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black tree.
Definition rb.h:269
Iterator structure for in-order traversal of an rbtree.
Definition rb.h:319
The main red black tree structure.
Definition rb.h:71
uint8_t max_nodes
Maximum number of cluster nodes to connect to.
Definition base.h:124
fr_redis_async_rcode_t
Definition base.h:80
@ REDIS_ASYNC_RCODE_BOOTSTRAP
The caller should issue a request to bootstrap the cluster map.
Definition base.h:83
@ REDIS_ASYNC_RCODE_ERROR
Unrecoverable error.
Definition base.h:82
@ REDIS_ASYNC_RCODE_GETMAP
The caller should issue a request to update the cluster map.
Definition base.h:84
@ REDIS_ASYNC_RCODE_FAIL
The command set trunk request has been failed.
Definition base.h:90
@ REDIS_ASYNC_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:86
@ REDIS_ASYNC_RCODE_SUCCESS
Operation was successful.
Definition base.h:81
@ FR_REDIS_CLUSTER_MAP_BOOTSTRAP
Definition base.h:102
@ FR_REDIS_CLUSTER_MAP_GET
Definition base.h:103
struct fr_redis_ct_s fr_redis_ct_t
Definition base.h:56
Configuration parameters for a redis connection.
Definition base.h:114
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:37
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:143
static int64_t fr_time_to_sec(fr_time_t when)
Convert an fr_time_t (internal time) to number of sec since the unix epoch (wallclock time)
Definition time.h:731
"server local" time.
Definition time.h:69
bool always_writable
Set to true if our ability to write requests to a connection handle is not dependent on the state of ...
Definition trunk.h:281
Common configuration parameters for a trunk.
Definition trunk.h:234
static fr_event_list_t * el
static unsigned count
Definition unittest.c:47
#define fr_pair_list_append_by_da(_ctx, _vp, _list, _attr, _val, _tainted)
Append a pair to a list, assigning its value.
Definition pair.h:304
#define fr_strerror_const(_msg)
Definition strerror.h:223
int fr_value_box_strdup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, bool tainted)
Copy a nul terminated string to a fr_value_box_t.
Definition value.c:4643
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309
static size_t char ** out
Definition value.h:1030