]> git.proxmox.com Git - mirror_frr.git/commitdiff
Merge pull request #4082 from opensourcerouting/grpc-nb-plugin
authorQuentin Young <qlyoung@users.noreply.github.com>
Tue, 7 May 2019 20:53:17 +0000 (16:53 -0400)
committerGitHub <noreply@github.com>
Tue, 7 May 2019 20:53:17 +0000 (16:53 -0400)
gRPC northbound plugin

21 files changed:
.gitignore
Makefile.am
configure.ac
grpc/Makefile [new file with mode: 0644]
grpc/frr-northbound.proto [new file with mode: 0644]
grpc/subdir.am [new file with mode: 0644]
isisd/isis_cli.c
lib/command.c
lib/if.c
lib/lib_errors.c
lib/lib_errors.h
lib/libfrr.c
lib/northbound.c
lib/northbound.h
lib/northbound_cli.c
lib/northbound_confd.c
lib/northbound_grpc.cpp [new file with mode: 0644]
lib/northbound_sysrepo.c
lib/subdir.am
lib/vty.c
lib/vty.h

index 5003c97572b7b0d51aa74f5d7bd52cb965e2e8b9..7a1378d588259f364446f64ab3a290ed6946803d 100644 (file)
@@ -49,6 +49,7 @@
 *.pb.h
 *.pb-c.h
 *.pb-c.c
+*.pb.cc
 *_clippy.c
 
 ### dist
index 5b9f26f7baa9bcb2c24d2368c08865226beede87..546aa85fe79de8c910aa225610db609a33389961 100644 (file)
@@ -121,6 +121,7 @@ include zebra/subdir.am
 include watchfrr/subdir.am
 include qpb/subdir.am
 include fpm/subdir.am
+include grpc/subdir.am
 include tools/subdir.am
 include solaris/subdir.am
 
@@ -196,6 +197,7 @@ EXTRA_DIST += \
        doc/user/Makefile \
        eigrpd/Makefile \
        fpm/Makefile \
+       grpc/Makefile \
        isisd/Makefile \
        ldpd/Makefile \
        lib/Makefile \
index 1a1c60e5fabfd905cca6e6c9d99f30d5d0cd935a..fe60ba2a74e38b4892e99dc6493480884ff02b30 100755 (executable)
@@ -126,12 +126,15 @@ dnl Check CC and friends
 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
 
@@ -447,6 +450,8 @@ AC_ARG_ENABLE([confd],
   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],
@@ -1734,6 +1739,25 @@ if test "$enable_sysrepo" = "yes"; then
 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 ---------------
