summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/user/pbr.rst107
-rw-r--r--lib/nexthop_group.c54
-rw-r--r--lib/nexthop_group.h3
-rw-r--r--pbrd/pbr_nht.c54
-rw-r--r--pbrd/pbr_nht.h2
-rw-r--r--pbrd/pbr_vty.c159
-rw-r--r--tests/topotests/all-protocol-startup/r1/pbrd.conf10
-rwxr-xr-xtests/topotests/all-protocol-startup/test_all_protocol_startup.py1
-rw-r--r--tests/topotests/lib/topogen.py2
-rw-r--r--tests/topotests/lib/topotest.py1
-rw-r--r--tests/topotests/pbr-topo1/r1/pbr-interface.json12
-rw-r--r--tests/topotests/pbr-topo1/r1/pbr-map.json60
-rw-r--r--tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json58
-rw-r--r--tests/topotests/pbr-topo1/r1/pbrd.conf33
-rw-r--r--tests/topotests/pbr-topo1/r1/zebra.conf11
-rwxr-xr-xtests/topotests/pbr-topo1/test_pbr_topo1.py180
16 files changed, 737 insertions, 10 deletions
diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst
index b9b28baced..149949e863 100644
--- a/doc/user/pbr.rst
+++ b/doc/user/pbr.rst
@@ -56,10 +56,36 @@ listing of ECMP nexthops used to forward packets for when a pbr-map is matched.
Showing Nexthop Group Information
---------------------------------
-.. clicmd:: show pbr nexthop-groups [NAME]
+.. clicmd:: show pbr nexthop-groups [NAME] [json]
Display information on a PBR nexthop-group. If ``NAME`` is omitted, all
- nexthop groups are shown.
+ nexthop groups are shown. Setting ``json`` will provide the same
+ information in an array of objects which obey the schema below:
+
+ +-----------+----------------------------+---------+
+ | Key | Description | Type |
+ +===========+============================+=========+
+ | id | Unique ID | Integer |
+ +-----------+----------------------------+---------+
+ | name | Name of this group | String |
+ +-----------+----------------------------+---------+
+ | valid | Is this group well-formed? | Boolean |
+ +-----------+----------------------------+---------+
+ | installed | ... and is it installed? | Boolean |
+ +-----------+----------------------------+---------+
+ | nexthops | Nexthops within this group | Array |
+ +-----------+----------------------------+---------+
+
+ Each element within ``nexthops`` describes a single target within this
+ group, and its structure is described by the JSON below:
+
+ +---------+------------------------------+---------+
+ | Key | Description | Type |
+ +=========+==============================+=========+
+ | nexthop | Name of this nexthop | String |
+ +---------+------------------------------+---------+
+ | valid | Is this nexthop well-formed? | Boolean |
+ +---------+------------------------------+---------+
.. _pbr-maps:
@@ -115,11 +141,68 @@ end destination.
Not supported with NETNS VRF backend.
-.. clicmd:: show pbr map [NAME] [detail]
+.. clicmd:: show pbr map [NAME] [detail|json]
Display pbr maps either all or by ``NAME``. If ``detail`` is set, it will
give information about the rules unique ID used internally and some extra
debugging information about install state for the nexthop/nexthop group.
+ Setting ``json`` will provide the same information in an array of objects
+ which obey the schema below:
+
+ +----------+--------------------------------+---------+
+ | Key | Description | Type |
+ +==========+================================+=========+
+ | name | Map name | String |
+ +----------+--------------------------------+---------+
+ | valid | Is the map well-formed? | Boolean |
+ +----------+--------------------------------+---------+
+ | policies | Rules to match packets against | Array |
+ +----------+--------------------------------+---------+
+
+ Each element of the ``policies`` array is composed of a handful of objects
+ representing the policies associated with this map. Each policy is
+ described as below (not all fields are required):
+
+ +-----------------+-------------------------------------------+---------+
+ | Key | Description | Type |
+ +=================+===========================================+=========+
+ | id | Unique ID | Integer |
+ +-----------------+-------------------------------------------+---------+
+ | sequenceNumber | Order of this policy within the map | Integer |
+ +-----------------+-------------------------------------------+---------+
+ | ruleNumber | Rule number to install into | Integer |
+ +-----------------+-------------------------------------------+---------+
+ | vrfUnchanged | Use interface's VRF | Boolean |
+ +-----------------+-------------------------------------------+---------+
+ | installed | Is this policy installed? | Boolean |
+ +-----------------+-------------------------------------------+---------+
+ | installedReason | Why (or why not?) | String |
+ +-----------------+-------------------------------------------+---------+
+ | matchSrc | Match packets with this source address | String |
+ +-----------------+-------------------------------------------+---------+
+ | matchDst | ... or with this destination address | String |
+ +-----------------+-------------------------------------------+---------+
+ | matchMark | ... or with this marker | Integer |
+ +-----------------+-------------------------------------------+---------+
+ | vrfName | Associated VRF (if relevant) | String |
+ +-----------------+-------------------------------------------+---------+
+ | nexthopGroup | This policy's nexthop group (if relevant) | Object |
+ +-----------------+-------------------------------------------+---------+
+
+ Finally, the ``nexthopGroup`` object above cotains information we know
+ about the configured nexthop for this policy:
+
+ +---------------------+--------------------------------------+---------+
+ | Key | Description | Type |
+ +=====================+======================================+=========+
+ | tableId | Nexthop table ID | Integer |
+ +---------------------+--------------------------------------+---------+
+ | name | Name of the nexthop group | String |
+ +---------------------+--------------------------------------+---------+
+ | installed | Is this nexthop group installed? | Boolean |
+ +---------------------+--------------------------------------+---------+
+ | installedInternally | Do we think this group is installed? | Integer |
+ +---------------------+--------------------------------------+---------+
.. _pbr-policy:
@@ -141,6 +224,24 @@ causes the policy to be installed into the kernel.
even if one is on the master. Each must have the PBR map explicitly added
to the interface.
+.. clicmd:: show pbr interface [NAME] [json]
+
+ Enumerates all interfaces which ``pbrd`` is keeping track of. Passing
+ ``json`` will return an array of interfaces; each returned interface will
+ adhere to the JSON schema below:
+
+ +--------+----------------------------+---------+
+ | Key | Description | Type |
+ +========+============================+=========+
+ | name | Interface name | String |
+ +--------+----------------------------+---------+
+ | index | Device Index | Integer |
+ +--------+----------------------------+---------+
+ | policy | PBR map for this interface | String |
+ +--------+----------------------------+---------+
+ | valid | Is the map well-formed? | Boolean |
+ +--------+----------------------------+---------+
+
.. _pbr-details:
PBR Details
diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c
index c23c57d2e1..c62096a126 100644
--- a/lib/nexthop_group.c
+++ b/lib/nexthop_group.c
@@ -996,6 +996,60 @@ void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh)
vty_out(vty, "\n");
}
+void nexthop_group_json_nexthop(json_object *j, struct nexthop *nh)
+{
+ char buf[100];
+ struct vrf *vrf;
+
+ switch (nh->type) {
+ case NEXTHOP_TYPE_IFINDEX:
+ json_object_string_add(j, "nexthop",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV4:
+ json_object_string_add(j, "nexthop", inet_ntoa(nh->gate.ipv4));
+ break;
+ case NEXTHOP_TYPE_IPV4_IFINDEX:
+ json_object_string_add(j, "nexthop", inet_ntoa(nh->gate.ipv4));
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_IPV6:
+ json_object_string_add(
+ j, "nexthop",
+ inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf)));
+ break;
+ case NEXTHOP_TYPE_IPV6_IFINDEX:
+ json_object_string_add(
+ j, "nexthop",
+ inet_ntop(AF_INET6, &nh->gate.ipv6, buf, sizeof(buf)));
+ json_object_string_add(j, "vrfId",
+ ifindex2ifname(nh->ifindex, nh->vrf_id));
+ break;
+ case NEXTHOP_TYPE_BLACKHOLE:
+ break;
+ }
+
+ if (nh->vrf_id != VRF_DEFAULT) {
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ json_object_string_add(j, "targetVrf", vrf->name);
+ }
+
+ if (nh->nh_label && nh->nh_label->num_labels > 0) {
+ char buf[200];
+
+ mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label,
+ buf, sizeof(buf), 0);
+ json_object_string_add(j, "label", buf);
+ }
+
+ if (nh->weight)
+ json_object_int_add(j, "weight", nh->weight);
+
+ if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP))
+ json_object_int_add(j, "backupIdx", nh->backup_idx);
+}
+
static void nexthop_group_write_nexthop_internal(struct vty *vty,
struct nexthop_hold *nh)
{
diff --git a/lib/nexthop_group.h b/lib/nexthop_group.h
index 3a5a1299c1..9888dad982 100644
--- a/lib/nexthop_group.h
+++ b/lib/nexthop_group.h
@@ -22,6 +22,7 @@
#define __NEXTHOP_GROUP__
#include <vty.h>
+#include "json.h"
#ifdef __cplusplus
extern "C" {
@@ -136,6 +137,8 @@ extern struct nexthop_group_cmd *nhgc_find(const char *name);
extern void nexthop_group_write_nexthop(struct vty *vty, struct nexthop *nh);
+extern void nexthop_group_json_nexthop(json_object *j, struct nexthop *nh);
+
/* Return the number of nexthops in this nhg */
extern uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg);
extern uint8_t
diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c
index 2f3591ac8d..98be958fce 100644
--- a/pbrd/pbr_nht.c
+++ b/pbrd/pbr_nht.c
@@ -1030,8 +1030,22 @@ static void pbr_nht_show_nhg_nexthops(struct hash_bucket *b, void *data)
nexthop_group_write_nexthop(vty, pnhc->nexthop);
}
+static void pbr_nht_json_nhg_nexthops(struct hash_bucket *b, void *data)
+{
+ struct pbr_nexthop_cache *pnhc = b->data;
+ json_object *all_hops = data;
+ json_object *this_hop;
+
+ this_hop = json_object_new_object();
+ nexthop_group_json_nexthop(this_hop, pnhc->nexthop);
+ json_object_boolean_add(this_hop, "valid", pnhc->valid);
+
+ json_object_array_add(all_hops, this_hop);
+}
+
struct pbr_nht_show {
struct vty *vty;
+ json_object *json;
const char *name;
};
@@ -1051,6 +1065,36 @@ static void pbr_nht_show_nhg(struct hash_bucket *b, void *data)
hash_iterate(pnhgc->nhh, pbr_nht_show_nhg_nexthops, vty);
}
+static void pbr_nht_json_nhg(struct hash_bucket *b, void *data)
+{
+ struct pbr_nexthop_group_cache *pnhgc = b->data;
+ struct pbr_nht_show *pns = data;
+ json_object *j, *this_group, *group_hops;
+
+ if (pns->name && strcmp(pns->name, pnhgc->name) != 0)
+ return;
+
+ j = pns->json;
+ this_group = json_object_new_object();
+
+ if (!j || !this_group)
+ return;
+
+ json_object_int_add(this_group, "id", pnhgc->table_id);
+ json_object_string_add(this_group, "name", pnhgc->name);
+ json_object_boolean_add(this_group, "valid", pnhgc->valid);
+ json_object_boolean_add(this_group, "installed", pnhgc->installed);
+
+ group_hops = json_object_new_array();
+
+ if (group_hops) {
+ hash_iterate(pnhgc->nhh, pbr_nht_json_nhg_nexthops, group_hops);
+ json_object_object_add(this_group, "nexthops", group_hops);
+ }
+
+ json_object_array_add(j, this_group);
+}
+
void pbr_nht_show_nexthop_group(struct vty *vty, const char *name)
{
struct pbr_nht_show pns;
@@ -1061,6 +1105,16 @@ void pbr_nht_show_nexthop_group(struct vty *vty, const char *name)
hash_iterate(pbr_nhg_hash, pbr_nht_show_nhg, &pns);
}
+void pbr_nht_json_nexthop_group(json_object *j, const char *name)
+{
+ struct pbr_nht_show pns;
+
+ pns.name = name;
+ pns.json = j;
+
+ hash_iterate(pbr_nhg_hash, pbr_nht_json_nhg, &pns);
+}
+
void pbr_nht_init(void)
{
pbr_nhg_hash = hash_create_size(
diff --git a/pbrd/pbr_nht.h b/pbrd/pbr_nht.h
index 2533942547..cbcf71d2f5 100644
--- a/pbrd/pbr_nht.h
+++ b/pbrd/pbr_nht.h
@@ -24,6 +24,7 @@
#include <lib/nexthop_group.h>
#include "pbr_map.h"
+#include "json.h"
#define PBR_NHC_NAMELEN PBR_MAP_NAMELEN + 10
@@ -112,6 +113,7 @@ extern char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno,
char *buffer);
extern void pbr_nht_show_nexthop_group(struct vty *vty, const char *name);
+extern void pbr_nht_json_nexthop_group(json_object *j, const char *name);
/*
* When we get a callback from zebra about a nexthop changing
diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c
index a52c2d1e30..54029206cc 100644
--- a/pbrd/pbr_vty.c
+++ b/pbrd/pbr_vty.c
@@ -27,6 +27,7 @@
#include "nexthop_group.h"
#include "nexthop_group_private.h"
#include "log.h"
+#include "json.h"
#include "debug.h"
#include "pbr.h"
@@ -590,6 +591,61 @@ static void vty_show_pbrms(struct vty *vty,
}
}
+static void vty_json_pbrms(json_object *j, struct vty *vty,
+ const struct pbr_map_sequence *pbrms)
+{
+ json_object *jpbrm, *nexthop_group;
+ char *nhg_name = pbrms->nhgrp_name ? pbrms->nhgrp_name
+ : pbrms->internal_nhg_name;
+ char buf[PREFIX_STRLEN];
+ char rbuf[64];
+
+ jpbrm = json_object_new_object();
+
+ json_object_int_add(jpbrm, "id", pbrms->unique);
+
+ if (pbrms->reason)
+ pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf));
+
+ json_object_int_add(jpbrm, "sequenceNumber", pbrms->seqno);
+ json_object_int_add(jpbrm, "ruleNumber", pbrms->ruleno);
+ json_object_boolean_add(jpbrm, "vrfUnchanged", pbrms->vrf_unchanged);
+ json_object_boolean_add(jpbrm, "installed",
+ pbr_nht_get_installed(nhg_name));
+ json_object_string_add(jpbrm, "installedReason",
+ pbrms->reason ? rbuf : "Valid");
+
+ if (nhg_name) {
+ nexthop_group = json_object_new_object();
+
+ json_object_int_add(nexthop_group, "tableId",
+ pbr_nht_get_table(nhg_name));
+ json_object_string_add(nexthop_group, "name", nhg_name);
+ json_object_boolean_add(nexthop_group, "installed",
+ pbr_nht_get_installed(nhg_name));
+ json_object_int_add(nexthop_group, "installedInternally",
+ pbrms->nhs_installed);
+
+ json_object_object_add(jpbrm, "nexthopGroup", nexthop_group);
+ }
+
+ if (pbrms->vrf_lookup)
+ json_object_string_add(jpbrm, "vrfName", pbrms->vrf_name);
+
+ if (pbrms->src)
+ json_object_string_add(
+ jpbrm, "matchSrc",
+ prefix2str(pbrms->src, buf, sizeof(buf)));
+ if (pbrms->dst)
+ json_object_string_add(
+ jpbrm, "matchDst",
+ prefix2str(pbrms->dst, buf, sizeof(buf)));
+ if (pbrms->mark)
+ json_object_int_add(jpbrm, "matchMark", pbrms->mark);
+
+ json_object_array_add(j, jpbrm);
+}
+
static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm,
bool detail)
{
@@ -603,54 +659,121 @@ static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm,
vty_show_pbrms(vty, pbrms, detail);
}
+static void vty_json_pbr_map(json_object *j, struct vty *vty,
+ const struct pbr_map *pbrm)
+{
+ struct pbr_map_sequence *pbrms;
+ struct listnode *node;
+ json_object *jpbrms;
+
+ json_object_string_add(j, "name", pbrm->name);
+ json_object_boolean_add(j, "valid", pbrm->valid);
+
+ jpbrms = json_object_new_array();
+
+ for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms))
+ vty_json_pbrms(jpbrms, vty, pbrms);
+
+ json_object_object_add(j, "policies", jpbrms);
+}
+
DEFPY (show_pbr_map,
show_pbr_map_cmd,
- "show pbr map [NAME$name] [detail$detail]",
+ "show pbr map [NAME$name] [detail$detail|json$json]",
SHOW_STR
PBR_STR
"PBR Map\n"
"PBR Map Name\n"
- "Detailed information\n")
+ "Detailed information\n"
+ JSON_STR)
{
struct pbr_map *pbrm;
+ json_object *j = NULL;
+
+ if (json)
+ j = json_object_new_array();
RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) {
+ json_object *this_map = NULL;
if (name && strcmp(name, pbrm->name) != 0)
continue;
+ if (j)
+ this_map = json_object_new_object();
+
+ if (this_map) {
+ vty_json_pbr_map(this_map, vty, pbrm);
+
+ json_object_array_add(j, this_map);
+ continue;
+ }
+
vty_show_pbr_map(vty, pbrm, detail);
}
+
+ if (j) {
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ j, JSON_C_TO_STRING_PRETTY));
+ json_object_free(j);
+ }
+
return CMD_SUCCESS;
}
DEFPY(show_pbr_nexthop_group,
show_pbr_nexthop_group_cmd,
- "show pbr nexthop-groups [WORD$word]",
+ "show pbr nexthop-groups [WORD$word] [json$json]",
SHOW_STR
PBR_STR
"Nexthop Groups\n"
- "Optional Name of the nexthop group\n")
+ "Optional Name of the nexthop group\n"
+ JSON_STR)
{
- pbr_nht_show_nexthop_group(vty, word);
+ json_object *j = NULL;
+
+ if (json)
+ j = json_object_new_array();
+
+ if (j) {
+ pbr_nht_json_nexthop_group(j, word);
+
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ j, JSON_C_TO_STRING_PRETTY));
+
+ json_object_free(j);
+ } else
+ pbr_nht_show_nexthop_group(vty, word);
+
return CMD_SUCCESS;
}
DEFPY (show_pbr_interface,
show_pbr_interface_cmd,
- "show pbr interface [NAME$name]",
+ "show pbr interface [NAME$name] [json$json]",
SHOW_STR
PBR_STR
"PBR Interface\n"
- "PBR Interface Name\n")
+ "PBR Interface Name\n"
+ JSON_STR)
{
struct interface *ifp;
struct vrf *vrf;
struct pbr_interface *pbr_ifp;
+ json_object *j = NULL;
+
+ if (json)
+ j = json_object_new_array();
RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) {
FOR_ALL_INTERFACES(vrf, ifp) {
struct pbr_map *pbrm;
+ json_object *this_iface = NULL;
+
+ if (j)
+ this_iface = json_object_new_object();
if (!ifp->info)
continue;
@@ -664,6 +787,21 @@ DEFPY (show_pbr_interface,
continue;
pbrm = pbrm_find(pbr_ifp->mapname);
+
+ if (this_iface) {
+ json_object_string_add(this_iface, "name",
+ ifp->name);
+ json_object_int_add(this_iface, "index",
+ ifp->ifindex);
+ json_object_string_add(this_iface, "policy",
+ pbr_ifp->mapname);
+ json_object_boolean_add(this_iface, "valid",
+ pbrm);
+
+ json_object_array_add(j, this_iface);
+ continue;
+ }
+
vty_out(vty, " %s(%d) with pbr-policy %s", ifp->name,
ifp->ifindex, pbr_ifp->mapname);
if (!pbrm)
@@ -672,6 +810,13 @@ DEFPY (show_pbr_interface,
}
}
+ if (j) {
+ vty_out(vty, "%s\n",
+ json_object_to_json_string_ext(
+ j, JSON_C_TO_STRING_PRETTY));
+ json_object_free(j);
+ }
+
return CMD_SUCCESS;
}
diff --git a/tests/topotests/all-protocol-startup/r1/pbrd.conf b/tests/topotests/all-protocol-startup/r1/pbrd.conf
new file mode 100644
index 0000000000..360fb13a1b
--- /dev/null
+++ b/tests/topotests/all-protocol-startup/r1/pbrd.conf
@@ -0,0 +1,10 @@
+log file pbrd.log
+
+nexthop-group A
+ nexthop 192.168.161.4
+!
+pbr-map FOO seq 10
+ match dst-ip 4.5.6.7/32
+ match src-ip 6.7.8.8/32
+ set nexthop-group A
+! \ No newline at end of file
diff --git a/tests/topotests/all-protocol-startup/test_all_protocol_startup.py b/tests/topotests/all-protocol-startup/test_all_protocol_startup.py
index fb211957a7..14e00b9664 100755
--- a/tests/topotests/all-protocol-startup/test_all_protocol_startup.py
+++ b/tests/topotests/all-protocol-startup/test_all_protocol_startup.py
@@ -123,6 +123,7 @@ def setup_module(module):
net['r%s' % i].loadConf('sharpd')
net['r%s' % i].loadConf('nhrpd', '%s/r%s/nhrpd.conf' % (thisDir, i))
net['r%s' % i].loadConf('babeld', '%s/r%s/babeld.conf' % (thisDir, i))
+ net['r%s' % i].loadConf('pbrd', '%s/r%s/pbrd.conf' % (thisDir, i))
net['r%s' % i].startRouter()
# For debugging after starting Quagga/FRR daemons, uncomment the next line
diff --git a/tests/topotests/lib/topogen.py b/tests/topotests/lib/topogen.py
index 673d65376f..414dc17874 100644
--- a/tests/topotests/lib/topogen.py
+++ b/tests/topotests/lib/topogen.py
@@ -556,6 +556,7 @@ class TopoRouter(TopoGear):
RD_BFD = 13
RD_SHARP = 14
RD_BABEL = 15
+ RD_PBRD = 16
RD = {
RD_ZEBRA: "zebra",
RD_RIP: "ripd",
@@ -572,6 +573,7 @@ class TopoRouter(TopoGear):
RD_BFD: "bfdd",
RD_SHARP: "sharpd",
RD_BABEL: "babeld",
+ RD_PBRD: "pbrd",
}
def __init__(self, tgen, cls, name, **params):
diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py
index b35606df8f..6262082193 100644
--- a/tests/topotests/lib/topotest.py
+++ b/tests/topotests/lib/topotest.py
@@ -871,6 +871,7 @@ class Router(Node):
"bfdd": 0,
"sharpd": 0,
"babeld": 0,
+ "pbrd": 0,
}
self.daemons_options = {"zebra": ""}
self.reportCores = True
diff --git a/tests/topotests/pbr-topo1/r1/pbr-interface.json b/tests/topotests/pbr-topo1/r1/pbr-interface.json
new file mode 100644
index 0000000000..452b24dcd7
--- /dev/null
+++ b/tests/topotests/pbr-topo1/r1/pbr-interface.json
@@ -0,0 +1,12 @@
+[
+ {
+ "name":"r1-eth1",
+ "policy":"EVA",
+ "valid":true
+ },
+ {
+ "name":"r1-eth2",
+ "policy":"DONNA",
+ "valid":true
+ }
+]
diff --git a/tests/topotests/pbr-topo1/r1/pbr-map.json b/tests/topotests/pbr-topo1/r1/pbr-map.json
new file mode 100644
index 0000000000..6b9eaa9ceb
--- /dev/null
+++ b/tests/topotests/pbr-topo1/r1/pbr-map.json
@@ -0,0 +1,60 @@
+[
+ {
+ "name":"DONNA",
+ "valid":true,
+ "policies":[
+ {
+ "id":3,
+ "sequenceNumber":5,
+ "ruleNumber":304,
+ "vrfUnchanged":false,
+ "installed":true,
+ "installedReason":"Valid",
+ "nexthopGroup":{
+ "tableId":10002,
+ "name":"C",
+ "installed":true,
+ "installedInternally":1
+ },
+ "matchSrc":"1.2.0.0\/16",
+ "matchDst":"3.4.5.0\/24"
+ }
+ ]
+ },
+ {
+ "name":"EVA",
+ "valid":true,
+ "policies":[
+ {
+ "id":1,
+ "sequenceNumber":5,
+ "ruleNumber":304,
+ "vrfUnchanged":false,
+ "installed":true,
+ "installedReason":"Valid",
+ "nexthopGroup":{
+ "tableId":10003,
+ "name":"EVA5",
+ "installed":true,
+ "installedInternally":1
+ },
+ "matchSrc":"4.5.6.7\/32"
+ },
+ {
+ "id":2,
+ "sequenceNumber":10,
+ "ruleNumber":309,
+ "vrfUnchanged":false,
+ "installed":true,
+ "installedReason":"Valid",
+ "nexthopGroup":{
+ "tableId":10000,
+ "name":"A",
+ "installed":true,
+ "installedInternally":1
+ },
+ "matchDst":"9.9.9.9\/32"
+ }
+ ]
+ }
+]
diff --git a/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json b/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json
new file mode 100644
index 0000000000..ff85438ad5
--- /dev/null
+++ b/tests/topotests/pbr-topo1/r1/pbr-nexthop-groups.json
@@ -0,0 +1,58 @@
+[
+ {
+ "id":10000,
+ "name":"A",
+ "valid":true,
+ "installed":true,
+ "nexthops":[
+ {
+ "nexthop":"192.168.2.2",
+ "valid":true
+ },
+ {
+ "nexthop":"192.168.3.2",
+ "valid":true
+ },
+ {
+ "nexthop":"192.168.1.2",
+ "valid":true
+ }
+ ]
+ },
+ {
+ "id":10002,
+ "name":"C",
+ "valid":true,
+ "installed":true,
+ "nexthops":[
+ {
+ "nexthop":"192.168.1.44",
+ "valid":true
+ }
+ ]
+ },
+ {
+ "id":10001,
+ "name":"B",
+ "valid":false,
+ "installed":false,
+ "nexthops":[
+ {
+ "nexthop":"192.168.50.1",
+ "valid":false
+ }
+ ]
+ },
+ {
+ "id":10003,
+ "name":"EVA5",
+ "valid":true,
+ "installed":true,
+ "nexthops":[
+ {
+ "nexthop":"192.168.1.5",
+ "valid":true
+ }
+ ]
+ }
+]
diff --git a/tests/topotests/pbr-topo1/r1/pbrd.conf b/tests/topotests/pbr-topo1/r1/pbrd.conf
new file mode 100644
index 0000000000..234683f307
--- /dev/null
+++ b/tests/topotests/pbr-topo1/r1/pbrd.conf
@@ -0,0 +1,33 @@
+nexthop-group A
+ nexthop 192.168.1.2
+ nexthop 192.168.2.2
+ nexthop 192.168.3.2
+ nexhtop 192.168.4.2
+!
+# This one is bogus and should
+# never work
+nexthop-group B
+ nexthop 192.168.50.1
+!
+nexthop-group C
+ nexthop 192.168.1.44
+!
+pbr-map EVA seq 5
+ match src-ip 4.5.6.7/32
+ set nexthop 192.168.1.5
+!
+pbr-map EVA seq 10
+ match dst-ip 9.9.9.9/32
+ set nexthop-group A
+!
+pbr-map DONNA seq 5
+ match dst-ip 3.4.5.0/24
+ match src-ip 1.2.0.0/16
+ set nexthop-group C
+!
+
+int r1-eth1
+ pbr-policy EVA
+!
+int r1-eth2
+ pbr-policy DONNA
diff --git a/tests/topotests/pbr-topo1/r1/zebra.conf b/tests/topotests/pbr-topo1/r1/zebra.conf
new file mode 100644
index 0000000000..f29b146a62
--- /dev/null
+++ b/tests/topotests/pbr-topo1/r1/zebra.conf
@@ -0,0 +1,11 @@
+int r1-eth0
+ ip address 192.168.1.1/24
+
+int r1-eth1
+ ip address 192.168.2.1/24
+
+int r1-eth2
+ ip address 192.168.3.1/24
+
+int r1-eth3
+ ip address 192.168.4.1/24
diff --git a/tests/topotests/pbr-topo1/test_pbr_topo1.py b/tests/topotests/pbr-topo1/test_pbr_topo1.py
new file mode 100755
index 0000000000..2853165d45
--- /dev/null
+++ b/tests/topotests/pbr-topo1/test_pbr_topo1.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+
+#
+# test_pbr_topo1.py
+#
+# Copyright (c) 2020 by
+# Cumulus Networks, Inc.
+# Donald Sharp
+#
+# Permission to use, copy, modify, and/or distribute this software
+# for any purpose with or without fee is hereby granted, provided
+# that the above copyright notice and this permission notice appear
+# in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+#
+
+"""
+test_pbr_topo1.py: Testing PBR
+
+"""
+
+import os
+import re
+import sys
+import pytest
+import json
+
+# Save the Current Working Directory to find configuration files.
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+# Import topogen and topotest helpers
+from lib import topotest
+from lib.topogen import Topogen, TopoRouter, get_topogen
+from lib.topolog import logger
+
+# Required to instantiate the topology builder class.
+from mininet.topo import Topo
+
+#####################################################
+##
+## Network Topology Definition
+##
+#####################################################
+
+
+class NetworkTopo(Topo):
+ "PBR Topology 1"
+
+ def build(self, **_opts):
+ "Build function"
+
+ tgen = get_topogen(self)
+
+ for routern in range(1, 2):
+ tgen.add_router("r{}".format(routern))
+
+ # On main router
+ # First switch is for a dummy interface (for local network)
+ switch = tgen.add_switch("sw1")
+ switch.add_link(tgen.gears["r1"])
+
+ # Switches for PBR
+ # switch 2 switch is for connection to PBR router
+ switch = tgen.add_switch("sw2")
+ switch.add_link(tgen.gears["r1"])
+
+ # switch 4 is stub on remote PBR router
+ switch = tgen.add_switch("sw4")
+ switch.add_link(tgen.gears["r1"])
+
+ # switch 3 is between PBR routers
+ switch = tgen.add_switch("sw3")
+ switch.add_link(tgen.gears["r1"])
+
+
+#####################################################
+##
+## Tests starting
+##
+#####################################################
+
+
+def setup_module(module):
+ "Setup topology"
+ tgen = Topogen(NetworkTopo, module.__name__)
+ tgen.start_topology()
+
+ # This is a sample of configuration loading.
+ router_list = tgen.routers()
+ for rname, router in router_list.iteritems():
+ router.load_config(
+ TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
+ )
+ router.load_config(
+ TopoRouter.RD_PBRD, os.path.join(CWD, "{}/pbrd.conf".format(rname))
+ )
+
+ tgen.start_router()
+ #gen.mininet_cli()
+
+def teardown_module(_mod):
+ "Teardown the pytest environment"
+ tgen = get_topogen()
+
+ # This function tears down the whole topology.
+ tgen.stop_topology()
+
+
+def test_converge_protocols():
+ "Wait for protocol convergence"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ topotest.sleep(5, "Waiting for PBR convergence")
+
+
+def test_pbr_data():
+ "Test PBR 'show ip eigrp'"
+
+ tgen = get_topogen()
+ # Don't run this test if we have any failure.
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ # Verify PBR Status
+ logger.info("Verifying PBR routes")
+
+ router_list = tgen.routers().values()
+ for router in router_list:
+ intf_file = "{}/{}/pbr-interface.json".format(CWD, router.name)
+
+ logger.info(intf_file)
+ # Read expected result from file
+ expected = json.loads(open(intf_file).read())
+
+ # Actual output from router
+ actual = router.vtysh_cmd("show pbr interface json", isjson=True)
+
+ assertmsg = '"show pbr interface" mismatches on {}'.format(router.name)
+ assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+ map_file = "{}/{}/pbr-map.json".format(CWD, router.name)
+ logger.info(map_file)
+ # Read expected result from file
+ expected = json.loads(open(map_file).read())
+
+ # Actual output from router
+ actual = router.vtysh_cmd("show pbr map json", isjson=True)
+
+ assertmsg = '"show pbr map" mismatches on {}'.format(router.name)
+ assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+ nexthop_file = "{}/{}/pbr-nexthop-groups.json".format(CWD, router.name)
+
+ # Read expected result from file
+ expected = json.loads(open(nexthop_file).read())
+
+ # Actual output from router
+ actual = router.vtysh_cmd("show pbr nexthop-groups json", isjson=True)
+
+ assertmsg = '"show pbr nexthop-groups" mismatches on {}'.format(router.name)
+ assert topotest.json_cmp(actual, expected) is None, assertmsg
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
+