mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-04-28 15:05:03 +00:00
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 <chopps@labn.net>
This commit is contained in:
parent
6198bc98be
commit
deca28a33b
@ -11,6 +11,7 @@ RUN apt update && \
|
|||||||
install-info build-essential libsystemd-dev libsnmp-dev perl \
|
install-info build-essential libsystemd-dev libsnmp-dev perl \
|
||||||
libcap-dev python2 libelf-dev \
|
libcap-dev python2 libelf-dev \
|
||||||
sudo gdb curl iputils-ping time \
|
sudo gdb curl iputils-ping time \
|
||||||
|
libgrpc++-dev libgrpc-dev protobuf-compiler-grpc \
|
||||||
mininet iproute2 iperf && \
|
mininet iproute2 iperf && \
|
||||||
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output /tmp/get-pip.py && \
|
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output /tmp/get-pip.py && \
|
||||||
python2 /tmp/get-pip.py && \
|
python2 /tmp/get-pip.py && \
|
||||||
@ -57,6 +58,7 @@ RUN cd ~/frr && \
|
|||||||
--sbindir=/usr/lib/frr \
|
--sbindir=/usr/lib/frr \
|
||||||
--sysconfdir=/etc/frr \
|
--sysconfdir=/etc/frr \
|
||||||
--enable-vtysh \
|
--enable-vtysh \
|
||||||
|
--enable-grpc \
|
||||||
--enable-pimd \
|
--enable-pimd \
|
||||||
--enable-sharpd \
|
--enable-sharpd \
|
||||||
--enable-multipath=64 \
|
--enable-multipath=64 \
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
#ifndef _FRR_ROUTING_NB_H_
|
#ifndef _FRR_ROUTING_NB_H_
|
||||||
#define _FRR_ROUTING_NB_H_
|
#define _FRR_ROUTING_NB_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
extern const struct frr_yang_module_info frr_routing_info;
|
extern const struct frr_yang_module_info frr_routing_info;
|
||||||
|
|
||||||
/* Mandatory callbacks. */
|
/* 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);
|
void routing_control_plane_protocols_register_vrf_dependency(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* _FRR_ROUTING_NB_H_ */
|
#endif /* _FRR_ROUTING_NB_H_ */
|
||||||
|
@ -217,6 +217,10 @@
|
|||||||
#define static_cast(l, r) (r)
|
#define static_cast(l, r) (r)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_STRLCAT
|
#ifndef HAVE_STRLCAT
|
||||||
size_t strlcat(char *__restrict dest,
|
size_t strlcat(char *__restrict dest,
|
||||||
const char *__restrict src, size_t destsize);
|
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_MAX UINT32_MAX
|
||||||
#define ROUTE_TAG_PRI PRIu32
|
#define ROUTE_TAG_PRI PRIu32
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* _ZEBRA_H */
|
#endif /* _ZEBRA_H */
|
||||||
|
@ -23,11 +23,14 @@
|
|||||||
#ifndef _STATIC_DEBUG_H
|
#ifndef _STATIC_DEBUG_H
|
||||||
#define _STATIC_DEBUG_H
|
#define _STATIC_DEBUG_H
|
||||||
|
|
||||||
|
|
||||||
#include <zebra.h>
|
#include <zebra.h>
|
||||||
|
|
||||||
#include "lib/debug.h"
|
#include "lib/debug.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/* staticd debugging records */
|
/* staticd debugging records */
|
||||||
extern struct debug static_dbg_events;
|
extern struct debug static_dbg_events;
|
||||||
extern struct debug static_dbg_route;
|
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);
|
void static_debug_set(int vtynode, bool onoff, bool events, bool route);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* _STATIC_DEBUG_H */
|
#endif /* _STATIC_DEBUG_H */
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
#ifndef _FRR_STATIC_NB_H_
|
#ifndef _FRR_STATIC_NB_H_
|
||||||
#define _FRR_STATIC_NB_H_
|
#define _FRR_STATIC_NB_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
extern const struct frr_yang_module_info frr_staticd_info;
|
extern const struct frr_yang_module_info frr_staticd_info;
|
||||||
|
|
||||||
/* Mandatory callbacks. */
|
/* Mandatory callbacks. */
|
||||||
@ -181,4 +185,8 @@ int routing_control_plane_protocols_name_validate(
|
|||||||
FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH \
|
FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH \
|
||||||
FRR_STATIC_ROUTE_NH_KEY_XPATH
|
FRR_STATIC_ROUTE_NH_KEY_XPATH
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
#ifndef __STATIC_NHT_H__
|
#ifndef __STATIC_NHT_H__
|
||||||
#define __STATIC_NHT_H__
|
#define __STATIC_NHT_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When we get notification that nexthop tracking has an answer for
|
* When we get notification that nexthop tracking has an answer for
|
||||||
* us call this function to find the nexthop we are tracking so it
|
* 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,
|
extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop,
|
||||||
size_t size);
|
size_t size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,6 +24,10 @@
|
|||||||
#include "table.h"
|
#include "table.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
DECLARE_MGROUP(STATIC);
|
DECLARE_MGROUP(STATIC);
|
||||||
|
|
||||||
/* Static route label information */
|
/* 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,
|
extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop,
|
||||||
size_t size);
|
size_t size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
#ifndef __STATIC_VRF_H__
|
#ifndef __STATIC_VRF_H__
|
||||||
#define __STATIC_VRF_H__
|
#define __STATIC_VRF_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
struct static_vrf {
|
struct static_vrf {
|
||||||
struct vrf *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);
|
struct static_vrf *svrf);
|
||||||
extern void static_vrf_terminate(void);
|
extern void static_vrf_terminate(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -19,8 +19,17 @@
|
|||||||
#ifndef __STATIC_VTY_H__
|
#ifndef __STATIC_VTY_H__
|
||||||
#define __STATIC_VTY_H__
|
#define __STATIC_VTY_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
int static_config(struct vty *vty, struct static_vrf *svrf,
|
int static_config(struct vty *vty, struct static_vrf *svrf,
|
||||||
afi_t afi, safi_t safi, const char *cmd);
|
afi_t afi, safi_t safi, const char *cmd);
|
||||||
|
|
||||||
void static_vty_init(void);
|
void static_vty_init(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -529,6 +529,16 @@ void static_zebra_init(void)
|
|||||||
"Static Nexthop Tracking hash");
|
"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)
|
void static_zebra_vrf_register(struct vrf *vrf)
|
||||||
{
|
{
|
||||||
if (vrf->vrf_id == VRF_DEFAULT)
|
if (vrf->vrf_id == VRF_DEFAULT)
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
#ifndef __STATIC_ZEBRA_H__
|
#ifndef __STATIC_ZEBRA_H__
|
||||||
#define __STATIC_ZEBRA_H__
|
#define __STATIC_ZEBRA_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
extern struct thread_master *master;
|
extern struct thread_master *master;
|
||||||
|
|
||||||
extern void static_zebra_nht_register(struct route_node *rn,
|
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,
|
struct static_path *pn, safi_t safi,
|
||||||
bool install);
|
bool install);
|
||||||
extern void static_zebra_init(void);
|
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_register(struct vrf *vrf);
|
||||||
extern void static_zebra_vrf_unregister(struct vrf *vrf);
|
extern void static_zebra_vrf_unregister(struct vrf *vrf);
|
||||||
extern int static_zebra_nh_update(struct route_node *rn,
|
extern int static_zebra_nh_update(struct route_node *rn,
|
||||||
struct static_nexthop *nh);
|
struct static_nexthop *nh);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
979
tests/lib/test_grpc.cpp
Normal file
979
tests/lib/test_grpc.cpp
Normal file
@ -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";
|
23
tests/lib/test_grpc.py
Normal file
23
tests/lib/test_grpc.py
Normal file
@ -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)
|
@ -106,6 +106,12 @@ check_PROGRAMS = \
|
|||||||
$(TESTS_ZEBRA) \
|
$(TESTS_ZEBRA) \
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
if GRPC
|
||||||
|
check_PROGRAMS += \
|
||||||
|
tests/lib/test_grpc \
|
||||||
|
#end
|
||||||
|
endif
|
||||||
|
|
||||||
if ZEROMQ
|
if ZEROMQ
|
||||||
check_PROGRAMS += \
|
check_PROGRAMS += \
|
||||||
tests/lib/test_zmq \
|
tests/lib/test_zmq \
|
||||||
@ -156,9 +162,19 @@ TESTS_CFLAGS = \
|
|||||||
# end
|
# end
|
||||||
# note no -Werror
|
# note no -Werror
|
||||||
|
|
||||||
|
TESTS_CXXFLAGS = \
|
||||||
|
$(AC_CXXFLAGS) \
|
||||||
|
$(LIBYANG_CFLAGS) \
|
||||||
|
$(SAN_FLAGS) \
|
||||||
|
# end
|
||||||
|
# note no -Werror
|
||||||
|
|
||||||
ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP)
|
ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP)
|
||||||
BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) -lm
|
BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) -lm
|
||||||
ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD)
|
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)
|
OSPFD_TEST_LDADD = ospfd/libfrrospf.a $(ALL_TESTS_LDADD)
|
||||||
OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD)
|
OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD)
|
||||||
ZEBRA_TEST_LDADD = zebra/label_manager.o $(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_LDADD = $(ALL_TESTS_LDADD)
|
||||||
tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c
|
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
|
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_CFLAGS = $(TESTS_CFLAGS)
|
||||||
tests_lib_test_assert_CPPFLAGS = $(TESTS_CPPFLAGS)
|
tests_lib_test_assert_CPPFLAGS = $(TESTS_CPPFLAGS)
|
||||||
tests_lib_test_assert_LDADD = $(ALL_TESTS_LDADD)
|
tests_lib_test_assert_LDADD = $(ALL_TESTS_LDADD)
|
||||||
|
Loading…
Reference in New Issue
Block a user