From 1c2facd12df7bc27758d7ea674b1e57e401fc234 Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Thu, 7 Dec 2017 17:31:48 -0200 Subject: [PATCH] lib: introduce new northbound API Signed-off-by: Renato Westphal --- .clang-format | 3 + Makefile.am | 5 + bgpd/bgp_main.c | 2 +- configure.ac | 39 + doc/user/basic.rst | 4 + doc/user/installation.rst | 4 + ldpd/ldpd.c | 2 +- lib/command.c | 35 +- lib/command.h | 1 + lib/db.c | 325 ++++++ lib/db.h | 52 + lib/lib_errors.c | 168 +++ lib/lib_errors.h | 28 + lib/libfrr.c | 75 +- lib/libfrr.h | 15 + lib/northbound.c | 1200 ++++++++++++++++++++ lib/northbound.h | 780 +++++++++++++ lib/northbound_cli.c | 1448 +++++++++++++++++++++++++ lib/northbound_cli.h | 66 ++ lib/northbound_db.c | 288 +++++ lib/northbound_db.h | 104 ++ lib/subdir.am | 22 +- lib/vty.c | 81 +- lib/vty.h | 49 +- lib/yang.c | 618 +++++++++++ lib/yang.h | 439 ++++++++ lib/yang_translator.c | 545 ++++++++++ lib/yang_translator.h | 144 +++ lib/yang_wrappers.c | 990 +++++++++++++++++ lib/yang_wrappers.h | 157 +++ ripd/rip_main.c | 2 +- ripngd/ripng_main.c | 2 +- tests/bgpd/test_peer_attr.c | 4 + tests/helpers/c/main.c | 4 +- tests/lib/cli/common_cli.c | 4 + tests/lib/cli/test_cli.refout.in | 2 + tests/lib/cli/test_commands.c | 4 + tools/.gitignore | 2 + tools/gen_northbound_callbacks.c | 302 ++++++ tools/gen_yang_deviations.c | 80 ++ tools/subdir.am | 13 +- vtysh/vtysh_config.c | 6 +- yang/frr-module-translator.yang | 68 ++ yang/libyang_plugins/frr_user_types.c | 100 ++ yang/libyang_plugins/subdir.am | 9 + yang/subdir.am | 1 + 46 files changed, 8267 insertions(+), 25 deletions(-) create mode 100644 lib/db.c create mode 100644 lib/db.h create mode 100644 lib/northbound.c create mode 100644 lib/northbound.h create mode 100644 lib/northbound_cli.c create mode 100644 lib/northbound_cli.h create mode 100644 lib/northbound_db.c create mode 100644 lib/northbound_db.h create mode 100644 lib/yang.c create mode 100644 lib/yang.h create mode 100644 lib/yang_translator.c create mode 100644 lib/yang_translator.h create mode 100644 lib/yang_wrappers.c create mode 100644 lib/yang_wrappers.h create mode 100644 tools/gen_northbound_callbacks.c create mode 100644 tools/gen_yang_deviations.c create mode 100644 yang/frr-module-translator.yang create mode 100644 yang/libyang_plugins/frr_user_types.c create mode 100644 yang/libyang_plugins/subdir.am create mode 100644 yang/subdir.am diff --git a/.clang-format b/.clang-format index 3c6a2784c..a65a29f8c 100644 --- a/.clang-format +++ b/.clang-format @@ -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 diff --git a/Makefile.am b/Makefile.am index 025afa565..d12d45264 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 503c3f9f8..bf0c6a42e 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -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. */ } diff --git a/configure.ac b/configure.ac index 5ebe188c1..3053532a9 100755 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/doc/user/basic.rst b/doc/user/basic.rst index da22bb2f8..8201fdf1b 100644 --- a/doc/user/basic.rst +++ b/doc/user/basic.rst @@ -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 diff --git a/doc/user/installation.rst b/doc/user/installation.rst index da431916a..59ae5830e 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -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/` diff --git a/ldpd/ldpd.c b/ldpd/ldpd.c index 137d9622d..c611ec12a 100644 --- a/ldpd/ldpd.c +++ b/ldpd/ldpd.c @@ -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); } diff --git a/lib/command.c b/lib/command.c index 127e1a0a0..bd000c374 100644 --- a/lib/command.c +++ b/lib/command.c @@ -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. diff --git a/lib/command.h b/lib/command.h index de65c8bd9..873ecdda9 100644 --- a/lib/command.h +++ b/lib/command.h @@ -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. */ diff --git a/lib/db.c b/lib/db.c new file mode 100644 index 000000000..32ba83b65 --- /dev/null +++ b/lib/db.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2018 Rafael Zalamena + * + * 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 + * + * 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 + +#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; +} diff --git a/lib/db.h b/lib/db.h new file mode 100644 index 000000000..8c936ec4b --- /dev/null +++ b/lib/db.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Rafael Zalamena + * + * 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 + * + * 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 + +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_ */ diff --git a/lib/lib_errors.c b/lib/lib_errors.c index ad83ef1e5..71d1ec6e5 100644 --- a/lib/lib_errors.c +++ b/lib/lib_errors.c @@ -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, } diff --git a/lib/lib_errors.h b/lib/lib_errors.h index e0f698a07..3831116dd 100644 --- a/lib/lib_errors.h +++ b/lib/lib_errors.h @@ -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); diff --git a/lib/libfrr.c b/lib/libfrr.c index 35a6da577..6ebe24eef 100644 --- a/lib/libfrr.c +++ b/lib/libfrr.c @@ -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:\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 */ diff --git a/lib/libfrr.h b/lib/libfrr.h index db58ff92b..2705397b8 100644 --- a/lib/libfrr.h +++ b/lib/libfrr.h @@ -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); diff --git a/lib/northbound.c b/lib/northbound.c new file mode 100644 index 000000000..8932b94da --- /dev/null +++ b/lib/northbound.c @@ -0,0 +1,1200 @@ +/* + * 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 + +#include "libfrr.h" +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "db.h" +#include "northbound.h" +#include "northbound_cli.h" +#include "northbound_db.h" + +DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node") +DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration") + +/* Running configuration - shouldn't be modified directly. */ +struct nb_config *running_config; + +/* + * Global lock used to prevent multiple configuration transactions from + * happening concurrently. + */ +static bool transaction_in_progress; + +static int nb_configuration_callback(const enum nb_event event, + struct nb_config_change *change); +static struct nb_transaction *nb_transaction_new(struct nb_config *config, + struct nb_config_cbs *changes, + enum nb_client client, + const char *comment); +static void nb_transaction_free(struct nb_transaction *transaction); +static int nb_transaction_process(enum nb_event event, + struct nb_transaction *transaction); +static void nb_transaction_apply_finish(struct nb_transaction *transaction); + +static void nb_node_new_cb(const struct lys_node *snode, void *arg1, void *arg2) +{ + struct nb_node *nb_node; + struct lys_node *sparent, *sparent_list; + + nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node)); + yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath, + sizeof(nb_node->xpath)); + nb_node->priority = NB_DFLT_PRIORITY; + sparent = yang_snode_real_parent(snode); + if (sparent) + nb_node->parent = sparent->priv; + sparent_list = yang_snode_parent_list(snode); + if (sparent_list) + nb_node->parent_list = sparent_list->priv; + + /* + * Link the northbound node and the libyang schema node with one + * another. + */ + nb_node->snode = snode; + lys_set_private(snode, nb_node); +} + +static void nb_node_del_cb(const struct lys_node *snode, void *arg1, void *arg2) +{ + struct nb_node *nb_node; + + nb_node = snode->priv; + lys_set_private(snode, NULL); + XFREE(MTYPE_NB_NODE, nb_node); +} + +struct nb_node *nb_node_find(const char *xpath) +{ + const struct lys_node *snode; + + /* + * Use libyang to find the schema node associated to the xpath and get + * the northbound node from there (snode private pointer). + */ + snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); + if (!snode) + return NULL; + + return snode->priv; +} + +static int nb_node_validate_cb(const struct nb_node *nb_node, + enum nb_operation operation, + int callback_implemented, bool optional) +{ + bool valid; + + valid = nb_operation_is_valid(operation, nb_node->snode); + + if (!valid && callback_implemented) + flog_warn(EC_LIB_NB_CB_UNNEEDED, + "unneeded '%s' callback for '%s'", + nb_operation_name(operation), nb_node->xpath); + + if (!optional && valid && !callback_implemented) { + flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'", + nb_operation_name(operation), nb_node->xpath); + return 1; + } + + return 0; +} + +/* + * Check if the required callbacks were implemented for the given northbound + * node. + */ +static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node) + +{ + unsigned int error = 0; + + error += nb_node_validate_cb(nb_node, NB_OP_CREATE, + !!nb_node->cbs.create, false); + error += nb_node_validate_cb(nb_node, NB_OP_MODIFY, + !!nb_node->cbs.modify, false); + error += nb_node_validate_cb(nb_node, NB_OP_DELETE, + !!nb_node->cbs.delete, false); + error += nb_node_validate_cb(nb_node, NB_OP_MOVE, !!nb_node->cbs.move, + false); + error += nb_node_validate_cb(nb_node, NB_OP_APPLY_FINISH, + !!nb_node->cbs.apply_finish, true); + error += nb_node_validate_cb(nb_node, NB_OP_GET_ELEM, + !!nb_node->cbs.get_elem, false); + error += nb_node_validate_cb(nb_node, NB_OP_GET_NEXT, + !!nb_node->cbs.get_next, false); + error += nb_node_validate_cb(nb_node, NB_OP_GET_KEYS, + !!nb_node->cbs.get_keys, false); + error += nb_node_validate_cb(nb_node, NB_OP_LOOKUP_ENTRY, + !!nb_node->cbs.lookup_entry, false); + error += nb_node_validate_cb(nb_node, NB_OP_RPC, !!nb_node->cbs.rpc, + false); + + return error; +} + +static unsigned int nb_node_validate_priority(const struct nb_node *nb_node) +{ + /* Top-level nodes can have any priority. */ + if (!nb_node->parent) + return 0; + + if (nb_node->priority < nb_node->parent->priority) { + flog_err(EC_LIB_NB_CB_INVALID_PRIO, + "node has higher priority than its parent [xpath %s]", + nb_node->xpath); + return 1; + } + + return 0; +} + +static void nb_node_validate(const struct lys_node *snode, void *arg1, + void *arg2) +{ + struct nb_node *nb_node = snode->priv; + unsigned int *errors = arg1; + + /* Validate callbacks and priority. */ + *errors += nb_node_validate_cbs(nb_node); + *errors += nb_node_validate_priority(nb_node); +} + +struct nb_config *nb_config_new(struct lyd_node *dnode) +{ + struct nb_config *config; + + config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config)); + if (dnode) + config->dnode = dnode; + else + config->dnode = yang_dnode_new(ly_native_ctx); + config->version = 0; + + return config; +} + +void nb_config_free(struct nb_config *config) +{ + if (config->dnode) + yang_dnode_free(config->dnode); + XFREE(MTYPE_NB_CONFIG, config); +} + +struct nb_config *nb_config_dup(const struct nb_config *config) +{ + struct nb_config *dup; + + dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup)); + dup->dnode = yang_dnode_dup(config->dnode); + dup->version = config->version; + + return dup; +} + +int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src, + bool preserve_source) +{ + int ret; + + ret = lyd_merge(config_dst->dnode, config_src->dnode, LYD_OPT_EXPLICIT); + if (ret != 0) + flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__); + + if (!preserve_source) + nb_config_free(config_src); + + return (ret == 0) ? NB_OK : NB_ERR; +} + +void nb_config_replace(struct nb_config *config_dst, + struct nb_config *config_src, bool preserve_source) +{ + /* Update version. */ + if (config_src->version != 0) + config_dst->version = config_src->version; + + /* Update dnode. */ + yang_dnode_free(config_dst->dnode); + if (preserve_source) { + config_dst->dnode = yang_dnode_dup(config_src->dnode); + } else { + config_dst->dnode = config_src->dnode; + config_src->dnode = NULL; + nb_config_free(config_src); + } +} + +/* Generate the nb_config_cbs tree. */ +static inline int nb_config_cb_compare(const struct nb_config_cb *a, + const struct nb_config_cb *b) +{ + /* Sort by priority first. */ + if (a->nb_node->priority < b->nb_node->priority) + return -1; + if (a->nb_node->priority > b->nb_node->priority) + return 1; + + /* + * Use XPath as a tie-breaker. This will naturally sort parent nodes + * before their children. + */ + return strcmp(a->xpath, b->xpath); +} +RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare); + +static void nb_config_diff_add_change(struct nb_config_cbs *changes, + enum nb_operation operation, + const struct lyd_node *dnode) +{ + struct nb_config_change *change; + + change = XCALLOC(MTYPE_TMP, sizeof(*change)); + change->cb.operation = operation; + change->cb.nb_node = dnode->schema->priv; + yang_dnode_get_path(dnode, change->cb.xpath, sizeof(change->cb.xpath)); + change->cb.dnode = dnode; + + RB_INSERT(nb_config_cbs, changes, &change->cb); +} + +static void nb_config_diff_del_changes(struct nb_config_cbs *changes) +{ + while (!RB_EMPTY(nb_config_cbs, changes)) { + struct nb_config_change *change; + + change = (struct nb_config_change *)RB_ROOT(nb_config_cbs, + changes); + RB_REMOVE(nb_config_cbs, changes, &change->cb); + XFREE(MTYPE_TMP, change); + } +} + +/* + * Helper function used when calculating the delta between two different + * configurations. Given a new subtree, calculate all new YANG data nodes, + * excluding default leafs and leaf-lists. This is a recursive function. + */ +static void nb_config_diff_new_subtree(const struct lyd_node *dnode, + struct nb_config_cbs *changes) +{ + struct lyd_node *child; + + LY_TREE_FOR (dnode->child, child) { + enum nb_operation operation; + + switch (child->schema->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + if (lyd_wd_default((struct lyd_node_leaf_list *)child)) + break; + + if (nb_operation_is_valid(NB_OP_CREATE, child->schema)) + operation = NB_OP_CREATE; + else if (nb_operation_is_valid(NB_OP_MODIFY, + child->schema)) + operation = NB_OP_MODIFY; + else + continue; + + nb_config_diff_add_change(changes, operation, child); + break; + case LYS_CONTAINER: + case LYS_LIST: + if (nb_operation_is_valid(NB_OP_CREATE, child->schema)) + nb_config_diff_add_change(changes, NB_OP_CREATE, + child); + nb_config_diff_new_subtree(child, changes); + break; + default: + break; + } + } +} + +/* Calculate the delta between two different configurations. */ +static void nb_config_diff(const struct nb_config *config1, + const struct nb_config *config2, + struct nb_config_cbs *changes) +{ + struct lyd_difflist *diff; + + diff = lyd_diff(config1->dnode, config2->dnode, + LYD_DIFFOPT_WITHDEFAULTS); + assert(diff); + + for (int i = 0; diff->type[i] != LYD_DIFF_END; i++) { + LYD_DIFFTYPE type; + struct lyd_node *dnode; + enum nb_operation operation; + + type = diff->type[i]; + + switch (type) { + case LYD_DIFF_CREATED: + dnode = diff->second[i]; + + if (nb_operation_is_valid(NB_OP_CREATE, dnode->schema)) + operation = NB_OP_CREATE; + else if (nb_operation_is_valid(NB_OP_MODIFY, + dnode->schema)) + operation = NB_OP_MODIFY; + else + continue; + break; + case LYD_DIFF_DELETED: + dnode = diff->first[i]; + operation = NB_OP_DELETE; + break; + case LYD_DIFF_CHANGED: + dnode = diff->second[i]; + operation = NB_OP_MODIFY; + break; + case LYD_DIFF_MOVEDAFTER1: + case LYD_DIFF_MOVEDAFTER2: + default: + continue; + } + + nb_config_diff_add_change(changes, operation, dnode); + + if (type == LYD_DIFF_CREATED + && (dnode->schema->nodetype & (LYS_CONTAINER | LYS_LIST))) + nb_config_diff_new_subtree(dnode, changes); + } + + lyd_free_diff(diff); +} + +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) +{ + struct lyd_node *dnode; + char xpath_edit[XPATH_MAXLEN]; + + if (!nb_operation_is_valid(operation, nb_node->snode)) { + flog_warn(EC_LIB_NB_CANDIDATE_EDIT_ERROR, + "%s: %s operation not valid for %s", __func__, + nb_operation_name(operation), xpath); + return NB_ERR; + } + + /* Use special notation for leaf-lists (RFC 6020, section 9.13.5). */ + if (nb_node->snode->nodetype == LYS_LEAFLIST) + snprintf(xpath_edit, sizeof(xpath_edit), "%s[.='%s']", xpath, + data->value); + else + strlcpy(xpath_edit, xpath, sizeof(xpath_edit)); + + switch (operation) { + case NB_OP_CREATE: + case NB_OP_MODIFY: + ly_errno = 0; + dnode = lyd_new_path(candidate->dnode, ly_native_ctx, + xpath_edit, (void *)data->value, 0, + LYD_PATH_OPT_UPDATE); + if (!dnode && ly_errno) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", + __func__); + return NB_ERR; + } + + /* + * If a new node was created, call lyd_validate() only to create + * default child nodes. + */ + if (dnode) { + lyd_schema_sort(dnode, 0); + lyd_validate(&dnode, LYD_OPT_CONFIG, ly_native_ctx); + } + break; + case NB_OP_DELETE: + dnode = yang_dnode_get(candidate->dnode, xpath_edit); + if (!dnode) + /* + * Return a special error code so the caller can choose + * whether to ignore it or not. + */ + return NB_ERR_NOT_FOUND; + lyd_free(dnode); + break; + case NB_OP_MOVE: + /* TODO: update configuration. */ + break; + default: + flog_warn(EC_LIB_DEVELOPMENT, + "%s: unknown operation (%u) [xpath %s]", __func__, + operation, xpath_edit); + return NB_ERR; + } + + return NB_OK; +} + +bool nb_candidate_needs_update(const struct nb_config *candidate) +{ + if (candidate->version < running_config->version) + return true; + + return false; +} + +int nb_candidate_update(struct nb_config *candidate) +{ + struct nb_config *updated_config; + + updated_config = nb_config_dup(running_config); + if (nb_config_merge(updated_config, candidate, true) != NB_OK) + return NB_ERR; + + nb_config_replace(candidate, updated_config, false); + + return NB_OK; +} + +/* + * The northbound configuration callbacks use the 'priv' pointer present in the + * libyang lyd_node structure to store pointers to FRR internal variables + * associated to YANG lists and presence containers. Before commiting a + * candidate configuration, we must restore the 'priv' pointers stored in the + * running configuration since they might be lost while editing the candidate. + */ +static void nb_candidate_restore_priv_pointers(struct nb_config *candidate) +{ + struct lyd_node *root, *next, *dnode_iter; + + LY_TREE_FOR (running_config->dnode, root) { + LY_TREE_DFS_BEGIN (root, next, dnode_iter) { + struct lyd_node *dnode_candidate; + char xpath[XPATH_MAXLEN]; + + if (!dnode_iter->priv) + goto next; + + yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath)); + dnode_candidate = + yang_dnode_get(candidate->dnode, xpath); + if (dnode_candidate) + yang_dnode_set_entry(dnode_candidate, + dnode_iter->priv); + + next: + LY_TREE_DFS_END(root, next, dnode_iter); + } + } +} + +/* + * Perform YANG syntactic and semantic validation. + * + * WARNING: lyd_validate() can change the configuration as part of the + * validation process. + */ +static int nb_candidate_validate_yang(struct nb_config *candidate) +{ + if (lyd_validate(&candidate->dnode, LYD_OPT_STRICT | LYD_OPT_CONFIG, + ly_native_ctx) + != 0) + return NB_ERR_VALIDATION; + + return NB_OK; +} + +/* Perform code-level validation using the northbound callbacks. */ +static int nb_candidate_validate_changes(struct nb_config *candidate, + struct nb_config_cbs *changes) +{ + struct nb_config_cb *cb; + + nb_candidate_restore_priv_pointers(candidate); + RB_FOREACH (cb, nb_config_cbs, changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + int ret; + + ret = nb_configuration_callback(NB_EV_VALIDATE, change); + if (ret != NB_OK) + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +int nb_candidate_validate(struct nb_config *candidate) +{ + struct nb_config_cbs changes; + int ret; + + if (nb_candidate_validate_yang(candidate) != NB_OK) + return NB_ERR_VALIDATION; + + RB_INIT(nb_config_cbs, &changes); + nb_config_diff(running_config, candidate, &changes); + ret = nb_candidate_validate_changes(candidate, &changes); + nb_config_diff_del_changes(&changes); + + return ret; +} + +int nb_candidate_commit_prepare(struct nb_config *candidate, + enum nb_client client, const char *comment, + struct nb_transaction **transaction) +{ + struct nb_config_cbs changes; + + if (nb_candidate_validate_yang(candidate) != NB_OK) { + flog_warn(EC_LIB_NB_CANDIDATE_INVALID, + "%s: failed to validate candidate configuration", + __func__); + return NB_ERR_VALIDATION; + } + + RB_INIT(nb_config_cbs, &changes); + nb_config_diff(running_config, candidate, &changes); + if (RB_EMPTY(nb_config_cbs, &changes)) + return NB_ERR_NO_CHANGES; + + if (nb_candidate_validate_changes(candidate, &changes) != NB_OK) { + flog_warn(EC_LIB_NB_CANDIDATE_INVALID, + "%s: failed to validate candidate configuration", + __func__); + nb_config_diff_del_changes(&changes); + return NB_ERR_VALIDATION; + } + + *transaction = nb_transaction_new(candidate, &changes, client, comment); + if (*transaction == NULL) { + flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED, + "%s: failed to create transaction", __func__); + nb_config_diff_del_changes(&changes); + return NB_ERR_LOCKED; + } + + return nb_transaction_process(NB_EV_PREPARE, *transaction); +} + +void nb_candidate_commit_abort(struct nb_transaction *transaction) +{ + (void)nb_transaction_process(NB_EV_ABORT, transaction); + nb_transaction_free(transaction); +} + +void nb_candidate_commit_apply(struct nb_transaction *transaction, + bool save_transaction, uint32_t *transaction_id) +{ + (void)nb_transaction_process(NB_EV_APPLY, transaction); + nb_transaction_apply_finish(transaction); + + /* Replace running by candidate. */ + transaction->config->version++; + nb_config_replace(running_config, transaction->config, true); + + /* Record transaction. */ + if (save_transaction + && nb_db_transaction_save(transaction, transaction_id) != NB_OK) + flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED, + "%s: failed to record transaction", __func__); + + nb_transaction_free(transaction); +} + +int nb_candidate_commit(struct nb_config *candidate, enum nb_client client, + bool save_transaction, const char *comment, + uint32_t *transaction_id) +{ + struct nb_transaction *transaction = NULL; + int ret; + + ret = nb_candidate_commit_prepare(candidate, client, comment, + &transaction); + /* + * Apply the changes if the preparation phase succeeded. Otherwise abort + * the transaction. + */ + if (ret == NB_OK) + nb_candidate_commit_apply(transaction, save_transaction, + transaction_id); + else if (transaction != NULL) + nb_candidate_commit_abort(transaction); + + return ret; +} + +static void nb_log_callback(const enum nb_event event, + enum nb_operation operation, const char *xpath, + const char *value) +{ + zlog_debug( + "northbound callback: event [%s] op [%s] xpath [%s] value [%s]", + nb_event_name(event), nb_operation_name(operation), xpath, + value); +} + +/* + * Call the northbound configuration callback associated to a given + * configuration change. + */ +static int nb_configuration_callback(const enum nb_event event, + struct nb_config_change *change) +{ + enum nb_operation operation = change->cb.operation; + const char *xpath = change->cb.xpath; + const struct nb_node *nb_node = change->cb.nb_node; + const struct lyd_node *dnode = change->cb.dnode; + union nb_resource *resource; + int ret = NB_ERR; + + if (debug_northbound) { + const char *value = "(none)"; + + if (dnode && !yang_snode_is_typeless_data(dnode->schema)) + value = yang_dnode_get_string(dnode, NULL); + + nb_log_callback(event, operation, xpath, value); + } + + if (event == NB_EV_VALIDATE) + resource = NULL; + else + resource = &change->resource; + + switch (operation) { + case NB_OP_CREATE: + ret = (*nb_node->cbs.create)(event, dnode, resource); + break; + case NB_OP_MODIFY: + ret = (*nb_node->cbs.modify)(event, dnode, resource); + break; + case NB_OP_DELETE: + ret = (*nb_node->cbs.delete)(event, dnode); + break; + case NB_OP_MOVE: + ret = (*nb_node->cbs.move)(event, dnode); + break; + default: + break; + } + + if (ret != NB_OK) + flog_warn( + EC_LIB_NB_CB_CONFIG, + "%s: error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]", + __func__, nb_err_name(ret), nb_event_name(event), + nb_operation_name(operation), xpath); + + return ret; +} + +static struct nb_transaction *nb_transaction_new(struct nb_config *config, + struct nb_config_cbs *changes, + enum nb_client client, + const char *comment) +{ + struct nb_transaction *transaction; + + if (transaction_in_progress) { + flog_warn( + EC_LIB_NB_TRANSACTION_CREATION_FAILED, + "%s: error - there's already another transaction in progress", + __func__); + return NULL; + } + transaction_in_progress = true; + + transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction)); + transaction->client = client; + if (comment) + strlcpy(transaction->comment, comment, + sizeof(transaction->comment)); + transaction->config = config; + transaction->changes = *changes; + + return transaction; +} + +static void nb_transaction_free(struct nb_transaction *transaction) +{ + nb_config_diff_del_changes(&transaction->changes); + XFREE(MTYPE_TMP, transaction); + transaction_in_progress = false; +} + +/* Process all configuration changes associated to a transaction. */ +static int nb_transaction_process(enum nb_event event, + struct nb_transaction *transaction) +{ + struct nb_config_cb *cb; + + RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + int ret; + + /* + * Only try to release resources that were allocated + * successfully. + */ + if (event == NB_EV_ABORT && change->prepare_ok == false) + break; + + /* Call the appropriate callback. */ + ret = nb_configuration_callback(event, change); + switch (event) { + case NB_EV_PREPARE: + if (ret != NB_OK) + return ret; + change->prepare_ok = true; + break; + case NB_EV_ABORT: + case NB_EV_APPLY: + /* + * At this point it's not possible to reject the + * transaction anymore, so any failure here can lead to + * inconsistencies and should be treated as a bug. + * Operations prone to errors, like validations and + * resource allocations, should be performed during the + * 'prepare' phase. + */ + break; + default: + break; + } + } + + return NB_OK; +} + +static struct nb_config_cb * +nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const char *xpath, + const struct nb_node *nb_node, + const struct lyd_node *dnode) +{ + struct nb_config_cb *cb; + + cb = XCALLOC(MTYPE_TMP, sizeof(*cb)); + strlcpy(cb->xpath, xpath, sizeof(cb->xpath)); + cb->nb_node = nb_node; + cb->dnode = dnode; + RB_INSERT(nb_config_cbs, cbs, cb); + + return cb; +} + +static struct nb_config_cb * +nb_apply_finish_cb_find(struct nb_config_cbs *cbs, const char *xpath, + const struct nb_node *nb_node) +{ + struct nb_config_cb s; + + strlcpy(s.xpath, xpath, sizeof(s.xpath)); + s.nb_node = nb_node; + return RB_FIND(nb_config_cbs, cbs, &s); +} + +/* Call the 'apply_finish' callbacks. */ +static void nb_transaction_apply_finish(struct nb_transaction *transaction) +{ + struct nb_config_cbs cbs; + struct nb_config_cb *cb; + + /* Initialize tree of 'apply_finish' callbacks. */ + RB_INIT(nb_config_cbs, &cbs); + + /* Identify the 'apply_finish' callbacks that need to be called. */ + RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + const struct lyd_node *dnode = change->cb.dnode; + + /* + * Iterate up to the root of the data tree. When a node is being + * deleted, skip its 'apply_finish' callback if one is defined + * (the 'apply_finish' callbacks from the node ancestors should + * be called though). + */ + if (change->cb.operation == NB_OP_DELETE) { + char xpath[XPATH_MAXLEN]; + + dnode = dnode->parent; + if (!dnode) + break; + + /* + * The dnode from 'delete' callbacks point to elements + * from the running configuration. Use yang_dnode_get() + * to get the corresponding dnode from the candidate + * configuration that is being committed. + */ + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + dnode = yang_dnode_get(transaction->config->dnode, + xpath); + } + while (dnode) { + char xpath[XPATH_MAXLEN]; + struct nb_node *nb_node; + + nb_node = dnode->schema->priv; + if (!nb_node->cbs.apply_finish) + goto next; + + /* + * Don't call the callback more than once for the same + * data node. + */ + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + if (nb_apply_finish_cb_find(&cbs, xpath, nb_node)) + goto next; + + nb_apply_finish_cb_new(&cbs, xpath, nb_node, dnode); + + next: + dnode = dnode->parent; + } + } + + /* Call the 'apply_finish' callbacks, sorted by their priorities. */ + RB_FOREACH (cb, nb_config_cbs, &cbs) { + if (debug_northbound) + nb_log_callback(NB_EV_APPLY, NB_OP_APPLY_FINISH, + cb->xpath, NULL); + + (*cb->nb_node->cbs.apply_finish)(cb->dnode); + } + + /* Release memory. */ + while (!RB_EMPTY(nb_config_cbs, &cbs)) { + cb = RB_ROOT(nb_config_cbs, &cbs); + RB_REMOVE(nb_config_cbs, &cbs, cb); + XFREE(MTYPE_TMP, cb); + } +} + +bool nb_operation_is_valid(enum nb_operation operation, + const struct lys_node *snode) +{ + struct lys_node_container *scontainer; + struct lys_node_leaf *sleaf; + + switch (operation) { + case NB_OP_CREATE: + if (!(snode->flags & LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lys_node_leaf *)snode; + if (sleaf->type.base != LY_TYPE_EMPTY) + return false; + break; + case LYS_CONTAINER: + scontainer = (struct lys_node_container *)snode; + if (!scontainer->presence) + return false; + break; + case LYS_LIST: + case LYS_LEAFLIST: + break; + default: + return false; + } + return true; + case NB_OP_MODIFY: + if (!(snode->flags & LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lys_node_leaf *)snode; + if (sleaf->type.base == LY_TYPE_EMPTY) + return false; + + /* List keys can't be modified. */ + if (lys_is_key(sleaf, NULL)) + return false; + break; + default: + return false; + } + return true; + case NB_OP_DELETE: + if (!(snode->flags & LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lys_node_leaf *)snode; + + /* List keys can't be deleted. */ + if (lys_is_key(sleaf, NULL)) + return false; + + /* + * Only optional leafs can be deleted, or leafs whose + * parent is a case statement. + */ + if (snode->parent->nodetype == LYS_CASE) + return true; + if (sleaf->when) + return true; + if ((sleaf->flags & LYS_MAND_TRUE) || sleaf->dflt) + return false; + break; + case LYS_CONTAINER: + scontainer = (struct lys_node_container *)snode; + if (!scontainer->presence) + return false; + break; + case LYS_LIST: + case LYS_LEAFLIST: + break; + default: + return false; + } + return true; + case NB_OP_MOVE: + if (!(snode->flags & LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LIST: + case LYS_LEAFLIST: + if (!(snode->flags & LYS_USERORDERED)) + return false; + break; + default: + return false; + } + return true; + case NB_OP_APPLY_FINISH: + if (!(snode->flags & LYS_CONFIG_W)) + return false; + return true; + case NB_OP_GET_ELEM: + if (!(snode->flags & LYS_CONFIG_R)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + break; + case LYS_CONTAINER: + scontainer = (struct lys_node_container *)snode; + if (!scontainer->presence) + return false; + break; + default: + return false; + } + return true; + case NB_OP_GET_NEXT: + case NB_OP_GET_KEYS: + case NB_OP_LOOKUP_ENTRY: + if (!(snode->flags & LYS_CONFIG_R)) + return false; + + switch (snode->nodetype) { + case LYS_LIST: + break; + default: + return false; + } + return true; + case NB_OP_RPC: + if (snode->flags & (LYS_CONFIG_W | LYS_CONFIG_R)) + return false; + + switch (snode->nodetype) { + case LYS_RPC: + case LYS_ACTION: + break; + default: + return false; + } + return true; + default: + return false; + } +} + +DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), + (xpath, arguments)); + +int nb_notification_send(const char *xpath, struct list *arguments) +{ + int ret; + + ret = hook_call(nb_notification_send, xpath, arguments); + if (arguments) + list_delete(&arguments); + + return ret; +} + +const char *nb_event_name(enum nb_event event) +{ + switch (event) { + case NB_EV_VALIDATE: + return "validate"; + case NB_EV_PREPARE: + return "prepare"; + case NB_EV_ABORT: + return "abort"; + case NB_EV_APPLY: + return "apply"; + default: + return "unknown"; + } +} + +const char *nb_operation_name(enum nb_operation operation) +{ + switch (operation) { + case NB_OP_CREATE: + return "create"; + case NB_OP_MODIFY: + return "modify"; + case NB_OP_DELETE: + return "delete"; + case NB_OP_MOVE: + return "move"; + case NB_OP_APPLY_FINISH: + return "apply_finish"; + case NB_OP_GET_ELEM: + return "get_elem"; + case NB_OP_GET_NEXT: + return "get_next"; + case NB_OP_GET_KEYS: + return "get_keys"; + case NB_OP_LOOKUP_ENTRY: + return "lookup_entry"; + case NB_OP_RPC: + return "rpc"; + default: + return "unknown"; + } +} + +const char *nb_err_name(enum nb_error error) +{ + switch (error) { + case NB_OK: + return "ok"; + case NB_ERR: + return "generic error"; + case NB_ERR_NO_CHANGES: + return "no changes"; + case NB_ERR_NOT_FOUND: + return "element not found"; + case NB_ERR_LOCKED: + return "resource is locked"; + case NB_ERR_VALIDATION: + return "validation error"; + case NB_ERR_RESOURCE: + return "failed to allocate resource"; + case NB_ERR_INCONSISTENCY: + return "internal inconsistency"; + default: + return "unknown"; + } +} + +const char *nb_client_name(enum nb_client client) +{ + switch (client) { + case NB_CLIENT_CLI: + return "CLI"; + default: + return "unknown"; + } +} + +static void nb_load_callbacks(const struct frr_yang_module_info *module) +{ + for (size_t i = 0; module->nodes[i].xpath; i++) { + struct nb_node *nb_node; + uint32_t priority; + + nb_node = nb_node_find(module->nodes[i].xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, + module->nodes[i].xpath); + continue; + } + + nb_node->cbs = module->nodes[i].cbs; + priority = module->nodes[i].priority; + if (priority != 0) + nb_node->priority = priority; + } +} + +void nb_init(const struct frr_yang_module_info *modules[], size_t nmodules) +{ + unsigned int errors = 0; + + /* Load YANG modules. */ + for (size_t i = 0; i < nmodules; i++) + yang_module_load(modules[i]->name); + + /* Create a nb_node for all YANG schema nodes. */ + yang_all_snodes_iterate(nb_node_new_cb, 0, NULL, NULL); + + /* Load northbound callbacks. */ + for (size_t i = 0; i < nmodules; i++) + nb_load_callbacks(modules[i]); + + /* Validate northbound callbacks. */ + yang_all_snodes_iterate(nb_node_validate, 0, &errors, NULL); + if (errors > 0) { + flog_err( + EC_LIB_NB_CBS_VALIDATION, + "%s: failed to validate northbound callbacks: %u error(s)", + __func__, errors); + exit(1); + } + + /* Initialize the northbound database (used for the rollback log). */ + if (nb_db_init() != NB_OK) + flog_warn(EC_LIB_NB_DATABASE, + "%s: failed to initialize northbound database", + __func__); + + /* Create an empty running configuration. */ + running_config = nb_config_new(NULL); + + /* Initialize the northbound CLI. */ + nb_cli_init(); +} + +void nb_terminate(void) +{ + /* Terminate the northbound CLI. */ + nb_cli_terminate(); + + /* Delete all nb_node's from all YANG modules. */ + yang_all_snodes_iterate(nb_node_del_cb, 0, NULL, NULL); + + /* Delete the running configuration. */ + nb_config_free(running_config); +} diff --git a/lib/northbound.h b/lib/northbound.h new file mode 100644 index 000000000..9963e14a7 --- /dev/null +++ b/lib/northbound.h @@ -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_ */ diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c new file mode 100644 index 000000000..8ae44e72d --- /dev/null +++ b/lib/northbound_cli.c @@ -0,0 +1,1448 @@ +/* + * 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 + +#include "libfrr.h" +#include "version.h" +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "termtable.h" +#include "db.h" +#include "yang_translator.h" +#include "northbound.h" +#include "northbound_cli.h" +#include "northbound_db.h" +#ifndef VTYSH_EXTRACT_PL +#include "lib/northbound_cli_clippy.c" +#endif + +int debug_northbound; +struct nb_config *vty_shared_candidate_config; + +static void vty_show_libyang_errors(struct vty *vty, struct ly_ctx *ly_ctx) +{ + struct ly_err_item *ei; + const char *path; + + ei = ly_err_first(ly_ctx); + if (!ei) + return; + + for (; ei; ei = ei->next) + vty_out(vty, "%s\n", ei->msg); + + path = ly_errpath(ly_ctx); + if (path) + vty_out(vty, "YANG path: %s\n", path); + + ly_err_clean(ly_ctx, NULL); +} + +int nb_cli_cfg_change(struct vty *vty, char *xpath_base, + struct cli_config_change changes[], size_t size) +{ + struct nb_config *candidate_transitory; + bool error = false; + int ret; + + VTY_CHECK_XPATH; + + /* + * Create a copy of the candidate configuration. For consistency, we + * need to ensure that either all changes made by the command are + * accepted or none are. + */ + candidate_transitory = nb_config_dup(vty->candidate_config); + + /* Edit candidate configuration. */ + for (size_t i = 0; i < size; i++) { + struct cli_config_change *change = &changes[i]; + struct nb_node *nb_node; + char xpath[XPATH_MAXLEN]; + struct yang_data *data; + + /* Handle relative XPaths. */ + memset(xpath, 0, sizeof(xpath)); + if (vty->xpath_index > 0 + && ((xpath_base && xpath_base[0] == '.') + || change->xpath[0] == '.')) + strlcpy(xpath, VTY_CURR_XPATH, sizeof(xpath)); + if (xpath_base) { + if (xpath_base[0] == '.') + xpath_base++; + strlcat(xpath, xpath_base, sizeof(xpath)); + } + if (change->xpath[0] == '.') + strlcat(xpath, change->xpath + 1, sizeof(xpath)); + else + strlcpy(xpath, change->xpath, sizeof(xpath)); + + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + error = true; + break; + } + + /* If the value is not set, get the default if it exists. */ + if (change->value == NULL) + change->value = yang_snode_get_default(nb_node->snode); + data = yang_data_new(xpath, change->value); + + /* + * Ignore "not found" errors when editing the candidate + * configuration. + */ + ret = nb_candidate_edit(candidate_transitory, nb_node, + change->operation, xpath, NULL, data); + yang_data_free(data); + if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) { + flog_warn( + EC_LIB_NB_CANDIDATE_EDIT_ERROR, + "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", + __func__, nb_operation_name(change->operation), + xpath); + error = true; + break; + } + } + + if (error) { + nb_config_free(candidate_transitory); + + switch (frr_get_cli_mode()) { + case FRR_CLI_CLASSIC: + vty_out(vty, "%% Configuration failed.\n\n"); + break; + case FRR_CLI_TRANSACTIONAL: + vty_out(vty, + "%% Failed to edit candidate configuration.\n\n"); + break; + } + vty_show_libyang_errors(vty, ly_native_ctx); + + return CMD_WARNING_CONFIG_FAILED; + } + + nb_config_replace(vty->candidate_config, candidate_transitory, false); + + /* Do an implicit "commit" when using the classic CLI mode. */ + if (frr_get_cli_mode() == FRR_CLI_CLASSIC) { + ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, + false, NULL, NULL); + if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { + vty_out(vty, "%% Configuration failed: %s.\n\n", + nb_err_name(ret)); + vty_out(vty, + "Please check the logs for more details.\n"); + + /* Regenerate candidate for consistency. */ + nb_config_replace(vty->candidate_config, running_config, + true); + return CMD_WARNING_CONFIG_FAILED; + } + } + + return CMD_SUCCESS; +} + +int nb_cli_rpc(const char *xpath, struct list *input, struct list *output) +{ + struct nb_node *nb_node; + int ret; + + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + return CMD_WARNING; + } + + ret = nb_node->cbs.rpc(xpath, input, output); + switch (ret) { + case NB_OK: + return CMD_SUCCESS; + default: + return CMD_WARNING; + } +} + +static int nb_cli_commit(struct vty *vty, bool force, char *comment) +{ + uint32_t transaction_id; + int ret; + + if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) { + vty_out(vty, "%% Configuration is locked by another VTY.\n\n"); + return CMD_WARNING; + } + + if (!force && nb_candidate_needs_update(vty->candidate_config)) { + vty_out(vty, + "%% Candidate configuration needs to be updated before commit.\n\n"); + vty_out(vty, + "Use the \"update\" command or \"commit force\".\n"); + return CMD_WARNING; + } + + ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true, + comment, &transaction_id); + + /* Map northbound return code to CLI return code. */ + switch (ret) { + case NB_OK: + nb_config_replace(vty->candidate_config_base, running_config, + true); + vty_out(vty, + "%% Configuration committed successfully (Transaction ID #%u).\n\n", + transaction_id); + return CMD_SUCCESS; + case NB_ERR_NO_CHANGES: + vty_out(vty, "%% No configuration changes to commit.\n\n"); + return CMD_SUCCESS; + default: + vty_out(vty, + "%% Failed to commit candidate configuration: %s.\n\n", + nb_err_name(ret)); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } +} + +static int nb_cli_candidate_load_file(struct vty *vty, + enum nb_cfg_format format, + struct yang_translator *translator, + const char *path, bool replace) +{ + struct nb_config *loaded_config = NULL; + struct lyd_node *dnode; + struct ly_ctx *ly_ctx; + int ly_format; + + switch (format) { + case NB_CFG_FMT_CMDS: + loaded_config = nb_config_new(NULL); + if (!vty_read_config(loaded_config, path, config_default)) { + vty_out(vty, "%% Failed to load configuration.\n\n"); + vty_out(vty, + "Please check the logs for more details.\n"); + nb_config_free(loaded_config); + return CMD_WARNING; + } + break; + case NB_CFG_FMT_JSON: + case NB_CFG_FMT_XML: + ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML; + + ly_ctx = translator ? translator->ly_ctx : ly_native_ctx; + dnode = lyd_parse_path(ly_ctx, path, ly_format, LYD_OPT_EDIT); + if (!dnode) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed", + __func__); + vty_out(vty, "%% Failed to load configuration:\n\n"); + vty_show_libyang_errors(vty, ly_ctx); + return CMD_WARNING; + } + if (translator + && yang_translate_dnode(translator, + YANG_TRANSLATE_TO_NATIVE, &dnode) + != YANG_TRANSLATE_SUCCESS) { + vty_out(vty, "%% Failed to translate configuration\n"); + yang_dnode_free(dnode); + return CMD_WARNING; + } + loaded_config = nb_config_new(dnode); + break; + } + + if (replace) + nb_config_replace(vty->candidate_config, loaded_config, false); + else if (nb_config_merge(vty->candidate_config, loaded_config, false) + != NB_OK) { + vty_out(vty, + "%% Failed to merge the loaded configuration:\n\n"); + vty_show_libyang_errors(vty, ly_native_ctx); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static int nb_cli_candidate_load_transaction(struct vty *vty, + uint32_t transaction_id, + bool replace) +{ + struct nb_config *loaded_config; + + loaded_config = nb_db_transaction_load(transaction_id); + if (!loaded_config) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + transaction_id); + return CMD_WARNING; + } + + if (replace) + nb_config_replace(vty->candidate_config, loaded_config, false); + else if (nb_config_merge(vty->candidate_config, loaded_config, false) + != NB_OK) { + vty_out(vty, + "%% Failed to merge the loaded configuration:\n\n"); + vty_show_libyang_errors(vty, ly_native_ctx); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +void nb_cli_show_dnode_cmds(struct vty *vty, struct lyd_node *root, + bool with_defaults) +{ + struct lyd_node *next, *child; + + LY_TREE_DFS_BEGIN (root, next, child) { + struct nb_node *nb_node; + + nb_node = child->schema->priv; + if (!nb_node->cbs.cli_show) + goto next; + + /* Skip default values. */ + if (!with_defaults && yang_dnode_is_default_recursive(child)) + goto next; + + (*nb_node->cbs.cli_show)(vty, child, with_defaults); + next: + LY_TREE_DFS_END(root, next, child); + } +} + +static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config, + bool with_defaults) +{ + struct lyd_node *root; + + vty_out(vty, "Configuration:\n"); + vty_out(vty, "!\n"); + vty_out(vty, "frr version %s\n", FRR_VER_SHORT); + vty_out(vty, "frr defaults %s\n", DFLT_NAME); + + LY_TREE_FOR (config->dnode, root) + nb_cli_show_dnode_cmds(vty, root, with_defaults); + + vty_out(vty, "!\n"); + vty_out(vty, "end\n"); +} + +static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format, + struct nb_config *config, + struct yang_translator *translator, + bool with_defaults) +{ + struct lyd_node *dnode; + char *strp; + int options; + + dnode = yang_dnode_dup(config->dnode); + if (translator + && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE, + &dnode) + != YANG_TRANSLATE_SUCCESS) { + vty_out(vty, "%% Failed to translate configuration\n"); + yang_dnode_free(dnode); + return CMD_WARNING; + } + + options = LYP_FORMAT | LYP_WITHSIBLINGS; + if (with_defaults) + options |= LYP_WD_ALL; + else + options |= LYP_WD_TRIM; + + if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) { + vty_out(vty, "%s", strp); + free(strp); + } + + yang_dnode_free(dnode); + + return CMD_SUCCESS; +} + +static int nb_cli_show_config(struct vty *vty, struct nb_config *config, + enum nb_cfg_format format, + struct yang_translator *translator, + bool with_defaults) +{ + switch (format) { + case NB_CFG_FMT_CMDS: + nb_cli_show_config_cmds(vty, config, with_defaults); + break; + case NB_CFG_FMT_JSON: + return nb_cli_show_config_libyang(vty, LYD_JSON, config, + translator, with_defaults); + case NB_CFG_FMT_XML: + return nb_cli_show_config_libyang(vty, LYD_XML, config, + translator, with_defaults); + } + + return CMD_SUCCESS; +} + +static int nb_write_config(struct nb_config *config, enum nb_cfg_format format, + struct yang_translator *translator, char *path, + size_t pathlen) +{ + int fd; + struct vty *file_vty; + int ret = 0; + + snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX"); + fd = mkstemp(path); + if (fd < 0) { + flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s", + __func__, safe_strerror(errno)); + return -1; + } + + /* Make vty for configuration file. */ + file_vty = vty_new(); + file_vty->wfd = fd; + file_vty->type = VTY_FILE; + if (config) + ret = nb_cli_show_config(file_vty, config, format, translator, + false); + vty_close(file_vty); + + return ret; +} + +static int nb_cli_show_config_compare(struct vty *vty, + struct nb_config *config1, + struct nb_config *config2, + enum nb_cfg_format format, + struct yang_translator *translator) +{ + char config1_path[256]; + char config2_path[256]; + char command[BUFSIZ]; + FILE *fp; + char line[1024]; + int lineno = 0; + + if (nb_write_config(config1, format, translator, config1_path, + sizeof(config1_path)) + != 0) { + vty_out(vty, "%% Failed to process configurations.\n\n"); + return CMD_WARNING; + } + if (nb_write_config(config2, format, translator, config2_path, + sizeof(config2_path)) + != 0) { + vty_out(vty, "%% Failed to process configurations.\n\n"); + unlink(config1_path); + return CMD_WARNING; + } + + snprintf(command, sizeof(command), "diff -u %s %s", config1_path, + config2_path); + fp = popen(command, "r"); + if (!fp) { + vty_out(vty, "%% Failed to generate configuration diff.\n\n"); + unlink(config1_path); + unlink(config2_path); + return CMD_WARNING; + } + /* Print diff line by line. */ + while (fgets(line, sizeof(line), fp) != NULL) { + if (lineno++ < 2) + continue; + vty_out(vty, "%s", line); + } + pclose(fp); + + unlink(config1_path); + unlink(config2_path); + + return CMD_SUCCESS; +} + +/* Configure exclusively from this terminal. */ +DEFUN (config_exclusive, + config_exclusive_cmd, + "configure exclusive", + "Configuration from vty interface\n" + "Configure exclusively from this terminal\n") +{ + if (vty_config_exclusive_lock(vty)) + vty->node = CONFIG_NODE; + else { + vty_out(vty, "VTY configuration is locked by other VTY\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vty->private_config = true; + vty->candidate_config = nb_config_dup(running_config); + vty->candidate_config_base = nb_config_dup(running_config); + vty_out(vty, + "Warning: uncommitted changes will be discarded on exit.\n\n"); + + return CMD_SUCCESS; +} + +/* Configure using a private candidate configuration. */ +DEFUN (config_private, + config_private_cmd, + "configure private", + "Configuration from vty interface\n" + "Configure using a private candidate configuration\n") +{ + if (vty_config_lock(vty)) + vty->node = CONFIG_NODE; + else { + vty_out(vty, "VTY configuration is locked by other VTY\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vty->private_config = true; + vty->candidate_config = nb_config_dup(running_config); + vty->candidate_config_base = nb_config_dup(running_config); + vty_out(vty, + "Warning: uncommitted changes will be discarded on exit.\n\n"); + + return CMD_SUCCESS; +} + +DEFPY (config_commit, + config_commit_cmd, + "commit [force$force]", + "Commit changes into the running configuration\n" + "Force commit even if the candidate is outdated\n") +{ + return nb_cli_commit(vty, !!force, NULL); +} + +DEFPY (config_commit_comment, + config_commit_comment_cmd, + "commit [force$force] comment LINE...", + "Commit changes into the running configuration\n" + "Force commit even if the candidate is outdated\n" + "Assign a comment to this commit\n" + "Comment for this commit (Max 80 characters)\n") +{ + char *comment; + int idx = 0; + int ret; + + argv_find(argv, argc, "LINE", &idx); + comment = argv_concat(argv, argc, idx); + ret = nb_cli_commit(vty, !!force, comment); + XFREE(MTYPE_TMP, comment); + + return ret; +} + +DEFPY (config_commit_check, + config_commit_check_cmd, + "commit check", + "Commit changes into the running configuration\n" + "Check if the configuration changes are valid\n") +{ + int ret; + + ret = nb_candidate_validate(vty->candidate_config); + if (ret != NB_OK) { + vty_out(vty, + "%% Failed to validate candidate configuration.\n\n"); + vty_show_libyang_errors(vty, ly_native_ctx); + return CMD_WARNING; + } + + vty_out(vty, "%% Candidate configuration validated successfully.\n\n"); + + return CMD_SUCCESS; +} + +DEFPY (config_update, + config_update_cmd, + "update", + "Update candidate configuration\n") +{ + if (!nb_candidate_needs_update(vty->candidate_config)) { + vty_out(vty, "%% Update is not necessary.\n\n"); + return CMD_SUCCESS; + } + + if (nb_candidate_update(vty->candidate_config) != NB_OK) { + vty_out(vty, + "%% Failed to update the candidate configuration.\n\n"); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } + + nb_config_replace(vty->candidate_config_base, running_config, true); + + vty_out(vty, "%% Candidate configuration updated successfully.\n\n"); + + return CMD_SUCCESS; +} + +DEFPY (config_discard, + config_discard_cmd, + "discard", + "Discard changes in the candidate configuration\n") +{ + nb_config_replace(vty->candidate_config, vty->candidate_config_base, + true); + + return CMD_SUCCESS; +} + +DEFPY (config_load, + config_load_cmd, + "configuration load\ + <\ + file [ [translate WORD$translator_family]] FILENAME$filename\ + |transaction (1-4294967296)$tid\ + >\ + [replace$replace]", + "Configuration related settings\n" + "Load configuration into candidate\n" + "Load configuration file into candidate\n" + "Load configuration file in JSON format\n" + "Load configuration file in XML format\n" + "Translate configuration file\n" + "YANG module translator\n" + "Configuration file name (full path)\n" + "Load configuration from transaction into candidate\n" + "Transaction ID\n" + "Replace instead of merge\n") +{ + if (filename) { + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, + "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + return nb_cli_candidate_load_file(vty, format, translator, + filename, !!replace); + } + + return nb_cli_candidate_load_transaction(vty, tid, !!replace); +} + +DEFPY (show_config_running, + show_config_running_cmd, + "show configuration running\ + [ [translate WORD$translator_family]]\ + [with-defaults$with_defaults]", + SHOW_STR + "Configuration information\n" + "Running configuration\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n") + +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + nb_cli_show_config(vty, running_config, format, translator, + !!with_defaults); + + return CMD_SUCCESS; +} + +DEFPY (show_config_candidate, + show_config_candidate_cmd, + "show configuration candidate\ + [ [translate WORD$translator_family]]\ + [<\ + with-defaults$with_defaults\ + |changes$changes\ + >]", + SHOW_STR + "Configuration information\n" + "Candidate configuration\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n" + "Show changes applied in the candidate configuration\n") + +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + if (changes) + return nb_cli_show_config_compare( + vty, vty->candidate_config_base, vty->candidate_config, + format, translator); + + nb_cli_show_config(vty, vty->candidate_config, format, translator, + !!with_defaults); + + return CMD_SUCCESS; +} + +DEFPY (show_config_compare, + show_config_compare_cmd, + "show configuration compare\ + <\ + candidate$c1_candidate\ + |running$c1_running\ + |transaction (1-4294967296)$c1_tid\ + >\ + <\ + candidate$c2_candidate\ + |running$c2_running\ + |transaction (1-4294967296)$c2_tid\ + >\ + [ [translate WORD$translator_family]]", + SHOW_STR + "Configuration information\n" + "Compare two different configurations\n" + "Candidate configuration\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Candidate configuration\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n") +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + struct nb_config *config1, *config_transaction1 = NULL; + struct nb_config *config2, *config_transaction2 = NULL; + int ret = CMD_WARNING; + + if (c1_candidate) + config1 = vty->candidate_config; + else if (c1_running) + config1 = running_config; + else { + config_transaction1 = nb_db_transaction_load(c1_tid); + if (!config_transaction1) { + vty_out(vty, "%% Transaction %u does not exist\n\n", + (unsigned int)c1_tid); + goto exit; + } + config1 = config_transaction1; + } + + if (c2_candidate) + config2 = vty->candidate_config; + else if (c2_running) + config2 = running_config; + else { + config_transaction2 = nb_db_transaction_load(c2_tid); + if (!config_transaction2) { + vty_out(vty, "%% Transaction %u does not exist\n\n", + (unsigned int)c2_tid); + goto exit; + } + config2 = config_transaction2; + } + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + goto exit; + } + } + + ret = nb_cli_show_config_compare(vty, config1, config2, format, + translator); +exit: + if (config_transaction1) + nb_config_free(config_transaction1); + if (config_transaction2) + nb_config_free(config_transaction2); + + return ret; +} + +/* + * Stripped down version of the "show configuration compare" command. + * The "candidate" option is not present so the command can be installed in + * the enable node. + */ +ALIAS (show_config_compare, + show_config_compare_without_candidate_cmd, + "show configuration compare\ + <\ + running$c1_running\ + |transaction (1-4294967296)$c1_tid\ + >\ + <\ + running$c2_running\ + |transaction (1-4294967296)$c2_tid\ + >\ + [ [translate WORD$translator_family]]", + SHOW_STR + "Configuration information\n" + "Compare two different configurations\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n") + +DEFPY (clear_config_transactions, + clear_config_transactions_cmd, + "clear configuration transactions oldest (1-100)$n", + CLEAR_STR + "Configuration activity\n" + "Delete transactions from the transactions log\n" + "Delete oldest transactions\n" + "Number of transactions to delete\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (nb_db_clear_transactions(n) != NB_OK) { + vty_out(vty, "%% Failed to delete transactions.\n\n"); + return CMD_WARNING; + } +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return CMD_SUCCESS; +} + +DEFPY (config_database_max_transactions, + config_database_max_transactions_cmd, + "configuration database max-transactions (1-100)$max", + "Configuration related settings\n" + "Configuration database\n" + "Set the maximum number of transactions to store\n" + "Number of transactions\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (nb_db_set_max_transactions(max) != NB_OK) { + vty_out(vty, + "%% Failed to update the maximum number of transactions.\n\n"); + return CMD_WARNING; + } + vty_out(vty, + "%% Maximum number of transactions updated successfully.\n\n"); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return CMD_SUCCESS; +} + +DEFPY (yang_module_translator_load, + yang_module_translator_load_cmd, + "yang module-translator load FILENAME$filename", + "YANG related settings\n" + "YANG module translator\n" + "Load YANG module translator\n" + "File name (full path)\n") +{ + struct yang_translator *translator; + + translator = yang_translator_load(filename); + if (!translator) { + vty_out(vty, "%% Failed to load \"%s\"\n\n", filename); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } + + vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n", + translator->family); + + return CMD_SUCCESS; +} + +DEFPY (yang_module_translator_unload_family, + yang_module_translator_unload_cmd, + "yang module-translator unload WORD$translator_family", + "YANG related settings\n" + "YANG module translator\n" + "Unload YANG module translator\n" + "Name of the module translator\n") +{ + struct yang_translator *translator; + + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + + yang_translator_unload(translator); + + return CMD_SUCCESS; +} + +#ifdef HAVE_CONFIG_ROLLBACKS +static void nb_cli_show_transactions_cb(void *arg, int transaction_id, + const char *client_name, + const char *date, const char *comment) +{ + struct ttable *tt = arg; + + ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date, + comment); +} + +static int nb_cli_show_transactions(struct vty *vty) +{ + struct ttable *tt; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Transaction ID|Client|Date|Comment"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + /* Fetch transactions from the northbound database. */ + if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt) + != NB_OK) { + vty_out(vty, + "%% Failed to fetch configuration transactions.\n"); + return CMD_WARNING; + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No configuration transactions to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} +#endif /* HAVE_CONFIG_ROLLBACKS */ + +DEFPY (show_config_transaction, + show_config_transaction_cmd, + "show configuration transaction\ + [\ + (1-4294967296)$transaction_id\ + [ [translate WORD$translator_family]]\ + [<\ + with-defaults$with_defaults\ + |changes$changes\ + >]\ + ]", + SHOW_STR + "Configuration information\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n" + "Show changes compared to the previous transaction\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (transaction_id) { + struct nb_config *config; + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, + "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + config = nb_db_transaction_load(transaction_id); + if (!config) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + (unsigned int)transaction_id); + return CMD_WARNING; + } + + if (changes) { + struct nb_config *prev_config; + int ret; + + /* NOTE: this can be NULL. */ + prev_config = + nb_db_transaction_load(transaction_id - 1); + + ret = nb_cli_show_config_compare( + vty, prev_config, config, format, translator); + if (prev_config) + nb_config_free(prev_config); + nb_config_free(config); + + return ret; + } + + nb_cli_show_config(vty, config, format, translator, + !!with_defaults); + nb_config_free(config); + + return CMD_SUCCESS; + } + + return nb_cli_show_transactions(vty); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); + return CMD_WARNING; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +DEFPY (show_yang_module, + show_yang_module_cmd, + "show yang module [module-translator WORD$translator_family]", + SHOW_STR + "YANG information\n" + "Show loaded modules\n" + "YANG module translator\n" + "YANG module translator\n") +{ + struct ly_ctx *ly_ctx; + struct yang_translator *translator = NULL; + const struct lys_module *module; + struct ttable *tt; + uint32_t idx = 0; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) { + char flags[8]; + + snprintf(flags, sizeof(flags), "%c%c", + module->implemented ? 'I' : ' ', + (module->deviated == 1) ? 'D' : ' '); + + ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name, + (module->version == 2) ? "1.1" : "1.0", + (module->rev_size > 0) ? module->rev[0].date + : "-", + flags, module->ns); + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n"); + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No YANG modules to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +DEFPY (show_yang_module_detail, + show_yang_module_detail_cmd, + "show yang module\ + [module-translator WORD$translator_family]\ + WORD$module_name ", + SHOW_STR + "YANG information\n" + "Show loaded modules\n" + "YANG module translator\n" + "YANG module translator\n" + "Module name\n" + "Display summary information about the module\n" + "Display module in the tree (RFC 8340) format\n" + "Display module in the YANG format\n" + "Display module in the YIN format\n") +{ + struct ly_ctx *ly_ctx; + struct yang_translator *translator = NULL; + const struct lys_module *module; + LYS_OUTFORMAT format; + char *strp; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + module = ly_ctx_get_module(ly_ctx, module_name, NULL, 0); + if (!module) { + vty_out(vty, "%% Module \"%s\" not found\n", module_name); + return CMD_WARNING; + } + + if (yang) + format = LYS_OUT_YANG; + else if (yin) + format = LYS_OUT_YIN; + else if (tree) + format = LYS_OUT_TREE; + else + format = LYS_OUT_INFO; + + if (lys_print_mem(&strp, module, format, NULL, 0, 0) == 0) { + vty_out(vty, "%s\n", strp); + free(strp); + } else { + /* Unexpected. */ + vty_out(vty, "%% Error generating module information\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFPY (show_yang_module_translator, + show_yang_module_translator_cmd, + "show yang module-translator", + SHOW_STR + "YANG information\n" + "Show loaded YANG module translators\n") +{ + struct yang_translator *translator; + struct ttable *tt; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + RB_FOREACH (translator, yang_translators, &yang_translators) { + struct yang_tmodule *tmodule; + struct listnode *ln; + + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family, + tmodule->module->name, + tmodule->deviations->name, + tmodule->coverage); + } + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No YANG module translators to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +#ifdef HAVE_CONFIG_ROLLBACKS +static int nb_cli_rollback_configuration(struct vty *vty, + uint32_t transaction_id) +{ + struct nb_config *candidate; + char comment[80]; + int ret; + + candidate = nb_db_transaction_load(transaction_id); + if (!candidate) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + transaction_id); + return CMD_WARNING; + } + + snprintf(comment, sizeof(comment), "Rollback to transaction %u", + transaction_id); + + ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment, + NULL); + nb_config_free(candidate); + switch (ret) { + case NB_OK: + vty_out(vty, + "%% Configuration was successfully rolled back.\n\n"); + return CMD_SUCCESS; + case NB_ERR_NO_CHANGES: + vty_out(vty, + "%% Aborting - no configuration changes detected.\n\n"); + return CMD_WARNING; + default: + vty_out(vty, "%% Rollback failed.\n\n"); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } +} +#endif /* HAVE_CONFIG_ROLLBACKS */ + +DEFPY (rollback_config, + rollback_config_cmd, + "rollback configuration (1-4294967296)$transaction_id", + "Rollback to a previous state\n" + "Running configuration\n" + "Transaction ID\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + return nb_cli_rollback_configuration(vty, transaction_id); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); + return CMD_SUCCESS; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +/* Debug CLI commands. */ +DEFUN (debug_nb, + debug_nb_cmd, + "debug northbound", + DEBUG_STR + "Northbound Debugging\n") +{ + debug_northbound = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_debug_nb, + no_debug_nb_cmd, + "no debug northbound", + NO_STR DEBUG_STR + "Northbound Debugging\n") +{ + debug_northbound = 0; + + return CMD_SUCCESS; +} + +static int nb_debug_config_write(struct vty *vty) +{ + if (debug_northbound) + vty_out(vty, "debug northbound\n"); + + return 1; +} + +static struct cmd_node nb_debug_node = {NORTHBOUND_DEBUG_NODE, "", 1}; + +void nb_cli_install_default(int node) +{ + if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL) + return; + + install_element(node, &config_commit_cmd); + install_element(node, &config_commit_comment_cmd); + install_element(node, &config_commit_check_cmd); + install_element(node, &config_update_cmd); + install_element(node, &config_discard_cmd); + install_element(node, &show_config_running_cmd); + install_element(node, &show_config_candidate_cmd); + install_element(node, &show_config_compare_cmd); + install_element(node, &show_config_transaction_cmd); +} + +/* YANG module autocomplete. */ +static void yang_module_autocomplete(vector comps, struct cmd_token *token) +{ + const struct lys_module *module; + struct yang_translator *module_tr; + uint32_t idx; + + idx = 0; + while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx))) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name)); + + RB_FOREACH (module_tr, yang_translators, &yang_translators) { + idx = 0; + while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx, + &idx))) + vector_set(comps, + XSTRDUP(MTYPE_COMPLETION, module->name)); + } +} + +/* YANG module translator autocomplete. */ +static void yang_translator_autocomplete(vector comps, struct cmd_token *token) +{ + struct yang_translator *module_tr; + + RB_FOREACH (module_tr, yang_translators, &yang_translators) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family)); +} + +static const struct cmd_variable_handler yang_var_handlers[] = { + {.varname = "module_name", .completions = yang_module_autocomplete}, + {.varname = "translator_family", + .completions = yang_translator_autocomplete}, + {.completions = NULL}}; + +void nb_cli_init(void) +{ + /* Initialize the shared candidate configuration. */ + vty_shared_candidate_config = nb_config_new(NULL); + + /* Install debug commands */ + install_node(&nb_debug_node, nb_debug_config_write); + install_element(ENABLE_NODE, &debug_nb_cmd); + install_element(ENABLE_NODE, &no_debug_nb_cmd); + install_element(CONFIG_NODE, &debug_nb_cmd); + install_element(CONFIG_NODE, &no_debug_nb_cmd); + + /* Install commands specific to the transaction-base mode. */ + if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) { + install_element(ENABLE_NODE, &config_exclusive_cmd); + install_element(ENABLE_NODE, &config_private_cmd); + install_element(ENABLE_NODE, &show_config_running_cmd); + install_element(ENABLE_NODE, + &show_config_compare_without_candidate_cmd); + install_element(ENABLE_NODE, &show_config_transaction_cmd); + install_element(ENABLE_NODE, &rollback_config_cmd); + install_element(ENABLE_NODE, &clear_config_transactions_cmd); + + install_element(CONFIG_NODE, &config_load_cmd); + install_element(CONFIG_NODE, + &config_database_max_transactions_cmd); + } + + /* Other commands. */ + install_element(CONFIG_NODE, &yang_module_translator_load_cmd); + install_element(CONFIG_NODE, &yang_module_translator_unload_cmd); + install_element(ENABLE_NODE, &show_yang_module_cmd); + install_element(ENABLE_NODE, &show_yang_module_detail_cmd); + install_element(ENABLE_NODE, &show_yang_module_translator_cmd); + cmd_variable_handler_register(yang_var_handlers); +} + +void nb_cli_terminate(void) +{ + nb_config_free(vty_shared_candidate_config); +} diff --git a/lib/northbound_cli.h b/lib/northbound_cli.h new file mode 100644 index 000000000..7f4a64c01 --- /dev/null +++ b/lib/northbound_cli.h @@ -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_ */ diff --git a/lib/northbound_db.c b/lib/northbound_db.c new file mode 100644 index 000000000..598805b96 --- /dev/null +++ b/lib/northbound_db.c @@ -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 + +#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; +} diff --git a/lib/northbound_db.h b/lib/northbound_db.h new file mode 100644 index 000000000..ad6096644 --- /dev/null +++ b/lib/northbound_db.h @@ -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_ */ diff --git a/lib/subdir.am b/lib/subdir.am index e2571f0c6..5304f8fc8 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -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 \ diff --git a/lib/vty.c b/lib/vty.c index a73cc23b9..90ca5f502 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -41,6 +41,7 @@ #include "libfrr.h" #include "frrstr.h" #include "lib_errors.h" +#include "northbound_cli.h" #include #include @@ -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; diff --git a/lib/vty.h b/lib/vty.h index efe91a568..4c434fb2f 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -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 *); diff --git a/lib/yang.c b/lib/yang.c new file mode 100644 index 000000000..bb5d38e2e --- /dev/null +++ b/lib/yang.c @@ -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 + +#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); +} diff --git a/lib/yang.h b/lib/yang.h new file mode 100644 index 000000000..cd5597ff8 --- /dev/null +++ b/lib/yang.h @@ -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 +#ifdef HAVE_SYSREPO +#include +#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_ */ diff --git a/lib/yang_translator.c b/lib/yang_translator.c new file mode 100644 index 000000000..27b92a0e6 --- /dev/null +++ b/lib/yang_translator.c @@ -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 + +#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); +} diff --git a/lib/yang_translator.h b/lib/yang_translator.h new file mode 100644 index 000000000..6b49d1acf --- /dev/null +++ b/lib/yang_translator.h @@ -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_ */ diff --git a/lib/yang_wrappers.c b/lib/yang_wrappers.c new file mode 100644 index 000000000..60a7456aa --- /dev/null +++ b/lib/yang_wrappers.c @@ -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 + +#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); +} diff --git a/lib/yang_wrappers.h b/lib/yang_wrappers.h new file mode 100644 index 000000000..08a263bac --- /dev/null +++ b/lib/yang_wrappers.h @@ -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_ */ diff --git a/ripd/rip_main.c b/ripd/rip_main.c index 16087c234..4602cfcd6 100644 --- a/ripd/rip_main.c +++ b/ripd/rip_main.c @@ -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. */ } diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c index bc81a956d..b416b2fd0 100644 --- a/ripngd/ripng_main.c +++ b/ripngd/ripng_main.c @@ -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. */ } diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c index 8e81119f5..2f464d721 100644 --- a/tests/bgpd/test_peer_attr.c +++ b/tests/bgpd/test_peer_attr.c @@ -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; diff --git a/tests/helpers/c/main.c b/tests/helpers/c/main.c index fed1d5a53..9e34a7c25 100644 --- a/tests/helpers/c/main.c +++ b/tests/helpers/c/main.c @@ -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(); diff --git a/tests/lib/cli/common_cli.c b/tests/lib/cli/common_cli.c index 0fd2f80a3..04f1e3253 100644 --- a/tests/lib/cli/common_cli.c +++ b/tests/lib/cli/common_cli.c @@ -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); diff --git a/tests/lib/cli/test_cli.refout.in b/tests/lib/cli/test_cli.refout.in index ba789de81..af8f9ce56 100644 --- a/tests/lib/cli/test_cli.refout.in +++ b/tests/lib/cli/test_cli.refout.in @@ -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 diff --git a/tests/lib/cli/test_commands.c b/tests/lib/cli/test_commands.c index a8b42ba78..74816ece8 100644 --- a/tests/lib/cli/test_commands.c +++ b/tests/lib/cli/test_commands.c @@ -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, diff --git a/tools/.gitignore b/tools/.gitignore index a8d01d680..c23322c4c 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1,3 +1,5 @@ /frr +/gen_northbound_callbacks +/gen_yang_deviations /permutations /ssd diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c new file mode 100644 index 000000000..8ef105484 --- /dev/null +++ b/tools/gen_northbound_callbacks.c @@ -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 + +#include + +#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; +} diff --git a/tools/gen_yang_deviations.c b/tools/gen_yang_deviations.c new file mode 100644 index 000000000..121969c6f --- /dev/null +++ b/tools/gen_yang_deviations.c @@ -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 + +#include + +#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; +} diff --git a/tools/subdir.am b/tools/subdir.am index f8a14d10c..2e68dfee0 100644 --- a/tools/subdir.am +++ b/tools/subdir.am @@ -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 += \ diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index 541eafcf7..fe12f82ef 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -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) diff --git a/yang/frr-module-translator.yang b/yang/frr-module-translator.yang new file mode 100644 index 000000000..3d64ec539 --- /dev/null +++ b/yang/frr-module-translator.yang @@ -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: + FRR Development List: "; + 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"; + } + } + } + } +} diff --git a/yang/libyang_plugins/frr_user_types.c b/yang/libyang_plugins/frr_user_types.c new file mode 100644 index 000000000..4814f5bc1 --- /dev/null +++ b/yang/libyang_plugins/frr_user_types.c @@ -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 + +#include "prefix.h" + +#include + +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 */ +}; diff --git a/yang/libyang_plugins/subdir.am b/yang/libyang_plugins/subdir.am new file mode 100644 index 000000000..956d22587 --- /dev/null +++ b/yang/libyang_plugins/subdir.am @@ -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 diff --git a/yang/subdir.am b/yang/subdir.am new file mode 100644 index 000000000..b290f9b27 --- /dev/null +++ b/yang/subdir.am @@ -0,0 +1 @@ +dist_yangmodels_DATA += yang/frr-module-translator.yang -- 2.39.5