lib: introduce new northbound API

Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
This commit is contained in:
Renato Westphal 2017-12-07 17:31:48 -02:00
parent 75082dafb5
commit 1c2facd12d
46 changed files with 8267 additions and 25 deletions

View file

@ -44,6 +44,9 @@ ForEachMacros:
- FOR_ALL_INTERFACES
- FOR_ALL_INTERFACES_ADDRESSES
- JSON_FOREACH
# libyang
- LY_TREE_FOR
- LY_TREE_DFS_BEGIN
# zebra
- RE_DEST_FOREACH_ROUTE
- RE_DEST_FOREACH_ROUTE_SAFE

View file

@ -95,9 +95,11 @@ noinst_LIBRARIES =
nodist_noinst_DATA =
lib_LTLIBRARIES =
module_LTLIBRARIES =
libyang_plugins_LTLIBRARIES =
pkginclude_HEADERS =
nodist_pkginclude_HEADERS =
dist_examples_DATA =
dist_yangmodels_DATA =
man_MANS =
vtysh_scan =
@ -108,6 +110,7 @@ vtysh_scan =
$(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES
$(AUTOMAKE_DUMMY)install-libyang_pluginsLTLIBRARIES: install-libLTLIBRARIES
include doc/subdir.am
include doc/user/subdir.am
@ -141,6 +144,8 @@ include pimd/subdir.am
include pbrd/subdir.am
include staticd/subdir.am
include bfdd/subdir.am
include yang/subdir.am
include yang/libyang_plugins/subdir.am
include vtysh/subdir.am
include tests/subdir.am

View file

@ -137,7 +137,7 @@ void sighup(void)
zlog_info("bgpd restarting!");
/* Reload config file. */
vty_read_config(bgpd_di.config_file, config_default);
vty_read_config(NULL, bgpd_di.config_file, config_default);
/* Try to return to normal operation. */
}

View file

