*.pb.h
*.pb-c.h
*.pb-c.c
+*.pb.cc
*_clippy.c
### dist
include watchfrr/subdir.am
include qpb/subdir.am
include fpm/subdir.am
+include grpc/subdir.am
include tools/subdir.am
include solaris/subdir.am
doc/user/Makefile \
eigrpd/Makefile \
fpm/Makefile \
+ grpc/Makefile \
isisd/Makefile \
ldpd/Makefile \
lib/Makefile \
dnl --------------------
dnl note orig_cflags is also used further down
orig_cflags="$CFLAGS"
+orig_cxxflags="$CXXFLAGS"
AC_LANG([C])
AC_PROG_CC
AC_PROG_CPP
+AC_PROG_CXX
AM_PROG_CC_C_O
dnl remove autoconf default "-g -O2"
CFLAGS="$orig_cflags"
+CXXFLAGS="$orig_cxxflags"
AC_PROG_CC_C99
dnl NB: see C11 below
AS_HELP_STRING([--enable-confd=ARG], [enable confd integration]))
AC_ARG_ENABLE([sysrepo],
AS_HELP_STRING([--enable-sysrepo], [enable sysrepo integration]))
+AC_ARG_ENABLE([grpc],
+ AS_HELP_STRING([--enable-grpc], [enable the gRPC northbound plugin]))
AC_ARG_ENABLE([zeromq],
AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)]))
AC_ARG_WITH([libpam],
fi
AM_CONDITIONAL([SYSREPO], [test "x$enable_sysrepo" = "xyes"])
+dnl ---------------
+dnl gRPC
+dnl ---------------
+if test "$enable_grpc" = "yes"; then
+ PKG_CHECK_MODULES([GRPC], [grpc grpc++ protobuf], [
+ AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false])
+ if test "$PROTOC" = "/bin/false"; then
+ AC_MSG_FAILURE([grpc requested but protoc not found.])
+ fi
+
+ AC_DEFINE([HAVE_GRPC], [1], [Enable the gRPC northbound plugin])
+ GRPC=true
+ ], [
+ GRPC=false
+ AC_MSG_ERROR([grpc/grpc++ were not found on your system.])
+ ])
+fi
+AM_CONDITIONAL([GRPC], [test "x$enable_grpc" = "xyes"])
+
dnl ---------------
dnl math
dnl ---------------
--- /dev/null
+all: ALWAYS
+ @$(MAKE) -s -C .. grpc/libfrrgrpc_pb.la
+%: ALWAYS
+ @$(MAKE) -s -C .. grpc/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
--- /dev/null
+//
+// Copyright (C) 2019 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
+//
+
+syntax = "proto3";
+
+package frr;
+
+// Service specification for the FRR northbound interface.
+service Northbound {
+ // Retrieve the capabilities supported by the target.
+ rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse) {}
+
+ // Retrieve configuration data, state data or both from the target.
+ rpc Get(GetRequest) returns (stream GetResponse) {}
+
+ // Create a new candidate configuration and return a reference to it. The
+ // created candidate is a copy of the running configuration.
+ rpc CreateCandidate(CreateCandidateRequest) returns (CreateCandidateResponse) {}
+
+ // Delete a candidate configuration.
+ rpc DeleteCandidate(DeleteCandidateRequest) returns (DeleteCandidateResponse) {}
+
+ // 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.
+ rpc UpdateCandidate(UpdateCandidateRequest) returns (UpdateCandidateResponse) {}
+
+ // Edit a candidate configuration. All changes are discarded if any error
+ // happens.
+ rpc EditCandidate(EditCandidateRequest) returns (EditCandidateResponse) {}
+
+ // Load configuration data into a candidate configuration. Both merge and
+ // replace semantics are supported.
+ rpc LoadToCandidate(LoadToCandidateRequest) returns (LoadToCandidateResponse) {}
+
+ // Create a new configuration transaction using a two-phase commit protocol.
+ rpc Commit(CommitRequest) returns (CommitResponse) {}
+
+ // List the metadata of all configuration transactions recorded in the
+ // transactions database.
+ rpc ListTransactions(ListTransactionsRequest) returns (stream ListTransactionsResponse) {}
+
+ // Fetch a configuration (identified by its transaction ID) from the
+ // transactions database.
+ rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {}
+
+ // Lock the running configuration, preventing other users from changing it.
+ rpc LockConfig(LockConfigRequest) returns (LockConfigResponse) {}
+
+ // Unlock the running configuration.
+ rpc UnlockConfig(UnlockConfigRequest) returns (UnlockConfigResponse) {}
+
+ // Execute a YANG RPC.
+ rpc Execute(ExecuteRequest) returns (ExecuteResponse) {}
+}
+
+// ----------------------- Parameters and return types -------------------------
+
+//
+// RPC: GetCapabilities()
+//
+message GetCapabilitiesRequest {
+ // Empty.
+}
+
+message GetCapabilitiesResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // FRR version.
+ string frr_version = 1;
+
+ // Indicates whether FRR was compiled with support for configuration
+ // rollbacks or not (--enable-config-rollbacks).
+ bool rollback_support = 2;
+
+ // Supported schema modules.
+ repeated ModuleData supported_modules = 3;
+
+ // Supported encodings.
+ repeated Encoding supported_encodings = 4;
+}
+
+//
+// RPC: Get()
+//
+message GetRequest {
+ // Type of elements within the data tree.
+ enum DataType {
+ // All data elements.
+ ALL = 0;
+
+ // Config elements.
+ CONFIG = 1;
+
+ // State elements.
+ STATE = 2;
+ }
+
+ // The type of data being requested.
+ DataType type = 1;
+
+ // Encoding to be used.
+ Encoding encoding = 2;
+
+ // Include implicit default nodes.
+ bool with_defaults = 3;
+
+ // Paths requested by the client.
+ repeated string path = 4;
+}
+
+message GetResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::INVALID_ARGUMENT: Invalid YANG data path.
+
+ // Timestamp in nanoseconds since Epoch.
+ int64 timestamp = 1;
+
+ // The requested data.
+ DataTree data = 2;
+}
+
+//
+// RPC: CreateCandidate()
+//
+message CreateCandidateRequest {
+ // Empty.
+}
+
+message CreateCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::RESOURCE_EXHAUSTED: can't create candidate
+ // configuration.
+
+ // Handle to the new created candidate configuration.
+ uint32 candidate_id = 1;
+}
+
+//
+// RPC: DeleteCandidate()
+//
+message DeleteCandidateRequest {
+ // Candidate configuration to delete.
+ uint32 candidate_id = 1;
+}
+
+message DeleteCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+}
+
+//
+// RPC: UpdateCandidate()
+//
+message UpdateCandidateRequest {
+ // Candidate configuration to update.
+ uint32 candidate_id = 1;
+}
+
+message UpdateCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+}
+
+//
+// RPC: EditCandidate()
+//
+message EditCandidateRequest {
+ // Candidate configuration that is going to be edited.
+ uint32 candidate_id = 1;
+
+ // Data elements to be created or updated.
+ repeated PathValue update = 2;
+
+ // Paths to be deleted from the data tree.
+ repeated PathValue delete = 3;
+}
+
+message EditCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found.
+ // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while editing the
+ // candidate configuration.
+}
+
+//
+// RPC: LoadToCandidate()
+//
+message LoadToCandidateRequest {
+ enum LoadType {
+ // Merge the data tree into the candidate configuration.
+ MERGE = 0;
+
+ // Replace the candidate configuration by the provided data tree.
+ REPLACE = 1;
+ }
+
+ // Candidate configuration that is going to be edited.
+ uint32 candidate_id = 1;
+
+ // Load operation to apply.
+ LoadType type = 2;
+
+ // Configuration data.
+ DataTree config = 3;
+}
+
+message LoadToCandidateResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while performing
+ // the load operation.
+}
+
+//
+// RPC: Commit()
+//
+message CommitRequest {
+ enum Phase {
+ // Validate if the configuration changes are valid (phase 0).
+ VALIDATE = 0;
+
+ // Prepare resources to apply the configuration changes (phase 1).
+ PREPARE = 1;
+
+ // Release previously allocated resources (phase 2).
+ ABORT = 2;
+
+ // Apply the configuration changes (phase 2).
+ APPLY = 3;
+
+ // All of the above (VALIDATE + PREPARE + ABORT/APPLY).
+ //
+ // This option can't be used to implement network-wide transactions,
+ // since they require the manager entity to take into account the results
+ // of the preparation phase of multiple managed devices.
+ ALL = 4;
+ }
+
+ // Candidate configuration that is going to be committed.
+ uint32 candidate_id = 1;
+
+ // Transaction phase.
+ Phase phase = 2;
+
+ // Assign a comment to this commit.
+ string comment = 3;
+}
+
+message CommitResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: misuse of the two-phase commit
+ // protocol.
+ // - grpc::StatusCode::INVALID_ARGUMENT: Validation error.
+ // - grpc::StatusCode::RESOURCE_EXHAUSTED: Failure to allocate resource.
+
+ // ID of the created configuration transaction (when the phase is APPLY
+ // or ALL).
+ uint32 transaction_id = 1;
+}
+
+//
+// RPC: ListTransactions()
+//
+message ListTransactionsRequest {
+ // Empty.
+}
+
+message ListTransactionsResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // Transaction ID.
+ uint32 id = 1;
+
+ // Client that committed the transaction.
+ string client = 2;
+
+ // Date and time the transaction was committed.
+ string date = 3;
+
+ // Comment assigned to the transaction.
+ string comment = 4;
+}
+
+//
+// RPC: GetTransaction()
+//
+message GetTransactionRequest {
+ // Transaction to retrieve.
+ uint32 transaction_id = 1;
+
+ // Encoding to be used.
+ Encoding encoding = 2;
+
+ // Include implicit default nodes.
+ bool with_defaults = 3;
+}
+
+message GetTransactionResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::NOT_FOUND: Transaction wasn't found in the transactions
+ // database.
+
+ DataTree config = 1;
+}
+
+//
+// RPC: LockConfig()
+//
+message LockConfigRequest {
+ // Empty.
+}
+
+message LockConfigResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration is
+ // locked already.
+}
+
+//
+// RPC: UnlockConfig()
+//
+message UnlockConfigRequest {
+ // Empty.
+}
+
+message UnlockConfigResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+ // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration isn't
+ // locked.
+}
+
+//
+// RPC: Execute()
+//
+message ExecuteRequest {
+ // Path of the YANG RPC or YANG Action.
+ string path = 1;
+
+ // Input parameters.
+ repeated PathValue input = 2;
+}
+
+message ExecuteResponse {
+ // Return values:
+ // - grpc::StatusCode::OK: Success.
+
+ // Output parameters.
+ repeated PathValue output = 1;
+}
+
+// -------------------------------- Definitions --------------------------------
+
+// YANG module.
+message ModuleData {
+ // Name of the YANG module;
+ string name = 1;
+
+ // Organization publishing the module.
+ string organization = 2;
+
+ // Latest revision of the module;
+ string revision = 3;
+}
+
+// Supported encodings for YANG instance data.
+enum Encoding {
+ JSON = 0;
+ XML = 1;
+}
+
+// Path-value pair representing a data element.
+message PathValue {
+ // YANG data path.
+ string path = 1;
+
+ // Data value.
+ string value = 2;
+}
+
+// YANG instance data.
+message DataTree {
+ Encoding encoding = 1;
+ string data = 2;
+}
--- /dev/null
+if GRPC
+lib_LTLIBRARIES += grpc/libfrrgrpc_pb.la
+endif
+
+grpc_libfrrgrpc_pb_la_LDFLAGS = -version-info 0:0:0
+grpc_libfrrgrpc_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(GRPC_CXXFLAGS)
+
+nodist_grpc_libfrrgrpc_pb_la_SOURCES = \
+ grpc/frr-northbound.pb.cc \
+ grpc/frr-northbound.grpc.pb.cc \
+ # end
+
+CLEANFILES += \
+ grpc/frr-northbound.pb.cc \
+ grpc/frr-northbound.pb.h \
+ grpc/frr-northbound.grpc.pb.cc \
+ grpc/frr-northbound.grpc.pb.h \
+ # end
+
+EXTRA_DIST += grpc/frr-northbound.proto
+
+AM_V_PROTOC = $(am__v_PROTOC_$(V))
+am__v_PROTOC_ = $(am__v_PROTOC_$(AM_DEFAULT_VERBOSITY))
+am__v_PROTOC_0 = @echo " PROTOC" $@;
+am__v_PROTOC_1 =
+
+.proto.pb.cc:
+ $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --cpp_out=$(top_srcdir) $(top_srcdir)/$^
+.proto.grpc.pb.cc:
+ $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --grpc_out=$(top_srcdir) --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` $(top_srcdir)/$^
}
/* check if the interface is a loopback and if so set it as passive */
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (ifp && if_is_loopback(ifp))
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
- NB_OP_MODIFY, "true");
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (ifp && if_is_loopback(ifp))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
+ NB_OP_MODIFY, "true");
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return nb_cli_apply_changes(vty, NULL);
}
}
/* check if the interface is a loopback and if so set it as passive */
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (ifp && if_is_loopback(ifp))
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
- NB_OP_MODIFY, "true");
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (ifp && if_is_loopback(ifp))
+ nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive",
+ NB_OP_MODIFY, "true");
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return nb_cli_apply_changes(vty, NULL);
}
"Act as both a station router and an area router\n"
"Act as an area router only\n")
{
- const char *value = NULL;
- struct isis_area *area;
+ const char *value;
- area = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ struct isis_area *area;
+
+ area = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+
+ /*
+ * Put the is-type back to defaults:
+ * - level-1-2 on first area
+ * - level-1 for the rest
+ */
+ if (area && listgetdata(listhead(isis->area_list)) == area)
+ value = "level-1-2";
+ else
+ value = NULL;
+ }
+ pthread_rwlock_unlock(&running_config->lock);
- /*
- * Put the is-type back to defaults:
- * - level-1-2 on first area
- * - level-1 for the rest
- */
- if (area && listgetdata(listhead(isis->area_list)) == area)
- value = "level-1-2";
- else
- value = NULL;
nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, value);
return nb_cli_apply_changes(vty, NULL);
"Level-1-2 adjacencies are formed\n"
"Level-2 only adjacencies are formed\n")
{
- struct interface *ifp;
- struct isis_circuit *circuit;
- int is_type;
- const char *circ_type;
+ const char *circ_type = NULL;
/*
* Default value depends on whether the circuit is part of an area,
* and the is-type of the area if there is one. So we need to do this
* here.
*/
- ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
- if (!ifp)
- goto def_val;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ struct interface *ifp;
+ struct isis_circuit *circuit;
- circuit = circuit_scan_by_ifp(ifp);
- if (!circuit)
- goto def_val;
+ ifp = nb_running_get_entry(NULL, VTY_CURR_XPATH, false);
+ if (!ifp)
+ goto unlock;
- if (circuit->state == C_STATE_UP)
- is_type = circuit->area->is_type;
- else
- goto def_val;
+ circuit = circuit_scan_by_ifp(ifp);
+ if (!circuit || circuit->state != C_STATE_UP)
+ goto unlock;
- switch (is_type) {
- case IS_LEVEL_1:
- circ_type = "level-1";
- break;
- case IS_LEVEL_2:
- circ_type = "level-2";
- break;
- case IS_LEVEL_1_AND_2:
- circ_type = "level-1-2";
- break;
- default:
- return CMD_ERR_NO_MATCH;
+ switch (circuit->area->is_type) {
+ case IS_LEVEL_1:
+ circ_type = "level-1";
+ break;
+ case IS_LEVEL_2:
+ circ_type = "level-2";
+ break;
+ case IS_LEVEL_1_AND_2:
+ circ_type = "level-1-2";
+ break;
+ }
}
- nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type",
- NB_OP_MODIFY, circ_type);
-
- return nb_cli_apply_changes(vty, NULL);
+unlock:
+ pthread_rwlock_unlock(&running_config->lock);
-def_val:
nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type",
- NB_OP_MODIFY, NULL);
+ NB_OP_MODIFY, circ_type);
return nb_cli_apply_changes(vty, NULL);
}
vty_out(vty, "frr defaults %s\n", DFLT_NAME);
vty_out(vty, "!\n");
- for (i = 0; i < vector_active(cmdvec); i++)
- if ((node = vector_slot(cmdvec, i)) && node->func
- && (node->vtysh || vty->type != VTY_SHELL)) {
- if ((*node->func)(vty))
- vty_out(vty, "!\n");
- }
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ for (i = 0; i < vector_active(cmdvec); i++)
+ if ((node = vector_slot(cmdvec, i)) && node->func
+ && (node->vtysh || vty->type != VTY_SHELL)) {
+ if ((*node->func)(vty))
+ vty_out(vty, "!\n");
+ }
+ }
+ pthread_rwlock_unlock(&running_config->lock);
if (vty->type == VTY_TERM) {
vty_out(vty, "end\n");
if (yang_module_find("frr-interface")) {
struct lyd_node *if_dnode;
- if_dnode = yang_dnode_get(
- running_config->dnode,
- "/frr-interface:lib/interface[name='%s'][vrf='%s']/vrf",
- ifp->name, old_vrf->name);
- if (if_dnode) {
- yang_dnode_change_leaf(if_dnode, vrf->name);
- running_config->version++;
+ pthread_rwlock_wrlock(&running_config->lock);
+ {
+ if_dnode = yang_dnode_get(
+ running_config->dnode,
+ "/frr-interface:lib/interface[name='%s'][vrf='%s']/vrf",
+ ifp->name, old_vrf->name);
+ if (if_dnode) {
+ yang_dnode_change_leaf(if_dnode, vrf->name);
+ running_config->version++;
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
}
}
-
/* Delete interface structure. */
void if_delete_retain(struct interface *ifp)
{
.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 = EC_LIB_GRPC_INIT,
+ .title = "gRPC initialization error",
+ .description = "Upon startup FRR failed to properly initialize and startup the gRPC northbound plugin",
+ .suggestion = "Check if the gRPC libraries are installed correctly in the system.",
+ },
{
.code = EC_LIB_NB_CB_CONFIG_ABORT,
.title = "A northbound configuration callback has failed in the ABORT phase",
EC_LIB_SYSREPO_INIT,
EC_LIB_SYSREPO_DATA_CONVERT,
EC_LIB_LIBSYSREPO,
+ EC_LIB_GRPC_INIT,
EC_LIB_ID_CONSISTENCY,
EC_LIB_ID_EXHAUST,
};
/*
* Update the shared candidate after reading the startup configuration.
*/
- nb_config_replace(vty_shared_candidate_config, running_config, true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty_shared_candidate_config, running_config,
+ true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return 0;
}
/* Hash table of user pointers associated with configuration entries. */
static struct hash *running_config_entries;
+/* Management lock for the running configuration. */
+static struct {
+ /* Mutex protecting this structure. */
+ pthread_mutex_t mtx;
+
+ /* Actual lock. */
+ bool locked;
+
+ /* Northbound client who owns this lock. */
+ enum nb_client owner_client;
+
+ /* Northbound user who owns this lock. */
+ const void *owner_user;
+} running_config_mgmt_lock;
+
/*
* Global lock used to prevent multiple configuration transactions from
* happening concurrently.
static struct nb_transaction *nb_transaction_new(struct nb_config *config,
struct nb_config_cbs *changes,
enum nb_client client,
+ const void *user,
const char *comment);
static void nb_transaction_free(struct nb_transaction *transaction);
static int nb_transaction_process(enum nb_event event,
else
config->dnode = yang_dnode_new(ly_native_ctx, true);
config->version = 0;
+ pthread_rwlock_init(&config->lock, NULL);
return config;
}
{
if (config->dnode)
yang_dnode_free(config->dnode);
+ pthread_rwlock_destroy(&config->lock);
XFREE(MTYPE_NB_CONFIG, config);
}
dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup));
dup->dnode = yang_dnode_dup(config->dnode);
dup->version = config->version;
+ pthread_rwlock_init(&dup->lock, NULL);
return dup;
}
bool nb_candidate_needs_update(const struct nb_config *candidate)
{
- if (candidate->version < running_config->version)
- return true;
+ bool ret = false;
- return false;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (candidate->version < running_config->version)
+ ret = true;
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
+ return ret;
}
int nb_candidate_update(struct nb_config *candidate)
{
struct nb_config *updated_config;
- updated_config = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ updated_config = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
if (nb_config_merge(updated_config, candidate, true) != NB_OK)
return NB_ERR;
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);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ ret = nb_candidate_validate_changes(candidate, &changes);
+ nb_config_diff_del_changes(&changes);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return ret;
}
int nb_candidate_commit_prepare(struct nb_config *candidate,
- enum nb_client client, const char *comment,
+ enum nb_client client, const void *user,
+ const char *comment,
struct nb_transaction **transaction)
{
struct nb_config_cbs changes;
}
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;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_diff(running_config, candidate, &changes);
+ if (RB_EMPTY(nb_config_cbs, &changes)) {
+ pthread_rwlock_unlock(&running_config->lock);
+ 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;
- }
+ 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);
+ pthread_rwlock_unlock(&running_config->lock);
+ 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;
+ *transaction = nb_transaction_new(candidate, &changes, client,
+ user, comment);
+ if (*transaction == NULL) {
+ flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: failed to create transaction", __func__);
+ nb_config_diff_del_changes(&changes);
+ pthread_rwlock_unlock(&running_config->lock);
+ return NB_ERR_LOCKED;
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
return nb_transaction_process(NB_EV_PREPARE, *transaction);
}
/* Replace running by candidate. */
transaction->config->version++;
- nb_config_replace(running_config, transaction->config, true);
+ pthread_rwlock_wrlock(&running_config->lock);
+ {
+ nb_config_replace(running_config, transaction->config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
/* Record transaction. */
if (save_transaction
}
int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
- bool save_transaction, const char *comment,
- uint32_t *transaction_id)
+ const void *user, 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,
+ ret = nb_candidate_commit_prepare(candidate, client, user, comment,
&transaction);
/*
* Apply the changes if the preparation phase succeeded. Otherwise abort
return ret;
}
+int nb_running_lock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked) {
+ running_config_mgmt_lock.locked = true;
+ running_config_mgmt_lock.owner_client = client;
+ running_config_mgmt_lock.owner_user = user;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_unlock(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (running_config_mgmt_lock.locked
+ && running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user) {
+ running_config_mgmt_lock.locked = false;
+ running_config_mgmt_lock.owner_client = NB_CLIENT_NONE;
+ running_config_mgmt_lock.owner_user = NULL;
+ ret = 0;
+ }
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
+int nb_running_lock_check(enum nb_client client, const void *user)
+{
+ int ret = -1;
+
+ pthread_mutex_lock(&running_config_mgmt_lock.mtx);
+ {
+ if (!running_config_mgmt_lock.locked
+ || (running_config_mgmt_lock.owner_client == client
+ && running_config_mgmt_lock.owner_user == user))
+ ret = 0;
+ }
+ pthread_mutex_unlock(&running_config_mgmt_lock.mtx);
+
+ return ret;
+}
+
static void nb_log_callback(const enum nb_event event,
enum nb_operation operation, const char *xpath,
const char *value)
return nb_node->cbs.rpc(xpath, input, output);
}
-static struct nb_transaction *nb_transaction_new(struct nb_config *config,
- struct nb_config_cbs *changes,
- enum nb_client client,
- const char *comment)
+static struct nb_transaction *
+nb_transaction_new(struct nb_config *config, struct nb_config_cbs *changes,
+ enum nb_client client, const void *user, const char *comment)
{
struct nb_transaction *transaction;
+ if (nb_running_lock_check(client, user)) {
+ flog_warn(
+ EC_LIB_NB_TRANSACTION_CREATION_FAILED,
+ "%s: running configuration is locked by another client",
+ __func__);
+ return NULL;
+ }
+
if (transaction_in_progress) {
flog_warn(
EC_LIB_NB_TRANSACTION_CREATION_FAILED,
{
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;
+ /*
+ * Need to lock the running configuration since transaction->changes
+ * can contain pointers to data nodes from the running configuration.
+ */
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ RB_FOREACH (cb, nb_config_cbs, &transaction->changes) {
+ struct nb_config_change *change =
+ (struct nb_config_change *)cb;
+ int ret;
- /* Call the appropriate callback. */
- ret = nb_callback_configuration(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.
+ * Only try to release resources that were allocated
+ * successfully.
*/
- break;
- default:
- break;
+ if (event == NB_EV_ABORT && change->prepare_ok == false)
+ break;
+
+ /* Call the appropriate callback. */
+ ret = nb_callback_configuration(event, change);
+ switch (event) {
+ case NB_EV_PREPARE:
+ if (ret != NB_OK) {
+ pthread_rwlock_unlock(
+ &running_config->lock);
+ 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;
+ }
}
}
+ pthread_rwlock_unlock(&running_config->lock);
return NB_OK;
}
return "ConfD";
case NB_CLIENT_SYSREPO:
return "Sysrepo";
+ case NB_CLIENT_GRPC:
+ return "gRPC";
default:
return "unknown";
}
running_config_entries = hash_create(running_config_entry_key_make,
running_config_entry_cmp,
"Running Configuration Entries");
+ pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL);
/* Initialize the northbound CLI. */
nb_cli_init(tm);
hash_clean(running_config_entries, running_config_entry_free);
hash_free(running_config_entries);
nb_config_free(running_config);
+ pthread_mutex_destroy(&running_config_mgmt_lock.mtx);
}
/* Northbound clients. */
enum nb_client {
- NB_CLIENT_CLI = 0,
+ NB_CLIENT_NONE = 0,
+ NB_CLIENT_CLI,
NB_CLIENT_CONFD,
NB_CLIENT_SYSREPO,
+ NB_CLIENT_GRPC,
};
/* Northbound configuration. */
struct nb_config {
+ /* Configuration data. */
struct lyd_node *dnode;
+
+ /* Configuration version. */
uint32_t version;
+
+ /*
+ * Lock protecting this structure. The use of this lock is always
+ * necessary when reading or modifying the global running configuration.
+ * For candidate configurations, use of this lock is optional depending
+ * on the threading scheme of the northbound plugin.
+ */
+ pthread_rwlock_t lock;
};
/* Northbound configuration callback. */
* client
* Northbound client performing the commit.
*
+ * user
+ * Northbound user performing the commit (can be NULL).
+ *
* comment
* Optional comment describing the commit.
*
* - NB_ERR for other errors.
*/
extern int nb_candidate_commit_prepare(struct nb_config *candidate,
- enum nb_client client,
+ enum nb_client client, const void *user,
const char *comment,
struct nb_transaction **transaction);
* client
* Northbound client performing the commit.
*
+ * user
+ * Northbound user performing the commit (can be NULL).
+ *
* save_transaction
* Specify whether the transaction should be recorded in the transactions log
* or not.
* - 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);
+ enum nb_client client, const void *user,
+ bool save_transaction, const char *comment,
+ uint32_t *transaction_id);
+
+/*
+ * Lock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already locked.
+ */
+extern int nb_running_lock(enum nb_client client, const void *user);
+
+/*
+ * Unlock the running configuration.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 on success, -1 when the running configuration is already unlocked or
+ * locked by another client/user.
+ */
+extern int nb_running_unlock(enum nb_client client, const void *user);
+
+/*
+ * Check if the running configuration is locked or not for the given
+ * client/user.
+ *
+ * client
+ * Northbound client.
+ *
+ * user
+ * Northbound user (can be NULL).
+ *
+ * Returns:
+ * 0 if the running configuration is unlocked or if the client/user owns the
+ * lock, -1 otherwise.
+ */
+extern int nb_running_lock_check(enum nb_client client, const void *user);
/*
- * Iterate over operetional data.
+ * Iterate over operational data.
*
* xpath
* Data path of the YANG data we want to iterate over.
/* 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);
+ vty, false, NULL, NULL);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
vty_out(vty, "%% Configuration failed: %s.\n\n",
nb_err_name(ret));
"Please check the logs for more details.\n");
/* Regenerate candidate for consistency. */
- nb_config_replace(vty->candidate_config, running_config,
- true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config,
+ running_config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
return CMD_WARNING_CONFIG_FAILED;
}
}
/* Perform the rollback. */
ret = nb_candidate_commit(
- vty->confirmed_commit_rollback, NB_CLIENT_CLI, true,
+ vty->confirmed_commit_rollback, NB_CLIENT_CLI, vty, true,
"Rollback to previous configuration - confirmed commit has timed out",
&transaction_id);
if (ret == NB_OK)
return CMD_SUCCESS;
}
- if (vty_exclusive_lock != NULL && vty_exclusive_lock != vty) {
- vty_out(vty, "%% Configuration is locked by another VTY.\n\n");
- return CMD_WARNING;
- }
-
/* "force" parameter. */
if (!force && nb_candidate_needs_update(vty->candidate_config)) {
vty_out(vty,
/* "confirm" parameter. */
if (confirmed_timeout) {
- vty->confirmed_commit_rollback = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ vty->confirmed_commit_rollback =
+ nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
vty->t_confirmed_commit_timeout = NULL;
thread_add_timer(master, nb_cli_confirmed_commit_timeout, vty,
&vty->t_confirmed_commit_timeout);
}
- ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, true,
- comment, &transaction_id);
+ ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI, vty,
+ 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);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config_base,
+ running_config, true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
vty_out(vty,
"%% Configuration committed successfully (Transaction ID #%u).\n\n",
transaction_id);
return CMD_WARNING;
}
- nb_config_replace(vty->candidate_config_base, running_config, true);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_config_replace(vty->candidate_config_base, running_config,
+ true);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
vty_out(vty, "%% Candidate configuration updated successfully.\n\n");
}
}
- nb_cli_show_config(vty, running_config, format, translator,
- !!with_defaults);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ nb_cli_show_config(vty, running_config, format, translator,
+ !!with_defaults);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return CMD_SUCCESS;
}
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;
+ /*
+ * For simplicity, lock the running configuration regardless if it's
+ * going to be used or not.
+ */
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ 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;
}
- 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;
+ 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;
}
- 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 (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;
+ 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);
+ 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);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
return ret;
}
snprintf(comment, sizeof(comment), "Rollback to transaction %u",
transaction_id);
- ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, true, comment,
+ ret = nb_candidate_commit(candidate, NB_CLIENT_CLI, vty, true, comment,
NULL);
nb_config_free(candidate);
switch (ret) {
struct cdb_iter_args iter_args;
int ret;
- candidate = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
/* Iterate over all configuration changes. */
iter_args.candidate = candidate;
*/
transaction = NULL;
ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_CONFD, NULL,
- &transaction);
+ NULL, &transaction);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
enum confd_errcode errcode;
const char *errmsg;
--- /dev/null
+//
+// Copyright (C) 2019 NetDEF, Inc.
+// Renato Westphal
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; see the file COPYING; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#include <zebra.h>
+
+#include "log.h"
+#include "libfrr.h"
+#include "version.h"
+#include "command.h"
+#include "lib_errors.h"
+#include "northbound.h"
+#include "northbound_db.h"
+
+#include <iostream>
+#include <sstream>
+#include <memory>
+#include <string>
+
+#include <grpcpp/grpcpp.h>
+#include "grpc/frr-northbound.grpc.pb.h"
+
+#define GRPC_DEFAULT_PORT 50051
+
+/*
+ * NOTE: we can't use the FRR debugging infrastructure here since it uses
+ * atomics and C++ has a different atomics API. Enable gRPC debugging
+ * unconditionally until we figure out a way to solve this problem.
+ */
+static bool nb_dbg_client_grpc = 1;
+
+static pthread_t grpc_pthread;
+
+class NorthboundImpl final : public frr::Northbound::Service
+{
+ public:
+ NorthboundImpl(void)
+ {
+ _nextCandidateId = 0;
+ }
+
+ ~NorthboundImpl(void)
+ {
+ // Delete candidates.
+ for (auto it = _candidates.begin(); it != _candidates.end();
+ it++)
+ delete_candidate(&it->second);
+ }
+
+ grpc::Status
+ GetCapabilities(grpc::ServerContext *context,
+ frr::GetCapabilitiesRequest const *request,
+ frr::GetCapabilitiesResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC GetCapabilities()");
+
+ // Response: string frr_version = 1;
+ response->set_frr_version(FRR_VERSION);
+
+ // Response: bool rollback_support = 2;
+#ifdef HAVE_CONFIG_ROLLBACKS
+ response->set_rollback_support(true);
+#else
+ response->set_rollback_support(false);
+#endif
+
+ // Response: repeated ModuleData supported_modules = 3;
+ struct yang_module *module;
+ RB_FOREACH (module, yang_modules, &yang_modules) {
+ auto m = response->add_supported_modules();
+
+ m->set_name(module->name);
+ if (module->info->rev_size)
+ m->set_revision(module->info->rev[0].date);
+ m->set_organization(module->info->org);
+ }
+
+ // Response: repeated Encoding supported_encodings = 4;
+ response->add_supported_encodings(frr::JSON);
+ response->add_supported_encodings(frr::XML);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Get(grpc::ServerContext *context,
+ frr::GetRequest const *request,
+ grpc::ServerWriter<frr::GetResponse> *writer) override
+ {
+ // Request: DataType type = 1;
+ int type = request->type();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = request->encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = request->with_defaults();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC Get(type: %u, encoding: %u, with_defaults: %u)",
+ type, encoding, with_defaults);
+
+ // Request: repeated string path = 4;
+ auto paths = request->path();
+ for (const std::string &path : paths) {
+ frr::GetResponse response;
+ grpc::Status status;
+
+ // Response: int64 timestamp = 1;
+ response.set_timestamp(time(NULL));
+
+ // Response: DataTree data = 2;
+ auto *data = response.mutable_data();
+ data->set_encoding(request->encoding());
+ status = get_path(data, path, type,
+ encoding2lyd_format(encoding),
+ with_defaults);
+
+ // Something went wrong...
+ if (!status.ok())
+ return status;
+
+ writer->Write(response);
+ }
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Get() end");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ CreateCandidate(grpc::ServerContext *context,
+ frr::CreateCandidateRequest const *request,
+ frr::CreateCandidateResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC CreateCandidate()");
+
+ struct candidate *candidate = create_candidate();
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::RESOURCE_EXHAUSTED,
+ "Can't create candidate configuration");
+
+ // Response: uint32 candidate_id = 1;
+ response->set_candidate_id(candidate->id);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ DeleteCandidate(grpc::ServerContext *context,
+ frr::DeleteCandidateRequest const *request,
+ frr::DeleteCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC DeleteCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ delete_candidate(candidate);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ UpdateCandidate(grpc::ServerContext *context,
+ frr::UpdateCandidateRequest const *request,
+ frr::UpdateCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC UpdateCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "candidate is in the middle of a transaction");
+
+ if (nb_candidate_update(candidate->config) != NB_OK)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "failed to update candidate configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ EditCandidate(grpc::ServerContext *context,
+ frr::EditCandidateRequest const *request,
+ frr::EditCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC EditCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ // Create a copy of the candidate. For consistency, we need to
+ // ensure that either all changes are accepted or none are (in
+ // the event of an error).
+ struct nb_config *candidate_tmp =
+ nb_config_dup(candidate->config);
+
+ auto pvs = request->update();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_edit(candidate_tmp->dnode, pv.path(),
+ pv.value())
+ != 0) {
+ nb_config_free(candidate_tmp);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to update \"" + pv.path()
+ + "\"");
+ }
+ }
+
+ pvs = request->delete_();
+ for (const frr::PathValue &pv : pvs) {
+ if (yang_dnode_delete(candidate_tmp->dnode, pv.path())
+ != 0) {
+ nb_config_free(candidate_tmp);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to remove \"" + pv.path()
+ + "\"");
+ }
+ }
+
+ // No errors, accept all changes.
+ nb_config_replace(candidate->config, candidate_tmp, false);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ LoadToCandidate(grpc::ServerContext *context,
+ frr::LoadToCandidateRequest const *request,
+ frr::LoadToCandidateResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+ // Request: LoadType type = 2;
+ int load_type = request->type();
+ // Request: DataTree config = 3;
+ auto config = request->config();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC LoadToCandidate(candidate_id: %u)",
+ candidate_id);
+
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ struct lyd_node *dnode = dnode_from_data_tree(&config, true);
+ if (!dnode)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to parse the configuration");
+
+ struct nb_config *loaded_config = nb_config_new(dnode);
+
+ if (load_type == frr::LoadToCandidateRequest::REPLACE)
+ nb_config_replace(candidate->config, loaded_config,
+ false);
+ else if (nb_config_merge(candidate->config, loaded_config,
+ false)
+ != NB_OK)
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to merge the loaded configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Commit(grpc::ServerContext *context,
+ frr::CommitRequest const *request,
+ frr::CommitResponse *response) override
+ {
+ // Request: uint32 candidate_id = 1;
+ uint32_t candidate_id = request->candidate_id();
+ // Request: Phase phase = 2;
+ int phase = request->phase();
+ // Request: string comment = 3;
+ const std::string comment = request->comment();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Commit(candidate_id: %u)",
+ candidate_id);
+
+ // Find candidate configuration.
+ struct candidate *candidate = get_candidate(candidate_id);
+ if (!candidate)
+ return grpc::Status(
+ grpc::StatusCode::NOT_FOUND,
+ "candidate configuration not found");
+
+ int ret = NB_OK;
+ uint32_t transaction_id = 0;
+
+ // Check for misuse of the two-phase commit protocol.
+ switch (phase) {
+ case frr::CommitRequest::PREPARE:
+ case frr::CommitRequest::ALL:
+ if (candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "pending transaction in progress");
+ break;
+ case frr::CommitRequest::ABORT:
+ case frr::CommitRequest::APPLY:
+ if (!candidate->transaction)
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "no transaction in progress");
+ break;
+ default:
+ break;
+ }
+
+ // Execute the user request.
+ switch (phase) {
+ case frr::CommitRequest::VALIDATE:
+ ret = nb_candidate_validate(candidate->config);
+ break;
+ case frr::CommitRequest::PREPARE:
+ ret = nb_candidate_commit_prepare(
+ candidate->config, NB_CLIENT_GRPC, NULL,
+ comment.c_str(), &candidate->transaction);
+ break;
+ case frr::CommitRequest::ABORT:
+ nb_candidate_commit_abort(candidate->transaction);
+ break;
+ case frr::CommitRequest::APPLY:
+ nb_candidate_commit_apply(candidate->transaction, true,
+ &transaction_id);
+ break;
+ case frr::CommitRequest::ALL:
+ ret = nb_candidate_commit(
+ candidate->config, NB_CLIENT_GRPC, NULL, true,
+ comment.c_str(), &transaction_id);
+ break;
+ }
+
+ // Map northbound error codes to gRPC error codes.
+ switch (ret) {
+ case NB_ERR_NO_CHANGES:
+ return grpc::Status(
+ grpc::StatusCode::ABORTED,
+ "No configuration changes detected");
+ case NB_ERR_LOCKED:
+ return grpc::Status(
+ grpc::StatusCode::UNAVAILABLE,
+ "There's already a transaction in progress");
+ case NB_ERR_VALIDATION:
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Validation error");
+ case NB_ERR_RESOURCE:
+ return grpc::Status(
+ grpc::StatusCode::RESOURCE_EXHAUSTED,
+ "Failed do allocate resources");
+ case NB_ERR:
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Internal error");
+ default:
+ break;
+ }
+
+ // Response: uint32 transaction_id = 1;
+ if (transaction_id)
+ response->set_transaction_id(transaction_id);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ ListTransactions(grpc::ServerContext *context,
+ frr::ListTransactionsRequest const *request,
+ grpc::ServerWriter<frr::ListTransactionsResponse>
+ *writer) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC ListTransactions()");
+
+ nb_db_transactions_iterate(list_transactions_cb, writer);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status
+ GetTransaction(grpc::ServerContext *context,
+ frr::GetTransactionRequest const *request,
+ frr::GetTransactionResponse *response) override
+ {
+ struct nb_config *nb_config;
+
+ // Request: uint32 transaction_id = 1;
+ uint32_t transaction_id = request->transaction_id();
+ // Request: Encoding encoding = 2;
+ frr::Encoding encoding = request->encoding();
+ // Request: bool with_defaults = 3;
+ bool with_defaults = request->with_defaults();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug(
+ "received RPC GetTransaction(transaction_id: %u, encoding: %u)",
+ transaction_id, encoding);
+
+ // Load configuration from the transactions database.
+ nb_config = nb_db_transaction_load(transaction_id);
+ if (!nb_config)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Transaction not found");
+
+ // Response: DataTree config = 1;
+ auto config = response->mutable_config();
+ config->set_encoding(encoding);
+
+ // Dump data using the requested format.
+ if (data_tree_from_dnode(config, nb_config->dnode,
+ encoding2lyd_format(encoding),
+ with_defaults)
+ != 0) {
+ nb_config_free(nb_config);
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+ }
+
+ nb_config_free(nb_config);
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status LockConfig(grpc::ServerContext *context,
+ frr::LockConfigRequest const *request,
+ frr::LockConfigResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC LockConfig()");
+
+ if (nb_running_lock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "running configuration is locked already");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status UnlockConfig(grpc::ServerContext *context,
+ frr::UnlockConfigRequest const *request,
+ frr::UnlockConfigResponse *response) override
+ {
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC UnlockConfig()");
+
+ if (nb_running_unlock(NB_CLIENT_GRPC, NULL))
+ return grpc::Status(
+ grpc::StatusCode::FAILED_PRECONDITION,
+ "failed to unlock the running configuration");
+
+ return grpc::Status::OK;
+ }
+
+ grpc::Status Execute(grpc::ServerContext *context,
+ frr::ExecuteRequest const *request,
+ frr::ExecuteResponse *response) override
+ {
+ struct nb_node *nb_node;
+ struct list *input_list;
+ struct list *output_list;
+ struct listnode *node;
+ struct yang_data *data;
+ const char *xpath;
+
+ // Request: string path = 1;
+ xpath = request->path().c_str();
+
+ if (nb_dbg_client_grpc)
+ zlog_debug("received RPC Execute(path: \"%s\")", xpath);
+
+ if (request->path().empty())
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path is empty");
+
+ nb_node = nb_node_find(xpath);
+ if (!nb_node)
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown data path");
+
+ input_list = yang_data_list_new();
+ output_list = yang_data_list_new();
+
+ // Read input parameters.
+ auto input = request->input();
+ for (const frr::PathValue &pv : input) {
+ // Request: repeated PathValue input = 2;
+ data = yang_data_new(pv.path().c_str(),
+ pv.value().c_str());
+ listnode_add(input_list, data);
+ }
+
+ // Execute callback registered for this XPath.
+ if (nb_node->cbs.rpc(xpath, input_list, output_list) != NB_OK) {
+ flog_warn(EC_LIB_NB_CB_RPC,
+ "%s: rpc callback failed: %s", __func__,
+ xpath);
+ list_delete(&input_list);
+ list_delete(&output_list);
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "RPC failed");
+ }
+
+ // Process output parameters.
+ for (ALL_LIST_ELEMENTS_RO(output_list, node, data)) {
+ // Response: repeated PathValue output = 1;
+ frr::PathValue *pv = response->add_output();
+ pv->set_path(data->xpath);
+ pv->set_value(data->value);
+ }
+
+ // Release memory.
+ list_delete(&input_list);
+ list_delete(&output_list);
+
+ return grpc::Status::OK;
+ }
+
+ private:
+ struct candidate {
+ uint32_t id;
+ struct nb_config *config;
+ struct nb_transaction *transaction;
+ };
+ std::map<uint32_t, struct candidate> _candidates;
+ uint32_t _nextCandidateId;
+
+ static int yang_dnode_edit(struct lyd_node *dnode,
+ const std::string &path,
+ const std::string &value)
+ {
+ ly_errno = LY_SUCCESS;
+ dnode = lyd_new_path(dnode, ly_native_ctx, path.c_str(),
+ (void *)value.c_str(),
+ (LYD_ANYDATA_VALUETYPE)0,
+ LYD_PATH_OPT_UPDATE);
+ if (!dnode && ly_errno != LY_SUCCESS) {
+ flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed",
+ __func__);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ static int yang_dnode_delete(struct lyd_node *dnode,
+ const std::string &path)
+ {
+ dnode = yang_dnode_get(dnode, path.c_str());
+ if (!dnode)
+ return -1;
+
+ lyd_free(dnode);
+
+ return 0;
+ }
+
+ static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding)
+ {
+ switch (encoding) {
+ case frr::JSON:
+ return LYD_JSON;
+ case frr::XML:
+ return LYD_XML;
+ }
+ }
+
+ static int get_oper_data_cb(const struct lys_node *snode,
+ struct yang_translator *translator,
+ struct yang_data *data, void *arg)
+ {
+ struct lyd_node *dnode = static_cast<struct lyd_node *>(arg);
+ int ret = yang_dnode_edit(dnode, data->xpath, data->value);
+ yang_data_free(data);
+
+ return (ret == 0) ? NB_OK : NB_ERR;
+ }
+
+ static void list_transactions_cb(void *arg, int transaction_id,
+ const char *client_name,
+ const char *date, const char *comment)
+ {
+ grpc::ServerWriter<frr::ListTransactionsResponse> *writer =
+ static_cast<grpc::ServerWriter<
+ frr::ListTransactionsResponse> *>(arg);
+ frr::ListTransactionsResponse response;
+
+ // Response: uint32 id = 1;
+ response.set_id(transaction_id);
+
+ // Response: string client = 2;
+ response.set_client(client_name);
+
+ // Response: string date = 3;
+ response.set_date(date);
+
+ // Response: string comment = 4;
+ response.set_comment(comment);
+
+ writer->Write(response);
+ }
+
+ static int data_tree_from_dnode(frr::DataTree *dt,
+ const struct lyd_node *dnode,
+ LYD_FORMAT lyd_format,
+ bool with_defaults)
+ {
+ char *strp;
+ int options = 0;
+
+ SET_FLAG(options, LYP_FORMAT | LYP_WITHSIBLINGS);
+ if (with_defaults)
+ SET_FLAG(options, LYP_WD_ALL);
+ else
+ SET_FLAG(options, LYP_WD_TRIM);
+
+ if (lyd_print_mem(&strp, dnode, lyd_format, options) == 0) {
+ if (strp) {
+ dt->set_data(strp);
+ free(strp);
+ }
+ return 0;
+ }
+
+ return -1;
+ }
+
+ static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt,
+ bool config_only)
+ {
+ struct lyd_node *dnode;
+ int options;
+
+ if (config_only)
+ options = LYD_OPT_CONFIG;
+ else
+ options = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
+
+ dnode = lyd_parse_mem(ly_native_ctx, dt->data().c_str(),
+ encoding2lyd_format(dt->encoding()),
+ options);
+
+ return dnode;
+ }
+
+ static struct lyd_node *get_dnode_config(const std::string &path)
+ {
+ struct lyd_node *dnode;
+
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ dnode = yang_dnode_get(running_config->dnode,
+ path.empty() ? NULL
+ : path.c_str());
+ if (dnode)
+ dnode = yang_dnode_dup(dnode);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+
+ return dnode;
+ }
+
+ static struct lyd_node *get_dnode_state(const std::string &path)
+ {
+ struct lyd_node *dnode;
+
+ dnode = yang_dnode_new(ly_native_ctx, false);
+ if (nb_oper_data_iterate(path.c_str(), NULL, 0,
+ get_oper_data_cb, dnode)
+ != NB_OK) {
+ yang_dnode_free(dnode);
+ return NULL;
+ }
+
+ return dnode;
+ }
+
+ static grpc::Status get_path(frr::DataTree *dt, const std::string &path,
+ int type, LYD_FORMAT lyd_format,
+ bool with_defaults)
+ {
+ struct lyd_node *dnode_config = NULL;
+ struct lyd_node *dnode_state = NULL;
+ struct lyd_node *dnode_final;
+
+ // Configuration data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_CONFIG) {
+ dnode_config = get_dnode_config(path);
+ if (!dnode_config)
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Data path not found");
+ }
+
+ // Operational data.
+ if (type == frr::GetRequest_DataType_ALL
+ || type == frr::GetRequest_DataType_STATE) {
+ dnode_state = get_dnode_state(path);
+ if (!dnode_state) {
+ if (dnode_config)
+ yang_dnode_free(dnode_config);
+ return grpc::Status(
+ grpc::StatusCode::INVALID_ARGUMENT,
+ "Failed to fetch operational data");
+ }
+ }
+
+ switch (type) {
+ case frr::GetRequest_DataType_ALL:
+ //
+ // Combine configuration and state data into a single
+ // dnode.
+ //
+ if (lyd_merge(dnode_state, dnode_config,
+ LYD_OPT_EXPLICIT)
+ != 0) {
+ yang_dnode_free(dnode_state);
+ yang_dnode_free(dnode_config);
+ return grpc::Status(
+ grpc::StatusCode::INTERNAL,
+ "Failed to merge configuration and state data");
+ }
+
+ dnode_final = dnode_state;
+ break;
+ case frr::GetRequest_DataType_CONFIG:
+ dnode_final = dnode_config;
+ break;
+ case frr::GetRequest_DataType_STATE:
+ dnode_final = dnode_state;
+ break;
+ }
+
+ // Validate data to create implicit default nodes if necessary.
+ int validate_opts = 0;
+ if (type == frr::GetRequest_DataType_CONFIG)
+ validate_opts = LYD_OPT_CONFIG;
+ else
+ validate_opts = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB;
+ lyd_validate(&dnode_final, validate_opts, ly_native_ctx);
+
+ // Dump data using the requested format.
+ int ret = data_tree_from_dnode(dt, dnode_final, lyd_format,
+ with_defaults);
+ yang_dnode_free(dnode_final);
+ if (ret != 0)
+ return grpc::Status(grpc::StatusCode::INTERNAL,
+ "Failed to dump data");
+
+ return grpc::Status::OK;
+ }
+
+ struct candidate *create_candidate(void)
+ {
+ uint32_t candidate_id = ++_nextCandidateId;
+
+ // Check for overflow.
+ // TODO: implement an algorithm for unique reusable IDs.
+ if (candidate_id == 0)
+ return NULL;
+
+ struct candidate *candidate = &_candidates[candidate_id];
+ candidate->id = candidate_id;
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate->config = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
+ candidate->transaction = NULL;
+
+ return candidate;
+ }
+
+ void delete_candidate(struct candidate *candidate)
+ {
+ _candidates.erase(candidate->id);
+ nb_config_free(candidate->config);
+ if (candidate->transaction)
+ nb_candidate_commit_abort(candidate->transaction);
+ }
+
+ struct candidate *get_candidate(uint32_t candidate_id)
+ {
+ struct candidate *candidate;
+
+ if (_candidates.count(candidate_id) == 0)
+ return NULL;
+
+ return &_candidates[candidate_id];
+ }
+};
+
+static void *grpc_pthread_start(void *arg)
+{
+ unsigned long *port = static_cast<unsigned long *>(arg);
+ NorthboundImpl service;
+ std::stringstream server_address;
+
+ server_address << "0.0.0.0:" << *port;
+
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(server_address.str(),
+ grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+
+ std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+
+ zlog_notice("gRPC server listening on %s",
+ server_address.str().c_str());
+
+ server->Wait();
+
+ return NULL;
+}
+
+static int frr_grpc_init(unsigned long *port)
+{
+ /* Create a pthread for gRPC since it runs its own event loop. */
+ if (pthread_create(&grpc_pthread, NULL, grpc_pthread_start, port)) {
+ flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
+ __func__, safe_strerror(errno));
+ return -1;
+ }
+ pthread_detach(grpc_pthread);
+
+ return 0;
+}
+
+static int frr_grpc_finish(void)
+{
+ // TODO: cancel the gRPC pthreads gracefully.
+
+ return 0;
+}
+
+static int frr_grpc_module_late_init(struct thread_master *tm)
+{
+ static unsigned long port = GRPC_DEFAULT_PORT;
+ const char *args = THIS_MODULE->load_args;
+
+ // Parse port number.
+ if (args) {
+ try {
+ port = std::stoul(args);
+ if (port < 1024)
+ throw std::invalid_argument(
+ "can't use privileged port");
+ if (port > UINT16_MAX)
+ throw std::invalid_argument(
+ "port number is too big");
+ } catch (std::exception &e) {
+ flog_err(EC_LIB_GRPC_INIT,
+ "%s: failed to parse port number: %s",
+ __func__, e.what());
+ goto error;
+ }
+ }
+
+ if (frr_grpc_init(&port) < 0)
+ goto error;
+
+ hook_register(frr_fini, frr_grpc_finish);
+
+ return 0;
+
+error:
+ flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module");
+ return -1;
+}
+
+static int frr_grpc_module_init(void)
+{
+ hook_register(frr_late_init, frr_grpc_module_late_init);
+
+ return 0;
+}
+
+FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION,
+ .description = "FRR gRPC northbound module",
+ .init = frr_grpc_module_init, )
return ret;
}
- candidate = nb_config_dup(running_config);
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ candidate = nb_config_dup(running_config);
+ }
+ pthread_rwlock_unlock(&running_config->lock);
while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val,
&sr_new_val))
* single event (SR_EV_ENABLED). This means we need to perform
* the full two-phase commit protocol in one go here.
*/
- ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, true,
- NULL, NULL);
+ ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, NULL,
+ true, NULL, NULL);
} else {
/*
* Validate the configuration changes and allocate all resources
* required to apply them.
*/
ret = nb_candidate_commit_prepare(candidate, NB_CLIENT_SYSREPO,
- NULL, &transaction);
+ NULL, NULL, &transaction);
}
/* Map northbound return code to sysrepo return code. */
lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS)
lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c
+#
+# gRPC northbound plugin
+#
+if GRPC
+module_LTLIBRARIES += lib/grpc.la
+endif
+
+lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS)
+lib_grpc_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
+lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS)
+lib_grpc_la_SOURCES = lib/northbound_grpc.cpp
+
#
# CLI utilities
#
CLIPPY_DEPS = $(HOSTTOOLS)lib/clippy $(top_srcdir)/python/clidef.py
-SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h
+SUFFIXES = _clippy.c .proto .pb-c.c .pb-c.h .pb.h .pb.cc .grpc.pb.cc
.c_clippy.c:
@{ test -x $(top_builddir)/$(HOSTTOOLS)lib/clippy || \
$(MAKE) -C $(top_builddir)/$(HOSTTOOLS) lib/clippy; }
/* Current directory. */
char *vty_cwd = NULL;
-/* Exclusive configuration lock. */
-struct vty *vty_exclusive_lock;
-
/* Login password check. */
static int no_password_check = 0;
if (config == NULL && vty->candidate_config
&& frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) {
ret = nb_candidate_commit(vty->candidate_config, NB_CLIENT_CLI,
- true, "Read configuration file",
+ vty, true, "Read configuration file",
NULL);
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
zlog_err("%s: failed to read configuration file.",
int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
{
- if (exclusive && !vty_config_exclusive_lock(vty)) {
- vty_out(vty, "VTY configuration is locked by other VTY\n");
+ if (exclusive && nb_running_lock(NB_CLIENT_CLI, vty)) {
+ vty_out(vty, "%% Configuration is locked by other client\n");
return CMD_WARNING;
}
vty->private_config = private_config;
vty->xpath_index = 0;
- if (private_config) {
- 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");
- } else {
- vty->candidate_config = vty_shared_candidate_config;
- if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ pthread_rwlock_rdlock(&running_config->lock);
+ {
+ if (private_config) {
+ 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");
+ } else {
+ vty->candidate_config = vty_shared_candidate_config;
+ if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
+ vty->candidate_config_base =
+ nb_config_dup(running_config);
+ }
}
+ pthread_rwlock_unlock(&running_config->lock);
return CMD_SUCCESS;
}
nb_cli_confirmed_commit_clean(vty);
}
- vty_config_exclusive_unlock(vty);
+ (void)nb_running_unlock(NB_CLIENT_CLI, vty);
if (vty->candidate_config) {
if (vty->private_config)
vty->config = false;
}
-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;
#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP)
#endif
-/* Exported variables */
-extern struct vty *vty_exclusive_lock;
-
/* Prototypes. */
extern void vty_init(struct thread_master *);
extern void vty_init_vtysh(void);
extern int vty_config_enter(struct vty *vty, bool private_config,
bool exclusive);
extern void vty_config_exit(struct vty *);
-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 *);