summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/developer/topotests.rst3
-rw-r--r--lib/northbound_grpc.cpp27
-rw-r--r--pimd/pim6_main.c3
-rw-r--r--pimd/pim_igmpv2.c21
-rw-r--r--pimd/pim_igmpv3.c93
-rw-r--r--pimd/pim_join.c49
-rw-r--r--pimd/pim_nht.c10
-rw-r--r--pimd/pim_pim.c18
-rw-r--r--pimd/pim_tlv.c32
-rw-r--r--tests/.gitignore2
-rw-r--r--tests/lib/test_grpc.py10
-rwxr-xr-xtests/topotests/analyze.py7
l---------tests/topotests/grpc_basic/lib1
-rw-r--r--tests/topotests/grpc_basic/r1/zebra.conf8
-rw-r--r--tests/topotests/grpc_basic/r2/zebra.conf8
-rw-r--r--tests/topotests/grpc_basic/test_basic_grpc.py179
-rwxr-xr-xtests/topotests/lib/grpc-query.py155
17 files changed, 519 insertions, 107 deletions
diff --git a/doc/developer/topotests.rst b/doc/developer/topotests.rst
index b41181f4e9..6c1d9148d1 100644
--- a/doc/developer/topotests.rst
+++ b/doc/developer/topotests.rst
@@ -35,6 +35,9 @@ Installing Topotest Requirements
python2 -m pip install 'exabgp<4.0.0'
useradd -d /var/run/exabgp/ -s /bin/false exabgp
+ # To enable the gRPC topotest install:
+ python3 -m pip install grpcio grpcio-tools
+
Enable Coredumps
""""""""""""""""
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
index e2a6290035..0a458b6262 100644
--- a/lib/northbound_grpc.cpp
+++ b/lib/northbound_grpc.cpp
@@ -78,7 +78,7 @@ class Candidates
{
// Delete candidates.
for (auto it = _cdb.begin(); it != _cdb.end(); it++)
- delete_candidate(&it->second);
+ delete_candidate(it->first);
}
struct candidate *create_candidate(void)
@@ -94,8 +94,14 @@ class Candidates
return c;
}
- void delete_candidate(struct candidate *c)
+ bool contains(uint64_t candidate_id)
{
+ return _cdb.count(candidate_id) > 0;
+ }
+
+ void delete_candidate(uint64_t candidate_id)
+ {
+ struct candidate *c = &_cdb[candidate_id];
char errmsg[BUFSIZ] = {0};
nb_config_free(c->config);
@@ -105,14 +111,14 @@ class Candidates
_cdb.erase(c->id);
}
- struct candidate *get_candidate(uint32_t id)
+ struct candidate *get_candidate(uint64_t id)
{
return _cdb.count(id) == 0 ? NULL : &_cdb[id];
}
private:
uint64_t _next_id = 0;
- std::map<uint32_t, struct candidate> _cdb;
+ std::map<uint64_t, struct candidate> _cdb;
};
class RpcStateBase
@@ -183,6 +189,9 @@ template <typename Q, typename S> class NewRpcState : RpcStateBase
pthread_cond_wait(&this->cond, &this->cmux);
pthread_mutex_unlock(&this->cmux);
+ if (enter_state == FINISH)
+ assert(this->state == DELETED);
+
if (this->state == DELETED) {
grpc_debug("%s RPC: -> [DELETED]", name);
delete this;
@@ -617,15 +626,14 @@ void HandleUnaryDeleteCandidate(NewRpcState<frr::DeleteCandidateRequest,
grpc_debug("%s(candidate_id: %u)", __func__, candidate_id);
- struct candidate *candidate = tag->cdb->get_candidate(candidate_id);
- if (!candidate) {
+ if (!tag->cdb->contains(candidate_id)) {
tag->responder.Finish(
tag->response,
grpc::Status(grpc::StatusCode::NOT_FOUND,
"candidate configuration not found"),
tag);
} else {
- tag->cdb->delete_candidate(candidate);
+ tag->cdb->delete_candidate(candidate_id);
tag->responder.Finish(tag->response, grpc::Status::OK, tag);
}
tag->state = FINISH;
@@ -1400,6 +1408,11 @@ static int frr_grpc_finish(void)
grpc_debug("%s: joining and destroy grpc thread", __func__);
pthread_join(fpt->thread, NULL);
frr_pthread_destroy(fpt);
+
+ // Fix protobuf 'memory leaks' during shutdown.
+ // https://groups.google.com/g/protobuf/c/4y_EmQiCGgs
+ google::protobuf::ShutdownProtobufLibrary();
+
return 0;
}
diff --git a/pimd/pim6_main.c b/pimd/pim6_main.c
index 8c7fca174c..ed53924616 100644
--- a/pimd/pim6_main.c
+++ b/pimd/pim6_main.c
@@ -178,8 +178,7 @@ int main(int argc, char **argv, char **envp)
pim_route_map_init();
#endif
- /* pim_init(); */
- pim_cmd_init();
+ pim_init();
/*
* Initialize zclient "update" and "lookup" sockets
*/
diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c
index 09a82069a2..34cda25963 100644
--- a/pimd/pim_igmpv2.c
+++ b/pimd/pim_igmpv2.c
@@ -24,6 +24,7 @@
#include "pim_igmp.h"
#include "pim_igmpv2.h"
#include "pim_igmpv3.h"
+#include "pim_ssm.h"
#include "pim_str.h"
#include "pim_time.h"
#include "pim_util.h"
@@ -107,10 +108,13 @@ int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from,
{
struct interface *ifp = igmp->interface;
struct in_addr group_addr;
+ struct pim_interface *pim_ifp;
char group_str[INET_ADDRSTRLEN];
on_trace(__func__, igmp->interface, from);
+ pim_ifp = ifp->info;
+
if (igmp->mtrace_only)
return 0;
@@ -142,6 +146,23 @@ int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from,
}
/*
+ * RFC 4604
+ * section 2.2.1
+ * EXCLUDE mode does not apply to SSM addresses, and an SSM-aware router
+ * will ignore MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE requests in
+ * the SSM range.
+ */
+ if (pim_is_grp_ssm(pim_ifp->pim, group_addr)) {
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug(
+ "Ignoring IGMPv2 group record %pI4 from %s on %s exclude mode in SSM range",
+ &group_addr.s_addr, from_str, ifp->name);
+ }
+ return -1;
+ }
+
+
+ /*
* RFC 3376
* 7.3.2. In the Presence of Older Version Group Members
*
diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c
index 027a79da98..b6114f9ead 100644
--- a/pimd/pim_igmpv3.c
+++ b/pimd/pim_igmpv3.c
@@ -32,6 +32,7 @@
#include "pim_time.h"
#include "pim_zebra.h"
#include "pim_oil.h"
+#include "pim_ssm.h"
static void group_retransmit_timer_on(struct gm_group *group);
static long igmp_group_timer_remain_msec(struct gm_group *group);
@@ -1820,6 +1821,64 @@ void igmp_v3_recv_query(struct gm_sock *igmp, const char *from_str,
} /* s_flag is clear: timer updates */
}
+static bool igmp_pkt_grp_addr_ok(struct interface *ifp, const char *from_str,
+ struct in_addr grp, int rec_type)
+{
+ struct pim_interface *pim_ifp;
+ struct in_addr grp_addr;
+
+ pim_ifp = ifp->info;
+
+ /* determine filtering status for group */
+ if (pim_is_group_filtered(pim_ifp, &grp)) {
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug(
+ "Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s",
+ &grp.s_addr, from_str, ifp->name,
+ pim_ifp->boundary_oil_plist);
+ }
+ return false;
+ }
+
+ /*
+ * If we receive a igmp report with the group in 224.0.0.0/24
+ * then we should ignore it
+ */
+
+ grp_addr.s_addr = ntohl(grp.s_addr);
+
+ if (pim_is_group_224_0_0_0_24(grp_addr)) {
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug(
+ "Ignoring IGMPv3 group record %pI4 from %s on %s group range falls in 224.0.0.0/24",
+ &grp.s_addr, from_str, ifp->name);
+ }
+ return false;
+ }
+
+ /*
+ * RFC 4604
+ * section 2.2.1
+ * EXCLUDE mode does not apply to SSM addresses, and an SSM-aware router
+ * will ignore MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE requests in
+ * the SSM range.
+ */
+ if (pim_is_grp_ssm(pim_ifp->pim, grp)) {
+ switch (rec_type) {
+ case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
+ case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug(
+ "Ignoring IGMPv3 group record %pI4 from %s on %s exclude mode in SSM range",
+ &grp.s_addr, from_str, ifp->name);
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
+
int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
const char *from_str, char *igmp_msg, int igmp_msg_len)
{
@@ -1828,14 +1887,10 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
uint8_t *report_pastend = (uint8_t *)igmp_msg + igmp_msg_len;
struct interface *ifp = igmp->interface;
int i;
- int local_ncb = 0;
- struct pim_interface *pim_ifp;
if (igmp->mtrace_only)
return 0;
- pim_ifp = igmp->interface->info;
-
if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
zlog_warn(
"Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
@@ -1880,9 +1935,6 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
int rec_auxdatalen;
int rec_num_sources;
int j;
- struct prefix lncb;
- struct prefix g;
- bool filtered = false;
if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE)
> report_pastend) {
@@ -1940,31 +1992,7 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
} /* for (sources) */
- lncb.family = AF_INET;
- lncb.u.prefix4.s_addr = 0x000000E0;
- lncb.prefixlen = 24;
-
- g.family = AF_INET;
- g.u.prefix4 = rec_group;
- g.prefixlen = IPV4_MAX_BITLEN;
-
- /* determine filtering status for group */
- filtered = pim_is_group_filtered(ifp->info, &rec_group);
-
- if (PIM_DEBUG_IGMP_PACKETS && filtered)
- zlog_debug(
- "Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s",
- &rec_group, from_str, ifp->name,
- pim_ifp->boundary_oil_plist);
-
- /*
- * If we receive a igmp report with the group in 224.0.0.0/24
- * then we should ignore it
- */
- if (prefix_match(&lncb, &g))
- local_ncb = 1;
-
- if (!local_ncb && !filtered)
+ if (igmp_pkt_grp_addr_ok(ifp, from_str, rec_group, rec_type))
switch (rec_type) {
case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
igmpv3_report_isin(igmp, from, rec_group,
@@ -2004,7 +2032,6 @@ int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from,
group_record +=
8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
- local_ncb = 0;
} /* for (group records) */
diff --git a/pimd/pim_join.c b/pimd/pim_join.c
index 929beea26b..2c11d5d13f 100644
--- a/pimd/pim_join.c
+++ b/pimd/pim_join.c
@@ -43,14 +43,10 @@
#include "pim_util.h"
#include "pim_ssm.h"
-static void on_trace(const char *label, struct interface *ifp,
- struct in_addr src)
+static void on_trace(const char *label, struct interface *ifp, pim_addr src)
{
- if (PIM_DEBUG_PIM_TRACE) {
- char src_str[INET_ADDRSTRLEN];
- pim_inet4_dump("<src?>", src, src_str, sizeof(src_str));
- zlog_debug("%s: from %s on %s", label, src_str, ifp->name);
- }
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug("%s: from %pPA on %s", label, &src, ifp->name);
}
static void recv_join(struct interface *ifp, struct pim_neighbor *neigh,
@@ -422,6 +418,7 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
size_t packet_left = 0;
size_t packet_size = 0;
size_t group_size = 0;
+ pim_addr rpf_addr;
if (rpf->source_nexthop.interface)
pim_ifp = rpf->source_nexthop.interface->info;
@@ -430,8 +427,9 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
return -1;
}
- on_trace(__func__, rpf->source_nexthop.interface,
- rpf->rpf_addr.u.prefix4);
+ rpf_addr = pim_addr_from_prefix(&rpf->rpf_addr);
+
+ on_trace(__func__, rpf->source_nexthop.interface, rpf_addr);
if (!pim_ifp) {
zlog_warn("%s: multicast not enabled on interface %s", __func__,
@@ -439,15 +437,12 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
return -1;
}
- if (rpf->rpf_addr.u.prefix4.s_addr == INADDR_ANY) {
- if (PIM_DEBUG_PIM_J_P) {
- char dst_str[INET_ADDRSTRLEN];
- pim_inet4_dump("<dst?>", rpf->rpf_addr.u.prefix4,
- dst_str, sizeof(dst_str));
- zlog_debug("%s: upstream=%s is myself on interface %s",
- __func__, dst_str,
- rpf->source_nexthop.interface->name);
- }
+ if (pim_addr_is_any(rpf_addr)) {
+ if (PIM_DEBUG_PIM_J_P)
+ zlog_debug(
+ "%s: upstream=%pPA is myself on interface %s",
+ __func__, &rpf_addr,
+ rpf->source_nexthop.interface->name);
return 0;
}
@@ -468,8 +463,8 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
memset(msg, 0, sizeof(*msg));
- pim_msg_addr_encode_ipv4_ucast((uint8_t *)&msg->addr,
- rpf->rpf_addr.u.prefix4);
+ pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
+ rpf_addr);
msg->reserved = 0;
msg->holdtime = htons(PIM_JP_HOLDTIME);
@@ -485,15 +480,11 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
packet_left = rpf->source_nexthop.interface->mtu - 24;
packet_left -= packet_size;
}
- if (PIM_DEBUG_PIM_J_P) {
- char dst_str[INET_ADDRSTRLEN];
- pim_inet4_dump("<dst?>", rpf->rpf_addr.u.prefix4,
- dst_str, sizeof(dst_str));
+ if (PIM_DEBUG_PIM_J_P)
zlog_debug(
- "%s: sending (G)=%pPAs to upstream=%s on interface %s",
- __func__, &group->group, dst_str,
+ "%s: sending (G)=%pPAs to upstream=%pPA on interface %s",
+ __func__, &group->group, &rpf_addr,
rpf->source_nexthop.interface->name);
- }
group_size = pim_msg_get_jp_group_size(group->sources);
if (group_size > packet_left) {
@@ -513,8 +504,8 @@ int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
msg = (struct pim_jp *)pim_msg;
memset(msg, 0, sizeof(*msg));
- pim_msg_addr_encode_ipv4_ucast((uint8_t *)&msg->addr,
- rpf->rpf_addr.u.prefix4);
+ pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
+ rpf_addr);
msg->reserved = 0;
msg->holdtime = htons(PIM_JP_HOLDTIME);
diff --git a/pimd/pim_nht.c b/pimd/pim_nht.c
index 4e7aad99f1..35c8469090 100644
--- a/pimd/pim_nht.c
+++ b/pimd/pim_nht.c
@@ -993,12 +993,14 @@ int pim_ecmp_nexthop_lookup(struct pim_instance *pim,
if (!nbr && !if_is_loopback(ifp)) {
if (i == mod_val)
mod_val++;
- i++;
if (PIM_DEBUG_PIM_NHT)
zlog_debug(
- "%s: NBR not found on input interface %s(%s) (RPF for source %pPA)",
- __func__, ifp->name,
- pim->vrf->name, &src_addr);
+ "%s: NBR (%pFXh) not found on input interface %s(%s) (RPF for source %pPA)",
+ __func__,
+ &nexthop_tab[i].nexthop_addr,
+ ifp->name, pim->vrf->name,
+ &src_addr);
+ i++;
continue;
}
}
diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c
index 3980e4828d..616cad16c6 100644
--- a/pimd/pim_pim.c
+++ b/pimd/pim_pim.c
@@ -523,7 +523,8 @@ static uint16_t ip_id = 0;
static int pim_msg_send_frame(int fd, char *buf, size_t len,
- struct sockaddr *dst, size_t salen)
+ struct sockaddr *dst, size_t salen,
+ const char *ifname)
{
struct ip *ip = (struct ip *)buf;
@@ -539,8 +540,8 @@ static int pim_msg_send_frame(int fd, char *buf, size_t len,
ip->ip_len = htons(sendlen);
ip->ip_off = htons(offset | IP_MF);
- if (pim_msg_send_frame(fd, buf, sendlen, dst, salen)
- == 0) {
+ if (pim_msg_send_frame(fd, buf, sendlen, dst, salen,
+ ifname) == 0) {
struct ip *ip2 = (struct ip *)(buf + newlen1);
size_t newlen2 = len - sendlen;
sendlen = newlen2 + hdrsize;
@@ -549,7 +550,8 @@ static int pim_msg_send_frame(int fd, char *buf, size_t len,
ip2->ip_len = htons(sendlen);
ip2->ip_off = htons(offset + (newlen1 >> 3));
return pim_msg_send_frame(fd, (char *)ip2,
- sendlen, dst, salen);
+ sendlen, dst, salen,
+ ifname);
}
}
@@ -559,9 +561,9 @@ static int pim_msg_send_frame(int fd, char *buf, size_t len,
pim_inet4_dump("<dst?>", ip->ip_dst, dst_str,
sizeof(dst_str));
zlog_warn(
- "%s: sendto() failure to %s: fd=%d msg_size=%zd: errno=%d: %s",
- __func__, dst_str, fd, len, errno,
- safe_strerror(errno));
+ "%s: sendto() failure to %s: iface=%s fd=%d msg_size=%zd: errno=%d: %s",
+ __func__, dst_str, ifname, fd, len,
+ errno, safe_strerror(errno));
}
return -1;
}
@@ -643,7 +645,7 @@ int pim_msg_send(int fd, pim_addr src, pim_addr dst, uint8_t *pim_msg,
}
pim_msg_send_frame(fd, (char *)buffer, sendlen, (struct sockaddr *)&to,
- tolen);
+ tolen, ifname);
return 0;
}
diff --git a/pimd/pim_tlv.c b/pimd/pim_tlv.c
index 86403dd54a..028514659b 100644
--- a/pimd/pim_tlv.c
+++ b/pimd/pim_tlv.c
@@ -127,11 +127,7 @@ int pim_encode_addr_ucast(uint8_t *buf, pim_addr addr)
{
uint8_t *start = buf;
-#if PIM_IPV == 4
- *buf++ = PIM_MSG_ADDRESS_FAMILY_IPV4;
-#else
- *buf++ = PIM_MSG_ADDRESS_FAMILY_IPV6;
-#endif
+ *buf++ = PIM_MSG_ADDRESS_FAMILY;
*buf++ = 0;
memcpy(buf, &addr, sizeof(addr));
buf += sizeof(addr);
@@ -624,16 +620,15 @@ int pim_parse_addr_source(pim_sgaddr *sg, uint8_t *flags, const uint8_t *buf,
}
switch (family) {
- case PIM_MSG_ADDRESS_FAMILY_IPV4:
- if ((addr + sizeof(struct in_addr)) > pastend) {
+ case PIM_MSG_ADDRESS_FAMILY:
+ if ((addr + sizeof(sg->src)) > pastend) {
zlog_warn(
- "%s: IPv4 source address overflow: left=%td needed=%zu",
- __func__, pastend - addr,
- sizeof(struct in_addr));
+ "%s: IP source address overflow: left=%td needed=%zu",
+ __func__, pastend - addr, sizeof(sg->src));
return -3;
}
- memcpy(&sg->src, addr, sizeof(struct in_addr));
+ memcpy(&sg->src, addr, sizeof(sg->src));
/*
RFC 4601: 4.9.1 Encoded Source and Group Address Formats
@@ -642,27 +637,24 @@ int pim_parse_addr_source(pim_sgaddr *sg, uint8_t *flags, const uint8_t *buf,
The mask length MUST be equal to the mask length in bits for
the given Address Family and Encoding Type (32 for IPv4
- native
- and 128 for IPv6 native). A router SHOULD ignore any
- messages
- received with any other mask length.
+ native and 128 for IPv6 native). A router SHOULD ignore any
+ messages received with any other mask length.
*/
- if (mask_len != IPV4_MAX_BITLEN) {
- zlog_warn("%s: IPv4 bad source address mask: %d",
+ if (mask_len != PIM_MAX_BITLEN) {
+ zlog_warn("%s: IP bad source address mask: %d",
__func__, mask_len);
return -4;
}
- addr += sizeof(struct in_addr);
+ addr += sizeof(sg->src);
break;
- default: {
+ default:
zlog_warn(
"%s: unknown source address encoding family=%d: %02x%02x%02x%02x",
__func__, family, buf[0], buf[1], buf[2], buf[3]);
return -5;
}
- }
return addr - buf;
}
diff --git a/tests/.gitignore b/tests/.gitignore
index 70d0ef6e0a..f00177abd8 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -1,6 +1,8 @@
*.log
*.sum
*.xml
+frr-northbound.proto
+frr_northbound*
.pytest_cache
/bgpd/test_aspath
/bgpd/test_bgp_table
diff --git a/tests/lib/test_grpc.py b/tests/lib/test_grpc.py
index 06ae6c05dd..2e292fadc9 100644
--- a/tests/lib/test_grpc.py
+++ b/tests/lib/test_grpc.py
@@ -1,8 +1,10 @@
import inspect
import os
import subprocess
-import pytest
+
import frrtest
+import pytest
+
class TestGRPC(object):
program = "./test_grpc"
@@ -15,9 +17,13 @@ class TestGRPC(object):
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
+ [frrtest.binpath(program)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
)
output, _ = proc.communicate()
self.exitcode = proc.wait()
if self.exitcode != 0:
+ print("OUTPUT:\n" + output.decode("ascii"))
raise frrtest.TestExitNonzero(self)
diff --git a/tests/topotests/analyze.py b/tests/topotests/analyze.py
index 888e706339..bdb2e56ee1 100755
--- a/tests/topotests/analyze.py
+++ b/tests/topotests/analyze.py
@@ -198,9 +198,12 @@ def main():
logging.critical("%s doesn't exist", args.results)
sys.exit(1)
ttfiles = [args.results]
+ elif os.path.exists("/tmp/topotests/topotests.xml"):
+ ttfiles.append("/tmp/topotests/topotests.xml")
- if not ttfiles and os.path.exists("/tmp/topotests.xml"):
- ttfiles.append("/tmp/topotests.xml")
+ if not ttfiles:
+ if os.path.exists("/tmp/topotests.xml"):
+ ttfiles.append("/tmp/topotests.xml")
for f in ttfiles:
m = re.match(r"tt-group-(\d+)/topotests.xml", f)
diff --git a/tests/topotests/grpc_basic/lib b/tests/topotests/grpc_basic/lib
new file mode 120000
index 0000000000..dc598c56dc
--- /dev/null
+++ b/tests/topotests/grpc_basic/lib
@@ -0,0 +1 @@
+../lib \ No newline at end of file
diff --git a/tests/topotests/grpc_basic/r1/zebra.conf b/tests/topotests/grpc_basic/r1/zebra.conf
new file mode 100644
index 0000000000..49a0911c53
--- /dev/null
+++ b/tests/topotests/grpc_basic/r1/zebra.conf
@@ -0,0 +1,8 @@
+log record-priority
+log timestamp precision 6
+log extended extlog
+ destination file ext-log.txt create
+ timestamp precision 6
+ structured-data code-location
+interface r1-eth0
+ ip address 192.168.1.1/24 \ No newline at end of file
diff --git a/tests/topotests/grpc_basic/r2/zebra.conf b/tests/topotests/grpc_basic/r2/zebra.conf
new file mode 100644
index 0000000000..20da1885d4
--- /dev/null
+++ b/tests/topotests/grpc_basic/r2/zebra.conf
@@ -0,0 +1,8 @@
+log record-priority
+log timestamp precision 6
+log extended extlog
+ destination file ext-log.txt create
+ timestamp precision 6
+ structured-data code-location
+interface r2-eth0
+ ip address 192.168.1.2/24
diff --git a/tests/topotests/grpc_basic/test_basic_grpc.py b/tests/topotests/grpc_basic/test_basic_grpc.py
new file mode 100644
index 0000000000..b6812a5afc
--- /dev/null
+++ b/tests/topotests/grpc_basic/test_basic_grpc.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+#
+# February 21 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, 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
+#
+"""
+test_basic_grpc.py: Test Basic gRPC.
+"""
+
+import logging
+import os
+import sys
+
+import pytest
+
+from lib.common_config import step
+from lib.micronet import commander
+from lib.topogen import Topogen, TopoRouter
+from lib.topolog import logger
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+
+GRPCP_ZEBRA = 50051
+GRPCP_STATICD = 50052
+GRPCP_BFDD = 50053
+GRPCP_ISISD = 50054
+GRPCP_OSPFD = 50055
+GRPCP_PIMD = 50056
+
+pytestmark = [
+ # pytest.mark.mgmtd -- Need a new non-protocol marker
+ # pytest.mark.bfdd,
+ # pytest.mark.isisd,
+ # pytest.mark.ospfd,
+ # pytest.mark.pimd,
+ pytest.mark.staticd,
+]
+
+script_path = os.path.realpath(os.path.join(CWD, "../lib/grpc-query.py"))
+
+try:
+ commander.cmd_raises([script_path, "--check"])
+except Exception:
+ pytest.skip(
+ "skipping; cannot create or import gRPC proto modules", allow_module_level=True
+ )
+
+
+@pytest.fixture(scope="module")
+def tgen(request):
+ "Setup/Teardown the environment and provide tgen argument to tests"
+ topodef = {"s1": ("r1", "r2")}
+ tgen = Topogen(topodef, request.module.__name__)
+
+ tgen.start_topology()
+ router_list = tgen.routers()
+
+ for rname, router in router_list.items():
+ router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf", f"-M grpc:{GRPCP_ZEBRA}")
+ router.load_config(TopoRouter.RD_STATIC, None, f"-M grpc:{GRPCP_STATICD}")
+ # router.load_config(TopoRouter.RD_BFD, None, f"-M grpc:{GRPCP_BFDD}")
+ # router.load_config(TopoRouter.RD_ISIS, None, f"-M grpc:{GRPCP_ISISD}")
+ # router.load_config(TopoRouter.RD_OSPF, None, f"-M grpc:{GRPCP_OSPFD}")
+ # router.load_config(TopoRouter.RD_PIM, None, f"-M grpc:{GRPCP_PIMD}")
+
+ tgen.start_router()
+ yield tgen
+
+ logging.info("Stopping all routers (no assert on error)")
+ tgen.stop_topology()
+
+
+# Let's not do this so we catch errors
+# Fixture that executes before each test
+@pytest.fixture(autouse=True)
+def skip_on_failure(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip("skipped because of previous test failure")
+
+
+# ===================
+# The tests functions
+# ===================
+
+
+def run_grpc_client(r, port, commands):
+ if not isinstance(commands, str):
+ commands = "\n".join(commands) + "\n"
+ if not commands.endswith("\n"):
+ commands += "\n"
+ return r.cmd_raises([script_path, f"--port={port}"], stdin=commands)
+
+
+def test_connectivity(tgen):
+ r1 = tgen.gears["r1"]
+ output = r1.cmd_raises("ping -c1 192.168.1.2")
+ logging.info("ping output: %s", output)
+
+
+def test_capabilities(tgen):
+ r1 = tgen.gears["r1"]
+ output = run_grpc_client(r1, GRPCP_ZEBRA, "GETCAP")
+ logging.info("grpc output: %s", output)
+
+
+def test_get_config(tgen):
+ nrepeat = 5
+ r1 = tgen.gears["r1"]
+
+ step("'GET' inteface config 10 times, once per invocation")
+
+ for i in range(0, nrepeat):
+ output = run_grpc_client(r1, GRPCP_ZEBRA, "GET,/frr-interface:lib")
+ logging.info("[iteration %s]: grpc GET output: %s", i, output)
+
+ step(f"'GET' YANG {nrepeat} times in one invocation")
+ commands = ["GET,/frr-interface:lib" for _ in range(0, 10)]
+ output = run_grpc_client(r1, GRPCP_ZEBRA, commands)
+ logging.info("grpc GET*{%d} output: %s", nrepeat, output)
+
+
+def test_get_vrf_config(tgen):
+ r1 = tgen.gears["r1"]
+
+ step("'GET' get VRF config")
+
+ output = run_grpc_client(r1, GRPCP_ZEBRA, "GET,/frr-vrf:lib")
+ logging.info("grpc GET /frr-vrf:lib output: %s", output)
+
+
+def test_shutdown_checks(tgen):
+ # Start a process rnuning that will fetch bunches of data then shut the routers down
+ # and check for cores.
+ nrepeat = 100
+ r1 = tgen.gears["r1"]
+ commands = ["GET,/frr-interface:lib" for _ in range(0, nrepeat)]
+ p = r1.popen([script_path, f"--port={GRPCP_ZEBRA}"] + commands)
+ import time
+
+ time.sleep(1)
+ try:
+ for r in tgen.routers().values():
+ r.net.stopRouter()
+ r.net.checkRouterCores()
+ finally:
+ if p:
+ p.terminate()
+ p.wait()
+
+
+# Memory leak test template
+# Not compatible with the shutdown check above
+def _test_memory_leak(tgen):
+ "Run the memory leak test and report results."
+
+ if not tgen.is_memleak_enabled():
+ pytest.skip("Memory leak test/report is disabled")
+
+ tgen.report_memory_leaks()
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/lib/grpc-query.py b/tests/topotests/lib/grpc-query.py
new file mode 100755
index 0000000000..61f01c36bb
--- /dev/null
+++ b/tests/topotests/lib/grpc-query.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 eval: (blacken-mode 1) -*-
+#
+# February 22 2022, Christian Hopps <chopps@labn.net>
+#
+# Copyright (c) 2022, LabN Consulting, L.L.C.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import argparse
+import logging
+import os
+import sys
+
+import pytest
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+
+# This is painful but works if you have installed grpc and grpc_tools would be *way*
+# better if we actually built and installed these but ... python packaging.
+try:
+ import grpc
+ import grpc_tools
+
+ from micronet import commander
+
+ commander.cmd_raises(f"cp {CWD}/../../../grpc/frr-northbound.proto .")
+ commander.cmd_raises(
+ f"python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I . frr-northbound.proto"
+ )
+except Exception as error:
+ logging.error("can't create proto definition modules %s", error)
+ raise
+
+try:
+ sys.path[0:0] = "."
+ import frr_northbound_pb2
+ import frr_northbound_pb2_grpc
+
+ # Would be nice if compiling the modules internally from the source worked
+ # # import grpc_tools.protoc
+ # # proto_include = pkg_resources.resource_filename("grpc_tools", "_proto")
+ # from grpc_tools.protoc import _proto_file_to_module_name, _protos_and_services
+ # try:
+ # frr_northbound_pb2, frr_northbound_pb2_grpc = _protos_and_services(
+ # "frr_northbound.proto"
+ # )
+ # finally:
+ # os.chdir(CWD)
+except Exception as error:
+ logging.error("can't import proto definition modules %s", error)
+ raise
+
+
+class GRPCClient:
+ def __init__(self, server, port):
+ self.channel = grpc.insecure_channel("{}:{}".format(server, port))
+ self.stub = frr_northbound_pb2_grpc.NorthboundStub(self.channel)
+
+ def get_capabilities(self):
+ request = frr_northbound_pb2.GetCapabilitiesRequest()
+ response = "NONE"
+ try:
+ response = self.stub.GetCapabilities(request)
+ except Exception as error:
+ logging.error("Got exception from stub: %s", error)
+
+ logging.debug("GRPC Capabilities: %s", response)
+ return response
+
+ def get(self, xpath):
+ request = frr_northbound_pb2.GetRequest()
+ request.path.append(xpath)
+ request.type = frr_northbound_pb2.GetRequest.ALL
+ request.encoding = frr_northbound_pb2.XML
+ xml = ""
+ for r in self.stub.Get(request):
+ logging.info('GRPC Get path: "%s" value: %s', request.path, r)
+ xml += str(r.data.data)
+ return xml
+
+
+def next_action(action_list=None):
+ "Get next action from list or STDIN"
+ if action_list:
+ for action in action_list:
+ yield action
+ else:
+ while True:
+ try:
+ action = input("")
+ if not action:
+ break
+ yield action.strip()
+ except EOFError:
+ break
+
+
+def main(*args):
+ parser = argparse.ArgumentParser(description="gRPC Client")
+ parser.add_argument(
+ "-s", "--server", default="localhost", help="gRPC Server Address"
+ )
+ parser.add_argument(
+ "-p", "--port", type=int, default=50051, help="gRPC Server TCP Port"
+ )
+ parser.add_argument("-v", "--verbose", action="store_true", help="be verbose")
+ parser.add_argument("--check", action="store_true", help="check runable")
+ parser.add_argument("actions", nargs="*", help="GETCAP|GET,xpath")
+ args = parser.parse_args(*args)
+
+ level = logging.DEBUG if args.verbose else logging.INFO
+ logging.basicConfig(
+ level=level,
+ format="%(asctime)s %(levelname)s: GRPC-CLI-CLIENT: %(name)s %(message)s",
+ )
+
+ if args.check:
+ sys.exit(0)
+
+ c = GRPCClient(args.server, args.port)
+
+ for action in next_action(args.actions):
+ action = action.casefold()
+ logging.info("GOT ACTION: %s", action)
+ if action == "getcap":
+ caps = c.get_capabilities()
+ print("Capabilities:", caps)
+ elif action.startswith("get,"):
+ # Print Interface State and Config
+ _, xpath = action.split(",", 1)
+ print("Get XPath: ", xpath)
+ xml = c.get(xpath)
+ print("{}: {}".format(xpath, xml))
+ # for _ in range(0, 1):
+
+
+if __name__ == "__main__":
+ main()