From: Mozejko, MarcinX Date: Wed, 20 Dec 2017 13:19:36 +0000 (+0000) Subject: SNMP Agent plugin: X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=3bf0437e2a57919ac2ae1fa944f30ade393c7a05;p=collectd.git SNMP Agent plugin: Support for complicated data definitions Add unit tests for functions: snmp_agent_oid_to_string() snmp_agent_prep_index_list() snmp_agent_fill_index_list() snmp_agent_config_index_key_source() snmp_agent_config_index_key_regex() snmp_agent_config_index_key() snmp_agent_format_name() snmp_agent_parse_index_key() snmp_agent_create_token() snmp_agent_delete_token() snmp_agent_get_token() snmp_agent_tokenize() snmp_agent_build_name() Change-Id: I31a8a5c771fa4c7d0122705fe79f021bc71c9904 Signed-off-by: Mozejko, MarcinX --- diff --git a/Makefile.am b/Makefile.am index 612346e1..232ddd6e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1565,6 +1565,23 @@ snmp_agent_la_SOURCES = src/snmp_agent.c snmp_agent_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBNETSNMPAGENT_CPPFLAGS) snmp_agent_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBNETSNMPAGENT_LDFLAGS) snmp_agent_la_LIBADD = $(BUILD_WITH_LIBNETSNMPAGENT_LIBS) + +test_plugin_snmp_agent_SOURCES = src/snmp_agent_test.c \ + src/daemon/utils_avltree.c \ + src/daemon/utils_llist.c \ + src/daemon/configfile.c \ + src/daemon/types_list.c +test_plugin_snmp_agent_CPPFLAGS = $(AM_CPPFLAGS) \ + $(BUILD_WITH_LIBNETSNMPAGENT_CPPFLAGS) +test_plugin_snmp_agent_LDFLAGS = $(PLUGIN_LDFLAGS) \ + $(BUILD_WITH_LIBNETSNMPAGENT_LDFLAGS) +test_plugin_snmp_agent_LDADD = liboconfig.la libplugin_mock.la \ + $(BUILD_WITH_LIBNETSNMPAGENT_LIBS) $(BUILD_WITH_LIBNETSNMP_LIBS) + +check_PROGRAMS += test_plugin_snmp_agent +TESTS += test_plugin_snmp_agent + + endif if BUILD_PLUGIN_STATSD diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 61dc6950..6e22a7f2 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -1392,7 +1392,9 @@ # IndexOID "IF-MIB::ifIndex" # SizeOID "IF-MIB::ifNumber" # -# Index "PluginInstance" +# +# Source "PluginInstance" +# # Plugin "interface" # OIDs "IF-MIB::ifDescr" # diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index b9d85837..6341574d 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -7791,7 +7791,9 @@ B IndexOID "IF-MIB::ifIndex" SizeOID "IF-MIB::ifNumber" - Index "PluginInstance" + + Source "PluginInstance" + Plugin "interface" OIDs "IF-MIB::ifDescr" @@ -7802,12 +7804,44 @@ B OIDs "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets" + + + + Source "PluginInstance" + + Plugin "virt" + OIDs "LIBVIRT-HYPERVISOR-MIB::lvhAffinityDomainName" + + + Plugin "virt" + + Source "TypeInstance" + Regex "^vcpu_([0-9]{1,3})-cpu_[0-9]{1,3}$" + Group 1 + + OIDs "LIBVIRT-HYPERVISOR-MIB::lvhVCPUIndex" + + + Plugin "virt" + + Source "TypeInstance" + Regex "^vcpu_[0-9]{1,3}-cpu_([0-9]{1,3})$" + Group 1 + + OIDs "LIBVIRT-HYPERVISOR-MIB::lvhCPUIndex" + + + Plugin "virt" + Type "cpu_affinity" + OIDs "LIBVIRT-HYPERVISOR-MIB::lvhCPUAffinity" + +
There are two types of blocks that can be contained in the CPluginE snmp_agentE> block: B and B: -=head3 The B block +=head3 B block The B block defines a list OIDs that are to be handled. This block can define scalar or table OIDs. If B block is defined inside of B
@@ -7816,15 +7850,35 @@ The following options can be set: =over 4 -=item B I +=item B block -B can be set to one of the following values: "Hostname", "Plugin", -"PluginInstance", "Type", "TypeInstance". This value indicates which field of -corresponding collectd metric is taken as a SNMP table index. In case more than -one table B block has B property set, then multiple key index is -built. If B block defines scalar data type B has no effect and can +B block contains all data needed for proper index build of snmp table. +In case more than +one table B block has B block present then multiple key index is +built. If B block defines scalar data type B has no effect and can be omitted. +=over 8 + +=item B I + +B can be set to one of the following values: "Hostname", "Plugin", +"PluginInstance", "Type", "TypeInstance". This value indicates which field of +corresponding collectd metric is taken as a SNMP table index. + +=item B I + +B option can also be used to parse strings or numbers out of +specific field. For example: type-instance field which is "vcpu1-cpu2" can be +parsed into two numeric fields CPU = 2 and VCPU = 1 and can be later used +as a table index. + +=item B I + +B number can be specified in case groups are used in regex. + +=back + =item B I Read plugin name whose collected data will be mapped to specified OIDs. diff --git a/src/daemon/plugin_mock.c b/src/daemon/plugin_mock.c index 8f8334ea..3d5b92d5 100644 --- a/src/daemon/plugin_mock.c +++ b/src/daemon/plugin_mock.c @@ -60,6 +60,16 @@ int plugin_register_read(const char *name, int (*callback)(void)) { return ENOTSUP; } +int plugin_register_write(const char *name, plugin_write_cb callback, + user_data_t const *ud) { + return ENOTSUP; +} + +int plugin_register_missing(const char *name, plugin_missing_cb callback, + user_data_t const *ud) { + return ENOTSUP; +} + int plugin_register_complex_read(const char *group, const char *name, int (*callback)(user_data_t *), cdtime_t interval, diff --git a/src/daemon/utils_cache_mock.c b/src/daemon/utils_cache_mock.c index 5389d126..1495a803 100644 --- a/src/daemon/utils_cache_mock.c +++ b/src/daemon/utils_cache_mock.c @@ -24,8 +24,8 @@ * Florian octo Forster */ -#include #include "utils_cache.h" +#include gauge_t *uc_get_rate(__attribute__((unused)) data_set_t const *ds, __attribute__((unused)) value_list_t const *vl) { @@ -41,3 +41,8 @@ int uc_get_rate_by_name(const char *name, gauge_t **ret_values, int uc_get_names(char ***ret_names, cdtime_t **ret_times, size_t *ret_number) { return ENOTSUP; } + +int uc_get_value_by_name(const char *name, value_t **ret_values, + size_t *ret_values_num) { + return ENOTSUP; +} diff --git a/src/snmp_agent.c b/src/snmp_agent.c index 9b5ccbb7..fa73e926 100644 --- a/src/snmp_agent.c +++ b/src/snmp_agent.c @@ -33,6 +33,7 @@ #include "utils_avltree.h" #include "utils_cache.h" #include "utils_llist.h" +#include #include @@ -42,17 +43,30 @@ #define PLUGIN_NAME "snmp_agent" #define TYPE_STRING -1 -#define MAX_INDEX_TYPES 5 +#define GROUP_UNUSED -1 +#define MAX_KEY_SOURCES 5 +#define MAX_INDEX_KEYS 5 +#define MAX_MATCHES 5 -/* Identifies index key origin */ -enum index_key_e { +/* Identifies index key source */ +enum index_key_src_e { INDEX_HOST = 0, INDEX_PLUGIN, INDEX_PLUGIN_INSTANCE, INDEX_TYPE, INDEX_TYPE_INSTANCE }; -typedef enum index_key_e index_key_t; +typedef enum index_key_src_e index_key_src_t; + +struct index_key_s { + index_key_src_t source; + u_char type; + char *regex; /* Pattern used to parse index key source string */ + int group; /* If pattern gives more than one group we can specify which one + we want to take */ + regex_t regex_info; +}; +typedef struct index_key_s index_key_t; struct oid_s { oid oid[MAX_OID_LEN]; @@ -61,6 +75,12 @@ struct oid_s { }; typedef struct oid_s oid_t; +struct token_s { + char *str; + netsnmp_variable_list *key; /* Points to succeeding key */ +}; +typedef struct token_s token_t; + struct table_definition_s { char *name; oid_t index_oid; @@ -68,12 +88,17 @@ struct table_definition_s { llist_t *columns; c_avl_tree_t *instance_index; c_avl_tree_t *index_instance; - index_key_t indexes[MAX_INDEX_TYPES]; /* Stores information about what each - index key represents */ - int indexes_len; + index_key_t index_keys[MAX_INDEX_KEYS]; /* Stores information about what each + index key represents */ + int index_keys_len; netsnmp_variable_list *index_list_cont; /* Index key container used for generating as well as parsing OIDs, not thread-safe */ + c_avl_tree_t *tokens[MAX_KEY_SOURCES]; /* Input string after regex execution + will be split into sepearate + tokens */ + + _Bool tokens_done; /* Set to 1 when all tokens are generated */ }; typedef struct table_definition_s table_definition_t; @@ -105,7 +130,7 @@ struct snmp_agent_ctx_s { typedef struct snmp_agent_ctx_s snmp_agent_ctx_t; static snmp_agent_ctx_t *g_agent; -const char *const index_opts[MAX_INDEX_TYPES] = { +const char *const index_opts[MAX_KEY_SOURCES] = { "Hostname", "Plugin", "PluginInstance", "Type", "TypeInstance"}; #define CHECK_DD_TYPE(_dd, _p, _pi, _t, _ti) \ @@ -121,6 +146,7 @@ static int snmp_agent_set_vardata(void *dst_buf, size_t *dst_buf_len, u_char asn_type, double scale, double shift, const void *value, size_t len, int type); static int snmp_agent_unregister_oid_index(oid_t *oid, int index); +static int num_compare(const int *a, const int *b); static u_char snmp_agent_get_asn_type(oid *oid, size_t oid_len) { struct tree *node = get_tree(oid, oid_len, g_agent->tp); @@ -154,6 +180,7 @@ static void snmp_agent_dump_data(llist_t *list) { char oid_str[DATA_MAX_NAME_LEN]; for (llentry_t *de = llist_head(list); de != NULL; de = de->next) { data_definition_t *dd = de->value; + table_definition_t const *td = dd->table; if (dd->table != NULL) DEBUG(PLUGIN_NAME ": Column:"); @@ -165,8 +192,18 @@ static void snmp_agent_dump_data(llist_t *list) { DEBUG(PLUGIN_NAME ": Plugin: %s", dd->plugin); if (dd->plugin_instance) DEBUG(PLUGIN_NAME ": PluginInstance: %s", dd->plugin_instance); - if (dd->is_index_key) - DEBUG(PLUGIN_NAME ": Index: %s", index_opts[dd->index_key_pos]); + if (dd->is_index_key) { + index_key_t const *index_key = &td->index_keys[dd->index_key_pos]; + + DEBUG(PLUGIN_NAME ": IndexKey:"); + DEBUG(PLUGIN_NAME ": Source: %s", index_opts[index_key->source]); + DEBUG(PLUGIN_NAME ": Type: %s", + (index_key->type == ASN_INTEGER) ? "Integer" : "String"); + if (index_key->regex) + DEBUG(PLUGIN_NAME ": Regex: %s", index_key->regex); + if (index_key->group != GROUP_UNUSED) + DEBUG(PLUGIN_NAME ": Group: %d", index_key->group); + } if (dd->type) DEBUG(PLUGIN_NAME ": Type: %s", dd->type); if (dd->type_instance) @@ -216,7 +253,7 @@ static int snmp_agent_validate_config(void) { for (llentry_t *te = llist_head(g_agent->tables); te != NULL; te = te->next) { table_definition_t *td = te->value; - if (!td->indexes_len) { + if (!td->index_keys_len) { ERROR(PLUGIN_NAME ": Index keys not defined for '%s'", td->name); return -EINVAL; } @@ -297,69 +334,242 @@ static int snmp_agent_validate_config(void) { return 0; } -static int snmp_agent_fill_index_list(table_definition_t const *td, +static int snmp_agent_parse_index_key(const char *input, char *regex, + regex_t *regex_info, int gi, + regmatch_t *m) { + regmatch_t matches[MAX_MATCHES]; + + int ret = regexec(regex_info, input, MAX_MATCHES, matches, 0); + if (!ret) { + if (gi > regex_info->re_nsub) { + ERROR(PLUGIN_NAME ": Group index %d not found. Check regex config", gi); + return -1; + } + *m = matches[gi]; + } else if (ret == REG_NOMATCH) { + ERROR(PLUGIN_NAME ": No match found"); + return -1; + } else { + char msgbuf[100]; + + regerror(ret, regex_info, msgbuf, sizeof(msgbuf)); + ERROR(PLUGIN_NAME ": Regex match failed: %s", msgbuf); + return -1; + } + + return 0; +} + +static int snmp_agent_create_token(char const *input, int t_off, int n, + c_avl_tree_t *tree, + netsnmp_variable_list *index_key) { + token_t *token = malloc(sizeof(*token)); + int *offset = malloc(sizeof(*offset)); + int ret = 0; + + assert(tree != NULL); + + if (token == NULL || offset == NULL) + goto error; + + token->key = index_key; + token->str = strndup(input + t_off, n); + + if (token->str == NULL) + goto error; + + *offset = t_off; + ret = c_avl_insert(tree, (void *)offset, (void *)token); + + if (ret != 0) + goto error; + + return 0; + +error: + + ERROR(PLUGIN_NAME ": Could not allocate memory to create token"); + sfree(token->str); + sfree(token); + sfree(offset); + return -1; +} + +static int snmp_agent_delete_token(int t_off, c_avl_tree_t *tree) { + token_t *token = NULL; + int *offset = NULL; + + int ret = c_avl_remove(tree, &t_off, (void **)&offset, (void **)&token); + + if (ret != 0) { + ERROR(PLUGIN_NAME ": Could not delete token"); + return -1; + } + + sfree(token->str); + sfree(token); + sfree(offset); + return 0; +} + +static int snmp_agent_get_token(c_avl_tree_t *tree, int mpos) { + + int *pos; + char *token; + int prev_pos = 0; + + c_avl_iterator_t *it = c_avl_get_iterator(tree); + while (c_avl_iterator_next(it, (void **)&pos, (void **)&token) == 0) { + if (*pos >= mpos) + break; + else + prev_pos = *pos; + } + + c_avl_iterator_destroy(it); + return prev_pos; +} + +static int snmp_agent_tokenize(const char *input, c_avl_tree_t *tokens, + const regmatch_t *m, + netsnmp_variable_list *key) { + assert(tokens != NULL); + + int ret = 0; + int len = strlen(input); + + /* Creating first token that is going to be split later */ + if (c_avl_size(tokens) == 0) { + ret = snmp_agent_create_token(input, 0, len, tokens, NULL); + if (ret != 0) + return ret; + } + + /* Divide token that contains current match into two */ + int t_pos = snmp_agent_get_token(tokens, m->rm_so); + ret = snmp_agent_delete_token(t_pos, tokens); + + if (ret != 0) + return -1; + + ret = snmp_agent_create_token(input, t_pos, m->rm_so - t_pos, tokens, key); + + if (ret != 0) + return -1; + + if (len - m->rm_eo > 1) { + ret = snmp_agent_create_token(input, m->rm_eo, len - m->rm_eo + 1, tokens, + NULL); + if (ret != 0) { + snmp_agent_delete_token(t_pos, tokens); + return -1; + } + } + + return 0; +} + +static int snmp_agent_fill_index_list(table_definition_t *td, value_list_t const *vl) { int ret; + int i; netsnmp_variable_list *key = td->index_list_cont; + char const *ptr; - for (int i = 0; i < td->indexes_len; i++) { + for (i = 0; i < td->index_keys_len; i++) { /* var should never be NULL */ assert(key != NULL); + ptr = NULL; + ret = 0; + const index_key_src_t source = td->index_keys[i].source; + c_avl_tree_t *const tokens = td->tokens[source]; /* Generating list filled with all data necessary to generate an OID */ - switch (td->indexes[i]) { + switch (source) { case INDEX_HOST: - ret = snmp_set_var_value(key, vl->host, strlen(vl->host)); + ptr = vl->host; break; case INDEX_PLUGIN: - ret = snmp_set_var_value(key, vl->plugin, strlen(vl->plugin)); + ptr = vl->plugin; break; case INDEX_PLUGIN_INSTANCE: - ret = snmp_set_var_value(key, vl->plugin_instance, - strlen(vl->plugin_instance)); + ptr = vl->plugin_instance; break; case INDEX_TYPE: - ret = snmp_set_var_value(key, vl->type, strlen(vl->type)); + ptr = vl->type; break; case INDEX_TYPE_INSTANCE: - ret = - snmp_set_var_value(key, vl->type_instance, strlen(vl->type_instance)); + ptr = vl->type_instance; break; default: - ERROR(PLUGIN_NAME ": Unknown index key type provided"); + ERROR(PLUGIN_NAME ": Unknown index key source provided"); return -EINVAL; } if (ret != 0) return -EINVAL; + + /* Parsing input string if necessary */ + if (td->index_keys[i].regex) { + regmatch_t m = {-1, -1}; + + /* Parsing input string */ + ret = snmp_agent_parse_index_key(ptr, td->index_keys[i].regex, + &td->index_keys[i].regex_info, + td->index_keys[i].group, &m); + if (ret != 0) { + ERROR(PLUGIN_NAME ": Error executing regex"); + return ret; + } + + /* Tokenizing input string if not done yet */ + if (td->tokens_done == 0) + ret = snmp_agent_tokenize(ptr, tokens, &m, key); + + if (ret != 0) + return -1; + + if (td->index_keys[i].type == ASN_INTEGER) { + int val = strtol(ptr + m.rm_so, NULL, 0); + ret = snmp_set_var_value(key, &val, sizeof(val)); + } else + ret = snmp_set_var_value(key, ptr + m.rm_so, m.rm_eo - m.rm_so); + } else + ret = snmp_set_var_value(key, ptr, strlen(ptr)); key = key->next_variable; } + + /* Tokens for all source strings are generated */ + for (i = 0; i < MAX_KEY_SOURCES; i++) + td->tokens_done = 1; + return 0; } static int snmp_agent_prep_index_list(table_definition_t const *td, netsnmp_variable_list **index_list) { /* Generating list having only the structure (with no values) letting us - * know how to parse an OID */ - for (int i = 0; i < td->indexes_len; i++) { - switch (td->indexes[i]) { + * know how to parse an OID*/ + for (int i = 0; i < td->index_keys_len; i++) { + switch (td->index_keys[i].source) { case INDEX_HOST: case INDEX_PLUGIN: case INDEX_PLUGIN_INSTANCE: case INDEX_TYPE: case INDEX_TYPE_INSTANCE: - snmp_varlist_add_variable(index_list, NULL, 0, ASN_OCTET_STR, NULL, 0); + snmp_varlist_add_variable(index_list, NULL, 0, td->index_keys[i].type, + NULL, 0); break; default: - ERROR(PLUGIN_NAME ": Unknown index key type provided"); + ERROR(PLUGIN_NAME ": Unknown index key source provided"); return -EINVAL; } } return 0; } -static int snmp_agent_generate_index(table_definition_t const *td, +static int snmp_agent_generate_index(table_definition_t *td, value_list_t const *vl, oid_t *index_oid) { - /* According to given information by indexes list + /* According to given information by index_keys list * index OID is going to be built */ int ret = snmp_agent_fill_index_list(td, vl); @@ -607,16 +817,33 @@ static void snmp_agent_free_table(table_definition_t **td) { c_avl_destroy((*td)->instance_index); (*td)->instance_index = NULL; } - snmp_free_varbind((*td)->index_list_cont); + + int i; + token_t *tok = NULL; + + for (i = 0; i < (*td)->index_keys_len; i++) { + sfree((*td)->index_keys[i].regex); + regfree(&(*td)->index_keys[i].regex_info); + } + for (i = 0; i < MAX_KEY_SOURCES; i++) + if ((*td)->tokens[i] != NULL) { + while (c_avl_pick((*td)->tokens[i], &key, (void **)&tok) == 0) { + sfree(key); + sfree(tok->str); + sfree(tok); + } + c_avl_destroy((*td)->tokens[i]); + (*td)->tokens[i] = NULL; + } sfree((*td)->name); sfree(*td); return; } -static int snmp_agent_parse_oid_indexes(const table_definition_t *td, - oid_t *index_oid) { +static int snmp_agent_parse_oid_index_keys(const table_definition_t *td, + oid_t *index_oid) { int ret = parse_oid_indexes(index_oid->oid, index_oid->oid_len, td->index_list_cont); if (ret != SNMPERR_SUCCESS) @@ -624,9 +851,47 @@ static int snmp_agent_parse_oid_indexes(const table_definition_t *td, return ret; } +static int snmp_agent_build_name(char **name, c_avl_tree_t *tokens) { + + int *pos; + token_t *tok; + char str[DATA_MAX_NAME_LEN]; + char out[DATA_MAX_NAME_LEN] = {0}; + c_avl_iterator_t *it = c_avl_get_iterator(tokens); + + if (it == NULL) { + ERROR(PLUGIN_NAME ": Error getting tokens list iterator"); + return -1; + } + + while (c_avl_iterator_next(it, (void **)&pos, (void **)&tok) == 0) { + strncat(out, tok->str, strlen(tok->str)); + if (tok->key != NULL) { + if (tok->key->type == ASN_INTEGER) { + snprintf(str, sizeof(str), "%ld", *tok->key->val.integer); + strncat(out, str, strlen(str)); + } else { /* OCTET_STR */ + strncat(out, (char *)tok->key->val.string, + strlen((char *)tok->key->val.string)); + } + } + } + *name = strdup(out); + c_avl_iterator_destroy(it); + + if (*name == NULL) { + ERROR(PLUGIN_NAME ": Could not allocate memory"); + return -ENOMEM; + } + + return 0; +} + static int snmp_agent_format_name(char *name, int name_len, data_definition_t *dd, oid_t *index_oid) { + int ret = 0; + if (index_oid == NULL) { /* It's a scalar */ format_name(name, name_len, hostname_g, dd->plugin, dd->plugin_instance, @@ -634,44 +899,52 @@ static int snmp_agent_format_name(char *name, int name_len, } else { /* Need to parse string index OID */ const table_definition_t *td = dd->table; - int ret = snmp_agent_parse_oid_indexes(td, index_oid); + ret = snmp_agent_parse_oid_index_keys(td, index_oid); if (ret != 0) return ret; int i = 0; netsnmp_variable_list *key = td->index_list_cont; - char *host = hostname_g; - char *plugin = dd->plugin; - char *plugin_instance = dd->plugin_instance; - char *type = dd->type; - char *type_instance = dd->type_instance; + char str[DATA_MAX_NAME_LEN]; + char *fields[MAX_KEY_SOURCES] = {hostname_g, dd->plugin, + dd->plugin_instance, dd->type, + dd->type_instance}; + + /* Looking for simple keys only */ while (key != NULL) { - switch (td->indexes[i]) { - case INDEX_HOST: - host = (char *)key->val.string; - break; - case INDEX_PLUGIN: - plugin = (char *)key->val.string; - break; - case INDEX_PLUGIN_INSTANCE: - plugin_instance = (char *)key->val.string; - break; - case INDEX_TYPE: - type = (char *)key->val.string; - break; - case INDEX_TYPE_INSTANCE: - type_instance = (char *)key->val.string; - break; - default: - ERROR(PLUGIN_NAME ": Unkown index type!"); - return -EINVAL; + if (!td->index_keys[i].regex) { + index_key_src_t source = td->index_keys[i].source; + + if (source < INDEX_HOST || source > INDEX_TYPE_INSTANCE) { + ERROR(PLUGIN_NAME ": Unkown index key source!"); + return -EINVAL; + } + + if (td->index_keys[i].type == ASN_INTEGER) { + snprintf(str, sizeof(str), "%ld", *key->val.integer); + fields[source] = str; + } else /* OCTET_STR */ + fields[source] = (char *)key->val.string; } key = key->next_variable; i++; } - format_name(name, name_len, host, plugin, plugin_instance, type, - type_instance); + /* Keys with regexes */ + for (i = 0; i < MAX_KEY_SOURCES; i++) { + if (td->tokens[i] == NULL) + continue; + ret = snmp_agent_build_name(&fields[i], td->tokens[i]); + if (ret != 0) + return ret; + } + format_name(name, name_len, fields[INDEX_HOST], fields[INDEX_PLUGIN], + fields[INDEX_PLUGIN_INSTANCE], fields[INDEX_TYPE], + fields[INDEX_TYPE_INSTANCE]); + for (i = 0; i < MAX_KEY_SOURCES; i++) { + if (td->tokens[i]) + sfree(fields[i]); + } } return 0; @@ -684,7 +957,7 @@ static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests, if (dd->is_index_key) { const table_definition_t *td = dd->table; - ret = snmp_agent_parse_oid_indexes(td, index_oid); + int ret = snmp_agent_parse_oid_index_keys(td, index_oid); if (ret != 0) return ret; @@ -694,10 +967,15 @@ static int snmp_agent_form_reply(struct netsnmp_request_info_s *requests, for (int pos = 0; pos < dd->index_key_pos; pos++) key = key->next_variable; - requests->requestvb->type = ASN_OCTET_STR; - snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, - (const u_char *)key->val.string, - strlen((const char *)key->val.string)); + requests->requestvb->type = td->index_keys[dd->index_key_pos].type; + + if (requests->requestvb->type == ASN_INTEGER) + snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, + key->val.integer, sizeof(*key->val.integer)); + else /* OCTET_STR */ + snmp_set_var_typed_value(requests->requestvb, requests->requestvb->type, + (const u_char *)key->val.string, + strlen((const char *)key->val.string)); pthread_mutex_unlock(&g_agent->lock); @@ -1113,9 +1391,10 @@ static int snmp_agent_config_table_index_oid(table_definition_t *td, return 0; } -/* Parsing table column representing index key */ -static int snmp_agent_config_index(table_definition_t *td, - data_definition_t *dd, oconfig_item_t *ci) { +/* Getting index key source that will represent table row */ +static int snmp_agent_config_index_key_source(table_definition_t *td, + data_definition_t *dd, + oconfig_item_t *ci) { char *val = NULL; int ret = cf_util_get_string(ci, &val); @@ -1124,9 +1403,11 @@ static int snmp_agent_config_index(table_definition_t *td, _Bool match = 0; - for (int i = 0; i < MAX_INDEX_TYPES; i++) { + for (int i = 0; i < MAX_KEY_SOURCES; i++) { if (strcasecmp(index_opts[i], (const char *)val) == 0) { - td->indexes[td->indexes_len] = i; + td->index_keys[td->index_keys_len].source = i; + td->index_keys[td->index_keys_len].group = GROUP_UNUSED; + td->index_keys[td->index_keys_len].regex = NULL; match = 1; break; } @@ -1139,18 +1420,67 @@ static int snmp_agent_config_index(table_definition_t *td, } sfree(val); - dd->index_key_pos = td->indexes_len++; + dd->index_key_pos = td->index_keys_len++; dd->is_index_key = 1; return 0; } +/* Getting format string used to parse values from index key source */ +static int snmp_agent_config_index_key_regex(table_definition_t *td, + data_definition_t *dd, + oconfig_item_t *ci) { + index_key_t *index_key = &td->index_keys[dd->index_key_pos]; + + int ret = cf_util_get_string(ci, &index_key->regex); + if (ret != 0) + return -1; + + ret = regcomp(&index_key->regex_info, index_key->regex, REG_EXTENDED); + if (ret) { + ERROR(PLUGIN_NAME ": Could not compile regex for %s", dd->name); + return -1; + } + + index_key_src_t source = index_key->source; + if (td->tokens[source] == NULL && index_key->regex != NULL) { + td->tokens[source] = + c_avl_create((int (*)(const void *, const void *))num_compare); + if (td->tokens[source] == NULL) { + ERROR(PLUGIN_NAME ": Could not allocate memory for AVL tree"); + return -ENOMEM; + } + } + + return 0; +} + +static int snmp_agent_config_index_key(table_definition_t *td, + data_definition_t *dd, + oconfig_item_t *ci) { + int ret = 0; + + for (int i = 0; (i < ci->children_num && ret == 0); i++) { + oconfig_item_t *option = ci->children + i; + + if (strcasecmp("Source", option->key) == 0) + ret = snmp_agent_config_index_key_source(td, dd, option); + else if (strcasecmp("Regex", option->key) == 0) + ret = snmp_agent_config_index_key_regex(td, dd, option); + else if (strcasecmp("Group", option->key) == 0) + ret = cf_util_get_int(option, &td->index_keys[dd->index_key_pos].group); + } + + return ret; +} + /* This function parses configuration of both scalar and table column * because they have nearly the same structure */ static int snmp_agent_config_table_column(table_definition_t *td, oconfig_item_t *ci) { data_definition_t *dd; int ret = 0; + oconfig_item_t *option_tmp = NULL; assert(ci != NULL); @@ -1175,10 +1505,11 @@ static int snmp_agent_config_table_column(table_definition_t *td, for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *option = ci->children + i; - /* Instance option is reserved for table entry only */ - if (strcasecmp("Index", option->key) == 0 && td != NULL) - ret = snmp_agent_config_index(td, dd, option); - else if (strcasecmp("Plugin", option->key) == 0) + /* First 3 options are reserved for table entry only */ + if (td != NULL && strcasecmp("IndexKey", option->key) == 0) { + dd->is_index_key = 1; + option_tmp = option; + } else if (strcasecmp("Plugin", option->key) == 0) ret = cf_util_get_string(option, &dd->plugin); else if (strcasecmp("PluginInstance", option->key) == 0) ret = cf_util_get_string(option, &dd->plugin_instance); @@ -1203,6 +1534,17 @@ static int snmp_agent_config_table_column(table_definition_t *td, } } + if (dd->is_index_key) { + ret = snmp_agent_config_index_key(td, dd, option_tmp); + td->index_keys[dd->index_key_pos].type = + snmp_agent_get_asn_type(dd->oids[0].oid, dd->oids[0].oid_len); + + if (ret != 0) { + snmp_agent_free_data(&dd); + return -1; + } + } + llentry_t *entry = llentry_create(dd->name, dd); if (entry == NULL) { snmp_agent_free_data(&dd); @@ -1260,6 +1602,10 @@ static int snmp_agent_config_table(oconfig_item_t *ci) { return -ENOMEM; } + for (int i = 0; i < MAX_KEY_SOURCES; i++) + td->tokens[i] = NULL; + td->tokens_done = 0; + for (int i = 0; i < ci->children_num; i++) { oconfig_item_t *option = ci->children + i; @@ -1728,7 +2074,6 @@ static int snmp_agent_shutdown(void) { } static int snmp_agent_config(oconfig_item_t *ci) { - int ret = snmp_agent_preinit(); if (ret != 0) { diff --git a/src/snmp_agent_test.c b/src/snmp_agent_test.c new file mode 100644 index 00000000..de13d2cf --- /dev/null +++ b/src/snmp_agent_test.c @@ -0,0 +1,854 @@ +/** + * collectd - src/snmp_agent_test.c + * + * Copyright(c) 2017 Intel Corporation. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Marcin Mozejko + **/ + +#include "snmp_agent.c" +#include "testing.h" + +DEF_TEST(oid_to_string) { + oid_t o = {.oid = {1, 2, 3, 4, 5, 6, 7, 8, 9}, .oid_len = 9}; + char oid_str[DATA_MAX_NAME_LEN]; + + int ret = snmp_agent_oid_to_string(oid_str, DATA_MAX_NAME_LEN, &o); + EXPECT_EQ_INT(o.oid_len * 2 - 1, ret); + EXPECT_EQ_STR("1.2.3.4.5.6.7.8.9", oid_str); + + return 0; +} + +/* Testing formatting metric name for simple scalar */ +DEF_TEST(format_name_scalar) { + data_definition_t *dd = calloc(1, sizeof(*dd)); + + dd->plugin = strdup("test_plugin"); + dd->plugin_instance = strdup("test_plugin_inst"); + dd->type = strdup("test_type"); + dd->type_instance = strdup("test_type_inst"); + + char name[DATA_MAX_NAME_LEN]; + int ret = snmp_agent_format_name(name, sizeof(name), dd, NULL); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR( + "example.com/test_plugin-test_plugin_inst/test_type-test_type_inst", + name); + + sfree(dd->plugin); + sfree(dd->plugin_instance); + sfree(dd->type); + sfree(dd->type_instance); + sfree(dd); + + return 0; +} + +DEF_TEST(format_name_simple_index) { + netsnmp_variable_list *index_list_tmp = NULL; + oid_t index_oid; + data_definition_t *dd = calloc(1, sizeof(*dd)); + table_definition_t *td = calloc(1, sizeof(*td)); + + td->index_list_cont = NULL; + td->index_keys[0].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[0].type = ASN_OCTET_STR; + td->index_keys[1].source = INDEX_TYPE_INSTANCE; + td->index_keys[1].type = ASN_OCTET_STR; + dd->table = td; + dd->plugin = strdup("test_plugin"); + dd->type = strdup("test_type"); + + char *plugin_inst = strdup("test_plugin_inst"); + char *type_inst = strdup("test_type_inst"); + + snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR, + plugin_inst, strlen(plugin_inst)); + snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR, type_inst, + strlen(type_inst)); + + build_oid_noalloc(index_oid.oid, sizeof(index_oid.oid), &index_oid.oid_len, + NULL, 0, index_list_tmp); + + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL, + 0); + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL, + 0); + + char name[DATA_MAX_NAME_LEN]; + + int ret = snmp_agent_format_name(name, DATA_MAX_NAME_LEN, dd, &index_oid); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR( + "example.com/test_plugin-test_plugin_inst/test_type-test_type_inst", + name); + + snmp_free_varbind(index_list_tmp); + snmp_free_varbind(td->index_list_cont); + sfree(dd->plugin); + sfree(dd->type); + sfree(dd); + sfree(td); + sfree(plugin_inst); + sfree(type_inst); + + return 0; +} + +DEF_TEST(format_name_regex_index) { + netsnmp_variable_list *index_list_tmp = NULL; + oid_t index_oid; + data_definition_t *dd = calloc(1, sizeof(*dd)); + table_definition_t *td = calloc(1, sizeof(*td)); + + td->index_keys_len = 3; + td->index_list_cont = NULL; + td->index_keys[0].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[0].type = ASN_OCTET_STR; + td->index_keys[1].source = INDEX_TYPE_INSTANCE; + td->index_keys[1].type = ASN_INTEGER; + td->index_keys[1].regex = strdup("^vcpu_([0-9]{1,3})-cpu_[0-9]{1,3}$"); + td->index_keys[1].group = 1; + td->index_keys[2].source = INDEX_TYPE_INSTANCE; + td->index_keys[2].type = ASN_INTEGER; + td->index_keys[2].regex = strdup("^vcpu_[0-9]{1,3}-cpu_([0-9]{1,3})$"); + td->index_keys[2].group = 1; + + dd->table = td; + dd->plugin = strdup("test_plugin"); + dd->type = strdup("test_type"); + + char *plugin_inst = strdup("test_plugin_inst"); + char *type_inst = strdup("vcpu_1-cpu_10"); + int vcpu = 1; + int cpu = 10; + + snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_OCTET_STR, + plugin_inst, strlen(plugin_inst)); + snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_INTEGER, &vcpu, 1); + snmp_varlist_add_variable(&index_list_tmp, NULL, 0, ASN_INTEGER, &cpu, 1); + + build_oid_noalloc(index_oid.oid, sizeof(index_oid.oid), &index_oid.oid_len, + NULL, 0, index_list_tmp); + + token_t *token; + int *offset; + + td->tokens[INDEX_TYPE_INSTANCE] = + c_avl_create((int (*)(const void *, const void *))num_compare); + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL, + 0); + + token = malloc(sizeof(*token)); + offset = malloc(sizeof(*offset)); + token->key = snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, + ASN_INTEGER, NULL, 0); + token->str = strdup("vcpu_"); + *offset = 0; + int ret = c_avl_insert(td->tokens[INDEX_TYPE_INSTANCE], (void *)offset, + (void *)token); + + token = malloc(sizeof(*token)); + offset = malloc(sizeof(*offset)); + token->key = snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, + ASN_INTEGER, NULL, 0); + token->str = strdup("-cpu_"); + *offset = 6; + ret += c_avl_insert(td->tokens[INDEX_TYPE_INSTANCE], (void *)offset, + (void *)token); + char name[DATA_MAX_NAME_LEN]; + + ret += snmp_agent_format_name(name, DATA_MAX_NAME_LEN, dd, &index_oid); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR( + "example.com/test_plugin-test_plugin_inst/test_type-vcpu_1-cpu_10", name); + while (c_avl_pick(td->tokens[INDEX_TYPE_INSTANCE], (void **)&offset, + (void **)&token) == 0) { + sfree(offset); + sfree(token->str); + sfree(token); + } + c_avl_destroy(td->tokens[INDEX_TYPE_INSTANCE]); + snmp_free_varbind(index_list_tmp); + snmp_free_varbind(td->index_list_cont); + for (int i = 0; i < td->index_keys_len; i++) + sfree(td->index_keys[i].regex); + sfree(dd->plugin); + sfree(dd->type); + sfree(dd); + sfree(td); + sfree(plugin_inst); + sfree(type_inst); + + return 0; +} + +DEF_TEST(prep_index_list) { + table_definition_t *td = calloc(1, sizeof(*td)); + + assert(td != NULL); + td->index_keys_len = 5; + td->index_keys[0].source = INDEX_HOST; + td->index_keys[0].type = ASN_OCTET_STR; + td->index_keys[1].source = INDEX_PLUGIN; + td->index_keys[1].type = ASN_OCTET_STR; + td->index_keys[2].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[2].type = ASN_INTEGER; + td->index_keys[3].source = INDEX_TYPE; + td->index_keys[3].type = ASN_INTEGER; + td->index_keys[4].source = INDEX_TYPE_INSTANCE; + td->index_keys[4].type = ASN_OCTET_STR; + td->index_list_cont = NULL; + + int ret = snmp_agent_prep_index_list(td, &td->index_list_cont); + EXPECT_EQ_INT(0, ret); + + netsnmp_variable_list *key = td->index_list_cont; + + OK(key != NULL); + EXPECT_EQ_INT(ASN_OCTET_STR, key->type); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(ASN_OCTET_STR, key->type); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(ASN_INTEGER, key->type); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(ASN_INTEGER, key->type); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(ASN_OCTET_STR, key->type); + key = key->next_variable; + OK(key == NULL); + + snmp_free_varbind(td->index_list_cont); + sfree(td); + + return 0; +} + +DEF_TEST(fill_index_list_simple) { + table_definition_t *td = calloc(1, sizeof(*td)); + assert(td != NULL); + + /* Preparing value list */ + value_list_t *vl = calloc(1, sizeof(*vl)); + assert(vl != NULL); + strncpy(vl->host, "test_hostname", DATA_MAX_NAME_LEN); + strncpy(vl->plugin, "test_plugin", DATA_MAX_NAME_LEN); + strncpy(vl->plugin_instance, "test_plugin_inst", DATA_MAX_NAME_LEN); + strncpy(vl->type, "test_type", DATA_MAX_NAME_LEN); + strncpy(vl->type_instance, "test_type_inst", DATA_MAX_NAME_LEN); + + td->index_keys_len = 5; + td->index_keys[0].source = INDEX_HOST; + td->index_keys[0].type = ASN_OCTET_STR; + td->index_keys[1].source = INDEX_PLUGIN; + td->index_keys[1].type = ASN_OCTET_STR; + td->index_keys[2].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[2].type = ASN_OCTET_STR; + td->index_keys[3].source = INDEX_TYPE; + td->index_keys[3].type = ASN_OCTET_STR; + td->index_keys[4].source = INDEX_TYPE_INSTANCE; + td->index_keys[4].type = ASN_OCTET_STR; + + td->index_list_cont = NULL; + for (int i = 0; i < td->index_keys_len; i++) + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, + NULL, 0); + + int ret = snmp_agent_fill_index_list(td, vl); + EXPECT_EQ_INT(0, ret); + + netsnmp_variable_list *key = td->index_list_cont; + + ret = 0; + + OK(key != NULL); + EXPECT_EQ_STR(vl->host, (char *)key->val.string); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_STR(vl->plugin, (char *)key->val.string); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_STR(vl->plugin_instance, (char *)key->val.string); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_STR(vl->type, (char *)key->val.string); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_STR(vl->type_instance, (char *)key->val.string); + key = key->next_variable; + OK(key == NULL); + + snmp_free_varbind(td->index_list_cont); + sfree(vl); + sfree(td); + + return 0; +} + +DEF_TEST(fill_index_list_regex) { + table_definition_t *td = calloc(1, sizeof(*td)); + int ret = 0; + + assert(td != NULL); + + /* Preparing value list */ + value_list_t *vl = calloc(1, sizeof(*vl)); + strncpy(vl->plugin_instance, "test_plugin_inst", DATA_MAX_NAME_LEN); + strncpy(vl->type_instance, "1test2test3", DATA_MAX_NAME_LEN); + + td->index_keys_len = 4; + td->index_keys[0].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[0].type = ASN_OCTET_STR; + td->index_keys[1].source = INDEX_TYPE_INSTANCE; + td->index_keys[1].type = ASN_INTEGER; + td->index_keys[1].regex = strdup("^([0-9])test[0-9]test[0-9]$"); + td->index_keys[1].group = 1; + td->index_keys[2].source = INDEX_TYPE_INSTANCE; + td->index_keys[2].type = ASN_INTEGER; + td->index_keys[2].regex = strdup("^[0-9]test([0-9])test[0-9]$"); + td->index_keys[2].group = 1; + td->index_keys[3].source = INDEX_TYPE_INSTANCE; + td->index_keys[3].type = ASN_INTEGER; + td->index_keys[3].regex = strdup("^[0-9]test[0-9]test([0-9])$"); + td->index_keys[3].group = 1; + + td->index_list_cont = NULL; + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_OCTET_STR, NULL, + 0); + for (int i = 1; i < td->index_keys_len; i++) { + snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, ASN_INTEGER, NULL, + 0); + ret = regcomp(&td->index_keys[i].regex_info, td->index_keys[i].regex, + REG_EXTENDED); + EXPECT_EQ_INT(0, ret); + } + td->tokens[INDEX_TYPE_INSTANCE] = + c_avl_create((int (*)(const void *, const void *))num_compare); + assert(td->tokens[INDEX_TYPE_INSTANCE] != NULL); + + ret = snmp_agent_fill_index_list(td, vl); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, td->tokens_done); + + netsnmp_variable_list *key = td->index_list_cont; + + OK(key != NULL); + EXPECT_EQ_STR(vl->plugin_instance, (char *)key->val.string); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(1, *key->val.integer); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(2, *key->val.integer); + key = key->next_variable; + OK(key != NULL); + EXPECT_EQ_INT(3, *key->val.integer); + key = key->next_variable; + OK(key == NULL); + + token_t *token; + int *offset; + + while (c_avl_pick(td->tokens[INDEX_TYPE_INSTANCE], (void **)&offset, + (void **)&token) == 0) { + sfree(offset); + sfree(token->str); + sfree(token); + } + + c_avl_destroy(td->tokens[INDEX_TYPE_INSTANCE]); + snmp_free_varbind(td->index_list_cont); + sfree(vl); + + for (int i = 0; i < td->index_keys_len; i++) { + sfree(td->index_keys[i].regex); + regfree(&td->index_keys[i].regex_info); + } + sfree(td); + + return 0; +} + +DEF_TEST(config_index_key_source) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + table_definition_t *td = calloc(1, sizeof(*td)); + data_definition_t *dd = calloc(1, sizeof(*dd)); + + assert(ci != NULL); + assert(td != NULL); + assert(dd != NULL); + + ci->values = calloc(1, sizeof(*ci->values)); + assert(ci->values != NULL); + ci->values_num = 1; + ci->values->value.string = strdup("PluginInstance"); + ci->values->type = OCONFIG_TYPE_STRING; + + int ret = snmp_agent_config_index_key_source(td, dd, ci); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, td->index_keys_len); + EXPECT_EQ_INT(0, dd->index_key_pos); + EXPECT_EQ_INT(INDEX_PLUGIN_INSTANCE, td->index_keys[0].source); + EXPECT_EQ_INT(GROUP_UNUSED, td->index_keys[0].group); + OK(td->index_keys[0].regex == NULL); + + sfree(ci->values->value.string); + sfree(ci->values); + sfree(ci); + sfree(td); + sfree(dd); + + return 0; +} + +DEF_TEST(config_index_key_regex) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + table_definition_t *td = calloc(1, sizeof(*td)); + data_definition_t *dd = calloc(1, sizeof(*dd)); + + assert(ci != NULL); + assert(td != NULL); + assert(dd != NULL); + + dd->index_key_pos = 0; + td->index_keys_len = 1; + td->index_keys[0].source = INDEX_PLUGIN_INSTANCE; + td->index_keys[0].group = 1; + ci->values = calloc(1, sizeof(*ci->values)); + assert(ci->values != NULL); + ci->values_num = 1; + ci->values->value.string = strdup("^([0-9])test[0-9]test[0-9]$"); + ci->values->type = OCONFIG_TYPE_STRING; + + int ret = snmp_agent_config_index_key_regex(td, dd, ci); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR(td->index_keys[0].regex, "^([0-9])test[0-9]test[0-9]$"); + OK(td->tokens[INDEX_PLUGIN_INSTANCE] != NULL); + + c_avl_destroy(td->tokens[INDEX_PLUGIN_INSTANCE]); + sfree(ci->values->value.string); + sfree(ci->values); + sfree(ci); + sfree(td->index_keys[0].regex); + regfree(&td->index_keys[0].regex_info); + sfree(td); + sfree(dd); + + return 0; +} + +DEF_TEST(config_index_key) { + oconfig_item_t *ci = calloc(1, sizeof(*ci)); + table_definition_t *td = calloc(1, sizeof(*td)); + data_definition_t *dd = calloc(1, sizeof(*dd)); + + assert(ci != NULL); + assert(td != NULL); + assert(dd != NULL); + + ci->children_num = 3; + ci->children = calloc(1, sizeof(*ci->children) * ci->children_num); + + ci->children[0].key = strdup("Source"); + ci->children[0].parent = ci; + ci->children[0].values_num = 1; + ci->children[0].values = calloc(1, sizeof(*ci->children[0].values)); + assert(ci->children[0].values != NULL); + ci->children[0].values->value.string = strdup("PluginInstance"); + ci->children[0].values->type = OCONFIG_TYPE_STRING; + + ci->children[1].key = strdup("Regex"); + ci->children[1].parent = ci; + ci->children[1].values_num = 1; + ci->children[1].values = calloc(1, sizeof(*ci->children[0].values)); + assert(ci->children[1].values != NULL); + ci->children[1].values->value.string = strdup("^([0-9])test[0-9]test[0-9]$"); + ci->children[1].values->type = OCONFIG_TYPE_STRING; + + ci->children[2].key = strdup("Group"); + ci->children[2].parent = ci; + ci->children[2].values_num = 1; + ci->children[2].values = calloc(1, sizeof(*ci->children[0].values)); + assert(ci->children[2].values != NULL); + ci->children[2].values->value.number = 1; + ci->children[2].values->type = OCONFIG_TYPE_NUMBER; + + int ret = snmp_agent_config_index_key(td, dd, ci); + + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(1, td->index_keys_len); + EXPECT_EQ_INT(0, dd->index_key_pos); + EXPECT_EQ_INT(INDEX_PLUGIN_INSTANCE, td->index_keys[0].source); + EXPECT_EQ_INT(1, td->index_keys[0].group); + EXPECT_EQ_STR("^([0-9])test[0-9]test[0-9]$", td->index_keys[0].regex); + OK(td->tokens[INDEX_PLUGIN_INSTANCE] != NULL); + + sfree(ci->children[0].values->value.string); + sfree(ci->children[0].values); + sfree(ci->children[0].key); + + sfree(ci->children[1].values->value.string); + sfree(ci->children[1].values); + sfree(ci->children[1].key); + + sfree(ci->children[2].values); + sfree(ci->children[2].key); + + sfree(ci->children); + sfree(ci); + + c_avl_destroy(td->tokens[INDEX_PLUGIN_INSTANCE]); + sfree(dd); + sfree(td->index_keys[0].regex); + regfree(&td->index_keys[0].regex_info); + sfree(td); + + return 0; +} + +DEF_TEST(parse_index_key) { + char *regex = strdup("test-([0-9])-([0-9])"); + char *input = strdup("snmp-test-5-6"); + regex_t regex_info; + regmatch_t match; + + assert(regex != NULL); + assert(input != NULL); + + int ret = regcomp(®ex_info, regex, REG_EXTENDED); + EXPECT_EQ_INT(0, ret); + + ret = snmp_agent_parse_index_key(input, regex, ®ex_info, 0, &match); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(5, match.rm_so); + EXPECT_EQ_INT(13, match.rm_eo); + + ret = snmp_agent_parse_index_key(input, regex, ®ex_info, 1, &match); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(10, match.rm_so); + EXPECT_EQ_INT(11, match.rm_eo); + + ret = snmp_agent_parse_index_key(input, regex, ®ex_info, 2, &match); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(12, match.rm_so); + EXPECT_EQ_INT(13, match.rm_eo); + + sfree(regex); + sfree(input); + regfree(®ex_info); + + return 0; +} + +DEF_TEST(create_token) { + c_avl_tree_t *tokens = + c_avl_create((int (*)(const void *, const void *))num_compare); + char *input = strdup("testA1-testB2"); + + assert(tokens != NULL); + assert(input != NULL); + + int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL); + EXPECT_EQ_INT(0, ret); + ret = snmp_agent_create_token(input, 6, 6, tokens, NULL); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(2, c_avl_size(tokens)); + + token_t *token; + int *offset; + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(6, *offset); + EXPECT_EQ_STR("-testB", token->str); + sfree(offset); + sfree(token->str); + sfree(token); + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(0, *offset); + EXPECT_EQ_STR("testA", token->str); + sfree(offset); + sfree(token->str); + sfree(token); + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + OK(ret != 0); + + c_avl_destroy(tokens); + sfree(input); + + return 0; +} + +DEF_TEST(delete_token) { + c_avl_tree_t *tokens = + c_avl_create((int (*)(const void *, const void *))num_compare); + char *input = strdup("testA1-testB2-testC3"); + + assert(tokens != NULL); + assert(input != NULL); + + int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL); + EXPECT_EQ_INT(0, ret); + ret = snmp_agent_create_token(input, 6, 6, tokens, NULL); + EXPECT_EQ_INT(0, ret); + ret = snmp_agent_create_token(input, 13, 6, tokens, NULL); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(3, c_avl_size(tokens)); + ret = snmp_agent_delete_token(6, tokens); + EXPECT_EQ_INT(0, ret); + + token_t *token; + int *offset; + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(0, *offset); + EXPECT_EQ_STR("testA", token->str); + sfree(offset); + sfree(token->str); + sfree(token); + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(13, *offset); + EXPECT_EQ_STR("-testC", token->str); + sfree(offset); + sfree(token->str); + sfree(token); + + ret = c_avl_pick(tokens, (void **)&offset, (void **)&token); + OK(ret != 0); + + c_avl_destroy(tokens); + sfree(input); + + return 0; +} + +DEF_TEST(get_token) { + c_avl_tree_t *tokens = + c_avl_create((int (*)(const void *, const void *))num_compare); + char *input = strdup("testA1-testB2-testC3"); + + assert(tokens != NULL); + assert(input != NULL); + + int ret = snmp_agent_create_token(input, 0, 5, tokens, NULL); + EXPECT_EQ_INT(0, ret); + ret = snmp_agent_create_token(input, 6, 6, tokens, NULL); + EXPECT_EQ_INT(0, ret); + ret = snmp_agent_create_token(input, 13, 6, tokens, NULL); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_INT(3, c_avl_size(tokens)); + ret = snmp_agent_get_token(tokens, 12); + EXPECT_EQ_INT(6, ret); + + token_t *token; + int *offset; + + while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0) { + sfree(offset); + sfree(token->str); + sfree(token); + } + + c_avl_destroy(tokens); + sfree(input); + + return 0; +} + +DEF_TEST(tokenize) { + regmatch_t m[3] = {{5, 6}, /* "1" */ + {12, 13}, /* "2" */ + {19, 20}}; /* "3" */ + c_avl_tree_t *tokens = + c_avl_create((int (*)(const void *, const void *))num_compare); + char *input = strdup("testA1-testB2-testC3"); + token_t *token; + int *offset; + c_avl_iterator_t *it; + int ret; + + assert(tokens != NULL); + assert(input != NULL); + + /* First pass */ + ret = snmp_agent_tokenize(input, tokens, &m[0], NULL); + EXPECT_EQ_INT(0, ret); + it = c_avl_get_iterator(tokens); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("testA", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("-testB2-testC3", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + OK(ret != 0); + c_avl_iterator_destroy(it); + + /* Second pass */ + ret = snmp_agent_tokenize(input, tokens, &m[1], NULL); + EXPECT_EQ_INT(0, ret); + it = c_avl_get_iterator(tokens); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("testA", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("-testB", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("-testC3", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + OK(ret != 0); + c_avl_iterator_destroy(it); + + /* Third pass */ + ret = snmp_agent_tokenize(input, tokens, &m[2], NULL); + EXPECT_EQ_INT(0, ret); + it = c_avl_get_iterator(tokens); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("testA", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("-testB", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("-testC", token->str); + ret = c_avl_iterator_next(it, (void **)&offset, (void **)&token); + OK(ret != 0); + c_avl_iterator_destroy(it); + + while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0) { + sfree(offset); + sfree(token->str); + sfree(token); + } + + c_avl_destroy(tokens); + sfree(input); + + return 0; +} + +DEF_TEST(build_name) { + table_definition_t *td = calloc(1, sizeof(*td)); + c_avl_tree_t *tokens = + c_avl_create((int (*)(const void *, const void *))num_compare); + + assert(tokens != NULL); + assert(td != NULL); + + int n[3] = {1, 2, 3}; + char *t[3] = {"testA", "-testB", "-testC"}; + int off[3] = {0, 6, 13}; + token_t *token; + int *offset; + int ret = 0; + char *name = NULL; + + td->index_list_cont = NULL; + for (int i = 0; i < 3; i++) { + token = malloc(sizeof(*token)); + token->str = t[i]; + token->key = snmp_varlist_add_variable(&td->index_list_cont, NULL, 0, + ASN_INTEGER, &n[i], sizeof(n[i])); + assert(token->key != NULL); + offset = &off[i]; + ret = c_avl_insert(tokens, (void *)offset, (void *)token); + assert(ret == 0); + } + + ret = snmp_agent_build_name(&name, tokens); + EXPECT_EQ_INT(0, ret); + EXPECT_EQ_STR("testA1-testB2-testC3", name); + + while (c_avl_pick(tokens, (void **)&offset, (void **)&token) == 0) + sfree(token); + + c_avl_destroy(tokens); + snmp_free_varbind(td->index_list_cont); + sfree(td); + sfree(name); + return 0; +} + +int main(void) { + /* snmp_agent_oid_to_string */ + RUN_TEST(oid_to_string); + + /* snmp_agent_prep_index_list */ + RUN_TEST(prep_index_list); + + /* snmp_agent_fill_index_list */ + RUN_TEST(fill_index_list_simple); + RUN_TEST(fill_index_list_regex); + + /* snmp_agent_format_name */ + RUN_TEST(format_name_scalar); + RUN_TEST(format_name_simple_index); + RUN_TEST(format_name_regex_index); + + /* snmp_agent_config_index_key_source */ + RUN_TEST(config_index_key_source); + + /* snmp_agent_config_index_key_regex */ + RUN_TEST(config_index_key_regex); + + /* snmp_agent_config_index_key */ + RUN_TEST(config_index_key); + + /*snmp_agent_parse_index_key */ + RUN_TEST(parse_index_key); + + /* snmp_agent_create_token */ + RUN_TEST(create_token); + + /* snmp_agent_delete_token */ + RUN_TEST(delete_token); + + /* snmp_agent_get_token */ + RUN_TEST(get_token); + + /* snmp_agent_tokenize */ + RUN_TEST(tokenize); + + /* snmp_agent_build_name */ + RUN_TEST(build_name); + + END_TEST; +}