DISPATCH-731: support wildcard configured addresses
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/492ef7d4 Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/492ef7d4 Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/492ef7d4 Branch: refs/heads/master Commit: 492ef7d4f3b22a62adf93614aa666383d0c5178b Parents: 704fc28 Author: Kenneth Giusti <kgiu...@apache.org> Authored: Thu Jul 13 11:55:41 2017 -0400 Committer: Kenneth Giusti <kgiu...@apache.org> Committed: Tue Jul 25 16:00:38 2017 -0400 ---------------------------------------------------------------------- include/qpid/dispatch/compose.h | 1 + python/qpid_dispatch/management/qdrouter.json | 10 +- .../qpid_dispatch_internal/management/config.py | 17 +- src/compose.c | 10 +- src/parse_tree.c | 314 ++++++++++++++----- src/parse_tree.h | 64 +++- src/router_config.c | 25 +- src/router_core/agent_config_address.c | 83 ++++- src/router_core/agent_config_address.h | 2 +- src/router_core/connections.c | 21 +- src/router_core/route_tables.c | 1 + src/router_core/router_core.c | 13 +- src/router_core/router_core_private.h | 4 +- src/router_private.h | 1 + tests/system_tests_qdmanage.py | 9 +- 15 files changed, 434 insertions(+), 141 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/include/qpid/dispatch/compose.h ---------------------------------------------------------------------- diff --git a/include/qpid/dispatch/compose.h b/include/qpid/dispatch/compose.h index e808ea2..78bb8d2 100644 --- a/include/qpid/dispatch/compose.h +++ b/include/qpid/dispatch/compose.h @@ -183,6 +183,7 @@ void qd_compose_insert_binary_buffers(qd_composed_field_t *field, qd_buffer_list */ void qd_compose_insert_string(qd_composed_field_t *field, const char *value); void qd_compose_insert_string2(qd_composed_field_t *field, const char *value1, const char *value2); +void qd_compose_insert_string_n(qd_composed_field_t *field, const char *value, size_t len); /** * Insert a utf8-encoded string into the field from an iterator http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/python/qpid_dispatch/management/qdrouter.json ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json index e80359f..1101961 100644 --- a/python/qpid_dispatch/management/qdrouter.json +++ b/python/qpid_dispatch/management/qdrouter.json @@ -951,9 +951,15 @@ "attributes": { "prefix": { "type": "string", - "description": "The address prefix for the configured settings", + "description": "The address prefix for the configured settings. Cannot be used with a pattern attribute.", "create": true, - "required": true + "required": false + }, + "pattern": { + "type": "string", + "description": "A wildcarded pattern for address matching. Incoming addresses are matched against this pattern. Matching addresses use the configured settings. The pattern consists of one or more tokens separated by a forward slash '/'. A token can be one of the following: a * character, a # character, or a sequence of characters that do not include /, *, or #. The * token matches any single token. The # token matches zero or more tokens. * has higher precedence than #, and exact match has the highest precedence. Cannot be used with a prefix attribute.", + "create": true, + "required": false }, "distribution": { "type": ["multicast", "closest", "balanced"], http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/python/qpid_dispatch_internal/management/config.py ---------------------------------------------------------------------- diff --git a/python/qpid_dispatch_internal/management/config.py b/python/qpid_dispatch_internal/management/config.py index 881394b..b8766fb 100644 --- a/python/qpid_dispatch_internal/management/config.py +++ b/python/qpid_dispatch_internal/management/config.py @@ -59,13 +59,22 @@ class Config(object): begin = re.compile(r'([\w-]+)[ \t]*{') # WORD { end = re.compile(r'}') # } attr = re.compile(r'([\w-]+)[ \t]*:[ \t]*(.+)') # WORD1: VALUE + pattern = re.compile(r'([\w-]+)[ \t]*:[ \t]*([\S]+).*') def sub(line): """Do substitutions to make line json-friendly""" - line = line.split('#')[0].strip() # Strip comments - line = re.sub(begin, r'["\1", {', line) - line = re.sub(end, r'}],', line) - line = re.sub(attr, r'"\1": "\2",', line) + line = line.strip() + if line.startswith("#"): + return "" + # 'pattern:' is a special snowflake. It allows '#' characters in + # its value, so they cannot be treated as comment delimiters + if line.split(':')[0].strip().lower() == "pattern": + line = re.sub(pattern, r'"\1": "\2",', line) + else: + line = line.split('#')[0].strip() + line = re.sub(begin, r'["\1", {', line) + line = re.sub(end, r'}],', line) + line = re.sub(attr, r'"\1": "\2",', line) return line js_text = "[%s]"%("\n".join([sub(l) for l in lines])) http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/compose.c ---------------------------------------------------------------------- diff --git a/src/compose.c b/src/compose.c index 01dccbb..2581241 100644 --- a/src/compose.c +++ b/src/compose.c @@ -425,10 +425,8 @@ void qd_compose_insert_binary_buffers(qd_composed_field_t *field, qd_buffer_list } -void qd_compose_insert_string(qd_composed_field_t *field, const char *value) +void qd_compose_insert_string_n(qd_composed_field_t *field, const char *value, size_t len) { - uint32_t len = strlen(value); - if (len < 256) { qd_insert_8(field, QD_AMQP_STR8_UTF8); qd_insert_8(field, (uint8_t) len); @@ -441,6 +439,12 @@ void qd_compose_insert_string(qd_composed_field_t *field, const char *value) } +void qd_compose_insert_string(qd_composed_field_t *field, const char *value) +{ + qd_compose_insert_string_n(field, value, strlen(value)); +} + + void qd_compose_insert_string2(qd_composed_field_t *field, const char *value1, const char *value2) { uint32_t len1 = strlen(value1); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/parse_tree.c ---------------------------------------------------------------------- diff --git a/src/parse_tree.c b/src/parse_tree.c index 07097b2..bd93ed9 100644 --- a/src/parse_tree.c +++ b/src/parse_tree.c @@ -19,7 +19,7 @@ #include "parse_tree.h" - +#include <qpid/dispatch/log.h> // Wildcard pattern matching: // '*' - match exactly one single token @@ -30,7 +30,7 @@ static const char HASH = '#'; // token parsing -// parse a string of tokens separated by a boundary character. +// parse a string of tokens separated by a boundary character (. or /). // // The format of a pattern string is a series of tokens. A // token can contain any characters but '#', '*', or the boundary character. @@ -38,6 +38,7 @@ static const char HASH = '#'; // TODO(kgiusti): really should create a token parsing qd_iterator_t view. // Then all this token/token_iterator stuff can be removed... // +const char * const QD_PARSE_TREE_TOKEN_SEP = "./"; typedef struct token { const char *begin; const char *end; @@ -48,18 +49,17 @@ typedef struct token { typedef struct token_iterator { token_t token; // token at head of string const char *terminator; // end of entire string - char separator; // delimits tokens } token_iterator_t; -static void token_iterator_init(token_iterator_t *t, const char *str, - char separator) +static void token_iterator_init(token_iterator_t *t, const char *str) { - const char *tend = strchr(str, separator); + while (*str && strchr(QD_PARSE_TREE_TOKEN_SEP, *str)) + str++; // skip any leading separators + const char *tend = strpbrk(str, QD_PARSE_TREE_TOKEN_SEP); t->terminator = str + strlen(str); t->token.begin = str; t->token.end = (tend) ? tend : t->terminator; - t->separator = separator; } @@ -70,7 +70,7 @@ static void token_iterator_next(token_iterator_t *t) } else { const char *tend; t->token.begin = t->token.end + 1; - tend = strchr(t->token.begin, t->separator); + tend = strpbrk(t->token.begin, QD_PARSE_TREE_TOKEN_SEP); t->token.end = (tend) ? tend : t->terminator; } } @@ -107,17 +107,21 @@ static bool token_iterator_match_char(const token_iterator_t *t, // #.* ---> *.# // #.# ---> # // -static char *normalize_pattern(char *pattern, char token_sep) +static bool normalize_pattern(char *pattern) { token_iterator_t t; + bool modified = false; + char *original = NULL; - token_iterator_init(&t, pattern, token_sep); + token_iterator_init(&t, pattern); while (!token_iterator_done(&t)) { if (token_iterator_match_char(&t, HASH)) { token_t last_token; token_iterator_pop(&t, &last_token); if (token_iterator_done(&t)) break; if (token_iterator_match_char(&t, HASH)) { // #.# --> # + if (!modified) original = strdup(pattern); + modified = true; char *src = (char *)t.token.begin; char *dest = (char *)last_token.begin; // note: overlapping strings, can't strcpy @@ -127,6 +131,8 @@ static char *normalize_pattern(char *pattern, char token_sep) t.terminator = dest; t.token = last_token; } else if (token_iterator_match_char(&t, STAR)) { // #.* --> *.# + if (!modified) original = strdup(pattern); + modified = true; *(char *)t.token.begin = HASH; *(char *)last_token.begin = STAR; } else { @@ -136,7 +142,15 @@ static char *normalize_pattern(char *pattern, char token_sep) token_iterator_next(&t); } } - return pattern; + + if (original) { + qd_log(qd_log_source("DEFAULT"), QD_LOG_NOTICE, + "configured address '%s' optimized and re-written to '%s'", + original, pattern); + free(original); + } + + return modified; } @@ -156,33 +170,31 @@ static char *normalize_pattern(char *pattern, char token_sep) // +-->x-->y-->... // -DEQ_DECLARE(parse_node_t, parse_node_list_t); -struct parse_node { - DEQ_LINKS(parse_node_t); // siblings +DEQ_DECLARE(qd_parse_node_t, qd_parse_node_list_t); +struct qd_parse_node { + DEQ_LINKS(qd_parse_node_t); // siblings char *token; // portion of pattern represented by this node - char token_sep; // boundary character between tokens bool is_star; bool is_hash; char *pattern; // entire normalized pattern matching this node - parse_node_list_t children; - struct parse_node *star_child; - struct parse_node *hash_child; + qd_parse_node_list_t children; + struct qd_parse_node *star_child; + struct qd_parse_node *hash_child; void *payload; // data returned on match against this node }; -ALLOC_DECLARE(parse_node_t); -ALLOC_DEFINE(parse_node_t); +ALLOC_DECLARE(qd_parse_node_t); +ALLOC_DEFINE(qd_parse_node_t); -static parse_node_t *new_parse_node(const token_t *t, char token_sep) +static qd_parse_node_t *new_parse_node(const token_t *t) { - parse_node_t *n = new_parse_node_t(); + qd_parse_node_t *n = new_qd_parse_node_t(); if (n) { DEQ_ITEM_INIT(n); DEQ_INIT(n->children); n->payload = NULL; n->pattern = NULL; n->star_child = n->hash_child = NULL; - n->token_sep = token_sep; if (t) { const size_t tlen = TOKEN_LEN(*t); @@ -201,27 +213,27 @@ static parse_node_t *new_parse_node(const token_t *t, char token_sep) // return count of child nodes -static int parse_node_child_count(const parse_node_t *n) +static int parse_node_child_count(const qd_parse_node_t *n) { return DEQ_SIZE(n->children) - + n->star_child ? 1 : 0 - + n->hash_child ? 1 : 0; + + (n->star_child ? 1 : 0) + + (n->hash_child ? 1 : 0); } -static void free_parse_node(parse_node_t *n) +static void free_parse_node(qd_parse_node_t *n) { assert(parse_node_child_count(n) == 0); free(n->token); free(n->pattern); - free_parse_node_t(n); + free_qd_parse_node_t(n); } // find immediate child node matching token -static parse_node_t *parse_node_find_child(const parse_node_t *node, const token_t *token) +static qd_parse_node_t *parse_node_find_child(const qd_parse_node_t *node, const token_t *token) { - parse_node_t *child = DEQ_HEAD(node->children); + qd_parse_node_t *child = DEQ_HEAD(node->children); while (child && !token_match_str(token, child->token)) child = DEQ_NEXT(child); return child; @@ -231,7 +243,7 @@ static parse_node_t *parse_node_find_child(const parse_node_t *node, const token // Add a new pattern and associated data to the tree. Returns the old payload // if the pattern has already been added to the tree. // -static void *parse_node_add_pattern(parse_node_t *node, +static void *parse_node_add_pattern(qd_parse_node_t *node, token_iterator_t *key, const char *pattern, void *payload) @@ -250,8 +262,7 @@ static void *parse_node_add_pattern(parse_node_t *node, if (token_iterator_match_char(key, STAR)) { if (!node->star_child) { - node->star_child = new_parse_node(&key->token, - node->token_sep); + node->star_child = new_parse_node(&key->token); } token_iterator_next(key); return parse_node_add_pattern(node->star_child, @@ -260,8 +271,7 @@ static void *parse_node_add_pattern(parse_node_t *node, payload); } else if (token_iterator_match_char(key, HASH)) { if (!node->hash_child) { - node->hash_child = new_parse_node(&key->token, - node->token_sep); + node->hash_child = new_parse_node(&key->token); } token_iterator_next(key); return parse_node_add_pattern(node->hash_child, @@ -273,14 +283,14 @@ static void *parse_node_add_pattern(parse_node_t *node, token_t current; token_iterator_pop(key, ¤t); - parse_node_t *child = parse_node_find_child(node, ¤t); + qd_parse_node_t *child = parse_node_find_child(node, ¤t); if (child) { return parse_node_add_pattern(child, key, pattern, payload); } else { - child = new_parse_node(¤t, node->token_sep); + child = new_parse_node(¤t); DEQ_INSERT_TAIL(node->children, child); return parse_node_add_pattern(child, key, @@ -292,7 +302,7 @@ static void *parse_node_add_pattern(parse_node_t *node, // remove pattern from the tree. // return the payload of the removed pattern -static void *parse_node_remove_pattern(parse_node_t *node, +static void *parse_node_remove_pattern(qd_parse_node_t *node, token_iterator_t *key, const char *pattern) { @@ -327,7 +337,7 @@ static void *parse_node_remove_pattern(parse_node_t *node, } else { token_t current; token_iterator_pop(key, ¤t); - parse_node_t *child = parse_node_find_child(node, ¤t); + qd_parse_node_t *child = parse_node_find_child(node, ¤t); if (child) { old = parse_node_remove_pattern(child, key, pattern); if (child->pattern == NULL @@ -341,14 +351,53 @@ static void *parse_node_remove_pattern(parse_node_t *node, } -static bool parse_node_find(parse_node_t *, token_iterator_t *, - parse_tree_visit_t *, void *); +// Find the pattern in the tree, return the payload pointer or NULL if pattern +// is not in the tree +static qd_parse_node_t *parse_node_get_pattern(qd_parse_node_t *node, + token_iterator_t *key, + const char *pattern) +{ + if (!node) + return NULL; + + if (token_iterator_done(key)) + return node; + + if (token_iterator_match_char(key, STAR)) { + token_iterator_next(key); + return parse_node_get_pattern(node->star_child, + key, + pattern); + } else if (token_iterator_match_char(key, HASH)) { + token_iterator_next(key); + return parse_node_get_pattern(node->hash_child, + key, + pattern); + } else { + // check the children nodes + token_t current; + token_iterator_pop(key, ¤t); + + qd_parse_node_t *child = parse_node_find_child(node, ¤t); + if (child) { + return parse_node_get_pattern(child, + key, + pattern); + } + } + + return NULL; // not found +} + + +static bool parse_node_find(qd_parse_node_t *, token_iterator_t *, + qd_parse_tree_visit_t *, void *); // search the sub-trees of this node. // This function returns false to stop the search -static bool parse_node_find_children(parse_node_t *node, token_iterator_t *value, - parse_tree_visit_t *callback, void *handle) +static bool parse_node_find_children(qd_parse_node_t *node, token_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { if (!token_iterator_done(value)) { @@ -358,7 +407,7 @@ static bool parse_node_find_children(parse_node_t *node, token_iterator_t *value token_t child_token; token_iterator_pop(&tmp, &child_token); - parse_node_t *child = parse_node_find_child(node, &child_token); + qd_parse_node_t *child = parse_node_find_child(node, &child_token); if (child) { if (!parse_node_find(child, &tmp, callback, handle)) return false; @@ -385,8 +434,8 @@ static bool parse_node_find_children(parse_node_t *node, token_iterator_t *value // This node matched the last token. // This function returns false to stop the search -static bool parse_node_find_token(parse_node_t *node, token_iterator_t *value, - parse_tree_visit_t *callback, void *handle) +static bool parse_node_find_token(qd_parse_node_t *node, token_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { if (token_iterator_done(value) && node->pattern) { // exact match current node @@ -401,8 +450,8 @@ static bool parse_node_find_token(parse_node_t *node, token_iterator_t *value, // this node matches any one token // returns false to stop the search -static bool parse_node_find_star(parse_node_t *node, token_iterator_t *value, - parse_tree_visit_t *callback, void *handle) +static bool parse_node_find_star(qd_parse_node_t *node, token_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { // must match exactly one token: if (token_iterator_done(value)) @@ -424,8 +473,8 @@ static bool parse_node_find_star(parse_node_t *node, token_iterator_t *value, // current node is hash, match the remainder of the string // return false to stop search -static bool parse_node_find_hash(parse_node_t *node, token_iterator_t *value, - parse_tree_visit_t *callback, void *handle) +static bool parse_node_find_hash(qd_parse_node_t *node, token_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { // consume each token and look for a match on the // remaining key. @@ -444,8 +493,8 @@ static bool parse_node_find_hash(parse_node_t *node, token_iterator_t *value, // find the payload associated with the input string 'value' -static bool parse_node_find(parse_node_t *node, token_iterator_t *value, - parse_tree_visit_t *callback, void *handle) +static bool parse_node_find(qd_parse_node_t *node, token_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { if (node->is_star) return parse_node_find_star(node, value, callback, handle); @@ -456,22 +505,16 @@ static bool parse_node_find(parse_node_t *node, token_iterator_t *value, } -parse_node_t *parse_tree_new(char token_sep) -{ - return new_parse_node(NULL, token_sep); -} - - -void parse_tree_free(parse_node_t *node) +static void parse_node_free(qd_parse_node_t *node) { if (node) { - if (node->star_child) parse_tree_free(node->star_child); - if (node->hash_child) parse_tree_free(node->hash_child); + if (node->star_child) parse_node_free(node->star_child); + if (node->hash_child) parse_node_free(node->hash_child); node->star_child = node->hash_child = NULL; while (!DEQ_IS_EMPTY(node->children)) { - parse_node_t *child = DEQ_HEAD(node->children); + qd_parse_node_t *child = DEQ_HEAD(node->children); DEQ_REMOVE_HEAD(node->children); - parse_tree_free(child); + parse_node_free(child); } free_parse_node(node); @@ -479,46 +522,151 @@ void parse_tree_free(parse_node_t *node) } -// Invoke callback for each pattern that matches 'value -void parse_tree_find(parse_node_t *node, const char *value, - parse_tree_visit_t *callback, void *handle) +qd_parse_node_t *qd_parse_tree_new() +{ + return new_parse_node(NULL); +} + + +// find best match for value +// +static bool get_first(void *handle, const char *pattern, void *payload) +{ + *(void **)handle = payload; + return false; +} + +bool qd_parse_tree_retrieve_match(qd_parse_node_t *node, + const qd_iterator_t *value, + void **payload) +{ + *payload = NULL; + qd_parse_tree_search(node, value, get_first, payload); + return *payload != NULL; +} + + +// Invoke callback for each pattern that matches 'value' +void qd_parse_tree_search(qd_parse_node_t *node, + const qd_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle) { token_iterator_t t_iter; + // @TODO(kgiusti) for now: + qd_iterator_t *dup = qd_iterator_dup(value); + char *str = (char *)qd_iterator_copy(dup); - token_iterator_init(&t_iter, value, node->token_sep); + normalize_pattern(str); + + token_iterator_init(&t_iter, str); parse_node_find(node, &t_iter, callback, handle); + + free(str); + qd_iterator_free(dup); } // returns old payload or NULL if new -void *parse_tree_add_pattern(parse_node_t *node, - const char *pattern, - void *payload) +void *qd_parse_tree_add_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern, + void *payload) { token_iterator_t key; void *rc = NULL; - char *norm = normalize_pattern(strdup(pattern), - node->token_sep); - - token_iterator_init(&key, norm, node->token_sep); - rc = parse_node_add_pattern(node, &key, pattern, payload); - free(norm); + // @TODO(kgiusti) for now: + qd_iterator_t *dup = qd_iterator_dup(pattern); + char *str = (char *)qd_iterator_copy(dup); + + normalize_pattern(str); + qd_log(qd_log_source("DEFAULT"), QD_LOG_DEBUG, + "Parse tree add address pattern '%s'", str); + + token_iterator_init(&key, str); + rc = parse_node_add_pattern(node, &key, str, payload); + free(str); + qd_iterator_free(dup); return rc; } +// returns true if pattern exists in tree +bool qd_parse_tree_get_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern, + void **payload) +{ + token_iterator_t key; + qd_parse_node_t *found = NULL; + // @TODO(kgiusti) for now: + qd_iterator_t *dup = qd_iterator_dup(pattern); + char *str = (char *)qd_iterator_copy(dup); + + normalize_pattern((char *)str); + qd_log(qd_log_source("DEFAULT"), QD_LOG_DEBUG, + "Parse tree get address pattern '%s'", str); + + token_iterator_init(&key, str); + found = parse_node_get_pattern(node, &key, str); + free(str); + qd_iterator_free(dup); + *payload = found ? found->payload : NULL; + return *payload != NULL; +} + + // returns the payload void * -void *parse_tree_remove_pattern(parse_node_t *node, - const char *pattern) +void *qd_parse_tree_remove_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern) { token_iterator_t key; void *rc = NULL; - char *norm = normalize_pattern(strdup(pattern), - node->token_sep); + // @TODO(kgiusti) for now: + qd_iterator_t *dup = qd_iterator_dup(pattern); + char *str = (char *)qd_iterator_copy(dup); - token_iterator_init(&key, norm, node->token_sep); - rc = parse_node_remove_pattern(node, &key, pattern); - free(norm); + normalize_pattern(str); + qd_log(qd_log_source("DEFAULT"), QD_LOG_DEBUG, + "Parse tree remove address pattern '%s'", str); + + token_iterator_init(&key, str); + rc = parse_node_remove_pattern(node, &key, str); + free(str); return rc; } + +#if 0 +#include <stdio.h> +void qd_parse_tree_dump(qd_parse_node_t *node, int depth) +{ + for (int i = 0; i < depth; i++) + fprintf(stdout, " "); + fprintf(stdout, "token: %s pattern: %s is star: %s is hash: %s # childs: %d\n", + node->token ? node->token : "*NONE*", + node->pattern ? node->pattern: "*NONE*", + node->is_star ? "yes" : "no", + node->is_hash ? "yes" : "no", + parse_node_child_count(node)); + if (node->star_child) qd_parse_tree_dump(node->star_child, depth + 1); + if (node->hash_child) qd_parse_tree_dump(node->hash_child, depth + 1); + qd_parse_node_t *child = DEQ_HEAD(node->children); + while (child) { + qd_parse_tree_dump(child, depth + 1); + child = DEQ_NEXT(child); + } +} + +void qd_parse_tree_free(qd_parse_node_t *node) +{ + fprintf(stdout, "PARSE TREE DUMP\n"); + qd_parse_tree_dump(node, 4); + parse_node_free(node); +} + +#else + +void qd_parse_tree_free(qd_parse_node_t *node) +{ + parse_node_free(node); +} +#endif + http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/parse_tree.h ---------------------------------------------------------------------- diff --git a/src/parse_tree.h b/src/parse_tree.h index 62917bc..d616e48 100644 --- a/src/parse_tree.h +++ b/src/parse_tree.h @@ -22,28 +22,64 @@ #include <stdbool.h> #include <qpid/dispatch/ctools.h> +#include <qpid/dispatch/iterator.h> #include "alloc.h" -typedef struct parse_node parse_node_t; +typedef struct qd_parse_node qd_parse_node_t; +extern const char * const QD_PARSE_TREE_TOKEN_SEP; -parse_node_t *parse_tree_new(char token_sep); -void parse_tree_free(parse_node_t *node); +qd_parse_node_t *qd_parse_tree_new(); +void qd_parse_tree_free(qd_parse_node_t *node); -// return false to stop searching -typedef bool parse_tree_visit_t(void *handle, - const char *pattern, +// returns old payload or NULL if new +void *qd_parse_tree_add_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern, void *payload); -void parse_tree_find(parse_node_t *node, const char *value, - parse_tree_visit_t *callback, void *handle); +// returns old payload or NULL if not present +void *qd_parse_tree_remove_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern); -void *parse_tree_add_pattern(parse_node_t *node, - const char *pattern, - void *payload); +// retrieves the payload pointer +// returns true if pattern found +bool qd_parse_tree_get_pattern(qd_parse_node_t *node, + const qd_iterator_t *pattern, + void **payload); + +// find the 'best' match to 'value', using the following precedence (highest +// first): +// +// 1) exact token match +// 2) * wildcard match +// 3) # wildcard match +// +// example: +// given patterns +// 1) 'a.b.c' +// 2) 'a.b.*' +// 3)'a.b.#' +// +// 'a.b.c' will match 1 +// 'a.b.x' will match 2 +// 'a.b' and 'a.b.c.x' will match 3 +// +// returns true on match and sets *payload +bool qd_parse_tree_retrieve_match(qd_parse_node_t *node, + const qd_iterator_t *value, + void **payload); + +// parse tree traversal +// visit each matching pattern that matches value in the order based on the +// above precedence rules + +// return false to stop tree transversal +typedef bool qd_parse_tree_visit_t(void *handle, + const char *pattern, + void *payload); + +void qd_parse_tree_search(qd_parse_node_t *node, const qd_iterator_t *value, + qd_parse_tree_visit_t *callback, void *handle); -// returns the payload void * -void *parse_tree_remove_pattern(parse_node_t *node, - const char *pattern); #endif /* parse_tree.h */ http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_config.c ---------------------------------------------------------------------- diff --git a/src/router_config.c b/src/router_config.c index a0b68c4..fd1fd7b 100644 --- a/src/router_config.c +++ b/src/router_config.c @@ -180,14 +180,29 @@ qd_error_t qd_router_configure_lrp(qd_router_t *router, qd_entity_t *entity) qd_error_t qd_router_configure_address(qd_router_t *router, qd_entity_t *entity) { char *name = 0; - char *prefix = 0; + char *pattern = 0; char *distrib = 0; + char *prefix = 0; do { name = qd_entity_opt_string(entity, "name", 0); QD_ERROR_BREAK(); - prefix = qd_entity_get_string(entity, "prefix"); QD_ERROR_BREAK(); distrib = qd_entity_opt_string(entity, "distribution", 0); QD_ERROR_BREAK(); + pattern = qd_entity_opt_string(entity, "pattern", 0); + prefix = qd_entity_opt_string(entity, "prefix", 0); + + if (prefix && pattern) { + qd_log(router->log_source, QD_LOG_WARNING, + "Cannot set both 'prefix' and 'pattern': ignoring %s, %s", + prefix, pattern); + break; + } else if (!prefix && !pattern) { + qd_log(router->log_source, QD_LOG_WARNING, + "Must set either 'prefix' or 'pattern' attribute:" + " ignoring configured address"); + break; + } + bool waypoint = qd_entity_opt_bool(entity, "waypoint", false); long in_phase = qd_entity_opt_long(entity, "ingressPhase", -1); long out_phase = qd_entity_opt_long(entity, "egressPhase", -1); @@ -208,6 +223,11 @@ qd_error_t qd_router_configure_address(qd_router_t *router, qd_entity_t *entity) qd_compose_insert_string(body, prefix); } + if (pattern) { + qd_compose_insert_string(body, "pattern"); + qd_compose_insert_string(body, pattern); + } + if (distrib) { qd_compose_insert_string(body, "distribution"); qd_compose_insert_string(body, distrib); @@ -235,6 +255,7 @@ qd_error_t qd_router_configure_address(qd_router_t *router, qd_entity_t *entity) free(name); free(prefix); free(distrib); + free(pattern); return qd_error_code(); } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/agent_config_address.c ---------------------------------------------------------------------- diff --git a/src/router_core/agent_config_address.c b/src/router_core/agent_config_address.c index c10f80e..f73ecf3 100644 --- a/src/router_core/agent_config_address.c +++ b/src/router_core/agent_config_address.c @@ -30,6 +30,7 @@ #define QDR_CONFIG_ADDRESS_WAYPOINT 5 #define QDR_CONFIG_ADDRESS_IN_PHASE 6 #define QDR_CONFIG_ADDRESS_OUT_PHASE 7 +#define QDR_CONFIG_ADDRESS_PATTERN 8 const char *qdr_config_address_columns[] = {"name", @@ -40,6 +41,7 @@ const char *qdr_config_address_columns[] = "waypoint", "ingressPhase", "egressPhase", + "pattern", 0}; const char *CONFIG_ADDRESS_TYPE = "org.apache.qpid.dispatch.router.config.address"; @@ -47,7 +49,6 @@ const char *CONFIG_ADDRESS_TYPE = "org.apache.qpid.dispatch.router.config.addres static void qdr_config_address_insert_column_CT(qdr_address_config_t *addr, int col, qd_composed_field_t *body, bool as_map) { const char *text = 0; - const char *key; if (as_map) qd_compose_insert_string(body, qdr_config_address_columns[col]); @@ -72,9 +73,20 @@ static void qdr_config_address_insert_column_CT(qdr_address_config_t *addr, int break; case QDR_CONFIG_ADDRESS_PREFIX: - key = (const char*) qd_hash_key_by_handle(addr->hash_handle); - if (key && key[0] == 'Z') - qd_compose_insert_string(body, &key[1]); + if (addr->is_prefix && addr->pattern) { + // Note (kgiusti): internally we prepend a '/#' to the configured + // prefix and treat it like a pattern. Remove trailing '/#' to put + // it back into its original form + const size_t len = strlen(addr->pattern); + assert(len > 1); + qd_compose_insert_string_n(body, addr->pattern, len - 2); + } else + qd_compose_insert_null(body); + break; + + case QDR_CONFIG_ADDRESS_PATTERN: + if (!addr->is_prefix && addr->pattern) + qd_compose_insert_string(body, addr->pattern); else qd_compose_insert_null(body); break; @@ -284,11 +296,16 @@ void qdra_config_address_delete_CT(qdr_core_t *core, qdr_agent_enqueue_response_CT(core, query); } +void qd_parse_tree_dump(qd_parse_node_t *node, int depth); + void qdra_config_address_create_CT(qdr_core_t *core, qd_iterator_t *name, qdr_query_t *query, qd_parsed_field_t *in_body) { + qd_iterator_t *iter = NULL; + char *buf = NULL; + while (true) { // // Ensure there isn't a duplicate name and that the body is a map @@ -318,36 +335,63 @@ void qdra_config_address_create_CT(qdr_core_t *core, // Extract the fields from the request // qd_parsed_field_t *prefix_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_PREFIX]); + qd_parsed_field_t *pattern_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_PATTERN]); qd_parsed_field_t *distrib_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_DISTRIBUTION]); qd_parsed_field_t *waypoint_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_WAYPOINT]); qd_parsed_field_t *in_phase_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_IN_PHASE]); qd_parsed_field_t *out_phase_field = qd_parse_value_by_key(in_body, qdr_config_address_columns[QDR_CONFIG_ADDRESS_OUT_PHASE]); // - // Prefix field is mandatory. Fail if it is not here. + // Either a prefix or a pattern field is mandatory. Prefix and pattern + // are mutually exclusive. Fail if either both or none are given. // - if (!prefix_field) { + if ((!prefix_field && !pattern_field) || (prefix_field && pattern_field)) { + query->status = QD_AMQP_BAD_REQUEST; + query->status.description = prefix_field ? "cannot specify both prefix and pattern" + : "a prefix or pattern field is mandatory"; + qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_ADDRESS_TYPE, query->status.description); + break; + } + + // strip leading and trailing token separators + qd_iterator_t *tmp = qd_iterator_dup(qd_parse_raw(prefix_field ? prefix_field : pattern_field)); + const int len = qd_iterator_length(tmp); + buf = malloc(len + 3); // +'/#' if needed + char *pattern = qd_iterator_strncpy(tmp, buf, len + 1); + qd_iterator_free(tmp); + + while (*pattern && strchr(QD_PARSE_TREE_TOKEN_SEP, *pattern)) + pattern++; + while (*pattern && strchr(QD_PARSE_TREE_TOKEN_SEP, pattern[strlen(pattern) - 1])) + pattern[strlen(pattern) - 1] = '\0'; + + if (!*pattern) { query->status = QD_AMQP_BAD_REQUEST; - query->status.description = "prefix field is mandatory"; + query->status.description = "invalid address pattern"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_ADDRESS_TYPE, query->status.description); break; } + if (prefix_field) { + // convert the old prefix value into a pattern by appending '/#' + // (match zero or more tokens) + strcat(pattern, "/#"); + } + + iter = qd_iterator_string(pattern, ITER_VIEW_ALL); + // - // Ensure that there isn't another configured address with the same prefix + // Ensure that there isn't another configured address with the same pattern // - qd_iterator_t *iter = qd_parse_raw(prefix_field); - qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH); - qd_iterator_annotate_prefix(iter, 'Z'); - addr = 0; - qd_hash_retrieve(core->addr_hash, iter, (void**) &addr); - if (!!addr) { + + if (qd_parse_tree_get_pattern(core->addr_parse_tree, iter, (void **)&addr)) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = "Address prefix conflicts with an existing entity"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_ADDRESS_TYPE, query->status.description); break; } + bool waypoint = waypoint_field ? qd_parse_as_bool(waypoint_field) : false; int in_phase = in_phase_field ? qd_parse_as_int(in_phase_field) : -1; int out_phase = out_phase_field ? qd_parse_as_int(out_phase_field) : -1; @@ -374,6 +418,7 @@ void qdra_config_address_create_CT(qdr_core_t *core, // // The request is good. Create the entity and insert it into the hash index and list. // + addr = new_qdr_address_config_t(); DEQ_ITEM_INIT(addr); addr->name = name ? (char*) qd_iterator_copy(name) : 0; @@ -381,8 +426,11 @@ void qdra_config_address_create_CT(qdr_core_t *core, addr->treatment = qdra_address_treatment_CT(distrib_field); addr->in_phase = in_phase; addr->out_phase = out_phase; + addr->pattern = (char *) qd_iterator_copy(iter); + addr->is_prefix = !!prefix_field; - qd_hash_insert(core->addr_hash, iter, addr, &addr->hash_handle); + qd_iterator_reset_view(iter, ITER_VIEW_ALL); + qd_parse_tree_add_pattern(core->addr_parse_tree, iter, addr); DEQ_INSERT_TAIL(core->addr_config, addr); // @@ -412,6 +460,11 @@ void qdra_config_address_create_CT(qdr_core_t *core, qdr_agent_enqueue_response_CT(core, query); } else qdr_query_free(query); + + if (buf) { + free(buf); + qd_iterator_free(iter); + } } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/agent_config_address.h ---------------------------------------------------------------------- diff --git a/src/router_core/agent_config_address.h b/src/router_core/agent_config_address.h index f4caf27..9b4331a 100644 --- a/src/router_core/agent_config_address.h +++ b/src/router_core/agent_config_address.h @@ -33,7 +33,7 @@ void qdra_config_address_get_CT(qdr_core_t *core, qdr_query_t *query, const char *qdr_config_address_columns[]); -#define QDR_CONFIG_ADDRESS_COLUMN_COUNT 8 +#define QDR_CONFIG_ADDRESS_COLUMN_COUNT 9 const char *qdr_config_address_columns[QDR_CONFIG_ADDRESS_COLUMN_COUNT + 1]; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/connections.c ---------------------------------------------------------------------- diff --git a/src/router_core/connections.c b/src/router_core/connections.c index df2f69e..7db6ecb 100644 --- a/src/router_core/connections.c +++ b/src/router_core/connections.c @@ -857,20 +857,19 @@ static char qdr_prefix_for_dir(qd_direction_t dir) return (dir == QD_INCOMING) ? 'C' : 'D'; } - qd_address_treatment_t qdr_treatment_for_address_CT(qdr_core_t *core, qdr_connection_t *conn, qd_iterator_t *iter, int *in_phase, int *out_phase) { qdr_address_config_t *addr = 0; + qd_iterator_view_t old_view = qd_iterator_get_view(iter); - // - // Set the prefix to 'Z' for configuration and do a prefix-retrieve to get the most - // specific match - // - qd_iterator_annotate_prefix(iter, 'Z'); + // @TODO(kgiusti) - why not lookup exact match in has first?? + qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_WITH_SPACE); if (conn && conn->tenant_space) qd_iterator_annotate_space(iter, conn->tenant_space, conn->tenant_space_len); - qd_hash_retrieve_prefix(core->addr_hash, iter, (void**) &addr); + qd_parse_tree_retrieve_match(core->addr_parse_tree, iter, (void **) &addr); qd_iterator_annotate_prefix(iter, '\0'); + qd_iterator_reset_view(iter, old_view); + if (in_phase) *in_phase = addr ? addr->in_phase : 0; if (out_phase) *out_phase = addr ? addr->out_phase : 0; @@ -905,11 +904,11 @@ qd_address_treatment_t qdr_treatment_for_address_hash_CT(qdr_core_t *core, qd_it // // Handle the mobile address case // - copy[1] = 'Z'; - qd_iterator_t *config_iter = qd_iterator_string(©[1], ITER_VIEW_ALL); - qdr_address_config_t *addr = 0; - qd_hash_retrieve_prefix(core->addr_hash, config_iter, (void**) &addr); + // @TODO(kgiusti) ask ted if tenant needs to be added first! + qd_iterator_t *config_iter = qd_iterator_string(©[2], ITER_VIEW_ADDRESS_WITH_SPACE); + qdr_address_config_t *addr = 0; + qd_parse_tree_retrieve_match(core->addr_parse_tree, config_iter, (void **) &addr); if (addr) trt = addr->treatment; qd_iterator_free(config_iter); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/route_tables.c ---------------------------------------------------------------------- diff --git a/src/router_core/route_tables.c b/src/router_core/route_tables.c index 1110985..c42aad0 100644 --- a/src/router_core/route_tables.c +++ b/src/router_core/route_tables.c @@ -220,6 +220,7 @@ void qdr_route_table_setup_CT(qdr_core_t *core) core->addr_hash = qd_hash(12, 32, 0); core->conn_id_hash = qd_hash(6, 4, 0); core->cost_epoch = 1; + core->addr_parse_tree = qd_parse_tree_new("/"); if (core->router_mode == QD_ROUTER_MODE_INTERIOR) { core->hello_addr = qdr_add_local_address_CT(core, 'L', "qdhello", QD_TREATMENT_MULTICAST_FLOOD); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/router_core.c ---------------------------------------------------------------------- diff --git a/src/router_core/router_core.c b/src/router_core/router_core.c index 76e2270..13e0e62 100644 --- a/src/router_core/router_core.c +++ b/src/router_core/router_core.c @@ -138,7 +138,7 @@ void qdr_core_free(qdr_core_t *core) qdr_core_remove_address_config(core, addr_config); } qd_hash_free(core->addr_hash); - + qd_parse_tree_free(core->addr_parse_tree); qd_hash_free(core->conn_id_hash); //TODO what about the actual connection identifier objects? @@ -324,15 +324,20 @@ void qdr_core_remove_address(qdr_core_t *core, qdr_address_t *addr) void qdr_core_remove_address_config(qdr_core_t *core, qdr_address_config_t *addr) { - // Remove the address from the list and the hash index. - qd_hash_remove_by_handle(core->addr_hash, addr->hash_handle); + qdr_address_config_t *tmp; + qd_iterator_t *pattern = qd_iterator_string(addr->pattern, ITER_VIEW_ALL); + + // Remove the address from the list and the parse tree DEQ_REMOVE(core->addr_config, addr); + qd_parse_tree_get_pattern(core->addr_parse_tree, pattern, (void **) &tmp); + assert(tmp == addr); // Free resources associated with this address. if (addr->name) { free(addr->name); } - qd_hash_handle_free(addr->hash_handle); + qd_iterator_free(pattern); + free(addr->pattern); free_qdr_address_config_t(addr); } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_core/router_core_private.h ---------------------------------------------------------------------- diff --git a/src/router_core/router_core_private.h b/src/router_core/router_core_private.h index 61e5afc..da36b14 100644 --- a/src/router_core/router_core_private.h +++ b/src/router_core/router_core_private.h @@ -477,9 +477,10 @@ void qdr_core_remove_address(qdr_core_t *core, qdr_address_t *addr); struct qdr_address_config_t { DEQ_LINKS(qdr_address_config_t); - qd_hash_handle_t *hash_handle; char *name; uint64_t identity; + char *pattern; + bool is_prefix; qd_address_treatment_t treatment; int in_phase; int out_phase; @@ -661,6 +662,7 @@ struct qdr_core_t { qd_hash_t *conn_id_hash; qdr_address_list_t addrs; qd_hash_t *addr_hash; + qd_parse_node_t *addr_parse_tree; qdr_address_t *hello_addr; qdr_address_t *router_addr_L; qdr_address_t *routerma_addr_L; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/src/router_private.h ---------------------------------------------------------------------- diff --git a/src/router_private.h b/src/router_private.h index 533955a..4e9ac9c 100644 --- a/src/router_private.h +++ b/src/router_private.h @@ -32,6 +32,7 @@ #include <qpid/dispatch/trace_mask.h> #include <qpid/dispatch/hash.h> #include <qpid/dispatch/log.h> +#include "parse_tree.h" #include "dispatch_private.h" #include "entity.h" http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/492ef7d4/tests/system_tests_qdmanage.py ---------------------------------------------------------------------- diff --git a/tests/system_tests_qdmanage.py b/tests/system_tests_qdmanage.py index 17304f1..91b38c0 100644 --- a/tests/system_tests_qdmanage.py +++ b/tests/system_tests_qdmanage.py @@ -49,7 +49,8 @@ class QdmanageTest(TestCase): ('address', {'name': 'test-address', 'prefix': 'abcd', 'distribution': 'multicast'}), ('linkRoute', {'name': 'test-link-route', 'prefix': 'xyz', 'dir': 'in'}), ('autoLink', {'name': 'test-auto-link', 'addr': 'mnop', 'dir': 'out'}), - ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl'}) + ('listener', {'port': cls.tester.get_port(), 'sslProfile': 'server-ssl'}), + ('address', {'name': 'pattern-address', 'pattern': 'a/*/b/#/c', 'distribution': 'closest'}) ]) config_2 = Qdrouterd.Config([ @@ -239,9 +240,15 @@ class QdmanageTest(TestCase): long_type = 'org.apache.qpid.dispatch.router.config.address' query_command = 'QUERY --type=' + long_type output = json.loads(self.run_qdmanage(query_command)) + self.assertEqual(len(output), 2) self.assertEqual(output[0]['name'], "test-address") self.assertEqual(output[0]['distribution'], "multicast") self.assertEqual(output[0]['prefix'], "abcd") + self.assertTrue('pattern' not in output[0]) + self.assertEqual(output[1]['name'], "pattern-address") + self.assertEqual(output[1]['distribution'], "closest") + self.assertEqual(output[1]['pattern'], "a/*/b/#/c") + self.assertTrue('prefix' not in output[1]) def test_check_link_route_name(self): long_type = 'org.apache.qpid.dispatch.router.config.linkRoute' --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org