From a41dcb7558922ed0a8ea9cccbe82ed72694a55dc Mon Sep 17 00:00:00 2001 From: Pushpasis Sarkar Date: Fri, 8 Sep 2023 10:33:25 -0700 Subject: [PATCH 1/5] lib,mgmtd,vtysh: Add utility for wildcard resolution of Xpath using a specific Mgmt Database This commit adds utilities for resolving a Xpath with wildcards in it using the contents of a live specific MGMT database. Co-authored-by: Yash Ranjan Signed-off-by: Pushpasis Sarkar --- lib/command.c | 2 + lib/mgmt_be_client.c | 2 + lib/mgmt_fe_client.c | 2 + lib/mgmt_util.c | 556 ++++++++++++++++++++++++++++++++++++++++ lib/mgmt_util.h | 304 ++++++++++++++++++++++ lib/subdir.am | 3 + lib/vty.c | 2 + mgmtd/mgmt_be_adapter.c | 4 +- mgmtd/mgmt_ds.c | 271 +++++++++++++++----- mgmtd/mgmt_ds.h | 62 +---- mgmtd/mgmt_txn.c | 3 +- mgmtd/mgmt_vty.c | 3 +- python/xref2vtysh.py | 5 +- vtysh/vtysh.h | 3 + 14 files changed, 1086 insertions(+), 136 deletions(-) create mode 100644 lib/mgmt_util.c create mode 100644 lib/mgmt_util.h diff --git a/lib/command.c b/lib/command.c index 1977dc4faed4..613fdc0119a2 100644 --- a/lib/command.c +++ b/lib/command.c @@ -33,6 +33,7 @@ #include "lib_errors.h" #include "mgmt_be_client.h" #include "mgmt_fe_client.h" +#include "mgmt_util.h" #include "northbound_cli.h" #include "network.h" #include "routemap.h" @@ -2437,6 +2438,7 @@ void cmd_show_lib_debugs(struct vty *vty) route_map_show_debug(vty); mgmt_debug_be_client_show_debug(vty); mgmt_debug_fe_client_show_debug(vty); + mgmt_debug_util_show_debug(vty); } void install_default(enum node_type node) diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 7bd9980357d5..394fac06ac86 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -10,6 +10,7 @@ #include "compiler.h" #include "libfrr.h" #include "mgmtd/mgmt.h" +#include "mgmt_util.h" #include "mgmt_be_client.h" #include "mgmt_msg.h" #include "mgmt_pb.h" @@ -968,6 +969,7 @@ void mgmt_be_client_lib_vty_init(void) install_node(&mgmt_dbg_node); install_element(ENABLE_NODE, &debug_mgmt_client_be_cmd); install_element(CONFIG_NODE, &debug_mgmt_client_be_cmd); + mgmt_util_vty_init(); } void mgmt_be_client_destroy(struct mgmt_be_client *client) diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 7e42e1c09e5e..9e13999ac487 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -13,6 +13,7 @@ #include "mgmt_fe_client.h" #include "mgmt_msg.h" #include "mgmt_pb.h" +#include "mgmt_util.h" #include "network.h" #include "stream.h" #include "sockopt.h" @@ -622,6 +623,7 @@ void mgmt_fe_client_lib_vty_init(void) install_node(&mgmt_dbg_node); install_element(ENABLE_NODE, &debug_mgmt_client_fe_cmd); install_element(CONFIG_NODE, &debug_mgmt_client_fe_cmd); + mgmt_util_vty_init(); } uint mgmt_fe_client_session_count(struct mgmt_fe_client *client) diff --git a/lib/mgmt_util.c b/lib/mgmt_util.c new file mode 100644 index 000000000000..7d8ecb6de41c --- /dev/null +++ b/lib/mgmt_util.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Utilities + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "mgmt_util.h" + +#include "lib/mgmt_util_clippy.c" + +struct debug mgmt_dbg_util = {0, "Management common utilities"}; + +/* + * Evaluates the regular expression match in the given xpath. + */ +int mgmt_xpath_eval_regexp_match(const char *xpath_regexp, + const char *xpath, + bool get_match_xpath, + char **xpath_match, + int *xpath_match_len, + bool full_match_only) +{ + int match_len = 0, re_indx = 0, xp_indx = 0; + int rexp_len, xpath_len; + bool match = true, re_wild = false, xp_wild = false; + bool delim = false, enter_wild_match = false; + bool ignore_wild_match = false, inside_keyval = false; + char *xp_next = NULL; + + rexp_len = strlen(xpath_regexp); + xpath_len = strlen(xpath); + MGMTD_UTIL_DBG("RE: %s / %d, XP: %s / %d", xpath_regexp, rexp_len, + xpath, xpath_len); + + /* + * Remove the trailing wildcard from the regexp and Xpath. + */ + if (rexp_len && xpath_regexp[rexp_len-1] == '*') + rexp_len--; + if (xpath_len && xpath[xpath_len-1] == '*') + xpath_len--; + + if (!rexp_len || !xpath_len) + return 0; + + if (get_match_xpath) { + /* + * Allocate a new Xpath if not provided. + */ + if (!*xpath_match) + *xpath_match = calloc(1, MGMTD_MAX_XPATH_LEN); + xp_next = *xpath_match; + *xp_next = '\0'; + if (xpath_match_len) + *xpath_match_len = 0; + } + + for (re_indx = 0, xp_indx = 0; + match && re_indx < rexp_len && xp_indx < xpath_len;) { + match = (xpath_regexp[re_indx] == xpath[xp_indx]); + + /* + * Check if we have a key on the Xpath and not on the + * corresponding token on regex. If so we need to ignore + * rest of the token on the Xpath. + */ + if (xpath_regexp[re_indx] == '/') { + if (xpath[xp_indx] == '[') { + ignore_wild_match = true; + re_wild = true; + } else if (xpath[xp_indx] == '/') { + ignore_wild_match = false; + re_wild = false; + } + } + + /* + * Check if we have a key on the regex and not on the + * corresponding token on Xpath. If so we need to ignore + * rest of the token on the regex. + */ + if (xpath[xp_indx] == '/') { + if (xpath_regexp[re_indx] == '[') { + ignore_wild_match = true; + xp_wild = true; + } else if (xp_wild && xpath_regexp[re_indx] == '/') { + ignore_wild_match = false; + xp_wild = false; + } + } + + /* + * Check if we have entered a key-value. + */ + if (re_indx && xp_indx + && xpath_regexp[re_indx-1] == '=' + && xpath_regexp[re_indx] == '\'' + && xpath[xp_indx-1] == '=' + && xpath[xp_indx] == '\'') + /* + * We have "='" on both the regexp and xpath. + * Entering a key-value. + */ + inside_keyval = true; + + /* + * Check if we need to enter wildcard matching. + */ + if (inside_keyval && !ignore_wild_match && !enter_wild_match + && !match && + (xpath_regexp[re_indx] == '*' + || xpath[xp_indx] == '*')) { + /* + * Found wildcard + */ + enter_wild_match = true; + if (xpath_regexp[re_indx] == '*') { + /* + * Begin RE wildcard match. + */ + re_wild = true; + } else if (xpath[xp_indx] == '*') { + /* + * Begin XP wildcard match. + */ + xp_wild = true; + } + } + + /* + * Check if we need to exit wildcard matching. + */ + if (inside_keyval && !ignore_wild_match && enter_wild_match) { + if (re_wild && + xpath[xp_indx] == xpath_regexp[re_indx+1]) { + /* + * End RE wildcard matching. + */ + re_wild = false; + re_indx++; + enter_wild_match = false; + } else if (xp_wild && + (xpath_regexp[re_indx] == xpath[xp_indx+1])) { + /* + * End XP wildcard matching. + */ + xp_wild = false; + xp_indx++; + enter_wild_match = false; + } + } + + /* + * Check if we are exiting a key-value. + */ + if (inside_keyval + && xpath_regexp[re_indx] == '\'' + && xpath_regexp[re_indx+1] == ']' + && xpath[xp_indx] == '\'' + && xpath[xp_indx+1] == ']') + /* + * We are already inside a key-value and we + * have "']" on both the regexp and xpath. + * Exiting the key-value. + */ + inside_keyval = false; + + match = (xp_wild || re_wild + || xpath_regexp[re_indx] == xpath[xp_indx]); + + /* + * Check if we found a delimiter in both the Xpaths + */ + delim = false; + if (!inside_keyval + && (((xpath[xp_indx] == '/' + || xpath[xp_indx] == ']') + && (re_wild + || xpath_regexp[re_indx] == xpath[xp_indx])) + || ((xpath_regexp[re_indx] == '/' + || xpath_regexp[re_indx] == ']') + && (xp_wild + || xpath_regexp[re_indx] == xpath[xp_indx])))) + delim = true; + + if (delim && re_indx && xp_indx) + /* + * Increment the match count if we have a new + * delimiter. + */ + match_len++; + + if (get_match_xpath) { + /* + * Copy the next character for the xpath match + * from the appropriate Xpath. + */ + if (xp_wild) + *xp_next++ = xpath_regexp[re_indx]; + else + *xp_next++ = xpath[xp_indx]; + + if (xpath_match_len) + (*xpath_match_len)++; + } + + MGMTD_UTIL_DBG("RE %c [indx: %d/%d, w: %d] XP %c [indx: %d/%d, w: %d] XM:%c IK:%d, W: %d Match: %d Len: %d", + xpath_regexp[re_indx], re_indx, rexp_len, + re_wild, xpath[xp_indx], xp_indx, xpath_len, + xp_wild, get_match_xpath ? *(xp_next-1) : '\0', + inside_keyval, enter_wild_match, match, + match_len); + + if (re_wild && xp_wild) + /* + * Ideally this should never happen. + */ + break; + + /* + * Proceed to the next character in the RE/XP string as + * necessary. + */ + if (!re_wild) + re_indx++; + if (!xp_wild) + xp_indx++; + } + + /* + * If we finished matching and the last token was a full match + * increment the match count appropriately. + */ + if (match && !delim) + match_len++; + + if (get_match_xpath) { + /* + * Copy rest of the XPath length from the bigger XPath. + */ + if (match && !full_match_only && (rexp_len - re_indx) > (xpath_len - xp_indx)) { + for (; re_indx < rexp_len; re_indx++) { + *xp_next++ = xpath_regexp[re_indx]; + if (xpath_match_len) + (*xpath_match_len)++; + } + } + + if ((xpath_len - xp_indx) > (rexp_len - re_indx)) { + for (; xp_indx < xpath_len; xp_indx++) { + *xp_next++ = xpath[xp_indx]; + if (xpath_match_len) + (*xpath_match_len)++; + } + } + + *xp_next++ = '\0'; + } + + return (!full_match_only || match ? match_len : 0); +} + +/* + * Returns the index of the first wildcard in a given xpath. + */ +int mgmt_xpath_find_first_wildcard(const char *xpath, int *key_end) +{ + bool key_val = false, wc_found = false; + int indx, last_sep = -1, key_start = -1; + + if (key_end) + *key_end = -1; + + for (indx = 0; ; indx++) { + switch (xpath[indx]) { + case '\0': + /* + * End of the xpath string. Return the wildcard keyset + * if we found one. + */ + if (wc_found && key_end) + *key_end = indx; + return wc_found ? key_start : -1; + case ']': + if (key_val) { + last_sep = -1; + key_val = false; + } + break; + case '[': + /* + * This could be the start of the wildcard keyset we + * are looking for. + */ + if (last_sep >= 0) + key_start = last_sep + 1; + key_val = true; + break; + case '*': + if (key_val) + wc_found = true; + break; + case '/': + if (!key_val) { + /* First separator found */ + last_sep = indx; + + if (wc_found) { + /* Found end of the wildcard keyset */ + if (key_end) + *key_end = indx; + return key_start; + } + }; + break; + default: + break; + } + + MGMTD_UTIL_DBG("Indx: %d, Char: '%c', K: %d, WC: %d, Last-Sep: %d, KS: %d, KE:%d ", + indx, xpath[indx], key_val, wc_found, last_sep, + key_start, key_end ? *key_end : -1); + } + + return -1; +} + +/* + * Resolve wildcard entry in a given xpath. + */ +int mgmt_xpath_resolve_wildcard(char *xpath, int start_indx, + void (*get_child_fn)(char *base_xpath, + const char *child_xpath[], + void *child_ctxt[], + int *num_child, + void *ctxt, char* xpath_key), + int (*iter_fn)(const char *child_xpath, + void *child_ctxt, + void *ctxt), + void *ctxt, int level) +{ + int wc_key_start, wc_key_end, indx, indx1, match, max_match; + int xp_match_len, num_dn_match; + char orig; + char xpath_key[MGMTD_MAX_XPATH_LEN]; + char *dnode_matched[MGMTD_MAX_NUM_DSNODES_PER_BATCH] = { 0 }; + void *dnode_ctxt[MGMTD_MAX_NUM_DSNODES_PER_BATCH] = { 0 }; + char *xp_matched[MGMTD_MAX_NUM_DSNODES_PER_BATCH] = { 0 }; + char *chld_xpath[MGMTD_MAX_NUM_DSNODES_PER_BATCH] = { 0 }; + void *chld_ctxt[MGMTD_MAX_NUM_DSNODES_PER_BATCH] = { 0 }; + int num_child, ret; + + wc_key_start = mgmt_xpath_find_first_wildcard(&xpath[start_indx], &wc_key_end); + + MGMTD_UTIL_DBG(" [%d] - SI: %d, KS:%d, KE:%d, Xpath: %s", + level, start_indx, wc_key_start, wc_key_end, xpath); + + if (wc_key_start < 0) { + ret = 0; + if (iter_fn) { + MGMTD_UTIL_DBG("start of one ds_iter "); + ret = (*iter_fn)(xpath, NULL, ctxt); + } + MGMTD_UTIL_DBG("End of one xpath "); + return ret; + } + + strlcpy(xpath_key, xpath, sizeof(xpath_key)); + if (wc_key_end >= 0) { + xpath_key[start_indx + wc_key_end] = '\0'; + MGMTD_UTIL_DBG(" [%d] -- Truncated Xpath: %s", + level, xpath_key); + } + + /* Get the exact set of children under base xpath. */ + if (wc_key_start >= 0) { + orig = xpath[start_indx + wc_key_start - 1]; + xpath[start_indx + wc_key_start - 1] = '\0'; + } + num_child = array_size(chld_xpath); + (*get_child_fn)(xpath, (const char**)chld_xpath, chld_ctxt, + &num_child, ctxt, xpath_key); + if (wc_key_start >= 0) + xpath[start_indx + wc_key_start - 1] = orig; + + /* Find the child nodes that match the wildcard key combination */ + num_dn_match = 0; + max_match = 0; + for (indx = 0; indx < num_child; indx++) { + if (!xp_matched[num_dn_match]) + xp_matched[num_dn_match] = calloc(1, MGMTD_MAX_XPATH_LEN); + match = mgmt_xpath_eval_regexp_match(xpath_key, chld_xpath[indx], + true, &xp_matched[num_dn_match], + &xp_match_len, true); + MGMTD_UTIL_DBG(" [%d] -- DNODE: %s, Match: %d/%d/%d, %s", + level, chld_xpath[indx], match, max_match, + xp_match_len, xp_matched[num_dn_match]); + if (!match || match < max_match) { + /* + * Not at par with previous best match. + */ + if (chld_xpath[indx]) { + /* Discard the child */ + free(chld_xpath[indx]); + chld_xpath[indx] = NULL; + } + if (xp_matched[num_dn_match]) { + /* Discard the resolved xpath as well */ + free(xp_matched[num_dn_match]); + xp_matched[num_dn_match] = NULL; + } + continue; + } + + if (match > max_match) { + /* + * Better match than all previous best matches + */ + max_match = match; + if (num_dn_match) { + /* + * There seems to be previous best matches. + * Let's drop them all first. + */ + for (indx1 = 0; indx1 < num_dn_match; indx1++) { + if (xp_matched[indx1]) { + free(xp_matched[indx1]); + xp_matched[indx1] = NULL; + } + } + /* + * And move the current one to the beginning of the + * best match. + */ + xp_matched[0] = xp_matched[num_dn_match]; + xp_matched[num_dn_match] = NULL; + } + num_dn_match = 0; + } + + /* + * Current child xpath is either the best match or is at + * par with the previous best matches. + */ + dnode_matched[num_dn_match] = chld_xpath[indx]; + chld_xpath[indx] = NULL; + dnode_ctxt[num_dn_match] = chld_ctxt[indx]; + MGMTD_UTIL_DBG(" [%d], XP_MATCH: %s", num_dn_match+1, + xp_matched[num_dn_match]); + num_dn_match++; + } + + ret = 0; + for (indx = 0; indx < num_dn_match; indx++) { + if (wc_key_start >= 0 && wc_key_end >= 0) { + int len = strlen(xp_matched[indx]); + + MGMTD_UTIL_DBG(" [%d] --- Merge XPATH: [%s] + [%s]", + level, xp_matched[indx], + &xpath[start_indx+wc_key_end+1]); + strlcat(xp_matched[indx], &xpath[start_indx+wc_key_end], MGMTD_MAX_XPATH_LEN - xp_match_len); + MGMTD_UTIL_DBG(" [%d] --- Next XPATH: %s", + level, xp_matched[indx]); + mgmt_xpath_resolve_wildcard(xp_matched[indx], len, + get_child_fn, iter_fn, ctxt, level+1); + } else { + MGMTD_UTIL_DBG(" [%d] ===> XPATH: %s", level, + dnode_matched[indx]); + + if (!ret && iter_fn) { + MGMTD_UTIL_DBG("start of second ds_iter"); + ret = (*iter_fn)(dnode_matched[indx], + dnode_ctxt[indx], ctxt); + MGMTD_UTIL_DBG("End of second xpath"); + } + } + + free(xp_matched[indx]); + free(dnode_matched[indx]); + } + + return ret; +} + +/* + * debug utilities command for mgmtd. + */ +DEFPY(debug_mgmt_util, debug_mgmt_util_cmd, + "[no] debug mgmt utilities", + NO_STR DEBUG_STR MGMTD_STR + "Common utilities\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + DEBUG_MODE_SET(&mgmt_dbg_util, mode, !no); + + return CMD_SUCCESS; +} + +/* + * Enable all debug utilities for mgmtd. + */ +static void mgmt_debug_util_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&mgmt_dbg_util, flags, set); +} + +/* + * Check debug mode config for mgmtd utility. + */ +static int mgmt_debug_util_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&mgmt_dbg_util, DEBUG_MODE_CONF)) + vty_out(vty, "debug mgmt utilities\n"); + + return CMD_SUCCESS; +} + +/* + * Show mgmtd debug utility. + */ +void mgmt_debug_util_show_debug(struct vty *vty) +{ + if (MGMTD_DBG_UTIL_CHECK()) + vty_out(vty, "debug mgmt utilities\n"); +} + +static struct debug_callbacks mgmt_dbg_util_cbs = { + .debug_set_all = mgmt_debug_util_set_all}; + +static struct cmd_node mgmt_dbg_util_node = { + .name = "mgmt utilities", + .node = DEBUG_NODE, + .prompt = "", + .config_write = mgmt_debug_util_config_write, +}; + +/* + * Initialize mgmt debug utility. + */ +void mgmt_util_vty_init(void) +{ + static bool mgmt_util_vty_initialized; + + if (mgmt_util_vty_initialized) + return; + + debug_init(&mgmt_dbg_util_cbs); + install_node(&mgmt_dbg_util_node); + install_element(ENABLE_NODE, &debug_mgmt_util_cmd); + install_element(CONFIG_NODE, &debug_mgmt_util_cmd); + mgmt_util_vty_initialized = true; +} diff --git a/lib/mgmt_util.h b/lib/mgmt_util.h new file mode 100644 index 000000000000..68b4639b37ba --- /dev/null +++ b/lib/mgmt_util.h @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Utilities + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "debug.h" + +#define MGMTD_MAX_NUM_DSNODES_PER_BATCH 128 + +extern struct debug mgmt_dbg_util; + +#define MGMTD_UTIL_DBG(fmt, ...) \ + DEBUGD(&mgmt_dbg_util, "UTIL: %s: " fmt, __func__, \ + ##__VA_ARGS__) +#define MGMTD_UTIL_ERR(fmt, ...) \ + zlog_err("UTIL: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) +#define MGMTD_DBG_UTIL_CHECK() \ + DEBUG_MODE_CHECK(&mgmt_dbg_util, DEBUG_MODE_ALL) + +/* + * Returns position of the previous separator ['/','[' or ']') in a given xpath + * with respect to a specific position in the xpath. + * + * Args: + * - base_xpath - The Xpath to look for a separator. + * - start_index - The position before which to search for the separator. + * + * Returns: The position of the previous separator if foumd, else -1. + */ +static inline int mgmt_xpath_find_prev_separator(const char *base_xpath, int start_index) +{ + bool key_val = false; + int last_sep = -1; + + while (start_index) { + switch (base_xpath[start_index]) { + case '[': + if (key_val) { + last_sep = -1; + key_val = false; + } + break; + case ']': + if (last_sep >= 0) + return last_sep; + key_val = true; + break; + case '/': + if (last_sep < 0) + last_sep = start_index; + else + return last_sep; + break; + default: + break; + } + + MGMTD_UTIL_DBG("Indx: %d, Char: '%c', K: %d, Last-Sep: %d\n", + start_index, base_xpath[start_index], key_val, last_sep); + start_index--; + } + + return last_sep; +} + +/* + * Returns position of the next separator ['/','[' or ']') in a given xpath + * with respect to a specific position in the xpath. + * + * Args: + * - base_xpath: The Xpath to look for a separator. + * - start_index: The position after which to search for the separator. + * + * Returns: The position of the previous separator if foumd, else -1. + */ +static inline int mgmt_xpath_find_next_separator(const char *base_xpath, int start_index) +{ + bool key_val = false; + int last_sep = -1; + + while (1) { + switch (base_xpath[start_index]) { + case '\0': + return last_sep; + case ']': + if (key_val) { + last_sep = -1; + key_val = false; + } + break; + case '[': + if (last_sep >= 0) + return last_sep; + key_val = true; + break; + case '/': + if (last_sep < 0) + last_sep = start_index; + else + return last_sep; + break; + default: + break; + } + + MGMTD_UTIL_DBG("Indx: %d, Char: '%c', K: %d, Last-Sep: %d\n", + start_index, base_xpath[start_index], key_val, last_sep); + start_index++; + } + + return last_sep; +} + +/* + * Appends trailing wildcard '/' '*' to a given xpath. + * + * Args: + * - xpath: The specific xpath to append to. + * - xpath_len: Length of the xpath string. + */ +static inline void mgmt_xpath_append_trail_wildcard(char *xpath, + size_t *xpath_len) +{ + if (!xpath || !xpath_len) + return; + + if (!*xpath_len) + *xpath_len = strlen(xpath); + + if (*xpath_len > 2 && *xpath_len < MGMTD_MAX_XPATH_LEN - 2) { + if (xpath[*xpath_len - 1] == '/') { + xpath[*xpath_len] = '*'; + xpath[*xpath_len + 1] = 0; + (*xpath_len)++; + } else if (xpath[*xpath_len - 1] != '*') { + xpath[*xpath_len] = '/'; + xpath[*xpath_len + 1] = '*'; + xpath[*xpath_len + 2] = 0; + (*xpath_len) += 2; + } + } +} + +/* + * Removes trailng wildcard '/' and '*' from a given xpath. + * + * Args: + * - xpath: The specific xpath to remove from. + * - xpath_len: Length of the xpath string. + */ +static inline void mgmt_xpath_remove_trail_wildcard(char *xpath, + size_t *xpath_len) +{ + if (!xpath || !xpath_len) + return; + + if (!*xpath_len) + *xpath_len = strlen(xpath); + + if (*xpath_len > 2 && xpath[*xpath_len - 2] == '/' + && xpath[*xpath_len - 1] == '*') { + xpath[*xpath_len - 2] = 0; + (*xpath_len) -= 2; + } +} + +/* + * Returns the index of the specific character in a given xpath. + * + * Args: + * - xpath: The specific xpath to search in. + * - start: The position to start the search from. + * - c: The specific character to look for. + */ +static inline int mgmt_xpath_find_character(char *xpath, int start, char c) +{ + int i = start; + + while (xpath[i] != '\0') { + if (xpath[i] == c) + return i; + i++; + } + return -1; +} + +/* + * Evaluates the regular expression match in the given xpath. + * + * xpath_regexp + * YANG regular expression. + * + * xpath + * YANG xpath. + * + * get_match_xpath + * Get the matching xpath. + * + * xpath_match + * YANG xpath which is matched. + * + * xpath_match_len + * xpath matched length. + * + * full_match_only + * True if full match between regex and xpath needed. + * + * Returns + * The number of tokens that matched between the regex and the + * actual xpath. + * + * Examples of type of XPATH regexps that this function can + * support are: + * - / + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='ipv4-*'][table-id='*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='254']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='*']/ + * route-entry[protocol='*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.1.1/32']/ + * route-entry[protocol='*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='*']/ + * route-entry[protocol='static']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.*']/ + * route-entry[protocol='*']/ + * - /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/ + * rib[afi-safi-name='*'][table-id='*']/route[prefix='*']/ + * route-entry[protocol='*']/distance + */ +extern int mgmt_xpath_eval_regexp_match(const char *xpath_regexp, + const char *xpath, + bool get_match_xpath, + char **xpath_match, + int *xpath_match_len, + bool full_match_only); + +/* + * Returns the index of the first wildcard in a given xpath. + * + * xpath + * YANG xpath. + * + * key_end + * xpath end index. + */ +extern int mgmt_xpath_find_first_wildcard(const char *xpath, int *key_end); + +/* + * Resolve wildcard entry in a given xpath. + * + * xpath + * YANG xpath. + * + * start_indx + * xpath start index. + * + * get_child_fn + * child xpath resolve function. + * + * iter_fn + * xpath iterator function. + * + * ctxt + * iterator context. + * + * level + * recursive resolution level. + */ +extern int mgmt_xpath_resolve_wildcard(char *xpath, int start_indx, + void (*get_child_fn)(char *base_xpath, + const char *child_xpath[], + void *child_ctxt[], + int *num_child, + void *ctxt, char* xpath_key), + int (*iter_fn)(const char *child_xpath, + void *child_ctxt, + void *ctxt), + void *ctxt, int level); + +/* + * Show mgmt debug utility. + * + * vty + * vty context. + */ +extern void mgmt_debug_util_show_debug(struct vty *vty); + +/* + * Initialize mgmt debug utility. + */ +extern void mgmt_util_vty_init(void); diff --git a/lib/subdir.am b/lib/subdir.am index d7b28ffbd59e..549b3c4aaa96 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -68,6 +68,7 @@ lib_libfrr_la_SOURCES = \ lib/mgmt_be_client.c \ lib/mgmt_fe_client.c \ lib/mgmt_msg.c \ + lib/mgmt_util.c \ lib/mlag.c \ lib/module.c \ lib/mpls.c \ @@ -182,6 +183,7 @@ clippy_scan += \ lib/log_vty.c \ lib/mgmt_be_client.c \ lib/mgmt_fe_client.c \ + lib/mgmt_util.c \ lib/nexthop_group.c \ lib/northbound_cli.c \ lib/plist.c \ @@ -254,6 +256,7 @@ pkginclude_HEADERS += \ lib/mgmt_be_client.h \ lib/mgmt_fe_client.h \ lib/mgmt_msg.h \ + lib/mgmt_util.h \ lib/mgmt_pb.h \ lib/module.h \ lib/monotime.h \ diff --git a/lib/vty.c b/lib/vty.c index 15cc340eb0c5..b8b981dba625 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -3906,6 +3906,8 @@ void vty_init(struct event_loop *master_thread, bool do_command_logging) install_element(VTY_NODE, &no_vty_login_cmd); install_element(VTY_NODE, &vty_ipv6_access_class_cmd); install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd); + + mgmt_fe_client_lib_vty_init(); } void vty_terminate(void) diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index 399fdafded1a..be7a29c63456 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -602,9 +602,11 @@ static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data, mgmtd__be_message__free_unpacked(be_msg, NULL); } -static void mgmt_be_iter_and_get_cfg(const char *xpath, struct lyd_node *node, +static void mgmt_be_iter_and_get_cfg(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, struct lyd_node *node, struct nb_node *nb_node, void *ctx) { + (void) ds_ctx; struct mgmt_be_get_adapter_config_params *parms = ctx; struct mgmt_be_client_adapter *adapter = parms->adapter; uint subscr; diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c index a0e610c7c745..a3dcbd85ef29 100644 --- a/mgmtd/mgmt_ds.c +++ b/mgmtd/mgmt_ds.c @@ -14,6 +14,7 @@ #include "mgmtd/mgmt_history.h" #include "mgmtd/mgmt_txn.h" #include "libyang/libyang.h" +#include "mgmt_util.h" #define MGMTD_DS_DBG(fmt, ...) \ DEBUGD(&mgmt_debug_ds, "DS: %s: " fmt, __func__, ##__VA_ARGS__) @@ -34,6 +35,23 @@ struct mgmt_ds_ctx { } root; }; +struct mgmt_ds_iter_ctx { + struct mgmt_ds_ctx *ds_ctx; + void (*ds_iter_fn)(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, + struct lyd_node *node, + struct nb_node *nb_node, + void *ctx); + void *usr_ctx; +}; + +struct mgmt_ds_child_collect_ctx { + const char **child_xpath; + void **child_ctx; + int max_child; + int num_child; +}; + const char *mgmt_ds_names[MGMTD_DS_MAX_ID + 1] = { MGMTD_DS_NAME_NONE, /* MGMTD_DS_NONE */ MGMTD_DS_NAME_RUNNING, /* MGMTD_DS_RUNNING */ @@ -75,6 +93,165 @@ static int mgmt_ds_dump_in_memory(struct mgmt_ds_ctx *ds_ctx, return 0; } +/* + * Iterate over datastore nodes till leaf node or desired depth + * is reached. + */ +static int mgmt_walk_ds_nodes(struct mgmt_ds_ctx *ds_ctx, + const char *base_xpath, + struct lyd_node *base_dnode, + void (*mgmt_ds_node_iter_fn)( + struct mgmt_ds_ctx *ds_ctx, + const char *xpath, + struct lyd_node *node, + struct nb_node *nb_node, void *ctx), + void *ctx, int tree_depth, bool iter_base) +{ + /* this is 1k per recursion... */ + char xpath[MGMTD_MAX_XPATH_LEN]; + struct lyd_node *dnode; + struct nb_node *nbnode; + int ret = 0; + bool same_as_base; + + assert(mgmt_ds_node_iter_fn); + + MGMTD_DS_DBG(" -- START: Base: %s, Tree-Depth: %d, Iter-Base: %d", + base_xpath, tree_depth, iter_base); + + if (!base_dnode) + /* + * This function only returns the first node of a possible set + * of matches issuing a warning if more than 1 matches + */ + base_dnode = yang_dnode_get( + ds_ctx->config_ds ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root, + base_xpath); + if (!base_dnode) + return -1; + + if (iter_base && mgmt_ds_node_iter_fn) { + MGMTD_DS_DBG(" search base schema: '%s'", + lysc_path(base_dnode->schema, LYSC_PATH_LOG, + xpath, sizeof(xpath))); + + nbnode = (struct nb_node *)base_dnode->schema->priv; + (*mgmt_ds_node_iter_fn)(ds_ctx, base_xpath, base_dnode, nbnode, + ctx); + } + + /* + * If the base_xpath points to leaf node, or this is the bottom of the + * tree depth desired we can skip the rest of the tree walk + */ + if (base_dnode->schema->nodetype & LYD_NODE_TERM || !tree_depth) + return 0; + + LY_LIST_FOR (lyd_child(base_dnode), dnode) { + assert(dnode->schema && dnode->schema->priv); + nbnode = (struct nb_node *)dnode->schema->priv; + + (void)lyd_path(dnode, LYD_PATH_STD, xpath, sizeof(xpath)); + + same_as_base = strncmp(xpath, base_xpath, strlen(xpath)) + ? false : true; + MGMTD_DS_DBG(" -- Child XPATH: %s, Same-as-Base: %d", + xpath, same_as_base); + + ret = mgmt_walk_ds_nodes(ds_ctx, xpath, dnode, + mgmt_ds_node_iter_fn, ctx, + same_as_base ? + tree_depth : tree_depth - 1, + !same_as_base); + + if (ret != 0) + break; + } + + return 0; +} + +/* + * Stores the child node xpath and node in the given xpath context. + */ +static void mgmt_ds_collect_child_node(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, + struct lyd_node *node, + struct nb_node *nb_node, void *ctx) +{ + struct mgmt_ds_child_collect_ctx *coll_ctx; + + coll_ctx = (struct mgmt_ds_child_collect_ctx *) ctx; + + if (coll_ctx->num_child >= coll_ctx->max_child) { + MGMTD_DS_ERR("Number of child %d exceeded maximum %d", + coll_ctx->num_child, coll_ctx->max_child); + return; + } + + coll_ctx->child_xpath[coll_ctx->num_child] = xpath; + coll_ctx->child_ctx[coll_ctx->num_child] = node; + MGMTD_DS_DBG(" -- [%d] Child XPATH: %s", coll_ctx->num_child+1, + coll_ctx->child_xpath[coll_ctx->num_child]); + coll_ctx->num_child++; +} + +/* + * Iterates over the datastore nodes to get child nodes for the xpath. + */ +static void mgmt_ds_get_child_nodes(char *base_xpath, + const char *child_xpath[], + void *child_ctx[], int *num_child, + void *ctx, char *xpath_key) +{ + struct mgmt_ds_iter_ctx *iter_ctx; + struct mgmt_ds_ctx *ds_ctx; + int max_child; + struct mgmt_ds_child_collect_ctx coll_ctx = { 0 }; + + iter_ctx = (struct mgmt_ds_iter_ctx *)ctx; + ds_ctx = iter_ctx->ds_ctx; + max_child = *num_child; + *num_child = 0; + coll_ctx.child_ctx = child_ctx; + coll_ctx.child_xpath = child_xpath; + coll_ctx.max_child = max_child; + mgmt_walk_ds_nodes(ds_ctx, base_xpath, NULL, + mgmt_ds_collect_child_node, &coll_ctx, + 1, false); + *num_child = coll_ctx.num_child; +} + +/* + * Iterate over the data nodes. + */ +static int mgmt_ds_iter_data_nodes(const char *node_xpath, void *node_ctx, + void *ctx) +{ + struct mgmt_ds_iter_ctx *iter_ctx; + struct mgmt_ds_ctx *ds_ctx; + struct lyd_node *dnode; + + iter_ctx = (struct mgmt_ds_iter_ctx *)ctx; + ds_ctx = iter_ctx->ds_ctx; + dnode = (struct lyd_node *)node_ctx; + + if (!dnode) { + dnode = yang_dnode_get(ds_ctx->config_ds ? + ds_ctx->root.cfg_root->dnode : + ds_ctx->root.dnode_root, + node_xpath); + if (!dnode) + return -1; + } + + return mgmt_walk_ds_nodes(ds_ctx, node_xpath, dnode, + iter_ctx->ds_iter_fn, + iter_ctx->usr_ctx, + -1, true); +} + static int mgmt_ds_replace_dst_with_src_ds(struct mgmt_ds_ctx *src, struct mgmt_ds_ctx *dst) { @@ -300,70 +477,6 @@ struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx) return ds_ctx->config_ds ? ds_ctx->root.cfg_root : NULL; } -static int mgmt_walk_ds_nodes( - struct nb_config *root, const char *base_xpath, - struct lyd_node *base_dnode, - void (*mgmt_ds_node_iter_fn)(const char *xpath, struct lyd_node *node, - struct nb_node *nb_node, void *ctx), - void *ctx) -{ - /* this is 1k per recursion... */ - char xpath[MGMTD_MAX_XPATH_LEN]; - struct lyd_node *dnode; - struct nb_node *nbnode; - int ret = 0; - - assert(mgmt_ds_node_iter_fn); - - MGMTD_DS_DBG(" -- START: base xpath: '%s'", base_xpath); - - if (!base_dnode) - /* - * This function only returns the first node of a possible set - * of matches issuing a warning if more than 1 matches - */ - base_dnode = yang_dnode_get(root->dnode, base_xpath); - if (!base_dnode) - return -1; - - MGMTD_DS_DBG(" search base schema: '%s'", - lysc_path(base_dnode->schema, LYSC_PATH_LOG, xpath, - sizeof(xpath))); - - nbnode = (struct nb_node *)base_dnode->schema->priv; - (*mgmt_ds_node_iter_fn)(base_xpath, base_dnode, nbnode, ctx); - - /* - * If the base_xpath points to a leaf node we can skip the tree walk. - */ - if (base_dnode->schema->nodetype & LYD_NODE_TERM) - return 0; - - /* - * at this point the xpath matched this container node (or some parent - * and we're wildcard descending now) so by walking it's children we - * continue to change the meaning of an xpath regex to rather be a - * prefix matching path - */ - - LY_LIST_FOR (lyd_child(base_dnode), dnode) { - assert(dnode->schema && dnode->schema->priv); - - (void)lyd_path(dnode, LYD_PATH_STD, xpath, sizeof(xpath)); - - MGMTD_DS_DBG(" -- Child xpath: %s", xpath); - - ret = mgmt_walk_ds_nodes(root, xpath, dnode, - mgmt_ds_node_iter_fn, ctx); - if (ret != 0) - break; - } - - MGMTD_DS_DBG(" -- END: base xpath: '%s'", base_xpath); - - return ret; -} - struct lyd_node *mgmt_ds_find_data_node_by_xpath(struct mgmt_ds_ctx *ds_ctx, const char *xpath) { @@ -444,7 +557,8 @@ int mgmt_ds_load_config_from_file(struct mgmt_ds_ctx *dst, int mgmt_ds_iter_data(Mgmtd__DatastoreId ds_id, struct nb_config *root, const char *base_xpath, - void (*mgmt_ds_node_iter_fn)(const char *xpath, + void (*mgmt_ds_node_iter_fn)(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, struct lyd_node *node, struct nb_node *nb_node, void *ctx), @@ -454,11 +568,14 @@ int mgmt_ds_iter_data(Mgmtd__DatastoreId ds_id, struct nb_config *root, char xpath[MGMTD_MAX_XPATH_LEN]; struct lyd_node *base_dnode = NULL; struct lyd_node *node; + struct mgmt_ds_ctx *ds_ctx = { 0 }; + struct mgmt_ds_iter_ctx iter_ctx = { 0 }; if (!root) return -1; strlcpy(xpath, base_xpath, sizeof(xpath)); + mgmt_remove_trailing_separator(xpath, '*'); mgmt_remove_trailing_separator(xpath, '/'); /* @@ -471,7 +588,10 @@ int mgmt_ds_iter_data(Mgmtd__DatastoreId ds_id, struct nb_config *root, /* If the base_xpath is empty then crawl the sibblings */ if (xpath[0] == 0) { - base_dnode = root->dnode; + base_dnode = root ? root->dnode : + ds_ctx->config_ds ? + ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root; /* get first top-level sibling */ while (base_dnode->parent) @@ -481,12 +601,21 @@ int mgmt_ds_iter_data(Mgmtd__DatastoreId ds_id, struct nb_config *root, base_dnode = base_dnode->prev; LY_LIST_FOR (base_dnode, node) { - ret = mgmt_walk_ds_nodes(root, xpath, node, - mgmt_ds_node_iter_fn, ctx); + lyd_path(node, LYD_PATH_STD, xpath, sizeof(xpath)); + ret = mgmt_ds_iter_data(ds_id, root, xpath, + mgmt_ds_node_iter_fn, ctx); } - } else - ret = mgmt_walk_ds_nodes(root, xpath, base_dnode, - mgmt_ds_node_iter_fn, ctx); + } else { + ds_ctx = mgmt_ds_get_ctx_by_id(mgmt_ds_mm, ds_id); + assert(ds_ctx); + iter_ctx.ds_ctx = ds_ctx; + iter_ctx.ds_iter_fn = mgmt_ds_node_iter_fn; + iter_ctx.usr_ctx = ctx; + ret = mgmt_xpath_resolve_wildcard(xpath, 0, + mgmt_ds_get_child_nodes, + mgmt_ds_iter_data_nodes, + (void *)&iter_ctx, 0); + } return ret; } diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h index 1cf4816027c3..d3ff43717ab5 100644 --- a/mgmtd/mgmt_ds.h +++ b/mgmtd/mgmt_ds.h @@ -16,8 +16,6 @@ #include "mgmtd/mgmt_be_adapter.h" #include "mgmtd/mgmt_fe_adapter.h" -#define MGMTD_MAX_NUM_DSNODES_PER_BATCH 128 - #define MGMTD_DS_NAME_MAX_LEN 32 #define MGMTD_DS_NAME_NONE "none" #define MGMTD_DS_NAME_RUNNING "running" @@ -95,63 +93,6 @@ static inline Mgmtd__DatastoreId mgmt_get_ds_id_by_name(const char *ds_name) return MGMTD_DS_NONE; } -/* - * Appends trail wildcard '/' '*' to a given xpath. - * - * xpath - * YANG xpath. - * - * path_len - * xpath length. - */ -static inline void mgmt_xpath_append_trail_wildcard(char *xpath, - size_t *xpath_len) -{ - if (!xpath || !xpath_len) - return; - - if (!*xpath_len) - *xpath_len = strlen(xpath); - - if (*xpath_len > 2 && *xpath_len < MGMTD_MAX_XPATH_LEN - 2) { - if (xpath[*xpath_len - 1] == '/') { - xpath[*xpath_len] = '*'; - xpath[*xpath_len + 1] = 0; - (*xpath_len)++; - } else if (xpath[*xpath_len - 1] != '*') { - xpath[*xpath_len] = '/'; - xpath[*xpath_len + 1] = '*'; - xpath[*xpath_len + 2] = 0; - (*xpath_len) += 2; - } - } -} - -/* - * Removes trail wildcard '/' '*' from a given xpath. - * - * xpath - * YANG xpath. - * - * path_len - * xpath length. - */ -static inline void mgmt_xpath_remove_trail_wildcard(char *xpath, - size_t *xpath_len) -{ - if (!xpath || !xpath_len) - return; - - if (!*xpath_len) - *xpath_len = strlen(xpath); - - if (*xpath_len > 2 && xpath[*xpath_len - 2] == '/' - && xpath[*xpath_len - 1] == '*') { - xpath[*xpath_len - 2] = 0; - (*xpath_len) -= 2; - } -} - /* Initialise datastore */ extern int mgmt_ds_init(struct mgmt_master *cm); @@ -257,7 +198,8 @@ extern int mgmt_ds_delete_data_nodes(struct mgmt_ds_ctx *ds_ctx, extern int mgmt_ds_iter_data( Mgmtd__DatastoreId ds_id, struct nb_config *root, const char *base_xpath, - void (*mgmt_ds_node_iter_fn)(const char *xpath, struct lyd_node *node, + void (*mgmt_ds_node_iter_fn)(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, struct lyd_node *node, struct nb_node *nb_node, void *ctx), void *ctx); diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 53b9b7df46c2..1fdc7a1bb7f9 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -1695,7 +1695,8 @@ static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, mgmt_reset_get_data_reply_buf(get_req); } -static void mgmt_txn_iter_and_send_get_cfg_reply(const char *xpath, +static void mgmt_txn_iter_and_send_get_cfg_reply(struct mgmt_ds_ctx *ds_ctx, + const char *xpath, struct lyd_node *node, struct nb_node *nb_node, void *ctx) diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 44c6c0097ad6..7e37a15116c7 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -12,6 +12,7 @@ #include "json.h" #include "network.h" #include "northbound_cli.h" +#include "mgmt_util.h" #include "mgmtd/mgmt.h" #include "mgmtd/mgmt_be_adapter.h" @@ -500,7 +501,7 @@ void mgmt_vty_init(void) install_element(ENABLE_NODE, &show_debugging_mgmt_cmd); - mgmt_fe_client_lib_vty_init(); + mgmt_util_vty_init(); /* * TODO: Register and handlers for auto-completion here. */ diff --git a/python/xref2vtysh.py b/python/xref2vtysh.py index 0a7e28ec7ac1..6ccbed3552ae 100644 --- a/python/xref2vtysh.py +++ b/python/xref2vtysh.py @@ -37,8 +37,9 @@ "lib/filter_cli.c": "VTYSH_ACL", "lib/if.c": "VTYSH_INTERFACE", "lib/keychain.c": "VTYSH_KEYS", - "lib/mgmt_be_client.c": "VTYSH_STATICD", - "lib/mgmt_fe_client.c": "VTYSH_MGMTD", + "lib/mgmt_be_client.c": "VTYSH_MGMT_BE", + "lib/mgmt_fe_client.c": "VTYSH_MGMT_FE", + "lib/mgmt_util.c": "VTYSH_MGMT_ALL", "lib/lib_vty.c": "VTYSH_ALL", "lib/log_vty.c": "VTYSH_ALL", "lib/nexthop_group.c": "VTYSH_NH_GROUP", diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index 65733ca61b4c..162c9dd1f5d3 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -64,6 +64,9 @@ extern struct event_loop *master; #define VTYSH_NH_GROUP VTYSH_PBRD|VTYSH_SHARPD #define VTYSH_SR VTYSH_ZEBRA|VTYSH_PATHD #define VTYSH_DPDK VTYSH_ZEBRA +#define VTYSH_MGMT_BE VTYSH_STATICD | VTYSH_ZEBRA +#define VTYSH_MGMT_FE VTYSH_MGMTD +#define VTYSH_MGMT_ALL VTYSH_MGMTD | VTYSH_MGMT_FE | VTYSH_MGMT_BE enum vtysh_write_integrated { WRITE_INTEGRATED_UNSPECIFIED, From 8b88c05e9b0a54a82a476cf5363abe6a0d3c51c8 Mon Sep 17 00:00:00 2001 From: Pushpasis Sarkar Date: Sun, 10 Sep 2023 06:40:37 -0700 Subject: [PATCH 2/5] lib,mgmtd: Add support for GETDATA_REQ on Operational DB Handle GETDATA_REQ from Front-end clients and retrieve data fron backend clients Co-authored-by: Yash Ranjan Signed-off-by: Pushpasis Sarkar --- .clang-format | 3 + lib/mgmt_be_client.c | 399 ++++++++++++++- lib/mgmt_util.c | 18 +- lib/mgmt_util.h | 10 +- lib/northbound.c | 221 ++++++-- lib/northbound.h | 103 ++++ lib/vty.c | 12 +- mgmtd/mgmt_be_adapter.c | 362 ++++++++------ mgmtd/mgmt_be_adapter.h | 70 ++- mgmtd/mgmt_defines.h | 5 +- mgmtd/mgmt_ds.c | 27 +- mgmtd/mgmt_fe_adapter.c | 7 +- mgmtd/mgmt_memory.c | 2 + mgmtd/mgmt_memory.h | 2 + mgmtd/mgmt_txn.c | 1056 +++++++++++++++++++++++++++++++++++---- mgmtd/mgmt_txn.h | 35 +- 16 files changed, 1990 insertions(+), 342 deletions(-) diff --git a/.clang-format b/.clang-format index 3971384a3659..f6a7859fce3b 100644 --- a/.clang-format +++ b/.clang-format @@ -103,6 +103,7 @@ ForEachMacros: - 'FOREACH_AFI_SAFI' - 'FOREACH_AFI_SAFI_NSF' - 'FOREACH_BE_APPLY_BATCH_IN_LIST' + - 'FOREACH_BE_GETDATA_BATCH_IN_LIST' - 'FOREACH_BE_TXN_BATCH_IN_LIST' - 'FOREACH_BE_TXN_IN_LIST' - 'FOREACH_CMT_REC' @@ -113,6 +114,8 @@ ForEachMacros: - 'FOREACH_TXN_CFG_BATCH_IN_LIST' - 'FOREACH_TXN_IN_LIST' - 'FOREACH_TXN_REQ_IN_LIST' + - 'FOREACH_XPATH_IN_LIST' + - 'FOREACH_XPATH_IN_SUBSCR_INFO' - 'JSON_FOREACH' - 'LIST_FOREACH' - 'LIST_FOREACH_SAFE' diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 394fac06ac86..965b5f9dd2d0 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -25,6 +25,14 @@ DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT, "backend client"); DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT_NAME, "backend client name"); DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_BATCH, "backend transaction batch data"); DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_TXN, "backend transaction data"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_GETDATA_REPLY, "MGMTD backend get-data reply message"); + +struct mgmt_be_iter_ctx { + int (*db_iter_fn)(const struct lysc_node *snode, + struct yang_translator *translator, + struct yang_data *data, void *arg); + void *usr_ctx; +}; enum mgmt_be_txn_event { MGMTD_BE_TXN_PROC_SETCFG = 1, @@ -37,9 +45,29 @@ struct mgmt_be_set_cfg_req { uint16_t num_cfg_changes; }; +struct mgmt_be_get_data_reply { + /* Buffer space for preparing data reply */ + int num_reply; + int last_batch; + Mgmtd__YangDataReply data_reply; + Mgmtd__YangData reply_data[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + Mgmtd__YangData * reply_datap[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + Mgmtd__YangDataValue reply_value[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + char *reply_xpathp[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; +}; + struct mgmt_be_get_data_req { char *xpaths[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; uint16_t num_xpaths; + + /* + * Buffer space for preparing reply. + * NOTE: Should only be malloc-ed on demand to reduce + * memory footprint. Freed up via mgmt_trx_req_free() + */ + struct mgmt_be_get_data_reply *reply; + + int total_reply; }; struct mgmt_be_txn_req { @@ -50,11 +78,17 @@ struct mgmt_be_txn_req { } req; }; +struct mgmt_be_client; + PREDECL_LIST(mgmt_be_batches); struct mgmt_be_batch_ctx { /* Batch-Id as assigned by MGMTD */ uint64_t batch_id; + uint64_t txn_id; + + struct mgmt_be_client *client_ctx; + struct mgmt_be_txn_req txn_req; uint32_t flags; @@ -77,6 +111,7 @@ struct mgmt_be_txn_ctx { /* List of batches belonging to this transaction */ struct mgmt_be_batches_head cfg_batches; struct mgmt_be_batches_head apply_cfgs; + struct mgmt_be_batches_head get_data; struct mgmt_be_txns_item list_linkage; @@ -93,6 +128,9 @@ DECLARE_LIST(mgmt_be_txns, struct mgmt_be_txn_ctx, list_linkage); #define FOREACH_BE_APPLY_BATCH_IN_LIST(txn, batch) \ frr_each_safe (mgmt_be_batches, &(txn)->apply_cfgs, (batch)) +#define FOREACH_BE_GETDATA_BATCH_IN_LIST(txn, batch) \ + frr_each_safe (mgmt_be_batches, &(txn)->get_data, (batch)) + struct mgmt_be_client { struct msg_client client; @@ -117,6 +155,15 @@ struct mgmt_be_client { #define FOREACH_BE_TXN_IN_LIST(client_ctx, txn) \ frr_each_safe (mgmt_be_txns, &(client_ctx)->txn_head, (txn)) +struct mgmt_be_child_iter_ctx { + const char *base_xpath; + char **child_xpath; + void **child_ctx; + int num_child; + void *ctx; + char *xpath_key; +}; + struct debug mgmt_dbg_be_client = {0, "Management backend client operations"}; const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { @@ -150,7 +197,8 @@ mgmt_be_find_batch_by_id(struct mgmt_be_txn_ctx *txn, } static struct mgmt_be_batch_ctx * -mgmt_be_batch_create(struct mgmt_be_txn_ctx *txn, uint64_t batch_id) +mgmt_be_batch_create(struct mgmt_be_txn_ctx *txn, uint64_t batch_id, + enum mgmt_be_txn_event event) { struct mgmt_be_batch_ctx *batch = NULL; @@ -160,12 +208,24 @@ mgmt_be_batch_create(struct mgmt_be_txn_ctx *txn, uint64_t batch_id) sizeof(struct mgmt_be_batch_ctx)); assert(batch); + batch->txn_id = txn->txn_id; batch->batch_id = batch_id; - mgmt_be_batches_add_tail(&txn->cfg_batches, batch); + switch (event) { + case MGMTD_BE_TXN_PROC_SETCFG: + mgmt_be_batches_add_tail(&txn->cfg_batches, batch); + break; + case MGMTD_BE_TXN_PROC_GETDATA: + mgmt_be_batches_add_tail(&txn->get_data, batch); + break; + case MGMTD_BE_TXN_PROC_GETCFG: + default: + assert(!"Invalid event type!!!"); + } + batch->txn_req.event = event; MGMTD_BE_CLIENT_DBG("Added new batch-id: %" PRIu64 - " to transaction", - batch_id); + " to transaction %p id: %" PRIu64, + batch_id, txn, txn->txn_id); } return batch; @@ -207,6 +267,10 @@ static void mgmt_be_cleanup_all_batches(struct mgmt_be_txn_ctx *txn) FOREACH_BE_APPLY_BATCH_IN_LIST (txn, batch) { mgmt_be_batch_delete(txn, &batch); } + + FOREACH_BE_GETDATA_BATCH_IN_LIST (txn, batch) { + mgmt_be_batch_delete(txn, &batch); + } } static struct mgmt_be_txn_ctx * @@ -241,6 +305,7 @@ mgmt_be_txn_create(struct mgmt_be_client *client_ctx, uint64_t txn_id) txn->client = client_ctx; mgmt_be_batches_init(&txn->cfg_batches); mgmt_be_batches_init(&txn->apply_cfgs); + mgmt_be_batches_init(&txn->get_data); mgmt_be_txns_add_tail(&client_ctx->txn_head, txn); MGMTD_BE_CLIENT_DBG("Created new txn-id: %" PRIu64, txn_id); @@ -541,14 +606,16 @@ static int mgmt_be_update_setcfg_in_batch(struct mgmt_be_client *client_ctx, int index; struct nb_cfg_change *cfg_chg; - batch = mgmt_be_batch_create(txn, batch_id); + batch = mgmt_be_find_batch_by_id(txn, batch_id); + if (!batch) + batch = mgmt_be_batch_create(txn, batch_id, + MGMTD_BE_TXN_PROC_SETCFG); if (!batch) { MGMTD_BE_CLIENT_ERR("Batch create failed!"); return -1; } txn_req = &batch->txn_req; - txn_req->event = MGMTD_BE_TXN_PROC_SETCFG; MGMTD_BE_CLIENT_DBG("Created SETCFG request for batch-id: %" PRIu64 " txn-id: %" PRIu64 " cfg-items:%d", batch_id, txn->txn_id, num_req); @@ -724,6 +791,313 @@ static int mgmt_be_process_cfg_apply(struct mgmt_be_client *client_ctx, return -1; } +/* + * Initialize backend getdata reply pointer. + */ +static void mgmt_be_init_get_data_reply(struct mgmt_be_get_data_reply *get_reply) +{ + size_t indx; + + for (indx = 0; indx < array_size(get_reply->reply_data); indx++) + get_reply->reply_datap[indx] = &get_reply->reply_data[indx]; +} + +/* + * Reset getdata reply structure to zero. + */ +static void mgmt_be_reset_get_data_reply(struct mgmt_be_get_data_reply *get_reply) +{ + int indx; + + for (indx = 0; indx < get_reply->num_reply; indx++) { + if (get_reply->reply_xpathp[indx]) { + free(get_reply->reply_xpathp[indx]); + get_reply->reply_xpathp[indx] = 0; + } + if (get_reply->reply_data[indx].xpath) { + MGMTD_BE_CLIENT_DBG("free xpath %p -> %s", + get_reply->reply_data[indx].xpath, + get_reply->reply_data[indx].xpath); + XFREE(MTYPE_TMP, get_reply->reply_data[indx].xpath); + XFREE(MTYPE_TMP, get_reply->reply_data[indx].value + ->encoded_str_val); + get_reply->reply_data[indx].xpath = 0; + } + } + + get_reply->num_reply = 0; + memset(&get_reply->data_reply, 0, sizeof(get_reply->data_reply)); + memset(&get_reply->reply_data, 0, sizeof(get_reply->reply_data)); + memset(&get_reply->reply_datap, 0, sizeof(get_reply->reply_datap)); + + memset(&get_reply->reply_value, 0, sizeof(get_reply->reply_value)); + + mgmt_be_init_get_data_reply(get_reply); +} + +/* + * Reset getdata reply buffer to zero. + */ +static void mgmt_be_reset_get_data_reply_buf(struct mgmt_be_get_data_req *get_data) +{ + if (get_data->reply) + mgmt_be_reset_get_data_reply(get_data->reply); +} + +/* + * Get data reply function from backend client to mgmt. + */ +static void mgmt_be_send_get_data_reply(uint64_t txn_id, + struct mgmt_be_get_data_req *get_req, + struct mgmt_be_batch_ctx *batch, + bool success, const char *error_if_any) +{ + struct mgmt_be_get_data_reply *get_reply; + Mgmtd__BeMessage be_msg; + Mgmtd__BeOperDataGetReply data_reply; + Mgmtd__YangDataReply reply; + + get_reply = get_req->reply; + + mgmtd__be_oper_data_get_reply__init(&data_reply); + + mgmt_yang_data_reply_init(&reply); + reply.data = get_reply->reply_datap; + reply.n_data = get_reply->num_reply; + reply.next_indx = + (!get_reply->last_batch ? get_req->total_reply : -1); + + data_reply.txn_id = batch->txn_id; + data_reply.batch_id = batch->batch_id; + data_reply.data = &reply; + data_reply.success = success; + data_reply.error = (char *)error_if_any; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_GET_REPLY; + be_msg.get_reply = &data_reply; + + MGMTD_BE_CLIENT_DBG( + "Sending GETDATA_REPLY message to MGMTD for txn 0x%llx, batch 0x%llx, success: %c, num-xpaths:%u, last:%d, next_indx:%" PRId64 "", + (unsigned long long)be_msg.get_reply->txn_id, + (unsigned long long)be_msg.get_reply->batch_id, + be_msg.get_reply->success ? 'Y' : 'N', + (unsigned int)be_msg.get_reply->data->n_data, + get_reply->last_batch, be_msg.get_reply->data->next_indx); + + mgmt_be_client_send_msg(batch->client_ctx, &be_msg); + + /* + * Reset reply buffer for next reply. + */ + mgmt_be_reset_get_data_reply_buf(get_req); +} + +/* + * Backend operdata callback function to send reply. + */ +static int mgmt_be_oper_data_callback(const struct lysc_node *snode, + struct yang_translator *translator, + struct yang_data *y_data, void *arg) +{ + struct mgmt_be_batch_ctx *batch = (struct mgmt_be_batch_ctx *)arg; + struct mgmt_be_get_data_req *get_req; + struct mgmt_be_get_data_reply *get_reply; + Mgmtd__YangData *data; + Mgmtd__YangDataValue *data_value; + + MGMTD_BE_CLIENT_DBG("reached for xpath %s and value %s", + y_data->xpath, y_data->value); + + get_req = &batch->txn_req.req.get_data; + assert(get_req); + get_reply = get_req->reply; + data = &get_reply->reply_data[get_reply->num_reply]; + data_value = &get_reply->reply_value[get_reply->num_reply]; + + mgmt_yang_data_init(data); + data->xpath = XSTRDUP(MTYPE_TMP, y_data->xpath); + mgmt_yang_data_value_init(data_value); + data_value->value_case = MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL; + data_value->encoded_str_val = XSTRDUP(MTYPE_TMP, y_data->value); + data->value = data_value; + + get_reply->num_reply++; + get_req->total_reply++; + + if (get_reply->num_reply == MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH) + mgmt_be_send_get_data_reply( + batch->txn_id, get_req, batch, true, NULL); + + yang_data_free(y_data); + + return 0; +} + +/* + * Copy the xpath info into child xpath context. This function is called + * from mgmt_be_get_children() which in turn is supplied as + * 'get_child_fn' parameter of mgmt_xpath_resolve_wildcard(). + */ +static int mgmt_be_iter_child(const struct lysc_node *snode, + struct yang_translator *translator, + struct yang_data *data, void *arg) +{ + struct mgmt_be_child_iter_ctx *ctx; + char *xpath; + + + ctx = (struct mgmt_be_child_iter_ctx *)arg; + MGMTD_BE_CLIENT_DBG(" -- child_xpath %s", data->xpath); + + /* + * Let's allocate a string for the Xpath and add it to + * the results. The same should be freed up from + * mgmt_xpath_resolve_wildcard(). + */ + xpath = calloc(1, MGMTD_MAX_XPATH_LEN); + strlcpy(xpath, data->xpath, MGMTD_MAX_XPATH_LEN); + ctx->child_xpath[ctx->num_child] = xpath; + ctx->num_child++; + + yang_data_free(data); + + return 0; +} + +/* + * Get the number of children for the given base xpath. This function is + * supplied as 'get_child_fn' parameter of mgmt_xpath_resolve_wildcard() + */ +static int mgmt_be_get_children(const char *base_xpath, + char *child_xpath[], void *child_ctx[], + int *num_child, void *ctx, char *xpath_key) +{ + char nb_xpath[MGMTD_MAX_XPATH_LEN]; + int nb_index, ret; + struct mgmt_be_child_iter_ctx iter_ctx; + + MGMTD_BE_CLIENT_DBG("started for base_xpath %s, xpath_key %s", + base_xpath, xpath_key); + + *num_child = 0; + + nb_index = mgmt_xpath_find_character(xpath_key, strlen(base_xpath), '['); + if (nb_index == -1) + return -1; + strlcpy(nb_xpath, base_xpath, sizeof(nb_xpath)); + + iter_ctx.base_xpath = base_xpath; + iter_ctx.child_xpath = child_xpath; + iter_ctx.child_ctx = child_ctx; + iter_ctx.xpath_key = xpath_key; + iter_ctx.num_child = 0; + iter_ctx.ctx = ctx; + + ret = nb_oper_data_iterate(nb_xpath, NULL, + NB_OPER_DATA_ITER_VISITLISTKEYS, + mgmt_be_iter_child, &iter_ctx); + + MGMTD_BE_CLIENT_DBG("Got %d key values for %s", + iter_ctx.num_child, xpath_key); + *num_child = iter_ctx.num_child; + + return ret; +} + +/* + * Wrapper function to iterate over the oper DB. + */ +static int mgmt_be_iterate_oper_db(const char *xpath, void *node_ctx, + void *ctx) +{ + struct mgmt_be_iter_ctx *iter_ctx; + int ret; + + iter_ctx = (struct mgmt_be_iter_ctx *)ctx; + + MGMTD_BE_CLIENT_DBG("Xpath: %s", xpath); + + ret = nb_oper_data_iterate(xpath, NULL, 0, iter_ctx->db_iter_fn, + iter_ctx->usr_ctx); + + return ret; +} + +/* + * Wrapper function to resolve wildcard entry in the given xpath. + */ +static int mgmt_be_resolve_wildcard(char *xpath, struct mgmt_be_batch_ctx *batch) +{ + struct mgmt_be_iter_ctx iter_ctx = {0}; + int ret; + + iter_ctx.db_iter_fn = mgmt_be_oper_data_callback; + iter_ctx.usr_ctx = (void *)batch; + + mgmt_remove_trailing_separator(xpath, '*'); + mgmt_remove_trailing_separator(xpath, '/'); + MGMTD_BE_CLIENT_DBG("xpath: %s", xpath); + + ret = mgmt_xpath_resolve_wildcard(xpath, 0, mgmt_be_get_children, + mgmt_be_iterate_oper_db, + (void *)&iter_ctx, 0); + + return ret; +} + +/* + * API to process get data request and send the reply. + */ +static int mgmt_be_process_get_req(struct mgmt_be_client *client_ctx, + uint64_t txn_id, uint64_t batch_id, + Mgmtd__YangGetDataReq * get_data_req[], + int num_req) +{ + struct mgmt_be_txn_ctx *txn; + struct mgmt_be_batch_ctx *batch; + struct mgmt_be_get_data_req *get_data = NULL; + int index; + char *root_xpath; + + txn = mgmt_be_find_txn_by_id(client_ctx, txn_id, true); + if (!txn) + goto proc_get_req_failed; + + batch = mgmt_be_batch_create(txn, batch_id, MGMTD_BE_TXN_PROC_GETDATA); + if (!batch) + return -1; + + batch->client_ctx = client_ctx; + get_data = &batch->txn_req.req.get_data; + if (!get_data->reply) + get_data->reply = XCALLOC( + MTYPE_MGMTD_BE_GETDATA_REPLY, + sizeof(struct mgmt_be_get_data_reply)); + + MGMTD_BE_CLIENT_DBG("Received Xpaths:"); + for (index = 0; index < num_req; index++) { + MGMTD_BE_CLIENT_DBG("%d: %s", index, get_data_req[index]->data->xpath); + mgmt_be_init_get_data_reply(get_data->reply); + root_xpath = get_data_req[index]->data->xpath; + + if (mgmt_be_resolve_wildcard(root_xpath, batch) != NB_OK) + MGMTD_BE_CLIENT_DBG("not able to resolve xpath %s", root_xpath); + } + + batch->txn_req.req.get_data.reply->last_batch = true; + mgmt_be_send_get_data_reply( + txn_id, get_data, batch, true, + NULL); + + XFREE(MTYPE_MGMTD_BE_GETDATA_REPLY, get_data->reply); + return 0; +proc_get_req_failed: + msg_conn_disconnect(&client_ctx->client.conn, true); + if (get_data && get_data->reply) + XFREE(MTYPE_MGMTD_BE_GETDATA_REPLY, get_data->reply); + return -1; +} static int mgmt_be_client_handle_msg(struct mgmt_be_client *client_ctx, Mgmtd__BeMessage *be_msg) @@ -769,11 +1143,12 @@ static int mgmt_be_client_handle_msg(struct mgmt_be_client *client_ctx, client_ctx, (uint64_t)be_msg->cfg_apply_req->txn_id); break; case MGMTD__BE_MESSAGE__MESSAGE_GET_REQ: - MGMTD_BE_CLIENT_ERR("Got unhandled message type %u", - be_msg->message_case); - /* - * TODO: Add handling code in future. - */ + MGMTD_BE_CLIENT_DBG("Got GET_DATA_REQ txn-id: %" PRIu64, + be_msg->cfg_data_req->txn_id); + mgmt_be_process_get_req(client_ctx, + (uint64_t)be_msg->get_req->txn_id, + (uint64_t)be_msg->get_req->batch_id, + be_msg->get_req->data, be_msg->get_req->n_data); break; /* * NOTE: The following messages are always sent from Backend @@ -912,7 +1287,7 @@ static void mgmt_debug_client_be_set_all(uint32_t flags, bool set) static int mgmt_debug_be_client_config_write(struct vty *vty) { if (DEBUG_MODE_CHECK(&mgmt_dbg_be_client, DEBUG_MODE_CONF)) - vty_out(vty, "debug mgmt client frontend\n"); + vty_out(vty, "debug mgmt client backend\n"); return 1; } diff --git a/lib/mgmt_util.c b/lib/mgmt_util.c index 7d8ecb6de41c..a90ad2836677 100644 --- a/lib/mgmt_util.c +++ b/lib/mgmt_util.c @@ -332,11 +332,11 @@ int mgmt_xpath_find_first_wildcard(const char *xpath, int *key_end) * Resolve wildcard entry in a given xpath. */ int mgmt_xpath_resolve_wildcard(char *xpath, int start_indx, - void (*get_child_fn)(char *base_xpath, - const char *child_xpath[], - void *child_ctxt[], - int *num_child, - void *ctxt, char* xpath_key), + int (*get_child_fn)(const char *base_xpath, + char *child_xpath[], + void *child_ctxt[], + int *num_child, + void *ctxt, char *xpath_key), int (*iter_fn)(const char *child_xpath, void *child_ctxt, void *ctxt), @@ -381,8 +381,12 @@ int mgmt_xpath_resolve_wildcard(char *xpath, int start_indx, xpath[start_indx + wc_key_start - 1] = '\0'; } num_child = array_size(chld_xpath); - (*get_child_fn)(xpath, (const char**)chld_xpath, chld_ctxt, - &num_child, ctxt, xpath_key); + ret = (*get_child_fn)(xpath, chld_xpath, chld_ctxt, &num_child, + ctxt, xpath_key); + if (ret) { + MGMTD_UTIL_DBG("Value of get child function %d", ret); + return ret; + } if (wc_key_start >= 0) xpath[start_indx + wc_key_start - 1] = orig; diff --git a/lib/mgmt_util.h b/lib/mgmt_util.h index 68b4639b37ba..e95cbc629089 100644 --- a/lib/mgmt_util.h +++ b/lib/mgmt_util.h @@ -280,11 +280,11 @@ extern int mgmt_xpath_find_first_wildcard(const char *xpath, int *key_end); * recursive resolution level. */ extern int mgmt_xpath_resolve_wildcard(char *xpath, int start_indx, - void (*get_child_fn)(char *base_xpath, - const char *child_xpath[], - void *child_ctxt[], - int *num_child, - void *ctxt, char* xpath_key), + int (*get_child_fn)(const char *base_xpath, + char *child_xpath[], + void *child_ctxt[], + int *num_child, + void *ctxt, char *xpath_key), int (*iter_fn)(const char *child_xpath, void *child_ctxt, void *ctxt), diff --git a/lib/northbound.c b/lib/northbound.c index 69b96d365693..eb236920c846 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -70,12 +70,6 @@ static int nb_transaction_process(enum nb_event event, char *errmsg, size_t errmsg_len); static void nb_transaction_apply_finish(struct nb_transaction *transaction, char *errmsg, size_t errmsg_len); -static int nb_oper_data_iter_node(const struct lysc_node *snode, - const char *xpath, const void *list_entry, - const struct yang_list_keys *list_keys, - struct yang_translator *translator, - bool first, uint32_t flags, - nb_oper_data_cb cb, void *arg); static int nb_node_check_config_only(const struct lysc_node *snode, void *arg) { @@ -1833,12 +1827,15 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction, } } -static int nb_oper_data_iter_children(const struct lysc_node *snode, - const char *xpath, const void *list_entry, - const struct yang_list_keys *list_keys, - struct yang_translator *translator, - bool first, uint32_t flags, - nb_oper_data_cb cb, void *arg) +/* + * API to iterate over child nodes. + */ +int nb_oper_data_iter_children(const struct lysc_node *snode, + const char *xpath, const void *list_entry, + const struct yang_list_keys *list_keys, + struct yang_translator *translator, + bool first, uint32_t flags, + nb_oper_data_cb cb, void *arg) { const struct lysc_node *child; @@ -1866,6 +1863,9 @@ static int nb_oper_data_iter_leaf(const struct nb_node *nb_node, if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W)) return NB_OK; + if (CHECK_FLAG(flags, NB_OPER_DATA_ITER_SKIPLEAFS)) + return NB_OK; + /* Ignore list keys. */ if (lysc_is_key(nb_node->snode)) return NB_OK; @@ -2015,6 +2015,14 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node, position++; } + if (CHECK_FLAG(flags, NB_OPER_DATA_ITER_VISITLISTKEYS)) { + struct yang_data *data = yang_data_new(xpath, NULL); + + ret = (*cb)(nb_node->snode, translator, data, arg); + + continue; + } + /* Iterate over the child nodes. */ ret = nb_oper_data_iter_children( nb_node->snode, xpath, list_entry, &list_keys, @@ -2026,13 +2034,16 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node, return NB_OK; } -static int nb_oper_data_iter_node(const struct lysc_node *snode, - const char *xpath_parent, - const void *list_entry, - const struct yang_list_keys *list_keys, - struct yang_translator *translator, - bool first, uint32_t flags, - nb_oper_data_cb cb, void *arg) +/* + * Iterate over operational data for a particular node. + */ +int nb_oper_data_iter_node(const struct lysc_node *snode, + const char *xpath_parent, + const void *list_entry, + const struct yang_list_keys *list_keys, + struct yang_translator *translator, + bool first, uint32_t flags, + nb_oper_data_cb cb, void *arg) { struct nb_node *nb_node; char xpath[XPATH_MAXLEN]; @@ -2098,31 +2109,29 @@ static int nb_oper_data_iter_node(const struct lysc_node *snode, return ret; } -int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, - uint32_t flags, nb_oper_data_cb cb, void *arg) +/* + * Iterarte over the to find the particular list entry. + */ +const void *nb_oper_get_list_entry(const char *xpath, + struct nb_node *nb_node, + struct lyd_node **pdnode, + struct yang_list_keys *list_keys) { - struct nb_node *nb_node; const void *list_entry = NULL; - struct yang_list_keys list_keys; - struct list *list_dnodes; struct lyd_node *dnode, *dn; struct listnode *ln; - int ret; + struct list *list_dnodes; + + zlog_debug("%s: start xpath %s", __func__, xpath); + *pdnode = NULL; + + if (!nb_node) + nb_node = nb_node_find(xpath); - nb_node = nb_node_find(xpath); if (!nb_node) { flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, "%s: unknown data path: %s", __func__, xpath); - return NB_ERR; - } - - /* For now this function works only with containers and lists. */ - if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) { - flog_warn( - EC_LIB_NB_OPERATIONAL_DATA, - "%s: can't iterate over YANG leaf or leaf-list [xpath %s]", - __func__, xpath); - return NB_ERR; + return NULL; } /* @@ -2131,13 +2140,13 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, */ LY_ERR err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, - LYD_NEW_PATH_UPDATE, NULL, &dnode); + LYD_NEW_PATH_UPDATE, NULL, &dnode); if (err || !dnode) { const char *errmsg = err ? ly_errmsg(ly_native_ctx) : "node not found"; flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed %s", __func__, errmsg); - return NB_ERR; + return NULL; } /* @@ -2149,6 +2158,7 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, continue; listnode_add_head(list_dnodes, dn); } + /* * Use the northbound callbacks to find list entry pointer corresponding * to the given XPath. @@ -2159,20 +2169,21 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, unsigned int n = 0; /* Obtain the list entry keys. */ - memset(&list_keys, 0, sizeof(list_keys)); + memset(list_keys, 0, sizeof(*list_keys)); LY_LIST_FOR (lyd_child(dn), child) { if (!lysc_is_key(child->schema)) break; - strlcpy(list_keys.key[n], + strlcpy(list_keys->key[n], yang_dnode_get_string(child, NULL), - sizeof(list_keys.key[n])); + sizeof(list_keys->key[n])); n++; } - list_keys.num = n; - if (list_keys.num != yang_snode_num_keys(dn->schema)) { + list_keys->num = n; + + if (list_keys->num != yang_snode_num_keys(dn->schema)) { list_delete(&list_dnodes); yang_dnode_free(dnode); - return NB_ERR_NOT_FOUND; + return NULL; } /* Find the list entry pointer. */ @@ -2184,18 +2195,128 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, __func__, xpath); list_delete(&list_dnodes); yang_dnode_free(dnode); - return NB_ERR; + return NULL; } list_entry = - nb_callback_lookup_entry(nn, list_entry, &list_keys); + nb_callback_lookup_entry(nn, list_entry, list_keys); if (list_entry == NULL) { list_delete(&list_dnodes); yang_dnode_free(dnode); - return NB_ERR_NOT_FOUND; + return NULL; + } + } + + *pdnode = dnode; + list_delete(&list_dnodes); + zlog_debug("%s: List-entry for '%s' --> %p", + __func__, nb_node->snode->name, list_entry); + return list_entry; +} + +/* + * API to iterarte over the nb tree to find the data node + * for nb_node, for a given xpath. + */ +static int nb_oper_data_walk_elem(const char *xpath, + struct nb_node *nb_node, + struct yang_translator *translator, + uint32_t flags, nb_oper_data_cb cb, + void *arg) +{ + const void *list_entry = NULL; + struct yang_list_keys list_keys; + struct lyd_node *dnode = NULL; + char *pos, *elem; + int ret = NB_OK; + + if (!nb_node) + nb_node = nb_node_find(xpath); + + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + ret = NB_ERR; + goto nb_oper_data_walk_elem_done; + } + + /* For now this function works only with containers and lists. */ + if (CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) { + flog_warn( + EC_LIB_NB_OPERATIONAL_DATA, + "%s: can't iterate a container or list [xpath %s]", + __func__, xpath); + ret = NB_ERR; + goto nb_oper_data_walk_elem_done; + } + + pos = strrchr(xpath, '/'); + if (pos) { + *pos = '\0'; + elem = pos + 1; + list_entry = + nb_oper_get_list_entry(xpath, NULL, &dnode, + &list_keys); + *pos = '/'; + if (!dnode) { + zlog_debug("%s: Did not get dnode for nb_node '%s'", + __func__, nb_node->snode->name); + ret = NB_ERR_NOT_FOUND; + goto nb_oper_data_walk_elem_done; + } else { + zlog_debug("%s: Parent list %p found for nb_node %p ('%s'/'%s')", + __func__, list_entry, nb_node, nb_node->snode->name, elem); } } + ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry, &list_keys, + translator, flags, cb, arg); + +nb_oper_data_walk_elem_done: + if (dnode) + yang_dnode_free(dnode); + + return ret; +} + +/* + * Iterate over operational data. + */ +int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, + uint32_t flags, nb_oper_data_cb cb, void *arg) +{ + struct nb_node *nb_node; + const void *list_entry = NULL; + struct yang_list_keys list_keys; + struct lyd_node *dnode = NULL; + int ret; + + zlog_debug("%s: start xpath %s", __func__, xpath); + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + return NB_ERR; + } + + /* For leaf nodes we need a different processing. */ + if (!CHECK_FLAG(nb_node->snode->nodetype, LYS_CONTAINER | LYS_LIST)) { + return nb_oper_data_walk_elem( + xpath, nb_node, translator, flags, cb, arg); + } + + list_entry = + nb_oper_get_list_entry(xpath, nb_node, &dnode, &list_keys); + if (!dnode) { + zlog_debug("%s: Did not get dnode for nb_node '%s'", + __func__, nb_node->snode->name); + ret = NB_ERR_NOT_FOUND; + goto nb_oper_data_iterate_done; + } else { + zlog_debug("%s: Parent list %p found for nb_node %p ('%s')", + __func__, list_entry, nb_node, nb_node->snode->name); + } + /* If a list entry was given, iterate over that list entry only. */ if (dnode->schema->nodetype == LYS_LIST && lyd_child(dnode)) ret = nb_oper_data_iter_children( @@ -2206,8 +2327,10 @@ int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, &list_keys, translator, true, flags, cb, arg); - list_delete(&list_dnodes); - yang_dnode_free(dnode); +nb_oper_data_iterate_done: + + if (dnode) + yang_dnode_free(dnode); return ret; } diff --git a/lib/northbound.h b/lib/northbound.h index 1723a87e4e75..efd8beee3fa1 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -713,6 +713,8 @@ typedef int (*nb_oper_data_cb)(const struct lysc_node *snode, /* Iterate over direct child nodes only. */ #define NB_OPER_DATA_ITER_NORECURSE 0x0001 +#define NB_OPER_DATA_ITER_VISITLISTKEYS 0x0002 +#define NB_OPER_DATA_ITER_SKIPLEAFS 0x0004 /* Hooks. */ DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), @@ -1282,6 +1284,107 @@ extern int nb_oper_data_iterate(const char *xpath, struct yang_translator *translator, uint32_t flags, nb_oper_data_cb cb, void *arg); +/* + * Iterarte over the to find the particular list entry. + * + * xpath + * Data path of the YANG data we want to iterate over. + * + * nb_node + * North bound node. + * + * pdnode + * Parent node. + * + * list_keys + * YANG keys list. + */ +extern const void *nb_oper_get_list_entry(const char *xpath, + struct nb_node *nb_node, + struct lyd_node **pdnode, + struct yang_list_keys *list_keys); + +/* + * Iterate over operational data for a particular node. + * + * snode + * LIBYANG source node. + * + * xpath_parent + * Parent xpath. + * + * list_entry + * YANG list entry. + * + * list_keys + * YANG keys list. + * + * translator + * YANG module translator (might be NULL). + * + * first + * priority first. + * + * flags + * NB operdata iter flags. + * + * cb + * NB callback function. + * + * arg + * arguments for callback function. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_oper_data_iter_node(const struct lysc_node *snode, + const char *xpath_parent, + const void *list_entry, + const struct yang_list_keys *list_keys, + struct yang_translator *translator, + bool first, uint32_t flags, + nb_oper_data_cb cb, void *arg); + +/* + * API to iterate over child nodes. + * + * snode + * LIBYANG source node. + * + * xpath_parent + * Parent xpath. + * + * list_entry + * YANG list entry. + * + * list_keys + * YANG keys list. + * + * translator + * YANG module translator (might be NULL). + * + * first + * priority first. + * + * flags + * NB operdata iter flags. + * + * cb + * NB callback function. + * + * arg + * arguments for callback function. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_oper_data_iter_children(const struct lysc_node *snode, + const char *xpath, const void *list_entry, + const struct yang_list_keys *list_keys, + struct yang_translator *translator, + bool first, uint32_t flags, + nb_oper_data_cb cb, void *arg); + /* * Validate if the northbound operation is valid for the given node. * diff --git a/lib/vty.c b/lib/vty.c index b8b981dba625..f44c28fa7f5c 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -3637,19 +3637,15 @@ static int vty_mgmt_get_data_result_notified( " req-id %" PRIu64, client_id, req_id); - if (req_id != mgmt_last_req_id) { + if (req_id != mgmt_last_req_id) mgmt_last_req_id = req_id; - vty_out(vty, "[\n"); - } - for (indx = 0; indx < num_data; indx++) { + for (indx = 0; indx < num_data; indx++) vty_out(vty, " \"%s\": \"%s\"\n", yang_data[indx]->xpath, yang_data[indx]->value->encoded_str_val); - } - if (next_key < 0) { - vty_out(vty, "]\n"); + + if (next_key < 0) vty_mgmt_resume_response(vty, success); - } return 0; } diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index be7a29c63456..a8ba837bd053 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -15,10 +15,12 @@ #include "libfrr.h" #include "mgmt_msg.h" #include "mgmt_pb.h" +#include "mgmt_util.h" #include "mgmtd/mgmt.h" #include "mgmtd/mgmt_memory.h" #include "mgmt_be_client.h" #include "mgmtd/mgmt_be_adapter.h" +#include "mgmtd/mgmt_txn.h" #define MGMTD_BE_ADAPTER_DBG(fmt, ...) \ DEBUGD(&mgmt_debug_be, "BE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) @@ -61,16 +63,19 @@ struct mgmt_be_get_adapter_config_params { static struct mgmt_be_client_xpath staticd_xpaths[] = { { .xpath = "/frr-vrf:lib/*", - .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG, + .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG + | MGMT_SUBSCR_OPER_OWN, }, { .xpath = "/frr-interface:lib/*", - .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG, + .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG + | MGMT_SUBSCR_OPER_OWN, }, { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/*", - .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG, + .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG + | MGMT_SUBSCR_OPER_OWN, }, }; #endif @@ -182,133 +187,6 @@ static void mgmt_be_xpath_map_cleanup(void) darr_free(mgmt_xpath_map); } -static int mgmt_be_eval_regexp_match(const char *xpath_regexp, - const char *xpath) -{ - int match_len = 0, re_indx = 0, xp_indx = 0; - int rexp_len, xpath_len; - bool match = true, re_wild = false, xp_wild = false; - bool delim = false, enter_wild_match = false; - char wild_delim = 0; - - rexp_len = strlen(xpath_regexp); - xpath_len = strlen(xpath); - - /* - * Remove the trailing wildcard from the regexp and Xpath. - */ - if (rexp_len && xpath_regexp[rexp_len-1] == '*') - rexp_len--; - if (xpath_len && xpath[xpath_len-1] == '*') - xpath_len--; - - if (!rexp_len || !xpath_len) - return 0; - - for (re_indx = 0, xp_indx = 0; - match && re_indx < rexp_len && xp_indx < xpath_len;) { - match = (xpath_regexp[re_indx] == xpath[xp_indx]); - - /* - * Check if we need to enter wildcard matching. - */ - if (!enter_wild_match && !match && - (xpath_regexp[re_indx] == '*' - || xpath[xp_indx] == '*')) { - /* - * Found wildcard - */ - enter_wild_match = - (xpath_regexp[re_indx-1] == '/' - || xpath_regexp[re_indx-1] == '\'' - || xpath[xp_indx-1] == '/' - || xpath[xp_indx-1] == '\''); - if (enter_wild_match) { - if (xpath_regexp[re_indx] == '*') { - /* - * Begin RE wildcard match. - */ - re_wild = true; - wild_delim = xpath_regexp[re_indx-1]; - } else if (xpath[xp_indx] == '*') { - /* - * Begin XP wildcard match. - */ - xp_wild = true; - wild_delim = xpath[xp_indx-1]; - } - } - } - - /* - * Check if we need to exit wildcard matching. - */ - if (enter_wild_match) { - if (re_wild && xpath[xp_indx] == wild_delim) { - /* - * End RE wildcard matching. - */ - re_wild = false; - if (re_indx < rexp_len-1) - re_indx++; - enter_wild_match = false; - } else if (xp_wild - && xpath_regexp[re_indx] == wild_delim) { - /* - * End XP wildcard matching. - */ - xp_wild = false; - if (xp_indx < xpath_len-1) - xp_indx++; - enter_wild_match = false; - } - } - - match = (xp_wild || re_wild - || xpath_regexp[re_indx] == xpath[xp_indx]); - - /* - * Check if we found a delimiter in both the Xpaths - */ - if ((xpath_regexp[re_indx] == '/' - && xpath[xp_indx] == '/') - || (xpath_regexp[re_indx] == ']' - && xpath[xp_indx] == ']') - || (xpath_regexp[re_indx] == '[' - && xpath[xp_indx] == '[')) { - /* - * Increment the match count if we have a - * new delimiter. - */ - if (match && re_indx && xp_indx && !delim) - match_len++; - delim = true; - } else { - delim = false; - } - - /* - * Proceed to the next character in the RE/XP string as - * necessary. - */ - if (!re_wild) - re_indx++; - if (!xp_wild) - xp_indx++; - } - - /* - * If we finished matching and the last token was a full match - * increment the match count appropriately. - */ - if (match && !delim && - (xpath_regexp[re_indx] == '/' - || xpath_regexp[re_indx] == ']')) - match_len++; - - return match_len; -} - static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter) { MGMTD_BE_ADAPTER_DBG("deleting client adapter '%s'", adapter->name); @@ -323,7 +201,7 @@ static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter) } assert(adapter->refcount == 1); - mgmt_be_adapter_unlock(&adapter); + MGMTD_BE_ADAPTER_UNLOCK(&adapter); } static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) @@ -491,9 +369,23 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, be_msg->cfg_apply_reply->error_if_any, adapter); break; case MGMTD__BE_MESSAGE__MESSAGE_GET_REPLY: + MGMTD_BE_ADAPTER_DBG( + "Got %s GET_REPLY Msg from '%s' with %d data for Trxn-Id 0x%" PRIx64 " Batch-Id 0x%" PRIx64 " with Err:'%s'", + be_msg->get_reply->success ? "successful" : "failed", + adapter->name, (int)be_msg->get_reply->data->n_data, + be_msg->get_reply->txn_id, be_msg->get_reply->batch_id, + be_msg->get_reply->error + ? be_msg->get_reply->error + : "None"); /* - * TODO: Add handling code in future. + * Forward the GET_REPLY reply to txn module. */ + mgmt_txn_notify_be_getdata_req_reply( + be_msg->get_reply->txn_id, + be_msg->get_reply->success, + be_msg->get_reply->batch_id, + be_msg->get_reply->data, + be_msg->get_reply->error, adapter); break; /* * NOTE: The following messages are always sent from MGMTD to @@ -584,6 +476,34 @@ int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, return mgmt_be_adapter_send_msg(adapter, &be_msg); } +/* + * Send GET_DATA_REQ to a backend Adapter for a specific set + * XPATHs. + */ +int mgmt_be_send_get_data_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, uint64_t batch_id, + struct mgmt_be_datareq *data_req) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeOperDataGetReq get_req; + + mgmtd__be_oper_data_get_req__init(&get_req); + get_req.txn_id = txn_id; + get_req.batch_id = batch_id; + get_req.n_data = data_req->num_reqs; + get_req.data = data_req->getdata_reqs; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_GET_REQ; + be_msg.get_req = &get_req; + + MGMTD_BE_ADAPTER_DBG( + "Sending GET_REQ message to Backend client '%s' for Trxn-Id 0x%" PRIx64, + adapter->name, txn_id); + + return mgmt_be_adapter_send_msg(adapter, &be_msg); +} + static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data, size_t len, struct msg_conn *conn) { @@ -612,8 +532,9 @@ static void mgmt_be_iter_and_get_cfg(struct mgmt_ds_ctx *ds_ctx, uint subscr; subscr = mgmt_be_get_subscr_for_xpath_and_client( - xpath, adapter->id, MGMT_SUBSCR_NOTIFY_CFG); - if (subscr) + xpath, adapter->id, + MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG); + if (subscr & (MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG)) nb_config_diff_created(node, &parms->seq, parms->cfg_chgs); } @@ -663,17 +584,25 @@ mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) &adapter->conn_init_ev); } -void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter) +void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter, + const char *file, int line) { adapter->refcount++; + MGMTD_BE_ADAPTER_DBG("%s:%d --> Lock BE adapter '%s' (%p) refcnt: %d", + file, line, adapter->name, adapter, adapter->refcount); } -extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter) +void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter, + const char *file, int line) { struct mgmt_be_client_adapter *a = *adapter; assert(a && a->refcount); - if (!--a->refcount) { + a->refcount--; + MGMTD_BE_ADAPTER_DBG("%s:%d --> Unlock BE adapter '%s' (%p) refcnt: %d", + file, line, (*adapter)->name, *adapter, + (*adapter)->refcount); + if (!a->refcount) { mgmt_be_adapters_del(&mgmt_be_adapters, a); EVENT_OFF(a->conn_init_ev); msg_server_conn_delete(a->conn); @@ -731,7 +660,7 @@ struct msg_conn *mgmt_be_create_adapter(int conn_fd, union sockunion *from) snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", conn_fd); - mgmt_be_adapter_lock(adapter); + MGMTD_BE_ADAPTER_LOCK(adapter); mgmt_be_adapters_add_tail(&mgmt_be_adapters, adapter); RB_INIT(nb_config_cbs, &adapter->cfg_chgs); @@ -788,32 +717,123 @@ int mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, return 0; } -void mgmt_be_get_subscr_info_for_xpath( - const char *xpath, struct mgmt_be_client_subscr_info *subscr_info) +/* + * This function maps a YANG data Xpath to one or more + * Backend Clients that should be contacted for various purposes. + * Note - Caller should de-allocate xp_map uisng + * mgmt_be_cleanup_xpath_subscr_info() + */ +int mgmt_be_get_subscr_info_for_xpath( + const char *xpath, struct mgmt_be_client_subscr_info *subscr_info, + bool get_full_match) { + int indx, indx1, match, max_match = 0, num_reg; struct mgmt_be_xpath_map *map; enum mgmt_be_client_id id; + struct mgmt_be_xpath_map + *reg_maps[MGMTD_BE_MAX_NUM_XPATH_MAP] = {0}; + char *xp_matches[MGMTD_BE_MAX_NUM_XPATH_MAP] = {0}; + char *xp_match = NULL; + struct mgmt_xpath_entry *xp_map = NULL; + bool root_xp = false; + + if (!subscr_info) + return -1; + num_reg = 0; memset(subscr_info, 0, sizeof(*subscr_info)); + FOREACH_MGMTD_BE_CLIENT_ID (id) { + mgmt_xpaths_init( + &subscr_info->xpath_subscr[id] + .xpaths); + } - MGMTD_BE_ADAPTER_DBG("XPATH: '%s'", xpath); + if (strlen(xpath) <= 2 && xpath[0] == '/' + && (!xpath[1] || xpath[1] == '*')) { + root_xp = true; + } + + MGMTD_BE_ADAPTER_DBG("XPATH: %s", xpath); darr_foreach_p (mgmt_xpath_map, map) { - if (!mgmt_be_eval_regexp_match(map->xpath_regexp, xpath)) - continue; + xp_match = NULL; + /* + * For Xpaths: '/' and '/ *' all xpath maps should match + * the given xpath. + */ + if (!root_xp) { + match = mgmt_xpath_eval_regexp_match( + map->xpath_regexp, xpath, + true, &xp_match, NULL, + get_full_match ? false : true); + MGMTD_BE_ADAPTER_DBG("RE: '%s' -- Match:%d, Max-match: %d, #Reg: %d", + map->xpath_regexp, match, max_match, num_reg); + if (!match || match < max_match) { + free (xp_match); + continue; + } + + if (match > max_match) { + for (indx1 = 0; indx1 < num_reg; indx1++) { + if (xp_matches[indx1]) { + free(xp_matches[indx1]); + xp_matches[indx1] = NULL; + } + } + + num_reg = 0; + max_match = match; + } + } else + xp_match = strdup(map->xpath_regexp); + + reg_maps[num_reg] = map; + xp_matches[num_reg] = xp_match; + xp_match = NULL; + num_reg++; + } + + MGMTD_BE_ADAPTER_DBG("Mapped regex to %d Map entries...", num_reg); + for (indx = 0; indx < num_reg; indx++) { + MGMTD_BE_ADAPTER_DBG("%d : %s", indx, reg_maps[indx]->xpath_regexp); FOREACH_MGMTD_BE_CLIENT_ID (id) { - subscr_info->xpath_subscr[id] |= map->subscr_info[id]; + if (reg_maps[indx]->subscr_info[id]) { + MGMTD_BE_ADAPTER_DBG( + "Cient: %s", + mgmt_be_client_id2name(id)); + if (!subscr_info->xpath_subscr[id].subscribed) { + mgmt_xpaths_init( + &subscr_info->xpath_subscr[id] + .xpaths); + subscr_info->xpath_subscr[id].subscribed |= + reg_maps[indx]->subscr_info[id]; + } + + /* + * Generate xp_map + */ + xp_map = XCALLOC( + MTYPE_MGMTD_XPATH_MAP, + sizeof(struct mgmt_xpath_entry)); + assert(xp_map); + xp_map->subscribed = + reg_maps[indx]->subscr_info[id]; + xp_map->xpath = strdup(xp_matches[indx]); + mgmt_xpaths_add_tail( + &subscr_info->xpath_subscr[id] + .xpaths, + xp_map); + } } } - if (DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)) { - FOREACH_MGMTD_BE_CLIENT_ID (id) { - if (!subscr_info->xpath_subscr[id]) - continue; - MGMTD_BE_ADAPTER_DBG("Cient: %s: subscribed: 0x%x", - mgmt_be_client_id2name(id), - subscr_info->xpath_subscr[id]); + for (indx = 0; indx < (int)array_size(xp_matches); indx++) { + if (xp_matches[indx]) { + free(xp_matches[indx]); + xp_matches[indx] = NULL; } } + + return 0; } /** @@ -843,8 +863,10 @@ static uint mgmt_be_get_subscr_for_xpath_and_client( map = &mgmt_client_xpaths[client_id]; for (i = 0; i < map->nxpaths; i++) { - if (!mgmt_be_eval_regexp_match(map->xpaths[i].xpath, xpath)) + if (!mgmt_xpath_eval_regexp_match(map->xpaths[i].xpath, xpath, + false, NULL, NULL, true)) continue; + MGMTD_BE_ADAPTER_DBG("xpath: %s: matched: %s", map->xpaths[i].xpath, xpath); subscr |= map->xpaths[i].subscribed; @@ -856,6 +878,29 @@ static uint mgmt_be_get_subscr_for_xpath_and_client( return subscr; } +/* + * Cleanup xpath map. + */ +void mgmt_be_cleanup_xpath_subscr_info( + struct mgmt_be_client_subscr_info *subscr) +{ + enum mgmt_be_client_id id; + struct mgmt_xpath_entry *xp_map = NULL; + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (!subscr->xpath_subscr[id].subscribed) + continue; + FOREACH_XPATH_IN_SUBSCR_INFO (subscr, id, xp_map) { + if (xp_map->xpath) + free((char *)xp_map->xpath); + mgmt_xpaths_del( + &subscr->xpath_subscr[id].xpaths, + xp_map); + XFREE(MTYPE_MGMTD_XPATH_MAP, xp_map); + } + } +} + void mgmt_be_adapter_status_write(struct vty *vty) { struct mgmt_be_client_adapter *adapter; @@ -889,7 +934,7 @@ void mgmt_be_xpath_register_write(struct vty *vty) vty_out(vty, "MGMTD Backend XPath Registry\n"); - darr_foreach_p (mgmt_xpath_map, map) { + darr_foreach_p(mgmt_xpath_map, map) { vty_out(vty, " - XPATH: '%s'\n", map->xpath_regexp); FOREACH_MGMTD_BE_CLIENT_ID (id) { info = map->subscr_info[id]; @@ -915,13 +960,14 @@ void mgmt_be_xpath_subscr_info_write(struct vty *vty, const char *xpath) struct mgmt_be_client_subscr_info subscr; enum mgmt_be_client_id id; struct mgmt_be_client_adapter *adapter; + struct mgmt_xpath_entry *xp; uint info; - mgmt_be_get_subscr_info_for_xpath(xpath, &subscr); + mgmt_be_get_subscr_info_for_xpath(xpath, &subscr, false); vty_out(vty, "XPath: '%s'\n", xpath); FOREACH_MGMTD_BE_CLIENT_ID (id) { - info = subscr.xpath_subscr[id]; + info = subscr.xpath_subscr[id].subscribed; if (!info) continue; vty_out(vty, @@ -931,7 +977,11 @@ void mgmt_be_xpath_subscr_info_write(struct vty *vty, const char *xpath) (info & MGMT_SUBSCR_NOTIFY_CFG) != 0, (info & MGMT_SUBSCR_OPER_OWN) != 0); adapter = mgmt_be_get_adapter_by_id(id); - if (adapter) + if (adapter) { vty_out(vty, " -- Adapter: %p\n", adapter); + vty_out(vty, " -- Matches:\n"); + FOREACH_XPATH_IN_SUBSCR_INFO (&subscr, id, xp) + vty_out(vty, " -- %s\n", xp->xpath); + } } } diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h index ca8f55c457ff..0edc2d19daa9 100644 --- a/mgmtd/mgmt_be_adapter.h +++ b/mgmtd/mgmt_be_adapter.h @@ -81,10 +81,32 @@ DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); #define MGMT_SUBSCR_OPER_OWN 0x4 #define MGMT_SUBSCR_ALL 0x7 +PREDECL_LIST(mgmt_xpaths); +struct mgmt_xpath_entry { + const char *xpath; + uint subscribed; + struct mgmt_xpaths_item xpaths_linkage; +}; +DECLARE_LIST(mgmt_xpaths, struct mgmt_xpath_entry, xpaths_linkage); + +#define FOREACH_XPATH_IN_LIST(list, xp) \ + frr_each_safe(mgmt_xpaths, (list), (xp)) + +struct mgmt_be_xpath_subscr_info { + uint subscribed; + struct mgmt_xpaths_head xpaths; +}; + +#define MGMTD_BE_MAX_NUM_XPATH_MAP 256 + struct mgmt_be_client_subscr_info { - uint xpath_subscr[MGMTD_BE_CLIENT_ID_MAX]; + struct mgmt_be_xpath_subscr_info + xpath_subscr[MGMTD_BE_CLIENT_ID_MAX]; }; +#define FOREACH_XPATH_IN_SUBSCR_INFO(subscr, id, xp) \ + FOREACH_XPATH_IN_LIST (&(subscr)->xpath_subscr[(id)].xpaths, (xp)) + /* Initialise backend adapter module. */ extern void mgmt_be_adapter_init(struct event_loop *tm); @@ -92,10 +114,17 @@ extern void mgmt_be_adapter_init(struct event_loop *tm); extern void mgmt_be_adapter_destroy(void); /* Acquire lock for backend adapter. */ -extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter); +extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter, + const char *file, int line); /* Remove lock from backend adapter. */ -extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter); +extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter, + const char *file, int line); + +#define MGMTD_BE_ADAPTER_LOCK(adapter) \ + mgmt_be_adapter_lock(adapter, __FILE__, __LINE__) +#define MGMTD_BE_ADAPTER_UNLOCK(adapter) \ + mgmt_be_adapter_unlock(adapter, __FILE__, __LINE__) /* Create backend adapter. */ extern struct msg_conn *mgmt_be_create_adapter(int conn_fd, @@ -180,9 +209,11 @@ extern void mgmt_be_xpath_register_write(struct vty *vty); * subscr_info - An array of uint indexed by client id * each eleemnt holds the subscription info * for that client. + * get_full_match - Gets the full matched xpaths. */ -extern void mgmt_be_get_subscr_info_for_xpath( - const char *xpath, struct mgmt_be_client_subscr_info *subscr_info); +extern int mgmt_be_get_subscr_info_for_xpath( + const char *xpath, struct mgmt_be_client_subscr_info *subscr_info, + bool get_full_match); /* * Dump backend client information for a given xpath to vty. @@ -190,4 +221,33 @@ extern void mgmt_be_get_subscr_info_for_xpath( extern void mgmt_be_xpath_subscr_info_write(struct vty *vty, const char *xpath); +/* + * Cleanup xpath map + * + * Args: + * subscr_info - An array of uint indexed by client id + * each eleemnt holds the subscription info + * for that client. + */ +extern void mgmt_be_cleanup_xpath_subscr_info( + struct mgmt_be_client_subscr_info *subscr); + +/* + * Send GET_DATA_REQ to a backend Adapter for a specific set + * XPATHs. + * + * Args: + * adapter - Backend adapter information. + * txn_id - Unique transaction identifier. + * batch_id - Unique batch identifier. + * data_req - Specific data-request to send. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int +mgmt_be_send_get_data_req(struct mgmt_be_client_adapter *adptr, + uint64_t txn_id, uint64_t batch_id, + struct mgmt_be_datareq *data_req); + #endif /* _FRR_MGMTD_BE_ADAPTER_H_ */ diff --git a/mgmtd/mgmt_defines.h b/mgmtd/mgmt_defines.h index 40fa67075d0e..7e2e9a31f60e 100644 --- a/mgmtd/mgmt_defines.h +++ b/mgmtd/mgmt_defines.h @@ -19,7 +19,10 @@ #define MGMTD_MAX_NUM_XPATH_REG 128 -#define MGMTD_MAX_NUM_DATA_REQ_IN_BATCH 32 +#define MGMTD_MAX_NUM_DATA_REQ_IN_BATCH \ + ((10 * MGMTD_BE_MSG_MAX_LEN) \ + / MGMTD_MAX_XPATH_LEN) + #define MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH 8 enum mgmt_result { diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c index a3dcbd85ef29..8bcc8381fbdc 100644 --- a/mgmtd/mgmt_ds.c +++ b/mgmtd/mgmt_ds.c @@ -46,7 +46,7 @@ struct mgmt_ds_iter_ctx { }; struct mgmt_ds_child_collect_ctx { - const char **child_xpath; + char **child_xpath; void **child_ctx; int max_child; int num_child; @@ -174,6 +174,9 @@ static int mgmt_walk_ds_nodes(struct mgmt_ds_ctx *ds_ctx, /* * Stores the child node xpath and node in the given xpath context. + * This function is called from mgmt_ds_get_child_nodes() which in + * turn is supplied as 'get_child_fn' parameter of + * mgmt_xpath_resolve_wildcard(). */ static void mgmt_ds_collect_child_node(struct mgmt_ds_ctx *ds_ctx, const char *xpath, @@ -190,7 +193,15 @@ static void mgmt_ds_collect_child_node(struct mgmt_ds_ctx *ds_ctx, return; } - coll_ctx->child_xpath[coll_ctx->num_child] = xpath; + /* + * Let's allocate a string for the Xpath and add it to + * the results. The same should be freed up from + * mgmt_xpath_resolve_wildcard(). + */ + coll_ctx->child_xpath[coll_ctx->num_child] = + calloc(1, MGMTD_MAX_XPATH_LEN); + strlcpy(coll_ctx->child_xpath[coll_ctx->num_child], xpath, + MGMTD_MAX_XPATH_LEN); coll_ctx->child_ctx[coll_ctx->num_child] = node; MGMTD_DS_DBG(" -- [%d] Child XPATH: %s", coll_ctx->num_child+1, coll_ctx->child_xpath[coll_ctx->num_child]); @@ -199,11 +210,13 @@ static void mgmt_ds_collect_child_node(struct mgmt_ds_ctx *ds_ctx, /* * Iterates over the datastore nodes to get child nodes for the xpath. + * This function is supplied as 'get_child_fn' parameter of + * mgmt_xpath_resolve_wildcard() */ -static void mgmt_ds_get_child_nodes(char *base_xpath, - const char *child_xpath[], - void *child_ctx[], int *num_child, - void *ctx, char *xpath_key) +static int mgmt_ds_get_child_nodes(const char *base_xpath, + char *child_xpath[], + void *child_ctx[], int *num_child, + void *ctx, char *xpath_key) { struct mgmt_ds_iter_ctx *iter_ctx; struct mgmt_ds_ctx *ds_ctx; @@ -221,6 +234,8 @@ static void mgmt_ds_get_child_nodes(char *base_xpath, mgmt_ds_collect_child_node, &coll_ctx, 1, false); *num_child = coll_ctx.num_child; + + return 0; } /* diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index 2b2471c9014e..e5ae7242c648 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -457,8 +457,11 @@ static int fe_adapter_send_get_reply(struct mgmt_fe_session_ctx *session, fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_GET_REPLY; fe_msg.get_reply = &get_reply; - MGMTD_FE_ADAPTER_DBG("Sending GET_REPLY message to MGMTD Frontend client '%s'", - session->adapter->name); + MGMTD_FE_ADAPTER_DBG("Sending GET_DATA_REPLY message to MGMTD Frontend client '%s' (req-id:%" PRIu64 ", success: %c, #xpaths: %d, next-indx: %" PRId64 ")", + session->adapter->name, get_reply.req_id, + get_reply.success ? 'Y' : 'N', + get_reply.data ? (int) get_reply.data->n_data : 0, + get_reply.data ? get_reply.data->next_indx : -2); /* * Cleanup the SHOW transaction associated with this session. diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c index b2a0f0e848c0..faa3126745b6 100644 --- a/mgmtd/mgmt_memory.c +++ b/mgmtd/mgmt_memory.c @@ -30,4 +30,6 @@ DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REQ, "txn get-data requests"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REPLY, "txn get-data replies"); DEFINE_MTYPE(MGMTD, MGMTD_TXN_CFG_BATCH, "txn config batches"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_DATA_BATCH, "MGMTD Transaction Data Batches"); DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info"); +DEFINE_MTYPE(MGMTD, MGMTD_XPATH_MAP, "Xpath Map"); diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h index 06518e38384f..f5a019e87746 100644 --- a/mgmtd/mgmt_memory.h +++ b/mgmtd/mgmt_memory.h @@ -24,6 +24,8 @@ DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ); DECLARE_MTYPE(MGMTD_TXN_GETDATA_REQ); DECLARE_MTYPE(MGMTD_TXN_GETDATA_REPLY); DECLARE_MTYPE(MGMTD_TXN_CFG_BATCH); +DECLARE_MTYPE(MGMTD_TXN_DATA_BATCH); DECLARE_MTYPE(MGMTD_BE_ADAPTER_MSG_BUF); DECLARE_MTYPE(MGMTD_CMT_INFO); +DECLARE_MTYPE(MGMTD_XPATH_MAP); #endif /* _FRR_MGMTD_MEMORY_H */ diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 1fdc7a1bb7f9..15b8d4fcae55 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -28,6 +28,7 @@ enum mgmt_txn_event { MGMTD_TXN_PROC_GETCFG, MGMTD_TXN_PROC_GETDATA, MGMTD_TXN_COMMITCFG_TIMEOUT, + MGMTD_TXN_GETDATA_TIMEOUT, MGMTD_TXN_CLEANUP }; @@ -149,6 +150,28 @@ struct mgmt_commit_cfg_req { struct mgmt_commit_stats *cmt_stats; }; +PREDECL_LIST(mgmt_txn_data_batches); + +struct mgmt_txn_be_data_batch { + struct mgmt_txn_req *txn_req; + uint64_t batch_id; + enum mgmt_be_client_id be_id; + struct mgmt_be_client_adapter *be_adapter; + uint xp_subscr[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; + Mgmtd__YangGetDataReq get_data[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; + Mgmtd__YangGetDataReq * get_datap[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; + Mgmtd__YangData data[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; + size_t num_get_data; + int buf_space_left; + struct mgmt_txn_data_batches_item list_linkage; +}; + +DECLARE_LIST(mgmt_txn_data_batches, struct mgmt_txn_be_data_batch, + list_linkage); + +#define FOREACH_TXN_DATA_BATCH_IN_LIST(list, batch) \ + frr_each_safe(mgmt_txn_data_batches, list, batch) + struct mgmt_get_data_reply { /* Buffer space for preparing data reply */ int num_reply; @@ -173,6 +196,29 @@ struct mgmt_get_data_req { */ struct mgmt_get_data_reply *reply; + /* + * Details on all the Backend Clients associated with + * this commit. + */ + struct mgmt_be_client_subscr_info subscr_info; + + /* + * List of backend batches for this commit to be validated + * and applied at the backend. + */ + struct mgmt_txn_data_batches_head + data_batches[MGMTD_BE_CLIENT_ID_MAX]; + + /* + * The last batch added for any backend client. This is always on + * 'batches' + */ + struct mgmt_txn_be_data_batch + *last_data_batch[MGMTD_BE_CLIENT_ID_MAX]; + + struct hash *batches; + uint64_t next_batch_id; + int last_batch_cnt; int total_reply; }; @@ -207,6 +253,7 @@ struct mgmt_txn_ctx { struct event *proc_get_cfg; struct event *proc_get_data; struct event *comm_cfg_timeout; + struct event *get_data_timeout; struct event *clnup; /* List of backend adapters involved in this transaction */ @@ -238,6 +285,7 @@ struct mgmt_txn_ctx { */ struct mgmt_txn_reqs_head get_data_reqs; struct mgmt_txn_reqs_head pending_get_datas; + bool be_txn_create_sent[MGMTD_BE_CLIENT_ID_MAX]; /* * There will always be one commit-config allowed for a given * transaction/session. No need to maintain lists for it. @@ -269,6 +317,7 @@ static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line); static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file, int line); static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, + struct mgmt_txn_req *txn_req, struct mgmt_be_client_adapter *adapter); static struct event_loop *mgmt_txn_tm; @@ -286,7 +335,9 @@ mgmt_txn_cfg_batch_alloc(struct mgmt_txn_ctx *txn, enum mgmt_be_client_id id, struct mgmt_be_client_adapter *be_adapter) { struct mgmt_txn_be_cfg_batch *batch; + struct mgmt_txn_req *txn_req; + txn_req = txn->commit_cfg_req; batch = XCALLOC(MTYPE_MGMTD_TXN_CFG_BATCH, sizeof(struct mgmt_txn_be_cfg_batch)); assert(batch); @@ -301,7 +352,7 @@ mgmt_txn_cfg_batch_alloc(struct mgmt_txn_ctx *txn, enum mgmt_be_client_id id, batch->be_adapter = be_adapter; batch->buf_space_left = MGMTD_BE_CFGDATA_MAX_MSG_LEN; if (be_adapter) - mgmt_be_adapter_lock(be_adapter); + MGMTD_BE_ADAPTER_LOCK(be_adapter); txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] = batch; if (!txn->commit_cfg_req->req.commit_cfg.next_batch_id) @@ -310,6 +361,10 @@ mgmt_txn_cfg_batch_alloc(struct mgmt_txn_ctx *txn, enum mgmt_be_client_id id, hash_get(txn->commit_cfg_req->req.commit_cfg.batches, batch, hash_alloc_intern); + MGMTD_TXN_DBG(" Batch: %p (id: %" PRIu64 "), Trxn: %p (id: %" PRIu64 "), Req: %p, Client: %s (Id: %u)", + batch, batch->batch_id, txn, txn->txn_id, txn_req, + be_adapter->name, batch->be_id); + return batch; } @@ -318,8 +373,11 @@ static void mgmt_txn_cfg_batch_free(struct mgmt_txn_be_cfg_batch **batch) size_t indx; struct mgmt_commit_cfg_req *cmtcfg_req; - MGMTD_TXN_DBG(" freeing batch-id: %" PRIu64 " txn-id %" PRIu64, - (*batch)->batch_id, (*batch)->txn->txn_id); + MGMTD_TXN_DBG(" Batch: %p (id: %" PRIu64 "), Trxn: %p (id: %" PRIu64 "), Client: %s (Id: %u)", + *batch, (*batch)->batch_id, (*batch)->txn, + (*batch)->txn->txn_id, (*batch)->be_adapter ? + (*batch)->be_adapter->name : "NULL", + (*batch)->be_id); assert((*batch)->txn && (*batch)->txn->type == MGMTD_TXN_TYPE_CONFIG); @@ -329,7 +387,7 @@ static void mgmt_txn_cfg_batch_free(struct mgmt_txn_be_cfg_batch **batch) mgmt_txn_batches_del(&cmtcfg_req->next_batches[(*batch)->be_id], *batch); if ((*batch)->be_adapter) - mgmt_be_adapter_unlock(&(*batch)->be_adapter); + MGMTD_BE_ADAPTER_UNLOCK(&(*batch)->be_adapter); for (indx = 0; indx < (*batch)->num_cfg_data; indx++) { if ((*batch)->data[indx].xpath) { @@ -403,6 +461,169 @@ static void mgmt_txn_cleanup_be_cfg_batches(struct mgmt_txn_ctx *txn, txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] = NULL; } +/* + * Allocate a new backend adapter transaction batch. + */ +static struct mgmt_txn_be_data_batch * +mgmt_txn_data_batch_alloc(struct mgmt_txn_req *txn_req, + enum mgmt_be_client_id id, + struct mgmt_be_client_adapter *be_adapter) +{ + struct mgmt_txn_be_data_batch *data_batch; + + data_batch = XCALLOC(MTYPE_MGMTD_TXN_DATA_BATCH, + sizeof(struct mgmt_txn_be_data_batch)); + assert(data_batch); + data_batch->be_id = id; + data_batch->txn_req = txn_req; + MGMTD_TXN_LOCK(txn_req->txn); + assert(txn_req->req.get_data); + mgmt_txn_data_batches_add_tail( + &txn_req->req.get_data->data_batches[id], + data_batch); + data_batch->be_adapter = be_adapter; + data_batch->buf_space_left = MGMTD_BE_CFGDATA_MAX_MSG_LEN; + if (be_adapter) + MGMTD_BE_ADAPTER_LOCK(be_adapter); + + txn_req->req.get_data->last_data_batch[id] = + data_batch; + if (!txn_req->req.get_data->next_batch_id) + txn_req->req.get_data->next_batch_id++; + data_batch->batch_id = + txn_req->req.get_data->next_batch_id++; + hash_get(txn_req->req.get_data->batches, data_batch, + hash_alloc_intern); + + MGMTD_TXN_DBG(" Batch: %p (id: %" PRIu64 "), Trxn: %p (id: %" PRIu64 "), Req: %p, Client: %s (Id: %u)", + data_batch, data_batch->batch_id, txn_req->txn, + txn_req->txn->txn_id, txn_req, be_adapter->name, + data_batch->be_id); + + return data_batch; +} + +/* + * Free backend adapter transaction batch. + */ +static void +mgmt_txn_data_batch_free(struct mgmt_txn_be_data_batch **data_batch, + bool remove_from_hash) +{ + size_t indx; + struct mgmt_get_data_req *getdata_req; + struct mgmt_txn_ctx *txn; + + assert((*data_batch)->txn_req && (*data_batch)->txn_req->txn + && (*data_batch)->txn_req->txn->type == MGMTD_TXN_TYPE_SHOW); + + txn = (*data_batch)->txn_req->txn; + + MGMTD_TXN_DBG(" Batch: %p (id: %" PRIu64 "), Trxn: %p (id: %" PRIu64 "), Client: %s (Id: %u)", + *data_batch, (*data_batch)->batch_id, txn, txn->txn_id, + (*data_batch)->be_adapter ? + (*data_batch)->be_adapter->name : "NULL", + (*data_batch)->be_id); + + MGMTD_TXN_DBG(" Batch: %p, Trxn: %p", *data_batch, (*data_batch)->txn_req->txn); + + getdata_req = (*data_batch)->txn_req->req.get_data; + if (remove_from_hash) + hash_release(getdata_req->batches, *data_batch); + mgmt_txn_data_batches_del( + &getdata_req->data_batches[(*data_batch)->be_id], *data_batch); + + if ((*data_batch)->be_adapter) + MGMTD_BE_ADAPTER_UNLOCK(&(*data_batch)->be_adapter); + + for (indx = 0; indx < (*data_batch)->num_get_data; indx++) { + if ((*data_batch)->data[indx].xpath) { + free((*data_batch)->data[indx].xpath); + (*data_batch)->data[indx].xpath = NULL; + } + } + + MGMTD_TXN_UNLOCK(&txn); + + XFREE(MTYPE_MGMTD_TXN_DATA_BATCH, *data_batch); + *data_batch = NULL; +} + +/* + * Get transaction batch hash key. + */ +static unsigned int mgmt_txn_databatch_hash_key(const void *data) +{ + const struct mgmt_txn_be_data_batch *batch = data; + + return jhash2((uint32_t *) &batch->batch_id, + sizeof(batch->batch_id) / sizeof(uint32_t), 0); +} + +/* + * Check if two transaction batch has same hash. + */ +static bool mgmt_txn_databatch_hash_cmp(const void *d1, const void *d2) +{ + const struct mgmt_txn_be_data_batch *batch1 = d1; + const struct mgmt_txn_be_data_batch *batch2 = d2; + + return (batch1->batch_id == batch2->batch_id); +} + +/* + * Free batch hash. + */ +static void mgmt_txn_databatch_hash_free(void *data) +{ + struct mgmt_txn_be_data_batch *batch = data; + + mgmt_txn_data_batch_free(&batch, false); +} + +/* + * Get transaction batch from batch_id. + */ +static inline struct mgmt_txn_be_data_batch * +mgmt_txn_databatch_id2ctx(struct mgmt_txn_ctx *txn, uint64_t batch_id, + bool search_pending) +{ + struct mgmt_txn_req *txn_req; + struct mgmt_txn_be_data_batch key = {0}; + struct mgmt_txn_be_data_batch *batch = NULL; + + FOREACH_TXN_REQ_IN_LIST (search_pending ? &txn->pending_get_datas : + &txn->get_data_reqs, txn_req) { + if (!txn_req->req.get_data) + continue; + key.batch_id = batch_id; + batch = hash_lookup(txn_req->req.get_data->batches, + &key); + if (batch) + break; + } + + return batch; +} + +/* + * Free all the batches for transaction request. + */ +static void mgmt_txn_cleanup_be_data_batches(struct mgmt_txn_req *txn_req, + enum mgmt_be_client_id id) +{ + struct mgmt_txn_be_data_batch *data_batch; + struct mgmt_txn_data_batches_head *list; + + list = &txn_req->req.get_data->data_batches[id]; + FOREACH_TXN_DATA_BATCH_IN_LIST (list, data_batch) + mgmt_txn_data_batch_free(&data_batch, true); + + mgmt_txn_data_batches_fini(list); + + txn_req->req.get_data->last_data_batch[id] = NULL; +} + static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, uint64_t req_id, enum mgmt_txn_event req_event) @@ -460,12 +681,23 @@ static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REQ, sizeof(struct mgmt_get_data_req)); assert(txn_req->req.get_data); + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + mgmt_txn_data_batches_init( + &txn_req->req.get_data->data_batches[id]); + } + + txn_req->req.get_data->batches = + hash_create(mgmt_txn_databatch_hash_key, + mgmt_txn_databatch_hash_cmp, + "MGMT Data Batches"); mgmt_txn_reqs_add_tail(&txn->get_data_reqs, txn_req); MGMTD_TXN_DBG("Added a new GETDATA req-id: %" PRIu64 " txn-id: %" PRIu64 " session-id: %" PRIu64, txn_req->req_id, txn->txn_id, txn->session_id); break; case MGMTD_TXN_COMMITCFG_TIMEOUT: + case MGMTD_TXN_GETDATA_TIMEOUT: case MGMTD_TXN_CLEANUP: break; } @@ -483,6 +715,7 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) enum mgmt_be_client_id id; struct mgmt_be_client_adapter *adapter; struct mgmt_commit_cfg_req *ccreq; + struct mgmt_get_data_req *gdreq; bool cleanup; switch ((*txn_req)->req_event) { @@ -547,49 +780,70 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) */ adapter = mgmt_be_get_adapter_by_id(id); if (adapter && cleanup && - ccreq->subscr_info.xpath_subscr[id]) + ccreq->subscr_info.xpath_subscr[id].subscribed & + (MGMT_SUBSCR_VALIDATE_CFG | + MGMT_SUBSCR_NOTIFY_CFG)) mgmt_txn_send_be_txn_delete((*txn_req)->txn, - adapter); + *txn_req, adapter); } break; case MGMTD_TXN_PROC_GETCFG: - for (indx = 0; indx < (*txn_req)->req.get_data->num_xpaths; - indx++) { - if ((*txn_req)->req.get_data->xpaths[indx]) - free((void *)(*txn_req) - ->req.get_data->xpaths[indx]); + gdreq = (*txn_req)->req.get_data; + for (indx = 0; indx < gdreq->num_xpaths; indx++) { + if (gdreq->xpaths[indx]) + free((void *)gdreq->xpaths[indx]); } req_list = &(*txn_req)->txn->get_cfg_reqs; MGMTD_TXN_DBG("Deleting GETCFG req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, (*txn_req)->txn->txn_id); - if ((*txn_req)->req.get_data->reply) - XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY, - (*txn_req)->req.get_data->reply); + if (gdreq->reply) + XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY, gdreq->reply); - if ((*txn_req)->req.get_data->cfg_root) - nb_config_free((*txn_req)->req.get_data->cfg_root); + if (gdreq->cfg_root) + nb_config_free(gdreq->cfg_root); XFREE(MTYPE_MGMTD_TXN_GETDATA_REQ, (*txn_req)->req.get_data); break; case MGMTD_TXN_PROC_GETDATA: - for (indx = 0; indx < (*txn_req)->req.get_data->num_xpaths; - indx++) { - if ((*txn_req)->req.get_data->xpaths[indx]) - free((void *)(*txn_req) - ->req.get_data->xpaths[indx]); + gdreq = (*txn_req)->req.get_data; + for (indx = 0; indx < gdreq->num_xpaths; indx++) { + if (gdreq->xpaths[indx]) + free((void *)gdreq->xpaths[indx]); } pending_list = &(*txn_req)->txn->pending_get_datas; req_list = &(*txn_req)->txn->get_data_reqs; MGMTD_TXN_DBG("Deleting GETDATA req-id: %" PRIu64 " txn-id: %" PRIu64, (*txn_req)->req_id, (*txn_req)->txn->txn_id); - if ((*txn_req)->req.get_data->reply) - XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY, - (*txn_req)->req.get_data->reply); + FOREACH_MGMTD_BE_CLIENT_ID (id) { + /* + * Send TXN_DELETE to cleanup state for this + * transaction on backend + */ + if (gdreq->subscr_info.xpath_subscr[id].subscribed) { + adapter = mgmt_be_get_adapter_by_id(id); + if (adapter) + mgmt_txn_send_be_txn_delete( + (*txn_req)->txn, *txn_req, + adapter); + } + + mgmt_txn_cleanup_be_data_batches((*txn_req), id); + if (gdreq->batches) { + hash_clean(gdreq->batches, + mgmt_txn_databatch_hash_free); + hash_free(gdreq->batches); + gdreq->batches = NULL; + } + } + if (gdreq->reply) + XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY, gdreq->reply); + XFREE(MTYPE_MGMTD_TXN_GETDATA_REQ, (*txn_req)->req.get_data); break; case MGMTD_TXN_COMMITCFG_TIMEOUT: + case MGMTD_TXN_GETDATA_TIMEOUT: case MGMTD_TXN_CLEANUP: break; } @@ -915,10 +1169,10 @@ mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn, * Check if all clients has moved to next phase or not. */ FOREACH_MGMTD_BE_CLIENT_ID (id) { - if (cmtcfg_req->subscr_info.xpath_subscr[id] && + if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed && mgmt_txn_batches_count(&cmtcfg_req->curr_batches[id])) { /* - * There's atleast once client who hasn't moved to + * There's atleast one client who hasn't moved to * next phase. * * TODO: Need to re-think this design for the case @@ -999,13 +1253,14 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, struct mgmt_txn_be_cfg_batch *batch; struct mgmt_be_client_subscr_info subscr_info; char *xpath = NULL, *value = NULL; - char err_buf[1024]; + const char *xp_match = NULL; enum mgmt_be_client_id id; struct mgmt_be_client_adapter *adapter; struct mgmt_commit_cfg_req *cmtcfg_req; - bool found_validator; + bool found_validator, found_match; int num_chgs = 0; - int xpath_len, value_len; + int xpath_len, value_len, xp_match_len; + struct mgmt_xpath_entry *xp_map = NULL; cmtcfg_req = &txn_req->req.commit_cfg; @@ -1032,21 +1287,44 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, MGMTD_TXN_DBG("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); - mgmt_be_get_subscr_info_for_xpath(xpath, &subscr_info); + if (mgmt_be_get_subscr_info_for_xpath(xpath, &subscr_info, + true) != 0) { + MGMTD_TXN_DBG("ERROR: Failed to get subscriber for '%s'", + xpath); + free(xpath); + continue; + } xpath_len = strlen(xpath) + 1; value_len = strlen(value) + 1; found_validator = false; FOREACH_MGMTD_BE_CLIENT_ID (id) { - if (!(subscr_info.xpath_subscr[id] & - (MGMT_SUBSCR_VALIDATE_CFG | - MGMT_SUBSCR_NOTIFY_CFG))) + if (!(subscr_info.xpath_subscr[id].subscribed & + (MGMT_SUBSCR_VALIDATE_CFG + | MGMT_SUBSCR_NOTIFY_CFG))) { + MGMTD_TXN_DBG(" -- Client: %s (Id: %u) not subscribed. Skipping!", + mgmt_be_client_id2name(id), id); continue; + } adapter = mgmt_be_get_adapter_by_id(id); if (!adapter) continue; + found_match = false; + FOREACH_XPATH_IN_SUBSCR_INFO (&subscr_info, id, + xp_map) { + MGMTD_TXN_DBG(" -- %s", xp_map->xpath); + xp_match = xp_map->xpath; + xp_match_len = strlen(xp_match) + 1; + if (xp_match_len <= xpath_len) + found_match = true; + } + if (!found_match) { + MGMTD_TXN_DBG("Xpath: %s -- No match found!", xpath); + continue; + } + batch = cmtcfg_req->last_be_cfg_batch[id]; if (!batch || (batch->num_cfg_data == @@ -1058,9 +1336,8 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, } batch->buf_space_left -= (xpath_len + value_len); - memcpy(&batch->xp_subscr[batch->num_cfg_data], - &subscr_info.xpath_subscr[id], - sizeof(batch->xp_subscr[0])); + batch->xp_subscr[batch->num_cfg_data] = + subscr_info.xpath_subscr[id].subscribed; mgmt_yang_cfg_data_req_init( &batch->cfg_data[batch->num_cfg_data]); @@ -1089,28 +1366,32 @@ static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, value; value = NULL; - if (subscr_info.xpath_subscr[id] & + if (subscr_info.xpath_subscr[id].subscribed & MGMT_SUBSCR_VALIDATE_CFG) found_validator = true; - cmtcfg_req->subscr_info.xpath_subscr[id] |= - subscr_info.xpath_subscr[id]; - MGMTD_TXN_DBG(" -- %s, batch-id: %" PRIu64 " item:%d", - adapter->name, batch->batch_id, - (int)batch->num_cfg_data); + cmtcfg_req->subscr_info.xpath_subscr[id].subscribed |= + subscr_info.xpath_subscr[id].subscribed; + MGMTD_TXN_DBG( + " -- %s, {V:%d, N:%d}, Batch: %p, Items:%d", + adapter->name, + subscr_info.xpath_subscr[id].subscribed & + MGMT_SUBSCR_VALIDATE_CFG ? 1 : 0, + subscr_info.xpath_subscr[id].subscribed & + MGMT_SUBSCR_NOTIFY_CFG ? 1 : 0, + batch, (int)batch->num_cfg_data); batch->num_cfg_data++; num_chgs++; } if (!found_validator) { - snprintf(err_buf, sizeof(err_buf), - "No validator module found for XPATH: '%s", - xpath); - MGMTD_TXN_ERR("***** %s", err_buf); + MGMTD_TXN_DBG("No validator module found for XPATH: '%s'", + xpath); } free(xpath); + mgmt_be_cleanup_xpath_subscr_info(&subscr_info); } cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs; @@ -1318,7 +1599,7 @@ static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn) cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; FOREACH_MGMTD_BE_CLIENT_ID (id) { - if (cmtcfg_req->subscr_info.xpath_subscr[id]) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed) { adapter = mgmt_be_get_adapter_by_id(id); if (mgmt_be_send_txn_req(adapter, txn->txn_id, true)) { (void)mgmt_txn_send_commit_cfg_reply( @@ -1364,7 +1645,8 @@ static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn, assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; - assert(cmtcfg_req->subscr_info.xpath_subscr[adapter->id]); + assert((cmtcfg_req->subscr_info.xpath_subscr[adapter->id].subscribed & + (MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG))); indx = 0; num_batches = @@ -1409,18 +1691,36 @@ static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn, } static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, + struct mgmt_txn_req *txn_req, struct mgmt_be_client_adapter *adapter) { - struct mgmt_commit_cfg_req *cmtcfg_req = - &txn->commit_cfg_req->req.commit_cfg; - - assert(txn->type == MGMTD_TXN_TYPE_CONFIG); - assert(!mgmt_txn_batches_count(&cmtcfg_req->curr_batches[adapter->id])); + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_get_data_req *getdata_req; + int ret = 0; - if (!cmtcfg_req->subscr_info.xpath_subscr[adapter->id]) - return 0; + assert(txn->type == MGMTD_TXN_TYPE_SHOW + || (txn->type == MGMTD_TXN_TYPE_CONFIG + && txn->commit_cfg_req)); + + if (txn->type == MGMTD_TXN_TYPE_CONFIG) { + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + if (adapter && + cmtcfg_req->subscr_info.xpath_subscr[adapter->id] + .subscribed) { + (void)mgmt_be_send_txn_req(adapter, txn->txn_id, + false); + } + } else if (txn->type == MGMTD_TXN_TYPE_SHOW) { + getdata_req = txn_req->req.get_data; + if (adapter && + getdata_req->subscr_info.xpath_subscr[adapter->id] + .subscribed) { + (void)mgmt_be_send_txn_req(adapter, txn->txn_id, + false); + } + } - return mgmt_be_send_txn_req(adapter, txn->txn_id, false); + return ret; } static void mgmt_txn_cfg_commit_timedout(struct event *thread) @@ -1476,13 +1776,14 @@ static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn) } FOREACH_MGMTD_BE_CLIENT_ID (id) { - if (cmtcfg_req->subscr_info.xpath_subscr[id] & - MGMT_SUBSCR_NOTIFY_CFG) { + if (cmtcfg_req->subscr_info.xpath_subscr[id].subscribed & + (MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG)) { adapter = mgmt_be_get_adapter_by_id(id); if (!adapter) return -1; batch_list = &cmtcfg_req->curr_batches[id]; + if (mgmt_be_send_cfgapply_req(adapter, txn->txn_id)) { (void)mgmt_txn_send_commit_cfg_reply( txn, MGMTD_INTERNAL_ERROR, @@ -1599,8 +1900,11 @@ static void mgmt_init_get_data_reply(struct mgmt_get_data_reply *get_reply) { size_t indx; + mgmt_yang_data_reply_init(&get_reply->data_reply); for (indx = 0; indx < array_size(get_reply->reply_data); indx++) get_reply->reply_datap[indx] = &get_reply->reply_data[indx]; + get_reply->data_reply.data = get_reply->reply_datap; + get_reply->data_reply.n_data = 0; } static void mgmt_reset_get_data_reply(struct mgmt_get_data_reply *get_reply) @@ -1613,8 +1917,8 @@ static void mgmt_reset_get_data_reply(struct mgmt_get_data_reply *get_reply) get_reply->reply_xpathp[indx] = 0; } if (get_reply->reply_data[indx].xpath) { - zlog_debug("%s free xpath %p", __func__, - get_reply->reply_data[indx].xpath); + MGMTD_TXN_DBG("%s free xpath %p", __func__, + get_reply->reply_data[indx].xpath); free(get_reply->reply_data[indx].xpath); get_reply->reply_data[indx].xpath = 0; } @@ -1653,11 +1957,10 @@ static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, data_reply->next_indx = (!get_reply->last_batch ? get_req->total_reply : -1); - MGMTD_TXN_DBG("Sending %zu Get-Config/Data replies next-index:%" PRId64, - data_reply->n_data, data_reply->next_indx); - switch (txn_req->req_event) { case MGMTD_TXN_PROC_GETCFG: + MGMTD_TXN_DBG("Sending %zu Get-Config replies next-index:%" PRId64, + data_reply->n_data, data_reply->next_indx); if (mgmt_fe_send_get_reply(txn_req->txn->session_id, txn_req->txn->txn_id, get_req->ds_id, txn_req->req_id, MGMTD_SUCCESS, @@ -1670,6 +1973,8 @@ static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, } break; case MGMTD_TXN_PROC_GETDATA: + MGMTD_TXN_DBG("Sending %zu Get-Data replies next-index:%" PRId64, + data_reply->n_data, data_reply->next_indx); if (mgmt_fe_send_get_reply(txn_req->txn->session_id, txn_req->txn->txn_id, get_req->ds_id, txn_req->req_id, MGMTD_SUCCESS, @@ -1684,6 +1989,7 @@ static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, case MGMTD_TXN_PROC_SETCFG: case MGMTD_TXN_PROC_COMMITCFG: case MGMTD_TXN_COMMITCFG_TIMEOUT: + case MGMTD_TXN_GETDATA_TIMEOUT: case MGMTD_TXN_CLEANUP: MGMTD_TXN_ERR("Invalid Txn-Req-Event %u", txn_req->req_event); break; @@ -1860,11 +2166,366 @@ static void mgmt_txn_process_get_cfg(struct event *thread) } } +/* + * Send get data reply to front end. + */ +static int mgmt_txn_send_get_data_reply(struct mgmt_txn_req *txn_req, + enum mgmt_result result, + const char *error_if_any) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_get_data_req *data_req; + + if (!txn_req || !txn_req->txn || !txn_req->req.get_data) { + MGMTD_TXN_ERR("Invalid txx-req: %p, txn-id: %" PRIu64, + txn_req, txn_req->txn ? txn_req->txn->txn_id : 0); + return -1; + } + + txn = txn_req->txn; + data_req = txn_req->req.get_data; + if (data_req->reply->last_batch) + data_req->reply->data_reply.next_indx = -1; + + if (mgmt_fe_send_get_reply(txn->session_id, txn->txn_id, + txn_req->req.get_data->ds_id, + txn_req->req_id, result, + result == MGMTD_SUCCESS ? + &data_req->reply->data_reply : NULL, + error_if_any) != 0) { + MGMTD_TXN_ERR("Failed to send GET-DATA-REPLY for Trxn %p, Sessn: 0x%" PRIx64 ", Req: %" PRIu64, + txn, txn->session_id, txn_req->req_id); + } else { + MGMTD_TXN_DBG("Sent GET-DATA-REPLY for Trxn %p, Sessn: %" PRIx64 ", Req: %" PRIu64, + txn, txn->session_id, txn_req->req_id); + } + + /* + * Reset reply buffer for next reply. + */ + mgmt_reset_get_data_reply_buf(data_req); + + /* + * If this is a intermediate successful reply no need to free up + * the transaction yet. + */ + if (result == MGMTD_SUCCESS && !data_req->reply->last_batch) + return 0; + + mgmt_txn_req_free(&txn_req); + + /* + * The SHOW Transaction should be destroyed from Frontend-adapter. + * But in case the transaction is not triggered from a front-end session + * we need to cleanup by itself. + */ + if (!txn->session_id + && mgmt_txn_reqs_count(&txn->get_data_reqs) + && mgmt_txn_reqs_count(&txn->pending_get_datas)) + mgmt_txn_register_event(txn, MGMTD_TXN_CLEANUP); + + return 0; +} + +/* + * Create get data request batches for particular + * transaction request. + */ +static int mgmt_txn_create_getdata_batches(struct mgmt_txn_req *txn_req, + struct mgmt_be_client_adapter *adapter, + struct mgmt_be_client_subscr_info *subscr_info, + int id) +{ + struct mgmt_txn_be_data_batch *getdata_batch; + struct mgmt_get_data_req *getdata_req; + struct mgmt_xpath_entry *xp_map = NULL; + const char *xpath = NULL; + int xpath_len; + int num_batches = 0; + + getdata_req = txn_req->req.get_data; + + FOREACH_XPATH_IN_SUBSCR_INFO (subscr_info, id, xp_map) { + MGMTD_TXN_DBG(" -- %s", xp_map->xpath); + xpath = xp_map->xpath; + xpath_len = strlen(xpath) + 1; + getdata_batch = getdata_req->last_data_batch[id]; + if (!getdata_batch + || (getdata_batch->num_get_data + == MGMTD_MAX_NUM_DATA_REQ_IN_BATCH) + || (getdata_batch->buf_space_left + < (xpath_len))) { + /* Allocate a new data batch */ + getdata_batch = mgmt_txn_data_batch_alloc( + txn_req, id, adapter); + MGMTD_TXN_DBG("Added new batch %p for client '%s'", + getdata_batch, + mgmt_be_client_id2name(id)); + } + + getdata_batch->buf_space_left -= (xpath_len); + getdata_batch->xp_subscr[getdata_batch->num_get_data] = + subscr_info->xpath_subscr[id].subscribed; + + mgmt_yang_get_data_req_init( + &getdata_batch->get_data[getdata_batch->num_get_data]); + getdata_batch->get_datap[getdata_batch->num_get_data] = + &getdata_batch->get_data[getdata_batch->num_get_data]; + + mgmt_yang_data_init( + &getdata_batch->data[getdata_batch->num_get_data]); + getdata_batch->get_data[getdata_batch->num_get_data].data = + &getdata_batch->data[getdata_batch->num_get_data]; + getdata_batch->data[getdata_batch->num_get_data].xpath = + strdup(xpath); + xpath = NULL; + + getdata_req->subscr_info.xpath_subscr[id].subscribed |= + subscr_info->xpath_subscr[id].subscribed; + MGMTD_TXN_DBG(" Add %s to batch: %p (total-xpaths: %d)", + getdata_batch->data[getdata_batch->num_get_data] + .xpath, getdata_batch, + (int)getdata_batch->num_get_data + 1); + MGMTD_TXN_DBG(" -- %s, {V:%d, N:%d, O:%d}, Batch: %p, Item:%d", + adapter->name, + subscr_info->xpath_subscr[id].subscribed & + MGMT_SUBSCR_VALIDATE_CFG ? 1 : 0, + subscr_info->xpath_subscr[id].subscribed & + MGMT_SUBSCR_NOTIFY_CFG ? 1 : 0, + subscr_info->xpath_subscr[id].subscribed & + MGMT_SUBSCR_OPER_OWN ? 1 : 0, + getdata_batch, (int)getdata_batch->num_get_data); + getdata_batch->num_get_data++; + num_batches++; + } + getdata_req->last_batch_cnt = num_batches; + + return 0; +} + +/* + * Abort get data request and return error. + */ +static void mgmt_txn_abort_get_data(struct mgmt_txn_ctx *txn, + const char *err_if_any) +{ + struct mgmt_txn_req *txn_req; + + MGMTD_TXN_ERR( + "Aborting GETDATA process for SHOW Trxn %p!!! Reason: %s", + txn, err_if_any ? err_if_any : "Unknown failure"); + + EVENT_OFF(txn->get_data_timeout); + + /* + * Send a GET_DATA_REPLY with failure with each of pending get data reqs. + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + FOREACH_TXN_REQ_IN_LIST (&txn->get_data_reqs, txn_req) { + mgmt_txn_send_get_data_reply( + txn_req, MGMTD_INTERNAL_ERROR, + err_if_any ? err_if_any : "Unknown failure"); + } + + FOREACH_TXN_REQ_IN_LIST (&txn->pending_get_datas, txn_req) { + mgmt_txn_send_get_data_reply( + txn_req, MGMTD_INTERNAL_ERROR, + err_if_any ? err_if_any : "Unknown failure"); + } +} + +/* + * Get data request timed out at the backend client. + */ +static void mgmt_txn_get_data_timedout(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + + txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); + assert(txn && txn->type == MGMTD_TXN_TYPE_SHOW); + + mgmt_txn_abort_get_data( + txn, "Operation on the backend timed-out!"); +} + +/* + * Process the get data request from the front end, create batchs + * and send get data request to backend. + */ +static int mgmt_txn_get_data(struct mgmt_txn_ctx *txn, + struct mgmt_txn_req *txn_req) +{ + struct mgmt_txn_reqs_head *req_list = NULL; + struct mgmt_txn_reqs_head *pending_list = NULL; + int indx, num_reqs, num_batches; + struct mgmt_get_data_req *get_data; + struct mgmt_be_datareq data_req = {0}; + struct mgmt_txn_be_data_batch *data_batch; + struct mgmt_be_client_subscr_info subscr; + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + + switch (txn_req->req_event) { + case MGMTD_TXN_PROC_GETDATA: + req_list = &txn->get_data_reqs; + pending_list = &txn->pending_get_datas; + break; + case MGMTD_TXN_PROC_GETCFG: + case MGMTD_TXN_PROC_SETCFG: + case MGMTD_TXN_PROC_COMMITCFG: + case MGMTD_TXN_COMMITCFG_TIMEOUT: + case MGMTD_TXN_GETDATA_TIMEOUT: + case MGMTD_TXN_CLEANUP: + assert(!"Wrong txn request type!"); + break; + } + + get_data = txn_req->req.get_data; + + if (!get_data->reply) { + get_data->reply = XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REPLY, + sizeof(struct mgmt_get_data_reply)); + if (!get_data->reply) { + mgmt_fe_send_get_reply( + txn->session_id, txn->txn_id, + get_data->ds_id, txn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "Internal error: Unable to allocate reply buffers!"); + goto mgmt_txn_get_data_failed; + } + } + + num_reqs = 0; + for (indx = 0; indx < get_data->num_xpaths; indx++) { + MGMTD_TXN_DBG("Trying to get all data under '%s'", + get_data->xpaths[indx]); + if (mgmt_be_get_subscr_info_for_xpath(get_data->xpaths[indx], + &subscr, false) != 0) { + MGMTD_TXN_DBG("ERROR: Failed to get subscriber for '%s'", + get_data->xpaths[indx]); + goto mgmt_txn_get_data_failed; + } + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (subscr.xpath_subscr[id].subscribed & + MGMT_SUBSCR_OPER_OWN) { + MGMTD_TXN_DBG(" -- Client: '%s'", + mgmt_be_client_id2name(id)); + adapter = mgmt_be_get_adapter_by_id(id); + if (!adapter) + MGMTD_TXN_DBG(" -- Adapter: Not created Skipping batch creation!"); + MGMTD_TXN_DBG(" -- Adapter: %p", adapter); + /* + * Creates batches from xpath + */ + if (mgmt_txn_create_getdata_batches( + txn_req, adapter, &subscr, id) != 0) { + mgmt_fe_send_get_reply( + txn->session_id, txn->txn_id, + get_data->ds_id, txn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "Failed to create get data batches!"); + goto mgmt_txn_get_data_failed; + } + } + num_batches = mgmt_txn_data_batches_count( + &txn_req->req.get_data->data_batches[id]); + num_reqs += num_batches; + MGMTD_TXN_DBG("Added %d batches for client '%s'", + num_batches, mgmt_be_client_id2name(id)); + } + + mgmt_be_cleanup_xpath_subscr_info(&subscr); + } + + /* + * If no GET-DATA requests has been generated towards backend, it means + * there's nothing return wrt the specific GET-DATA request. Return + * success without any data. + */ + if (!num_reqs) { + Mgmtd__YangDataReply dummy = { 0 }; + + mgmt_yang_data_reply_init(&dummy); + dummy.next_indx = -1; + mgmt_fe_send_get_reply(txn->session_id, txn->txn_id, + get_data->ds_id, txn_req->req_id, + MGMTD_SUCCESS, &dummy, NULL); + goto mgmt_txn_get_data_failed; + } + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (!(subscr.xpath_subscr[id].subscribed + & MGMT_SUBSCR_OPER_OWN)) + continue; + + adapter = mgmt_be_get_adapter_by_id(id); + if (!adapter) + continue; + + if (!txn->be_txn_create_sent[id]) { + if (mgmt_be_send_txn_req(adapter, txn->txn_id, true) + != 0) { + mgmt_fe_send_get_reply( + txn->session_id, txn->txn_id, + get_data->ds_id, txn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "Internal error: Unable to create transaction!"); + return -1; + } + + txn->be_txn_create_sent[id] = true; + + /* + * Start the GETDATA Timeout Timer to abort Trxn + * if things get stuck at backend. + */ + mgmt_txn_register_event(txn, + MGMTD_TXN_GETDATA_TIMEOUT); + } else { + FOREACH_TXN_DATA_BATCH_IN_LIST ( + &txn_req->req.get_data + ->data_batches[adapter->id], + data_batch) { + MGMTD_TXN_DBG("Sending xpath:%s batch: %" PRId64 " txn-id: %" PRId64 "", + data_batch->get_data->data->xpath, + get_data->last_data_batch[adapter->id] + ->batch_id, txn->txn_id); + data_req.getdata_reqs = data_batch->get_datap; + data_req.num_reqs = data_batch->num_get_data; + mgmt_be_send_get_data_req(adapter, txn->txn_id, + get_data->last_data_batch[adapter->id] + ->batch_id, &data_req); + } + } + } + +mgmt_txn_get_data_failed: + + if (pending_list) { + /* + * Move the transaction to corresponding pending list. + */ + if (req_list) + mgmt_txn_reqs_del(req_list, txn_req); + txn_req->pending_be_proc = true; + mgmt_txn_reqs_add_tail(pending_list, txn_req); + MGMTD_TXN_DBG( + "Moved Req: %p for Trxn: %p from Req-List to Pending-List", + txn_req, txn_req->txn); + } + + return 0; +} + static void mgmt_txn_process_get_data(struct event *thread) { struct mgmt_txn_ctx *txn; struct mgmt_txn_req *txn_req; + struct mgmt_ds_ctx *ds_ctx; int num_processed = 0; + bool error; txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); assert(txn); @@ -1875,22 +2536,53 @@ static void mgmt_txn_process_get_data(struct event *thread) txn->session_id); FOREACH_TXN_REQ_IN_LIST (&txn->get_data_reqs, txn_req) { + error = false; assert(txn_req->req_event == MGMTD_TXN_PROC_GETDATA); + ds_ctx = mgmt_ds_get_ctx_by_id(mgmt_txn_mm, + txn_req->req.get_data->ds_id); + if (!ds_ctx) { + mgmt_fe_send_get_reply( + txn->session_id, txn->txn_id, + txn_req->req.get_data->ds_id, txn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "No such database!"); + error = true; + goto mgmt_txn_process_get_data_done; + } - /* - * TODO: Trigger GET procedures for Backend - * For now return back error. - */ - mgmt_fe_send_get_reply(txn->session_id, txn->txn_id, - txn_req->req.get_data->ds_id, - txn_req->req_id, MGMTD_INTERNAL_ERROR, - NULL, "GET-DATA is not supported yet!"); - /* - * Delete the txn request. - * Note: The following will remove it from the list - * as well. - */ - mgmt_txn_req_free(&txn_req); + if (mgmt_ds_is_config(ds_ctx)) { + if (mgmt_txn_get_config(txn, txn_req, + txn_req->req.get_data->cfg_root) + != 0) { + MGMTD_TXN_ERR( + "Unable to retrieve Config from DB %d for Txn %p, Sessn: 0x%" PRIx64 ", Req: %" PRIu64 "!", + txn_req->req.get_data->ds_id, txn, + txn->session_id, txn_req->req_id); + error = true; + } + } else { + /* + * Trigger GET procedures for Backend + */ + if (mgmt_txn_get_data(txn, txn_req) != 0) { + MGMTD_TXN_ERR( + "Unable to retrieve data from backend for Trxn %p, Sessn: 0x%" PRIx64 ", Req: %" PRId64 "!", + txn, txn->session_id, + txn_req->req_id); + error = true; + } + } + +mgmt_txn_process_get_data_done: + + if (error) { + /* + * Delete the txn request. + * Note: The following will remove it from the list + * as well. + */ + mgmt_txn_req_free(&txn_req); + } /* * Else the transaction would have been already deleted or @@ -2055,6 +2747,7 @@ static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file, EVENT_OFF((*txn)->proc_get_data); EVENT_OFF((*txn)->proc_comm_cfg); EVENT_OFF((*txn)->comm_cfg_timeout); + EVENT_OFF((*txn)->get_data_timeout); hash_release(mgmt_txn_mm->txn_hash, *txn); mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn); @@ -2127,6 +2820,12 @@ static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, txn, MGMTD_TXN_CFG_COMMIT_MAX_DELAY_MSEC, &txn->comm_cfg_timeout); break; + case MGMTD_TXN_GETDATA_TIMEOUT: + event_add_timer_msec(mgmt_txn_tm, + mgmt_txn_get_data_timedout, txn, + MGMTD_TXN_GET_DATA_MAX_DELAY_MSEC, + &txn->get_data_timeout); + break; case MGMTD_TXN_CLEANUP: tv.tv_usec = MGMTD_TXN_CLEANUP_DELAY_USEC; event_add_timer_tv(mgmt_txn_tm, mgmt_txn_cleanup, txn, &tv, @@ -2359,8 +3058,6 @@ int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, * has failed. */ FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { - /* TODO: update with operational state when that is - * completed */ if (txn->type == MGMTD_TXN_TYPE_CONFIG) { cmtcfg_req = txn->commit_cfg_req ? &txn->commit_cfg_req->req @@ -2368,11 +3065,14 @@ int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, : NULL; if (cmtcfg_req && cmtcfg_req->subscr_info - .xpath_subscr[adapter->id]) { + .xpath_subscr[adapter->id].subscribed) { mgmt_txn_send_commit_cfg_reply( txn, MGMTD_INTERNAL_ERROR, "Backend daemon disconnected while processing commit!"); } + } else { + mgmt_txn_abort_get_data(txn, + "Backend daemon disconnected while processing GETDATA request!"); } } } @@ -2380,16 +3080,12 @@ int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, return 0; } -int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, - struct mgmt_be_client_adapter *adapter) +static int mgmt_txn_notify_cfg_txn_reply(struct mgmt_txn_ctx *txn, bool create, + bool success, + struct mgmt_be_client_adapter *adapter) { - struct mgmt_txn_ctx *txn; struct mgmt_commit_cfg_req *cmtcfg_req = NULL; - txn = mgmt_txn_id2ctx(txn_id); - if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG) - return -1; - if (!create && !txn->commit_cfg_req) return 0; @@ -2424,6 +3120,91 @@ int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, return 0; } +/* + * Notify backend transaction reply for show. + */ +static int mgmt_txn_notify_show_txn_reply(struct mgmt_txn_ctx *txn, bool create, + bool success, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_req *txn_req; + struct mgmt_get_data_req *getdata_req = NULL; + struct mgmt_be_datareq data_req = {0}; + struct mgmt_txn_be_data_batch *data_batch; + int num_reqs = (int) mgmt_txn_reqs_count(&txn->pending_get_datas); + + /* + * No need to process TXN-DELETE-REPLY for SHOW Transactions. + */ + if (!create) + return 0; + + if (success) { + MGMTD_TXN_DBG("Trying to send %d GET-DATA reqs to client: %s txn-id: %" PRId64 "", + num_reqs, mgmt_be_client_id2name(adapter->id), + txn->txn_id); + /* + * Send get data request to backend adapter + */ + FOREACH_TXN_REQ_IN_LIST (&txn->pending_get_datas, txn_req) { + getdata_req = txn_req->req.get_data; + num_reqs = mgmt_txn_data_batches_count( + &getdata_req->data_batches[adapter->id]); + MGMTD_TXN_DBG("Pending %d batches for txn-id: %" PRId64 "", + num_reqs, txn->txn_id); + num_reqs = 0; + FOREACH_TXN_DATA_BATCH_IN_LIST ( + &getdata_req->data_batches[adapter->id], + data_batch) { + data_req.getdata_reqs = data_batch->get_datap; + data_req.num_reqs = data_batch->num_get_data; + MGMTD_TXN_DBG("Sending %d xpaths (1st: %s) for batch: %" PRId64 " txn-id: %" PRId64 "", + (int) data_req.num_reqs, + data_req.getdata_reqs[0] + ->data->xpath, + data_batch->batch_id, + txn->txn_id); + mgmt_be_send_get_data_req(adapter, txn->txn_id, + data_batch->batch_id, + &data_req); + num_reqs++; + } + + MGMTD_TXN_DBG("Sent %d batches for txn-id: %" PRId64 "", + num_reqs, txn->txn_id); + } + } + + return 0; +} + +/* + * Reply to backend adapter about transaction create/delete. + */ +int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, + bool success, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_ctx *txn; + + txn = mgmt_txn_id2ctx(txn_id); + + MGMTD_TXN_DBG("Got TXN REPLY from client: %s txn-id: %" PRId64 " success: %d", mgmt_be_client_id2name(adapter->id), txn_id, success); + if (!txn || (txn->type != MGMTD_TXN_TYPE_CONFIG + && txn->type != MGMTD_TXN_TYPE_SHOW)) + return -1; + + switch (txn->type) { + case MGMTD_TXN_TYPE_CONFIG: + return mgmt_txn_notify_cfg_txn_reply(txn, create, success, adapter); + case MGMTD_TXN_TYPE_SHOW: + return mgmt_txn_notify_show_txn_reply(txn, create, success, adapter); + case MGMTD_TXN_TYPE_NONE: + return -1; + } + return 0; +} + int mgmt_txn_notify_be_cfgdata_reply(uint64_t txn_id, uint64_t batch_id, bool success, char *error_if_any, struct mgmt_be_client_adapter *adapter) @@ -2477,6 +3258,7 @@ int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, struct mgmt_be_client_adapter *adapter) { struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; struct mgmt_txn_be_cfg_batch *batch; struct mgmt_commit_cfg_req *cmtcfg_req = NULL; size_t indx; @@ -2485,6 +3267,7 @@ int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req) return -1; + txn_req = txn->commit_cfg_req; cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; if (!success) { @@ -2518,7 +3301,7 @@ int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, * Send TXN-DELETE to wrap up the transaction for this backend. */ SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); - mgmt_txn_send_be_txn_delete(txn, adapter); + mgmt_txn_send_be_txn_delete(txn, txn_req, adapter); } mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); @@ -2528,6 +3311,103 @@ int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, return 0; } +/* + * Notify txn with get data request reply. + */ +int mgmt_txn_notify_be_getdata_req_reply(uint64_t txn_id, bool success, + uint64_t batch_id, + Mgmtd__YangDataReply *data_reply, + char *error_if_any, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct mgmt_txn_be_data_batch *batch; + struct mgmt_get_data_req *get_req = NULL; + struct mgmt_get_data_reply *get_reply; + Mgmtd__YangData *data; + Mgmtd__YangDataValue *data_value; + size_t indx; + enum mgmt_be_client_id id; + bool last_batch = false; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn || txn->type != MGMTD_TXN_TYPE_SHOW) { + MGMTD_TXN_ERR("Could not find appropriate trxn (%p) for id: %" PRIu64, + txn, txn_id); + return -1; + } + + batch = mgmt_txn_databatch_id2ctx(txn, batch_id, true); + if (!batch) { + MGMTD_TXN_ERR("Could not find appropriate batch for id: %" PRIu64, + batch_id); + return -1; + } + + txn_req = batch->txn_req; + + if (!success) { + MGMTD_TXN_ERR( + "GETDATA_REQ sent to '%s' failed for Trxn %p, Batch [0x%" PRIx64 "], Err: %s", + adapter->name, txn, batch_id, + error_if_any ? error_if_any : "None"); + mgmt_txn_send_get_data_reply(txn_req, MGMTD_INTERNAL_ERROR, + error_if_any ? error_if_any : + "Internal error! Failed to process GET_DATA request on backend!"); + return 0; + } + + MGMTD_TXN_DBG("Got GET_DATA_REPLY mesaage from backend for batch %p, #xpaths: %d", + batch, (int)data_reply->n_data); + get_req = txn_req->req.get_data; + get_reply = get_req->reply; + mgmt_init_get_data_reply(get_reply); + get_reply->num_reply = data_reply->n_data; + if (data_reply->next_indx == -1) { + last_batch = true; + mgmt_txn_data_batch_free(&batch, true); + + /* + * Find out if there are anymore batches left or not. + */ + FOREACH_MGMTD_BE_CLIENT_ID (id) { + MGMTD_TXN_DBG("Batch list size of client %d is %zu", + id, mgmt_txn_data_batches_count( + &get_req->data_batches[id])); + if (mgmt_txn_data_batches_count( + &get_req->data_batches[id])) { + last_batch = false; + break; + } + } + } + + get_reply->last_batch = last_batch; + + for (indx = 0; indx < data_reply->n_data; indx++) { + data = &get_reply->reply_data[indx]; + data_value = &get_reply->reply_value[indx]; + mgmt_yang_data_init(data); + data->xpath = strndup(data_reply->data[indx]->xpath, + MGMTD_MAX_XPATH_LEN); + mgmt_yang_data_value_init(data_value); + data_value->value_case = + data_reply->data[indx]->value->value_case; + data_value->encoded_str_val = + data_reply->data[indx]->value->encoded_str_val; + data->value = data_value; + MGMTD_TXN_DBG("[%d] - Got Xpath: %s", (int)indx, + get_reply->reply_data[indx].xpath); + get_reply->data_reply.n_data++; + } + + if (mgmt_txn_send_get_data_reply(txn_req, MGMTD_SUCCESS, NULL) != 0) + MGMTD_TXN_ERR("Failed to send GET_DATA_REPLY to frontend!"); + + return 0; +} + int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId ds_id, struct nb_config *cfg_root, Mgmtd__YangGetDataReq **data_req, size_t num_reqs) diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index 068f07a5ca8a..8801a0721535 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -22,6 +22,7 @@ #define MGMTD_TXN_SEND_CFGVALIDATE_DELAY_MSEC 100 #define MGMTD_TXN_SEND_CFGAPPLY_DELAY_MSEC 100 #define MGMTD_TXN_CFG_COMMIT_MAX_DELAY_MSEC 30000 /* 30 seconds */ +#define MGMTD_TXN_GET_DATA_MAX_DELAY_MSEC 30000 /* 30 seconds */ #define MGMTD_TXN_CLEANUP_DELAY_MSEC 100 #define MGMTD_TXN_CLEANUP_DELAY_USEC 10 @@ -200,7 +201,7 @@ mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, */ extern int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, - struct mgmt_be_client_adapter *adapter); + struct mgmt_be_client_adapter *adapter); /* * Reply to backend adapater with config data create request. @@ -211,7 +212,7 @@ mgmt_txn_notify_be_cfgdata_reply(uint64_t txn_id, uint64_t batch_id, struct mgmt_be_client_adapter *adapter); /* - * Reply to backend adapater with config data validate request. + * Reply to backend adapter with config data validate request. */ extern int mgmt_txn_notify_be_cfg_validate_reply( uint64_t txn_id, bool success, uint64_t batch_ids[], @@ -219,7 +220,7 @@ extern int mgmt_txn_notify_be_cfg_validate_reply( struct mgmt_be_client_adapter *adapter); /* - * Reply to backend adapater with config data apply request. + * Reply to backend adapter with config data apply request. */ extern int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, @@ -227,6 +228,34 @@ mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, size_t num_batch_ids, char *error_if_any, struct mgmt_be_client_adapter *adapter); +/* + * Notify txn with get data request reply. + * + * txn_id + * Unique transaction identifier. + * + * success + * return status. + * + * batch_id + * Batch id. + * + * data + * YANG data reply. + * + * error_if_any + * error string if any. + * + * adptr + * backend adapter + */ +extern int +mgmt_txn_notify_be_getdata_req_reply(uint64_t txn_id, bool success, + uint64_t batch_id, + Mgmtd__YangDataReply *data, + char *error_if_any, + struct mgmt_be_client_adapter *adptr); + /* * Dump transaction status to vty. */ From 2bfdc76006cd483a87158784091c2d984c241813 Mon Sep 17 00:00:00 2001 From: Pushpasis Sarkar Date: Mon, 11 Sep 2023 23:18:31 -0700 Subject: [PATCH 3/5] lib,mgmtd,zebra: Add Zebra as a MGMT Bckend client Add Zebra as a MGMT Backend client. This commit does not divert show commands to MGMTd yet. Co-authored-by: Yash Ranjan Signed-off-by: Pushpasis Sarkar --- lib/mgmt_be_client.c | 1 + lib/mgmt_be_client.h | 1 + mgmtd/mgmt_be_adapter.c | 20 ++++++++++++++++++++ mgmtd/mgmt_main.c | 6 ++++++ zebra/main.c | 10 ++++++++++ zebra/zebra_nb_state.c | 4 ++++ zebra/zebra_vty.c | 3 +++ 7 files changed, 45 insertions(+) diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 965b5f9dd2d0..72c348b3fed2 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -170,6 +170,7 @@ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { #ifdef HAVE_STATICD [MGMTD_BE_CLIENT_ID_STATICD] = "staticd", #endif + [MGMTD_BE_CLIENT_ID_ZEBRA] = "zebra", [MGMTD_BE_CLIENT_ID_MAX] = "Unknown/Invalid", }; diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h index 4ad5ca5957cd..645bac3b55bf 100644 --- a/lib/mgmt_be_client.h +++ b/lib/mgmt_be_client.h @@ -30,6 +30,7 @@ enum mgmt_be_client_id { #ifdef HAVE_STATICD MGMTD_BE_CLIENT_ID_STATICD, #endif + MGMTD_BE_CLIENT_ID_ZEBRA, MGMTD_BE_CLIENT_ID_MAX }; diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index a8ba837bd053..d4c809de24d0 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -79,6 +79,24 @@ static struct mgmt_be_client_xpath staticd_xpaths[] = { }, }; #endif +static struct mgmt_be_client_xpath zebra_xpaths[] = { + /* + * For now we will add only support for retrieving Oerational + * data. In future we need to enable support for Config data + * it can be done by adding extra flags like below. + * + * .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_NOTIFY_CFG + * | MGMT_SUBSCR_OPER_OWN, + */ + { + .xpath = "/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_OPER_OWN, + }, + { + .xpath = "/frr-vrf:lib/vrf[name='*']/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_OPER_OWN, + }, +}; static struct mgmt_be_client_xpath_map mgmt_client_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { @@ -86,6 +104,8 @@ static struct mgmt_be_client_xpath_map [MGMTD_BE_CLIENT_ID_STATICD] = {staticd_xpaths, array_size(staticd_xpaths)}, #endif + [MGMTD_BE_CLIENT_ID_ZEBRA] = {zebra_xpaths, + array_size(zebra_xpaths)}, }; /* diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 39362fa74a83..1406a2cfe6eb 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -207,6 +207,12 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = { &(struct frr_yang_module_info){.name = "frr-staticd", .ignore_cbs = true}, #endif + &(struct frr_yang_module_info){.name = "frr-zebra", + .ignore_cbs = true}, + &(struct frr_yang_module_info){.name = "frr-zebra-route-map", + .ignore_cbs = true}, + &(struct frr_yang_module_info){.name = "frr-affinity-map", + .ignore_cbs = true}, }; FRR_DAEMON_INFO(mgmtd, MGMTD, .vty_port = MGMTD_VTY_PORT, diff --git a/zebra/main.c b/zebra/main.c index 1e833ce7f182..94e830b5d677 100644 --- a/zebra/main.c +++ b/zebra/main.c @@ -21,6 +21,7 @@ #include "affinitymap.h" #include "routemap.h" #include "routing_nb.h" +#include "mgmt_be_client.h" #include "zebra/zebra_router.h" #include "zebra/zebra_errors.h" @@ -115,6 +116,8 @@ struct zebra_privs_t zserv_privs = { .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; +struct mgmt_be_client *mgmt_be_client; + /* SIGHUP handler. */ static void sighup(void) { @@ -140,6 +143,8 @@ static void sigint(void) zlog_notice("Terminating on signal"); + mgmt_be_client_destroy(mgmt_be_client); + atomic_store_explicit(&zrouter.in_shutdown, true, memory_order_relaxed); @@ -418,6 +423,11 @@ int main(int argc, char **argv) zebra_ns_init(); router_id_cmd_init(); zebra_vty_init(); + + /* Initialize MGMT backend functionalities */ + mgmt_be_client = mgmt_be_client_create("zebra", NULL, 0, + zrouter.master); + access_list_init(); prefix_list_init(); rtadv_cmd_init(); diff --git a/zebra/zebra_nb_state.c b/zebra/zebra_nb_state.c index ba537475cbcc..4fa6587b0a93 100644 --- a/zebra/zebra_nb_state.c +++ b/zebra/zebra_nb_state.c @@ -156,6 +156,8 @@ const void *lib_vrf_zebra_ribs_rib_get_next(struct nb_cb_get_next_args *args) safi_t safi; zvrf = zebra_vrf_lookup_by_id(vrf->vrf_id); + if (!zvrf) + return NULL; if (args->list_entry == NULL) { afi = AFI_IP; @@ -198,6 +200,8 @@ lib_vrf_zebra_ribs_rib_lookup_entry(struct nb_cb_lookup_entry_args *args) uint32_t table_id = 0; zvrf = zebra_vrf_lookup_by_id(vrf->vrf_id); + if (!zvrf) + return NULL; yang_afi_safi_identity2value(args->keys->key[0], &afi, &safi); table_id = yang_str2uint32(args->keys->key[1]); diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index d36c2f81c789..80d34b884a57 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -20,6 +20,7 @@ #include "vxlan.h" #include "termtable.h" #include "affinitymap.h" +#include "mgmt_be_client.h" #include "zebra/zebra_router.h" #include "zebra/zserv.h" @@ -4702,4 +4703,6 @@ void zebra_vty_init(void) #endif /* HAVE_SCRIPTING */ install_element(VIEW_NODE, &zebra_show_routing_tables_summary_cmd); + + mgmt_be_client_lib_vty_init(); } From db4451970c95c4e28d06900afba744d52832d2eb Mon Sep 17 00:00:00 2001 From: Pushpasis Sarkar Date: Wed, 13 Sep 2023 23:47:20 -0700 Subject: [PATCH 4/5] doc: Update MGMTd documentation This commit updates the existing MGMTd user and developer documentations with details regarding the new support for retrieving operational data via the MGMT Front-end interface. Signed-off-by: Pushpasis Sarkar --- doc/developer/mgmtd-dev.rst | 108 +++++++++++++++++++++++++++++++++++- doc/user/mgmtd.rst | 35 +++++++++--- 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/doc/developer/mgmtd-dev.rst b/doc/developer/mgmtd-dev.rst index 9839aa8b6c82..4dcc8641e960 100644 --- a/doc/developer/mgmtd-dev.rst +++ b/doc/developer/mgmtd-dev.rst @@ -34,8 +34,8 @@ has been converted to it, but in the future RESTCONF and NETCONF servers can easily be added as *front-ends* to mgmtd to support those protocols as well. -Converting A Daemon to MGMTD -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Converting A Daemon to MGMTD Client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A daemon must first be transitioned to the new *northbound* interface if that has not already been done (see `this northbound conversion documentation @@ -57,11 +57,13 @@ Front-End Interface: 3. Add CLI handler file[s] to ``mgmtd/subdir.am`` (e.g., ``staticd/static_vty.c``) 4. [if needed] Exclude (#ifndef) non-configuration CLI handlers from CLI source file (e.g., inside ``staticd/static_vty.c``) +5. Redirect MGMT Frontend Client debug CLI handlers to the daemon. Back-End Interface: -5. Add XPATHs mappings to a couple arrays to direct ``mgmtd`` at your daemon in +6. Add XPATHs mappings to a couple arrays to direct ``mgmtd`` at your daemon in ``mgmtd/mgmt_be_adapter.c`` +7. Redirect MGMT Backend Client debug CLI handlers to the daemon. Add YANG and CLI into MGMTD @@ -140,6 +142,29 @@ handlers. For example: } +Redirect MGMT Frontend Debug CLI handlers to the Daemon +------------------------------------------------------- + +In order to debug the MGMT frontend client library operations on the client +daemon MGMT provides 'debug mgmt frontend' CLI that needs to notify the +command on the new client daemon as well. To do that the daemon needs to be +added to 'VTYSH_MGMT_FE' list in the file 'vtysh/vtysh.h' as shown below. + +.. code-block:: c + + #define VTYSH_MGMT_FE VTYSH_MGMT | VTYSH_XXXX + +And then the daemon needs to call mgmt_fe_client_lib_vty_init() from it's +daemon-specifc VTY initializing routine as show below. + +.. code-block:: c + + void daemon_vty_init(void) + { + ... + + mgmt_fe_client_lib_vty_init(); + } Add Back-End XPATH mappings --------------------------- @@ -202,6 +227,83 @@ Below are the strings added for staticd support: #endif }; +The abobe example adds 'staticd' as a configuration validator for certain xpaths +under the overall data subtree in 'running' and 'candidate' datastores. +However, sometimes daemons may also need to provide operational data and state +via the MGMT frontend interface. Below is an example how the zebra daemon +can add support for retrieveing oprational data for the data subtree it +represents under the entire data tree in the operational datastore. Please note +that zebra here only takes responsibility of providing real-time operational +values of the dpecified data portions,. It does not accept any responsibility +of validating any data under these data portions, by specifying only +'MGMT_SUBSCR_OPER_OWN' for the '.subsribed' value for the corresponding Xpath +map entry. + +.. code-block:: c + + static struct mgmt_be_client_xpath zebra_xpaths[] = { + { + .xpath = "/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_OPER_OWN, + }, + { + .xpath = "/frr-vrf:lib/vrf[name='*']/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_OPER_OWN, + }, + }; + + static struct mgmt_be_client_xpath_map + mgmt_client_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + ... + [MGMTD_BE_CLIENT_ID_ZEBRA] = {zebra_xpaths, + array_size(zebra_xpaths)}, + ... + }; + +However a daemon can certainly support both accepting configurational +values for a data portion (via one of the config datastsores), as well as +providing the real-time operational value of the same data portion +(via the 'operational' datastore). In such case it should combine both +'MGMT_SUBSCR_VALIDATE_CFG' and 'MGMT_SUBSCR_OPER_OWN' values for the +'.subscribed' attribute in the corresponding Xpath map entry. Below is +a variation of the zebra Xpath mappings given above that combines both. + +.. code-block:: c + + static struct mgmt_be_client_xpath zebra_xpaths[] = { + { + .xpath = "/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_VALIDATE_CFG | MGMT_SUBSCR_OPER_OWN, + }, + { + .xpath = "/frr-vrf:lib/vrf[name='*']/frr-zebra:zebra/*", + .subscribed = MGMT_SUBSCR_OPER_OWN, + }, + }; + +Redirect MGMT Backend Debug CLI handlers to the Daemon +------------------------------------------------------ + +In order to debug the MGMT backend client library operations on the client +daemon MGMT provides 'debug mgmt backend' CLI that needs to notify the +command on the new client daemon as well. To do that the daemon needs to be +added to 'VTYSH_MGMT_BE' list in the file 'vtysh/vtysh.h' as shown below. + +.. code-block:: c + + #define VTYSH_MGMT_BE VTYSH_STATICD | VTYSH_ZEBRA + +And then the daemon needs to call mgmt_be_client_lib_vty_init() from it's +daemon-specifc VTY initializing routine as show below. + +.. code-block:: c + + void daemon_vty_init(void) + { + ... + + mgmt_be_client_lib_vty_init(); + } MGMTD Internals ^^^^^^^^^^^^^^^ diff --git a/doc/user/mgmtd.rst b/doc/user/mgmtd.rst index 42bc860b7271..740d92e92b95 100644 --- a/doc/user/mgmtd.rst +++ b/doc/user/mgmtd.rst @@ -347,19 +347,23 @@ MGMT Show commands .. clicmd:: show mgmt get-config [candidate|running] XPATH This command uses the GET_CONFIG operation over the MGMT Frontend interface and - returns the xpaths and values of the nodes of the subtree pointed by the . + returns the xpaths and values of the nodes of the subtree pointed by the + from the specifed datastore. Currenlty only supported values for datastore are + 'candidate' and 'running'. -.. clicmd:: show mgmt get-data [candidate|operation|running] XPATH +.. clicmd:: show mgmt get-data operational XPATH This command uses the GET_DATA operation over the MGMT Frontend interface and - returns the xpaths and values of the nodes of the subtree pointed by the . - Currenlty supported values for 'candidate' and 'running' only - ('operational' shall be supported in future soon). + returns the xpaths and values of the nodes of the subtree pointed by the + from a specified datastore. Currenlty only supported values for datastore is + 'operational'. GET-DATA operation on 'running' and 'candidate' are not allowed + (use the 'show mgmt get-config [candidate|running]' instead). -.. clicmd:: show mgmt datastore-contents [candidate|operation|running] [xpath WORD] [file WORD] json|xml +.. clicmd:: show mgmt database-contents [candidate|running] [xpath WORD] [file WORD] json|xml - This command dumps the subtree pointed by the xpath in JSON or XML format. If filepath is - not present then the tree will be printed on the shell. + This command dumps the subtree pointed by the xpath from a specifed datastore + in JSON or XML format. If filepath is not present then the tree will be printed on the + shell. Currenlty only supported values for datastore are 'candidate' and 'running'. .. clicmd:: show mgmt commit-history @@ -395,7 +399,7 @@ The following debug commands enable debugging within the management daemon: MGMT Client debug commands ========================== -The following debug commands enable debugging within the management front and +The following debug commands enable debugging within the management frontend and backend clients: .. clicmd:: [no] debug mgmt client backend @@ -407,3 +411,16 @@ backend clients: Enable[/Disable] debugging messages related to frontend operations inside the frontend mgmtd clients. + + +MGMT Common debug commands +========================== + +The following debug commands enable debugging of some common operations that +are running on the management daemon as well as all management frontend and +backend clients: + +.. clicmd:: [no] debug mgmt utilities + + Enable[/Disable] debugging messages related to common utility operations on the + mamagemnt daemon as well as all management frontend and backend clients. From dcfd9c5649fa7788111705b080b09ad4c2cfda3c Mon Sep 17 00:00:00 2001 From: Pushpasis Sarkar Date: Thu, 14 Sep 2023 01:51:09 -0700 Subject: [PATCH 5/5] tests: Add topotest for MGMT OPER-DATA command. This commit ads a topotest test-case to check the GET-OPER-DATA functionality. Signed-off-by: Pushpasis Sarkar --- .../show_mgmt_oper_data_route_1.ref | 1 + .../show_mgmt_oper_data_route_1_2.ref | 2 + .../show_mgmt_oper_data_route_2.ref | 1 + tests/topotests/mgmt_tests/test_yang_mgmt.py | 131 ++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1.ref create mode 100644 tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1_2.ref create mode 100644 tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_2.ref diff --git a/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1.ref b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1.ref new file mode 100644 index 000000000000..5c4a60f7cbb1 --- /dev/null +++ b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1.ref @@ -0,0 +1 @@ + "/frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='frr-routing:ipv4-unicast'][table-id='254']/route[prefix='192.168.1.1/32']/route-entry[protocol='static']/distance": "1" diff --git a/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1_2.ref b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1_2.ref new file mode 100644 index 000000000000..904d192840e0 --- /dev/null +++ b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_1_2.ref @@ -0,0 +1,2 @@ + "/frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='frr-routing:ipv4-unicast'][table-id='254']/route[prefix='192.168.1.1/32']/route-entry[protocol='static']/distance": "1" + "/frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='frr-routing:ipv4-unicast'][table-id='254']/route[prefix='192.168.1.3/32']/route-entry[protocol='static']/distance": "1" diff --git a/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_2.ref b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_2.ref new file mode 100644 index 000000000000..60aaccfe79bc --- /dev/null +++ b/tests/topotests/mgmt_tests/r1/test_oper_data/show_mgmt_oper_data_route_2.ref @@ -0,0 +1 @@ + "/frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='frr-routing:ipv4-unicast'][table-id='254']/route[prefix='192.168.1.3/32']/route-entry[protocol='static']/distance": "1" diff --git a/tests/topotests/mgmt_tests/test_yang_mgmt.py b/tests/topotests/mgmt_tests/test_yang_mgmt.py index 921b4e622cb8..98624aae0943 100644 --- a/tests/topotests/mgmt_tests/test_yang_mgmt.py +++ b/tests/topotests/mgmt_tests/test_yang_mgmt.py @@ -44,8 +44,10 @@ # pylint: disable=C0413 # Import topogen and topotest helpers +from lib import topotest from lib.topogen import Topogen, get_topogen from lib.topotest import version_cmp +from functools import partial # Import topoJson from lib, to create topology and initial configuration from lib.common_config import ( @@ -156,6 +158,47 @@ def populate_nh(): return next_hop_ip +def router_compare_text_output(rname, command, reference, check_for_mismatch=False): + "Compare router text output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = open(filename).read() + + # Run test function until we get an result. Wait at most 80 seconds. + test_func = partial( + topotest.router_output_cmp, tgen.gears[rname], command, expected + ) + if check_for_mismatch: + result, diff = topotest.run_and_expect(test_func, "", count=5, wait=1) + logger.info("Expected result:\n========\n{}\n==========".format(expected)) + logger.info( + "Actual result:\n========\n{}\n========".format( + tgen.gears[rname].vtysh_cmd(command) + ) + ) + assertmsg = '"{}" Text output does not mismatch the expected result. Diff:\n {}\n'.format( + rname, diff + ) + assert result is not None, assertmsg + else: + result, diff = topotest.run_and_expect(test_func, "", count=5, wait=1) + logger.info("Expected result:\n========\n{}\n==========".format(expected)) + logger.info( + "Actual result:\n========\n{}\n========".format( + tgen.gears[rname].vtysh_cmd(command) + ) + ) + assertmsg = ( + '"{}" Text output mismatches the expected result. Diff:\n {}\n'.format( + rname, diff + ) + ) + assert result, assertmsg + + ##################################################### # # Testcases @@ -401,6 +444,94 @@ def test_mgmt_delete_config(request): write_test_footer(tc_name) +def test_mgmt_get_oper_data(request): + """ + Verify mgmt get oper-data. + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Configure a static route using commit apply") + + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.1/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Get and verify oper-data from Zebra for route #1") + router_compare_text_output( + "r1", + "show mgmt get-data operational /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.1.1/32']/route-entry[protocol='static']/distance", + "test_oper_data/show_mgmt_oper_data_route_1.ref", + ) + + step("Configure another static route using commit apply") + + raw_config = { + "r1": { + "raw_config": [ + "mgmt set-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.3/32'][afi-safi='frr-routing:ipv4-unicast']/path-list[table-id='0'][distance='1']/frr-nexthops/nexthop[nh-type='blackhole'][vrf='default'][gateway=''][interface='(null)']/vrf default", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Get and verify oper-data from Zebra for route #2") + router_compare_text_output( + "r1", + "show mgmt get-data operational /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.1.3/32']/route-entry[protocol='static']/distance", + "test_oper_data/show_mgmt_oper_data_route_2.ref", + ) + + step("Get and verify oper-data from Zebra for both route #1 and #2") + router_compare_text_output( + "r1", + "show mgmt get-data operational /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.*/32']/route-entry[protocol='static']/distance", + "test_oper_data/show_mgmt_oper_data_route_1_2.ref", + ) + + step("Delete static route 1 and 2") + raw_config = { + "r1": { + "raw_config": [ + "mgmt delete-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.1/32'][afi-safi='frr-routing:ipv4-unicast']", + "mgmt delete-config /frr-routing:routing/control-plane-protocols/control-plane-protocol[type='frr-staticd:staticd'][name='staticd'][vrf='default']/frr-staticd:staticd/route-list[prefix='192.168.1.3/32'][afi-safi='frr-routing:ipv4-unicast']", + "mgmt commit apply", + ] + } + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify that the routes are deleted from RIB") + router_compare_text_output( + "r1", + "show mgmt get-data operational /frr-vrf:lib/vrf[name='default']/frr-zebra:zebra/ribs/rib[afi-safi-name='*'][table-id='*']/route[prefix='192.168.*/32']/route-entry[protocol='static']/distance", + "test_oper_data/show_mgmt_oper_data_route_1_2.ref", + check_for_mismatch=True, + ) + + write_test_footer(tc_name) + + def test_mgmt_chaos_stop_start_frr(request): """ Kill mgmtd - verify that watch frr restarts.