@ -91,6 +91,12 @@ AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directo
])
AC_SUBST([moduledir], [$moduledir])
yangmodelsdir="\${datarootdir}/yang"
AC_SUBST([yangmodelsdir], [$yangmodelsdir])
libyang_pluginsdir="\${libdir}/frr/libyang_plugins"
AC_SUBST(libyang_pluginsdir)
AC_ARG_ENABLE(tcmalloc,
AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]),
[case "${enableval}" in
@ -417,6 +423,8 @@ AC_ARG_ENABLE(bgp-vnc,
AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support]))
AC_ARG_ENABLE(snmp,
AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx]))
AC_ARG_ENABLE(config_rollbacks,
AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)]))
AC_ARG_ENABLE(zeromq,
AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)]))
AC_ARG_WITH(libpam,
@ -1563,6 +1571,28 @@ AM_CONDITIONAL([SNMP], [test "x${SNMP_METHOD}" != "x"])
AC_SUBST(SNMP_LIBS)
AC_SUBST(SNMP_CFLAGS)
dnl ---------------
dnl libyang
dnl ---------------
PKG_CHECK_MODULES(libyang, [libyang >= 0.16.7], , [
AC_MSG_ERROR([libyang (>= 0.16.7) was not found on your system.])
])
dnl ---------------
dnl configuration rollbacks
dnl ---------------
SQLITE3=false
if test "$enable_config_rollbacks" = "yes"; then
PKG_CHECK_MODULES(sqlite3,[sqlite3], [
AC_DEFINE(HAVE_CONFIG_ROLLBACKS,1,Enable configuration rollbacks)
AC_DEFINE(HAVE_SQLITE3,1,Enable sqlite3 database)
SQLITE3=true
], [
AC_MSG_ERROR([--enable-config-rollbacks given but sqlite3 was not found on your system.])
])
fi
AM_CONDITIONAL(SQLITE3, $SQLITE3)
dnl ---------------
dnl math
dnl ---------------
@ -2004,6 +2034,7 @@ AC_DEFINE_UNQUOTED(LDPD_SOCKET, "$frr_statedir/ldpd.sock",ldpd control socket)
AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$frr_statedir/zserv.api",zebra api socket)
AC_DEFINE_UNQUOTED(BFDD_CONTROL_SOCKET, "$frr_statedir/bfdd.sock", bfdd control socket)
AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$frr_statedir",daemon vty directory)
AC_DEFINE_UNQUOTED(DAEMON_DB_DIR, "$frr_statedir",daemon database directory)
dnl autoconf does this, but it does it too late...
test "x$prefix" = xNONE && prefix=$ac_default_prefix
@ -2021,17 +2052,25 @@ CFG_SYSCONF="$sysconfdir"
CFG_SBIN="$sbindir"
CFG_STATE="$frr_statedir"
CFG_MODULE="$moduledir"
CFG_YANGMODELS="$yangmodelsdir"
CFG_LIBYANG_PLUGINS="$libyang_pluginsdir"
for I in 1 2 3 4 5 6 7 8 9 10; do
eval CFG_SYSCONF="\"$CFG_SYSCONF\""
eval CFG_SBIN="\"$CFG_SBIN\""
eval CFG_STATE="\"$CFG_STATE\""
eval CFG_MODULE="\"$CFG_MODULE\""
eval CFG_YANGMODELS="\"$CFG_YANGMODELS\""
eval CFG_LIBYANG_PLUGINS="\"$CFG_LIBYANG_PLUGINS\""
done
AC_SUBST(CFG_SYSCONF)
AC_SUBST(CFG_SBIN)
AC_SUBST(CFG_STATE)
AC_SUBST(CFG_MODULE)
AC_SUBST(CFG_YANGMODELS)
AC_SUBST(CFG_LIBYANG_PLUGINS)
AC_DEFINE_UNQUOTED(MODULE_PATH, "$CFG_MODULE", path to modules)
AC_DEFINE_UNQUOTED(YANG_MODELS_PATH, "$CFG_YANGMODELS", path to YANG data models)
AC_DEFINE_UNQUOTED(LIBYANG_PLUGINS_PATH, "$CFG_LIBYANG_PLUGINS", path to libyang plugins)
dnl ------------------------------------
dnl Enable RPKI and add librtr to libs

View file

@ -478,6 +478,10 @@ These options apply to all |PACKAGE_NAME| daemons.
When initializing the daemon, allow the specification of a default
log level at startup from one of the specified levels.
.. option:: --tcli
Enable the transactional CLI mode.
.. _loadable-module-support:
Loadable Module Support

View file

@ -222,6 +222,10 @@ options from the list below.
the COMMIT (git hash) and TOKEN (codecov upload token) environment variables
be set.
.. option:: --enable-config-rollbacks
Build with configuration rollback support. Requires SQLite3.
You may specify any combination of the above options to the configure
script. By default, the executables are placed in :file:`/usr/local/sbin`
and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/`

View file

@ -138,7 +138,7 @@ sighup(void)
* and build a new configuartion from scratch.
*/
ldp_config_reset(vty_conf);
vty_read_config(ldpd_di.config_file, config_default);
vty_read_config(NULL, ldpd_di.config_file, config_default);
ldp_config_apply(NULL, vty_conf);
}

View file

@ -46,6 +46,7 @@
#include "jhash.h"
#include "hook.h"
#include "lib_errors.h"
#include "northbound_cli.h"
DEFINE_MTYPE(LIB, HOST, "Host config")
DEFINE_MTYPE(LIB, COMPLETION, "Completion item")
@ -81,6 +82,7 @@ const char *node_names[] = {
"config", // CONFIG_NODE,
"debug", // DEBUG_NODE,
"vrf debug", // VRF_DEBUG_NODE,
"northbound debug", // NORTHBOUND_DEBUG_NODE,
"vnc debug", // DEBUG_VNC_NODE,
"aaa", // AAA_NODE,
"keychain", // KEYCHAIN_NODE,
@ -718,11 +720,14 @@ vector cmd_describe_command(vector vline, struct vty *vty, int *status)
if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
enum node_type onode;
int orig_xpath_index;
vector shifted_vline;
unsigned int index;
onode = vty->node;
orig_xpath_index = vty->xpath_index;
vty->node = ENABLE_NODE;
vty->xpath_index = 0;
/* We can try it on enable node, cos' the vty is authenticated
*/
@ -737,6 +742,7 @@ vector cmd_describe_command(vector vline, struct vty *vty, int *status)
vector_free(shifted_vline);
vty->node = onode;
vty->xpath_index = orig_xpath_index;
return ret;
}
@ -1075,14 +1081,17 @@ int cmd_execute_command(vector vline, struct vty *vty,
{
int ret, saved_ret = 0;
enum node_type onode, try_node;
int orig_xpath_index;
onode = try_node = vty->node;
orig_xpath_index = vty->xpath_index;
if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
vector shifted_vline;
unsigned int index;
vty->node = ENABLE_NODE;
vty->xpath_index = 0;
/* We can try it on enable node, cos' the vty is authenticated
*/
@ -1097,6 +1106,7 @@ int cmd_execute_command(vector vline, struct vty *vty,
vector_free(shifted_vline);
vty->node = onode;
vty->xpath_index = orig_xpath_index;
return ret;
}
@ -1113,6 +1123,8 @@ int cmd_execute_command(vector vline, struct vty *vty,
while (vty->node > CONFIG_NODE) {
try_node = node_parent(try_node);
vty->node = try_node;
if (vty->xpath_index > 0)
vty->xpath_index--;
ret = cmd_execute_command_real(vline, FILTER_RELAXED,
vty, cmd);
if (ret == CMD_SUCCESS || ret == CMD_WARNING
@ -1122,6 +1134,7 @@ int cmd_execute_command(vector vline, struct vty *vty,
}
/* no command succeeded, reset the vty to the original node */
vty->node = onode;
vty->xpath_index = orig_xpath_index;
}
/* return command status for original node */
@ -1285,7 +1298,6 @@ int command_config_read_one_line(struct vty *vty,
uint32_t line_num, int use_daemon)
{
vector vline;
int saved_node;
int ret;
vline = cmd_make_strvec(vty->buf);
@ -1303,14 +1315,16 @@ int command_config_read_one_line(struct vty *vty,
&& ret != CMD_SUCCESS && ret != CMD_WARNING
&& ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED
&& vty->node != CONFIG_NODE) {
saved_node = vty->node;
int saved_node = vty->node;
int saved_xpath_index = vty->xpath_index;
while (!(use_daemon && ret == CMD_SUCCESS_DAEMON)
&& !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
&& ret != CMD_SUCCESS && ret != CMD_WARNING
&& vty->node > CONFIG_NODE) {
vty->node = node_parent(vty->node);
if (vty->xpath_index > 0)
vty->xpath_index--;
ret = cmd_execute_command_strict(vline, vty, cmd);
}
@ -1320,6 +1334,7 @@ int command_config_read_one_line(struct vty *vty,
&& !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
&& ret != CMD_SUCCESS && ret != CMD_WARNING) {
vty->node = saved_node;
vty->xpath_index = saved_xpath_index;
}
}
@ -1377,6 +1392,12 @@ DEFUN (config_terminal,
vty_out(vty, "VTY configuration is locked by other VTY\n");
return CMD_WARNING_CONFIG_FAILED;
}
vty->private_config = false;
vty->candidate_config = vty_shared_candidate_config;
if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
vty->candidate_config_base = nb_config_dup(running_config);
return CMD_SUCCESS;
}
@ -1500,6 +1521,9 @@ void cmd_exit(struct vty *vty)
default:
break;
}
if (vty->xpath_index > 0)
vty->xpath_index--;
}
/* ALIAS_FIXME */
@ -1576,6 +1600,9 @@ DEFUN (config_end,
default:
break;
}
vty->xpath_index = 0;
return CMD_SUCCESS;
}
@ -2785,6 +2812,8 @@ void install_default(enum node_type node)
install_element(node, &show_running_config_cmd);
install_element(node, &autocomplete_cmd);
nb_cli_install_default(node);
}
/* Initialize command interface. Install basic nodes and commands.

View file

@ -77,6 +77,7 @@ enum node_type {
CONFIG_NODE, /* Config node. Default mode of config file. */
DEBUG_NODE, /* Debug node. */
VRF_DEBUG_NODE, /* Vrf Debug node. */
NORTHBOUND_DEBUG_NODE, /* Northbound Debug node. */
DEBUG_VNC_NODE, /* Debug VNC node. */
AAA_NODE, /* AAA node. */
KEYCHAIN_NODE, /* Key-chain node. */

325
lib/db.c Normal file
View file

@ -0,0 +1,325 @@
/*
* Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
*
* 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
*/
/*
* Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <zebra.h>
#include "db.h"
#include "log.h"
static struct sqlite3 *dbp;
/*
* Initialize the database in path.
*
* It's possible to use in memory database with ':memory:' path.
*/
int db_init(const char *path_fmt, ...)
{
char path[BUFSIZ];
va_list ap;
if (dbp)
return -1;
va_start(ap, path_fmt);
vsnprintf(path, sizeof(path), path_fmt, ap);
va_end(ap);
if (sqlite3_open_v2(path, &dbp,
(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), NULL)
!= SQLITE_OK) {
if (dbp == NULL) {
zlog_warn("%s: failed to open dabatase '%s'", __func__,
path);
return -1;
}
zlog_warn("%s: failed to open database '%s': %s", __func__,
path, sqlite3_errmsg(dbp));
if (sqlite3_close_v2(dbp) != SQLITE_OK)
zlog_warn("%s: failed to terminate database", __func__);
dbp = NULL;
return -1;
}
return 0;
}
/* Closes the database if open. */
int db_close(void)
{
if (dbp == NULL)
return 0;
if (sqlite3_close_v2(dbp) != SQLITE_OK) {
zlog_warn("%s: failed to terminate database", __func__);
return -1;
}
return 0;
}
/* Helper function to handle formating. */
static int db_vbindf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
{
const char *sptr = fmt;
int column = 1;
const char *str;
void *blob;
uint64_t uinteger64;
uint32_t uinteger;
int vlen;
while (*sptr) {
if (*sptr != '%') {
sptr++;
continue;
}
if (sptr++ && *sptr == 0)
break;
switch (*sptr) {
case 'i':
uinteger = va_arg(vl, uint32_t);
if (sqlite3_bind_int(ss, column++, uinteger)
!= SQLITE_OK)
return -1;
break;
case 'd':
uinteger64 = va_arg(vl, uint64_t);
if (sqlite3_bind_int64(ss, column++, uinteger64)
!= SQLITE_OK)
return -1;
break;
case 's':
str = va_arg(vl, const char *);
vlen = va_arg(vl, int);
if (sqlite3_bind_text(ss, column++, str, vlen,
SQLITE_STATIC)
!= SQLITE_OK)
return -1;
break;
case 'b':
blob = va_arg(vl, void *);
vlen = va_arg(vl, int);
if (sqlite3_bind_blob(ss, column++, blob, vlen,
SQLITE_STATIC)
!= SQLITE_OK)
return -1;
break;
case 'n':
if (sqlite3_bind_null(ss, column++) != SQLITE_OK)
return -1;
break;
default:
zlog_warn("%s: invalid format '%c'", __func__, *sptr);
return -1;
}
}
return 0;
}
/*
* Binds values using format to the database query.
*
* Might be used to bind variables to a query, insert or update.
*/
int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...)
{
va_list vl;
int result;
va_start(vl, fmt);
result = db_vbindf(ss, fmt, vl);
va_end(vl);
return result;
}
/* Prepares an statement to the database with the statement length. */
struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen)
{
struct sqlite3_stmt *ss;
int c;
if (dbp == NULL)
return NULL;
c = sqlite3_prepare_v2(dbp, stmt, stmtlen, &ss, NULL);
if (ss == NULL) {
zlog_warn("%s: failed to prepare (%d:%s)", __func__, c,
sqlite3_errmsg(dbp));
return NULL;
}
return ss;
}
/* Prepares an statement to the database. */
struct sqlite3_stmt *db_prepare(const char *stmt)
{
return db_prepare_len(stmt, strlen(stmt));
}
/* Run a prepared statement. */
int db_run(struct sqlite3_stmt *ss)
{
int result;
result = sqlite3_step(ss);
switch (result) {
case SQLITE_BUSY:
/* TODO handle busy database. */
break;
case SQLITE_OK:
/*
* SQLITE_DONE just causes confusion since it means the query went OK,
* but it has a different value.
*/
case SQLITE_DONE:
result = SQLITE_OK;
break;
case SQLITE_ROW:
/* NOTHING */
/* It is expected to receive SQLITE_ROW on search queries. */
break;
default:
zlog_warn("%s: step failed (%d:%s)", __func__, result,
sqlite3_errstr(result));
}
return result;
}
/* Helper function to load format to variables. */
static int db_vloadf(struct sqlite3_stmt *ss, const char *fmt, va_list vl)
{
const char *sptr = fmt;
int column = 0;
const char **str;
void *blob;
const void *blobsrc;
uint64_t *uinteger64;
uint32_t *uinteger;
int vlen;
int dlen;
int columncount;
columncount = sqlite3_column_count(ss);
if (columncount == 0)
return -1;
while (*sptr) {
if (*sptr != '%') {
sptr++;
continue;
}
if (sptr++ && *sptr == 0)
break;
switch (*sptr) {
case 'i':
uinteger = va_arg(vl, uint32_t *);
*uinteger = sqlite3_column_int(ss, column);
break;
case 'd':
uinteger64 = va_arg(vl, uint64_t *);
*uinteger64 = sqlite3_column_int64(ss, column);
break;
case 's':
str = va_arg(vl, const char **);
*str = (const char *)sqlite3_column_text(ss, column);
break;
case 'b':
blob = va_arg(vl, void *);
vlen = va_arg(vl, int);
dlen = sqlite3_column_bytes(ss, column);
blobsrc = sqlite3_column_blob(ss, column);
memcpy(blob, blobsrc, MIN(vlen, dlen));
break;
default:
zlog_warn("%s: invalid format '%c'", __func__, *sptr);
return -1;
}
column++;
}
return 0;
}
/* Function to load format from database row. */
int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...)
{
va_list vl;
int result;
va_start(vl, fmt);
result = db_vloadf(ss, fmt, vl);
va_end(vl);
return result;
}
/* Finalize query and return memory. */
void db_finalize(struct sqlite3_stmt **ss)
{
sqlite3_finalize(*ss);
*ss = NULL;
}
/* Execute one or more statements. */
int db_execute(const char *stmt_fmt, ...)
{
char stmt[BUFSIZ];
va_list ap;
if (dbp == NULL)
return -1;
va_start(ap, stmt_fmt);
vsnprintf(stmt, sizeof(stmt), stmt_fmt, ap);
va_end(ap);
if (sqlite3_exec(dbp, stmt, NULL, 0, NULL) != SQLITE_OK) {
zlog_warn("%s: failed to execute statement(s): %s", __func__,
sqlite3_errmsg(dbp));
return -1;
}
return 0;
}

52
lib/db.h Normal file
View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018 Rafael Zalamena <rzalamena@gmail.com>
*
* 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
*/
/*
* Copyright (c) 2016 Rafael Zalamena <rzalamena@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _FRR_DB_H_
#define _FRR_DB_H_
#ifdef HAVE_SQLITE3
#include <sqlite3.h>
extern int db_init(const char *path_fmt, ...);
extern int db_close(void);
extern int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...);
extern struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen);
extern struct sqlite3_stmt *db_prepare(const char *stmt);
extern int db_run(struct sqlite3_stmt *ss);
extern int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...);
extern void db_finalize(struct sqlite3_stmt **ss);
extern int db_execute(const char *stmt_fmt, ...);
#endif /* HAVE_SQLITE3 */
#endif /* _FRR_DB_H_ */

View file

@ -68,6 +68,84 @@ static struct log_ref ferr_lib_warn[] = {
.description = "The VRF subsystem, during initialization, has found a parsing error with input it has received",
.suggestion = "Check the length of the vrf name and adjust accordingly",
},
{
.code = EC_LIB_YANG_DATA_TRUNCATED,
.title = "YANG data truncation",
.description = "The northbound subsystem has detected that YANG data has been truncated as the given buffer wasn't big enough",
.suggestion = "Gather log data and open an Issue",
},
{
.code = EC_LIB_YANG_UNKNOWN_DATA_PATH,
.title = "Unknown YANG data path",
.description = "The northbound subsystem has detected an unknown YANG data path",
.suggestion = "Gather log data and open an Issue",
},
{
.code = EC_LIB_YANG_TRANSLATOR_LOAD,
.title = "Unable to load YANG module translator",
.description = "The northbound subsystem has detected an error while loading a YANG module translator",
.suggestion = "Ensure the YANG module translator file is valid. See documentation for further information.",
},
{
.code = EC_LIB_YANG_TRANSLATION_ERROR,
.title = "YANG translation error",
.description = "The northbound subsystem has detected an error while performing a YANG XPath translation",
.suggestion = "Gather log data and open an Issue",
},
{
.code = EC_LIB_NB_DATABASE,
.title = "The northbound database wasn't initialized correctly",
.description = "An error occurred while initializing the northbound database. As a result, the configuration rollback feature won't work as expected.",
.suggestion = "Ensure permissions are correct for FRR files, users and groups are correct."
},
{
.code = EC_LIB_NB_CB_UNNEEDED,
.title = "Unneeded northbound callback",
.description = "The northbound subsystem, during initialization, has detected a callback that doesn't need to be implemented",
.suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
},
{
.code = EC_LIB_NB_CB_CONFIG,
.title = "A northbound configuration callback has failed",
.description = "The northbound subsystem has detected that a callback used to process a configuration change has returned an error",
.suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
},
{
.code = EC_LIB_NB_CB_STATE,
.title = "A northbound callback for operational data has failed",
.description = "The northbound subsystem has detected that a callback used to fetch operational data has returned an error",
.suggestion = "Gather log data and open an Issue",
},
{
.code = EC_LIB_NB_CB_RPC,
.title = "A northbound RPC callback has failed",
.description = "The northbound subsystem has detected that a callback used to process YANG RPCs/actions has returned an error",
.suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
},
{
.code = EC_LIB_NB_CANDIDATE_INVALID,
.title = "Invalid candidate configuration",
.description = "The northbound subsystem failed to validate a candidate configuration",
.suggestion = "Check the log messages to see the validation errors and edit the candidate configuration to fix them",
},
{
.code = EC_LIB_NB_CANDIDATE_EDIT_ERROR,
.title = "Failure to edit a candidate configuration",
.description = "The northbound subsystem failed to edit a candidate configuration",
.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",
.suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.",
},
{
.code = EC_LIB_NB_TRANSACTION_RECORD_FAILED,
.title = "Failure to record a configuration transaction",
.description = "The northbound subsystem failed to record a configuration transaction in the northbound database",
.suggestion = "Gather log data and open an Issue",
},
{
.code = END_FERR,
},
@ -152,6 +230,96 @@ static struct log_ref ferr_lib_err[] = {
.description = "FRR was not compiled with support for a particular feature, or it is not available on the current platform",
.suggestion = "Recompile FRR with the feature enabled, or find out what platforms support the feature"
},
{
.code = EC_LIB_YANG_MODULE_LOAD,
.title = "Unable to load YANG module from the file system",
.description = "The northbound subsystem has detected an error while loading a YANG module from the file system",
.suggestion = "Ensure all FRR YANG modules were installed correctly in the system.",
},
{
.code = EC_LIB_YANG_MODULE_LOADED_ALREADY,
.title = "Attempt to load a YANG module that is already loaded",
.description = "The northbound subsystem has detected an attempt to load a YANG module that is already loaded",
.suggestion = "This is a bug; please report it"
},
{
.code = EC_LIB_YANG_DATA_CONVERT,
.title = "YANG data conversion error",
.description = "An error has occurred while converting a YANG data value from string to binary representation or vice-versa",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = EC_LIB_YANG_DNODE_NOT_FOUND,
.title = "YANG data node not found",
.description = "The northbound subsystem failed to find a YANG data node that was supposed to exist",
.suggestion = "This is a bug; please report it"
},
{
.code = EC_LIB_NB_CB_MISSING,
.title = "Missing northbound callback",
.description = "The northbound subsystem, during initialization, has detected a missing callback for one node of the loaded YANG modules",
.suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
},
{
.code = EC_LIB_NB_CB_INVALID_PRIO,
.title = "Norhtbound callback has an invalid priority",
.description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid",
.suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
},
{
.code = EC_LIB_NB_CBS_VALIDATION,
.title = "Failure to validate the northbound callbacks",
.description = "The northbound subsystem, during initialization, has detected one or more errors while loading the northbound callbacks",
.suggestion = "Check if the installed FRR YANG modules are in sync with the FRR binaries",
},
{
.code = EC_LIB_LIBYANG,
.title = "The libyang library returned an error",
.description = "The northbound subsystem has detected that the libyang library returned an error",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = EC_LIB_LIBYANG_PLUGIN_LOAD,
.title = "Failure to load a libyang plugin",
.description = "The northbound subsystem, during initialization, has detected that a libyang plugin failed to be loaded",
.suggestion = "Check if the FRR libyang plugins were installed correctly in the system",
},
{
.code = EC_LIB_CONFD_INIT,
.title = "ConfD initialization error",
.description = "Upon startup FRR failed to properly initialize and startup the ConfD northbound plugin",
.suggestion = "Check if ConfD is installed correctly in the system. Also, check if the confd daemon is running.",
},
{
.code = EC_LIB_CONFD_DATA_CONVERT,
.title = "ConfD data conversion error",
.description = "An error has occurred while converting a ConfD data value (binary) to a string",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = EC_LIB_LIBCONFD,
.title = "libconfd error",
.description = "The northbound subsystem has detected that the libconfd library returned an error",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = EC_LIB_SYSREPO_INIT,
.title = "Sysrepo initialization error",
.description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin",
.suggestion = "Check if Sysrepo is installed correctly in the system",
},
{
.code = EC_LIB_SYSREPO_DATA_CONVERT,
.title = "Sysrepo data conversion error",
.description = "An error has occurred while converting a YANG data value to the Sysrepo format",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = EC_LIB_LIBSYSREPO,
.title = "libsysrepo error",
.description = "The northbound subsystem has detected that the libsysrepo library returned an error",
.suggestion = "Open an Issue with all relevant log files and restart FRR"
},
{
.code = END_FERR,
}

View file

@ -44,6 +44,34 @@ enum lib_log_refs {
EC_LIB_RMAP_RECURSION_LIMIT,
EC_LIB_BACKUP_CONFIG,
EC_LIB_VRF_LENGTH,
EC_LIB_YANG_MODULE_LOAD,
EC_LIB_YANG_MODULE_LOADED_ALREADY,
EC_LIB_YANG_DATA_CONVERT,
EC_LIB_YANG_DATA_TRUNCATED,
EC_LIB_YANG_UNKNOWN_DATA_PATH,
EC_LIB_YANG_DNODE_NOT_FOUND,
EC_LIB_YANG_TRANSLATOR_LOAD,
EC_LIB_YANG_TRANSLATION_ERROR,
EC_LIB_NB_DATABASE,
EC_LIB_NB_CB_UNNEEDED,
EC_LIB_NB_CB_MISSING,
EC_LIB_NB_CB_INVALID_PRIO,
EC_LIB_NB_CBS_VALIDATION,
EC_LIB_NB_CB_CONFIG,
EC_LIB_NB_CB_STATE,
EC_LIB_NB_CB_RPC,
EC_LIB_NB_CANDIDATE_INVALID,
EC_LIB_NB_CANDIDATE_EDIT_ERROR,
EC_LIB_NB_TRANSACTION_CREATION_FAILED,
EC_LIB_NB_TRANSACTION_RECORD_FAILED,
EC_LIB_LIBYANG,
EC_LIB_LIBYANG_PLUGIN_LOAD,
EC_LIB_CONFD_INIT,
EC_LIB_CONFD_DATA_CONVERT,
EC_LIB_LIBCONFD,
EC_LIB_SYSREPO_INIT,
EC_LIB_SYSREPO_DATA_CONVERT,
EC_LIB_LIBSYSREPO,
};
extern void lib_error_init(void);

View file

@ -36,6 +36,8 @@
#include "module.h"
#include "network.h"
#include "lib_errors.h"
#include "db.h"
#include "northbound_cli.h"
DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
DEFINE_KOOH(frr_early_fini, (), ())
@ -43,6 +45,9 @@ DEFINE_KOOH(frr_fini, (), ())
const char frr_sysconfdir[] = SYSCONFDIR;
const char frr_vtydir[] = DAEMON_VTY_DIR;
#ifdef HAVE_SQLITE3
const char frr_dbdir[] = DAEMON_DB_DIR;
#endif
const char frr_moduledir[] = MODULE_PATH;
char frr_protoname[256] = "NONE";
@ -51,6 +56,9 @@ char frr_protonameinst[256] = "NONE";
char config_default[512];
char frr_zclientpath[256];
static char pidfile_default[512];
#ifdef HAVE_SQLITE3
static char dbfile_default[512];
#endif
static char vtypath_default[256];
bool debug_memstats_at_exit = 0;
@ -82,6 +90,8 @@ static void opt_extend(const struct optspec *os)
#define OPTION_MODULEDIR 1002
#define OPTION_LOG 1003
#define OPTION_LOGLEVEL 1004
#define OPTION_TCLI 1005
#define OPTION_DB_FILE 1006
static const struct option lo_always[] = {
{"help", no_argument, NULL, 'h'},
@ -92,6 +102,7 @@ static const struct option lo_always[] = {
{"moduledir", required_argument, NULL, OPTION_MODULEDIR},
{"log", required_argument, NULL, OPTION_LOG},
{"log-level", required_argument, NULL, OPTION_LOGLEVEL},
{"tcli", no_argument, NULL, OPTION_TCLI},
{NULL}};
static const struct optspec os_always = {
"hvdM:",
@ -102,13 +113,17 @@ static const struct optspec os_always = {
" --vty_socket Override vty socket path\n"
" --moduledir Override modules directory\n"
" --log Set Logging to stdout, syslog, or file:<name>\n"
" --log-level Set Logging Level to use, debug, info, warn, etc\n",
" --log-level Set Logging Level to use, debug, info, warn, etc\n"
" --tcli Use transaction-based CLI\n",
lo_always};
static const struct option lo_cfg_pid_dry[] = {
{"pid_file", required_argument, NULL, 'i'},
{"config_file", required_argument, NULL, 'f'},
#ifdef HAVE_SQLITE3
{"db_file", required_argument, NULL, OPTION_DB_FILE},
#endif
{"pathspace", required_argument, NULL, 'N'},
{"dryrun", no_argument, NULL, 'C'},
{"terminal", no_argument, NULL, 't'},
@ -117,6 +132,9 @@ static const struct optspec os_cfg_pid_dry = {
"f:i:CtN:",
" -f, --config_file Set configuration file name\n"
" -i, --pid_file Set process identifier file name\n"
#ifdef HAVE_SQLITE3
" --db_file Set database file name\n"
#endif
" -N, --pathspace Insert prefix into config & socket paths\n"
" -C, --dryrun Check configuration for validity and exit\n"
" -t, --terminal Open terminal session on stdio\n"
@ -289,11 +307,17 @@ void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv)
frr_sysconfdir, di->name);
snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid",
frr_vtydir, di->name);
#ifdef HAVE_SQLITE3
snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db",
frr_dbdir, di->name);
#endif
strlcpy(frr_protoname, di->logname, sizeof(frr_protoname));
strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst));
strlcpy(frr_zclientpath, ZEBRA_SERV_PATH, sizeof(frr_zclientpath));
di->cli_mode = FRR_CLI_CLASSIC;
}
void frr_opt_add(const char *optstr, const struct option *longopts,
@ -380,6 +404,13 @@ static int frr_opt(int opt)
}
di->pathspace = optarg;
break;
#ifdef HAVE_SQLITE3
case OPTION_DB_FILE:
if (di->flags & FRR_NO_CFG_PID_DRY)
return 1;
di->db_file = optarg;
break;
#endif
case 'C':
if (di->flags & FRR_NO_CFG_PID_DRY)
return 1;
@ -444,6 +475,9 @@ static int frr_opt(int opt)
}
di->module_path = optarg;
break;
case OPTION_TCLI:
di->cli_mode = FRR_CLI_TRANSACTIONAL;
break;
case 'u':
if (di->flags & FRR_NO_PRIVSEP)
return 1;
@ -556,6 +590,10 @@ struct thread_master *frr_init(void)
frr_sysconfdir, p_pathspace, di->name, p_instance);
snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s%s.pid",
frr_vtydir, p_pathspace, di->name, p_instance);
#ifdef HAVE_SQLITE3
snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db",
frr_dbdir, p_pathspace, di->name, p_instance);
#endif
zprivs_preinit(di->privs);
@ -597,19 +635,39 @@ struct thread_master *frr_init(void)
master = thread_master_create(NULL);
signal_init(master, di->n_signals, di->signals);
#ifdef HAVE_SQLITE3
if (!di->db_file)
di->db_file = dbfile_default;
db_init(di->db_file);
#endif
if (di->flags & FRR_LIMITED_CLI)
cmd_init(-1);
else
cmd_init(1);
vty_init(master);
memory_init();
log_ref_init();
lib_error_init();
yang_init();
nb_init(di->yang_modules, di->n_yang_modules);
return master;
}
const char *frr_get_progname(void)
{
return di ? di->progname : NULL;
}
enum frr_cli_mode frr_get_cli_mode(void)
{
return di ? di->cli_mode : FRR_CLI_CLASSIC;
}
static int rcvd_signal = 0;
static void rcv_signal(int signum)
@ -752,17 +810,23 @@ static void frr_daemonize(void)
*/
static int frr_config_read_in(struct thread *t)
{
if (!vty_read_config(di->config_file, config_default) &&
if (!vty_read_config(NULL, di->config_file, config_default) &&
di->backup_config_file) {
char *orig = XSTRDUP(MTYPE_TMP, host_config_get());
zlog_info("Attempting to read backup config file: %s specified",
di->backup_config_file);
vty_read_config(di->backup_config_file, config_default);
vty_read_config(NULL, di->backup_config_file, config_default);
host_config_set(orig);
XFREE(MTYPE_TMP, orig);
}
/*
* Update the shared candidate after reading the startup configuration.
*/
nb_config_replace(vty_shared_candidate_config, running_config, true);
return 0;
}
@ -963,6 +1027,11 @@ void frr_fini(void)
/* memory_init -> nothing needed */
vty_terminate();
cmd_terminate();
nb_terminate();
yang_terminate();
#ifdef HAVE_SQLITE3
db_close();
#endif
log_ref_fini();
zprivs_terminate(di->privs);
/* signal_init -> nothing needed */

View file

@ -28,6 +28,7 @@
#include "getopt.h"
#include "module.h"
#include "hook.h"
#include "northbound.h"
/* The following options disable specific command line options that
* are not applicable for a particular daemon.
@ -45,6 +46,11 @@
*/
#define FRR_DETACH_LATER (1 << 5)
enum frr_cli_mode {
FRR_CLI_CLASSIC = 0,
FRR_CLI_TRANSACTIONAL,
};
struct frr_daemon_info {
unsigned flags;
@ -60,11 +66,15 @@ struct frr_daemon_info {
bool dryrun;
bool daemon_mode;
bool terminal;
enum frr_cli_mode cli_mode;
struct thread *read_in;
const char *config_file;
const char *backup_config_file;
const char *pid_file;
#ifdef HAVE_SQLITE3
const char *db_file;
#endif
const char *vty_path;
const char *module_path;
const char *pathspace;
@ -80,6 +90,9 @@ struct frr_daemon_info {
size_t n_signals;
struct zebra_privs_t *privs;
const struct frr_yang_module_info **yang_modules;
size_t n_yang_modules;
};
/* execname is the daemon's executable (and pidfile and configfile) name,
@ -108,6 +121,8 @@ extern int frr_getopt(int argc, char *const argv[], int *longindex);
extern void frr_help_exit(int status);
extern struct thread_master *frr_init(void);
extern const char *frr_get_progname(void);
extern enum frr_cli_mode frr_get_cli_mode(void);
DECLARE_HOOK(frr_late_init, (struct thread_master * tm), (tm))
extern void frr_config_fork(void);

1200
lib/northbound.c Normal file

File diff suppressed because it is too large Load diff

780
lib/northbound.h Normal file
View file

@ -0,0 +1,780 @@
/*
* 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
*/
#ifndef _FRR_NORTHBOUND_H_
#define _FRR_NORTHBOUND_H_
#include "hook.h"
#include "yang.h"
#include "linklist.h"
#include "openbsd-tree.h"
/* Forward declaration(s). */
struct vty;
/* Northbound events. */
enum nb_event {
/*
* The configuration callback is supposed to verify that the changes are
* valid and can be applied.
*/
NB_EV_VALIDATE,
/*
* The configuration callback is supposed to prepare all resources
* required to apply the changes.
*/
NB_EV_PREPARE,
/*
* Transaction has failed, the configuration callback needs to release
* all resources previously allocated.
*/
NB_EV_ABORT,
/*
* The configuration changes need to be applied. The changes can't be
* rejected at this point (errors are logged and ignored).
*/
NB_EV_APPLY,
};
/*
* Northbound operations.
*
* Refer to the documentation comments of nb_callbacks for more details.
*/
enum nb_operation {
NB_OP_CREATE,
NB_OP_MODIFY,
NB_OP_DELETE,
NB_OP_MOVE,
NB_OP_APPLY_FINISH,
NB_OP_GET_ELEM,
NB_OP_GET_NEXT,
NB_OP_GET_KEYS,
NB_OP_LOOKUP_ENTRY,
NB_OP_RPC,
};
union nb_resource {
int fd;
void *ptr;
};
struct nb_callbacks {
/*
* Configuration callback.
*
* A presence container, list entry, leaf-list entry or leaf of type
* empty has been created.
*
* For presence-containers and list entries, the callback is supposed to
* initialize the default values of its children (if any) from the YANG
* models.
*
* event
* The transaction phase. Refer to the documentation comments of
* nb_event for more details.
*
* dnode
* libyang data node that is being created.
*
* resource
* Pointer to store resource(s) allocated during the NB_EV_PREPARE
* phase. The same pointer can be used during the NB_EV_ABORT and
* NB_EV_APPLY phases to either release or make use of the allocated
* resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_VALIDATION when a validation error occurred.
* - NB_ERR_RESOURCE when the callback failed to allocate a resource.
* - NB_ERR_INCONSISTENCY when an inconsistency was detected.
* - NB_ERR for other errors.
*/
int (*create)(enum nb_event event, const struct lyd_node *dnode,
union nb_resource *resource);
/*
* Configuration callback.
*
* The value of a leaf has been modified.
*
* List keys don't need to implement this callback. When a list key is
* modified, the northbound treats this as if the list was deleted and a
* new one created with the updated key value.
*
* event
* The transaction phase. Refer to the documentation comments of
* nb_event for more details.
*
* dnode
* libyang data node that is being modified
*
* resource
* Pointer to store resource(s) allocated during the NB_EV_PREPARE
* phase. The same pointer can be used during the NB_EV_ABORT and
* NB_EV_APPLY phases to either release or make use of the allocated
* resource(s). It's set to NULL when the event is NB_EV_VALIDATE.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_VALIDATION when a validation error occurred.
* - NB_ERR_RESOURCE when the callback failed to allocate a resource.
* - NB_ERR_INCONSISTENCY when an inconsistency was detected.
* - NB_ERR for other errors.
*/
int (*modify)(enum nb_event event, const struct lyd_node *dnode,
union nb_resource *resource);
/*
* Configuration callback.
*
* A presence container, list entry, leaf-list entry or optional leaf
* has been deleted.
*
* The callback is supposed to delete the entire configuration object,
* including its children when they exist.
*
* event
* The transaction phase. Refer to the documentation comments of
* nb_event for more details.
*
* dnode
* libyang data node that is being deleted.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_VALIDATION when a validation error occurred.
* - NB_ERR_INCONSISTENCY when an inconsistency was detected.
* - NB_ERR for other errors.
*/
int (*delete)(enum nb_event event, const struct lyd_node *dnode);
/*
* Configuration callback.
*
* A list entry or leaf-list entry has been moved. Only applicable when
* the "ordered-by user" statement is present.
*
* event
* The transaction phase. Refer to the documentation comments of
* nb_event for more details.
*
* dnode
* libyang data node that is being moved.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_VALIDATION when a validation error occurred.
* - NB_ERR_INCONSISTENCY when an inconsistency was detected.
* - NB_ERR for other errors.
*/
int (*move)(enum nb_event event, const struct lyd_node *dnode);
/*
* Optional configuration callback.
*
* The 'apply_finish' callbacks are called after all other callbacks
* during the apply phase (NB_EV_APPLY). These callbacks are called only
* under one of the following two cases:
* - The data node has been created or modified (but not deleted);
* - Any change was made within the descendants of the data node (e.g. a
* child leaf was modified, created or deleted).
*
* In the second case above, the 'apply_finish' callback is called only
* once even if multiple changes occurred within the descendants of the
* data node.
*
* dnode
* libyang data node associated with the 'apply_finish' callback.
*/
void (*apply_finish)(const struct lyd_node *dnode);
/*
* 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.
*
* xpath
* YANG data path of the data we want to get.
*
* list_entry
* Pointer to list entry.
*
* Returns:
* Pointer to newly created yang_data structure, or NULL to indicate
* the absence of data.
*/
struct yang_data *(*get_elem)(const char *xpath,
const void *list_entry);
/*
* Operational data callback for YANG lists.
*
* The callback function should return the next entry in the list. The
* 'list_entry' parameter will be NULL on the first invocation.
*
* xpath
* Data path of the YANG list.
*
* list_entry
* Pointer to list entry.
*
* Returns:
* Pointer to the next entry in the list, or NULL to signal that the
* end of the list was reached.
*/
const void *(*get_next)(const char *xpath, const void *list_entry);
/*
* Operational data callback for YANG lists.
*
* The callback function should fill the 'keys' parameter based on the
* given list_entry.
*
* list_entry
* Pointer to list entry.
*
* keys
* Structure to be filled based on the attributes of the provided
* list entry.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
int (*get_keys)(const void *list_entry, struct yang_list_keys *keys);
/*
* Operational data callback for YANG lists.
*
* The callback function should return a list entry based on the list
* keys given as a parameter.
*
* 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);
/*
* RPC and action callback.
*
* Both 'input' and 'output' are lists of 'yang_data' structures. The
* callback should fetch all the input parameters from the 'input' list,
* and add output parameters to the 'output' list if necessary.
*
* xpath
* XPath of the YANG RPC or action.
*
* input
* Read-only list of input parameters.
*
* output
* List of output parameters to be populated by the callback.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
int (*rpc)(const char *xpath, const struct list *input,
struct list *output);
/*
* Optional callback to show the CLI command associated to the given
* YANG data node.
*
* vty
* The vty terminal to dump the configuration to.
*
* dnode
* libyang data node that should be shown in the form of a CLI
* command.
*
* show_defaults
* Specify whether to display default configuration values or not.
* This parameter can be ignored most of the time since the
* northbound doesn't call this callback for default leaves or
* non-presence containers that contain only default child nodes.
* The exception are commands associated to multiple configuration
* nodes, in which case it might be desirable to hide one or more
* parts of the command when this parameter is set to false.
*/
void (*cli_show)(struct vty *vty, struct lyd_node *dnode,
bool show_defaults);
};
/*
* Northbound-specific data that is allocated for each schema node of the native
* YANG modules.
*/
struct nb_node {
/* Back pointer to the libyang schema node. */
const struct lys_node *snode;
/* Data path of this YANG node. */
char xpath[XPATH_MAXLEN];
/* Priority - lower priorities are processed first. */
uint32_t priority;
/* Callbacks implemented for this node. */
struct nb_callbacks cbs;
/*
* Pointer to the parent node (disconsidering non-presence containers).
*/
struct nb_node *parent;
/* Pointer to the nearest parent list, if any. */
struct nb_node *parent_list;
#ifdef HAVE_CONFD
/* ConfD hash value corresponding to this YANG path. */
int confd_hash;
#endif
};
struct frr_yang_module_info {
/* YANG module name. */
const char *name;
/* Northbound callbacks. */
const struct {
/* Data path of this YANG node. */
const char *xpath;
/* Callbacks implemented for this node. */
struct nb_callbacks cbs;
/* Priority - lower priorities are processed first. */
uint32_t priority;
} nodes[];
};
/* Northbound error codes. */
enum nb_error {
NB_OK = 0,
NB_ERR,
NB_ERR_NO_CHANGES,
NB_ERR_NOT_FOUND,
NB_ERR_LOCKED,
NB_ERR_VALIDATION,
NB_ERR_RESOURCE,
NB_ERR_INCONSISTENCY,
};
/* Default priority. */
#define NB_DFLT_PRIORITY (UINT32_MAX / 2)
/* Default maximum of configuration rollbacks to store. */
#define NB_DLFT_MAX_CONFIG_ROLLBACKS 20
/* Northbound clients. */
enum nb_client {
NB_CLIENT_CLI = 0,
};
/* Northbound configuration. */
struct nb_config {
struct lyd_node *dnode;
uint32_t version;
};
/* Northbound configuration callback. */
struct nb_config_cb {
RB_ENTRY(nb_config_cb) entry;
enum nb_operation operation;
char xpath[XPATH_MAXLEN];
const struct nb_node *nb_node;
const struct lyd_node *dnode;
};
RB_HEAD(nb_config_cbs, nb_config_cb);
RB_PROTOTYPE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare);
/* Northbound configuration change. */
struct nb_config_change {
struct nb_config_cb cb;
union nb_resource resource;
bool prepare_ok;
};
/* Northbound configuration transaction. */
struct nb_transaction {
enum nb_client client;
char comment[80];
struct nb_config *config;
struct nb_config_cbs changes;
};
DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments),
(xpath, arguments))
extern int debug_northbound;
extern struct nb_config *running_config;
/*
* Find the northbound node corresponding to a YANG data path.
*
* xpath
* XPath to search for (with or without predicates).
*
* Returns:
* Pointer to northbound node if found, NULL otherwise.
*/
extern struct nb_node *nb_node_find(const char *xpath);
/*
* Create a new northbound configuration.
*
* dnode
* Pointer to a libyang data node containing the configuration data. If NULL
* is given, an empty configuration will be created.
*
* Returns:
* Pointer to newly created northbound configuration.
*/
extern struct nb_config *nb_config_new(struct lyd_node *dnode);
/*
* Delete a northbound configuration.
*
* config
* Pointer to the config that is going to be deleted.
*/
extern void nb_config_free(struct nb_config *config);
/*
* Duplicate a northbound configuration.
*
* config
* Northbound configuration to duplicate.
*
* Returns:
* Pointer to duplicated configuration.
*/
extern struct nb_config *nb_config_dup(const struct nb_config *config);
/*
* Merge one configuration into another.
*
* config_dst
* Configuration to merge to.
*
* config_src
* Configuration to merge config_dst with.
*
* preserve_source
* Specify whether config_src should be deleted or not after the merge
* operation.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_config_merge(struct nb_config *config_dst,
struct nb_config *config_src, bool preserve_source);
/*
* Replace one configuration by another.
*
* config_dst
* Configuration to be replaced.
*
* config_src
* Configuration to replace config_dst.
*
* preserve_source
* Specify whether config_src should be deleted or not after the replace
* operation.
*/
extern void nb_config_replace(struct nb_config *config_dst,
struct nb_config *config_src,
bool preserve_source);
/*
* Edit a candidate configuration.
*
* candidate
* Candidate configuration to edit.
*
* nb_node
* Northbound node associated to the configuration being edited.
*
* operation
* Operation to apply.
*
* xpath
* XPath of the configuration node being edited.
*
* previous
* Previous value of the configuration node. Should be used only when the
* operation is NB_OP_MOVE, otherwise this parameter is ignored.
*
* data
* New value of the configuration node.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_NOT_FOUND when the element to be deleted was not found.
* - NB_ERR for other errors.
*/
extern int nb_candidate_edit(struct nb_config *candidate,
const struct nb_node *nb_node,
enum nb_operation operation, const char *xpath,
const struct yang_data *previous,
const struct yang_data *data);
/*
* Check if a candidate configuration is outdated and needs to be updated.
*
* candidate
* Candidate configuration to check.
*
* Returns:
* true if the candidate is outdated, false otherwise.
*/
extern bool nb_candidate_needs_update(const struct nb_config *candidate);
/*
* Update a candidate configuration by rebasing the changes on top of the latest
* running configuration. Resolve conflicts automatically by giving preference
* to the changes done in the candidate configuration.
*
* candidate
* Candidate configuration to update.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_candidate_update(struct nb_config *candidate);
/*
* Validate a candidate configuration. Perform both YANG syntactic/semantic
* validation and code-level validation using the northbound callbacks.
*
* WARNING: the candidate can be modified as part of the validation process
* (e.g. add default nodes).
*
* candidate
* Candidate configuration to validate.
*
* Returns:
* NB_OK on success, NB_ERR_VALIDATION otherwise.
*/
extern int nb_candidate_validate(struct nb_config *candidate);
/*
* Create a new configuration transaction but do not commit it yet. Only
* validate the candidate and prepare all resources required to apply the
* configuration changes.
*
* candidate
* Candidate configuration to commit.
*
* client
* Northbound client performing the commit.
*
* comment
* Optional comment describing the commit.
*
* transaction
* Output parameter providing the created transaction when one is created
* successfully. In this case, it must be either aborted using
* nb_candidate_commit_abort() or committed using
* nb_candidate_commit_apply().
*
* Returns:
* - NB_OK on success.
* - NB_ERR_NO_CHANGES when the candidate is identical to the running
* configuration.
* - NB_ERR_LOCKED when there's already another transaction in progress.
* - NB_ERR_VALIDATION when the candidate fails the validation checks.
* - NB_ERR_RESOURCE when the system fails to allocate resources to apply
* the candidate configuration.
* - NB_ERR for other errors.
*/
extern int nb_candidate_commit_prepare(struct nb_config *candidate,
enum nb_client client,
const char *comment,
struct nb_transaction **transaction);
/*
* Abort a previously created configuration transaction, releasing all resources
* allocated during the preparation phase.
*
* transaction
* Candidate configuration to abort. It's consumed by this function.
*/
extern void nb_candidate_commit_abort(struct nb_transaction *transaction);
/*
* Commit a previously created configuration transaction.
*
* transaction
* Configuration transaction to commit. It's consumed by this function.
*
* save_transaction
* Specify whether the transaction should be recorded in the transactions log
* or not.
*
* transaction_id
* Optional output parameter providing the ID of the committed transaction.
*/
extern void nb_candidate_commit_apply(struct nb_transaction *transaction,
bool save_transaction,
uint32_t *transaction_id);
/*
* Create a new transaction to commit a candidate configuration. This is a
* convenience function that performs the two-phase commit protocol
* transparently to the user. The cost is reduced flexibility, since
* network-wide and multi-daemon transactions require the network manager to
* take into account the results of the preparation phase of multiple managed
* entities.
*
* candidate
* Candidate configuration to commit. It's preserved regardless if the commit
* operation fails or not.
*
* client
* Northbound client performing the commit.
*
* save_transaction
* Specify whether the transaction should be recorded in the transactions log
* or not.
*
* comment
* Optional comment describing the commit.
*
* transaction_id
* Optional output parameter providing the ID of the committed transaction.
*
* Returns:
* - NB_OK on success.
* - NB_ERR_NO_CHANGES when the candidate is identical to the running
* configuration.
* - NB_ERR_LOCKED when there's already another transaction in progress.
* - NB_ERR_VALIDATION when the candidate fails the validation checks.
* - NB_ERR_RESOURCE when the system fails to allocate resources to apply
* the candidate configuration.
* - NB_ERR for other errors.
*/
extern int nb_candidate_commit(struct nb_config *candidate,
enum nb_client client, bool save_transaction,
const char *comment, uint32_t *transaction_id);
/*
* Validate if the northbound operation is valid for the given node.
*
* operation
* Operation we want to check.
*
* snode
* libyang schema node we want to check.
*
* Returns:
* true if the operation is valid, false otherwise.
*/
extern bool nb_operation_is_valid(enum nb_operation operation,
const struct lys_node *snode);
/*
* Send a YANG notification. This is a no-op unless the 'nb_notification_send'
* hook was registered by a northbound plugin.
*
* xpath
* XPath of the YANG notification.
*
* arguments
* Linked list containing the arguments that should be sent. This list is
* deleted after being used.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_notification_send(const char *xpath, struct list *arguments);
/*
* Return a human-readable string representing a northbound event.
*
* event
* Northbound event.
*
* Returns:
* String representation of the given northbound event.
*/
extern const char *nb_event_name(enum nb_event event);
/*
* Return a human-readable string representing a northbound operation.
*
* operation
* Northbound operation.
*
* Returns:
* String representation of the given northbound operation.
*/
extern const char *nb_operation_name(enum nb_operation operation);
/*
* Return a human-readable string representing a northbound error.
*
* error
* Northbound error.
*
* Returns:
* String representation of the given northbound error.
*/
extern const char *nb_err_name(enum nb_error error);
/*
* Return a human-readable string representing a northbound client.
*
* client
* Northbound client.
*
* Returns:
* String representation of the given northbound client.
*/
extern const char *nb_client_name(enum nb_client client);
/*
* Initialize the northbound layer. Should be called only once during the
* daemon initialization process.
*
* modules
* Array of YANG modules to parse and initialize.
*
* nmodules
* Size of the modules array.
*/
extern void nb_init(const struct frr_yang_module_info *modules[],
size_t nmodules);
/*
* Finish the northbound layer gracefully. Should be called only when the daemon
* is exiting.
*/
extern void nb_terminate(void);
#endif /* _FRR_NORTHBOUND_H_ */

1448
lib/northbound_cli.c Normal file

File diff suppressed because it is too large Load diff

66
lib/northbound_cli.h Normal file
View file

@ -0,0 +1,66 @@
/*
* 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
*/
#ifndef _FRR_NORTHBOUND_CLI_H_
#define _FRR_NORTHBOUND_CLI_H_
#include "northbound.h"
struct cli_config_change {
/*
* XPath (absolute or relative) of the configuration option being
* edited.
*/
char xpath[XPATH_MAXLEN];
/*
* Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or
* NB_OP_DELETE).
*/
enum nb_operation operation;
/*
* New value of the configuration option. Should be NULL for typeless
* YANG data (e.g. presence-containers). For convenience, NULL can also
* be used to restore a leaf to its default value.
*/
const char *value;
};
/* Possible formats in which a configuration can be displayed. */
enum nb_cfg_format {
NB_CFG_FMT_CMDS = 0,
NB_CFG_FMT_JSON,
NB_CFG_FMT_XML,
};
extern struct nb_config *vty_shared_candidate_config;
/* Prototypes. */
extern int nb_cli_cfg_change(struct vty *vty, char *xpath_list,
struct cli_config_change changes[], size_t size);
extern int nb_cli_rpc(const char *xpath, struct list *input,
struct list *output);
extern void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *dnode,
bool show_defaults);
extern void nb_cli_install_default(int node);
extern void nb_cli_init(void);
extern void nb_cli_terminate(void);
#endif /* _FRR_NORTHBOUND_CLI_H_ */

288
lib/northbound_db.c Normal file
View file

@ -0,0 +1,288 @@
/*
* 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 "libfrr.h"
#include "log.h"
#include "lib_errors.h"
#include "command.h"
#include "db.h"
#include "northbound.h"
#include "northbound_db.h"
int nb_db_init(void)
{
#ifdef HAVE_CONFIG_ROLLBACKS
/*
* NOTE: the delete_tail SQL trigger is used to implement a ring buffer
* where only the last N transactions are recorded in the configuration
* log.
*/
if (db_execute(
"BEGIN TRANSACTION;\n"
" CREATE TABLE IF NOT EXISTS transactions(\n"
" client CHAR(32) NOT NULL,\n"
" date DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
" comment CHAR(80) ,\n"
" configuration TEXT NOT NULL\n"
" );\n"
" CREATE TRIGGER IF NOT EXISTS delete_tail\n"
" AFTER INSERT ON transactions\n"
" FOR EACH ROW\n"
" BEGIN\n"
" DELETE\n"
" FROM\n"
" transactions\n"
" WHERE\n"
" rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
" END;\n"
"COMMIT;",
NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS)
!= 0)
return NB_ERR;
#endif /* HAVE_CONFIG_ROLLBACKS */
return NB_OK;
}
int nb_db_transaction_save(const struct nb_transaction *transaction,
uint32_t *transaction_id)
{
#ifdef HAVE_CONFIG_ROLLBACKS
struct sqlite3_stmt *ss;
const char *client_name;
char *config_str = NULL;
int ret = NB_ERR;
/*
* Use a transaction to ensure consistency between the INSERT and SELECT
* queries.
*/
if (db_execute("BEGIN TRANSACTION;") != 0)
return NB_ERR;
ss = db_prepare(
"INSERT INTO transactions\n"
" (client, comment, configuration)\n"
"VALUES\n"
" (?, ?, ?);");
if (!ss)
goto exit;
client_name = nb_client_name(transaction->client);
/* Always record configurations in the XML format. */
if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
LYP_FORMAT | LYP_WITHSIBLINGS)
!= 0)
goto exit;
if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name),
transaction->comment, strlen(transaction->comment),
config_str ? config_str : "",
config_str ? strlen(config_str) : 0)
!= 0)
goto exit;
if (db_run(ss) != SQLITE_OK)
goto exit;
db_finalize(&ss);
/*
* transaction_id is an optional output parameter that provides the ID
* of the recorded transaction.
*/
if (transaction_id) {
ss = db_prepare("SELECT last_insert_rowid();");
if (!ss)
goto exit;
if (db_run(ss) != SQLITE_ROW)
goto exit;
if (db_loadf(ss, "%i", transaction_id) != 0)
goto exit;
db_finalize(&ss);
}
if (db_execute("COMMIT;") != 0)
goto exit;
ret = NB_OK;
exit:
if (config_str)
free(config_str);
if (ss)
db_finalize(&ss);
if (ret != NB_OK)
(void)db_execute("ROLLBACK TRANSACTION;");
return ret;
#else /* HAVE_CONFIG_ROLLBACKS */
return NB_OK;
#endif /* HAVE_CONFIG_ROLLBACKS */
}
struct nb_config *nb_db_transaction_load(uint32_t transaction_id)
{
struct nb_config *config = NULL;
#ifdef HAVE_CONFIG_ROLLBACKS
struct lyd_node *dnode;
const char *config_str;
struct sqlite3_stmt *ss;
ss = db_prepare(
"SELECT\n"
" configuration\n"
"FROM\n"
" transactions\n"
"WHERE\n"
" rowid=?;");
if (!ss)
return NULL;
if (db_bindf(ss, "%d", transaction_id) != 0)
goto exit;
if (db_run(ss) != SQLITE_ROW)
goto exit;
if (db_loadf(ss, "%s", &config_str) != 0)
goto exit;
dnode = lyd_parse_mem(ly_native_ctx, config_str, LYD_XML,
LYD_OPT_CONFIG);
if (!dnode)
flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed",
__func__);
else
config = nb_config_new(dnode);
exit:
db_finalize(&ss);
#endif /* HAVE_CONFIG_ROLLBACKS */
return config;
}
int nb_db_clear_transactions(unsigned int n_oldest)
{
#ifdef HAVE_CONFIG_ROLLBACKS
/* Delete oldest N entries. */
if (db_execute("DELETE\n"
"FROM\n"
" transactions\n"
"WHERE\n"
" ROWID IN (\n"
" SELECT\n"
" ROWID\n"
" FROM\n"
" transactions\n"
" ORDER BY ROWID ASC LIMIT %u\n"
" );",
n_oldest)
!= 0)
return NB_ERR;
#endif /* HAVE_CONFIG_ROLLBACKS */
return NB_OK;
}
int nb_db_set_max_transactions(unsigned int max)
{
#ifdef HAVE_CONFIG_ROLLBACKS
/*
* Delete old entries if necessary and update the SQL trigger that
* auto-deletes old entries.
*/
if (db_execute("BEGIN TRANSACTION;\n"
" DELETE\n"
" FROM\n"
" transactions\n"
" WHERE\n"
" ROWID IN (\n"
" SELECT\n"
" ROWID\n"
" FROM\n"
" transactions\n"
" ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n"
" );\n"
" DROP TRIGGER delete_tail;\n"
" CREATE TRIGGER delete_tail\n"
" AFTER INSERT ON transactions\n"
" FOR EACH ROW\n"
" BEGIN\n"
" DELETE\n"
" FROM\n"
" transactions\n"
" WHERE\n"
" rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n"
" END;\n"
"COMMIT;",
max, max, max)
!= 0)
return NB_ERR;
#endif /* HAVE_CONFIG_ROLLBACKS */
return NB_OK;
}
int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id,
const char *client_name,
const char *date,
const char *comment),
void *arg)
{
#ifdef HAVE_CONFIG_ROLLBACKS
struct sqlite3_stmt *ss;
/* Send SQL query and parse the result. */
ss = db_prepare(
"SELECT\n"
" rowid, client, date, comment\n"
"FROM\n"
" transactions\n"
"ORDER BY\n"
" rowid DESC;");
if (!ss)
return NB_ERR;
while (db_run(ss) == SQLITE_ROW) {
int transaction_id;
const char *client_name;
const char *date;
const char *comment;
int ret;
ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name,
&date, &comment);
if (ret != 0)
continue;
(*func)(arg, transaction_id, client_name, date, comment);
}
db_finalize(&ss);
#endif /* HAVE_CONFIG_ROLLBACKS */
return NB_OK;
}

104
lib/northbound_db.h Normal file
View file

@ -0,0 +1,104 @@
/*
* 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
*/
#ifndef _FRR_NORTHBOUND_DB_H_
#define _FRR_NORTHBOUND_DB_H_
#include "northbound.h"
/*
* Initialize the northbound database.
*
* Currently the database is used only for storing and retrieving configuration
* transactions.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
int nb_db_init(void);
/*
* Save a configuration transaction in the northbound database.
*
* transaction
* Configuration transaction to be saved.
*
* transaction_id
* Output parameter providing the ID of the saved transaction.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
int nb_db_transaction_save(const struct nb_transaction *transaction,
uint32_t *transaction_id);
/*
* Load a configuration transaction from the transactions log.
*
* transaction_id
* ID of the transaction to be loaded.
*
* Returns:
* Pointer to newly created configuration or NULL in the case of an error.
*/
extern struct nb_config *nb_db_transaction_load(uint32_t transaction_id);
/*
* Delete the specified number of transactions from the transactions log.
*
* n_oldest
* Number of transactions to delete.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_db_clear_transactions(unsigned int n_oldest);
/*
* Specify the maximum number of transactions we want to record in the
* transactions log. Note that older transactions can be removed during this
* operation.
*
* max
* New upper limit of maximum transactions to log.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_db_set_max_transactions(unsigned int max);
/*
* Iterate over all configuration transactions stored in the northbound
* database, sorted in descending order.
*
* func
* Function to call with each configuration transaction.
*
* arg
* Arbitrary argument passed as the first parameter in each call to 'func'.
*
* Returns:
* NB_OK on success, NB_ERR otherwise.
*/
extern int nb_db_transactions_iterate(
void (*func)(void *arg, int transaction_id, const char *client_name,
const char *date, const char *comment),
void *arg);
#endif /* _FRR_NORTHBOUND_DB_H_ */

View file

@ -3,7 +3,7 @@
#
lib_LTLIBRARIES += lib/libfrr.la
lib_libfrr_la_LDFLAGS = -version-info 0:0:0 -Xlinker -e_libfrr_version
lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS)
lib_libfrr_la_LIBADD = @LIBCAP@ $(UNWIND_LIBS) -lyang
lib_libfrr_la_SOURCES = \
lib/agg_table.c \
@ -50,6 +50,9 @@ lib_libfrr_la_SOURCES = \
lib/netns_linux.c \
lib/netns_other.c \
lib/nexthop_group.c \
lib/northbound.c \
lib/northbound_cli.c \
lib/northbound_db.c \
lib/openbsd-tree.c \
lib/pid_output.c \
lib/plist.c \
@ -80,6 +83,9 @@ lib_libfrr_la_SOURCES = \
lib/vty.c \
lib/wheel.c \
lib/workqueue.c \
lib/yang.c \
lib/yang_translator.c \
lib/yang_wrappers.c \
lib/zclient.c \
lib/logicalrouter.c \
lib/lua.c \
@ -101,10 +107,17 @@ vtysh_scan += \
# can be loaded as DSO - always include for vtysh
vtysh_scan += $(top_srcdir)/lib/agentx.c
if SQLITE3
lib_libfrr_la_LIBADD += -lsqlite3
lib_libfrr_la_SOURCES += lib/db.c
endif
lib/plist_clippy.c: $(CLIPPY_DEPS)
lib/plist.lo: lib/plist_clippy.c
lib/nexthop_group_clippy.c: $(CLIPPY_DEPS)
lib/nexthop_group.lo: lib/nexthop_group_clippy.c
lib/northbound_cli_clippy.c: $(CLIPPY_DEPS)
lib/northbound_cli.lo: lib/northbound_cli_clippy.c
pkginclude_HEADERS += \
lib/agg_table.h \
@ -117,6 +130,7 @@ pkginclude_HEADERS += \
lib/command_match.h \
lib/compiler.h \
lib/csv.h \
lib/db.h \
lib/debug.h \
lib/distribute.h \
lib/event_counter.h \
@ -152,6 +166,9 @@ pkginclude_HEADERS += \
lib/network.h \
lib/nexthop.h \
lib/nexthop_group.h \
lib/northbound.h \
lib/northbound_cli.h \
lib/northbound_db.h \
lib/ns.h \
lib/openbsd-queue.h \
lib/openbsd-tree.h \
@ -187,6 +204,9 @@ pkginclude_HEADERS += \
lib/vxlan.h \
lib/wheel.h \
lib/workqueue.h \
lib/yang.h \
lib/yang_translator.h \
lib/yang_wrappers.h \
lib/zassert.h \
lib/zclient.h \
lib/zebra.h \

View file

@ -41,6 +41,7 @@
#include "libfrr.h"
#include "frrstr.h"
#include "lib_errors.h"
#include "northbound_cli.h"
#include <arpa/telnet.h>
#include <termios.h>
@ -89,6 +90,9 @@ char *vty_cwd = NULL;
static int vty_config;
static int vty_config_is_lockless = 0;
/* Exclusive configuration lock. */
struct vty *vty_exclusive_lock;
/* Login password check. */
static int no_password_check = 0;
@ -828,6 +832,8 @@ static void vty_end_config(struct vty *vty)
break;
}
vty->xpath_index = 0;
vty_prompt(vty);
vty->cp = 0;
}
@ -1718,6 +1724,10 @@ static struct vty *vty_new_init(int vty_sock)
memset(vty->hist, 0, sizeof(vty->hist));
vty->hp = 0;
vty->hindex = 0;
vty->xpath_index = 0;
memset(vty->xpath, 0, sizeof(vty->xpath));
vty->private_config = false;
vty->candidate_config = vty_shared_candidate_config;
vector_set_index(vtyvec, vty_sock, vty);
vty->status = VTY_NORMAL;
vty->lines = -1;
@ -2372,7 +2382,7 @@ static int vty_timeout(struct thread *thread)
}
/* Read up configuration file from file_name. */
static void vty_read_file(FILE *confp)
static void vty_read_file(struct nb_config *config, FILE *confp)
{
int ret;
struct vty *vty;
@ -2391,6 +2401,12 @@ static void vty_read_file(FILE *confp)
vty->wfd = STDERR_FILENO;
vty->type = VTY_FILE;
vty->node = CONFIG_NODE;
if (config)
vty->candidate_config = config;
else {
vty->private_config = true;
vty->candidate_config = nb_config_new(NULL);
}
/* Execute configuration file */
ret = config_from_file(vty, confp, &line_num);
@ -2436,6 +2452,22 @@ static void vty_read_file(FILE *confp)
}
}
/*
* Automatically commit the candidate configuration after
* reading the configuration file.
*/
if (config == NULL && vty->candidate_config
&& frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
int ret;
ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
true, "Read configuration file",
NULL);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
zlog_err("%s: failed to read configuration file.",
__func__);
}
vty_close(vty);
}
@ -2494,7 +2526,8 @@ static FILE *vty_use_backup_config(const char *fullpath)
}
/* Read up configuration file from file_name. */
bool vty_read_config(const char *config_file, char *config_default_dir)
bool vty_read_config(struct nb_config *config, const char *config_file,
char *config_default_dir)
{
char cwd[MAXPATHLEN];
FILE *confp = NULL;
@ -2508,9 +2541,9 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
if (getcwd(cwd, MAXPATHLEN) == NULL) {
flog_err_sys(
EC_LIB_SYSTEM_CALL,
"Failure to determine Current Working Directory %d!",
errno);
exit(1);
"%s: failure to determine Current Working Directory %d!",
__func__, errno);
goto tmp_free_and_out;
}
tmp = XMALLOC(MTYPE_TMP,
strlen(cwd) + strlen(config_file) + 2);
@ -2533,10 +2566,11 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
EC_LIB_BACKUP_CONFIG,
"WARNING: using backup configuration file!");
else {
flog_err(EC_LIB_VTY,
"can't open configuration file [%s]",
config_file);
exit(1);
flog_err(
EC_LIB_VTY,
"%s: can't open configuration file [%s]",
__func__, config_file);
goto tmp_free_and_out;
}
}
} else {
@ -2593,7 +2627,7 @@ bool vty_read_config(const char *config_file, char *config_default_dir)
fullpath = config_default_dir;
}
vty_read_file(confp);
vty_read_file(config, confp);
read_success = true;
fclose(confp);
@ -2671,6 +2705,18 @@ int vty_config_lock(struct vty *vty)
int vty_config_unlock(struct vty *vty)
{
vty_config_exclusive_unlock(vty);
if (vty->candidate_config) {
if (vty->private_config)
nb_config_free(vty->candidate_config);
vty->candidate_config = NULL;
}
if (vty->candidate_config_base) {
nb_config_free(vty->candidate_config_base);
vty->candidate_config_base = NULL;
}
if (vty_config_is_lockless)
return 0;
if (vty_config == 1 && vty->config == 1) {
@ -2685,6 +2731,21 @@ void vty_config_lockless(void)
vty_config_is_lockless = 1;
}
int vty_config_exclusive_lock(struct vty *vty)
{
if (vty_exclusive_lock == NULL) {
vty_exclusive_lock = vty;
return 1;
}
return 0;
}
void vty_config_exclusive_unlock(struct vty *vty)
{
if (vty_exclusive_lock == vty)
vty_exclusive_lock = NULL;
}
/* Master of the threads. */
static struct thread_master *vty_master;

View file

@ -29,9 +29,11 @@
#include "sockunion.h"
#include "qobj.h"
#include "compiler.h"
#include "northbound.h"
#define VTY_BUFSIZ 4096
#define VTY_MAXHIST 20
#define VTY_MAXDEPTH 8
struct vty_error {
char error_buf[VTY_BUFSIZ];
@ -96,6 +98,19 @@ struct vty {
/* History insert end point */
int hindex;
/* XPath of the current node */
int xpath_index;
char xpath[VTY_MAXDEPTH][XPATH_MAXLEN];
/* Private candidate configuration mode. */
bool private_config;
/* Candidate configuration. */
struct nb_config *candidate_config;
/* Base candidate configuration. */
struct nb_config *candidate_config_base;
/* qobj object ID (replacement for "index") */
uint64_t qobj_index;
@ -201,6 +216,34 @@ static inline void vty_push_context(struct vty *vty, int node, uint64_t id)
struct structname *ptr = VTY_GET_CONTEXT(structname); \
VTY_CHECK_CONTEXT(ptr);
/* XPath macros. */
#define VTY_PUSH_XPATH(nodeval, value) \
do { \
if (vty->xpath_index >= VTY_MAXDEPTH) { \
vty_out(vty, "%% Reached maximum CLI depth (%u)\n", \
VTY_MAXDEPTH); \
return CMD_WARNING; \
} \
vty->node = nodeval; \
strlcpy(vty->xpath[vty->xpath_index], value, \
sizeof(vty->xpath[0])); \
vty->xpath_index++; \
} while (0)
#define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1]
#define VTY_CHECK_XPATH \
do { \
if (vty->xpath_index > 0 \
&& !yang_dnode_exists(vty->candidate_config->dnode, \
VTY_CURR_XPATH)) { \
vty_out(vty, \
"Current configuration object was deleted " \
"by another process.\n\n"); \
return CMD_WARNING; \
} \
} while (0)
struct vty_arg {
const char *name;
const char *value;
@ -228,6 +271,7 @@ struct vty_arg {
/* Exported variables */
extern char integrate_default[];
extern struct vty *vty_exclusive_lock;
/* Prototypes. */
extern void vty_init(struct thread_master *);
@ -247,7 +291,8 @@ extern void vty_frame(struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3);
extern void vty_endframe(struct vty *, const char *);
bool vty_set_include(struct vty *vty, const char *regexp);
extern bool vty_read_config(const char *, char *);
extern bool vty_read_config(struct nb_config *config, const char *config_file,
char *config_default_dir);
extern void vty_time_print(struct vty *, int);
extern void vty_serv_sock(const char *, unsigned short, const char *);
extern void vty_close(struct vty *);
@ -257,6 +302,8 @@ extern void vty_log(const char *level, const char *proto, const char *fmt,
extern int vty_config_lock(struct vty *);
extern int vty_config_unlock(struct vty *);
extern void vty_config_lockless(void);
extern int vty_config_exclusive_lock(struct vty *vty);
extern void vty_config_exclusive_unlock(struct vty *vty);
extern int vty_shell(struct vty *);
extern int vty_shell_serv(struct vty *);
extern void vty_hello(struct vty *);

618
lib/yang.c Normal file
View file

@ -0,0 +1,618 @@
/*
* 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 "log.h"
#include "log_int.h"
#include "lib_errors.h"
#include "yang.h"
#include "yang_translator.h"
#include "northbound.h"
DEFINE_MTYPE(LIB, YANG_MODULE, "YANG module")
DEFINE_MTYPE(LIB, YANG_DATA, "YANG data structure")
/* libyang container. */
struct ly_ctx *ly_native_ctx;
/* Generate the yang_modules tree. */
static inline int yang_module_compare(const struct yang_module *a,
const struct yang_module *b)
{
return strcmp(a->name, b->name);
}
RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare)
struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules);
struct yang_module *yang_module_load(const char *module_name)
{
struct yang_module *module;
const struct lys_module *module_info;
module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL);
if (!module_info) {
flog_err(EC_LIB_YANG_MODULE_LOAD,
"%s: failed to load data model: %s", __func__,
module_name);
exit(1);
}
module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module));
module->name = module_name;
module->info = module_info;
if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) {
flog_err(EC_LIB_YANG_MODULE_LOADED_ALREADY,
"%s: YANG module is loaded already: %s", __func__,
module_name);
exit(1);
}
return module;
}
struct yang_module *yang_module_find(const char *module_name)
{
struct yang_module s;
s.name = module_name;
return RB_FIND(yang_modules, &yang_modules, &s);
}
/*
* Helper function for yang_module_snodes_iterate() and
* yang_all_snodes_iterate(). This is a recursive function.
*/
static void yang_snodes_iterate(const struct lys_node *snode,
void (*func)(const struct lys_node *, void *,
void *),
uint16_t flags, void *arg1, void *arg2)
{
struct lys_node *child;
if (CHECK_FLAG(flags, YANG_ITER_FILTER_IMPLICIT)) {
switch (snode->nodetype) {
case LYS_CASE:
case LYS_INPUT:
case LYS_OUTPUT:
if (snode->flags & LYS_IMPLICIT)
goto next;
break;
default:
break;
}
}
switch (snode->nodetype) {
case LYS_CONTAINER:
if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) {
struct lys_node_container *scontainer;
scontainer = (struct lys_node_container *)snode;
if (!scontainer->presence)
goto next;
}
break;
case LYS_LEAF:
if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) {
struct lys_node_leaf *sleaf;
/* Ignore list keys. */
sleaf = (struct lys_node_leaf *)snode;
if (lys_is_key(sleaf, NULL))
goto next;
}
break;
case LYS_GROUPING:
/* Return since we're not interested in the grouping subtree. */
return;
case LYS_USES:
case LYS_AUGMENT:
/* Always ignore nodes of these types. */
goto next;
case LYS_INPUT:
case LYS_OUTPUT:
if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT))
goto next;
break;
default:
break;
}
(*func)(snode, arg1, arg2);
next:
/*
* YANG leafs and leaf-lists can't have child nodes, and trying to
* access snode->child is undefined behavior.
*/
if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
return;
LY_TREE_FOR (snode->child, child) {
if (child->parent != snode)
continue;
yang_snodes_iterate(child, func, flags, arg1, arg2);
}
}
void yang_module_snodes_iterate(const struct lys_module *module,
void (*func)(const struct lys_node *, void *,
void *),
uint16_t flags, void *arg1, void *arg2)
{
struct lys_node *snode;
LY_TREE_FOR (module->data, snode) {
yang_snodes_iterate(snode, func, flags, arg1, arg2);
}
for (uint8_t i = 0; i < module->augment_size; i++) {
yang_snodes_iterate(
(const struct lys_node *)&module->augment[i], func,
flags, arg1, arg2);
}
}
void yang_all_snodes_iterate(void (*func)(const struct lys_node *, void *,
void *),
uint16_t flags, void *arg1, void *arg2)
{
struct yang_module *module;
RB_FOREACH (module, yang_modules, &yang_modules)
yang_module_snodes_iterate(module->info, func, flags, arg1,
arg2);
}
void yang_snode_get_path(const struct lys_node *snode, enum yang_path_type type,
char *xpath, size_t xpath_len)
{
char *xpath_ptr;
switch (type) {
case YANG_PATH_SCHEMA:
xpath_ptr = lys_path(snode, 0);
break;
case YANG_PATH_DATA:
xpath_ptr = lys_data_path(snode);
break;
default:
flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u",
__func__, type);
exit(1);
}
strlcpy(xpath, xpath_ptr, xpath_len);
free(xpath_ptr);
}
struct lys_node *yang_snode_real_parent(const struct lys_node *snode)
{
struct lys_node *parent = snode->parent;
while (parent) {
struct lys_node_container *scontainer;
switch (parent->nodetype) {
case LYS_CONTAINER:
scontainer = (struct lys_node_container *)parent;
if (scontainer->presence)
return parent;
break;
case LYS_LIST:
return parent;
default:
break;
}
parent = parent->parent;
}
return NULL;
}
struct lys_node *yang_snode_parent_list(const struct lys_node *snode)
{
struct lys_node *parent = snode->parent;
while (parent) {
switch (parent->nodetype) {
case LYS_LIST:
return parent;
default:
break;
}
parent = parent->parent;
}
return NULL;
}
bool yang_snode_is_typeless_data(const struct lys_node *snode)
{
struct lys_node_leaf *sleaf;
switch (snode->nodetype) {
case LYS_LEAF:
sleaf = (struct lys_node_leaf *)snode;
if (sleaf->type.base == LY_TYPE_EMPTY)
return true;
return false;
case LYS_LEAFLIST:
return false;
default:
return true;
}
}
const char *yang_snode_get_default(const struct lys_node *snode)
{
struct lys_node_leaf *sleaf;
switch (snode->nodetype) {
case LYS_LEAF:
sleaf = (struct lys_node_leaf *)snode;
/* NOTE: this might be null. */
return sleaf->dflt;
case LYS_LEAFLIST:
/* TODO: check leaf-list default values */
return NULL;
default:
return NULL;
}
}
const struct lys_type *yang_snode_get_type(const struct lys_node *snode)
{
struct lys_node_leaf *sleaf = (struct lys_node_leaf *)snode;
struct lys_type *type;
if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)))
return NULL;
type = &sleaf->type;
while (type->base == LY_TYPE_LEAFREF)
type = &type->info.lref.target->type;
return type;
}
void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
size_t xpath_len)
{
char *xpath_ptr;
xpath_ptr = lyd_path(dnode);
strlcpy(xpath, xpath_ptr, xpath_len);
free(xpath_ptr);
}
struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
va_list ap;
char xpath[XPATH_MAXLEN];
struct ly_set *set;
struct lyd_node *dnode_ret = NULL;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
set = lyd_find_path(dnode, xpath);
assert(set);
if (set->number == 0)
goto exit;
if (set->number > 1) {
flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND,
"%s: found %u elements (expected 0 or 1) [xpath %s]",
__func__, set->number, xpath);
goto exit;
}
dnode_ret = set->set.d[0];
exit:
ly_set_free(set);
return dnode_ret;
}
bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath_fmt, ...)
{
va_list ap;
char xpath[XPATH_MAXLEN];
struct ly_set *set;
bool found;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
set = lyd_find_path(dnode, xpath);
assert(set);
found = (set->number > 0);
ly_set_free(set);
return found;
}
bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath_fmt,
...)
{
struct lys_node *snode;
struct lys_node_leaf *sleaf;
struct lys_node_container *scontainer;
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
}
assert(dnode);
snode = dnode->schema;
switch (snode->nodetype) {
case LYS_LEAF:
sleaf = (struct lys_node_leaf *)snode;
if (sleaf->type.base == LY_TYPE_EMPTY)
return false;
return lyd_wd_default((struct lyd_node_leaf_list *)dnode);
case LYS_LEAFLIST:
/* TODO: check leaf-list default values */
return false;
case LYS_CONTAINER:
scontainer = (struct lys_node_container *)snode;
if (scontainer->presence)
return false;
return true;
default:
return false;
}
}
bool yang_dnode_is_default_recursive(const struct lyd_node *dnode)
{
struct lys_node *snode;
struct lyd_node *root, *next, *dnode_iter;
snode = dnode->schema;
if (snode->nodetype & (LYS_LEAF | LYS_LEAFLIST))
return yang_dnode_is_default(dnode, NULL);
if (!yang_dnode_is_default(dnode, NULL))
return false;
LY_TREE_FOR (dnode->child, root) {
LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
if (!yang_dnode_is_default(dnode_iter, NULL))
return false;
LY_TREE_DFS_END(root, next, dnode_iter);
}
}
return true;
}
void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value)
{
assert(dnode->schema->nodetype == LYS_LEAF);
lyd_change_leaf((struct lyd_node_leaf_list *)dnode, value);
}
void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry)
{
assert(dnode->schema->nodetype & (LYS_LIST | LYS_CONTAINER));
lyd_set_private(dnode, entry);
}
void *yang_dnode_get_entry(const struct lyd_node *dnode)
{
const struct lyd_node *orig_dnode = dnode;
char xpath[XPATH_MAXLEN];
while (dnode) {
switch (dnode->schema->nodetype) {
case LYS_CONTAINER:
case LYS_LIST:
if (dnode->priv)
return dnode->priv;
break;
default:
break;
}
dnode = dnode->parent;
}
yang_dnode_get_path(orig_dnode, xpath, sizeof(xpath));
flog_err(EC_LIB_YANG_DNODE_NOT_FOUND,
"%s: failed to find entry [xpath %s]", __func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx)
{
struct lyd_node *dnode;
dnode = NULL;
if (lyd_validate(&dnode, LYD_OPT_CONFIG, ly_ctx) != 0) {
/* Should never happen. */
flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__);
exit(1);
}
return dnode;
}
struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode)
{
return lyd_dup_withsiblings(dnode, 1);
}
void yang_dnode_free(struct lyd_node *dnode)
{
lyd_free_withsiblings(dnode);
}
struct yang_data *yang_data_new(const char *xpath, const char *value)
{
const struct lys_node *snode;
struct yang_data *data;
snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
if (!snode)
snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 1);
if (!snode) {
flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data));
strlcpy(data->xpath, xpath, sizeof(data->xpath));
data->snode = snode;
if (value)
data->value = strdup(value);
return data;
}
void yang_data_free(struct yang_data *data)
{
if (data->value)
free(data->value);
XFREE(MTYPE_YANG_DATA, data);
}
struct list *yang_data_list_new(void)
{
struct list *list;
list = list_new();
list->del = (void (*)(void *))yang_data_free;
return list;
}
static void *ly_dup_cb(const void *priv)
{
/* Make a shallow copy of the priv pointer. */
return (void *)priv;
}
/* Make libyang log its errors using FRR logging infrastructure. */
static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
{
int priority;
switch (level) {
case LY_LLERR:
priority = LOG_ERR;
break;
case LY_LLWRN:
priority = LOG_WARNING;
break;
case LY_LLVRB:
priority = LOG_DEBUG;
break;
default:
return;
}
if (path)
zlog(priority, "libyang: %s (%s)", msg, path);
else
zlog(priority, "libyang: %s", msg);
}
void yang_init(void)
{
static char ly_plugin_dir[PATH_MAX];
const char *const *ly_loaded_plugins;
const char *ly_plugin;
bool found_ly_frr_types = false;
/* Tell libyang where to find its plugins. */
snprintf(ly_plugin_dir, sizeof(ly_plugin_dir), "%s=%s",
"LIBYANG_USER_TYPES_PLUGINS_DIR", LIBYANG_PLUGINS_PATH);
putenv(ly_plugin_dir);
/* Initialize libyang global parameters that affect all containers. */
ly_set_log_clb(ly_log_cb, 1);
ly_log_options(LY_LOLOG | LY_LOSTORE);
/* Initialize libyang container for native models. */
ly_native_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
if (!ly_native_ctx) {
flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
exit(1);
}
ly_ctx_set_searchdir(ly_native_ctx, YANG_MODELS_PATH);
ly_ctx_set_priv_dup_clb(ly_native_ctx, ly_dup_cb);
/* Detect if the required libyang plugin(s) were loaded successfully. */
ly_loaded_plugins = ly_get_loaded_plugins();
for (size_t i = 0; (ly_plugin = ly_loaded_plugins[i]); i++) {
if (strmatch(ly_plugin, "frr_user_types")) {
found_ly_frr_types = true;
break;
}
}
if (!found_ly_frr_types) {
flog_err(EC_LIB_LIBYANG_PLUGIN_LOAD,
"%s: failed to load frr_user_types.so", __func__);
exit(1);
}
yang_translator_init();
}
void yang_terminate(void)
{
struct yang_module *module;
yang_translator_terminate();
while (!RB_EMPTY(yang_modules, &yang_modules)) {
module = RB_ROOT(yang_modules, &yang_modules);
/*
* We shouldn't call ly_ctx_remove_module() here because this
* function also removes other modules that depend on it.
*
* ly_ctx_destroy() will release all memory for us.
*/
RB_REMOVE(yang_modules, &yang_modules, module);
XFREE(MTYPE_YANG_MODULE, module);
}
ly_ctx_destroy(ly_native_ctx, NULL);
}

439
lib/yang.h Normal file
View file

@ -0,0 +1,439 @@
/*
* 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
*/
#ifndef _FRR_YANG_H_
#define _FRR_YANG_H_
#include "memory.h"
#include <libyang/libyang.h>
#ifdef HAVE_SYSREPO
#include <sysrepo.h>
#endif
#include "yang_wrappers.h"
DECLARE_MTYPE(YANG_MODULE)
DECLARE_MTYPE(YANG_DATA)
/* Maximum XPath length. */
#define XPATH_MAXLEN 256
/* Maximum list key length. */
#define LIST_MAXKEYS 8
/* Maximum list key length. */
#define LIST_MAXKEYLEN 128
/* Maximum string length of an YANG value. */
#define YANG_VALUE_MAXLEN 1024
struct yang_module {
RB_ENTRY(yang_module) entry;
const char *name;
const struct lys_module *info;
#ifdef HAVE_CONFD
int confd_hash;
#endif
#ifdef HAVE_SYSREPO
sr_subscription_ctx_t *sr_subscription;
#endif
};
RB_HEAD(yang_modules, yang_module);
RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare);
struct yang_data {
/* XPath identifier of the data element. */
char xpath[XPATH_MAXLEN];
/*
* Schema information (necessary to interpret certain values like
* enums).
*/
const struct lys_node *snode;
/* Value encoded as a raw string. */
char *value;
};
struct yang_list_keys {
/* Number os keys (max: LIST_MAXKEYS). */
uint8_t num;
struct {
/*
* Schema information (necessary to interpret certain values
* like enums).
*/
struct lys_node *snode;
/* Value encoded as a raw string. */
char value[LIST_MAXKEYLEN];
} key[LIST_MAXKEYS];
};
enum yang_path_type {
YANG_PATH_SCHEMA = 0,
YANG_PATH_DATA,
};
/* Filter non-presence containers. */
#define YANG_ITER_FILTER_NPCONTAINERS 0x0001
/* Filter list keys (leafs). */
#define YANG_ITER_FILTER_LIST_KEYS 0x0002
/* Filter RPC input/output nodes. */
#define YANG_ITER_FILTER_INPUT_OUTPUT 0x0004
/* Filter implicitely created nodes. */
#define YANG_ITER_FILTER_IMPLICIT 0x0008
/* Global libyang context for native FRR models. */
extern struct ly_ctx *ly_native_ctx;
/* Tree of all loaded YANG modules. */
extern struct yang_modules yang_modules;
/*
* Create a new YANG module and load it using libyang. If the YANG module is not
* found in the YANG_MODELS_PATH directory, the program will exit with an error.
* Once loaded, a YANG module can't be unloaded anymore.
*
* module_name
* Name of the YANG module.
*
* Returns:
* Pointer to newly created YANG module.
*/
extern struct yang_module *yang_module_load(const char *module_name);
/*
* Find a YANG module by its name.
*
* module_name
* Name of the YANG module.
*
* Returns:
* Pointer to YANG module if found, NULL otherwise.
*/
extern struct yang_module *yang_module_find(const char *module_name);
/*
* Iterate over all libyang schema nodes from the given YANG module.
*
* module
* YANG module to operate on.
*
* func
* Function to call with each schema node.
*
* flags
* YANG_ITER_FILTER_* flags to specify node types that should be filtered.
*
* arg1
* Arbitrary argument passed as the second parameter in each call to 'func'.
*
* arg2
* Arbitrary argument passed as the third parameter in each call to 'func'.
*/
extern void yang_module_snodes_iterate(const struct lys_module *module,
void (*func)(const struct lys_node *,
void *, void *),
uint16_t flags, void *arg1, void *arg2);
/*
* Iterate over all libyang schema nodes from all loaded YANG modules.
*
* func
* Function to call with each schema node.
*
* flags
* YANG_ITER_FILTER_* flags to specify node types that should be filtered.
*
* arg1
* Arbitrary argument passed as the second parameter in each call to 'func'.
*
* arg2
* Arbitrary argument passed as the third parameter in each call to 'func'.
*/
extern void yang_all_snodes_iterate(void (*func)(const struct lys_node *,
void *, void *),
uint16_t flags, void *arg1, void *arg2);
/*
* Build schema path or data path of the schema node.
*
* snode
* libyang schema node to be processed.
*
* type
* Specify whether a schema path or a data path should be built.
*
* xpath
* Pointer to previously allocated buffer.
*
* xpath_len
* Size of the xpath buffer.
*/
extern void yang_snode_get_path(const struct lys_node *snode,
enum yang_path_type type, char *xpath,
size_t xpath_len);
/*
* Find first parent schema node which is a presence-container or a list
* (non-presence containers are ignored).
*
* snode
* libyang schema node to operate on.
*
* Returns:
* The parent libyang schema node if found, or NULL if not found.
*/
extern struct lys_node *yang_snode_real_parent(const struct lys_node *snode);
/*
* Find first parent schema node which is a list.
*
* snode
* libyang schema node to operate on.
*
* Returns:
* The parent libyang schema node (list) if found, or NULL if not found.
*/
extern struct lys_node *yang_snode_parent_list(const struct lys_node *snode);
/*
* Check if the libyang schema node represents typeless data (e.g. containers,
* leafs of type empty, etc).
*
* snode
* libyang schema node to operate on.
*
* Returns:
* true if the schema node represents typeless data, false otherwise.
*/
extern bool yang_snode_is_typeless_data(const struct lys_node *snode);
/*
* Get the default value associated to a YANG leaf or leaf-list.
*
* snode
* libyang schema node to operate on.
*
* Returns:
* The default value if it exists, NULL otherwise.
*/
extern const char *yang_snode_get_default(const struct lys_node *snode);
/*
* Get the type structure of a leaf of leaf-list. If the type is a leafref, the
* final (if there is a chain of leafrefs) target's type is found.
*
* snode
* libyang schema node to operate on.
*
* Returns:
* The found type if the schema node represents a leaf or a leaf-list, NULL
* otherwise.
*/
extern const struct lys_type *yang_snode_get_type(const struct lys_node *snode);
/*
* Build data path of the data node.
*
* dnode
* libyang data node to be processed.
*
* xpath
* Pointer to previously allocated buffer.
*
* xpath_len
* Size of the xpath buffer.
*/
extern void yang_dnode_get_path(const struct lyd_node *dnode, char *xpath,
size_t xpath_len);
/*
* Find a libyang data node by its YANG data path.
*
* dnode
* Base libyang data node to operate on.
*
* xpath_fmt
* XPath expression (absolute or relative).
*
* Returns:
* The libyang data node if found, or NULL if not found.
*/
extern struct lyd_node *yang_dnode_get(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
/*
* Check if a libyang data node exists.
*
* dnode
* Base libyang data node to operate on.
*
* xpath_fmt
* XPath expression (absolute or relative).
*
* Returns:
* true if the libyang data node was found, false otherwise.
*/
extern bool yang_dnode_exists(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
/*
* Check if the libyang data node contains a default value. Non-presence
* containers are assumed to always contain a default value.
*
* dnode
* Base libyang data node to operate on.
*
* xpath_fmt
* Optional XPath expression (absolute or relative) to specify a different
* data node to operate on in the same data tree.
*
* Returns:
* true if the data node contains the default value, false otherwise.
*/
extern bool yang_dnode_is_default(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
/*
* Check if the libyang data node and all of its children contain default
* values. Non-presence containers are assumed to always contain a default
* value.
*
* dnode
* libyang data node to operate on.
*
* Returns:
* true if the data node and all of its children contain default values,
* false otherwise.
*/
extern bool yang_dnode_is_default_recursive(const struct lyd_node *dnode);
/*
* Change the value of a libyang leaf node.
*
* dnode
* libyang data node to operate on.
*
* value
* String representing the new value.
*/
extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value);
/*
* Set the libyang private pointer to a user pointer. Can only be used on YANG
* lists and containers.
*
* dnode
* libyang data node to operate on.
*
* entry
* Arbitrary user-specified pointer.
*/
extern void yang_dnode_set_entry(const struct lyd_node *dnode, void *entry);
/*
* Find the closest data node that contains an user pointer and return it.
*
* dnode
* libyang data node to operate on.
*
* Returns:
* User pointer if found, NULL otherwise.
*/
extern void *yang_dnode_get_entry(const struct lyd_node *dnode);
/*
* Create a new libyang data node.
*
* ly_ctx
* libyang context to operate on.
*
* Returns:
* Pointer to newly created libyang data node.
*/
extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx);
/*
* Duplicate a libyang data node.
*
* dnode
* libyang data node to duplicate.
*
* Returns:
* Pointer to duplicated libyang data node.
*/
extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
/*
* Delete a libyang data node.
*
* dnode
* Pointer to the libyang data node that is going to be deleted.
*/
extern void yang_dnode_free(struct lyd_node *dnode);
/*
* Create a new yang_data structure.
*
* xpath
* Data path of the YANG data.
*
* value
* String representing the value of the YANG data.
*
* Returns:
* Pointer to newly created yang_data structure.
*/
extern struct yang_data *yang_data_new(const char *xpath, const char *value);
/*
* Delete a yang_data structure.
*
* data
* yang_data to delete.
*/
extern void yang_data_free(struct yang_data *data);
/*
* Create a new linked list of yang_data structures. The list 'del' callback is
* initialized appropriately so that the entire list can be deleted safely with
* list_delete_and_null().
*
* Returns:
* Pointer to newly created linked list.
*/
extern struct list *yang_data_list_new(void);
/*
* Initialize the YANG subsystem. Should be called only once during the
* daemon initialization process.
*/
extern void yang_init(void);
/*
* Finish the YANG subsystem gracefully. Should be called only when the daemon
* is exiting.
*/
extern void yang_terminate(void);
#endif /* _FRR_YANG_H_ */

545
lib/yang_translator.c Normal file
View file

@ -0,0 +1,545 @@
/*
* 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 "log.h"
#include "lib_errors.h"
#include "hash.h"
#include "yang.h"
#include "yang_translator.h"
DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator")
DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module")
DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping")
/* Generate the yang_translators tree. */
static inline int yang_translator_compare(const struct yang_translator *a,
const struct yang_translator *b)
{
return strcmp(a->family, b->family);
}
RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare)
struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators);
/* Separate libyang context for the translator module. */
static struct ly_ctx *ly_translator_ctx;
static unsigned int
yang_translator_validate(struct yang_translator *translator);
static unsigned int yang_module_nodes_count(const struct lys_module *module);
static void str_replace(char *o_string, const char *s_string,
const char *r_string);
struct yang_mapping_node {
char xpath_from_canonical[XPATH_MAXLEN];
char xpath_from_fmt[XPATH_MAXLEN];
char xpath_to_fmt[XPATH_MAXLEN];
};
static bool yang_mapping_hash_cmp(const void *value1, const void *value2)
{
const struct yang_mapping_node *c1 = value1;
const struct yang_mapping_node *c2 = value2;
return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical);
}
static unsigned int yang_mapping_hash_key(void *value)
{
return string_hash_make(value);
}
static void *yang_mapping_hash_alloc(void *p)
{
struct yang_mapping_node *new, *key = p;
new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new));
strlcpy(new->xpath_from_canonical, key->xpath_from_canonical,
sizeof(new->xpath_from_canonical));
return new;
}
static void yang_mapping_hash_free(void *arg)
{
XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg);
}
static struct yang_mapping_node *
yang_mapping_lookup(const struct yang_translator *translator, int dir,
const char *xpath)
{
struct yang_mapping_node s;
strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical));
return hash_lookup(translator->mappings[dir], &s);
}
static void yang_mapping_add(struct yang_translator *translator, int dir,
const struct lys_node *snode,
const char *xpath_from_fmt,
const char *xpath_to_fmt)
{
struct yang_mapping_node *mapping, s;
yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical,
sizeof(s.xpath_from_canonical));
mapping = hash_get(translator->mappings[dir], &s,
yang_mapping_hash_alloc);
strlcpy(mapping->xpath_from_fmt, xpath_from_fmt,
sizeof(mapping->xpath_from_fmt));
strlcpy(mapping->xpath_to_fmt, xpath_to_fmt,
sizeof(mapping->xpath_to_fmt));
str_replace(mapping->xpath_from_fmt, "KEY1", "%[^']");
str_replace(mapping->xpath_from_fmt, "KEY2", "%[^']");
str_replace(mapping->xpath_from_fmt, "KEY3", "%[^']");
str_replace(mapping->xpath_from_fmt, "KEY4", "%[^']");
str_replace(mapping->xpath_to_fmt, "KEY1", "%s");
str_replace(mapping->xpath_to_fmt, "KEY2", "%s");
str_replace(mapping->xpath_to_fmt, "KEY3", "%s");
str_replace(mapping->xpath_to_fmt, "KEY4", "%s");
}
struct yang_translator *yang_translator_load(const char *path)
{
struct yang_translator *translator;
struct yang_tmodule *tmodule;
const char *family;
struct lyd_node *dnode;
struct ly_set *set;
struct listnode *ln;
/* Load module translator (JSON file). */
dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON,
LYD_OPT_CONFIG);
if (!dnode) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: lyd_parse_path() failed", __func__);
return NULL;
}
dnode = yang_dnode_get(dnode,
"/frr-module-translator:frr-module-translator");
/*
* libyang guarantees the "frr-module-translator" top-level container is
* always present since it contains mandatory child nodes.
*/
assert(dnode);
family = yang_dnode_get_string(dnode, "./family");
translator = yang_translator_find(family);
if (translator != NULL) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: module translator \"%s\" is loaded already",
__func__, family);
return NULL;
}
translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator));
strlcpy(translator->family, family, sizeof(translator->family));
translator->modules = list_new();
for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
translator->mappings[i] = hash_create(yang_mapping_hash_key,
yang_mapping_hash_cmp,
"YANG translation table");
RB_INSERT(yang_translators, &yang_translators, translator);
/* Initialize the translator libyang context. */
translator->ly_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
if (!translator->ly_ctx) {
flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
goto error;
}
ly_ctx_set_searchdir(translator->ly_ctx, YANG_MODELS_PATH);
/* Load modules and deviations. */
set = lyd_find_path(dnode, "./module");
assert(set);
for (size_t i = 0; i < set->number; i++) {
const char *module_name;
tmodule =
XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule));
module_name = yang_dnode_get_string(set->set.d[i], "./name");
tmodule->module = ly_ctx_load_module(translator->ly_ctx,
module_name, NULL);
if (!tmodule->module) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: failed to load module: %s", __func__,
module_name);
ly_set_free(set);
goto error;
}
module_name =
yang_dnode_get_string(set->set.d[i], "./deviations");
tmodule->deviations = ly_ctx_load_module(translator->ly_ctx,
module_name, NULL);
if (!tmodule->deviations) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: failed to load module: %s", __func__,
module_name);
ly_set_free(set);
goto error;
}
lys_set_disabled(tmodule->deviations);
listnode_add(translator->modules, tmodule);
}
ly_set_free(set);
/* Calculate the coverage. */
for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
tmodule->nodes_before_deviations =
yang_module_nodes_count(tmodule->module);
lys_set_enabled(tmodule->deviations);
tmodule->nodes_after_deviations =
yang_module_nodes_count(tmodule->module);
tmodule->coverage = ((double)tmodule->nodes_after_deviations
/ (double)tmodule->nodes_before_deviations)
* 100;
}
/* Load mappings. */
set = lyd_find_path(dnode, "./module/mappings");
assert(set);
for (size_t i = 0; i < set->number; i++) {
const char *xpath_custom, *xpath_native;
const struct lys_node *snode_custom, *snode_native;
xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom");
snode_custom = ly_ctx_get_node(translator->ly_ctx, NULL,
xpath_custom, 0);
if (!snode_custom) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: unknown data path: %s", __func__,
xpath_custom);
ly_set_free(set);
goto error;
}
xpath_native = yang_dnode_get_string(set->set.d[i], "./native");
snode_native =
ly_ctx_get_node(ly_native_ctx, NULL, xpath_native, 0);
if (!snode_native) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: unknown data path: %s", __func__,
xpath_native);
ly_set_free(set);
goto error;
}
yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE,
snode_custom, xpath_custom, xpath_native);
yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE,
snode_native, xpath_native, xpath_custom);
}
ly_set_free(set);
/* Validate mappings. */
if (yang_translator_validate(translator) != 0)
goto error;
yang_dnode_free(dnode);
return translator;
error:
yang_dnode_free(dnode);
yang_translator_unload(translator);
return NULL;
}
static void yang_tmodule_delete(struct yang_tmodule *tmodule)
{
XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule);
}
void yang_translator_unload(struct yang_translator *translator)
{
for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++)
hash_clean(translator->mappings[i], yang_mapping_hash_free);
translator->modules->del = (void (*)(void *))yang_tmodule_delete;
list_delete(&translator->modules);
ly_ctx_destroy(translator->ly_ctx, NULL);
RB_REMOVE(yang_translators, &yang_translators, translator);
XFREE(MTYPE_YANG_TRANSLATOR, translator);
}
struct yang_translator *yang_translator_find(const char *family)
{
struct yang_translator s;
strlcpy(s.family, family, sizeof(s.family));
return RB_FIND(yang_translators, &yang_translators, &s);
}
enum yang_translate_result
yang_translate_xpath(const struct yang_translator *translator, int dir,
char *xpath, size_t xpath_len)
{
struct ly_ctx *ly_ctx;
const struct lys_node *snode;
struct yang_mapping_node *mapping;
char xpath_canonical[XPATH_MAXLEN];
char keys[4][LIST_MAXKEYLEN];
int n;
if (dir == YANG_TRANSLATE_TO_NATIVE)
ly_ctx = translator->ly_ctx;
else
ly_ctx = ly_native_ctx;
snode = ly_ctx_get_node(ly_ctx, NULL, xpath, 0);
if (!snode) {
flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
"%s: unknown data path: %s", __func__, xpath);
return YANG_TRANSLATE_FAILURE;
}
yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical,
sizeof(xpath_canonical));
mapping = yang_mapping_lookup(translator, dir, xpath_canonical);
if (!mapping)
return YANG_TRANSLATE_NOTFOUND;
n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2],
keys[3]);
if (n < 0) {
flog_warn(EC_LIB_YANG_TRANSLATION_ERROR,
"%s: sscanf() failed: %s", __func__,
safe_strerror(errno));
return YANG_TRANSLATE_FAILURE;
}
snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1],
keys[2], keys[3]);
return YANG_TRANSLATE_SUCCESS;
}
int yang_translate_dnode(const struct yang_translator *translator, int dir,
struct lyd_node **dnode)
{
struct ly_ctx *ly_ctx;
struct lyd_node *new;
struct lyd_node *root, *next, *dnode_iter;
/* Create new libyang data node to hold the translated data. */
if (dir == YANG_TRANSLATE_TO_NATIVE)
ly_ctx = ly_native_ctx;
else
ly_ctx = translator->ly_ctx;
new = yang_dnode_new(ly_ctx);
/* Iterate over all nodes from the data tree. */
LY_TREE_FOR (*dnode, root) {
LY_TREE_DFS_BEGIN (root, next, dnode_iter) {
char xpath[XPATH_MAXLEN];
enum yang_translate_result ret;
yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath));
ret = yang_translate_xpath(translator, dir, xpath,
sizeof(xpath));
switch (ret) {
case YANG_TRANSLATE_SUCCESS:
break;
case YANG_TRANSLATE_NOTFOUND:
goto next;
case YANG_TRANSLATE_FAILURE:
goto error;
}
/* Create new node in the tree of translated data. */
ly_errno = 0;
if (!lyd_new_path(new, ly_ctx, xpath,
(void *)yang_dnode_get_string(
dnode_iter, NULL),
0, LYD_PATH_OPT_UPDATE)
&& ly_errno) {
flog_err(EC_LIB_LIBYANG,
"%s: lyd_new_path() failed", __func__);
goto error;
}
next:
LY_TREE_DFS_END(root, next, dnode_iter);
}
}
/* Replace dnode by the new translated dnode. */
yang_dnode_free(*dnode);
*dnode = new;
return YANG_TRANSLATE_SUCCESS;
error:
yang_dnode_free(new);
return YANG_TRANSLATE_FAILURE;
}
static void yang_translator_validate_cb(const struct lys_node *snode_custom,
void *arg1, void *arg2)
{
struct yang_translator *translator = arg1;
unsigned int *errors = arg2;
struct yang_mapping_node *mapping;
const struct lys_node *snode_native;
const struct lys_type *stype_custom, *stype_native;
char xpath[XPATH_MAXLEN];
yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath));
mapping = yang_mapping_lookup(translator, YANG_TRANSLATE_TO_NATIVE,
xpath);
if (!mapping) {
flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: missing mapping for \"%s\"", __func__, xpath);
*errors += 1;
return;
}
snode_native =
ly_ctx_get_node(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0);
assert(snode_native);
/* Check if the YANG types are compatible. */
stype_custom = yang_snode_get_type(snode_custom);
stype_native = yang_snode_get_type(snode_native);
if (stype_custom && stype_native) {
if (stype_custom->base != stype_native->base) {
flog_warn(
EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: YANG types are incompatible (xpath: \"%s\")",
__func__, xpath);
*errors += 1;
return;
}
/* TODO: check if the value spaces are identical. */
}
}
/*
* Check if the modules from the translator have a mapping for all of their
* schema nodes (after loading the deviations).
*/
static unsigned int yang_translator_validate(struct yang_translator *translator)
{
struct yang_tmodule *tmodule;
struct listnode *ln;
unsigned int errors = 0;
for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) {
yang_module_snodes_iterate(
tmodule->module, yang_translator_validate_cb,
YANG_ITER_FILTER_NPCONTAINERS
| YANG_ITER_FILTER_LIST_KEYS
| YANG_ITER_FILTER_INPUT_OUTPUT,
translator, &errors);
}
if (errors)
flog_warn(
EC_LIB_YANG_TRANSLATOR_LOAD,
"%s: failed to validate \"%s\" module translator: %u error(s)",
__func__, translator->family, errors);
return errors;
}
static void yang_module_nodes_count_cb(const struct lys_node *snode, void *arg1,
void *arg2)
{
unsigned int *total = arg1;
*total += 1;
}
/* Calculate the number of nodes for the given module. */
static unsigned int yang_module_nodes_count(const struct lys_module *module)
{
unsigned int total = 0;
yang_module_snodes_iterate(module, yang_module_nodes_count_cb,
YANG_ITER_FILTER_NPCONTAINERS
| YANG_ITER_FILTER_LIST_KEYS
| YANG_ITER_FILTER_INPUT_OUTPUT,
&total, NULL);
return total;
}
/* TODO: rewrite this function. */
static void str_replace(char *o_string, const char *s_string,
const char *r_string)
{
char buffer[BUFSIZ];
char *ch;
ch = strstr(o_string, s_string);
if (!ch)
return;
strncpy(buffer, o_string, ch - o_string);
buffer[ch - o_string] = 0;
sprintf(buffer + (ch - o_string), "%s%s", r_string,
ch + strlen(s_string));
o_string[0] = 0;
strcpy(o_string, buffer);
return str_replace(o_string, s_string, r_string);
}
void yang_translator_init(void)
{
ly_translator_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD);
if (!ly_translator_ctx) {
flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__);
exit(1);
}
ly_ctx_set_searchdir(ly_translator_ctx, YANG_MODELS_PATH);
if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator",
NULL)) {
flog_err(
EC_LIB_YANG_MODULE_LOAD,
"%s: failed to load the \"frr-module-translator\" module",
__func__);
exit(1);
}
}
void yang_translator_terminate(void)
{
while (!RB_EMPTY(yang_translators, &yang_translators)) {
struct yang_translator *translator;
translator = RB_ROOT(yang_translators, &yang_translators);
yang_translator_unload(translator);
}
ly_ctx_destroy(ly_translator_ctx, NULL);
}

144
lib/yang_translator.h Normal file
View file

@ -0,0 +1,144 @@
/*
* 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
*/
#ifndef _FRR_YANG_TRANSLATOR_H_
#define _FRR_YANG_TRANSLATOR_H_
#define YANG_TRANSLATE_TO_NATIVE 0
#define YANG_TRANSLATE_FROM_NATIVE 1
#define YANG_TRANSLATE_MAX 2
struct yang_tmodule {
const struct lys_module *module;
const struct lys_module *deviations;
uint32_t nodes_before_deviations;
uint32_t nodes_after_deviations;
double coverage;
};
struct yang_translator {
RB_ENTRY(yang_translator) entry;
char family[32];
struct ly_ctx *ly_ctx;
struct list *modules;
struct hash *mappings[YANG_TRANSLATE_MAX];
};
RB_HEAD(yang_translators, yang_translator);
RB_PROTOTYPE(yang_translators, yang_translator, entry, yang_translator_compare);
enum yang_translate_result {
YANG_TRANSLATE_SUCCESS,
YANG_TRANSLATE_NOTFOUND,
YANG_TRANSLATE_FAILURE,
};
/* Tree of all loaded YANG module translators. */
extern struct yang_translators yang_translators;
/*
* Load a YANG module translator from a JSON file.
*
* path
* Absolute path to the module translator file.
*
* Returns:
* Pointer to newly created YANG module translator, or NULL in the case of an
* error.
*/
extern struct yang_translator *yang_translator_load(const char *path);
/*
* Unload a YANG module translator.
*
* translator
* Pointer to the YANG module translator.
*/
extern void yang_translator_unload(struct yang_translator *translator);
/*
* Find a YANG module translator by its family name.
*
* family
* Family of the YANG module translator (e.g. ietf, openconfig).
*
* Returns:
* Pointer to the YANG module translator if found, NULL otherwise.
*/
extern struct yang_translator *yang_translator_find(const char *family);
/*
* Translate an XPath expression.
*
* translator
* Pointer to YANG module translator.
*
* dir
* Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
* YANG_TRANSLATE_FROM_NATIVE).
*
* xpath
* Pointer to previously allocated buffer containing the xpath expression to
* be translated.
*
* xpath_len
* Size of the xpath buffer.
*
* Returns:
* - YANG_TRANSLATE_SUCCESS on success.
* - YANG_TRANSLATE_NOTFOUND when there's no available mapping to perform
* the translation.
* - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
*/
extern enum yang_translate_result
yang_translate_xpath(const struct yang_translator *translator, int dir,
char *xpath, size_t xpath_len);
/*
* Translate an entire libyang data node.
*
* translator
* Pointer to YANG module translator.
*
* dir
* Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or
* YANG_TRANSLATE_FROM_NATIVE).
*
* dnode
* libyang schema node we want to translate.
*
* Returns:
* - YANG_TRANSLATE_SUCCESS on success.
* - YANG_TRANSLATE_FAILURE when an error occurred during the translation.
*/
extern int yang_translate_dnode(const struct yang_translator *translator,
int dir, struct lyd_node **dnode);
/*
* Initialize the YANG module translator subsystem. Should be called only once
* during the daemon initialization process.
*/
extern void yang_translator_init(void);
/*
* Finish the YANG module translator subsystem gracefully. Should be called only
* when the daemon is exiting.
*/
extern void yang_translator_terminate(void);
#endif /* _FRR_YANG_TRANSLATOR_H_ */

990
lib/yang_wrappers.c Normal file
View file

@ -0,0 +1,990 @@
/*
* 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 "log.h"
#include "lib_errors.h"
#include "northbound.h"
static const char *yang_get_default_value(const char *xpath)
{
const struct lys_node *snode;
const char *value;
snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
if (snode == NULL) {
flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
value = yang_snode_get_default(snode);
assert(value);
return value;
}
#define YANG_DNODE_GET_ASSERT(dnode, xpath) \
do { \
if ((dnode) == NULL) { \
flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, \
"%s: couldn't find %s", __func__, (xpath)); \
zlog_backtrace(LOG_ERR); \
abort(); \
} \
} while (0)
/*
* Primitive type: bool.
*/
bool yang_str2bool(const char *value)
{
return strmatch(value, "true");
}
struct yang_data *yang_data_new_bool(const char *xpath, bool value)
{
return yang_data_new(xpath, (value == true) ? "true" : "false");
}
bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt,
...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_BOOL);
return dleaf->value.bln;
}
bool yang_get_default_bool(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2bool(value);
}
/*
* Primitive type: dec64.
*/
double yang_str2dec64(const char *xpath, const char *value)
{
double dbl = 0;
if (sscanf(value, "%lf", &dbl) != 1) {
flog_err(EC_LIB_YANG_DATA_CONVERT,
"%s: couldn't convert string to decimal64 [xpath %s]",
__func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
return dbl;
}
struct yang_data *yang_data_new_dec64(const char *xpath, double value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%lf", value);
return yang_data_new(xpath, value_str);
}
double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt,
...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_DEC64);
return lyd_dec64_to_double(dnode);
}
double yang_get_default_dec64(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2dec64(xpath, value);
}
/*
* Primitive type: enum.
*/
int yang_str2enum(const char *xpath, const char *value)
{
const struct lys_node *snode;
const struct lys_node_leaf *sleaf;
const struct lys_type_info_enums *enums;
snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
if (snode == NULL) {
flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
sleaf = (const struct lys_node_leaf *)snode;
enums = &sleaf->type.info.enums;
for (unsigned int i = 0; i < enums->count; i++) {
const struct lys_type_enum *enm = &enums->enm[i];
if (strmatch(value, enm->name))
return enm->value;
}
flog_err(EC_LIB_YANG_DATA_CONVERT,
"%s: couldn't convert string to enum [xpath %s]", __func__,
xpath);
zlog_backtrace(LOG_ERR);
abort();
}
struct yang_data *yang_data_new_enum(const char *xpath, int value)
{
const struct lys_node *snode;
const struct lys_node_leaf *sleaf;
const struct lys_type_info_enums *enums;
snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0);
if (snode == NULL) {
flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH,
"%s: unknown data path: %s", __func__, xpath);
zlog_backtrace(LOG_ERR);
abort();
}
sleaf = (const struct lys_node_leaf *)snode;
enums = &sleaf->type.info.enums;
for (unsigned int i = 0; i < enums->count; i++) {
const struct lys_type_enum *enm = &enums->enm[i];
if (value == enm->value)
return yang_data_new(xpath, enm->name);
}
flog_err(EC_LIB_YANG_DATA_CONVERT,
"%s: couldn't convert enum to string [xpath %s]", __func__,
xpath);
zlog_backtrace(LOG_ERR);
abort();
}
int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt,
...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_ENUM);
return dleaf->value.enm->value;
}
int yang_get_default_enum(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2enum(xpath, value);
}
/*
* Primitive type: int8.
*/
int8_t yang_str2int8(const char *value)
{
return strtol(value, NULL, 10);
}
struct yang_data *yang_data_new_int8(const char *xpath, int8_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%d", value);
return yang_data_new(xpath, value_str);
}
int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt,
...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_INT8);
return dleaf->value.int8;
}
int8_t yang_get_default_int8(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2int8(value);
}
/*
* Primitive type: int16.
*/
int16_t yang_str2int16(const char *value)
{
return strtol(value, NULL, 10);
}
struct yang_data *yang_data_new_int16(const char *xpath, int16_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%d", value);
return yang_data_new(xpath, value_str);
}
int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_INT16);
return dleaf->value.int16;
}
int16_t yang_get_default_int16(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2int16(value);
}
/*
* Primitive type: int32.
*/
int32_t yang_str2int32(const char *value)
{
return strtol(value, NULL, 10);
}
struct yang_data *yang_data_new_int32(const char *xpath, int32_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%d", value);
return yang_data_new(xpath, value_str);
}
int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_INT32);
return dleaf->value.int32;
}
int32_t yang_get_default_int32(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2int32(value);
}
/*
* Primitive type: int64.
*/
int64_t yang_str2int64(const char *value)
{
return strtoll(value, NULL, 10);
}
struct yang_data *yang_data_new_int64(const char *xpath, int64_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%" PRId64, value);
return yang_data_new(xpath, value_str);
}
int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_INT64);
return dleaf->value.int64;
}
int64_t yang_get_default_int64(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2int64(value);
}
/*
* Primitive type: uint8.
*/
uint8_t yang_str2uint8(const char *value)
{
return strtoul(value, NULL, 10);
}
struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%u", value);
return yang_data_new(xpath, value_str);
}
uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_UINT8);
return dleaf->value.uint8;
}
uint8_t yang_get_default_uint8(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2uint8(value);
}
/*
* Primitive type: uint16.
*/
uint16_t yang_str2uint16(const char *value)
{
return strtoul(value, NULL, 10);
}
struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%u", value);
return yang_data_new(xpath, value_str);
}
uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_UINT16);
return dleaf->value.uint16;
}
uint16_t yang_get_default_uint16(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2uint16(value);
}
/*
* Primitive type: uint32.
*/
uint32_t yang_str2uint32(const char *value)
{
return strtoul(value, NULL, 10);
}
struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%u", value);
return yang_data_new(xpath, value_str);
}
uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_UINT32);
return dleaf->value.uint32;
}
uint32_t yang_get_default_uint32(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2uint32(value);
}
/*
* Primitive type: uint64.
*/
uint64_t yang_str2uint64(const char *value)
{
return strtoull(value, NULL, 10);
}
struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value)
{
char value_str[BUFSIZ];
snprintf(value_str, sizeof(value_str), "%" PRIu64, value);
return yang_data_new(xpath, value_str);
}
uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_UINT64);
return dleaf->value.uint64;
}
uint64_t yang_get_default_uint64(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
return yang_str2uint64(value);
}
/*
* Primitive type: string.
*
* All string wrappers can be used with non-string types.
*/
struct yang_data *yang_data_new_string(const char *xpath, const char *value)
{
return yang_data_new(xpath, value);
}
const char *yang_dnode_get_string(const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
return dleaf->value_str;
}
void yang_dnode_get_string_buf(char *buf, size_t size,
const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
if (strlcpy(buf, dleaf->value_str, size) >= size) {
char xpath[XPATH_MAXLEN];
yang_dnode_get_path(dnode, xpath, sizeof(xpath));
flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
"%s: value was truncated [xpath %s]", __func__,
xpath);
}
}
const char *yang_get_default_string(const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
return yang_get_default_value(xpath);
}
void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt,
...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
if (strlcpy(buf, value, size) >= size)
flog_warn(EC_LIB_YANG_DATA_TRUNCATED,
"%s: value was truncated [xpath %s]", __func__,
xpath);
}
/*
* Derived type: ipv4.
*/
void yang_str2ipv4(const char *value, struct in_addr *addr)
{
(void)inet_pton(AF_INET, value, addr);
}
struct yang_data *yang_data_new_ipv4(const char *xpath,
const struct in_addr *addr)
{
char value_str[INET_ADDRSTRLEN];
(void)inet_ntop(AF_INET, addr, value_str, sizeof(value_str));
return yang_data_new(xpath, value_str);
}
void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
memcpy(addr, dleaf->value.ptr, sizeof(*addr));
}
void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
yang_str2ipv4(value, var);
}
/*
* Derived type: ipv4p.
*/
void yang_str2ipv4p(const char *value, union prefixptr prefix)
{
struct prefix_ipv4 *prefix4 = prefix.p4;
(void)str2prefix_ipv4(value, prefix4);
apply_mask_ipv4(prefix4);
}
struct yang_data *yang_data_new_ipv4p(const char *xpath,
const union prefixptr prefix)
{
char value_str[PREFIX2STR_BUFFER];
(void)prefix2str(prefix.p, value_str, sizeof(value_str));
return yang_data_new(xpath, value_str);
}
void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
struct prefix_ipv4 *prefix4 = prefix.p4;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
memcpy(prefix4, dleaf->value.ptr, sizeof(*prefix4));
}
void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
yang_str2ipv4p(value, var);
}
/*
* Derived type: ipv6.
*/
void yang_str2ipv6(const char *value, struct in6_addr *addr)
{
(void)inet_pton(AF_INET6, value, addr);
}
struct yang_data *yang_data_new_ipv6(const char *xpath,
const struct in6_addr *addr)
{
char value_str[INET6_ADDRSTRLEN];
(void)inet_ntop(AF_INET6, addr, value_str, sizeof(value_str));
return yang_data_new(xpath, value_str);
}
void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
memcpy(addr, dleaf->value.ptr, sizeof(*addr));
}
void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
yang_str2ipv6(value, var);
}
/*
* Derived type: ipv6p.
*/
void yang_str2ipv6p(const char *value, union prefixptr prefix)
{
struct prefix_ipv6 *prefix6 = prefix.p6;
(void)str2prefix_ipv6(value, prefix6);
apply_mask_ipv6(prefix6);
}
struct yang_data *yang_data_new_ipv6p(const char *xpath,
const union prefixptr prefix)
{
char value_str[PREFIX2STR_BUFFER];
(void)prefix2str(prefix.p, value_str, sizeof(value_str));
return yang_data_new(xpath, value_str);
}
void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode,
const char *xpath_fmt, ...)
{
const struct lyd_node_leaf_list *dleaf;
struct prefix_ipv6 *prefix6 = prefix.p6;
assert(dnode);
if (xpath_fmt) {
va_list ap;
char xpath[XPATH_MAXLEN];
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
dnode = yang_dnode_get(dnode, xpath);
YANG_DNODE_GET_ASSERT(dnode, xpath);
}
dleaf = (const struct lyd_node_leaf_list *)dnode;
assert(dleaf->value_type == LY_TYPE_STRING);
memcpy(prefix6, dleaf->value.ptr, sizeof(*prefix6));
}
void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...)
{
char xpath[XPATH_MAXLEN];
const char *value;
va_list ap;
va_start(ap, xpath_fmt);
vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
va_end(ap);
value = yang_get_default_value(xpath);
yang_str2ipv6p(value, var);
}

157
lib/yang_wrappers.h Normal file
View file

@ -0,0 +1,157 @@
/*
* 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
*/
#ifndef _FRR_NORTHBOUND_WRAPPERS_H_
#define _FRR_NORTHBOUND_WRAPPERS_H_
#include "prefix.h"
/* bool */
extern bool yang_str2bool(const char *value);
extern struct yang_data *yang_data_new_bool(const char *xpath, bool value);
extern bool yang_dnode_get_bool(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern bool yang_get_default_bool(const char *xpath_fmt, ...);
/* dec64 */
extern double yang_str2dec64(const char *xpath, const char *value);
extern struct yang_data *yang_data_new_dec64(const char *xpath, double value);
extern double yang_dnode_get_dec64(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern double yang_get_default_dec64(const char *xpath_fmt, ...);
/* enum */
extern int yang_str2enum(const char *xpath, const char *value);
extern struct yang_data *yang_data_new_enum(const char *xpath, int value);
extern int yang_dnode_get_enum(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern int yang_get_default_enum(const char *xpath_fmt, ...);
/* int8 */
extern int8_t yang_str2int8(const char *value);
extern struct yang_data *yang_data_new_int8(const char *xpath, int8_t value);
extern int8_t yang_dnode_get_int8(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern int8_t yang_get_default_int8(const char *xpath_fmt, ...);
/* int16 */
extern int16_t yang_str2int16(const char *value);
extern struct yang_data *yang_data_new_int16(const char *xpath, int16_t value);
extern int16_t yang_dnode_get_int16(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern int16_t yang_get_default_int16(const char *xpath_fmt, ...);
/* int32 */
extern int32_t yang_str2int32(const char *value);
extern struct yang_data *yang_data_new_int32(const char *xpath, int32_t value);
extern int32_t yang_dnode_get_int32(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern int32_t yang_get_default_int32(const char *xpath_fmt, ...);
/* int64 */
extern int64_t yang_str2int64(const char *value);
extern struct yang_data *yang_data_new_int64(const char *xpath, int64_t value);
extern int64_t yang_dnode_get_int64(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern int64_t yang_get_default_int64(const char *xpath_fmt, ...);
/* uint8 */
extern uint8_t yang_str2uint8(const char *value);
extern struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value);
extern uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern uint8_t yang_get_default_uint8(const char *xpath_fmt, ...);
/* uint16 */
extern uint16_t yang_str2uint16(const char *value);
extern struct yang_data *yang_data_new_uint16(const char *xpath,
uint16_t value);
extern uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern uint16_t yang_get_default_uint16(const char *xpath_fmt, ...);
/* uint32 */
extern uint32_t yang_str2uint32(const char *value);
extern struct yang_data *yang_data_new_uint32(const char *xpath,
uint32_t value);
extern uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern uint32_t yang_get_default_uint32(const char *xpath_fmt, ...);
/* uint64 */
extern uint64_t yang_str2uint64(const char *value);
extern struct yang_data *yang_data_new_uint64(const char *xpath,
uint64_t value);
extern uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern uint64_t yang_get_default_uint64(const char *xpath_fmt, ...);
/* string */
extern struct yang_data *yang_data_new_string(const char *xpath,
const char *value);
extern const char *yang_dnode_get_string(const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern void yang_dnode_get_string_buf(char *buf, size_t size,
const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern const char *yang_get_default_string(const char *xpath_fmt, ...);
extern void yang_get_default_string_buf(char *buf, size_t size,
const char *xpath_fmt, ...);
/* ipv4 */
extern void yang_str2ipv4(const char *value, struct in_addr *addr);
extern struct yang_data *yang_data_new_ipv4(const char *xpath,
const struct in_addr *addr);
extern void yang_dnode_get_ipv4(struct in_addr *addr,
const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt,
...);
/* ipv4p */
extern void yang_str2ipv4p(const char *value, union prefixptr prefix);
extern struct yang_data *yang_data_new_ipv4p(const char *xpath,
const union prefixptr prefix);
extern void yang_dnode_get_ipv4p(union prefixptr prefix,
const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt,
...);
/* ipv6 */
extern void yang_str2ipv6(const char *value, struct in6_addr *addr);
extern struct yang_data *yang_data_new_ipv6(const char *xpath,
const struct in6_addr *addr);
extern void yang_dnode_get_ipv6(struct in6_addr *addr,
const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt,
...);
/* ipv6p */
extern void yang_str2ipv6p(const char *value, union prefixptr prefix);
extern struct yang_data *yang_data_new_ipv6p(const char *xpath,
const union prefixptr prefix);
extern void yang_dnode_get_ipv6p(union prefixptr prefix,
const struct lyd_node *dnode,
const char *xpath_fmt, ...);
extern void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt,
...);
#endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */

View file

@ -76,7 +76,7 @@ static void sighup(void)
zlog_info("ripd restarting!");
/* Reload config file. */
vty_read_config(ripd_di.config_file, config_default);
vty_read_config(NULL, ripd_di.config_file, config_default);
/* Try to return to normal operation. */
}

View file

@ -76,7 +76,7 @@ static void sighup(void)
ripng_reset();
/* Reload config file. */
vty_read_config(ripngd_di.config_file, config_default);
vty_read_config(NULL, ripngd_di.config_file, config_default);
/* Try to return to normal operation. */
}

View file

@ -1383,6 +1383,8 @@ static void bgp_startup(void)
LOG_DAEMON);
zprivs_preinit(&bgpd_privs);
zprivs_init(&bgpd_privs);
yang_init();
nb_init(NULL, 0);
master = thread_master_create(NULL);
bgp_master_init(master);
@ -1428,6 +1430,8 @@ static void bgp_shutdown(void)
vty_terminate();
cmd_terminate();
nb_terminate();
yang_terminate();
zprivs_terminate(&bgpd_privs);
thread_master_free(master);
master = NULL;

View file

@ -155,6 +155,8 @@ int main(int argc, char **argv)
cmd_init(1);
vty_init(master);
memory_init();
yang_init();
nb_init(NULL, 0);
/* OSPF vty inits. */
test_vty_init();
@ -171,7 +173,7 @@ int main(int argc, char **argv)
/* Configuration file read*/
if (!config_file)
usage(progname, 1);
vty_read_config(config_file, NULL);
vty_read_config(NULL, config_file, NULL);
test_timer_init();

View file

@ -50,6 +50,8 @@ static void vty_do_exit(int isexit)
printf("\nend.\n");
cmd_terminate();
vty_terminate();
nb_terminate();
yang_terminate();
thread_master_free(master);
closezlog();
@ -81,6 +83,8 @@ int main(int argc, char **argv)
vty_init(master);
memory_init();
yang_init();
nb_init(NULL, 0);
test_init(argc, argv);

View file

@ -313,6 +313,7 @@ frr defaults @DFLT_NAME@
hostname test
!
!
!
line vty
!
end
@ -328,6 +329,7 @@ frr defaults @DFLT_NAME@
hostname foohost
!
!
!
line vty
!
end

View file

@ -142,6 +142,8 @@ static void test_init(void)
struct cmd_element *cmd;
cmd_init(1);
yang_init();
nb_init(NULL, 0);
install_node(&bgp_node, NULL);
install_node(&rip_node, NULL);
@ -184,6 +186,8 @@ static void test_terminate(void)
XFREE(MTYPE_TMP, vector_slot(test_cmds, i));
vector_free(test_cmds);
cmd_terminate();
nb_terminate();
yang_terminate();
}
static void test_run(struct prng *prng, struct vty *vty, const char *cmd,

2
tools/.gitignore vendored
View file

@ -1,3 +1,5 @@
/frr
/gen_northbound_callbacks
/gen_yang_deviations
/permutations
/ssd

View file

@ -0,0 +1,302 @@
/*
* 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
*/
#define REALLY_NEED_PLAIN_GETOPT 1
#include <zebra.h>
#include <unistd.h>
#include "yang.h"
#include "northbound.h"
static void __attribute__((noreturn)) usage(int status)
{
fprintf(stderr, "usage: gen_northbound_callbacks [-h] MODULE\n");
exit(status);
}
static struct nb_callback_info {
int operation;
bool optional;
char return_type[32];
char return_value[32];
char arguments[128];
} nb_callbacks[] = {
{
.operation = NB_OP_CREATE,
.return_type = "int ",
.return_value = "NB_OK",
.arguments =
"enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
},
{
.operation = NB_OP_MODIFY,
.return_type = "int ",
.return_value = "NB_OK",
.arguments =
"enum nb_event event, const struct lyd_node *dnode, union nb_resource *resource",
},
{
.operation = NB_OP_DELETE,
.return_type = "int ",
.return_value = "NB_OK",
.arguments =
"enum nb_event event, const struct lyd_node *dnode",
},
{
.operation = NB_OP_MOVE,
.return_type = "int ",
.return_value = "NB_OK",
.arguments =
"enum nb_event event, const struct lyd_node *dnode",
},
{
.operation = NB_OP_APPLY_FINISH,
.optional = true,
.return_type = "void ",
.return_value = "",
.arguments = "const struct lyd_node *dnode",
},
{
.operation = NB_OP_GET_ELEM,
.return_type = "struct yang_data *",
.return_value = "NULL",
.arguments = "const char *xpath, const void *list_entry",
},
{
.operation = NB_OP_GET_NEXT,
.return_type = "const void *",
.return_value = "NULL",
.arguments = "const char *xpath, 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",
},
{
.operation = NB_OP_LOOKUP_ENTRY,
.return_type = "const void *",
.return_value = "NULL",
.arguments = "const struct yang_list_keys *keys",
},
{
.operation = NB_OP_RPC,
.return_type = "int ",
.return_value = "NB_OK",
.arguments =
"const char *xpath, const struct list *input, struct list *output",
},
{
/* sentinel */
.operation = -1,
},
};
static void replace_hyphens_by_underscores(char *str)
{
char *p;
p = str;
while ((p = strchr(p, '-')) != NULL)
*p++ = '_';
}
static void generate_callback_name(struct lys_node *snode,
enum nb_operation operation, char *buffer,
size_t size)
{
struct list *snodes;
struct listnode *ln;
snodes = list_new();
for (; snode; snode = lys_parent(snode)) {
/* Skip schema-only snodes. */
if (snode->nodetype
& (LYS_USES | LYS_CHOICE | LYS_CASE | LYS_INPUT
| LYS_OUTPUT))
continue;
listnode_add_head(snodes, snode);
}
memset(buffer, 0, size);
for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) {
strlcat(buffer, snode->name, size);
strlcat(buffer, "_", size);
}
strlcat(buffer, nb_operation_name(operation), size);
list_delete(&snodes);
replace_hyphens_by_underscores(buffer);
}
static void generate_callbacks(const struct lys_node *snode, void *arg1,
void *arg2)
{
bool first = true;
switch (snode->nodetype) {
case LYS_CONTAINER:
case LYS_LEAF:
case LYS_LEAFLIST:
case LYS_LIST:
case LYS_NOTIF:
case LYS_RPC:
break;
default:
return;
}
for (struct nb_callback_info *cb = &nb_callbacks[0];
cb->operation != -1; cb++) {
char cb_name[BUFSIZ];
if (cb->optional
|| !nb_operation_is_valid(cb->operation, snode))
continue;
if (first) {
char xpath[XPATH_MAXLEN];
yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
sizeof(xpath));
printf("/*\n"
" * XPath: %s\n"
" */\n",
xpath);
first = false;
}
generate_callback_name((struct lys_node *)snode, cb->operation,
cb_name, sizeof(cb_name));
printf("static %s%s(%s)\n"
"{\n"
"\t/* TODO: implement me. */\n"
"\treturn %s;\n"
"}\n\n",
nb_callbacks[cb->operation].return_type, cb_name,
nb_callbacks[cb->operation].arguments,
nb_callbacks[cb->operation].return_value);
}
}
static void generate_nb_nodes(const struct lys_node *snode, void *arg1,
void *arg2)
{
bool first = true;
switch (snode->nodetype) {
case LYS_CONTAINER:
case LYS_LEAF:
case LYS_LEAFLIST:
case LYS_LIST:
case LYS_NOTIF:
case LYS_RPC:
break;
default:
return;
}
for (struct nb_callback_info *cb = &nb_callbacks[0];
cb->operation != -1; cb++) {
char cb_name[BUFSIZ];
if (cb->optional
|| !nb_operation_is_valid(cb->operation, snode))
continue;
if (first) {
char xpath[XPATH_MAXLEN];
yang_snode_get_path(snode, YANG_PATH_DATA, xpath,
sizeof(xpath));
printf("\t\t{\n"
"\t\t\t.xpath = \"%s\",\n",
xpath);
first = false;
}
generate_callback_name((struct lys_node *)snode, cb->operation,
cb_name, sizeof(cb_name));
printf("\t\t\t.cbs.%s = %s,\n",
nb_operation_name(cb->operation), cb_name);
}
if (!first)
printf("\t\t},\n");
}
int main(int argc, char *argv[])
{
struct yang_module *module;
char module_name_underscores[64];
int opt;
while ((opt = getopt(argc, argv, "h")) != -1) {
switch (opt) {
case 'h':
usage(EXIT_SUCCESS);
/* NOTREACHED */
default:
usage(EXIT_FAILURE);
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
if (argc != 1)
usage(EXIT_FAILURE);
yang_init();
/* Load YANG module. */
module = yang_module_load(argv[0]);
/* Generate callback functions. */
yang_module_snodes_iterate(module->info, generate_callbacks, 0, NULL,
NULL);
strlcpy(module_name_underscores, module->name,
sizeof(module_name_underscores));
replace_hyphens_by_underscores(module_name_underscores);
/* Generate frr_yang_module_info array. */
printf("/* clang-format off */\n"
"const struct frr_yang_module_info %s_info = {\n"
"\t.name = \"%s\",\n"
"\t.nodes = {\n",
module_name_underscores, module->name);
yang_module_snodes_iterate(module->info, generate_nb_nodes, 0, NULL,
NULL);
printf("\t\t{\n"
"\t\t\t.xpath = NULL,\n"
"\t\t},\n");
printf("\t}\n"
"};\n");
/* Cleanup and exit. */
yang_terminate();
return 0;
}

View file

@ -0,0 +1,80 @@
/*
* 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
*/
#define REALLY_NEED_PLAIN_GETOPT 1
#include <zebra.h>
#include <unistd.h>
#include "yang.h"
#include "northbound.h"
static void __attribute__((noreturn)) usage(int status)
{
fprintf(stderr, "usage: gen_yang_deviations [-h] MODULE\n");
exit(status);
}
static void generate_yang_deviation(const struct lys_node *snode, void *arg1,
void *arg2)
{
char xpath[XPATH_MAXLEN];
yang_snode_get_path(snode, YANG_PATH_SCHEMA, xpath, sizeof(xpath));
printf(" deviation \"%s\" {\n", xpath);
printf(" deviate not-supported;\n");
printf(" }\n\n");
}
int main(int argc, char *argv[])
{
struct yang_module *module;
int opt;
while ((opt = getopt(argc, argv, "h")) != -1) {
switch (opt) {
case 'h':
usage(EXIT_SUCCESS);
/* NOTREACHED */
default:
usage(EXIT_FAILURE);
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;
if (argc != 1)
usage(EXIT_FAILURE);
yang_init();
/* Load YANG module. */
module = yang_module_load(argv[0]);
/* Generate deviations. */
yang_module_snodes_iterate(module->info, generate_yang_deviation,
YANG_ITER_FILTER_IMPLICIT, NULL, NULL);
/* Cleanup and exit. */
yang_terminate();
return 0;
}

View file

@ -2,7 +2,12 @@
# tools
#
noinst_PROGRAMS += tools/permutations
noinst_PROGRAMS += \
tools/permutations \
tools/gen_northbound_callbacks \
tools/gen_yang_deviations \
# end
sbin_PROGRAMS += tools/ssd
sbin_SCRIPTS += \
tools/frr-reload \
@ -13,6 +18,12 @@ sbin_SCRIPTS += \
tools_permutations_SOURCES = tools/permutations.c
tools_permutations_LDADD = lib/libfrr.la
tools_gen_northbound_callbacks_SOURCES = tools/gen_northbound_callbacks.c
tools_gen_northbound_callbacks_LDADD = lib/libfrr.la -lyang
tools_gen_yang_deviations_SOURCES = tools/gen_yang_deviations.c
tools_gen_yang_deviations_LDADD = lib/libfrr.la -lyang
tools_ssd_SOURCES = tools/start-stop-daemon.c
EXTRA_DIST += \

View file

@ -366,6 +366,10 @@ void vtysh_config_parse_line(void *arg, const char *line)
config = config_get(FORWARDING_NODE, line);
else if (strncmp(line, "debug vrf", strlen("debug vrf")) == 0)
config = config_get(VRF_DEBUG_NODE, line);
else if (strncmp(line, "debug northbound",
strlen("debug northbound"))
== 0)
config = config_get(NORTHBOUND_DEBUG_NODE, line);
else if (strncmp(line, "debug", strlen("debug")) == 0)
config = config_get(DEBUG_NODE, line);
else if (strncmp(line, "password", strlen("password")) == 0
@ -411,7 +415,7 @@ void vtysh_config_parse_line(void *arg, const char *line)
|| (I) == ACCESS_IPV6_NODE || (I) == ACCESS_MAC_NODE \
|| (I) == PREFIX_IPV6_NODE || (I) == FORWARDING_NODE \
|| (I) == DEBUG_NODE || (I) == AAA_NODE || (I) == VRF_DEBUG_NODE \
|| (I) == MPLS_NODE)
|| (I) == NORTHBOUND_DEBUG_NODE || (I) == MPLS_NODE)
/* Display configuration to file pointer. */
void vtysh_config_dump(void)

View file

@ -0,0 +1,68 @@
module frr-module-translator {
yang-version 1.1;
namespace "http://frrouting.org/yang/frr-module-translator";
prefix frr-module-translator;
organization
"Free Range Routing";
contact
"FRR Users List: <mailto:frog@lists.frrouting.org>
FRR Development List: <mailto:dev@lists.frrouting.org>";
description
"A model for FRR YANG module translators.";
revision 2018-07-31 {
description
"Initial revision.";
}
container frr-module-translator {
leaf family {
type string {
length "0 .. 32";
}
mandatory true;
description
"Family of YANG models.";
}
list module {
key "name";
ordered-by user;
description
"YANG module.";
leaf name {
type string;
description
"Module name.";
}
leaf deviations {
type string;
mandatory true;
description
"Module containing the YANG deviations.";
}
list mappings {
key "custom";
description
"YANG mappings between the custom module and FRR native modules.";
leaf custom {
type string {
length "0 .. 256";
}
description
"YANG path of the custom module.";
}
leaf native {
type string {
length "0 .. 256";
}
mandatory true;
description
"Corresponding path of the native YANG modules";
}
}
}
}
}

View file

@ -0,0 +1,100 @@
/*
* 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 "prefix.h"
#include <libyang/user_types.h>
static int ipv4_address_store_clb(const char *type_name, const char *value_str,
lyd_val *value, char **err_msg)
{
value->ptr = malloc(sizeof(struct in_addr));
if (!value->ptr)
return 1;
if (inet_pton(AF_INET, value_str, value->ptr) != 1) {
free(value->ptr);
return 1;
}
return 0;
}
static int ipv6_address_store_clb(const char *type_name, const char *value_str,
lyd_val *value, char **err_msg)
{
value->ptr = malloc(INET6_ADDRSTRLEN);
if (!value->ptr)
return 1;
if (inet_pton(AF_INET6, value_str, value->ptr) != 1) {
free(value->ptr);
return 1;
}
return 0;
}
static int ipv4_prefix_store_clb(const char *type_name, const char *value_str,
lyd_val *value, char **err_msg)
{
value->ptr = malloc(sizeof(struct prefix_ipv4));
if (!value->ptr)
return 1;
if (str2prefix_ipv4(value_str, value->ptr) == 0) {
free(value->ptr);
return 1;
}
return 0;
}
static int ipv6_prefix_store_clb(const char *type_name, const char *value_str,
lyd_val *value, char **err_msg)
{
value->ptr = malloc(sizeof(struct prefix_ipv6));
if (!value->ptr)
return 1;
if (str2prefix_ipv6(value_str, value->ptr) == 0) {
free(value->ptr);
return 1;
}
return 0;
}
struct lytype_plugin_list frr_user_types[] = {
{"ietf-inet-types", "2013-07-15", "ipv4-address",
ipv4_address_store_clb, free},
{"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone",
ipv4_address_store_clb, free},
{"ietf-inet-types", "2013-07-15", "ipv6-address",
ipv6_address_store_clb, free},
{"ietf-inet-types", "2013-07-15", "ipv6-address-no-zone",
ipv6_address_store_clb, free},
{"ietf-inet-types", "2013-07-15", "ipv4-prefix", ipv4_prefix_store_clb,
free},
{"ietf-inet-types", "2013-07-15", "ipv6-prefix", ipv6_prefix_store_clb,
free},
{NULL, NULL, NULL, NULL, NULL} /* terminating item */
};

View file

@ -0,0 +1,9 @@
#
# libyang user types
#
libyang_plugins_LTLIBRARIES += yang/libyang_plugins/frr_user_types.la
yang_libyang_plugins_frr_user_types_la_CFLAGS = $(WERROR)
yang_libyang_plugins_frr_user_types_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
yang_libyang_plugins_frr_user_types_la_LIBADD =
yang_libyang_plugins_frr_user_types_la_SOURCES = yang/libyang_plugins/frr_user_types.c

1
yang/subdir.am Normal file
View file

@ -0,0 +1 @@
dist_yangmodels_DATA += yang/frr-module-translator.yang