summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/lib_errors.c6
-rw-r--r--lib/lib_errors.h1
-rw-r--r--lib/northbound.c345
-rw-r--r--lib/northbound.h70
-rw-r--r--lib/northbound_cli.c112
-rw-r--r--lib/northbound_confd.c269
-rw-r--r--lib/northbound_sysrepo.c130
-rw-r--r--ripd/rip_northbound.c13
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/lib/northbound/test_oper_data.c466
-rw-r--r--tests/lib/northbound/test_oper_data.in1
-rw-r--r--tests/lib/northbound/test_oper_data.py4
-rw-r--r--tests/lib/northbound/test_oper_data.refout119
-rw-r--r--tests/subdir.am9
-rw-r--r--tools/gen_northbound_callbacks.c9
-rw-r--r--yang/frr-test-module.yang59
-rw-r--r--yang/subdir.am1
17 files changed, 1374 insertions, 241 deletions
diff --git a/lib/lib_errors.c b/lib/lib_errors.c
index 71d1ec6e58..b1ed7d2f6b 100644
--- a/lib/lib_errors.c
+++ b/lib/lib_errors.c
@@ -135,6 +135,12 @@ static struct log_ref ferr_lib_warn[] = {
.suggestion = "This is a bug; please report it"
},
{
+ .code = EC_LIB_NB_OPERATIONAL_DATA,
+ .title = "Failure to obtain operational data",
+ .description = "The northbound subsystem failed to obtain YANG-modeled operational data",
+ .suggestion = "This is a bug; please report it"
+ },
+ {
.code = EC_LIB_NB_TRANSACTION_CREATION_FAILED,
.title = "Failure to create a configuration transaction",
.description = "The northbound subsystem failed to create a configuration transaction",
diff --git a/lib/lib_errors.h b/lib/lib_errors.h
index 38c75f913e..5534edbd8d 100644
--- a/lib/lib_errors.h
+++ b/lib/lib_errors.h
@@ -62,6 +62,7 @@ enum lib_log_refs {
EC_LIB_NB_CB_RPC,
EC_LIB_NB_CANDIDATE_INVALID,
EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ EC_LIB_NB_OPERATIONAL_DATA,
EC_LIB_NB_TRANSACTION_CREATION_FAILED,
EC_LIB_NB_TRANSACTION_RECORD_FAILED,
EC_LIB_LIBYANG,
diff --git a/lib/northbound.c b/lib/northbound.c
index f8045b89aa..490b3abe57 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -50,6 +50,12 @@ static void nb_transaction_free(struct nb_transaction *transaction);
static int nb_transaction_process(enum nb_event event,
struct nb_transaction *transaction);
static void nb_transaction_apply_finish(struct nb_transaction *transaction);
+static int nb_oper_data_iter_node(const struct lys_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 lys_node *snode, void *arg)
{
@@ -930,6 +936,331 @@ static void nb_transaction_apply_finish(struct nb_transaction *transaction)
}
}
+static int nb_oper_data_iter_children(const struct lys_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)
+{
+ struct lys_node *child;
+
+ LY_TREE_FOR (snode->child, child) {
+ int ret;
+
+ ret = nb_oper_data_iter_node(child, xpath, list_entry,
+ list_keys, translator, false,
+ flags, cb, arg);
+ if (ret != NB_OK)
+ return ret;
+ }
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_leaf(const struct nb_node *nb_node,
+ const char *xpath, const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+ struct yang_data *data;
+
+ if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+ return NB_OK;
+
+ /* Ignore list keys. */
+ if (lys_is_key((struct lys_node_leaf *)nb_node->snode, NULL))
+ return NB_OK;
+
+ data = nb_node->cbs.get_elem(xpath, list_entry);
+ if (data == NULL)
+ /* Leaf of type "empty" is not present. */
+ return NB_OK;
+
+ return (*cb)(nb_node->snode, translator, data, arg);
+}
+
+static int nb_oper_data_iter_container(const struct nb_node *nb_node,
+ const char *xpath,
+ const void *list_entry,
+ const struct yang_list_keys *list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb,
+ void *arg)
+{
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return NB_OK;
+
+ /* Presence containers. */
+ if (nb_node->cbs.get_elem) {
+ struct yang_data *data;
+ int ret;
+
+ data = nb_node->cbs.get_elem(xpath, list_entry);
+ if (data == NULL)
+ /* Presence container is not present. */
+ return NB_OK;
+
+ ret = (*cb)(nb_node->snode, translator, data, arg);
+ if (ret != NB_OK)
+ return ret;
+ }
+
+ /* Iterate over the child nodes. */
+ return nb_oper_data_iter_children(nb_node->snode, xpath, list_entry,
+ list_keys, translator, false, flags,
+ cb, arg);
+}
+
+static int
+nb_oper_data_iter_leaflist(const struct nb_node *nb_node, const char *xpath,
+ const void *parent_list_entry,
+ const struct yang_list_keys *parent_list_keys,
+ struct yang_translator *translator, uint32_t flags,
+ nb_oper_data_cb cb, void *arg)
+{
+ const void *list_entry = NULL;
+
+ if (CHECK_FLAG(nb_node->snode->flags, LYS_CONFIG_W))
+ return NB_OK;
+
+ do {
+ struct yang_data *data;
+ int ret;
+
+ list_entry =
+ nb_node->cbs.get_next(parent_list_entry, list_entry);
+ if (!list_entry)
+ /* End of the list. */
+ break;
+
+ data = nb_node->cbs.get_elem(xpath, list_entry);
+ if (data == NULL)
+ continue;
+
+ ret = (*cb)(nb_node->snode, translator, data, arg);
+ if (ret != NB_OK)
+ return ret;
+ } while (list_entry);
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_list(const struct nb_node *nb_node,
+ const char *xpath_list,
+ const void *parent_list_entry,
+ const struct yang_list_keys *parent_list_keys,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg)
+{
+ struct lys_node_list *slist = (struct lys_node_list *)nb_node->snode;
+ const void *list_entry = NULL;
+
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return NB_OK;
+
+ /* Iterate over all list entries. */
+ do {
+ struct yang_list_keys list_keys;
+ char xpath[XPATH_MAXLEN];
+ int ret;
+
+ /* Obtain list entry. */
+ list_entry =
+ nb_node->cbs.get_next(parent_list_entry, list_entry);
+ if (!list_entry)
+ /* End of the list. */
+ break;
+
+ /* Obtain the list entry keys. */
+ if (nb_node->cbs.get_keys(list_entry, &list_keys) != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_STATE,
+ "%s: failed to get list keys", __func__);
+ return NB_ERR;
+ }
+
+ /* Build XPath of the list entry. */
+ strlcpy(xpath, xpath_list, sizeof(xpath));
+ for (unsigned int i = 0; i < list_keys.num; i++) {
+ snprintf(xpath + strlen(xpath),
+ sizeof(xpath) - strlen(xpath), "[%s='%s']",
+ slist->keys[i]->name, list_keys.key[i]);
+ }
+
+ /* Iterate over the child nodes. */
+ ret = nb_oper_data_iter_children(
+ nb_node->snode, xpath, list_entry, &list_keys,
+ translator, false, flags, cb, arg);
+ if (ret != NB_OK)
+ return ret;
+ } while (list_entry);
+
+ return NB_OK;
+}
+
+static int nb_oper_data_iter_node(const struct lys_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];
+ int ret = NB_OK;
+
+ if (!first && CHECK_FLAG(flags, NB_OPER_DATA_ITER_NORECURSE)
+ && CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST))
+ return NB_OK;
+
+ /* Update XPath. */
+ strlcpy(xpath, xpath_parent, sizeof(xpath));
+ if (!first && snode->nodetype != LYS_USES)
+ snprintf(xpath + strlen(xpath), sizeof(xpath) - strlen(xpath),
+ "/%s", snode->name);
+
+ nb_node = snode->priv;
+ switch (snode->nodetype) {
+ case LYS_CONTAINER:
+ ret = nb_oper_data_iter_container(nb_node, xpath, list_entry,
+ list_keys, translator, flags,
+ cb, arg);
+ break;
+ case LYS_LEAF:
+ ret = nb_oper_data_iter_leaf(nb_node, xpath, list_entry,
+ list_keys, translator, flags, cb,
+ arg);
+ break;
+ case LYS_LEAFLIST:
+ ret = nb_oper_data_iter_leaflist(nb_node, xpath, list_entry,
+ list_keys, translator, flags,
+ cb, arg);
+ break;
+ case LYS_LIST:
+ ret = nb_oper_data_iter_list(nb_node, xpath, list_entry,
+ list_keys, translator, flags, cb,
+ arg);
+ break;
+ case LYS_USES:
+ ret = nb_oper_data_iter_children(snode, xpath, list_entry,
+ list_keys, translator, false,
+ flags, cb, arg);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+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 list *list_dnodes;
+ struct lyd_node *dnode, *dn;
+ struct listnode *ln;
+ int ret;
+
+ 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;
+ }
+
+ /*
+ * Create a data tree from the XPath so that we can parse the keys of
+ * all YANG lists (if any).
+ */
+ ly_errno = 0;
+ dnode = lyd_new_path(NULL, ly_native_ctx, xpath, NULL, 0,
+ LYD_PATH_OPT_UPDATE);
+ if (!dnode && ly_errno) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ return NB_ERR;
+ }
+ /*
+ * We can remove the following two lines once we depend on
+ * libyang-v0.16-r2, which has the LYD_PATH_OPT_NOPARENTRET flag for
+ * lyd_new_path().
+ */
+ dnode = yang_dnode_get(dnode, xpath);
+ assert(dnode);
+
+ /*
+ * Create a linked list to sort the data nodes starting from the root.
+ */
+ list_dnodes = list_new();
+ for (dn = dnode; dn; dn = dn->parent) {
+ if (dn->schema->nodetype != LYS_LIST || !dn->child)
+ continue;
+ listnode_add_head(list_dnodes, dn);
+ }
+ /*
+ * Use the northbound callbacks to find list entry pointer corresponding
+ * to the given XPath.
+ */
+ for (ALL_LIST_ELEMENTS_RO(list_dnodes, ln, dn)) {
+ struct lyd_node *child;
+ struct nb_node *nn;
+ unsigned int n = 0;
+
+ /* Obtain the list entry keys. */
+ memset(&list_keys, 0, sizeof(list_keys));
+ LY_TREE_FOR (dn->child, child) {
+ if (!lys_is_key((struct lys_node_leaf *)child->schema,
+ NULL))
+ continue;
+ strlcpy(list_keys.key[n],
+ yang_dnode_get_string(child, NULL),
+ sizeof(list_keys.key[n]));
+ n++;
+ }
+ list_keys.num = n;
+ assert(list_keys.num
+ == ((struct lys_node_list *)dn->schema)->keys_size);
+
+ /* Find the list entry pointer. */
+ nn = dn->schema->priv;
+ list_entry = nn->cbs.lookup_entry(list_entry, &list_keys);
+ if (list_entry == NULL) {
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+ return NB_ERR_NOT_FOUND;
+ }
+ }
+
+ /* If a list entry was given, iterate over that list entry only. */
+ if (dnode->schema->nodetype == LYS_LIST && dnode->child)
+ ret = nb_oper_data_iter_children(
+ nb_node->snode, xpath, list_entry, &list_keys,
+ translator, true, flags, cb, arg);
+ else
+ ret = nb_oper_data_iter_node(nb_node->snode, xpath, list_entry,
+ &list_keys, translator, true,
+ flags, cb, arg);
+
+ list_delete(&list_dnodes);
+ yang_dnode_free(dnode);
+
+ return ret;
+}
+
bool nb_operation_is_valid(enum nb_operation operation,
const struct lys_node *snode)
{
@@ -1038,6 +1369,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
switch (snode->nodetype) {
case LYS_LEAF:
+ case LYS_LEAFLIST:
break;
case LYS_CONTAINER:
scontainer = (struct lys_node_container *)snode;
@@ -1049,6 +1381,19 @@ bool nb_operation_is_valid(enum nb_operation operation,
}
return true;
case NB_OP_GET_NEXT:
+ switch (snode->nodetype) {
+ case LYS_LIST:
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY))
+ return false;
+ break;
+ case LYS_LEAFLIST:
+ if (CHECK_FLAG(snode->flags, LYS_CONFIG_W))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ return true;
case NB_OP_GET_KEYS:
case NB_OP_LOOKUP_ENTRY:
switch (snode->nodetype) {
diff --git a/lib/northbound.h b/lib/northbound.h
index 68bce5b398..e26a2f8617 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -21,9 +21,10 @@
#define _FRR_NORTHBOUND_H_
#include "hook.h"
-#include "yang.h"
#include "linklist.h"
#include "openbsd-tree.h"
+#include "yang.h"
+#include "yang_translator.h"
/* Forward declaration(s). */
struct vty;
@@ -211,15 +212,15 @@ struct nb_callbacks {
/*
* Operational data callback.
*
- * The callback function should return the value of a specific leaf or
- * inform if a typeless value (presence containers or leafs of type
- * empty) exists or not.
+ * The callback function should return the value of a specific leaf,
+ * leaf-list entry or inform if a typeless value (presence containers or
+ * leafs of type empty) exists or not.
*
* xpath
* YANG data path of the data we want to get.
*
* list_entry
- * Pointer to list entry.
+ * Pointer to list entry (might be NULL).
*
* Returns:
* Pointer to newly created yang_data structure, or NULL to indicate
@@ -229,22 +230,24 @@ struct nb_callbacks {
const void *list_entry);
/*
- * Operational data callback for YANG lists.
+ * Operational data callback for YANG lists and leaf-lists.
*
- * The callback function should return the next entry in the list. The
- * 'list_entry' parameter will be NULL on the first invocation.
+ * The callback function should return the next entry in the list or
+ * leaf-list. The 'list_entry' parameter will be NULL on the first
+ * invocation.
*
- * xpath
- * Data path of the YANG list.
+ * parent_list_entry
+ * Pointer to parent list entry.
*
* list_entry
- * Pointer to list entry.
+ * Pointer to (leaf-)list entry.
*
* Returns:
- * Pointer to the next entry in the list, or NULL to signal that the
- * end of the list was reached.
+ * Pointer to the next entry in the (leaf-)list, or NULL to signal
+ * that the end of the (leaf-)list was reached.
*/
- const void *(*get_next)(const char *xpath, const void *list_entry);
+ const void *(*get_next)(const void *parent_list_entry,
+ const void *list_entry);
/*
* Operational data callback for YANG lists.
@@ -270,13 +273,17 @@ struct nb_callbacks {
* The callback function should return a list entry based on the list
* keys given as a parameter.
*
+ * parent_list_entry
+ * Pointer to parent list entry.
+ *
* keys
* Structure containing the keys of the list entry.
*
* Returns:
* Pointer to the list entry if found, or NULL if not found.
*/
- const void *(*lookup_entry)(const struct yang_list_keys *keys);
+ const void *(*lookup_entry)(const void *parent_list_entry,
+ const struct yang_list_keys *keys);
/*
* RPC and action callback.
@@ -434,6 +441,14 @@ struct nb_transaction {
struct nb_config_cbs changes;
};
+/* Callback function used by nb_oper_data_iterate(). */
+typedef int (*nb_oper_data_cb)(const struct lys_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg);
+
+/* Iterate over direct child nodes only. */
+#define NB_OPER_DATA_ITER_NORECURSE 0x0001
+
DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
(xpath, arguments))
@@ -701,6 +716,31 @@ extern int nb_candidate_commit(struct nb_config *candidate,
const char *comment, uint32_t *transaction_id);
/*
+ * Iterate over operetional data.
+ *
+ * xpath
+ * Data path of the YANG data we want to iterate over.
+ *
+ * translator
+ * YANG module translator (might be NULL).
+ *
+ * flags
+ * NB_OPER_DATA_ITER_ flags to control how the iteration is performed.
+ *
+ * cb
+ * Function to call with each data node.
+ *
+ * arg
+ * Arbitrary argument passed as the fourth parameter in each call to 'cb'.
+ *
+ * Returns:
+ * NB_OK on success, NB_ERR otherwise.
+ */
+extern int nb_oper_data_iterate(const char *xpath,
+ struct yang_translator *translator,
+ uint32_t flags, nb_oper_data_cb cb, void *arg);
+
+/*
* Validate if the northbound operation is valid for the given node.
*
* operation
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
index c7378d2440..1ffd65af42 100644
--- a/lib/northbound_cli.c
+++ b/lib/northbound_cli.c
@@ -1097,6 +1097,117 @@ DEFPY (show_config_transaction,
#endif /* HAVE_CONFIG_ROLLBACKS */
}
+static int nb_cli_oper_data_cb(const struct lys_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+{
+ struct lyd_node *dnode = arg;
+ struct ly_ctx *ly_ctx;
+
+ if (translator) {
+ int ret;
+
+ ret = yang_translate_xpath(translator,
+ YANG_TRANSLATE_FROM_NATIVE,
+ data->xpath, sizeof(data->xpath));
+ switch (ret) {
+ case YANG_TRANSLATE_SUCCESS:
+ break;
+ case YANG_TRANSLATE_NOTFOUND:
+ goto exit;
+ case YANG_TRANSLATE_FAILURE:
+ goto error;
+ }
+
+ ly_ctx = translator->ly_ctx;
+ } else
+ ly_ctx = ly_native_ctx;
+
+ ly_errno = 0;
+ dnode = lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value, 0,
+ LYD_PATH_OPT_UPDATE);
+ if (!dnode && ly_errno) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ goto error;
+ }
+
+exit:
+ yang_data_free(data);
+ return NB_OK;
+
+error:
+ yang_data_free(data);
+ return NB_ERR;
+}
+
+DEFPY (show_yang_operational_data,
+ show_yang_operational_data_cmd,
+ "show yang operational-data XPATH$xpath\
+ [{\
+ format <json$json|xml$xml>\
+ |translate WORD$translator_family\
+ }]",
+ SHOW_STR
+ "YANG information\n"
+ "Show YANG operational data\n"
+ "XPath expression specifying the YANG data path\n"
+ "Set the output format\n"
+ "JavaScript Object Notation\n"
+ "Extensible Markup Language\n"
+ "Translate operational data\n"
+ "YANG module translator\n")
+{
+ LYD_FORMAT format;
+ struct yang_translator *translator = NULL;
+ struct ly_ctx *ly_ctx;
+ struct lyd_node *dnode;
+ char *strp;
+
+ if (xml)
+ format = LYD_XML;
+ else
+ format = LYD_JSON;
+
+ if (translator_family) {
+ translator = yang_translator_find(translator_family);
+ if (!translator) {
+ vty_out(vty, "%% Module translator \"%s\" not found\n",
+ translator_family);
+ return CMD_WARNING;
+ }
+
+ ly_ctx = translator->ly_ctx;
+ } else
+ ly_ctx = ly_native_ctx;
+
+ /* Obtain data. */
+ dnode = yang_dnode_new(ly_ctx, false);
+ if (nb_oper_data_iterate(xpath, translator, 0, nb_cli_oper_data_cb,
+ dnode)
+ != NB_OK) {
+ vty_out(vty, "%% Failed to fetch operational data.\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+ lyd_validate(&dnode, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, ly_ctx);
+
+ /* Display the data. */
+ if (lyd_print_mem(&strp, dnode, format,
+ LYP_FORMAT | LYP_WITHSIBLINGS | LYP_WD_ALL)
+ != 0
+ || !strp) {
+ vty_out(vty, "%% Failed to display operational data.\n");
+ yang_dnode_free(dnode);
+ return CMD_WARNING;
+ }
+ vty_out(vty, "%s", strp);
+ free(strp);
+ yang_dnode_free(dnode);
+
+ return CMD_SUCCESS;
+}
+
DEFPY (show_yang_module,
show_yang_module_cmd,
"show yang module [module-translator WORD$translator_family]",
@@ -1436,6 +1547,7 @@ void nb_cli_init(void)
/* Other commands. */
install_element(CONFIG_NODE, &yang_module_translator_load_cmd);
install_element(CONFIG_NODE, &yang_module_translator_unload_cmd);
+ install_element(ENABLE_NODE, &show_yang_operational_data_cmd);
install_element(ENABLE_NODE, &show_yang_module_cmd);
install_element(ENABLE_NODE, &show_yang_module_detail_cmd);
install_element(ENABLE_NODE, &show_yang_module_translator_cmd);
diff --git a/lib/northbound_confd.c b/lib/northbound_confd.c
index 886f17f81f..9d01541205 100644
--- a/lib/northbound_confd.c
+++ b/lib/northbound_confd.c
@@ -91,22 +91,61 @@ static int frr_confd_val2str(const char *xpath, const confd_value_t *value,
return 0;
}
-/* Obtain list keys from ConfD hashed keypath. */
-static void frr_confd_hkeypath_get_keys(const confd_hkeypath_t *kp,
- struct yang_list_keys *keys)
+/* Obtain list entry from ConfD hashed keypath. */
+static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp,
+ struct nb_node *nb_node,
+ const void **list_entry)
{
- memset(keys, 0, sizeof(*keys));
- for (int i = 0; i < kp->len; i++) {
+ struct nb_node *nb_node_list;
+ int parent_lists = 0;
+ int curr_list = 0;
+
+ *list_entry = NULL;
+
+ /*
+ * Count the number of YANG lists in the path, disconsidering the
+ * last element.
+ */
+ nb_node_list = nb_node;
+ while (nb_node_list->parent_list) {
+ nb_node_list = nb_node_list->parent_list;
+ parent_lists++;
+ }
+ if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0)
+ return 0;
+
+ /* Start from the beginning and move down the tree. */
+ for (int i = kp->len; i >= 0; i--) {
+ struct yang_list_keys keys;
+
+ /* Not a YANG list. */
if (kp->v[i][0].type != C_BUF)
continue;
+ /* Obtain list keys. */
+ memset(&keys, 0, sizeof(keys));
for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) {
- strlcpy(keys->key[keys->num],
+ strlcpy(keys.key[keys.num],
(char *)kp->v[i][j].val.buf.ptr,
- sizeof(keys->key[keys->num]));
- keys->num++;
+ sizeof(keys.key[keys.num]));
+ keys.num++;
}
+
+ /* Obtain northbound node associated to the YANG list. */
+ nb_node_list = nb_node;
+ for (int j = curr_list; j < parent_lists; j++)
+ nb_node_list = nb_node_list->parent_list;
+
+ /* Obtain list entry. */
+ *list_entry =
+ nb_node_list->cbs.lookup_entry(*list_entry, &keys);
+ if (*list_entry == NULL)
+ return -1;
+
+ curr_list++;
}
+
+ return 0;
}
/* Fill the current date and time into a confd_datetime structure. */
@@ -493,12 +532,13 @@ static int frr_confd_transaction_init(struct confd_trans_ctx *tctx)
return CONFD_OK;
}
+#define CONFD_MAX_CHILD_NODES 32
+
static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
confd_hkeypath_t *kp)
{
- struct nb_node *nb_node, *parent_list;
+ struct nb_node *nb_node;
char xpath[BUFSIZ];
- struct yang_list_keys keys;
struct yang_data *data;
confd_value_t v;
const void *list_entry = NULL;
@@ -513,17 +553,9 @@ static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
return CONFD_OK;
}
- parent_list = nb_node->parent_list;
- if (parent_list) {
- frr_confd_hkeypath_get_keys(kp, &keys);
- list_entry = parent_list->cbs.lookup_entry(&keys);
- if (!list_entry) {
- flog_warn(EC_LIB_NB_CB_STATE,
- "%s: list entry not found: %s", __func__,
- xpath);
- confd_data_reply_not_found(tctx);
- return CONFD_OK;
- }
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
+ confd_data_reply_not_found(tctx);
+ return CONFD_OK;
}
data = nb_node->cbs.get_elem(xpath, list_entry);
@@ -546,7 +578,8 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
struct nb_node *nb_node;
char xpath[BUFSIZ];
struct yang_list_keys keys;
- const void *nb_next;
+ struct yang_data *data;
+ const void *parent_list_entry, *nb_next;
confd_value_t v[LIST_MAXKEYS];
frr_confd_get_xpath(kp, xpath, sizeof(xpath));
@@ -559,24 +592,51 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
return CONFD_OK;
}
- nb_next = nb_node->cbs.get_next(xpath,
- (next == -1) ? NULL : (void *)next);
- if (!nb_next) {
- /* End of the list. */
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+ != 0) {
+ /* List entry doesn't exist anymore. */
confd_data_reply_next_key(tctx, NULL, -1, -1);
return CONFD_OK;
}
- if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
- flog_warn(EC_LIB_NB_CB_STATE, "%s: failed to get list keys",
- __func__);
+
+ nb_next = nb_node->cbs.get_next(parent_list_entry,
+ (next == -1) ? NULL : (void *)next);
+ if (!nb_next) {
+ /* End of the list or leaf-list. */
confd_data_reply_next_key(tctx, NULL, -1, -1);
return CONFD_OK;
}
- /* Feed keys to ConfD. */
- for (size_t i = 0; i < keys.num; i++)
- CONFD_SET_STR(&v[i], keys.key[i]);
- confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
+ switch (nb_node->snode->nodetype) {
+ case LYS_LIST:
+ memset(&keys, 0, sizeof(keys));
+ if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_STATE,
+ "%s: failed to get list keys", __func__);
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ return CONFD_OK;
+ }
+
+ /* Feed keys to ConfD. */
+ for (size_t i = 0; i < keys.num; i++)
+ CONFD_SET_STR(&v[i], keys.key[i]);
+ confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
+ break;
+ case LYS_LEAFLIST:
+ data = nb_node->cbs.get_elem(xpath, nb_next);
+ if (data) {
+ if (data->value) {
+ CONFD_SET_STR(&v[0], data->value);
+ confd_data_reply_next_key(tctx, v, 1,
+ (long)nb_next);
+ }
+ yang_data_free(data);
+ } else
+ confd_data_reply_next_key(tctx, NULL, -1, -1);
+ break;
+ default:
+ break;
+ }
return CONFD_OK;
}
@@ -588,15 +648,14 @@ static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
confd_hkeypath_t *kp)
{
struct nb_node *nb_node;
+ const struct lys_node *child;
char xpath[BUFSIZ];
- char xpath_children[XPATH_MAXLEN];
char xpath_child[XPATH_MAXLEN];
- struct yang_list_keys keys;
struct list *elements;
struct yang_data *data;
const void *list_entry;
- struct ly_set *set;
- confd_value_t *values;
+ confd_value_t values[CONFD_MAX_CHILD_NODES];
+ size_t nvalues = 0;
frr_confd_get_xpath(kp, xpath, sizeof(xpath));
@@ -605,57 +664,53 @@ static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, xpath);
confd_data_reply_not_found(tctx);
- return CONFD_OK;
+ return CONFD_ERR;
}
- frr_confd_hkeypath_get_keys(kp, &keys);
- list_entry = nb_node->cbs.lookup_entry(&keys);
- if (!list_entry) {
- flog_warn(EC_LIB_NB_CB_STATE, "%s: list entry not found: %s",
- __func__, xpath);
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
confd_data_reply_not_found(tctx);
return CONFD_OK;
}
- /* Find list child nodes. */
- snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
- set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
- if (!set) {
- flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
- __func__);
- return CONFD_ERR;
- }
-
elements = yang_data_list_new();
- values = XMALLOC(MTYPE_CONFD, set->number * sizeof(*values));
/* Loop through list child nodes. */
- for (size_t i = 0; i < set->number; i++) {
- struct lys_node *child;
- struct nb_node *nb_node_child;
+ LY_TREE_FOR (nb_node->snode->child, child) {
+ struct nb_node *nb_node_child = child->priv;
+ confd_value_t *v;
+
+ if (nvalues > CONFD_MAX_CHILD_NODES)
+ break;
+
+ v = &values[nvalues++];
- child = set->set.s[i];
- nb_node_child = child->priv;
+ /* Non-presence containers, lists and leaf-lists. */
+ if (!nb_node_child->cbs.get_elem) {
+ CONFD_SET_NOEXISTS(v);
+ continue;
+ }
snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
child->name);
-
data = nb_node_child->cbs.get_elem(xpath_child, list_entry);
if (data) {
if (data->value)
- CONFD_SET_STR(&values[i], data->value);
- else
- CONFD_SET_NOEXISTS(&values[i]);
+ CONFD_SET_STR(v, data->value);
+ else {
+ /* Presence containers and empty leafs. */
+ CONFD_SET_XMLTAG(
+ v, nb_node_child->confd_hash,
+ confd_str2hash(nb_node_child->snode
+ ->module->ns));
+ }
listnode_add(elements, data);
} else
- CONFD_SET_NOEXISTS(&values[i]);
+ CONFD_SET_NOEXISTS(v);
}
- confd_data_reply_value_array(tctx, values, set->number);
+ confd_data_reply_value_array(tctx, values, nvalues);
/* Release memory. */
- ly_set_free(set);
- XFREE(MTYPE_CONFD, values);
list_delete(&elements);
return CONFD_OK;
@@ -668,10 +723,9 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
confd_hkeypath_t *kp, long next)
{
char xpath[BUFSIZ];
- char xpath_children[XPATH_MAXLEN];
struct nb_node *nb_node;
- struct ly_set *set;
struct list *elements;
+ const void *parent_list_entry;
const void *nb_next;
#define CONFD_OBJECTS_PER_TIME 100
struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
@@ -687,13 +741,10 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
return CONFD_OK;
}
- /* Find list child nodes. */
- snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
- set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
- if (!set) {
- flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
- __func__);
- return CONFD_ERR;
+ if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
+ != 0) {
+ confd_data_reply_next_object_array(tctx, NULL, 0, 0);
+ return CONFD_OK;
}
elements = yang_data_list_new();
@@ -702,62 +753,76 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
memset(objects, 0, sizeof(objects));
for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) {
struct confd_next_object *object;
- struct yang_list_keys keys;
+ struct lys_node *child;
struct yang_data *data;
- const void *list_entry;
+ size_t nvalues = 0;
object = &objects[j];
- nb_next = nb_node->cbs.get_next(xpath, nb_next);
+ nb_next = nb_node->cbs.get_next(parent_list_entry, nb_next);
if (!nb_next)
/* End of the list. */
break;
- if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
- flog_warn(EC_LIB_NB_CB_STATE,
- "%s: failed to get list keys", __func__);
- continue;
- }
object->next = (long)nb_next;
- list_entry = nb_node->cbs.lookup_entry(&keys);
- if (!list_entry) {
- flog_warn(EC_LIB_NB_CB_STATE,
- "%s: failed to lookup list entry", __func__);
- continue;
+ /* Leaf-lists require special handling. */
+ if (nb_node->snode->nodetype == LYS_LEAFLIST) {
+ object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t));
+ data = nb_node->cbs.get_elem(xpath, nb_next);
+ assert(data && data->value);
+ CONFD_SET_STR(object->v, data->value);
+ nvalues++;
+ listnode_add(elements, data);
+ goto next;
}
- object->v = XMALLOC(MTYPE_CONFD,
- set->number * sizeof(confd_value_t));
+ object->v =
+ XMALLOC(MTYPE_CONFD,
+ CONFD_MAX_CHILD_NODES * sizeof(confd_value_t));
/* Loop through list child nodes. */
- for (unsigned int i = 0; i < set->number; i++) {
- struct lys_node *child;
- struct nb_node *nb_node_child;
+ LY_TREE_FOR (nb_node->snode->child, child) {
+ struct nb_node *nb_node_child = child->priv;
char xpath_child[XPATH_MAXLEN];
- confd_value_t *v = &object->v[i];
+ confd_value_t *v;
+
+ if (nvalues > CONFD_MAX_CHILD_NODES)
+ break;
- child = set->set.s[i];
- nb_node_child = child->priv;
+ v = &object->v[nvalues++];
+
+ /* Non-presence containers, lists and leaf-lists. */
+ if (!nb_node_child->cbs.get_elem) {
+ CONFD_SET_NOEXISTS(v);
+ continue;
+ }
snprintf(xpath_child, sizeof(xpath_child), "%s/%s",
xpath, child->name);
-
data = nb_node_child->cbs.get_elem(xpath_child,
- list_entry);
+ nb_next);
if (data) {
if (data->value)
CONFD_SET_STR(v, data->value);
- else
- CONFD_SET_NOEXISTS(v);
+ else {
+ /*
+ * Presence containers and empty leafs.
+ */
+ CONFD_SET_XMLTAG(
+ v, nb_node_child->confd_hash,
+ confd_str2hash(
+ nb_node_child->snode
+ ->module->ns));
+ }
listnode_add(elements, data);
} else
CONFD_SET_NOEXISTS(v);
}
- object->n = set->number;
+ next:
+ object->n = nvalues;
nobjects++;
}
- ly_set_free(set);
if (nobjects == 0) {
confd_data_reply_next_object_array(tctx, NULL, 0, 0);
diff --git a/lib/northbound_sysrepo.c b/lib/northbound_sysrepo.c
index 9c07db8560..ffda4c65d0 100644
--- a/lib/northbound_sysrepo.c
+++ b/lib/northbound_sysrepo.c
@@ -299,111 +299,15 @@ static int frr_sr_config_change_cb(sr_session_ctx_t *session,
}
}
-static void frr_sr_state_get_elem(struct list *elements,
- struct nb_node *nb_node,
- const void *list_entry, const char *xpath)
+static int frr_sr_state_data_iter_cb(const struct lys_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
{
- struct yang_data *data;
-
- data = nb_node->cbs.get_elem(xpath, list_entry);
- if (data)
- listnode_add(elements, data);
-}
-
-static void frr_sr_state_cb_container(struct list *elements, const char *xpath,
- const struct lys_node *snode)
-{
- struct lys_node *child;
-
- LY_TREE_FOR (snode->child, child) {
- struct nb_node *nb_node = child->priv;
- char xpath_child[XPATH_MAXLEN];
-
- if (!nb_operation_is_valid(NB_OP_GET_ELEM, child))
- continue;
-
- snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
- child->name);
-
- frr_sr_state_get_elem(elements, nb_node, NULL, xpath_child);
- }
-}
-
-static void frr_sr_state_cb_list_entry(struct list *elements,
- const char *xpath_list,
- const void *list_entry,
- struct lys_node *child)
-{
- struct nb_node *nb_node = child->priv;
- struct lys_node_leaf *sleaf;
- char xpath_child[XPATH_MAXLEN];
-
- /* Sysrepo doesn't want to know about list keys. */
- switch (child->nodetype) {
- case LYS_LEAF:
- sleaf = (struct lys_node_leaf *)child;
- if (lys_is_key(sleaf, NULL))
- return;
- break;
- case LYS_LEAFLIST:
- break;
- default:
- return;
- }
-
- if (!nb_operation_is_valid(NB_OP_GET_ELEM, child))
- return;
-
- snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath_list,
- child->name);
+ struct list *elements = arg;
- frr_sr_state_get_elem(elements, nb_node, list_entry, xpath_child);
-}
+ listnode_add(elements, data);
-static void frr_sr_state_cb_list(struct list *elements, const char *xpath,
- const struct lys_node *snode)
-{
- struct nb_node *nb_node = snode->priv;
- struct lys_node_list *slist = (struct lys_node_list *)snode;
- const void *next;
-
- for (next = nb_node->cbs.get_next(xpath, NULL); next;
- next = nb_node->cbs.get_next(xpath, next)) {
- struct yang_list_keys keys;
- const void *list_entry;
- char xpath_list[XPATH_MAXLEN];
- struct lys_node *child;
-
- /* Get the list keys. */
- if (nb_node->cbs.get_keys(next, &keys) != NB_OK) {
- flog_warn(EC_LIB_NB_CB_STATE,
- "%s: failed to get list keys", __func__);
- continue;
- }
-
- /* Get list item. */
- list_entry = nb_node->cbs.lookup_entry(&keys);
- if (!list_entry) {
- flog_warn(EC_LIB_NB_CB_STATE,
- "%s: failed to lookup list entry", __func__);
- continue;
- }
-
- /* Append list keys to the XPath. */
- strlcpy(xpath_list, xpath, sizeof(xpath_list));
- for (unsigned int i = 0; i < keys.num; i++) {
- snprintf(xpath_list + strlen(xpath_list),
- sizeof(xpath_list) - strlen(xpath_list),
- "[%s='%s']", slist->keys[i]->name,
- keys.key[i]);
- }
-
- /* Loop through list entries. */
- LY_TREE_FOR (snode->child, child) {
- frr_sr_state_cb_list_entry(elements, xpath_list,
- list_entry, child);
- }
- }
+ return NB_OK;
}
/* Callback for state retrieval. */
@@ -413,26 +317,20 @@ static int frr_sr_state_cb(const char *xpath, sr_val_t **values,
{
struct list *elements;
struct yang_data *data;
- const struct lys_node *snode;
struct listnode *node;
sr_val_t *v;
int ret, count, i = 0;
- /* Find schema node. */
- snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
-
elements = yang_data_list_new();
-
- switch (snode->nodetype) {
- case LYS_CONTAINER:
- frr_sr_state_cb_container(elements, xpath, snode);
- break;
- case LYS_LIST:
- frr_sr_state_cb_list(elements, xpath, snode);
- break;
- default:
- break;
+ if (nb_oper_data_iterate(xpath, NULL, NB_OPER_DATA_ITER_NORECURSE,
+ frr_sr_state_data_iter_cb, elements)
+ != NB_OK) {
+ flog_warn(EC_LIB_NB_OPERATIONAL_DATA,
+ "%s: failed to obtain operational data [xpath %s]",
+ __func__, xpath);
+ goto exit;
}
+
if (list_isempty(elements))
goto exit;
diff --git a/ripd/rip_northbound.c b/ripd/rip_northbound.c
index f0f6edce89..7f26d55b83 100644
--- a/ripd/rip_northbound.c
+++ b/ripd/rip_northbound.c
@@ -1003,7 +1003,7 @@ lib_interface_rip_authentication_key_chain_delete(enum nb_event event,
* XPath: /frr-ripd:ripd/state/neighbors/neighbor
*/
static const void *
-ripd_state_neighbors_neighbor_get_next(const char *xpath,
+ripd_state_neighbors_neighbor_get_next(const void *parent_list_entry,
const void *list_entry)
{
struct listnode *node;
@@ -1030,7 +1030,8 @@ static int ripd_state_neighbors_neighbor_get_keys(const void *list_entry,
}
static const void *
-ripd_state_neighbors_neighbor_lookup_entry(const struct yang_list_keys *keys)
+ripd_state_neighbors_neighbor_lookup_entry(const void *parent_list_entry,
+ const struct yang_list_keys *keys)
{
struct in_addr address;
@@ -1089,8 +1090,9 @@ ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath,
/*
* XPath: /frr-ripd:ripd/state/routes/route
*/
-static const void *ripd_state_routes_route_get_next(const char *xpath,
- const void *list_entry)
+static const void *
+ripd_state_routes_route_get_next(const void *parent_list_entry,
+ const void *list_entry)
{
struct route_node *rn;
@@ -1119,7 +1121,8 @@ static int ripd_state_routes_route_get_keys(const void *list_entry,
}
static const void *
-ripd_state_routes_route_lookup_entry(const struct yang_list_keys *keys)
+ripd_state_routes_route_lookup_entry(const void *parent_list_entry,
+ const struct yang_list_keys *keys)
{
struct prefix prefix;
struct route_node *rn;
diff --git a/tests/.gitignore b/tests/.gitignore
index a6202786be..5453c0d80a 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -18,6 +18,7 @@
/lib/cli/test_cli_clippy.c
/lib/cli/test_commands
/lib/cli/test_commands_defun.c
+/lib/northbound/test_oper_data
/lib/test_buffer
/lib/test_checksum
/lib/test_graph
diff --git a/tests/lib/northbound/test_oper_data.c b/tests/lib/northbound/test_oper_data.c
new file mode 100644
index 0000000000..a9a89ee491
--- /dev/null
+++ b/tests/lib/northbound/test_oper_data.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2018 NetDEF, Inc.
+ * Renato Westphal
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "thread.h"
+#include "vty.h"
+#include "command.h"
+#include "memory.h"
+#include "memory_vty.h"
+#include "log.h"
+#include "northbound.h"
+
+static struct thread_master *master;
+
+struct troute {
+ struct prefix_ipv4 prefix;
+ struct in_addr nexthop;
+ char ifname[IFNAMSIZ];
+ uint8_t metric;
+ bool active;
+};
+
+struct tvrf {
+ char name[32];
+ struct list *interfaces;
+ struct list *routes;
+};
+
+static struct list *vrfs;
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf
+ */
+static const void *
+frr_test_module_vrfs_vrf_get_next(const void *parent_list_entry,
+ const void *list_entry)
+{
+ struct listnode *node;
+
+ if (list_entry == NULL)
+ node = listhead(vrfs);
+ else
+ node = listnextnode((struct listnode *)list_entry);
+
+ return node;
+}
+
+static int frr_test_module_vrfs_vrf_get_keys(const void *list_entry,
+ struct yang_list_keys *keys)
+{
+ const struct tvrf *vrf;
+
+ vrf = listgetdata((struct listnode *)list_entry);
+
+ keys->num = 1;
+ strlcpy(keys->key[0], vrf->name, sizeof(keys->key[0]));
+
+ return NB_OK;
+}
+
+static const void *
+frr_test_module_vrfs_vrf_lookup_entry(const void *parent_list_entry,
+ const struct yang_list_keys *keys)
+{
+ struct listnode *node;
+ struct tvrf *vrf;
+ const char *vrfname;
+
+ vrfname = keys->key[0];
+
+ for (ALL_LIST_ELEMENTS_RO(vrfs, node, vrf)) {
+ if (strmatch(vrf->name, vrfname))
+ return node;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/name
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_name_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct tvrf *vrf;
+
+ vrf = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_string(xpath, vrf->name);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_interfaces_interface_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const char *interface;
+
+ interface = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_string(xpath, interface);
+}
+
+static const void *frr_test_module_vrfs_vrf_interfaces_interface_get_next(
+ const void *parent_list_entry, const void *list_entry)
+{
+ const struct tvrf *vrf;
+ struct listnode *node;
+
+ vrf = listgetdata((struct listnode *)parent_list_entry);
+ if (list_entry == NULL)
+ node = listhead(vrf->interfaces);
+ else
+ node = listnextnode((struct listnode *)list_entry);
+
+ return node;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route
+ */
+static const void *
+frr_test_module_vrfs_vrf_routes_route_get_next(const void *parent_list_entry,
+ const void *list_entry)
+{
+ const struct tvrf *vrf;
+ struct listnode *node;
+
+ vrf = listgetdata((struct listnode *)parent_list_entry);
+ if (list_entry == NULL)
+ node = listhead(vrf->routes);
+ else
+ node = listnextnode((struct listnode *)list_entry);
+
+ return node;
+}
+
+static int
+frr_test_module_vrfs_vrf_routes_route_get_keys(const void *list_entry,
+ struct yang_list_keys *keys)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+
+ keys->num = 1;
+ (void)prefix2str(&route->prefix, keys->key[0], sizeof(keys->key[0]));
+
+ return NB_OK;
+}
+
+static const void *frr_test_module_vrfs_vrf_routes_route_lookup_entry(
+ const void *parent_list_entry, const struct yang_list_keys *keys)
+{
+ const struct tvrf *vrf;
+ const struct troute *route;
+ struct listnode *node;
+ struct prefix prefix;
+
+ yang_str2ipv4p(keys->key[0], &prefix);
+
+ vrf = listgetdata((struct listnode *)parent_list_entry);
+ for (ALL_LIST_ELEMENTS_RO(vrf->routes, node, route)) {
+ if (prefix_same((struct prefix *)&route->prefix, &prefix))
+ return node;
+ }
+
+ return NULL;
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_prefix_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_ipv4p(xpath, &route->prefix);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_ipv4(xpath, &route->nexthop);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_interface_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_string(xpath, route->ifname);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_metric_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+ return yang_data_new_uint8(xpath, route->metric);
+}
+
+/*
+ * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/active
+ */
+static struct yang_data *
+frr_test_module_vrfs_vrf_routes_route_active_get_elem(const char *xpath,
+ const void *list_entry)
+{
+ const struct troute *route;
+
+ route = listgetdata((struct listnode *)list_entry);
+ if (route->active)
+ return yang_data_new(xpath, NULL);
+
+ return NULL;
+}
+
+/* clang-format off */
+const struct frr_yang_module_info frr_test_module_info = {
+ .name = "frr-test-module",
+ .nodes = {
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf",
+ .cbs.get_next = frr_test_module_vrfs_vrf_get_next,
+ .cbs.get_keys = frr_test_module_vrfs_vrf_get_keys,
+ .cbs.lookup_entry = frr_test_module_vrfs_vrf_lookup_entry,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/name",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_name_get_elem,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_interfaces_interface_get_elem,
+ .cbs.get_next = frr_test_module_vrfs_vrf_interfaces_interface_get_next,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route",
+ .cbs.get_next = frr_test_module_vrfs_vrf_routes_route_get_next,
+ .cbs.get_keys = frr_test_module_vrfs_vrf_routes_route_get_keys,
+ .cbs.lookup_entry = frr_test_module_vrfs_vrf_routes_route_lookup_entry,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_prefix_get_elem,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_interface_get_elem,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_metric_get_elem,
+ },
+ {
+ .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/active",
+ .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_active_get_elem,
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
+/* clang-format on */
+
+static const struct frr_yang_module_info *modules[] = {
+ &frr_test_module_info,
+};
+
+static void create_data(unsigned int num_vrfs, unsigned int num_interfaces,
+ unsigned int num_routes)
+{
+ struct prefix_ipv4 base_prefix;
+ struct in_addr base_nexthop;
+
+ (void)str2prefix_ipv4("10.0.0.0/32", &base_prefix);
+ (void)inet_pton(AF_INET, "172.16.0.0", &base_nexthop);
+
+ vrfs = list_new();
+
+ /* Create VRFs. */
+ for (unsigned int i = 0; i < num_vrfs; i++) {
+ struct tvrf *vrf;
+
+ vrf = XCALLOC(MTYPE_TMP, sizeof(*vrf));
+ snprintf(vrf->name, sizeof(vrf->name), "vrf%u", i);
+ vrf->interfaces = list_new();
+ vrf->routes = list_new();
+
+ /* Create interfaces. */
+ for (unsigned int j = 0; j < num_interfaces; j++) {
+ char ifname[32];
+ char *interface;
+
+ snprintf(ifname, sizeof(ifname), "eth%u", j);
+ interface = XSTRDUP(MTYPE_TMP, ifname);
+ listnode_add(vrf->interfaces, interface);
+ }
+
+ /* Create routes. */
+ for (unsigned int j = 0; j < num_routes; j++) {
+ struct troute *route;
+
+ route = XCALLOC(MTYPE_TMP, sizeof(*route));
+
+ memcpy(&route->prefix, &base_prefix,
+ sizeof(route->prefix));
+ route->prefix.prefix.s_addr =
+ htonl(ntohl(route->prefix.prefix.s_addr) + j);
+
+ memcpy(&route->nexthop, &base_nexthop,
+ sizeof(route->nexthop));
+ route->nexthop.s_addr =
+ htonl(ntohl(route->nexthop.s_addr) + j);
+
+ snprintf(route->ifname, sizeof(route->ifname), "eth%u",
+ j);
+ route->metric = j % 256;
+ route->active = (j % 2 == 0);
+ listnode_add(vrf->routes, route);
+ }
+
+ listnode_add(vrfs, vrf);
+ }
+}
+
+static void interface_delete(void *ptr)
+{
+ char *interface = ptr;
+
+ XFREE(MTYPE_TMP, interface);
+}
+
+static void route_delete(void *ptr)
+{
+ struct troute *route = ptr;
+
+ XFREE(MTYPE_TMP, route);
+}
+
+static void vrf_delete(void *ptr)
+{
+ struct tvrf *vrf = ptr;
+
+ vrf->interfaces->del = interface_delete;
+ list_delete(&vrf->interfaces);
+ vrf->routes->del = route_delete;
+ list_delete(&vrf->routes);
+ XFREE(MTYPE_TMP, vrf);
+}
+
+static void delete_data(void)
+{
+ vrfs->del = vrf_delete;
+ list_delete(&vrfs);
+}
+
+static void vty_do_exit(int isexit)
+{
+ printf("\nend.\n");
+
+ delete_data();
+
+ cmd_terminate();
+ vty_terminate();
+ nb_terminate();
+ yang_terminate();
+ thread_master_free(master);
+ closezlog();
+
+ log_memstats(stderr, "test-nb-oper-data");
+ if (!isexit)
+ exit(0);
+}
+
+/* main routine. */
+int main(int argc, char **argv)
+{
+ struct thread thread;
+ unsigned int num_vrfs = 2;
+ unsigned int num_interfaces = 4;
+ unsigned int num_routes = 6;
+
+ if (argc > 1)
+ num_vrfs = atoi(argv[1]);
+ if (argc > 2)
+ num_interfaces = atoi(argv[2]);
+ if (argc > 3)
+ num_routes = atoi(argv[3]);
+
+ /* Set umask before anything for security */
+ umask(0027);
+
+ /* master init. */
+ master = thread_master_create(NULL);
+
+ openzlog("test-nb-oper-data", "NONE", 0,
+ LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
+ zlog_set_level(ZLOG_DEST_SYSLOG, ZLOG_DISABLED);
+ zlog_set_level(ZLOG_DEST_STDOUT, ZLOG_DISABLED);
+ zlog_set_level(ZLOG_DEST_MONITOR, LOG_DEBUG);
+
+ /* Library inits. */
+ cmd_init(1);
+ cmd_hostname_set("test");
+ vty_init(master);
+ memory_init();
+ yang_init();
+ nb_init(modules, array_size(modules));
+
+ /* Create artificial data. */
+ create_data(num_vrfs, num_interfaces, num_routes);
+
+ /* Read input from .in file. */
+ vty_stdio(vty_do_exit);
+
+ /* Fetch next active thread. */
+ while (thread_fetch(master, &thread))
+ thread_call(&thread);
+
+ /* Not reached. */
+ exit(0);
+}
diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in
new file mode 100644
index 0000000000..a6c4f874f5
--- /dev/null
+++ b/tests/lib/northbound/test_oper_data.in
@@ -0,0 +1 @@
+show yang operational-data /frr-test-module:frr-test-module
diff --git a/tests/lib/northbound/test_oper_data.py b/tests/lib/northbound/test_oper_data.py
new file mode 100644
index 0000000000..8f5fdd6fd0
--- /dev/null
+++ b/tests/lib/northbound/test_oper_data.py
@@ -0,0 +1,4 @@
+import frrtest
+
+class TestNbOperData(frrtest.TestRefOut):
+ program = './test_oper_data'
diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout
new file mode 100644
index 0000000000..57ecd2f0a0
--- /dev/null
+++ b/tests/lib/northbound/test_oper_data.refout
@@ -0,0 +1,119 @@
+test# show yang operational-data /frr-test-module:frr-test-module
+{
+ "frr-test-module:frr-test-module": {
+ "vrfs": {
+ "vrf": [
+ {
+ "name": "vrf0",
+ "interfaces": {
+ "interface": [
+ "eth0",
+ "eth1",
+ "eth2",
+ "eth3"
+ ]
+ },
+ "routes": {
+ "route": [
+ {
+ "prefix": "10.0.0.0/32",
+ "next-hop": "172.16.0.0",
+ "interface": "eth0",
+ "metric": 0,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.1/32",
+ "next-hop": "172.16.0.1",
+ "interface": "eth1",
+ "metric": 1
+ },
+ {
+ "prefix": "10.0.0.2/32",
+ "next-hop": "172.16.0.2",
+ "interface": "eth2",
+ "metric": 2,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.3/32",
+ "next-hop": "172.16.0.3",
+ "interface": "eth3",
+ "metric": 3
+ },
+ {
+ "prefix": "10.0.0.4/32",
+ "next-hop": "172.16.0.4",
+ "interface": "eth4",
+ "metric": 4,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.5/32",
+ "next-hop": "172.16.0.5",
+ "interface": "eth5",
+ "metric": 5
+ }
+ ]
+ }
+ },
+ {
+ "name": "vrf1",
+ "interfaces": {
+ "interface": [
+ "eth0",
+ "eth1",
+ "eth2",
+ "eth3"
+ ]
+ },
+ "routes": {
+ "route": [
+ {
+ "prefix": "10.0.0.0/32",
+ "next-hop": "172.16.0.0",
+ "interface": "eth0",
+ "metric": 0,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.1/32",
+ "next-hop": "172.16.0.1",
+ "interface": "eth1",
+ "metric": 1
+ },
+ {
+ "prefix": "10.0.0.2/32",
+ "next-hop": "172.16.0.2",
+ "interface": "eth2",
+ "metric": 2,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.3/32",
+ "next-hop": "172.16.0.3",
+ "interface": "eth3",
+ "metric": 3
+ },
+ {
+ "prefix": "10.0.0.4/32",
+ "next-hop": "172.16.0.4",
+ "interface": "eth4",
+ "metric": 4,
+ "active": [null]
+ },
+ {
+ "prefix": "10.0.0.5/32",
+ "next-hop": "172.16.0.5",
+ "interface": "eth5",
+ "metric": 5
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
+test#
+end.
diff --git a/tests/subdir.am b/tests/subdir.am
index 6b52c90bc0..7d2800a3a2 100644
--- a/tests/subdir.am
+++ b/tests/subdir.am
@@ -68,6 +68,7 @@ check_PROGRAMS = \
tests/lib/test_graph \
tests/lib/cli/test_cli \
tests/lib/cli/test_commands \
+ tests/lib/northbound/test_oper_data \
$(TESTS_BGPD) \
$(TESTS_ISISD) \
$(TESTS_OSPF6D) \
@@ -175,6 +176,11 @@ tests_lib_cli_test_commands_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD)
nodist_tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands_defun.c
tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands.c tests/helpers/c/prng.c
+tests_lib_northbound_test_oper_data_CFLAGS = $(TESTS_CFLAGS)
+tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD)
+tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c
+nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c
tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS)
tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD)
@@ -284,6 +290,9 @@ EXTRA_DIST += \
tests/lib/cli/test_cli.in \
tests/lib/cli/test_cli.py \
tests/lib/cli/test_cli.refout \
+ tests/lib/northbound/test_oper_data.in \
+ tests/lib/northbound/test_oper_data.py \
+ tests/lib/northbound/test_oper_data.refout \
tests/lib/test_nexthop_iter.py \
tests/lib/test_ringbuf.py \
tests/lib/test_srcdest_table.py \
diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c
index 5ecb34e023..eded87c12e 100644
--- a/tools/gen_northbound_callbacks.c
+++ b/tools/gen_northbound_callbacks.c
@@ -84,19 +84,22 @@ static struct nb_callback_info {
.operation = NB_OP_GET_NEXT,
.return_type = "const void *",
.return_value = "NULL",
- .arguments = "const char *xpath, const void *list_entry",
+ .arguments =
+ "const void *parent_list_entry, const void *list_entry",
},
{
.operation = NB_OP_GET_KEYS,
.return_type = "int ",
.return_value = "NB_OK",
- .arguments = "const void *list_entry, struct yang_list_keys *keys",
+ .arguments =
+ "const void *list_entry, struct yang_list_keys *keys",
},
{
.operation = NB_OP_LOOKUP_ENTRY,
.return_type = "const void *",
.return_value = "NULL",
- .arguments = "const struct yang_list_keys *keys",
+ .arguments =
+ "const void *parent_list_entry, const struct yang_list_keys *keys",
},
{
.operation = NB_OP_RPC,
diff --git a/yang/frr-test-module.yang b/yang/frr-test-module.yang
new file mode 100644
index 0000000000..c02c0a11d7
--- /dev/null
+++ b/yang/frr-test-module.yang
@@ -0,0 +1,59 @@
+module frr-test-module {
+ yang-version 1.1;
+ namespace "urn:frr-test-module";
+ prefix frr-test-module;
+
+ import ietf-inet-types {
+ prefix inet;
+ }
+ import ietf-yang-types {
+ prefix yang;
+ }
+ import frr-interface {
+ prefix frr-interface;
+ }
+
+ revision 2018-11-26 {
+ description
+ "Initial revision.";
+ }
+
+ container frr-test-module {
+ config false;
+ container vrfs {
+ list vrf {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+ container interfaces {
+ leaf-list interface {
+ type string;
+ }
+ }
+ container routes {
+ list route {
+ key "prefix";
+
+ leaf prefix {
+ type inet:ipv4-prefix;
+ }
+ leaf next-hop {
+ type inet:ipv4-address;
+ }
+ leaf interface {
+ type string;
+ }
+ leaf metric {
+ type uint8;
+ }
+ leaf active {
+ type empty;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/yang/subdir.am b/yang/subdir.am
index ee6fbc181d..07bd225780 100644
--- a/yang/subdir.am
+++ b/yang/subdir.am
@@ -20,6 +20,7 @@ EXTRA_DIST += yang/embedmodel.py
# without problems, as seen in libfrr.
dist_yangmodels_DATA += yang/frr-module-translator.yang
+dist_yangmodels_DATA += yang/frr-test-module.yang
dist_yangmodels_DATA += yang/frr-interface.yang
dist_yangmodels_DATA += yang/frr-route-types.yang