diff --git a/grpc/Makefile b/grpc/Makefile
new file mode 100644 (file)
index 0000000..8748286
--- /dev/null
@@ -0,0 +1,10 @@
+all: ALWAYS
+       @$(MAKE) -s -C .. grpc/libfrrgrpc_pb.la
+%: ALWAYS
+       @$(MAKE) -s -C .. grpc/$@
+
+Makefile:
+       #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/grpc/frr-northbound.proto b/grpc/frr-northbound.proto
new file mode 100644 (file)
index 0000000..d070d71
--- /dev/null
@@ -0,0 +1,412 @@
+//
+// 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;
+}
diff --git a/grpc/subdir.am b/grpc/subdir.am
new file mode 100644 (file)
index 0000000..3fb163f
--- /dev/null
@@ -0,0 +1,30 @@
+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)/$^
index 00948528259d56d158b297677134488c9bb7df98..0334b98a122fb71c54a0704b8d97cf068a266457 100644 (file)
@@ -188,10 +188,14 @@ DEFPY(ip_router_isis, ip_router_isis_cmd, "ip router isis WORD$tag",
        }
 
        /* 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);
 }
@@ -258,10 +262,14 @@ DEFPY(ip6_router_isis, ip6_router_isis_cmd, "ipv6 router isis WORD$tag",
        }
 
        /* 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);
 }
@@ -368,20 +376,26 @@ DEFPY(no_is_type, no_is_type_cmd,
       "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);
@@ -1769,50 +1783,43 @@ DEFPY(no_isis_circuit_type, no_isis_circuit_type_cmd,
       "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);
 }
index 559457c11998ec0bbd968b734b5a6fbf826aeabd..b3ef028004f687dcf686c7669bc205f10a50548c 100644 (file)
@@ -1705,12 +1705,16 @@ static int vty_write_config(struct vty *vty)
        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");
index 86b850c059f785ab1926d957ab09c29156a7c34a..38f3f45ed1e020f079f4d85b4d6ae1c4a917f679 100644 (file)
--- a/lib/if.c
+++ b/lib/if.c
@@ -187,18 +187,21 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
        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)
 {
index 5f6c25b770e033c2ec55e08f453f01e599e8bf83..b6c764d8739b80203b10a250e7eadf94660b22a6 100644 (file)
@@ -332,6 +332,12 @@ static struct log_ref ferr_lib_err[] = {
                .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",
index fc405c20983a3a2303def2bc57152bb4c486befd..39b39fb065b9b618f9cfc97a06d6704e400544d8 100644 (file)
@@ -80,6 +80,7 @@ enum lib_log_refs {
        EC_LIB_SYSREPO_INIT,
        EC_LIB_SYSREPO_DATA_CONVERT,
        EC_LIB_LIBSYSREPO,
+       EC_LIB_GRPC_INIT,
        EC_LIB_ID_CONSISTENCY,
        EC_LIB_ID_EXHAUST,
 };
index 0d4c8d6c0f1b4200895491cc3ef28c7fb5aa2847..5970e70a6bd493230f9b7cb5091cb963277178c5 100644 (file)
@@ -830,7 +830,12 @@ static int frr_config_read_in(struct thread *t)
        /*
         * 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;
 }
index 5e031ac2cea8ba4fdc9cbcb88f3ea0fbf5ef1572..e8b3e46c19deb2c624f35ee4c9ba654204781a59 100644 (file)
@@ -40,6 +40,21 @@ struct nb_config *running_config;
 /* 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.
@@ -51,6 +66,7 @@ static int nb_callback_configuration(const enum nb_event event,
 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,
@@ -248,6 +264,7 @@ struct nb_config *nb_config_new(struct lyd_node *dnode)
        else
                config->dnode = yang_dnode_new(ly_native_ctx, true);
        config->version = 0;
+       pthread_rwlock_init(&config->lock, NULL);
 
        return config;
 }
@@ -256,6 +273,7 @@ void nb_config_free(struct nb_config *config)
 {
        if (config->dnode)
                yang_dnode_free(config->dnode);
+       pthread_rwlock_destroy(&config->lock);
        XFREE(MTYPE_NB_CONFIG, config);
 }
 
@@ -266,6 +284,7 @@ struct nb_config *nb_config_dup(const struct 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;
 }
@@ -513,17 +532,28 @@ int nb_candidate_edit(struct nb_config *candidate,
 
 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;
 
@@ -575,15 +605,20 @@ int nb_candidate_validate(struct nb_config *candidate)
                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;
@@ -596,25 +631,36 @@ int nb_candidate_commit_prepare(struct nb_config *candidate,
        }
 
        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);
 }
@@ -633,7 +679,11 @@ void nb_candidate_commit_apply(struct nb_transaction *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
@@ -645,13 +695,13 @@ void nb_candidate_commit_apply(struct nb_transaction *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
@@ -666,6 +716,60 @@ int nb_candidate_commit(struct nb_config *candidate, enum nb_client client,
        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)
@@ -812,13 +916,20 @@ int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath,
        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,
@@ -852,40 +963,52 @@ static int nb_transaction_process(enum nb_event event,
 {
        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;
 }
@@ -1704,6 +1827,8 @@ const char *nb_client_name(enum nb_client client)
                return "ConfD";
        case NB_CLIENT_SYSREPO:
                return "Sysrepo";
+       case NB_CLIENT_GRPC:
+               return "gRPC";
        default:
                return "unknown";
        }
@@ -1761,6 +1886,7 @@ void nb_init(struct thread_master *tm,
        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);
@@ -1778,4 +1904,5 @@ void nb_terminate(void)
        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);
 }
index 14f27c1d41034b07ce1989014f5ad1d85d4248a9..8f6753506b9de2cf3c942511291e4e5416197097 100644 (file)
@@ -414,15 +414,28 @@ enum nb_error {
 
 /* 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. */
@@ -662,6 +675,9 @@ extern int nb_candidate_validate(struct nb_config *candidate);
  * client
  *    Northbound client performing the commit.
  *
+ * user
+ *    Northbound user performing the commit (can be NULL).
+ *
  * comment
  *    Optional comment describing the commit.
  *
@@ -682,7 +698,7 @@ extern int nb_candidate_validate(struct nb_config *candidate);
  *    - 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);
 
@@ -727,6 +743,9 @@ extern void nb_candidate_commit_apply(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.
@@ -748,11 +767,57 @@ extern void nb_candidate_commit_apply(struct nb_transaction *transaction,
  *    - 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.
index 48faa7595aa04709730050e7e42487c6a27193b7..ae1b0578a004ef473da89b2c97ea8c74a6a2a906 100644 (file)
@@ -185,7 +185,7 @@ int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
        /* 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));
@@ -193,8 +193,13 @@ int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...)
                                "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;
                }
        }
@@ -237,7 +242,7 @@ int nb_cli_confirmed_commit_rollback(struct vty *vty)
 
        /* 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)
@@ -291,11 +296,6 @@ static int nb_cli_commit(struct vty *vty, bool force,
                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,
@@ -307,7 +307,12 @@ static int nb_cli_commit(struct vty *vty, bool force,
 
        /* "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,
@@ -315,14 +320,19 @@ static int nb_cli_commit(struct vty *vty, bool force,
                                 &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);
@@ -687,7 +697,12 @@ DEFPY (config_update,
                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");
 
@@ -787,8 +802,12 @@ DEFPY (show_config_running,
                }
        }
 
-       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;
 }
@@ -902,57 +921,68 @@ DEFPY (show_config_compare,
        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;
 }
@@ -1509,7 +1539,7 @@ static int nb_cli_rollback_configuration(struct vty *vty,
        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) {
index 0df286511ecea668bad11fdb39a6fc590a75ea9a..e9669fc7e16c8d5cb6a313f022b0a5acb75c20e4 100644 (file)
@@ -289,7 +289,11 @@ static int frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen)
        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;
@@ -322,7 +326,7 @@ static int frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen)
         */
        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;
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
new file mode 100644 (file)
index 0000000..a55da23
--- /dev/null
@@ -0,0 +1,936 @@
+//
+// 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, )
index 33b6c247825e86f3622d7f983892de53d6fc0f9a..44a55137f82e23e93227c74369b1aaa7a5f52695 100644 (file)
@@ -256,7 +256,11 @@ static int frr_sr_config_change_cb_verify(sr_session_ctx_t *session,
                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))
@@ -282,15 +286,15 @@ static int frr_sr_config_change_cb_verify(sr_session_ctx_t *session,
                 * 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. */
index 7027f3f0daadd67674aaac0ab877508f018db590..0b7af18fcffaf54df27367f70e103841d6ae1d54 100644 (file)
@@ -309,6 +309,18 @@ lib_sysrepo_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
 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
 #
@@ -353,7 +365,7 @@ am__v_CLIPPY_1 =
 
 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; }
index dae8e825993ba84356c3ef5797bc2221400a2d00..0ee9b78b91f9afa77951178dbd734bd003396e08 100644 (file)
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -86,9 +86,6 @@ static vector Vvty_serv_thread;
 /* Current directory. */
 char *vty_cwd = NULL;
 
-/* Exclusive configuration lock. */
-struct vty *vty_exclusive_lock;
-
 /* Login password check. */
 static int no_password_check = 0;
 
@@ -2369,7 +2366,7 @@ static void vty_read_file(struct nb_config *config, FILE *confp)
        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.",
@@ -2601,8 +2598,8 @@ void vty_log_fixed(char *buf, size_t len)
 
 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;
        }
 
@@ -2611,17 +2608,22 @@ int vty_config_enter(struct vty *vty, bool private_config, bool exclusive)
        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;
 }
@@ -2636,7 +2638,7 @@ void vty_config_exit(struct vty *vty)
                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)
@@ -2651,21 +2653,6 @@ void vty_config_exit(struct vty *vty)
        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;
 
index 9c2653e1ac0ded45a43a604adbb9f5a63a5bcf69..98d75542fd3794796e84fdc5832704e5a51faf97 100644 (file)
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -289,9 +289,6 @@ struct vty_arg {
 #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);
@@ -321,8 +318,6 @@ extern void vty_log(const char *level, const char *proto, const char *fmt,
 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 *);