diff options
| -rw-r--r-- | docker/ubuntu20-ci/Dockerfile | 2 | ||||
| -rw-r--r-- | lib/routing_nb.h | 8 | ||||
| -rw-r--r-- | lib/zebra.h | 8 | ||||
| -rw-r--r-- | staticd/static_debug.h | 8 | ||||
| -rw-r--r-- | staticd/static_nb.h | 8 | ||||
| -rw-r--r-- | staticd/static_nht.h | 9 | ||||
| -rw-r--r-- | staticd/static_routes.h | 9 | ||||
| -rw-r--r-- | staticd/static_vrf.h | 8 | ||||
| -rw-r--r-- | staticd/static_vty.h | 9 | ||||
| -rw-r--r-- | staticd/static_zebra.c | 10 | ||||
| -rw-r--r-- | staticd/static_zebra.h | 10 | ||||
| -rw-r--r-- | tests/lib/test_grpc.cpp | 979 | ||||
| -rw-r--r-- | tests/lib/test_grpc.py | 23 | ||||
| -rw-r--r-- | tests/subdir.am | 22 | 
14 files changed, 1112 insertions, 1 deletions
diff --git a/docker/ubuntu20-ci/Dockerfile b/docker/ubuntu20-ci/Dockerfile index 71fde305e6..8b7557db1d 100644 --- a/docker/ubuntu20-ci/Dockerfile +++ b/docker/ubuntu20-ci/Dockerfile @@ -11,6 +11,7 @@ RUN apt update && \        install-info build-essential libsystemd-dev libsnmp-dev perl \        libcap-dev python2 libelf-dev \        sudo gdb curl iputils-ping time \ +      libgrpc++-dev libgrpc-dev protobuf-compiler-grpc \        mininet iproute2 iperf && \        curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output /tmp/get-pip.py && \        python2 /tmp/get-pip.py && \ @@ -57,6 +58,7 @@ RUN cd ~/frr && \         --sbindir=/usr/lib/frr \         --sysconfdir=/etc/frr \         --enable-vtysh \ +       --enable-grpc \         --enable-pimd \         --enable-sharpd \         --enable-multipath=64 \ diff --git a/lib/routing_nb.h b/lib/routing_nb.h index bdd12b262b..c185091a4b 100644 --- a/lib/routing_nb.h +++ b/lib/routing_nb.h @@ -1,6 +1,10 @@  #ifndef _FRR_ROUTING_NB_H_  #define _FRR_ROUTING_NB_H_ +#ifdef __cplusplus +extern "C" { +#endif +  extern const struct frr_yang_module_info frr_routing_info;  /* Mandatory callbacks. */ @@ -28,4 +32,8 @@ DECLARE_HOOK(routing_conf_event, (struct nb_cb_create_args *args), (args));  void routing_control_plane_protocols_register_vrf_dependency(void); +#ifdef __cplusplus +} +#endif +  #endif /* _FRR_ROUTING_NB_H_ */ diff --git a/lib/zebra.h b/lib/zebra.h index 3b624117de..6a02dcb922 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -217,6 +217,10 @@  #define static_cast(l, r) (r)  #endif +#ifdef __cplusplus +extern "C" { +#endif +  #ifndef HAVE_STRLCAT  size_t strlcat(char *__restrict dest,  	       const char *__restrict src, size_t destsize); @@ -379,4 +383,8 @@ typedef uint32_t route_tag_t;  #define ROUTE_TAG_MAX UINT32_MAX  #define ROUTE_TAG_PRI PRIu32 +#ifdef __cplusplus +} +#endif +  #endif /* _ZEBRA_H */ diff --git a/staticd/static_debug.h b/staticd/static_debug.h index 3a96339f47..ee9f7b0537 100644 --- a/staticd/static_debug.h +++ b/staticd/static_debug.h @@ -23,11 +23,14 @@  #ifndef _STATIC_DEBUG_H  #define _STATIC_DEBUG_H -  #include <zebra.h>  #include "lib/debug.h" +#ifdef __cplusplus +extern "C" { +#endif +  /* staticd debugging records */  extern struct debug static_dbg_events;  extern struct debug static_dbg_route; @@ -70,5 +73,8 @@ int static_debug_status_write(struct vty *vty);   */  void static_debug_set(int vtynode, bool onoff, bool events, bool route); +#ifdef __cplusplus +} +#endif  #endif /* _STATIC_DEBUG_H */ diff --git a/staticd/static_nb.h b/staticd/static_nb.h index 0313ae1c3a..5c3030fcfa 100644 --- a/staticd/static_nb.h +++ b/staticd/static_nb.h @@ -18,6 +18,10 @@  #ifndef _FRR_STATIC_NB_H_  #define _FRR_STATIC_NB_H_ +#ifdef __cplusplus +extern "C" { +#endif +  extern const struct frr_yang_module_info frr_staticd_info;  /* Mandatory callbacks. */ @@ -181,4 +185,8 @@ int routing_control_plane_protocols_name_validate(  	FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH                             \  	FRR_STATIC_ROUTE_NH_KEY_XPATH +#ifdef __cplusplus +} +#endif +  #endif diff --git a/staticd/static_nht.h b/staticd/static_nht.h index 9139c367d1..08dba2ebb5 100644 --- a/staticd/static_nht.h +++ b/staticd/static_nht.h @@ -20,6 +20,10 @@  #ifndef __STATIC_NHT_H__  #define __STATIC_NHT_H__ +#ifdef __cplusplus +extern "C" { +#endif +  /*   * When we get notification that nexthop tracking has an answer for   * us call this function to find the nexthop we are tracking so it @@ -53,4 +57,9 @@ extern void static_nht_mark_state(struct prefix *sp, vrf_id_t vrf_id,   */  extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop,  			      size_t size); + +#ifdef __cplusplus +} +#endif +  #endif diff --git a/staticd/static_routes.h b/staticd/static_routes.h index f64a40329d..1269621cc9 100644 --- a/staticd/static_routes.h +++ b/staticd/static_routes.h @@ -24,6 +24,10 @@  #include "table.h"  #include "memory.h" +#ifdef __cplusplus +extern "C" { +#endif +  DECLARE_MGROUP(STATIC);  /* Static route label information */ @@ -216,4 +220,9 @@ extern void zebra_stable_node_cleanup(struct route_table *table,   */  extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop,  			      size_t size); + +#ifdef __cplusplus +} +#endif +  #endif diff --git a/staticd/static_vrf.h b/staticd/static_vrf.h index 3977899054..be311af8c4 100644 --- a/staticd/static_vrf.h +++ b/staticd/static_vrf.h @@ -20,6 +20,10 @@  #ifndef __STATIC_VRF_H__  #define __STATIC_VRF_H__ +#ifdef __cplusplus +extern "C" { +#endif +  struct static_vrf {  	struct vrf *vrf; @@ -43,4 +47,8 @@ struct route_table *static_vrf_static_table(afi_t afi, safi_t safi,  					    struct static_vrf *svrf);  extern void static_vrf_terminate(void); +#ifdef __cplusplus +} +#endif +  #endif diff --git a/staticd/static_vty.h b/staticd/static_vty.h index 7ffc8d9c98..01577685e5 100644 --- a/staticd/static_vty.h +++ b/staticd/static_vty.h @@ -19,8 +19,17 @@  #ifndef __STATIC_VTY_H__  #define __STATIC_VTY_H__ +#ifdef __cplusplus +extern "C" { +#endif +  int static_config(struct vty *vty, struct static_vrf *svrf,  		  afi_t afi, safi_t safi, const char *cmd);  void static_vty_init(void); + +#ifdef __cplusplus +} +#endif +  #endif diff --git a/staticd/static_zebra.c b/staticd/static_zebra.c index 19c578c60e..40275908f7 100644 --- a/staticd/static_zebra.c +++ b/staticd/static_zebra.c @@ -529,6 +529,16 @@ void static_zebra_init(void)  				      "Static Nexthop Tracking hash");  } +/* static_zebra_stop used by tests/lib/test_grpc.cpp */ +void static_zebra_stop(void) +{ +	if (!zclient) +		return; +	zclient_stop(zclient); +	zclient_free(zclient); +	zclient = NULL; +} +  void static_zebra_vrf_register(struct vrf *vrf)  {  	if (vrf->vrf_id == VRF_DEFAULT) diff --git a/staticd/static_zebra.h b/staticd/static_zebra.h index 9f93f3ee63..ca6308559e 100644 --- a/staticd/static_zebra.h +++ b/staticd/static_zebra.h @@ -19,6 +19,10 @@  #ifndef __STATIC_ZEBRA_H__  #define __STATIC_ZEBRA_H__ +#ifdef __cplusplus +extern "C" { +#endif +  extern struct thread_master *master;  extern void static_zebra_nht_register(struct route_node *rn, @@ -28,9 +32,15 @@ extern void static_zebra_route_add(struct route_node *rn,  				   struct static_path *pn, safi_t safi,  				   bool install);  extern void static_zebra_init(void); +/* static_zebra_stop used by tests/lib/test_grpc.cpp */ +extern void static_zebra_stop(void);  extern void static_zebra_vrf_register(struct vrf *vrf);  extern void static_zebra_vrf_unregister(struct vrf *vrf);  extern int static_zebra_nh_update(struct route_node *rn,  				  struct static_nexthop *nh); +#ifdef __cplusplus +} +#endif +  #endif diff --git a/tests/lib/test_grpc.cpp b/tests/lib/test_grpc.cpp new file mode 100644 index 0000000000..491796802a --- /dev/null +++ b/tests/lib/test_grpc.cpp @@ -0,0 +1,979 @@ +/* + * May 16 2021, Christian Hopps <chopps@labn.net> + * + * Copyright (c) 2021, LabN Consulting, L.L.C + * + * 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 <time.h> +#include <unistd.h> +#include <zebra.h> + +#include "filter.h" +#include "frr_pthread.h" +#include "libfrr.h" +#include "routing_nb.h" +#include "northbound_cli.h" +#include "thread.h" +#include "vrf.h" +#include "vty.h" + +#include "staticd/static_debug.h" +#include "staticd/static_nb.h" +#include "staticd/static_vrf.h" +#include "staticd/static_vty.h" +#include "staticd/static_zebra.h" + +// GRPC C++ includes +#include <string> +#include <sstream> +#include <grpc/grpc.h> +#include <grpcpp/channel.h> +#include <grpcpp/client_context.h> +#include <grpcpp/create_channel.h> +#include <grpcpp/security/credentials.h> +#include "grpc/frr-northbound.grpc.pb.h" + +DEFINE_HOOK(frr_late_init, (struct thread_master * tm), (tm)); +DEFINE_KOOH(frr_fini, (), ()); + +struct vty *vty; + +bool mpls_enabled; +struct thread_master *master; +struct zebra_privs_t static_privs = {0}; +struct frrmod_runtime *grpc_module; +char binpath[2 * MAXPATHLEN + 1]; + +extern const char *json_expect1; +extern const char *json_expect2; +extern const char *json_expect3; +extern const char *json_loadconf1; + +int test_dbg = 1; + +void inline test_debug(const std::string &s) +{ +	if (test_dbg) +		std::cout << s << std::endl; +} + +// static struct option_chain modules[] = {{ .arg = "grpc:50051" }] +// static struct option_chain **modnext = modules->next; + +static const struct frr_yang_module_info *const staticd_yang_modules[] = { +	&frr_interface_info, &frr_filter_info, &frr_routing_info, +	&frr_staticd_info,   &frr_vrf_info, +}; + +static int grpc_thread_stop(struct thread *thread); + +static void static_startup(void) +{ +	// struct frrmod_runtime module; +	// static struct option_chain *oc; +	char moderr[256] = {}; +	cmd_init(1); + +	zlog_aux_init("NONE: ", LOG_DEBUG); +	zprivs_preinit(&static_privs); +	zprivs_init(&static_privs); + +	/* Load the server side module -- check libtool path first */ +	std::string modpath = std::string(binpath) + std::string("../../../lib/.libs"); +	grpc_module = frrmod_load("grpc:50051", modpath.c_str(), moderr, sizeof(moderr)); +	if (!grpc_module) { +		modpath = std::string(binpath) +  std::string("../../lib"); +		grpc_module = frrmod_load("grpc:50051", modpath.c_str(), moderr, +					  sizeof(moderr)); +	} +	if (!grpc_module) { +		std::cout << "Failed to load grpc module:" << moderr +			  << std::endl; +		exit(1); +	} + +	static_debug_init(); + +	master = thread_master_create(NULL); +	nb_init(master, staticd_yang_modules, array_size(staticd_yang_modules), +		false); + +	static_zebra_init(); +	vty_init(master, true); +	static_vrf_init(); +	static_vty_init(); + +	hook_register(routing_conf_event, +		      routing_control_plane_protocols_name_validate); + +	routing_control_plane_protocols_register_vrf_dependency(); + +	// Add a route +	vty = vty_new(); +	vty->type = vty::VTY_TERM; +	vty_config_enter(vty, true, false); + +	auto ret = cmd_execute(vty, "ip route 11.0.0.0/8 Null0", NULL, 0); +	assert(!ret); + +	ret = cmd_execute(vty, "end", NULL, 0); +	assert(!ret); + +	nb_cli_pending_commit_check(vty); + +	frr_pthread_init(); + +	// frr_config_fork(); +	hook_call(frr_late_init, master); +} + +static void static_shutdown(void) +{ +	hook_call(frr_fini); +	vty_close(vty); +	vrf_terminate(); +	vty_terminate(); +	cmd_terminate(); +	nb_terminate(); +	yang_terminate(); +	thread_master_free(master); +	master = NULL; +} + +using frr::Northbound; +using grpc::Channel; +using grpc::ClientAsyncResponseReader; +using grpc::ClientContext; +using grpc::CompletionQueue; +using grpc::Status; + +class NorthboundClient +{ +      public: +	NorthboundClient(std::shared_ptr<Channel> channel) +	    : stub_(frr::Northbound::NewStub(channel)) +	{ +	} + +	void Commit(uint32_t candidate_id) +	{ +		frr::CommitRequest request; +		frr::CommitResponse reply; +		ClientContext context; +		Status status; + +		request.set_candidate_id(candidate_id); + +		request.set_phase(frr::CommitRequest::ALL); +		status = stub_->Commit(&context, request, &reply); +		_throw_if_not_ok(status); +#if 0 +		request.set_phase(frr::CommitRequest::VALIDATE); +		status = stub_->Commit(&context, request, &reply); +		_throw_if_not_ok(status); + +		request.set_phase(frr::CommitRequest::PREPARE); +		status = stub_->Commit(&context, request, &reply); +		_throw_if_not_ok(status); + +		request.set_phase(frr::CommitRequest::APPLY); +		status = stub_->Commit(&context, request, &reply); +		_throw_if_not_ok(status); +#endif +	} + +	uint32_t CreateCandidate() +	{ +		frr::CreateCandidateRequest request; +		frr::CreateCandidateResponse reply; +		ClientContext context; +		Status status; + +		status = stub_->CreateCandidate(&context, request, &reply); +		_throw_if_not_ok(status); +		return reply.candidate_id(); +	} + +	void DeleteCandidate(uint32_t candidate_id) +	{ +		frr::DeleteCandidateRequest request; +		frr::DeleteCandidateResponse reply; +		ClientContext context; +		Status status; + +		request.set_candidate_id(candidate_id); +		status = stub_->DeleteCandidate(&context, request, &reply); +		_throw_if_not_ok(status); +	} + +	void EditCandidate(uint32_t candidate_id, const std::string &path, +			   const std::string &value) +	{ +		frr::EditCandidateRequest request; +		frr::EditCandidateResponse reply; +		ClientContext context; + +		request.set_candidate_id(candidate_id); +		frr::PathValue *pv = request.add_update(); +		pv->set_path(path); +		pv->set_value(value); + +		Status status = stub_->EditCandidate(&context, request, &reply); +		_throw_if_not_ok(status); +	} + +	std::string Get(const std::string &path, +			frr::GetRequest::DataType dtype, frr::Encoding enc, +			bool with_defaults) +	{ +		frr::GetRequest request; +		frr::GetResponse reply; +		ClientContext context; +		std::ostringstream ss; + +		request.set_type(dtype); +		request.set_encoding(enc); +		request.set_with_defaults(with_defaults); +		request.add_path(path); + +		auto stream = stub_->Get(&context, request); +		while (stream->Read(&reply)) { +			ss << reply.data().data() << std::endl; +		} +		auto status = stream->Finish(); +		_throw_if_not_ok(status); +		return ss.str(); +	} + +	std::string GetCapabilities() +	{ +		frr::GetCapabilitiesRequest request; +		frr::GetCapabilitiesResponse reply; +		ClientContext context; + +		Status status = +			stub_->GetCapabilities(&context, request, &reply); +		_throw_if_not_ok(status); + +		std::ostringstream ss; +		ss << "Capabilities:" << std::endl +		   << "\tVersion: " << reply.frr_version() << std::endl +		   << "\tRollback Support: " << reply.rollback_support() +		   << std::endl +		   << "\tSupported Modules:"; + +		for (int i = 0; i < reply.supported_modules_size(); i++) { +			auto sm = reply.supported_modules(i); +			ss << std::endl +			   << "\t\tName: \"" << sm.name() +			   << "\" Revision: " << sm.revision() << " Org: \"" +			   << sm.organization() << "\""; +		} + +		ss << std::endl << "\tSupported Encodings:"; + +		for (int i = 0; i < reply.supported_encodings_size(); i++) { +			auto se = reply.supported_encodings(i); +			auto desc = +				google::protobuf::GetEnumDescriptor<decltype( +					se)>(); +			ss << std::endl +			   << "\t\t" << desc->FindValueByNumber(se)->name(); +		} + +		ss << std::endl; + +		return ss.str(); +	} + +	void LoadToCandidate(uint32_t candidate_id, bool is_replace, +			     bool is_json, const std::string &data) +	{ +		frr::LoadToCandidateRequest request; +		frr::LoadToCandidateResponse reply; +		frr::DataTree *dt = new frr::DataTree; +		ClientContext context; + +		request.set_candidate_id(candidate_id); +		request.set_type(is_replace +					 ? frr::LoadToCandidateRequest::REPLACE +					 : frr::LoadToCandidateRequest::MERGE); +		dt->set_encoding(is_json ? frr::JSON : frr::XML); +		dt->set_data(data); +		request.set_allocated_config(dt); + +		Status status = +			stub_->LoadToCandidate(&context, request, &reply); +		_throw_if_not_ok(status); +	} + +	std::string ListTransactions() +	{ +		frr::ListTransactionsRequest request; +		frr::ListTransactionsResponse reply; +		ClientContext context; +		std::ostringstream ss; + +		auto stream = stub_->ListTransactions(&context, request); + +		while (stream->Read(&reply)) { +			ss << "Tx ID: " << reply.id() +			   << " client: " << reply.client() +			   << " date: " << reply.date() +			   << " comment: " << reply.comment() << std::endl; +		} + +		auto status = stream->Finish(); +		_throw_if_not_ok(status); +		return ss.str(); +	} + +      private: +	std::unique_ptr<frr::Northbound::Stub> stub_; + +	void _throw_if_not_ok(Status &status) +	{ +		if (!status.ok()) +			throw std::runtime_error( +				std::to_string(status.error_code()) + ": " +				+ status.error_message()); +	} +}; + + +bool stop = false; + +int grpc_client_test_stop(struct frr_pthread *fpt, void **result) +{ +	test_debug("client: STOP pthread"); + +	assert(fpt->running); +	atomic_store_explicit(&fpt->running, false, memory_order_relaxed); + +	test_debug("client: joining pthread"); +	pthread_join(fpt->thread, result); + +	test_debug("client: joined pthread"); +	return 0; +} + +int find_first_diff(const std::string &s1, const std::string &s2) +{ +	int s1len = s1.length(); +	int s2len = s2.length(); +	int mlen = std::min(s1len, s2len); + +	for (int i = 0; i < mlen; i++) +		if (s1[i] != s2[i]) +			return i; +	return s1len == s2len ? -1 : mlen; +} + +void assert_no_diff(const std::string &s1, const std::string &s2) +{ +	int pos = find_first_diff(s1, s2); +	if (pos == -1) +		return; +	std::cout << "not ok" << std::endl; +	std::cout << "Same: " << s1.substr(0, pos) << std::endl; +	std::cout << "Diff s1: " << s1.substr(pos) << std::endl; +	std::cout << "Diff s2: " << s2.substr(pos) << std::endl; +	assert(false); +} + +void assert_config_same(NorthboundClient &client, const std::string &compare) +{ +	std::string confs = client.Get("/frr-routing:routing", +				       frr::GetRequest::ALL, frr::JSON, true); +	assert_no_diff(confs, compare); +	std::cout << "ok" << std::endl; +} + +void grpc_client_run_test(void) +{ +	NorthboundClient client(grpc::CreateChannel( +		"localhost:50051", grpc::InsecureChannelCredentials())); + +	std::string reply = client.GetCapabilities(); + +	uint32_t cid; +	cid = client.CreateCandidate(); +	std::cout << "CreateCandidate -> " << cid << std::endl; +	assert(cid == 1); +	client.DeleteCandidate(cid); +	std::cout << "DeleteCandidate(" << cid << ")" << std::endl; +	cid = client.CreateCandidate(); +	assert(cid == 2); +	std::cout << "CreateCandidate -> " << cid << std::endl; + +	/* +	 * Get initial configuration +	 */ +	std::cout << "Comparing initial config..."; +	assert_config_same(client, json_expect1); + +	/* +	 * Add config using EditCandidate +	 */ + +	char xpath_buf[1024]; +	strlcpy(xpath_buf, +		"/frr-routing:routing/control-plane-protocols/" +		"control-plane-protocol[type='frr-staticd:staticd']" +		"[name='staticd'][vrf='default']/frr-staticd:staticd/route-list", +		sizeof(xpath_buf)); +	int slen = strlen(xpath_buf); +	for (int i = 0; i < 4; i++) { +		snprintf(xpath_buf + slen, sizeof(xpath_buf) - slen, +			 "[prefix='13.0.%d.0/24']" +			 "[afi-safi='frr-routing:ipv4-unicast']/" +			 "path-list[table-id='0'][distance='1']/" +			 "frr-nexthops/nexthop[nh-type='blackhole']" +			 "[vrf='default'][gateway=''][interface='(null)']", +			 i); +		client.EditCandidate(cid, xpath_buf, ""); +	} +	client.Commit(cid); +	std::cout << "Comparing EditCandidate config..."; +	assert_config_same(client, json_expect2); + +	client.DeleteCandidate(cid); +	std::cout << "DeleteCandidate(" << cid << ")" << std::endl; + +	/* +	 * Add config using LoadToCandidate +	 */ + +	cid = client.CreateCandidate(); +	std::cout << "CreateCandidate -> " << cid << std::endl; + +	client.LoadToCandidate(cid, false, true, json_loadconf1); +	client.Commit(cid); + +	std::cout << "Comparing LoadToCandidate config..."; +	assert_config_same(client, json_expect3); + +	client.DeleteCandidate(cid); +	std::cout << "DeleteCandidate(" << cid << ")" << std::endl; + +	std::string ltxreply = client.ListTransactions(); +	// std::cout << "client: pthread received: " << ltxreply << std::endl; +} + +void *grpc_client_test_start(void *arg) +{ +	struct frr_pthread *fpt = (struct frr_pthread *)arg; +	fpt->master->owner = pthread_self(); +	frr_pthread_set_name(fpt); +	frr_pthread_notify_running(fpt); + +	try { +		grpc_client_run_test(); +		std::cout << "TEST PASSED" << std::endl; +	} catch (std::exception &e) { +		std::cout << "Exception in test: " << e.what() << std::endl; +	} + +	// Signal FRR event loop to stop +	test_debug("client: pthread: adding event to stop us"); +	thread_add_event(master, grpc_thread_stop, NULL, 0, NULL); + +	test_debug("client: pthread: DONE (returning)"); + +	return NULL; +} + +static int grpc_thread_start(struct thread *thread) +{ +	struct frr_pthread_attr client = { +		.start = grpc_client_test_start, +		.stop = grpc_client_test_stop, +	}; + +	auto pth = frr_pthread_new(&client, "GRPC Client thread", "grpc"); +	frr_pthread_run(pth, NULL); +	frr_pthread_wait_running(pth); + +	return 0; +} + +static int grpc_thread_stop(struct thread *thread) +{ +	std::cout << __func__ << ": frr_pthread_stop_all" << std::endl; +	frr_pthread_stop_all(); +	std::cout << __func__ << ": static_shutdown" << std::endl; +	static_shutdown(); +	std::cout << __func__ << ": exit cleanly" << std::endl; +	exit(0); +} + +/* + * return abs path to this binary with trailing `/`. Does not parse path + * environment to find in path, which should not matter for unit testing. + */ +static int get_binpath(const char *argv0, char cwd[2 * MAXPATHLEN + 1]) +{ +	const char *rch; +	if (argv0[0] == '/') { +		*cwd = 0; +		rch = strrchr(argv0, '/'); +		strlcpy(cwd, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1)); +		return 0; +	} +	if (!(rch = strrchr(argv0, '/'))) { +		/* Does not handle using PATH, shouldn't matter for test */ +		errno = EINVAL; +		return -1; +	} +	if (!getcwd(cwd, MAXPATHLEN)) +		return -1; +	int len = strlen(cwd); +	cwd[len++] = '/'; +	strlcpy(cwd + len, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1)); +	return 0; +} + +int main(int argc, char **argv) +{ +	assert(argc >= 1); +	if (get_binpath(argv[0], binpath) < 0) +		exit(1); + +	static_startup(); + +	thread_add_event(master, grpc_thread_start, NULL, 0, NULL); + +	/* Event Loop */ +	struct thread thread; +	while (thread_fetch(master, &thread)) +		thread_call(&thread); +	return 0; +} + +// clang-format off + +const char *json_expect1 = R"NONCE({ +  "frr-routing:routing": { +    "control-plane-protocols": { +      "control-plane-protocol": [ +        { +          "type": "frr-staticd:staticd", +          "name": "staticd", +          "vrf": "default", +          "frr-staticd:staticd": { +            "route-list": [ +              { +                "prefix": "11.0.0.0/8", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              } +            ] +          } +        } +      ] +    } +  }, +  "frr-vrf:lib": { +    "vrf": [ +      { +        "name": "default", +        "state": { +          "active": false +        } +      } +    ] +  } +} + +)NONCE"; + +const char *json_loadconf1 = R"NONCE( +{ +  "frr-routing:routing": { +    "control-plane-protocols": { +      "control-plane-protocol": [ +        { +          "type": "frr-staticd:staticd", +          "name": "staticd", +          "vrf": "default", +          "frr-staticd:staticd": { +            "route-list": [ +              { +                "prefix": "10.0.0.0/13", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)" +                        } +                      ] +                    } +                  } +                ] +              } +            ] +          } +        } +      ] +    } +  }, +  "frr-vrf:lib": { +    "vrf": [ +      { +        "name": "default" +      } +    ] +  } +})NONCE"; + +const char *json_expect2 = R"NONCE({ +  "frr-routing:routing": { +    "control-plane-protocols": { +      "control-plane-protocol": [ +        { +          "type": "frr-staticd:staticd", +          "name": "staticd", +          "vrf": "default", +          "frr-staticd:staticd": { +            "route-list": [ +              { +                "prefix": "11.0.0.0/8", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.0.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.1.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.2.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.3.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              } +            ] +          } +        } +      ] +    } +  }, +  "frr-vrf:lib": { +    "vrf": [ +      { +        "name": "default", +        "state": { +          "active": false +        } +      } +    ] +  } +} + +)NONCE"; + +const char *json_expect3 = R"NONCE({ +  "frr-routing:routing": { +    "control-plane-protocols": { +      "control-plane-protocol": [ +        { +          "type": "frr-staticd:staticd", +          "name": "staticd", +          "vrf": "default", +          "frr-staticd:staticd": { +            "route-list": [ +              { +                "prefix": "11.0.0.0/8", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.0.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.1.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.2.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "13.0.3.0/24", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              }, +              { +                "prefix": "10.0.0.0/13", +                "afi-safi": "frr-routing:ipv4-unicast", +                "path-list": [ +                  { +                    "table-id": 0, +                    "distance": 1, +                    "tag": 0, +                    "frr-nexthops": { +                      "nexthop": [ +                        { +                          "nh-type": "blackhole", +                          "vrf": "default", +                          "gateway": "", +                          "interface": "(null)", +                          "bh-type": "null", +                          "onlink": false +                        } +                      ] +                    } +                  } +                ] +              } +            ] +          } +        } +      ] +    } +  }, +  "frr-vrf:lib": { +    "vrf": [ +      { +        "name": "default", +        "state": { +          "active": false +        } +      } +    ] +  } +} + +)NONCE"; diff --git a/tests/lib/test_grpc.py b/tests/lib/test_grpc.py new file mode 100644 index 0000000000..06ae6c05dd --- /dev/null +++ b/tests/lib/test_grpc.py @@ -0,0 +1,23 @@ +import inspect +import os +import subprocess +import pytest +import frrtest + +class TestGRPC(object): +    program = "./test_grpc" + +    @pytest.mark.skipif( +        'S["GRPC_TRUE"]=""\n' not in open("../config.status").readlines(), +        reason="GRPC not enabled", +    ) +    def test_exits_cleanly(self): +        basedir = os.path.dirname(inspect.getsourcefile(type(self))) +        program = os.path.join(basedir, self.program) +        proc = subprocess.Popen( +            [frrtest.binpath(program)], stdin=subprocess.PIPE, stdout=subprocess.PIPE +        ) +        output, _ = proc.communicate() +        self.exitcode = proc.wait() +        if self.exitcode != 0: +            raise frrtest.TestExitNonzero(self) diff --git a/tests/subdir.am b/tests/subdir.am index 3996699774..ca477851e3 100644 --- a/tests/subdir.am +++ b/tests/subdir.am @@ -106,6 +106,12 @@ check_PROGRAMS = \  	$(TESTS_ZEBRA) \  	# end +if GRPC +check_PROGRAMS += \ +	tests/lib/test_grpc \ +	#end +endif +  if ZEROMQ  check_PROGRAMS += \  	tests/lib/test_zmq \ @@ -156,9 +162,19 @@ TESTS_CFLAGS = \  	# end  # note no -Werror +TESTS_CXXFLAGS = \ +	$(AC_CXXFLAGS) \ +	$(LIBYANG_CFLAGS) \ +	$(SAN_FLAGS) \ +	# end +# note no -Werror +  ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP)  BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) -lm  ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD) +if GRPC +GRPC_TESTS_LDADD = staticd/libstatic.a grpc/libfrrgrpc_pb.la -lgrpc++ -lprotobuf $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) -lm +endif  OSPFD_TEST_LDADD = ospfd/libfrrospf.a $(ALL_TESTS_LDADD)  OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD)  ZEBRA_TEST_LDADD = zebra/label_manager.o $(ALL_TESTS_LDADD) @@ -251,6 +267,12 @@ tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS)  tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD)  tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c  nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c +if GRPC +tests_lib_test_grpc_CXXFLAGS = $(WERROR) $(TESTS_CXXFLAGS) +tests_lib_test_grpc_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_grpc_LDADD = $(GRPC_TESTS_LDADD) +tests_lib_test_grpc_SOURCES = tests/lib/test_grpc.cpp +endif  tests_lib_test_assert_CFLAGS = $(TESTS_CFLAGS)  tests_lib_test_assert_CPPFLAGS = $(TESTS_CPPFLAGS)  tests_lib_test_assert_LDADD = $(ALL_TESTS_LDADD)  | 
