MEDIUM: log/balance: support for the "hash" lb algorithm

hash lb algorithm can be configured with the "log-balance hash <cnv_list>"
directive. With this algorithm, the user specifies a converter list with
<cnv_list>.

The produced log message will be passed as-is to the provided converter
list, and the resulting hash will be used to select the log server that
will receive the log message.
This commit is contained in:
Aurelien DARRAGON 2023-09-19 10:51:53 +02:00 committed by Christopher Faulet
parent 7251344748
commit b30bd7adba
6 changed files with 126 additions and 6 deletions

View File

@ -8849,6 +8849,19 @@ log-balance <algorithm> [ <arguments> ]
pool of available servers as it may avoid the hammering pool of available servers as it may avoid the hammering
effect that could result from roundrobin in this situation. effect that could result from roundrobin in this situation.
hash <arguments> should be found in the form: <cnv_list>
e.g.: log-balance hash <cnv_list>
Each log message will be passed to the converter list
specified in <cnv_list> (ie: "cnv1,cnv2..."), and it will
then be passed to haproxy hashing function according to
"hash-type" settings. The resulting hash will be used to
select the destination server among the ones declared in the
log backend. The goal of this algorithm is to be able to
extract a key within the final log message using string
converters and then be able to stick to the same server thanks
to the hash. Only "map-based" hashes are supported for now.
<arguments> is an optional list of arguments which may be needed by some <arguments> is an optional list of arguments which may be needed by some
algorithms. algorithms.
@ -8862,6 +8875,7 @@ log-balance <algorithm> [ <arguments> ]
global global
log backend@mylog-rrb local0 # send all logs to mylog-rrb backend log backend@mylog-rrb local0 # send all logs to mylog-rrb backend
log backend@mylog-hash local0 # send all logs to mylog-hash backend
backend mylog-rrb backend mylog-rrb
mode log mode log
@ -8870,6 +8884,18 @@ log-balance <algorithm> [ <arguments> ]
server s1 udp@127.0.0.1:514 # will receive 50% of log messages server s1 udp@127.0.0.1:514 # will receive 50% of log messages
server s2 udp@127.0.0.1:514 server s2 udp@127.0.0.1:514
backend mylog-hash
mode log
# extract "METHOD URL PROTO" at the end of the log message,
# and let haproxy hash it so that log messages generated from
# similar requests get sent to the same syslog server:
log-balance hash 'field(-2,\")'
# server list here
server s1 127.0.0.1:514
#...
log-format <string> log-format <string>
Specifies the log format string to use for traffic logs Specifies the log format string to use for traffic logs
May be used in sections: defaults | frontend | listen | backend May be used in sections: defaults | frontend | listen | backend

View File

