diff options
author | Renato Westphal <renato@opensourcerouting.org> | 2018-12-08 20:31:16 +0100 |
---|---|---|
committer | Renato Westphal <renato@opensourcerouting.org> | 2018-12-09 16:58:53 +0100 |
commit | 99fb518fef7d40c20921ce63764e7578d1149fa8 (patch) | |
tree | c2f15f9d552ed00f4cc1b5f8bbf3dbbc30a71b26 /lib | |
parent | Merge pull request #3449 from opensourcerouting/network-wide-transactions (diff) | |
download | frr-99fb518fef7d40c20921ce63764e7578d1149fa8.tar.xz frr-99fb518fef7d40c20921ce63764e7578d1149fa8.zip |
lib, tests: add support for keyless YANG lists
YANG allows lists without keys for operational data, in which case
the list elements are uniquely identified using a positional index
(starting from one).
This commit does the following:
* Remove the need to implement the 'get_keys' and 'lookup_entry'
callbacks for keyless lists.
* Extend nb_oper_data_iter_list() so that it special-cases keyless
lists appropriately. Since both the CLI and the sysrepo plugin
use nb_oper_data_iterate() to fetch operational data, both these
northbound clients automatically gain the ability to understand
keyless lists without additional changes.
* Extend the confd plugin to special-case keyless lists as well. This
was a bit painful to implement given ConfD's clumsy API, but
keyless lists should work ok now.
* Update the "test_oper_data" unit test to test keyless YANG lists in
addition to regular lists.
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/northbound.c | 48 | ||||
-rw-r--r-- | lib/northbound.h | 8 | ||||
-rw-r--r-- | lib/northbound_confd.c | 96 |
3 files changed, 121 insertions, 31 deletions
diff --git a/lib/northbound.c b/lib/northbound.c index 8503f87d6..8b96dc4a6 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -95,6 +95,13 @@ static int nb_node_new_cb(const struct lys_node *snode, void *arg) if (config_only) SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY); } + if (CHECK_FLAG(snode->nodetype, LYS_LIST)) { + struct lys_node_list *slist; + + slist = (struct lys_node_list *)snode; + if (slist->keys_size == 0) + SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST); + } /* * Link the northbound node and the libyang schema node with one @@ -1056,6 +1063,7 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node, { struct lys_node_list *slist = (struct lys_node_list *)nb_node->snode; const void *list_entry = NULL; + uint32_t position = 1; if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return NB_OK; @@ -1073,19 +1081,31 @@ static int nb_oper_data_iter_list(const struct nb_node *nb_node, /* 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]); + if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { + /* 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]); + } + } else { + /* + * Keyless list - build XPath using a positional index. + */ + snprintf(xpath, sizeof(xpath), "%s[%u]", xpath_list, + position); + position++; } /* Iterate over the child nodes. */ @@ -1400,6 +1420,8 @@ bool nb_operation_is_valid(enum nb_operation operation, case LYS_LIST: if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) return false; + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) + return false; break; default: return false; diff --git a/lib/northbound.h b/lib/northbound.h index c8e8d7570..9d35a4e64 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -254,7 +254,8 @@ struct nb_callbacks { * Operational data callback for YANG lists. * * The callback function should fill the 'keys' parameter based on the - * given list_entry. + * given list_entry. Keyless lists don't need to implement this + * callback. * * list_entry * Pointer to list entry. @@ -272,7 +273,8 @@ struct nb_callbacks { * Operational data callback for YANG lists. * * The callback function should return a list entry based on the list - * keys given as a parameter. + * keys given as a parameter. Keyless lists don't need to implement this + * callback. * * parent_list_entry * Pointer to parent list entry. @@ -367,6 +369,8 @@ struct nb_node { }; /* The YANG container or list contains only config data. */ #define F_NB_NODE_CONFIG_ONLY 0x01 +/* The YANG list doesn't contain key leafs. */ +#define F_NB_NODE_KEYLESS_LIST 0x02 struct frr_yang_module_info { /* YANG module name. */ diff --git a/lib/northbound_confd.c b/lib/northbound_confd.c index e819384af..53149d0fd 100644 --- a/lib/northbound_confd.c +++ b/lib/northbound_confd.c @@ -138,10 +138,19 @@ static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp, 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; + if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) { + *list_entry = nb_node_list->cbs.lookup_entry( + *list_entry, &keys); + if (*list_entry == NULL) + return -1; + } else { + unsigned long ptr_ulong; + + /* Retrieve list entry from pseudo-key (string). */ + if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1) + return -1; + *list_entry = (const void *)ptr_ulong; + } curr_list++; } @@ -640,7 +649,6 @@ 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; struct yang_data *data; const void *parent_list_entry, *nb_next; confd_value_t v[LIST_MAXKEYS]; @@ -672,18 +680,53 @@ static int frr_confd_data_get_next(struct confd_trans_ctx *tctx, 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; - } + if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { + struct yang_list_keys keys; + + 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); + /* 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); + } else { + char pointer_str[16]; + + /* + * ConfD 6.6 user guide, chapter 6.11 (Operational data + * lists without keys): + * "To support this without having completely separate + * APIs, we use a "pseudo" key in the ConfD APIs for + * this type of list. This key is not part of the data + * model, and completely hidden in the northbound agent + * interfaces, but is used with e.g. the get_next() and + * get_elem() callbacks as if it were a normal key. This + * "pseudo" key is always a single signed 64-bit + * integer, i.e. the confd_value_t type is C_INT64. The + * values can be chosen arbitrarily by the application, + * as long as a key value returned by get_next() can be + * used to get the data for the corresponding list entry + * with get_elem() or get_object() as usual. It could + * e.g. be an index into an array that holds the data, + * or even a memory address in integer form". + * + * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY + * option, we must convert our pseudo-key (a void + * pointer) to a string before sending it to confd. + */ + snprintf(pointer_str, sizeof(pointer_str), "%lu", + (unsigned long)nb_next); + CONFD_SET_STR(&v[0], pointer_str); + confd_data_reply_next_key(tctx, v, 1, (long)nb_next); + } break; case LYS_LEAFLIST: data = nb_node->cbs.get_elem(xpath, nb_next); @@ -792,6 +835,7 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx, const void *nb_next; #define CONFD_OBJECTS_PER_TIME 100 struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1]; + char pseudo_keys[CONFD_OBJECTS_PER_TIME][16]; int nobjects = 0; frr_confd_get_xpath(kp, xpath, sizeof(xpath)); @@ -844,6 +888,26 @@ static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx, XMALLOC(MTYPE_CONFD, CONFD_MAX_CHILD_NODES * sizeof(confd_value_t)); + /* + * ConfD 6.6 user guide, chapter 6.11 (Operational data lists + * without keys): + * "In the response to the get_next_object() callback, the data + * provider is expected to provide the key values along with the + * other leafs in an array that is populated according to the + * data model. This must be done also for this type of list, + * even though the key isn't actually in the data model. The + * "pseudo" key must always be the first element in the array". + */ + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { + confd_value_t *v; + + snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu", + (unsigned long)nb_next); + + v = &object->v[nvalues++]; + CONFD_SET_STR(v, pseudo_keys[j]); + } + /* Loop through list child nodes. */ LY_TREE_FOR (nb_node->snode->child, child) { struct nb_node *nb_node_child = child->priv; |