lib: northbound/mgmtd: add backend model support

Signed-off-by: Christian Hopps <chopps@labn.net>
This commit is contained in:
Christian Hopps 2025-01-06 07:52:31 -05:00
parent 5f35096123
commit 5f2a927d7b
13 changed files with 416 additions and 14 deletions

View file

@ -99,12 +99,12 @@ struct mgmt_be_client {
struct nb_config *candidate_config; struct nb_config *candidate_config;
struct nb_config *running_config; struct nb_config *running_config;
unsigned long num_edit_nb_cfg; uint64_t num_edit_nb_cfg;
unsigned long avg_edit_nb_cfg_tm; uint64_t avg_edit_nb_cfg_tm;
unsigned long num_prep_nb_cfg; uint64_t num_prep_nb_cfg;
unsigned long avg_prep_nb_cfg_tm; uint64_t avg_prep_nb_cfg_tm;
unsigned long num_apply_nb_cfg; uint64_t num_apply_nb_cfg;
unsigned long avg_apply_nb_cfg_tm; uint64_t avg_apply_nb_cfg_tm;
struct mgmt_be_txns_head txn_head; struct mgmt_be_txns_head txn_head;
@ -117,7 +117,7 @@ struct mgmt_be_client {
struct debug mgmt_dbg_be_client = { struct debug mgmt_dbg_be_client = {
.conf = "debug mgmt client backend", .conf = "debug mgmt client backend",
.desc = "Management backend client operations" .desc = "Management backend client operations",
}; };
/* NOTE: only one client per proc for now. */ /* NOTE: only one client per proc for now. */
@ -621,7 +621,7 @@ static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn)
mgmt_be_send_cfgdata_create_reply(client_ctx, txn->txn_id, mgmt_be_send_cfgdata_create_reply(client_ctx, txn->txn_id,
error ? false : true, error ? err_buf : NULL); error ? false : true, error ? err_buf : NULL);
debug_be_client("Avg-nb-edit-duration %lu uSec, nb-prep-duration %lu (avg: %lu) uSec, batch size %u", debug_be_client("Avg-nb-edit-duration %Lu uSec, nb-prep-duration %lu (avg: %Lu) uSec, batch size %u",
client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm, client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm,
client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed); client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed);
@ -770,10 +770,9 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
gettimeofday(&apply_nb_cfg_end, NULL); gettimeofday(&apply_nb_cfg_end, NULL);
apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start); apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start);
client_ctx->avg_apply_nb_cfg_tm = ((client_ctx->avg_apply_nb_cfg_tm * client_ctx->avg_apply_nb_cfg_tm =
client_ctx->num_apply_nb_cfg) + ((client_ctx->avg_apply_nb_cfg_tm * client_ctx->num_apply_nb_cfg) + apply_nb_cfg_tm) /
apply_nb_cfg_tm) / (client_ctx->num_apply_nb_cfg + 1);
(client_ctx->num_apply_nb_cfg + 1);
client_ctx->num_apply_nb_cfg++; client_ctx->num_apply_nb_cfg++;
txn->nb_txn = NULL; txn->nb_txn = NULL;
@ -789,8 +788,8 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn)
mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL);
debug_be_client("Nb-apply-duration %lu (avg: %lu) uSec", debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm,
apply_nb_cfg_tm, client_ctx->avg_apply_nb_cfg_tm); client_ctx->avg_apply_nb_cfg_tm);
return 0; return 0;
} }
@ -1333,6 +1332,190 @@ DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
/*
* XPath: /frr-backend:clients/client
*
* We only implement a list of one entry (for the this backend client) the
* results will be merged inside mgmtd.
*/
static const void *clients_client_get_next(struct nb_cb_get_next_args *args)
{
if (args->list_entry == NULL)
return __be_client;
return NULL;
}
static int clients_client_get_keys(struct nb_cb_get_keys_args *args)
{
args->keys->num = 1;
strlcpy(args->keys->key[0], __be_client->name, sizeof(args->keys->key[0]));
return NB_OK;
}
static const void *clients_client_lookup_entry(struct nb_cb_lookup_entry_args *args)
{
const char *name = args->keys->key[0];
if (!strcmp(name, __be_client->name))
return __be_client;
return NULL;
}
/*
* XPath: /frr-backend:clients/client/name
*/
static enum nb_error clients_client_name_get(const struct nb_node *nb_node,
const void *parent_list_entry, struct lyd_node *parent)
{
const struct lysc_node *snode = nb_node->snode;
LY_ERR err;
err = lyd_new_term(parent, snode->module, snode->name, __be_client->name, false, NULL);
if (err != LY_SUCCESS)
return NB_ERR_RESOURCE;
return NB_OK;
}
/*
* XPath: /frr-backend:clients/client/state/candidate-config-version
*/
static enum nb_error clients_client_state_candidate_config_version_get(
const struct nb_node *nb_node, const void *parent_list_entry, struct lyd_node *parent)
{
const struct lysc_node *snode = nb_node->snode;
uint64_t value = __be_client->candidate_config->version;
if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
LYD_NEW_PATH_UPDATE, NULL))
return NB_ERR_RESOURCE;
return NB_OK;
}
/*
* XPath: /frr-backend:clients/client/state/running-config-version
*/
static enum nb_error clients_client_state_running_config_version_get(const struct nb_node *nb_node,
const void *parent_list_entry,
struct lyd_node *parent)
{
const struct lysc_node *snode = nb_node->snode;
uint64_t value = __be_client->running_config->version;
if (lyd_new_term_bin(parent, snode->module, snode->name, &value, sizeof(value),
LYD_NEW_PATH_UPDATE, NULL))
return NB_ERR_RESOURCE;
return NB_OK;
}
/*
* XPath: /frr-backend:clients/client/state/notify-selectors
*
* Is this better in northbound_notif.c? Let's decide when we add more to this module.
*/
static enum nb_error clients_client_state_notify_selectors_get(const struct nb_node *nb_node,
const void *parent_list_entry,
struct lyd_node *parent)
{
const struct lysc_node *snode = nb_node->snode;
const char **p;
LY_ERR err;
darr_foreach_p (nb_notif_filters, p) {
err = lyd_new_term(parent, snode->module, snode->name, *p, false, NULL);
if (err != LY_SUCCESS)
return NB_ERR_RESOURCE;
}
return NB_OK;
}
/* clang-format off */
const struct frr_yang_module_info frr_backend_info = {
.name = "frr-backend",
.nodes = {
{
.xpath = "/frr-backend:clients/client",
.cbs = {
.get_next = clients_client_get_next,
.get_keys = clients_client_get_keys,
.lookup_entry = clients_client_lookup_entry,
}
},
{
.xpath = "/frr-backend:clients/client/name",
.cbs.get = clients_client_name_get,
},
{
.xpath = "/frr-backend:clients/client/state/candidate-config-version",
.cbs = {
.get = clients_client_state_candidate_config_version_get,
}
},
{
.xpath = "/frr-backend:clients/client/state/running-config-version",
.cbs = {
.get = clients_client_state_running_config_version_get,
}
},
{
.xpath = "/frr-backend:clients/client/state/edit-count",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_edit_nb_cfg),
}
},
{
.xpath = "/frr-backend:clients/client/state/avg-edit-time",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_edit_nb_cfg_tm),
}
},
{
.xpath = "/frr-backend:clients/client/state/prep-count",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_prep_nb_cfg),
}
},
{
.xpath = "/frr-backend:clients/client/state/avg-prep-time",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_prep_nb_cfg_tm),
}
},
{
.xpath = "/frr-backend:clients/client/state/apply-count",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, num_apply_nb_cfg),
}
},
{
.xpath = "/frr-backend:clients/client/state/avg-apply-time",
.cbs = {
.get = nb_oper_uint64_get,
.get_elem = (void *)(intptr_t)offsetof(struct mgmt_be_client, avg_apply_nb_cfg_tm),
}
},
{
.xpath = "/frr-backend:clients/client/state/notify-selectors",
.cbs.get = clients_client_state_notify_selectors_get,
},
{
.xpath = NULL,
},
}
};
/* clang-format on */
struct mgmt_be_client *mgmt_be_client_create(const char *client_name, struct mgmt_be_client *mgmt_be_client_create(const char *client_name,
struct mgmt_be_client_cbs *cbs, struct mgmt_be_client_cbs *cbs,
uintptr_t user_data, uintptr_t user_data,

View file

@ -85,6 +85,8 @@ struct mgmt_be_client_cbs {
extern struct debug mgmt_dbg_be_client; extern struct debug mgmt_dbg_be_client;
extern const struct frr_yang_module_info frr_backend_info;
/*************************************************************** /***************************************************************
* API prototypes * API prototypes
***************************************************************/ ***************************************************************/

View file

@ -836,6 +836,9 @@ extern struct debug nb_dbg_libyang;
/* Global running configuration. */ /* Global running configuration. */
extern struct nb_config *running_config; extern struct nb_config *running_config;
/* Global notification filters */
extern const char **nb_notif_filters;
/* Wrappers for the northbound callbacks. */ /* Wrappers for the northbound callbacks. */
extern struct yang_data *nb_callback_has_new_get_elem(const struct nb_node *nb_node); extern struct yang_data *nb_callback_has_new_get_elem(const struct nb_node *nb_node);
@ -1521,6 +1524,13 @@ extern void *nb_oper_walk_finish_arg(void *walk);
*/ */
extern void *nb_oper_walk_cb_arg(void *walk); extern void *nb_oper_walk_cb_arg(void *walk);
/* Generic getter functions */
extern enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node,
const void *parent_list_entry, struct lyd_node *parent);
extern enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node,
const void *parent_list_entry, struct lyd_node *parent);
/* /*
* Validate if the northbound callback operation is valid for the given node. * Validate if the northbound callback operation is valid for the given node.
* *

View file

@ -1918,6 +1918,87 @@ enum nb_error nb_oper_iterate_legacy(const char *xpath,
return ret; return ret;
} }
static const char *__adjust_ptr(struct lysc_node_leaf *lsnode, const char *valuep, size_t *size)
{
switch (lsnode->type->basetype) {
case LY_TYPE_INT8:
case LY_TYPE_UINT8:
#ifdef BIG_ENDIAN
valuep += 7;
#endif
*size = 1;
break;
case LY_TYPE_INT16:
case LY_TYPE_UINT16:
#ifdef BIG_ENDIAN
valuep += 6;
#endif
*size = 2;
break;
case LY_TYPE_INT32:
case LY_TYPE_UINT32:
#ifdef BIG_ENDIAN
valuep += 4;
#endif
*size = 4;
break;
case LY_TYPE_INT64:
case LY_TYPE_UINT64:
*size = 8;
break;
case LY_TYPE_UNKNOWN:
case LY_TYPE_BINARY:
case LY_TYPE_STRING:
case LY_TYPE_BITS:
case LY_TYPE_BOOL:
case LY_TYPE_DEC64:
case LY_TYPE_EMPTY:
case LY_TYPE_ENUM:
case LY_TYPE_IDENT:
case LY_TYPE_INST:
case LY_TYPE_LEAFREF:
case LY_TYPE_UNION:
default:
assert(0);
}
return valuep;
}
enum nb_error nb_oper_uint64_get(const struct nb_node *nb_node, const void *parent_list_entry,
struct lyd_node *parent)
{
struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
struct lysc_node *snode = &lsnode->node;
ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
const char *valuep;
size_t size;
valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
NULL))
return NB_ERR_RESOURCE;
return NB_OK;
}
enum nb_error nb_oper_uint32_get(const struct nb_node *nb_node, const void *parent_list_entry,
struct lyd_node *parent)
{
struct lysc_node_leaf *lsnode = (struct lysc_node_leaf *)nb_node->snode;
struct lysc_node *snode = &lsnode->node;
ssize_t offset = (ssize_t)nb_node->cbs.get_elem;
uint64_t ubigval = *(uint64_t *)((char *)parent_list_entry + offset);
const char *valuep;
size_t size;
valuep = __adjust_ptr(lsnode, (const char *)&ubigval, &size);
if (lyd_new_term_bin(parent, snode->module, snode->name, valuep, size, LYD_NEW_PATH_UPDATE,
NULL))
return NB_ERR_RESOURCE;
return NB_OK;
}
void nb_oper_init(struct event_loop *loop) void nb_oper_init(struct event_loop *loop)
{ {
event_loop = loop; event_loop = loop;

View file

@ -145,6 +145,7 @@ lib_libfrr_la_SOURCES = \
nodist_lib_libfrr_la_SOURCES = \ nodist_lib_libfrr_la_SOURCES = \
yang/frr-affinity-map.yang.c \ yang/frr-affinity-map.yang.c \
yang/frr-backend.yang.c \
yang/frr-filter.yang.c \ yang/frr-filter.yang.c \
yang/frr-if-rmap.yang.c \ yang/frr-if-rmap.yang.c \
yang/frr-interface.yang.c \ yang/frr-interface.yang.c \

View file

@ -77,6 +77,7 @@ static const char *const zebra_config_xpaths[] = {
}; };
static const char *const zebra_oper_xpaths[] = { static const char *const zebra_oper_xpaths[] = {
"/frr-backend:clients",
"/frr-interface:lib/interface", "/frr-interface:lib/interface",
"/frr-vrf:lib/vrf/frr-zebra:zebra", "/frr-vrf:lib/vrf/frr-zebra:zebra",
"/frr-zebra:zebra", "/frr-zebra:zebra",
@ -94,6 +95,7 @@ static const char *const ripd_config_xpaths[] = {
NULL, NULL,
}; };
static const char *const ripd_oper_xpaths[] = { static const char *const ripd_oper_xpaths[] = {
"/frr-backend:clients",
"/frr-ripd:ripd", "/frr-ripd:ripd",
"/ietf-key-chain:key-chains", "/ietf-key-chain:key-chains",
NULL, NULL,
@ -114,6 +116,7 @@ static const char *const ripngd_config_xpaths[] = {
NULL, NULL,
}; };
static const char *const ripngd_oper_xpaths[] = { static const char *const ripngd_oper_xpaths[] = {
"/frr-backend:clients",
"/frr-ripngd:ripngd", "/frr-ripngd:ripngd",
NULL, NULL,
}; };
@ -130,6 +133,11 @@ static const char *const staticd_config_xpaths[] = {
"/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd", "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd",
NULL, NULL,
}; };
static const char *const staticd_oper_xpaths[] = {
"/frr-backend:clients",
NULL,
};
#endif #endif
static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
@ -151,6 +159,9 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = {
#endif #endif
#ifdef HAVE_RIPNGD #ifdef HAVE_RIPNGD
[MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths, [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths,
#endif
#ifdef HAVE_STATICD
[MGMTD_BE_CLIENT_ID_STATICD] = staticd_oper_xpaths,
#endif #endif
[MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths, [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths,
}; };

View file

@ -159,6 +159,12 @@ const struct frr_yang_module_info ietf_netconf_with_defaults_info = {
* clients into mgmtd. The modules are used by libyang in order to support * clients into mgmtd. The modules are used by libyang in order to support
* parsing binary data returns from the backend. * parsing binary data returns from the backend.
*/ */
const struct frr_yang_module_info frr_backend_client_info = {
.name = "frr-backend",
.ignore_cfg_cbs = true,
.nodes = { { .xpath = NULL } },
};
const struct frr_yang_module_info zebra_route_map_info = { const struct frr_yang_module_info zebra_route_map_info = {
.name = "frr-zebra-route-map", .name = "frr-zebra-route-map",
.ignore_cfg_cbs = true, .ignore_cfg_cbs = true,
@ -183,6 +189,7 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = {
/* /*
* YANG module info used by backend clients get added here. * YANG module info used by backend clients get added here.
*/ */
&frr_backend_client_info,
&frr_zebra_cli_info, &frr_zebra_cli_info,
&zebra_route_map_info, &zebra_route_map_info,

View file

@ -127,6 +127,7 @@ static struct frr_signal_t ripd_signals[] = {
}; };
static const struct frr_yang_module_info *const ripd_yang_modules[] = { static const struct frr_yang_module_info *const ripd_yang_modules[] = {
&frr_backend_info,
&frr_filter_info, &frr_filter_info,
&frr_interface_info, &frr_interface_info,
&frr_ripd_info, &frr_ripd_info,

View file

@ -120,6 +120,7 @@ struct frr_signal_t ripng_signals[] = {
}; };
static const struct frr_yang_module_info *const ripngd_yang_modules[] = { static const struct frr_yang_module_info *const ripngd_yang_modules[] = {
&frr_backend_info,
&frr_filter_info, &frr_filter_info,
&frr_interface_info, &frr_interface_info,
&frr_ripngd_info, &frr_ripngd_info,

View file

@ -107,6 +107,7 @@ struct frr_signal_t static_signals[] = {
}; };
static const struct frr_yang_module_info *const staticd_yang_modules[] = { static const struct frr_yang_module_info *const staticd_yang_modules[] = {
&frr_backend_info,
&frr_interface_info, &frr_interface_info,
&frr_vrf_info, &frr_vrf_info,
&frr_routing_info, &frr_routing_info,

102
yang/frr-backend.yang Normal file
View file

@ -0,0 +1,102 @@
// SPDX-License-Identifier: BSD-2-Clause
module frr-backend {
yang-version 1.1;
namespace "http://frrouting.org/yang/oper";
prefix frr-backend;
organization
"FRRouting";
contact
"FRR Users List: <mailto:frog@lists.frrouting.org>
FRR Development List: <mailto:dev@lists.frrouting.org>";
description
"This module defines a model for FRR backend management.
Copyright (c) 2024, LabN Consulting, L.L.C.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
revision 2024-12-29 {
description "Initial revision";
reference "FRR source code";
}
container clients {
config false;
description "The backend clients";
list client {
key name;
description "A backend client";
leaf name {
type string;
description "Name of the backend client";
}
container state {
description "FRR backend operational state";
leaf candidate-config-version {
type uint64;
description "Local candidate config version.";
}
leaf running-config-version {
type uint64;
description "Local running config version.";
}
leaf edit-count {
type uint64;
description "Number of config edits handled.";
}
leaf avg-edit-time {
type uint64;
description "Average edit time in microseconds.";
}
leaf prep-count {
type uint64;
description "Number of config preps handled.";
}
leaf avg-prep-time {
type uint64;
description "Average prep time in microseconds.";
}
leaf apply-count {
type uint64;
description "Number of config applies handled.";
}
leaf avg-apply-time {
type uint64;
description "Average apply time in microseconds.";
}
leaf-list notify-selectors {
type string;
description
"List of paths identifying which state to send change
notifications for.";
}
}
}
}
}

View file

@ -20,6 +20,7 @@ EXTRA_DIST += yang/embedmodel.py
# without problems, as seen in libfrr. # without problems, as seen in libfrr.
dist_yangmodels_DATA += yang/frr-affinity-map.yang dist_yangmodels_DATA += yang/frr-affinity-map.yang
dist_yangmodels_DATA += yang/frr-backend.yang
dist_yangmodels_DATA += yang/frr-filter.yang dist_yangmodels_DATA += yang/frr-filter.yang
dist_yangmodels_DATA += yang/frr-module-translator.yang dist_yangmodels_DATA += yang/frr-module-translator.yang
dist_yangmodels_DATA += yang/frr-nexthop.yang dist_yangmodels_DATA += yang/frr-nexthop.yang

View file

@ -287,6 +287,7 @@ struct frr_signal_t zebra_signals[] = {
/* clang-format off */ /* clang-format off */
static const struct frr_yang_module_info *const zebra_yang_modules[] = { static const struct frr_yang_module_info *const zebra_yang_modules[] = {
&frr_backend_info,
&frr_filter_info, &frr_filter_info,
&frr_interface_info, &frr_interface_info,
&frr_route_map_info, &frr_route_map_info,