@ -147,6 +147,8 @@ static inline int srv_lb_status_changed(const struct server *srv)
*/ */
void set_backend_down(struct proxy *be); void set_backend_down(struct proxy *be);
unsigned int gen_hash(const struct proxy* px, const char* key, unsigned long len);
#endif /* _HAPROXY_BACKEND_H */ #endif /* _HAPROXY_BACKEND_H */
/* /*

View File

@ -70,7 +70,7 @@ int be_lastsession(const struct proxy *be)
} }
/* helper function to invoke the correct hash method */ /* helper function to invoke the correct hash method */
static unsigned int gen_hash(const struct proxy* px, const char* key, unsigned long len) unsigned int gen_hash(const struct proxy* px, const char* key, unsigned long len)
{ {
unsigned int hash; unsigned int hash;
@ -2855,8 +2855,19 @@ int backend_parse_log_balance(const char **args, char **err, struct proxy *curpr
curproxy->lbprm.algo &= ~BE_LB_ALGO; curproxy->lbprm.algo &= ~BE_LB_ALGO;
curproxy->lbprm.algo |= BE_LB_ALGO_RND; curproxy->lbprm.algo |= BE_LB_ALGO_RND;
} }
else if (strcmp(args[0], "hash") == 0) {
if (!*args[1]) {
memprintf(err, "%s requires a converter list.", args[0]);
return -1;
}
curproxy->lbprm.algo &= ~BE_LB_ALGO;
curproxy->lbprm.algo |= BE_LB_ALGO_SMP;
ha_free(&curproxy->lbprm.arg_str);
curproxy->lbprm.arg_str = strdup(args[1]);
}
else { else {
memprintf(err, "only supports 'roundrobin', 'sticky', 'random', options"); memprintf(err, "only supports 'roundrobin', 'sticky', 'random', 'hash' options");
return -1; return -1;
} }
return 0; return 0;

View File

@ -2576,8 +2576,8 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
*/ */
curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC | BE_LB_HASH_MOD); curproxy->lbprm.algo &= ~(BE_LB_HASH_TYPE | BE_LB_HASH_FUNC | BE_LB_HASH_MOD);
if (curproxy->mode != PR_MODE_TCP && curproxy->mode != PR_MODE_HTTP) { if (curproxy->mode != PR_MODE_TCP && curproxy->mode != PR_MODE_HTTP && curproxy->mode != PR_MODE_SYSLOG) {
ha_alert("parsing [%s:%d] : '%s' requires TCP or HTTP mode.\n", file, linenum, args[0]); ha_alert("parsing [%s:%d] : '%s' requires TCP, HTTP or LOG mode.\n", file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }

View File

@ -3510,8 +3510,11 @@ int check_config_validity()
curproxy->conf.args.line = 0; curproxy->conf.args.line = 0;
} }
/* "balance hash" needs to compile its expression */ /* "balance hash" needs to compile its expression
if ((curproxy->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) { * (log backends will handle this in proxy log postcheck)
*/
if (curproxy->mode != PR_MODE_SYSLOG &&
(curproxy->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) {
int idx = 0; int idx = 0;
const char *args[] = { const char *args[] = {
curproxy->lbprm.arg_str, curproxy->lbprm.arg_str,

View File

@ -42,6 +42,7 @@
#include <haproxy/stconn.h> #include <haproxy/stconn.h>
#include <haproxy/stream.h> #include <haproxy/stream.h>
#include <haproxy/time.h> #include <haproxy/time.h>
#include <haproxy/hash.h>
#include <haproxy/tools.h> #include <haproxy/tools.h>
/* global recv logs counter */ /* global recv logs counter */
@ -907,6 +908,65 @@ static int postcheck_log_backend(struct proxy *be)
be->srv_act = 0; be->srv_act = 0;
be->srv_bck = 0; be->srv_bck = 0;
/* "log-balance hash" needs to compile its expression */
if ((be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) {
struct sample_expr *expr;
char *expr_str = NULL;
char *err_str = NULL;
int idx = 0;
/* only map-based hash method is supported for now */
if ((be->lbprm.algo & BE_LB_HASH_TYPE) != BE_LB_HASH_MAP) {
memprintf(&msg, "unsupported hash method (from \"hash-type\")");
err_code |= ERR_ALERT | ERR_FATAL;
goto end;
}
/* a little bit of explanation about what we're going to do here:
* as the user gave us a list of converters, instead of the fetch+conv list
* tuple as we're used to, we need to insert a dummy fetch at the start of
* the converter list so that sample_parse_expr() is able to properly parse
* the expr. We're explicitly using str() as dummy fetch, since the input
* sample that will be passed to the converter list at runtime will be a
* string (the log message about to be sent). Doing so allows sample_parse_expr()
* to ensure that the provided converters will be compatible with string type.
*/
memprintf(&expr_str, "str(dummy),%s", be->lbprm.arg_str);
if (!expr_str) {
memprintf(&msg, "memory error during converter list argument parsing (from \"log-balance hash\")");
err_code |= ERR_ALERT | ERR_FATAL;
goto end;
}
expr = sample_parse_expr((char*[]){expr_str, NULL}, &idx,
be->conf.file,
be->conf.line,
&err_str, NULL, NULL);
if (!expr) {
memprintf(&msg, "%s (from converter list argument in \"log-balance hash\")", err_str);
ha_free(&err_str);
err_code |= ERR_ALERT | ERR_FATAL;
ha_free(&expr_str);
goto end;
}
/* We expect the log_message->conv_list expr to resolve as a binary-compatible
* value because its output will be passed to gen_hash() to compute the hash.
*
* So we check the last converter's output type to ensure that it can be
* converted into the expected type. Invalid output type will result in an
* error to prevent unexpected results during runtime.
*/
if (sample_casts[smp_expr_output_type(expr)][SMP_T_BIN] == NULL) {
memprintf(&msg, "invalid output type at the end of converter list for \"log-balance hash\" directive");
err_code |= ERR_ALERT | ERR_FATAL;
release_sample_expr(expr);
ha_free(&expr_str);
goto end;
}
ha_free(&expr_str);
be->lbprm.expr = expr;
}
/* finish the initialization of proxy's servers */ /* finish the initialization of proxy's servers */
srv = be->srv; srv = be->srv;
while (srv) { while (srv) {
@ -2120,6 +2180,24 @@ static inline void __do_send_log_backend(struct proxy *be, struct log_header hdr
/* random mode */ /* random mode */
targetid = statistical_prng() % nb_srv; targetid = statistical_prng() % nb_srv;
} }
else if ((be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_SMP) {
struct sample result;
/* log-balance hash */
memset(&result, 0, sizeof(result));
result.data.type = SMP_T_STR;
result.flags = SMP_F_CONST;
result.data.u.str.area = message;
result.data.u.str.data = size;
result.data.u.str.size = size + 1; /* with terminating NULL byte */
if (sample_process_cnv(be->lbprm.expr, &result)) {
/* gen_hash takes binary input, ensure that we provide such value to it */
if (result.data.type == SMP_T_BIN || sample_casts[result.data.type][SMP_T_BIN]) {
sample_casts[result.data.type][SMP_T_BIN](&result);
targetid = gen_hash(be, result.data.u.str.area, result.data.u.str.data) % nb_srv;
}
}
}
skip_lb: skip_lb: