diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | bgpd/bgp_rpki.c | 4 | ||||
| -rwxr-xr-x | configure.ac | 24 | ||||
| -rw-r--r-- | doc/user/zebra.rst | 16 | ||||
| -rw-r--r-- | grpc/Makefile | 10 | ||||
| -rw-r--r-- | grpc/frr-northbound.proto | 412 | ||||
| -rw-r--r-- | grpc/subdir.am | 30 | ||||
| -rw-r--r-- | isisd/isis_cli.c | 111 | ||||
| -rw-r--r-- | lib/command.c | 16 | ||||
| -rw-r--r-- | lib/if.c | 19 | ||||
| -rw-r--r-- | lib/lib_errors.c | 6 | ||||
| -rw-r--r-- | lib/lib_errors.h | 1 | ||||
| -rw-r--r-- | lib/libfrr.c | 7 | ||||
| -rw-r--r-- | lib/northbound.c | 249 | ||||
| -rw-r--r-- | lib/northbound.h | 75 | ||||
| -rw-r--r-- | lib/northbound_cli.c | 154 | ||||
| -rw-r--r-- | lib/northbound_confd.c | 8 | ||||
| -rw-r--r-- | lib/northbound_grpc.cpp | 936 | ||||
| -rw-r--r-- | lib/northbound_sysrepo.c | 12 | ||||
| -rw-r--r-- | lib/subdir.am | 14 | ||||
| -rw-r--r-- | lib/vty.c | 47 | ||||
| -rw-r--r-- | lib/vty.h | 5 | ||||
| -rw-r--r-- | ospfd/ospf_te.c | 195 | 
24 files changed, 2019 insertions, 335 deletions
diff --git a/.gitignore b/.gitignore index 5003c97572..7a1378d588 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@  *.pb.h  *.pb-c.h  *.pb-c.c +*.pb.cc  *_clippy.c  ### dist diff --git a/Makefile.am b/Makefile.am index 5b9f26f7ba..546aa85fe7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/bgpd/bgp_rpki.c b/bgpd/bgp_rpki.c index c63d4f9ad2..010322233d 100644 --- a/bgpd/bgp_rpki.c +++ b/bgpd/bgp_rpki.c @@ -1112,7 +1112,7 @@ DEFPY (rpki_cache,  		vty_out(vty,  			"ssh sockets are not supported. "  			"Please recompile rtrlib and frr with ssh support. " -			"If you want to use it"); +			"If you want to use it\n");  #endif  	} else { // use tcp connection  		return_value = add_tcp_cache(cache, tcpport, preference); @@ -1253,6 +1253,7 @@ DEFUN (show_rpki_cache_server,  				cache->tr_config.tcp_config->host,  				cache->tr_config.tcp_config->port); +#if defined(FOUND_SSH)  		} else if (cache->type == SSH) {  			vty_out(vty,  				"host: %s port: %d username: %s " @@ -1264,6 +1265,7 @@ DEFUN (show_rpki_cache_server,  					->server_hostkey_path,  				cache->tr_config.ssh_config  					->client_privkey_path); +#endif  		}  	} diff --git a/configure.ac b/configure.ac index 1a1c60e5fa..fe60ba2a74 100755 --- a/configure.ac +++ b/configure.ac @@ -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], @@ -1735,6 +1740,25 @@ 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 ---------------  AC_SEARCH_LIBS([sqrt], [m]) diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index bb3d6e491c..51ada8bc62 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -738,6 +738,22 @@ zebra Terminal Mode Commands     Display various statistics related to the installation and deletion     of routes, neighbor updates, and LSP's into the kernel. +.. index:: show zebra client [summary] +.. clicmd:: show zebra client [summary] + +   Display statistics about clients that are connected to zebra.  This is +   useful for debugging and seeing how much data is being passed between +   zebra and it's clients.  If the summary form of the command is choosen +   a table is displayed with shortened information. + +.. index:: show zebra router table summary +.. clicmd:: show zebra router table summary + +   Display summarized data about tables created, their afi/safi/tableid +   and how many routes each table contains.  Please note this is the +   total number of route nodes in the table.  Which will be higher than +   the actual number of routes that are held. +  .. index:: show zebra fpm stats  .. clicmd:: show zebra fpm stats diff --git a/grpc/Makefile b/grpc/Makefile new file mode 100644 index 0000000000..8748286629 --- /dev/null +++ b/grpc/Makefile @@ -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 index 0000000000..d070d715e8 --- /dev/null +++ b/grpc/frr-northbound.proto @@ -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 index 0000000000..3fb163fccf --- /dev/null +++ b/grpc/subdir.am @@ -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)/$^ diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index 0094852825..0334b98a12 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -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);  } diff --git a/lib/command.c b/lib/command.c index 559457c119..b3ef028004 100644 --- a/lib/command.c +++ b/lib/command.c @@ -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"); @@ -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)  { diff --git a/lib/lib_errors.c b/lib/lib_errors.c index 5f6c25b770..b6c764d873 100644 --- a/lib/lib_errors.c +++ b/lib/lib_errors.c @@ -333,6 +333,12 @@ static struct log_ref ferr_lib_err[] = {  		.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",  		.description = "A callback used to process a configuration change has returned an error while trying to abort a change", diff --git a/lib/lib_errors.h b/lib/lib_errors.h index fc405c2098..39b39fb065 100644 --- a/lib/lib_errors.h +++ b/lib/lib_errors.h @@ -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,  }; diff --git a/lib/libfrr.c b/lib/libfrr.c index 0d4c8d6c0f..5970e70a6b 100644 --- a/lib/libfrr.c +++ b/lib/libfrr.c @@ -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;  } diff --git a/lib/northbound.c b/lib/northbound.c index 5e031ac2ce..e8b3e46c19 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -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);  } diff --git a/lib/northbound.h b/lib/northbound.h index 14f27c1d41..8f6753506b 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -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. diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c index 48faa7595a..ae1b0578a0 100644 --- a/lib/northbound_cli.c +++ b/lib/northbound_cli.c @@ -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) { diff --git a/lib/northbound_confd.c b/lib/northbound_confd.c index 0df286511e..e9669fc7e1 100644 --- a/lib/northbound_confd.c +++ b/lib/northbound_confd.c @@ -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 index 0000000000..a55da23dd1 --- /dev/null +++ b/lib/northbound_grpc.cpp @@ -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, ) diff --git a/lib/northbound_sysrepo.c b/lib/northbound_sysrepo.c index 33b6c24782..44a55137f8 100644 --- a/lib/northbound_sysrepo.c +++ b/lib/northbound_sysrepo.c @@ -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. */ diff --git a/lib/subdir.am b/lib/subdir.am index 7027f3f0da..0b7af18fcf 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -310,6 +310,18 @@ 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  #  noinst_PROGRAMS += \ @@ -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; } @@ -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; @@ -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 *); diff --git a/ospfd/ospf_te.c b/ospfd/ospf_te.c index bd8cbee11a..1488aa88cd 100644 --- a/ospfd/ospf_te.c +++ b/ospfd/ospf_te.c @@ -397,53 +397,13 @@ static void set_linkparams_link_type(struct ospf_interface *oi,  	return;  } -static void set_linkparams_link_id(struct ospf_interface *oi, -				   struct mpls_te_link *lp) +static void set_linkparams_link_id(struct mpls_te_link *lp, +				   struct in_addr link_id)  { -	struct ospf_neighbor *nbr; -	int done = 0;  	lp->link_id.header.type = htons(TE_LINK_SUBTLV_LINK_ID);  	lp->link_id.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); - -	/* -	 * The Link ID is identical to the contents of the Link ID field -	 * in the Router LSA for these link types. -	 */ -	switch (oi->type) { -	case OSPF_IFTYPE_POINTOPOINT: -		/* Take the router ID of the neighbor. */ -		if ((nbr = ospf_nbr_lookup_ptop(oi)) -		    && nbr->state == NSM_Full) { -			lp->link_id.value = nbr->router_id; -			done = 1; -		} -		break; -	case OSPF_IFTYPE_BROADCAST: -	case OSPF_IFTYPE_NBMA: -		/* Take the interface address of the designated router. */ -		if ((nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi))) == NULL) -			break; - -		if (nbr->state == NSM_Full -		    || (IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi)) -			&& ospf_nbr_count(oi, NSM_Full) > 0)) { -			lp->link_id.value = DR(oi); -			done = 1; -		} -		break; -	default: -		/* Not supported yet. */ /* XXX */ -		lp->link_id.header.type = htons(0); -		break; -	} - -	if (!done) { -		struct in_addr mask; -		masklen2ip(oi->address->prefixlen, &mask); -		lp->link_id.value.s_addr = -			oi->address->u.prefix4.s_addr & mask.s_addr; -	} +	lp->link_id.value = link_id;  	return;  } @@ -958,40 +918,33 @@ void ospf_mpls_te_update_if(struct interface *ifp)  	return;  } +/* + * Just add interface and set available information. Other information + * and flooding of LSA will be done later when adjacency will be up + * See ospf_mpls_te_nsm_change() after + */  static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_state)  { -	struct te_link_subtlv_link_type old_type; -	struct te_link_subtlv_link_id old_id; +  	struct mpls_te_link *lp; -	if ((lp = lookup_linkparams_by_ifp(oi->ifp)) == NULL) { +	lp = lookup_linkparams_by_ifp(oi->ifp); +	if (lp == NULL) {  		flog_warn(  			EC_OSPF_TE_UNEXPECTED, -			"ospf_mpls_te_ism_change: Cannot get linkparams from OI(%s)?", -			IF_NAME(oi)); +			"MPLS-TE (%s): Cannot get linkparams from OI(%s)?", +			__func__, IF_NAME(oi));  		return;  	}  	if (oi->area == NULL || oi->area->ospf == NULL) {  		flog_warn(  			EC_OSPF_TE_UNEXPECTED, -			"ospf_mpls_te_ism_change: Cannot refer to OSPF from OI(%s)?", -			IF_NAME(oi)); +			"MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", +			__func__, IF_NAME(oi));  		return;  	} -#ifdef notyet -	if ((lp->area != NULL -	     && !IPV4_ADDR_SAME(&lp->area->area_id, &oi->area->area_id)) -	    || (lp->area != NULL && oi->area == NULL)) { -		/* How should we consider this case? */ -		flog_warn( -			EC_OSPF_TE_UNEXPECTED, -			"MPLS-TE: Area for OI(%s) has changed to [%s], flush previous LSAs", -			IF_NAME(oi), -			oi->area ? inet_ntoa(oi->area->area_id) : "N/A"); -		ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); -	} -#endif +  	/* Keep Area information in combination with linkparams. */  	lp->area = oi->area; @@ -1003,55 +956,103 @@ static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_state)  	case ISM_DROther:  	case ISM_Backup:  	case ISM_DR: -		old_type = lp->link_type; -		old_id = lp->link_id; - -		/* Set Link type, Link ID, Local and Remote IP addr */ +		/* Set Link type and Local IP addr */  		set_linkparams_link_type(oi, lp); -		set_linkparams_link_id(oi, lp);  		set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4); -		if (oi->type == LINK_TYPE_SUBTLV_VALUE_PTP) { -			struct prefix *pref = CONNECTED_PREFIX(oi->connected); -			if (pref != NULL) -				set_linkparams_rmtif_ipaddr(lp, -							    pref->u.prefix4); -		} - -		/* Update TE parameters */ -		update_linkparams(lp); - -		/* Try to Schedule LSA */ -		if ((ntohs(old_type.header.type) -			     != ntohs(lp->link_type.header.type) -		     || old_type.link_type.value -				!= lp->link_type.link_type.value) -		    || (ntohs(old_id.header.type) -				!= ntohs(lp->link_id.header.type) -			|| ntohl(old_id.value.s_addr) -				   != ntohl(lp->link_id.value.s_addr))) { -			if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) -				ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); -			else -				ospf_mpls_te_lsa_schedule(lp, -							  REORIGINATE_THIS_LSA); -		}  		break;  	default: -		lp->link_type.header.type = htons(0); -		lp->link_id.header.type = htons(0); - +		/* State is undefined: Flush LSA if engaged */  		if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED))  			ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA);  		break;  	} +	if (IS_DEBUG_OSPF_TE) +		zlog_debug( +			"MPLS-TE(%s): Update Link parameters for interface %s", +			__func__, IF_NAME(oi)); +  	return;  } +/* + * Complete TE info and schedule LSA flooding + * Link-ID and Remote IP address must be set with neighbor info + * which are only valid once NSM state is FULL + */  static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_state)  { -	/* Nothing to do here */ +	struct ospf_interface *oi = nbr->oi; +	struct mpls_te_link *lp; + +	/* Process Neighbor only when its state is NSM Full */ +	if (nbr->state != NSM_Full) +		return; + +	/* Get interface information for Traffic Engineering */ +	lp = lookup_linkparams_by_ifp(oi->ifp); +	if (lp == NULL) { +		flog_warn( +			EC_OSPF_TE_UNEXPECTED, +			"MPLS-TE (%s): Cannot get linkparams from OI(%s)?", +			__func__, IF_NAME(oi)); +		return; +	} + +	if (oi->area == NULL || oi->area->ospf == NULL) { +		flog_warn( +			EC_OSPF_TE_UNEXPECTED, +			"MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", +			__func__, IF_NAME(oi)); +		return; +	} + +	/* Keep Area information in combination with SR info. */ +	lp->area = oi->area; + +	/* Keep interface MPLS-TE status */ +	lp->flags = HAS_LINK_PARAMS(oi->ifp); + +	/* +	 * The Link ID is identical to the contents of the Link ID field +	 * in the Router LSA for these link types. +	 */ +	switch (oi->state) { +	case ISM_PointToPoint: +		/* Set Link ID with neighbor Router ID */ +		set_linkparams_link_id(lp, nbr->router_id); +		/* Set Remote IP address */ +		set_linkparams_rmtif_ipaddr(lp, nbr->address.u.prefix4); +		break; + +	case ISM_DR: +	case ISM_DROther: +	case ISM_Backup: +		/* Set Link ID with the Designated Router ID */ +		set_linkparams_link_id(lp, DR(oi)); +		break; + +	default: +		/* State is undefined: Flush LSA if engaged */ +		if (OspfMplsTE.enabled && +			CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) +			ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); +		return; +	} + +	if (IS_DEBUG_OSPF_TE) +		zlog_debug( +			"MPLS-TE (%s): Add Link-ID %s for interface %s ", +			__func__, inet_ntoa(lp->link_id.value), oi->ifp->name); + +	/* Try to Schedule LSA */ +	if (OspfMplsTE.enabled) { +		if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) +			ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); +		else +			ospf_mpls_te_lsa_schedule(lp, REORIGINATE_THIS_LSA); +	}  	return;  }  | 
