Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

operational-state (datastore) change notifications #17796

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lib/northbound.h
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,68 @@ extern void nb_oper_init(struct event_loop *loop);
extern void nb_oper_terminate(void);
extern bool nb_oper_is_yang_lib_query(const char *xpath);


/**
* nb_op_update() - Create new state data.
* @tree: subtree @path is relative to or NULL in which case @path must be
* absolute.
* @path: The path of the state node to create.
* @value: The canonical value of the state.
*
* Return: The new libyang node.
*/
extern struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value);

/**
* nb_op_update_delete() - Delete state data.
* @tree: subtree @path is relative to or NULL in which case @path must be
* absolute.
* @path: The path of the state node to delete, or NULL if @tree should just be
* deleted.
*/
extern void nb_op_update_delete(struct lyd_node *tree, const char *path);

/**
* nb_op_update_pathf() - Create new state data.
* @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
* be absolute.
* @path_fmt: The path format string of the state node to create.
* @value: The canonical value of the state.
* @...: The values to substitute into @path_fmt.
*
* Return: The new libyang node.
*/
extern struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt,
const char *value, ...) PRINTFRR(2, 4);
extern struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt,
const char *value, va_list ap);
/**
* nb_op_update_delete_pathf() - Delete state data.
* @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
* be absolute.
* @path: The path of the state node to delete.
* @...: The values to substitute into @path_fmt.
*/
extern void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
PRINTFRR(2, 3);
extern void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);

/**
* nb_op_updatef() - Create new state data.
* @tree: subtree @path is relative to or NULL in which case @path must be
* absolute.
* @path: The path of the state node to create.
* @val_fmt: The value format string to set the canonical value of the state.
* @...: The values to substitute into @val_fmt.
*
* Return: The new libyang node.
*/
extern struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt,
...) PRINTFRR(3, 4);

extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
va_list ap);

#ifdef __cplusplus
}
#endif
Expand Down
156 changes: 156 additions & 0 deletions lib/northbound_notif.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* December 1 2024, Christian Hopps <[email protected]>
*
* Copyright (c) 2024, LabN Consulting, L.L.C.
*
*/
#include <zebra.h>
#include "debug.h"
#include "typesafe.h"
#include "northbound.h"

#define __dbg(fmt, ...) DEBUGD(&nb_dbg_notif, "NB_OP_CHANGE: %s: " fmt, __func__, ##__VA_ARGS__)
#define __log_err(fmt, ...) zlog_err("NB_OP_CHANGE: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)

static void nb_notif_add(const char *path)
{
}


static void nb_notif_delete(const char *path)
{
}

struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value)
{
struct lyd_node *dnode;
const char *abs_path = NULL;


__dbg("updating path: %s with value: %s", path, value);

dnode = yang_state_new(tree, path, value);

if (path[0] == '/')
abs_path = path;
else {
abs_path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
}

nb_notif_add(abs_path);

if (abs_path != path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the chance that abs_path and path are equal and path was passed in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abs_path starts out assigned as NULL, it is then either assigned the value path or it is a new allocation from lyd_path(). path is not currently allowed to be NULL (notice we use path[0] without checking for path == NULL). So path can only ever equal abs_path when we set it that way (which is what our equality check is deciding, did we set it or newly allocate it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I don't see a reason we couldn't also support a NULL path for updating the path/value of the tree node itself, so I'll modify the code to also handle path == NULL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually nm, this gets tricky deeper in the implementation, i'll leave extended functionality for later if we need it.

free((char *)abs_path);

return dnode;
}

void nb_op_update_delete(struct lyd_node *tree, const char *path)
{
char *abs_path = NULL;

__dbg("deleting path: %s", path);

if (path && path[0] == '/')
abs_path = (char *)path;
else {
assert(tree);
abs_path = lyd_path(tree, LYD_PATH_STD, NULL, 0);
assert(abs_path);
if (path) {
char *tmp = darr_strdup(abs_path);
free(abs_path);
abs_path = tmp;
if (*darr_last(abs_path) != '/')
darr_in_strcat(abs_path, "/");
assert(abs_path); /* silence bad CLANG NULL warning */
darr_in_strcat(abs_path, path);
}
}

yang_state_delete(tree, path);

nb_notif_delete(abs_path);

if (abs_path != path) {
if (path)
darr_free(abs_path);
else
free(abs_path);
}
}

PRINTFRR(2, 0)
struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt, const char *value,
va_list ap)
{
struct lyd_node *dnode;
char *path;

path = darr_vsprintf(path_fmt, ap);
dnode = nb_op_update(tree, path, value);
darr_free(path);

return dnode;
}

struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt, const char *value,
...)
{
struct lyd_node *dnode;
va_list ap;

va_start(ap, value);
dnode = nb_op_update_vpathf(tree, path_fmt, value, ap);
va_end(ap);

return dnode;
}

