From deca28a33bcd913abeaf03017c52b1d2e9fd50e7 Mon Sep 17 00:00:00 2001 From: Christian Hopps Date: Thu, 20 May 2021 19:22:14 -0400 Subject: [PATCH] tests: add grpc unit test Test uses staticd which required some C++ header protections. Additionally, the test also runs in the ubuntu20 docker container as grpc is supported there by the packaging system. Signed-off-by: Christian Hopps --- docker/ubuntu20-ci/Dockerfile | 2 + lib/routing_nb.h | 8 + lib/zebra.h | 8 + staticd/static_debug.h | 8 +- staticd/static_nb.h | 8 + staticd/static_nht.h | 9 + staticd/static_routes.h | 9 + staticd/static_vrf.h | 8 + staticd/static_vty.h | 9 + staticd/static_zebra.c | 10 + staticd/static_zebra.h | 10 + tests/lib/test_grpc.cpp | 979 ++++++++++++++++++++++++++++++++++ tests/lib/test_grpc.py | 23 + tests/subdir.am | 22 + 14 files changed, 1112 insertions(+), 1 deletion(-) create mode 100644 tests/lib/test_grpc.cpp create mode 100644 tests/lib/test_grpc.py 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 #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 + * + * 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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#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) + : 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(); + 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 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)