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 @@ -134,6 +134,12 @@ static struct log_ref ferr_lib_warn[] = { .description = "The northbound subsystem failed to edit a candidate configuration", .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", 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)) @@ -700,6 +715,31 @@ extern int nb_candidate_commit(struct nb_config *candidate, enum nb_client client, bool save_transaction, 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. * 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 \ + |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. */ - 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__); + 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; } - /* 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); + 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; + } + + 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; - child = set->set.s[i]; - nb_node_child = child->priv; + if (nvalues > CONFD_MAX_CHILD_NODES) + break; + + v = &values[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); 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; - child = set->set.s[i]; - nb_node_child = child->priv; + if (nvalues > CONFD_MAX_CHILD_NODES) + break; + + 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; + struct list *elements = arg; - data = nb_node->cbs.get_elem(xpath, list_entry); - if (data) - listnode_add(elements, 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); - - frr_sr_state_get_elem(elements, nb_node, list_entry, xpath_child); -} - -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 + +#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