PRINTFRR(2, 0)
void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
{
char *path;

path = darr_vsprintf(path_fmt, ap);
nb_op_update_delete(tree, path);
darr_free(path);
}

void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
{
va_list ap;

va_start(ap, path_fmt);
nb_op_update_delete_vpathf(tree, path_fmt, ap);
va_end(ap);
}


PRINTFRR(3, 0)
struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
va_list ap)
{
struct lyd_node *dnode;
char *value;

value = darr_vsprintf(val_fmt, ap);
dnode = nb_op_update(tree, path, value);
darr_free(value);

return dnode;
}


struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
{
struct lyd_node *dnode;
va_list ap;

va_start(ap, val_fmt);
dnode = nb_op_vupdatef(tree, path, val_fmt, ap);
va_end(ap);

return dnode;
}
1 change: 1 addition & 0 deletions lib/northbound_oper.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* We must also process containers with lookup-next descendants last.
*/

DEFINE_MTYPE_STATIC(LIB, NB_STATE, "Northbound State");
DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State");
DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos");

Expand Down
1 change: 1 addition & 0 deletions lib/subdir.am
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ lib_libfrr_la_SOURCES = \
lib/northbound.c \
lib/northbound_cli.c \
lib/northbound_db.c \
lib/northbound_notif.c \
lib/northbound_oper.c \
lib/ntop.c \
lib/openbsd-tree.c \
Expand Down
111 changes: 111 additions & 0 deletions lib/yang.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <libyang/version.h>
#include "northbound.h"
#include "frrstr.h"
#include "darr.h"

#include "lib/config_paths.h"

Expand Down Expand Up @@ -680,6 +681,116 @@ void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath,
assert(err == LY_SUCCESS);
}

struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value)
{
struct lyd_node *dnode, *parent;
LY_ERR err;

err = lyd_new_path2(tree, ly_native_ctx, path, value, 0, 0, LYD_NEW_PATH_UPDATE, &parent,
&dnode);
assert(err == LY_SUCCESS);

/*
* If the node exists and isn't updated returned dnode will be NULL, so
* we need to find it. But even if returned it can be the first newly
* created node (could be container of path) not the actual path dnode.
* So we always find.
*/
err = lyd_find_path(tree ?: parent, path, false, &dnode);
assert(err == LY_SUCCESS);

return dnode;
}

void yang_state_delete(struct lyd_node *tree, const char *path)
{
LY_ERR err;

if (!tree)
return;

if (path) {
err = lyd_find_path(tree, path, false, &tree);
if (err != LY_SUCCESS) {
zlog_info("State %s has already been deleted", path);
return;
}
}
lyd_free_tree(tree);
}

PRINTFRR(2, 0)
struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
const char *value, va_list ap)
{
struct lyd_node *dnode;
char *path;

path = darr_vsprintf(path_fmt, ap);
dnode = yang_state_new(tree, path, value);
darr_free(path);

return dnode;
}

struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
const char *value, ...)
{
struct lyd_node *dnode;
va_list ap;

va_start(ap, value);
dnode = yang_state_new_vpathf(tree, path_fmt, value, ap);
va_end(ap);

return dnode;
}

PRINTFRR(2, 0)
void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
{
char *path;

path = darr_vsprintf(path_fmt, ap);
yang_state_delete(tree, path);
darr_free(path);
}

void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
{
va_list ap;

va_start(ap, path_fmt);
yang_state_delete_vpathf(tree, path_fmt, ap);
va_end(ap);
}

PRINTFRR(3, 0)
struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path, const char *val_fmt,
va_list ap)
{
struct lyd_node *dnode;
char *value;

value = darr_vsprintf(val_fmt, ap);
dnode = yang_state_new(tree, path, value);
darr_free(value);

return dnode;
}

struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
{
struct lyd_node *dnode;
va_list ap;

va_start(ap, val_fmt);
dnode = yang_state_vnewf(tree, path, val_fmt, ap);
va_end(ap);

return dnode;
}

struct yang_data *yang_data_new(const char *xpath, const char *value)
{
struct yang_data *data;
Expand Down
Loading