diff --git a/addons/otel/Makefile b/addons/otel/Makefile index 7e246ae34..28ed89616 100644 --- a/addons/otel/Makefile +++ b/addons/otel/Makefile @@ -56,6 +56,7 @@ OPTIONS_OBJS += \ addons/otel/src/conf.o \ addons/otel/src/event.o \ addons/otel/src/filter.o \ + addons/otel/src/group.o \ addons/otel/src/http.o \ addons/otel/src/otelc.o \ addons/otel/src/parser.o \ diff --git a/addons/otel/include/group.h b/addons/otel/include/group.h new file mode 100644 index 000000000..3850992bb --- /dev/null +++ b/addons/otel/include/group.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _OTEL_GROUP_H_ +#define _OTEL_GROUP_H_ + +#define FLT_OTEL_ACTION_GROUP "otel-group" + +/* Argument indices for the otel-group action rule. */ +enum FLT_OTEL_ARG_enum { + FLT_OTEL_ARG_FILTER_ID = 0, + FLT_OTEL_ARG_GROUP_ID, + + FLT_OTEL_ARG_FLT_CONF = 0, + FLT_OTEL_ARG_CONF, + FLT_OTEL_ARG_GROUP, +}; + +/* + * A description of the macro arguments can be found in the structure + * flt_otel_group_data definition + */ +#define FLT_OTEL_GROUP_DEFINES \ + FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_CON, SMP_VAL_FE_CON_ACC, SMP_OPT_DIR_REQ) \ + FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_SES, SMP_VAL_FE_SES_ACC, SMP_OPT_DIR_REQ) \ + FLT_OTEL_GROUP_DEF(ACT_F_TCP_REQ_CNT, SMP_VAL_FE_REQ_CNT, SMP_OPT_DIR_REQ) \ + FLT_OTEL_GROUP_DEF(ACT_F_TCP_RES_CNT, SMP_VAL_BE_RES_CNT, SMP_OPT_DIR_RES) \ + FLT_OTEL_GROUP_DEF(ACT_F_HTTP_REQ, SMP_VAL_FE_HRQ_HDR, SMP_OPT_DIR_REQ) \ + FLT_OTEL_GROUP_DEF(ACT_F_HTTP_RES, SMP_VAL_BE_HRS_HDR, SMP_OPT_DIR_RES) + +/* Per-action-from metadata mapping action types to fetch directions. */ +struct flt_otel_group_data { + enum act_from act_from; /* ACT_F_* */ + uint smp_val; /* Valid FE/BE fetch location. */ + uint smp_opt_dir; /* Fetch direction (request/response). */ +}; + +#endif /* _OTEL_GROUP_H_ */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/include/include.h b/addons/otel/include/include.h index e59fec400..17a04c628 100644 --- a/addons/otel/include/include.h +++ b/addons/otel/include/include.h @@ -33,6 +33,7 @@ #include "conf.h" #include "conf_funcs.h" #include "filter.h" +#include "group.h" #include "http.h" #include "otelc.h" #include "parser.h" diff --git a/addons/otel/src/group.c b/addons/otel/src/group.c new file mode 100644 index 000000000..44fb7d98a --- /dev/null +++ b/addons/otel/src/group.c @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../include/include.h" + + +/* Group data table built from the X-macro list. */ +#define FLT_OTEL_GROUP_DEF(a,b,c) { a, b, c }, +const struct flt_otel_group_data flt_otel_group_data[] = { FLT_OTEL_GROUP_DEFINES }; +#undef FLT_OTEL_GROUP_DEF + + +/*** + * NAME + * flt_otel_group_action - group action execution callback + * + * SYNOPSIS + * static enum act_return flt_otel_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts) + * + * ARGUMENTS + * rule - action rule containing group configuration references + * px - proxy instance + * sess - current session + * s - current stream + * opts - action options (ACT_OPT_* flags) + * + * DESCRIPTION + * Executes the action_ptr callback for the FLT_OTEL_ACTION_GROUP action. + * Retrieves the filter configuration, group definition, and runtime context + * from the rule's argument pointers. If the filter is disabled or not + * attached to the stream, processing is skipped. Otherwise, iterates over + * all scopes defined in the group and runs each via flt_otel_scope_run(). + * Scope execution errors are logged but do not prevent the remaining scopes + * from executing. + * + * RETURN VALUE + * Returns ACT_RET_CONT. + */ +static enum act_return flt_otel_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts) +{ + const struct filter *filter; + const struct flt_conf *fconf; + const struct flt_otel_conf *conf; + const struct flt_otel_conf_group *conf_group; + const struct flt_otel_runtime_context *rt_ctx = NULL; + const struct flt_otel_conf_ph *ph_scope; + char *err = NULL; + int i, rc; + + OTELC_FUNC("%p, %p, %p, %p, %d", rule, px, sess, s, opts); + + OTELC_DBG(DEBUG, "from: %d, arg.act %p:{ %p %p %p %p }", rule->from, &(rule->arg.act), rule->arg.act.p[0], rule->arg.act.p[1], rule->arg.act.p[2], rule->arg.act.p[3]); + + fconf = rule->arg.act.p[FLT_OTEL_ARG_FLT_CONF]; + conf = rule->arg.act.p[FLT_OTEL_ARG_CONF]; + conf_group = ((const struct flt_otel_conf_ph *)(rule->arg.act.p[FLT_OTEL_ARG_GROUP]))->ptr; + + if ((fconf == NULL) || (conf == NULL) || (conf_group == NULL)) { + FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": internal error, invalid group action"); + + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); + } + + if (_HA_ATOMIC_LOAD(&(conf->instr->flag_disabled))) { + OTELC_DBG(INFO, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id); + + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); + } + + /* Find the OpenTelemetry filter instance from the current stream. */ + list_for_each_entry(filter, &(s->strm_flt.filters), list) + if (filter->config == fconf) { + rt_ctx = filter->ctx; + + break; + } + + if (rt_ctx == NULL) { + OTELC_DBG(INFO, "cannot find filter, probably not attached to the stream"); + + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); + } + else if (flt_otel_is_disabled(filter FLT_OTEL_DBG_ARGS(, -1))) { + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); + } + else { + OTELC_DBG(DEBUG, "run group '%s'", conf_group->id); + FLT_OTEL_DBG_CONF_GROUP("run group ", conf_group); + } + + /* + * Check the value of rule->from; in case it is incorrect, + * report an error. + */ + for (i = 0; i < OTELC_TABLESIZE(flt_otel_group_data); i++) + if (flt_otel_group_data[i].act_from == rule->from) + break; + + if (i >= OTELC_TABLESIZE(flt_otel_group_data)) { + FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": internal error, invalid rule->from=%d", rule->from); + + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); + } + + /* Execute each scope defined in this group. */ + list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) { + rc = flt_otel_scope_run(s, rt_ctx->filter, (flt_otel_group_data[i].smp_opt_dir == SMP_OPT_DIR_REQ) ? &(s->req) : &(s->res), ph_scope->ptr, NULL, NULL, flt_otel_group_data[i].smp_opt_dir, &err); + if ((rc == FLT_OTEL_RET_ERROR) && (opts & ACT_OPT_FINAL)) { + FLT_OTEL_LOG(LOG_ERR, FLT_OTEL_ACTION_GROUP ": scope '%s' failed in group '%s'", ph_scope->id, conf_group->id); + OTELC_SFREE_CLEAR(err); + } + } + + OTELC_RETURN_EX(ACT_RET_CONT, enum act_return, "%d"); +} + + +/*** + * NAME + * flt_otel_group_check - group action post-parse check callback + * + * SYNOPSIS + * static int flt_otel_group_check(struct act_rule *rule, struct proxy *px, char **err) + * + * ARGUMENTS + * rule - action rule to validate + * px - proxy instance + * err - indirect pointer to error message string + * + * DESCRIPTION + * Validates the check_ptr callback for the FLT_OTEL_ACTION_GROUP action. + * Resolves the filter ID and group ID string references stored during parsing + * into direct pointers to the filter configuration and group configuration + * structures. Searches the proxy's filter list for a matching OTel filter, + * then locates the named group within that filter's configuration. On + * success, replaces the string ID pointers in ->arg.act.p with the + * resolved configuration pointers. + * + * RETURN VALUE + * Returns 1 on success, or 0 on failure with filled. + */ +static int flt_otel_group_check(struct act_rule *rule, struct proxy *px, char **err) +{ + struct flt_conf *fconf_tmp, *fconf = NULL; + struct flt_otel_conf *conf; + struct flt_otel_conf_ph *ph_group; + const char *filter_id; + const char *group_id; + bool flag_found = 0; + int i; + + OTELC_FUNC("%p, %p, %p:%p", rule, px, OTELC_DPTR_ARGS(err)); + + filter_id = rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID]; + group_id = rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID]; + + OTELC_DBG(NOTICE, "checking filter_id='%s', group_id='%s'", filter_id, group_id); + + /* + * Check the value of rule->from; in case it is incorrect, report an + * error. + */ + for (i = 0; i < OTELC_TABLESIZE(flt_otel_group_data); i++) + if (flt_otel_group_data[i].act_from == rule->from) + break; + + if (i >= OTELC_TABLESIZE(flt_otel_group_data)) { + FLT_OTEL_ERR("internal error, unexpected rule->from=%d, please report this bug!", rule->from); + + OTELC_RETURN_INT(0); + } + + /* + * Try to find the OpenTelemetry filter by checking all filters for the + * proxy . + */ + list_for_each_entry(fconf_tmp, &(px->filter_configs), list) { + conf = fconf_tmp->conf; + + if (fconf_tmp->id != otel_flt_id) { + /* This is not an OpenTelemetry filter. */ + continue; + } + else if (strcmp(conf->id, filter_id) == 0) { + /* This is the good filter ID. */ + fconf = fconf_tmp; + + break; + } + } + + if (fconf == NULL) { + FLT_OTEL_ERR("unable to find the OpenTelemetry filter '%s' used by the " FLT_OTEL_ACTION_GROUP " '%s'", filter_id, group_id); + + OTELC_RETURN_INT(0); + } + + /* + * Attempt to find if the group is defined in the OpenTelemetry filter + * configuration. + */ + list_for_each_entry(ph_group, &(conf->instr->ph_groups), list) + if (strcmp(ph_group->id, group_id) == 0) { + flag_found = 1; + + break; + } + + if (!flag_found) { + FLT_OTEL_ERR("unable to find group '%s' in the OpenTelemetry filter '%s' configuration", group_id, filter_id); + + OTELC_RETURN_INT(0); + } + + OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID]); + OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID]); + + /* Replace string IDs with resolved configuration pointers. */ + rule->arg.act.p[FLT_OTEL_ARG_FLT_CONF] = fconf; + rule->arg.act.p[FLT_OTEL_ARG_CONF] = conf; + rule->arg.act.p[FLT_OTEL_ARG_GROUP] = ph_group; + + OTELC_RETURN_INT(1); +} + + +/*** + * NAME + * flt_otel_group_release - group action release callback + * + * SYNOPSIS + * static void flt_otel_group_release(struct act_rule *rule) + * + * ARGUMENTS + * rule - action rule being released + * + * DESCRIPTION + * Provides the release_ptr callback for the FLT_OTEL_ACTION_GROUP action. + * This is a no-op because the group action's argument pointers reference + * shared configuration structures that are freed separately during filter + * deinitialization. + * + * RETURN VALUE + * This function does not return a value. + */ +static void flt_otel_group_release(struct act_rule *rule) +{ + OTELC_FUNC("%p", rule); + + OTELC_RETURN(); +} + + +/*** + * NAME + * flt_otel_group_parse - group action keyword parser + * + * SYNOPSIS + * static enum act_parse_ret flt_otel_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err) + * + * ARGUMENTS + * args - configuration line arguments array + * cur_arg - pointer to the current argument index + * px - proxy instance + * rule - action rule to populate + * err - indirect pointer to error message string + * + * DESCRIPTION + * Parses the FLT_OTEL_ACTION_GROUP action keyword from HAProxy configuration + * rules. Expects two arguments: a filter ID and a group ID, optionally + * followed by "if" or "unless" conditions. The filter ID and group ID are + * duplicated and stored in the 's argument pointers for later + * resolution by flt_otel_group_check(). The 's callbacks are set to + * flt_otel_group_action(), flt_otel_group_check(), and + * flt_otel_group_release(). This parser is registered for tcp-request, + * tcp-response, http-request, http-response, and http-after-response action + * contexts. + * + * RETURN VALUE + * Returns ACT_RET_PRS_OK on success, or ACT_RET_PRS_ERR on failure. + */ +static enum act_parse_ret flt_otel_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err) +{ + OTELC_FUNC("%p, %p, %p, %p, %p:%p", args, cur_arg, px, rule, OTELC_DPTR_ARGS(err)); + + FLT_OTEL_ARGS_DUMP(); + + if (!FLT_OTEL_ARG_ISVALID(*cur_arg) || !FLT_OTEL_ARG_ISVALID(*cur_arg + 1) || + (FLT_OTEL_ARG_ISVALID(*cur_arg + 2) && + !FLT_OTEL_PARSE_KEYWORD(*cur_arg + 2, FLT_OTEL_CONDITION_IF) && + !FLT_OTEL_PARSE_KEYWORD(*cur_arg + 2, FLT_OTEL_CONDITION_UNLESS))) { + FLT_OTEL_ERR("expects: [{ if | unless } ...]"); + + OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d"); + } + + /* Copy the OpenTelemetry filter id. */ + rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID] = OTELC_STRDUP(args[*cur_arg]); + if (rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID] == NULL) { + FLT_OTEL_ERR("%s : out of memory", args[*cur_arg]); + + OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d"); + } + + /* Copy the OpenTelemetry group id. */ + rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID] = OTELC_STRDUP(args[*cur_arg + 1]); + if (rule->arg.act.p[FLT_OTEL_ARG_GROUP_ID] == NULL) { + FLT_OTEL_ERR("%s : out of memory", args[*cur_arg + 1]); + + OTELC_SFREE_CLEAR(rule->arg.act.p[FLT_OTEL_ARG_FILTER_ID]); + + OTELC_RETURN_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d"); + } + + /* Wire up the rule callbacks. */ + rule->action = ACT_CUSTOM; + rule->action_ptr = flt_otel_group_action; + rule->check_ptr = flt_otel_group_check; + rule->release_ptr = flt_otel_group_release; + + *cur_arg += 2; + + OTELC_RETURN_EX(ACT_RET_PRS_OK, enum act_parse_ret, "%d"); +} + + +/* TCP request content action keywords for the OTel group action. */ +static struct action_kw_list tcp_req_action_kws = { ILH, { + { FLT_OTEL_ACTION_GROUP, flt_otel_group_parse }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_action_kws); + +/* TCP response content action keywords for the OTel group action. */ +static struct action_kw_list tcp_res_action_kws = { ILH, { + { FLT_OTEL_ACTION_GROUP, flt_otel_group_parse }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_action_kws); + +/* HTTP request action keywords for the OTel group action. */ +static struct action_kw_list http_req_action_kws = { ILH, { + { FLT_OTEL_ACTION_GROUP, flt_otel_group_parse }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_action_kws); + +/* HTTP response action keywords for the OTel group action. */ +static struct action_kw_list http_res_action_kws = { ILH, { + { FLT_OTEL_ACTION_GROUP, flt_otel_group_parse }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_action_kws); + +/* HTTP after-response action keywords for the OTel group action. */ +static struct action_kw_list http_after_res_actions_kws = { ILH, { + { FLT_OTEL_ACTION_GROUP, flt_otel_group_parse }, + { /* END */ }, + } +}; + +INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions_kws); + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */