From 6b06c1e97f967e5ec2294e996e1c06df4c89d2f1 Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Mon, 6 Jan 2025 07:52:31 -0500 Subject: [PATCH] lib: northbound/mgmtd: add backend model support Signed-off-by: Christian Hopps --- lib/mgmt_be_client.c | 213 +++++++++++++++++++++++++++++++++++++--- lib/mgmt_be_client.h | 2 + lib/northbound.h | 10 ++ lib/northbound_oper.c | 81 +++++++++++++++ lib/subdir.am | 1 + mgmtd/mgmt_be_adapter.c | 11 +++ mgmtd/mgmt_main.c | 7 ++ ripd/rip_main.c | 1 + ripngd/ripng_main.c | 1 + staticd/static_main.c | 1 + yang/frr-backend.yang | 102 +++++++++++++++++++ zebra/main.c | 1 + 12 files changed, 417 insertions(+), 14 deletions(-) create mode 100644 yang/frr-backend.yang diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 2c7db4405a34..6263772563c0 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -99,12 +99,12 @@ struct mgmt_be_client { struct nb_config *candidate_config; struct nb_config *running_config; - unsigned long num_edit_nb_cfg; - unsigned long avg_edit_nb_cfg_tm; - unsigned long num_prep_nb_cfg; - unsigned long avg_prep_nb_cfg_tm; - unsigned long num_apply_nb_cfg; - unsigned long avg_apply_nb_cfg_tm; + uint64_t num_edit_nb_cfg; + uint64_t avg_edit_nb_cfg_tm; + uint64_t num_prep_nb_cfg; + uint64_t avg_prep_nb_cfg_tm; + uint64_t num_apply_nb_cfg; + uint64_t avg_apply_nb_cfg_tm; struct mgmt_be_txns_head txn_head; @@ -117,7 +117,8 @@ struct mgmt_be_client { struct debug mgmt_dbg_be_client = { .conf = "debug mgmt client backend", - .desc = "Management backend client operations" + .desc = "Management backend client operations", + .flags = DEBUG_MODE_ALL, }; /* NOTE: only one client per proc for now. */ @@ -622,7 +623,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, 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_prep_nb_cfg_tm, (uint32_t)num_processed); @@ -771,10 +772,9 @@ static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) gettimeofday(&apply_nb_cfg_end, NULL); 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->num_apply_nb_cfg) + - apply_nb_cfg_tm) / - (client_ctx->num_apply_nb_cfg + 1); + client_ctx->avg_apply_nb_cfg_tm = + ((client_ctx->avg_apply_nb_cfg_tm * client_ctx->num_apply_nb_cfg) + apply_nb_cfg_tm) / + (client_ctx->num_apply_nb_cfg + 1); client_ctx->num_apply_nb_cfg++; txn->nb_txn = NULL; @@ -790,8 +790,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); - debug_be_client("Nb-apply-duration %lu (avg: %lu) uSec", - apply_nb_cfg_tm, client_ctx->avg_apply_nb_cfg_tm); + debug_be_client("Nb-apply-duration %lu (avg: %Lu) uSec", apply_nb_cfg_tm, + client_ctx->avg_apply_nb_cfg_tm); return 0; } @@ -1334,6 +1334,191 @@ DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd, 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_cbs *cbs, uintptr_t user_data, diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h index a3e3896d5295..5e78f0f433a7 100644 --- a/lib/mgmt_be_client.h +++ b/lib/mgmt_be_client.h @@ -85,6 +85,8 @@ struct mgmt_be_client_cbs { extern struct debug mgmt_dbg_be_client; +extern const struct frr_yang_module_info frr_backend_info; + /*************************************************************** * API prototypes ***************************************************************/ diff --git a/lib/northbound.h b/lib/northbound.h index bd802e0888f9..ce59bfd01a43 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -836,6 +836,9 @@ extern struct debug nb_dbg_libyang; /* Global running configuration. */ extern struct nb_config *running_config; +/* Global notification filters */ +extern const char **nb_notif_filters; + /* Wrappers for the northbound callbacks. */ 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); +/* 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. * diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c index a296b147acc3..6336db502aae 100644 --- a/lib/northbound_oper.c +++ b/lib/northbound_oper.c @@ -1918,6 +1918,87 @@ enum nb_error nb_oper_iterate_legacy(const char *xpath, 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) { event_loop = loop; diff --git a/lib/subdir.am b/lib/subdir.am index 984e92ff17fd..a975eb2fc46f 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -145,6 +145,7 @@ lib_libfrr_la_SOURCES = \ nodist_lib_libfrr_la_SOURCES = \ yang/frr-affinity-map.yang.c \ + yang/frr-backend.yang.c \ yang/frr-filter.yang.c \ yang/frr-if-rmap.yang.c \ yang/frr-interface.yang.c \ diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index e159d68ec0e5..1c32f936b022 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -77,6 +77,7 @@ static const char *const zebra_config_xpaths[] = { }; static const char *const zebra_oper_xpaths[] = { + "/frr-backend:clients", "/frr-interface:lib/interface", "/frr-vrf:lib/vrf/frr-zebra:zebra", "/frr-zebra:zebra", @@ -94,6 +95,7 @@ static const char *const ripd_config_xpaths[] = { NULL, }; static const char *const ripd_oper_xpaths[] = { + "/frr-backend:clients", "/frr-ripd:ripd", "/ietf-key-chain:key-chains", NULL, @@ -114,6 +116,7 @@ static const char *const ripngd_config_xpaths[] = { NULL, }; static const char *const ripngd_oper_xpaths[] = { + "/frr-backend:clients", "/frr-ripngd:ripngd", NULL, }; @@ -130,6 +133,11 @@ static const char *const staticd_config_xpaths[] = { "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd", NULL, }; + +static const char *const staticd_oper_xpaths[] = { + "/frr-backend:clients", + NULL, +}; #endif 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 #ifdef HAVE_RIPNGD [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths, +#endif +#ifdef HAVE_STATICD + [MGMTD_BE_CLIENT_ID_STATICD] = staticd_oper_xpaths, #endif [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths, }; diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 1880d94415fa..7d909446c3bf 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -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 * 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 = { .name = "frr-zebra-route-map", .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. */ + &frr_backend_client_info, &frr_zebra_cli_info, &zebra_route_map_info, diff --git a/ripd/rip_main.c b/ripd/rip_main.c index 67469f5fe52c..cfe4a7e43762 100644 --- a/ripd/rip_main.c +++ b/ripd/rip_main.c @@ -127,6 +127,7 @@ static struct frr_signal_t ripd_signals[] = { }; static const struct frr_yang_module_info *const ripd_yang_modules[] = { + &frr_backend_info, &frr_filter_info, &frr_interface_info, &frr_ripd_info, diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c index ada9ad4e78b9..b3584b9c3a7e 100644 --- a/ripngd/ripng_main.c +++ b/ripngd/ripng_main.c @@ -120,6 +120,7 @@ struct frr_signal_t ripng_signals[] = { }; static const struct frr_yang_module_info *const ripngd_yang_modules[] = { + &frr_backend_info, &frr_filter_info, &frr_interface_info, &frr_ripngd_info, diff --git a/staticd/static_main.c b/staticd/static_main.c index 9468a98b833e..5e74326e38be 100644 --- a/staticd/static_main.c +++ b/staticd/static_main.c @@ -107,6 +107,7 @@ struct frr_signal_t static_signals[] = { }; static const struct frr_yang_module_info *const staticd_yang_modules[] = { + &frr_backend_info, &frr_interface_info, &frr_vrf_info, &frr_routing_info, diff --git a/yang/frr-backend.yang b/yang/frr-backend.yang new file mode 100644 index 000000000000..7149cbb991e0 --- /dev/null +++ b/yang/frr-backend.yang @@ -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: + FRR Development List: "; + 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."; + } + } + } + } +} diff --git a/zebra/main.c b/zebra/main.c index 47b4a44082b9..d189d1e0a033 100644 --- a/zebra/main.c +++ b/zebra/main.c @@ -287,6 +287,7 @@ struct frr_signal_t zebra_signals[] = { /* clang-format off */ static const struct frr_yang_module_info *const zebra_yang_modules[] = { + &frr_backend_info, &frr_filter_info, &frr_interface_info, &frr_route_map_info,