+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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>
struct nb_config_change *change,
char *errmsg, size_t errmsg_len);
static struct nb_transaction *
-nb_transaction_new(struct nb_context *context, struct nb_config *config,
+nb_transaction_new(struct nb_context context, struct nb_config *config,
struct nb_config_cbs *changes, const char *comment,
char *errmsg, size_t errmsg_len);
static void nb_transaction_free(struct nb_transaction *transaction);
{
struct nb_node *nb_node;
struct lysc_node *sparent, *sparent_list;
+ struct frr_yang_module_info *module;
+ module = (struct frr_yang_module_info *)arg;
nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));
yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,
sizeof(nb_node->xpath));
assert(snode->priv == NULL);
((struct lysc_node *)snode)->priv = nb_node;
+ if (module && module->ignore_cbs)
+ SET_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS);
+
return YANG_ITER_CONTINUE;
}
* Use libyang to find the schema node associated to the path and get
* the northbound node from there (snode private pointer).
*/
- snode = lys_find_path(ly_native_ctx, NULL, path, 0);
+ snode = yang_find_snode(ly_native_ctx, path, 0);
if (!snode)
return NULL;
{
unsigned int error = 0;
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return error;
+
error += nb_node_validate_cb(nb_node, NB_OP_CREATE,
!!nb_node->cbs.create, false);
error += nb_node_validate_cb(nb_node, NB_OP_MODIFY,
config->dnode = yang_dnode_new(ly_native_ctx, true);
config->version = 0;
+ RB_INIT(nb_config_cbs, &config->cfg_chgs);
+
return config;
}
{
if (config->dnode)
yang_dnode_free(config->dnode);
+ nb_config_diff_del_changes(&config->cfg_chgs);
XFREE(MTYPE_NB_CONFIG, config);
}
dup->dnode = yang_dnode_dup(config->dnode);
dup->version = config->version;
+ RB_INIT(nb_config_cbs, &dup->cfg_chgs);
+
return dup;
}
RB_INSERT(nb_config_cbs, changes, &change->cb);
}
-static void nb_config_diff_del_changes(struct nb_config_cbs *changes)
+void nb_config_diff_del_changes(struct nb_config_cbs *changes)
{
while (!RB_EMPTY(nb_config_cbs, changes)) {
struct nb_config_change *change;
* configurations. Given a new subtree, calculate all new YANG data nodes,
* excluding default leafs and leaf-lists. This is a recursive function.
*/
-static void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq,
- struct nb_config_cbs *changes)
+void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq,
+ struct nb_config_cbs *changes)
{
enum nb_operation operation;
struct lyd_node *child;
}
#endif
-/* Calculate the delta between two different configurations. */
-static void nb_config_diff(const struct nb_config *config1,
- const struct nb_config *config2,
- struct nb_config_cbs *changes)
+/*
+ * Calculate the delta between two different configurations.
+ *
+ * NOTE: 'config1' is the reference DB, while 'config2' is
+ * the DB being compared against 'config1'. Typically 'config1'
+ * should be the Running DB and 'config2' is the Candidate DB.
+ */
+void nb_config_diff(const struct nb_config *config1,
+ const struct nb_config *config2,
+ struct nb_config_cbs *changes)
{
struct lyd_node *diff = NULL;
const struct lyd_node *root, *dnode;
return NB_OK;
}
+static void nb_update_candidate_changes(struct nb_config *candidate,
+ struct nb_cfg_change *change,
+ uint32_t *seq)
+{
+ enum nb_operation oper = change->operation;
+ char *xpath = change->xpath;
+ struct lyd_node *root = NULL;
+ struct lyd_node *dnode;
+ struct nb_config_cbs *cfg_chgs = &candidate->cfg_chgs;
+ int op;
+
+ switch (oper) {
+ case NB_OP_CREATE:
+ case NB_OP_MODIFY:
+ root = yang_dnode_get(candidate->dnode, xpath);
+ break;
+ case NB_OP_DESTROY:
+ root = yang_dnode_get(running_config->dnode, xpath);
+ /* code */
+ break;
+ case NB_OP_MOVE:
+ case NB_OP_PRE_VALIDATE:
+ case NB_OP_APPLY_FINISH:
+ case NB_OP_GET_ELEM:
+ case NB_OP_GET_NEXT:
+ case NB_OP_GET_KEYS:
+ case NB_OP_LOOKUP_ENTRY:
+ case NB_OP_RPC:
+ break;
+ default:
+ assert(!"non-enum value, invalid");
+ }
+
+ if (!root)
+ return;
+
+ LYD_TREE_DFS_BEGIN (root, dnode) {
+ op = nb_lyd_diff_get_op(dnode);
+ switch (op) {
+ case 'c':
+ nb_config_diff_created(dnode, seq, cfg_chgs);
+ LYD_TREE_DFS_continue = 1;
+ break;
+ case 'd':
+ nb_config_diff_deleted(dnode, seq, cfg_chgs);
+ LYD_TREE_DFS_continue = 1;
+ break;
+ case 'r':
+ nb_config_diff_add_change(cfg_chgs, NB_OP_MODIFY, seq,
+ dnode);
+ break;
+ default:
+ break;
+ }
+ LYD_TREE_DFS_END(root, dnode);
+ }
+}
+
+static bool nb_is_operation_allowed(struct nb_node *nb_node,
+ struct nb_cfg_change *change)
+{
+ enum nb_operation oper = change->operation;
+
+ if (lysc_is_key(nb_node->snode)) {
+ if (oper == NB_OP_MODIFY || oper == NB_OP_DESTROY)
+ return false;
+ }
+ return true;
+}
+
+void nb_candidate_edit_config_changes(
+ struct nb_config *candidate_config, struct nb_cfg_change cfg_changes[],
+ size_t num_cfg_changes, const char *xpath_base, const char *curr_xpath,
+ int xpath_index, char *err_buf, int err_bufsize, bool *error)
+{
+ uint32_t seq = 0;
+
+ if (error)
+ *error = false;
+
+ if (xpath_base == NULL)
+ xpath_base = "";
+
+ /* Edit candidate configuration. */
+ for (size_t i = 0; i < num_cfg_changes; i++) {
+ struct nb_cfg_change *change = &cfg_changes[i];
+ struct nb_node *nb_node;
+ char xpath[XPATH_MAXLEN];
+ struct yang_data *data;
+ int ret;
+
+ /* Handle relative XPaths. */
+ memset(xpath, 0, sizeof(xpath));
+ if (xpath_index > 0 &&
+ (xpath_base[0] == '.' || change->xpath[0] == '.'))
+ strlcpy(xpath, curr_xpath, sizeof(xpath));
+ if (xpath_base[0]) {
+ if (xpath_base[0] == '.')
+ strlcat(xpath, xpath_base + 1, sizeof(xpath));
+ else
+ strlcat(xpath, xpath_base, sizeof(xpath));
+ }
+ if (change->xpath[0] == '.')
+ strlcat(xpath, change->xpath + 1, sizeof(xpath));
+ else
+ strlcpy(xpath, change->xpath, sizeof(xpath));
+
+ /* Find the northbound node associated to the data path. */
+ nb_node = nb_node_find(xpath);
+ if (!nb_node) {
+ flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
+ "%s: unknown data path: %s", __func__, xpath);
+ if (error)
+ *error = true;
+ continue;
+ }
+ /* Find if the node to be edited is not a key node */
+ if (!nb_is_operation_allowed(nb_node, change)) {
+ zlog_err(" Xpath %s points to key node", xpath);
+ if (error)
+ *error = true;
+ break;
+ }
+
+ /* If the value is not set, get the default if it exists. */
+ if (change->value == NULL)
+ change->value = yang_snode_get_default(nb_node->snode);
+ data = yang_data_new(xpath, change->value);
+
+ /*
+ * Ignore "not found" errors when editing the candidate
+ * configuration.
+ */
+ ret = nb_candidate_edit(candidate_config, nb_node,
+ change->operation, xpath, NULL, data);
+ yang_data_free(data);
+ if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) {
+ flog_warn(
+ EC_LIB_NB_CANDIDATE_EDIT_ERROR,
+ "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
+ __func__, nb_operation_name(change->operation),
+ xpath);
+ if (error)
+ *error = true;
+ continue;
+ }
+ nb_update_candidate_changes(candidate_config, change, &seq);
+ }
+
+ if (error && *error) {
+ char buf[BUFSIZ];
+
+ /*
+ * Failure to edit the candidate configuration should never
+ * happen in practice, unless there's a bug in the code. When
+ * that happens, log the error but otherwise ignore it.
+ */
+ snprintf(err_buf, err_bufsize,
+ "%% Failed to edit configuration.\n\n%s",
+ yang_print_errors(ly_native_ctx, buf, sizeof(buf)));
+ }
+}
+
bool nb_candidate_needs_update(const struct nb_config *candidate)
{
if (candidate->version < running_config->version)
* WARNING: lyd_validate() can change the configuration as part of the
* validation process.
*/
-static int nb_candidate_validate_yang(struct nb_config *candidate, char *errmsg,
- size_t errmsg_len)
+int nb_candidate_validate_yang(struct nb_config *candidate, bool no_state,
+ char *errmsg, size_t errmsg_len)
{
if (lyd_validate_all(&candidate->dnode, ly_native_ctx,
- LYD_VALIDATE_NO_STATE, NULL)
- != 0) {
+ no_state ? LYD_VALIDATE_NO_STATE
+ : LYD_VALIDATE_PRESENT,
+ NULL) != 0) {
yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
return NB_ERR_VALIDATION;
}
}
/* Perform code-level validation using the northbound callbacks. */
-static int nb_candidate_validate_code(struct nb_context *context,
- struct nb_config *candidate,
- struct nb_config_cbs *changes,
- char *errmsg, size_t errmsg_len)
+int nb_candidate_validate_code(struct nb_context *context,
+ struct nb_config *candidate,
+ struct nb_config_cbs *changes, char *errmsg,
+ size_t errmsg_len)
{
struct nb_config_cb *cb;
struct lyd_node *root, *child;
return NB_OK;
}
+int nb_candidate_diff_and_validate_yang(struct nb_context *context,
+ struct nb_config *candidate,
+ struct nb_config_cbs *changes,
+ char *errmsg, size_t errmsg_len)
+{
+ if (nb_candidate_validate_yang(candidate, true, errmsg,
+ sizeof(errmsg_len)) != NB_OK)
+ return NB_ERR_VALIDATION;
+
+ RB_INIT(nb_config_cbs, changes);
+ nb_config_diff(running_config, candidate, changes);
+
+ return NB_OK;
+}
+
int nb_candidate_validate(struct nb_context *context,
struct nb_config *candidate, char *errmsg,
size_t errmsg_len)
struct nb_config_cbs changes;
int ret;
- if (nb_candidate_validate_yang(candidate, errmsg, errmsg_len) != NB_OK)
- return NB_ERR_VALIDATION;
+ ret = nb_candidate_diff_and_validate_yang(context, candidate, &changes,
+ errmsg, errmsg_len);
+ if (ret != NB_OK)
+ return ret;
- RB_INIT(nb_config_cbs, &changes);
- nb_config_diff(running_config, candidate, &changes);
ret = nb_candidate_validate_code(context, candidate, &changes, errmsg,
errmsg_len);
nb_config_diff_del_changes(&changes);
return ret;
}
-int nb_candidate_commit_prepare(struct nb_context *context,
+int nb_candidate_commit_prepare(struct nb_context context,
struct nb_config *candidate,
const char *comment,
struct nb_transaction **transaction,
+ bool skip_validate, bool ignore_zero_change,
char *errmsg, size_t errmsg_len)
{
struct nb_config_cbs changes;
- if (nb_candidate_validate_yang(candidate, errmsg, errmsg_len)
- != NB_OK) {
+ if (!skip_validate &&
+ nb_candidate_validate_yang(candidate, true, errmsg, errmsg_len) !=
+ NB_OK) {
flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
"%s: failed to validate candidate configuration",
__func__);
RB_INIT(nb_config_cbs, &changes);
nb_config_diff(running_config, candidate, &changes);
- if (RB_EMPTY(nb_config_cbs, &changes)) {
+ if (!ignore_zero_change && RB_EMPTY(nb_config_cbs, &changes)) {
snprintf(
errmsg, errmsg_len,
"No changes to apply were found during preparation phase");
return NB_ERR_NO_CHANGES;
}
- if (nb_candidate_validate_code(context, candidate, &changes, errmsg,
- errmsg_len)
- != NB_OK) {
+ if (!skip_validate &&
+ nb_candidate_validate_code(&context, candidate, &changes, errmsg,
+ errmsg_len) != NB_OK) {
flog_warn(EC_LIB_NB_CANDIDATE_INVALID,
"%s: failed to validate candidate configuration",
__func__);
return NB_ERR_VALIDATION;
}
- *transaction = nb_transaction_new(context, candidate, &changes, comment,
- errmsg, errmsg_len);
+ /*
+ * Re-use an existing transaction if provided. Else allocate a new one.
+ */
+ if (!*transaction)
+ *transaction = nb_transaction_new(context, candidate, &changes,
+ comment, errmsg, errmsg_len);
if (*transaction == NULL) {
flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
"%s: failed to create transaction: %s", __func__,
nb_transaction_free(transaction);
}
-int nb_candidate_commit(struct nb_context *context, struct nb_config *candidate,
+int nb_candidate_commit(struct nb_context context, struct nb_config *candidate,
bool save_transaction, const char *comment,
uint32_t *transaction_id, char *errmsg,
size_t errmsg_len)
int ret;
ret = nb_candidate_commit_prepare(context, candidate, comment,
- &transaction, errmsg, errmsg_len);
+ &transaction, false, false, errmsg,
+ errmsg_len);
/*
* Apply the changes if the preparation phase succeeded. Otherwise abort
* the transaction.
bool unexpected_error = false;
int ret;
+ assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS));
+
nb_log_config_callback(event, NB_OP_CREATE, dnode);
args.context = context;
bool unexpected_error = false;
int ret;
+ assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS));
+
nb_log_config_callback(event, NB_OP_MODIFY, dnode);
args.context = context;
bool unexpected_error = false;
int ret;
+ assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS));
+
nb_log_config_callback(event, NB_OP_DESTROY, dnode);
args.context = context;
bool unexpected_error = false;
int ret;
+ assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS));
+
nb_log_config_callback(event, NB_OP_MOVE, dnode);
args.context = context;
bool unexpected_error = false;
int ret;
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return 0;
+
nb_log_config_callback(NB_EV_VALIDATE, NB_OP_PRE_VALIDATE, dnode);
args.dnode = dnode;
{
struct nb_cb_apply_finish_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return;
+
nb_log_config_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH, dnode);
args.context = context;
{
struct nb_cb_get_elem_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return NULL;
+
DEBUGD(&nb_dbg_cbs_state,
"northbound callback (get_elem): xpath [%s] list_entry [%p]",
xpath, list_entry);
{
struct nb_cb_get_next_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return NULL;
+
DEBUGD(&nb_dbg_cbs_state,
"northbound callback (get_next): node [%s] parent_list_entry [%p] list_entry [%p]",
nb_node->xpath, parent_list_entry, list_entry);
{
struct nb_cb_get_keys_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return 0;
+
DEBUGD(&nb_dbg_cbs_state,
"northbound callback (get_keys): node [%s] list_entry [%p]",
nb_node->xpath, list_entry);
{
struct nb_cb_lookup_entry_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return NULL;
+
DEBUGD(&nb_dbg_cbs_state,
"northbound callback (lookup_entry): node [%s] parent_list_entry [%p]",
nb_node->xpath, parent_list_entry);
{
struct nb_cb_rpc_args args = {};
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return 0;
+
DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath);
args.xpath = xpath;
union nb_resource *resource;
int ret = NB_ERR;
+ if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CBS))
+ return NB_OK;
+
if (event == NB_EV_VALIDATE)
resource = NULL;
else
}
static struct nb_transaction *
-nb_transaction_new(struct nb_context *context, struct nb_config *config,
+nb_transaction_new(struct nb_context context, struct nb_config *config,
struct nb_config_cbs *changes, const char *comment,
char *errmsg, size_t errmsg_len)
{
struct nb_transaction *transaction;
- if (nb_running_lock_check(context->client, context->user)) {
+ if (nb_running_lock_check(context.client, context.user)) {
strlcpy(errmsg,
"running configuration is locked by another client",
errmsg_len);
break;
/* Call the appropriate callback. */
- ret = nb_callback_configuration(transaction->context, event,
+ ret = nb_callback_configuration(&transaction->context, event,
change, errmsg, errmsg_len);
switch (event) {
case NB_EV_PREPARE:
/* Call the 'apply_finish' callbacks, sorted by their priorities. */
RB_FOREACH (cb, nb_config_cbs, &cbs)
- nb_callback_apply_finish(transaction->context, cb->nb_node,
+ nb_callback_apply_finish(&transaction->context, cb->nb_node,
cb->dnode, errmsg, errmsg_len);
/* Release memory. */
/* Iterate over all list entries. */
do {
const struct lysc_node_leaf *skey;
- struct yang_list_keys list_keys;
+ struct yang_list_keys list_keys = {};
char xpath[XPATH_MAXLEN * 2];
int ret;
* all YANG lists (if any).
*/
- LY_ERR err = lyd_new_path(NULL, ly_native_ctx, xpath, NULL,
- LYD_NEW_PATH_UPDATE, &dnode);
+ LY_ERR err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0,
+ LYD_NEW_PATH_UPDATE, NULL, &dnode);
if (err || !dnode) {
const char *errmsg =
err ? ly_errmsg(ly_native_ctx) : "node not found";
return "gRPC";
case NB_CLIENT_PCEP:
return "Pcep";
+ case NB_CLIENT_MGMTD_SERVER:
+ return "MGMTD Server";
+ case NB_CLIENT_MGMTD_BE:
+ return "MGMT Backend";
case NB_CLIENT_NONE:
return "None";
}
static void nb_load_callbacks(const struct frr_yang_module_info *module)
{
+
+ if (module->ignore_cbs)
+ return;
+
for (size_t i = 0; module->nodes[i].xpath; i++) {
struct nb_node *nb_node;
uint32_t priority;
}
-void nb_init(struct thread_master *tm,
+void nb_init(struct event_loop *tm,
const struct frr_yang_module_info *const modules[],
size_t nmodules, bool db_enabled)
{
/* Initialize the compiled nodes with northbound data */
for (size_t i = 0; i < nmodules; i++) {
- yang_snodes_iterate(loaded[i]->info, nb_node_new_cb, 0, NULL);
+ yang_snodes_iterate(loaded[i]->info, nb_node_new_cb, 0,
+ (void *)modules[i]);
nb_load_callbacks(modules[i]);
}
nb_nodes_delete();
/* Delete the running configuration. */
- hash_clean(running_config_entries, running_config_entry_free);
- hash_free(running_config_entries);
+ hash_clean_and_free(&running_config_entries, running_config_entry_free);
nb_config_free(running_config);
pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
}