diff options
| -rw-r--r-- | lib/lib_errors.c | 6 | ||||
| -rw-r--r-- | lib/lib_errors.h | 1 | ||||
| -rw-r--r-- | lib/northbound.c | 345 | ||||
| -rw-r--r-- | lib/northbound.h | 70 | ||||
| -rw-r--r-- | lib/northbound_cli.c | 112 | ||||
| -rw-r--r-- | lib/northbound_confd.c | 269 | ||||
| -rw-r--r-- | lib/northbound_sysrepo.c | 130 | ||||
| -rw-r--r-- | ripd/rip_northbound.c | 13 | ||||
| -rw-r--r-- | tests/.gitignore | 1 | ||||
| -rw-r--r-- | tests/lib/northbound/test_oper_data.c | 466 | ||||
| -rw-r--r-- | tests/lib/northbound/test_oper_data.in | 1 | ||||
| -rw-r--r-- | tests/lib/northbound/test_oper_data.py | 4 | ||||
| -rw-r--r-- | tests/lib/northbound/test_oper_data.refout | 119 | ||||
| -rw-r--r-- | tests/subdir.am | 9 | ||||
| -rw-r--r-- | tools/gen_northbound_callbacks.c | 9 | ||||
| -rw-r--r-- | yang/frr-test-module.yang | 59 | ||||
| -rw-r--r-- | yang/subdir.am | 1 | 
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  | 
